Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 46 additions & 1 deletion eng/packages/http-client-csharp-mgmt/eng/scripts/Generate.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ param(
)

Import-Module "$PSScriptRoot\Generation.psm1" -DisableNameChecking -Force;
Import-Module "$PSScriptRoot\Spector-Helper.psm1" -DisableNameChecking -Force;

$mgmtPackageRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '..')
Write-Host "Mgmt Package root: $packageRoot" -ForegroundColor Cyan
Write-Host "Mgmt Package root: $mgmtPackageRoot" -ForegroundColor Cyan
$mgmtSolutionDir = Join-Path $mgmtPackageRoot 'generator'

if (-not $LaunchOnly) {
Expand Down Expand Up @@ -38,6 +39,43 @@ if (-not $LaunchOnly) {
}
}

$spectorRoot = Join-Path $mgmtPackageRoot 'generator' 'TestProjects' 'Spector'

$spectorLaunchProjects = @{}

foreach ($specFile in Get-Sorted-Specs) {
$subPath = Get-SubPath $specFile
$folders = $subPath.Split([System.IO.Path]::DirectorySeparatorChar)

if (-not (Compare-Paths $subPath $filter)) {
continue
}

$generationDir = $spectorRoot
foreach ($folder in $folders) {
$generationDir = Join-Path $generationDir $folder
}

# create the directory if it doesn't exist
if (-not (Test-Path $generationDir)) {
New-Item -ItemType Directory -Path $generationDir | Out-Null
}

Write-Host "Generating $subPath" -ForegroundColor Cyan

$spectorLaunchProjects.Add(($folders -join "-"), ("TestProjects/Spector/$($subPath.Replace([System.IO.Path]::DirectorySeparatorChar, '/'))"))
if ($LaunchOnly) {
continue
}

Invoke (Get-Mgmt-TspCommand $specFile $generationDir -debug:$Debug)

# exit if the generation failed
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
}

# only write new launch settings if no filter was passed in
if ($null -eq $filter) {
$mgmtSpec = "TestProjects/Local/Mgmt-TypeSpec"
Expand All @@ -50,6 +88,13 @@ if ($null -eq $filter) {
$mgmtLaunchSettings["profiles"]["Mgmt-TypeSpec"].Add("commandName", "Executable")
$mgmtLaunchSettings["profiles"]["Mgmt-TypeSpec"].Add("executablePath", "dotnet")

foreach ($kvp in $spectorLaunchProjects.GetEnumerator()) {
$mgmtLaunchSettings["profiles"].Add($kvp.Key, @{})
$mgmtLaunchSettings["profiles"][$kvp.Key].Add("commandLineArgs", "`$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll `$(SolutionDir)/$($kvp.Value) -g AzureStubGenerator")
$mgmtLaunchSettings["profiles"][$kvp.Key].Add("commandName", "Executable")
$mgmtLaunchSettings["profiles"][$kvp.Key].Add("executablePath", "dotnet")
}

$mgmtSortedLaunchSettings = @{}
$mgmtSortedLaunchSettings.Add("profiles", [ordered]@{})
$mgmtLaunchSettings["profiles"].Keys | Sort-Object | ForEach-Object {
Expand Down
115 changes: 115 additions & 0 deletions eng/packages/http-client-csharp-mgmt/eng/scripts/Spector-Helper.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot '..')

# These are specs that are not yet building correctly with the management generator
# Add specs here as needed when they fail to build
$failingSpecs = @(
# method-subscription-id: Skipped due to "Some file paths are too long" error in CI
"http/azure/resource-manager/method-subscription-id"
)

function Capitalize-FirstLetter {
param (
[string]$inputString
)

if ([string]::IsNullOrEmpty($inputString)) {
return $inputString
}

$firstChar = $inputString[0].ToString().ToUpper()
$restOfString = $inputString.Substring(1)

return $firstChar + $restOfString
}

function Get-Namespace {
param (
[string]$dir
)

$words = $dir.Split('-')
$namespace = ""
foreach ($word in $words) {
$namespace += Capitalize-FirstLetter $word
}
return $namespace
}

function IsValidSpecDir {
param (
[string]$fullPath
)
if (-not(Test-Path "$fullPath/main.tsp")){
return $false;
}

$subPath = Get-SubPath $fullPath

if ($failingSpecs.Contains($subPath)) {
Write-Host "Skipping $subPath" -ForegroundColor Yellow
return $false
}

return $true
}

function Get-Azure-Specs-Directory {
$packageRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '..')
return Join-Path $packageRoot 'node_modules' '@azure-tools' 'azure-http-specs'
}

function Get-Sorted-Specs {
$azureSpecsDirectory = Get-Azure-Specs-Directory

# Only get azure resource-manager specs
$resourceManagerPath = Join-Path $azureSpecsDirectory "specs" "azure" "resource-manager"
$directories = @(Get-ChildItem -Path $resourceManagerPath -Directory -Recurse)

$sep = [System.IO.Path]::DirectorySeparatorChar
$pattern = "${sep}specs${sep}"

return $directories | Where-Object { IsValidSpecDir $_.FullName } | ForEach-Object {

# Pick client.tsp if it exists, otherwise main.tsp
$specFile = Join-Path $_.FullName "client.tsp"
if (-not (Test-Path $specFile)) {
$specFile = Join-Path $_.FullName "main.tsp"
}

# Extract the relative path after "specs/" and normalize slashes
$relativePath = ($specFile -replace '[\\\/]', '/').Substring($_.FullName.IndexOf($pattern) + $pattern.Length)
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using IndexOf without checking if the pattern exists first could result in -1 + pattern.Length if pattern is not found, causing an invalid substring index.

Suggested change
$relativePath = ($specFile -replace '[\\\/]', '/').Substring($_.FullName.IndexOf($pattern) + $pattern.Length)
$normalizedFullName = $_.FullName -replace '[\\\/]', '/'
$patternIndex = $normalizedFullName.IndexOf($pattern)
if ($patternIndex -ge 0) {
$relativePath = ($specFile -replace '[\\\/]', '/').Substring($patternIndex + $pattern.Length)
} else {
Write-Host "Warning: Pattern '$pattern' not found in '$normalizedFullName'. Skipping this item." -ForegroundColor Yellow
$relativePath = ""
}

Copilot uses AI. Check for mistakes.

# Remove the filename to get just the directory path
$dirPath = $relativePath -replace '/[^/]+\.tsp$', ''

# Produce an object with the path for sorting
[PSCustomObject]@{
SpecFile = $specFile
DirPath = $dirPath
}
} | Sort-Object -Property @{Expression = { $_.DirPath -replace '/', '!' }; Ascending = $true} | ForEach-Object { $_.SpecFile }
}

function Get-SubPath {
param (
[string]$fullPath
)
$azureSpecsDirectory = Get-Azure-Specs-Directory

$subPath = $fullPath.Substring($azureSpecsDirectory.Length + 1)

# Keep consistent with the previous folder name because 'http' makes more sense then current 'specs'
$subPath = $subPath -replace '^specs', 'http'

# also strip off the spec file name if present
$leaf = Split-Path -Leaf $subPath
if ($leaf -like '*.tsp') {
return (Split-Path $subPath)
}

return $subPath
}

Export-ModuleMember -Function "Get-Namespace"
Export-ModuleMember -Function "Get-Sorted-Specs"
Export-ModuleMember -Function "Get-SubPath"
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#Requires -Version 7.0

param($filter)

Import-Module "$PSScriptRoot\Generation.psm1" -DisableNameChecking -Force;
Import-Module "$PSScriptRoot\Spector-Helper.psm1" -DisableNameChecking -Force;

$packageRoot = Resolve-Path (Join-Path $PSScriptRoot '..' '..')

Refresh-Mgmt-Build

$spectorRoot = Join-Path $packageRoot 'generator' 'TestProjects' 'Spector'
$spectorCsproj = Join-Path $packageRoot 'generator' 'TestProjects' 'Spector.Tests' 'Azure.Generator.Spector.Tests.csproj'

$coverageDir = Join-Path $packageRoot 'generator' 'artifacts' 'coverage'

if (-not (Test-Path $coverageDir)) {
New-Item -ItemType Directory -Path $coverageDir | Out-Null
}

foreach ($specFile in Get-Sorted-Specs) {
$subPath = Get-SubPath $specFile

# skip the HTTP root folder when computing the namespace filter
$folders = $subPath.Split([System.IO.Path]::DirectorySeparatorChar) | Select-Object -Skip 1

if (-not (Compare-Paths $subPath $filter)) {
continue
}

$testPath = Join-Path "$spectorRoot.Tests" "Http"
$testFilter = "TestProjects.Spector.Tests.Http"
foreach ($folder in $folders) {
$segment = "$(Get-Namespace $folder)"

# the test directory names match the test namespace names, but the source directory names will not have the leading underscore
# so check to see if the filter should contain a leading underscore by comparing with the test directory
if (-not (Test-Path (Join-Path $testPath $segment))) {
$testFilter += "._$segment"
$testPath = Join-Path $testPath "_$segment"
}
else{
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Missing space before opening brace on line 42. Should be } else { for consistency with PowerShell style guidelines.

Suggested change
else{
else {

Copilot uses AI. Check for mistakes.
$testFilter += ".$segment"
$testPath = Join-Path $testPath $segment
}
}

Write-Host "Regenerating $subPath" -ForegroundColor Cyan

$outputDir = Join-Path $spectorRoot $subPath

$command = Get-Mgmt-TspCommand $specFile $outputDir
Invoke $command

# exit if the generation failed
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}

Write-Host "Testing $subPath" -ForegroundColor Cyan
$command = "dotnet test $spectorCsproj --filter `"FullyQualifiedName~$testFilter`""
Invoke $command
# exit if the testing failed
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}

Write-Host "Restoring $subPath" -ForegroundColor Cyan

$command = "git clean -xfd $outputDir"
Invoke $command
# exit if the restore failed
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$command = "git restore $outputDir"
Invoke $command
# exit if the restore failed
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
{
"profiles": {
"http-azure-resource-manager-common-properties": {
"commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll $(SolutionDir)/TestProjects/Spector/http/azure/resource-manager/common-properties -g AzureStubGenerator",
"commandName": "Executable",
"executablePath": "dotnet"
},
"http-azure-resource-manager-large-header": {
"commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll $(SolutionDir)/TestProjects/Spector/http/azure/resource-manager/large-header -g AzureStubGenerator",
"commandName": "Executable",
"executablePath": "dotnet"
},
"http-azure-resource-manager-non-resource": {
"commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll $(SolutionDir)/TestProjects/Spector/http/azure/resource-manager/non-resource -g AzureStubGenerator",
"commandName": "Executable",
"executablePath": "dotnet"
},
"http-azure-resource-manager-operation-templates": {
"commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll $(SolutionDir)/TestProjects/Spector/http/azure/resource-manager/operation-templates -g AzureStubGenerator",
"commandName": "Executable",
"executablePath": "dotnet"
},
"http-azure-resource-manager-resources": {
"commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll $(SolutionDir)/TestProjects/Spector/http/azure/resource-manager/resources -g AzureStubGenerator",
"commandName": "Executable",
"executablePath": "dotnet"
},
"Mgmt-TypeSpec": {
"commandLineArgs": "$(SolutionDir)/../dist/generator/Microsoft.TypeSpec.Generator.dll $(SolutionDir)/TestProjects/Local/Mgmt-TypeSpec -g MgmtClientGenerator",
"commandName": "Executable",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net9.0</TargetFrameworks>
<RequiredTargetFrameworks>net9.0</RequiredTargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" />
<PackageReference Include="NUnit" />
<PackageReference Include="NUnit3TestAdapter" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Azure.Generator.Management\test\Common\Azure.Generator.Management.Tests.Common.csproj" />
<!-- Some generated projects have build issues - commenting out for now -->
<!-- <ProjectReference Include="..\Spector\http\azure\resource-manager\common-properties\src\Azure.ResourceManager.CommonProperties.csproj" /> -->
<ProjectReference Include="..\Spector\http\azure\resource-manager\large-header\src\Azure.ResourceManager.LargeHeader.csproj" />
<!-- method-subscription-id: Skipped due to "Some file paths are too long" error in CI -->
<!-- <ProjectReference Include="..\Spector\http\azure\resource-manager\method-subscription-id\src\Azure.ResourceManager.MethodSubscriptionId.csproj" /> -->
<!-- <ProjectReference Include="..\Spector\http\azure\resource-manager\non-resource\src\Azure.ResourceManager.NonResource.csproj" /> -->
<ProjectReference Include="..\Spector\http\azure\resource-manager\operation-templates\src\Azure.ResourceManager.OperationTemplates.csproj" />
<!-- <ProjectReference Include="..\Spector\http\azure\resource-manager\resources\src\Azure.ResourceManager.Resources.csproj" /> -->
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="TestProjects.Spector.Tests.BuildProperties">
<_Parameter1>$(RepoRoot)</_Parameter1>
<_Parameter2>$(RepoRoot)\eng\packages\http-client-csharp-mgmt\generator\artifacts</_Parameter2>
</AssemblyAttribute>
</ItemGroup>

<!-- Include shared code from Azure.Core -->
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\AzureKeyCredentialPolicy.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\RawRequestUriBuilder.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\AppContextSwitchHelper.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\ClientDiagnostics.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\DiagnosticScopeFactory.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\DiagnosticScope.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\HttpMessageSanitizer.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core.TestFramework\src\ProcessTracker.cs" LinkBase="Shared/TestFramework" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\TypeFormatters.cs" LinkBase="Shared/Core" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\..\..\..\sdk\core\Azure.Core\src\Shared\RequestHeaderExtensions.cs" LinkBase="Shared/Core" />
</ItemGroup>

<ItemGroup>
<None Update="Http\**\TestData\**\*.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
Loading