Skip to content

Commit dec945a

Browse files
fix(wrapper-generator): align wrapper packaging projects
1 parent 1b6f282 commit dec945a

3 files changed

Lines changed: 108 additions & 47 deletions

File tree

‎tools/Build-WrapperModule.ps1‎

Lines changed: 62 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ For each module name, reproduces the pipeline the Mail spike proved:
88
99
1. kiota generate -> <out>/<Module>/src/Client (ApiClient + models)
1010
2. WrapperGenerator -> <out>/<Module>/src/Cmdlets (one *.g.cs per cmdlet)
11-
3. write csproj -> <out>/<Module>/src/
12-
4. dotnet build -> <out>/<Module>/src/bin/<Configuration>/net10.0/
13-
5. New-ModuleManifest -> <ModuleName>.psd1 next to the dll
11+
3. write client project -> <out>/<Module>/src/Client/Client.csproj
12+
4. write wrapper project -> <out>/<Module>/src/<ModuleName>.csproj
13+
5. dotnet build -> <out>/<Module>/src/bin/<Configuration>/net10.0/
14+
6. New-ModuleManifest -> <ModuleName>.psd1 next to the dll
1415
1516
Both generators consume the SAME OpenAPI document, so the wrappers always match the client
1617
they compile against.
@@ -76,26 +77,55 @@ if (-not $SpecRoot) { $SpecRoot = Join-Path $repoRoot 'openApiDocs_KiotaCompat'
7677
if (-not $OutputRoot) { $OutputRoot = Join-Path $repoRoot 'artifacts\wrapper-modules' }
7778
$generatorProject = Join-Path $repoRoot 'tools\WrapperGenerator'
7879
$authCsproj = Join-Path $repoRoot 'src\Authentication\Authentication\Microsoft.Graph.Authentication.csproj'
80+
$clientProjectTemplate = Join-Path $PSScriptRoot 'Templates\WrapperClient.csproj.template'
81+
$moduleProjectTemplate = Join-Path $PSScriptRoot 'Templates\WrapperModule.csproj.template'
7982

8083
if (-not (Get-Command kiota -ErrorAction SilentlyContinue)) {
8184
Write-Error "kiota CLI not found on PATH. Install: dotnet tool install --global Microsoft.OpenApi.Kiota"
8285
exit 1
8386
}
8487

85-
# Same extraction the parity gate uses: the emitted [Cmdlet(VerbsX.Verb, "Noun")] attribute
86-
# is the source of truth for what the dll will export, without having to load the assembly.
87-
$cmdletAttrPattern = '\[Cmdlet\(Verbs\w+\.(\w+),\s*"((?:\\.|[^"\\])*)"'
88-
function Get-EmittedCmdletNames {
89-
param([string]$CmdletsDir)
90-
Get-ChildItem -Path $CmdletsDir -Filter '*.g.cs' -File | ForEach-Object {
91-
$match = [regex]::Match((Get-Content -Path $_.FullName -Raw), $cmdletAttrPattern)
92-
if ($match.Success) {
93-
"$($match.Groups[1].Value)-$([regex]::Unescape($match.Groups[2].Value))"
94-
}
88+
function New-ProjectFromTemplate {
89+
param(
90+
[Parameter(Mandatory)][string]$TemplatePath,
91+
[Parameter(Mandatory)][string]$DestinationPath,
92+
[Parameter(Mandatory)][hashtable]$Replacements
93+
)
94+
95+
$content = Get-Content -Path $TemplatePath -Raw
96+
foreach ($placeholder in $Replacements.Keys) {
97+
$content = $content.Replace("{$placeholder}", $Replacements[$placeholder])
98+
}
99+
$unresolved = [regex]::Matches($content, '\{[A-Za-z][A-Za-z0-9]*\}') | ForEach-Object Value | Sort-Object -Unique
100+
if ($unresolved) {
101+
throw "unresolved placeholder(s) in $TemplatePath`: $($unresolved -join ', ')"
102+
}
103+
Set-Content -Path $DestinationPath -Value $content -Encoding utf8
104+
}
105+
106+
function Get-CompiledCmdletNames {
107+
param([Parameter(Mandatory)][string]$AssemblyPath)
108+
109+
# Import in a child process so discovery observes the compiled binary PowerShell will load,
110+
# and so assemblies from one module cannot contaminate or lock the next module's build.
111+
$escapedAssemblyPath = $AssemblyPath.Replace("'", "''")
112+
$discovery = @"
113+
`$ErrorActionPreference = 'Stop'
114+
`$module = Import-Module -Name '$escapedAssemblyPath' -PassThru
115+
[pscustomobject]@{ Cmdlets = @(`$module.ExportedCmdlets.Keys | Sort-Object) } |
116+
ConvertTo-Json -Compress
117+
"@
118+
$encodedDiscovery = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($discovery))
119+
$output = & pwsh -NoProfile -NonInteractive -EncodedCommand $encodedDiscovery 2>&1
120+
if ($LASTEXITCODE -ne 0) {
121+
throw "compiled module discovery failed: $(($output | Select-Object -Last 3) -join ' | ')"
95122
}
123+
$json = $output | Where-Object { $_ -match '^\{' } | Select-Object -Last 1
124+
if (-not $json) { throw 'compiled module discovery produced no result' }
125+
@((ConvertFrom-Json $json).Cmdlets)
96126
}
97127

98-
function Build-OneModule {
128+
function Build-Module {
99129
param([string]$Name)
100130

101131
$started = Get-Date
@@ -157,38 +187,22 @@ function Build-OneModule {
157187
return $result
158188
}
159189

160-
# Relative to $srcDir rather than the absolute $authCsproj, so the csproj is portable
161-
# across clones and stays correct if a module's output folder ever moves (the eventual
162-
# src/<Module>/<ApiVersion>/ commit target sits at a different depth than
163-
# artifacts/wrapper-modules/<Module>/src/).
190+
$clientAssemblyName = "$moduleName.Client"
191+
$clientCsprojPath = Join-Path $clientDir 'Client.csproj'
192+
New-ProjectFromTemplate -TemplatePath $clientProjectTemplate -DestinationPath $clientCsprojPath -Replacements @{
193+
ClientAssemblyName = $clientAssemblyName
194+
}
195+
196+
# Project references are relative so generated projects remain portable across clones
197+
# and across the artifacts and eventual src/<Module>/<ApiVersion>/wrapper layouts.
164198
$csprojPath = Join-Path $srcDir "$moduleName.csproj"
165199
$authCsprojRelative = [System.IO.Path]::GetRelativePath($srcDir, $authCsproj) -replace '/', '\'
166-
@"
167-
<!-- Generated by tools/Build-WrapperModule.ps1 - do not edit or commit. -->
168-
<Project Sdk="Microsoft.NET.Sdk">
169-
170-
<PropertyGroup>
171-
<TargetFramework>net10.0</TargetFramework>
172-
<LangVersion>latest</LangVersion>
173-
<Nullable>enable</Nullable>
174-
<ImplicitUsings>enable</ImplicitUsings>
175-
<AssemblyName>$moduleName</AssemblyName>
176-
<!-- Copy every dependency next to the module dll so Import-Module resolves them. -->
177-
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
178-
<NoWarn>`$(NoWarn);CS1591</NoWarn>
179-
</PropertyGroup>
180-
181-
<ItemGroup>
182-
<PackageReference Include="Microsoft.Kiota.Bundle" Version="2.0.0" />
183-
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="all" />
184-
</ItemGroup>
185-
186-
<ItemGroup>
187-
<ProjectReference Include="$authCsprojRelative" />
188-
</ItemGroup>
189-
190-
</Project>
191-
"@ | Set-Content -Path $csprojPath -Encoding utf8
200+
$clientCsprojRelative = [System.IO.Path]::GetRelativePath($srcDir, $clientCsprojPath) -replace '/', '\'
201+
New-ProjectFromTemplate -TemplatePath $moduleProjectTemplate -DestinationPath $csprojPath -Replacements @{
202+
ModuleAssemblyName = $moduleName
203+
ClientProjectPath = $clientCsprojRelative
204+
AuthenticationProjectPath = $authCsprojRelative
205+
}
192206

193207
$buildOut = & dotnet build $csprojPath -c $Configuration --nologo -v minimal 2>&1
194208
if ($LASTEXITCODE -ne 0) {
@@ -197,10 +211,11 @@ function Build-OneModule {
197211
return $result
198212
}
199213

200-
$cmdlets = @(Get-EmittedCmdletNames -CmdletsDir $cmdletsDir)
214+
$binDir = Join-Path $srcDir "bin\$Configuration\net10.0"
215+
$assemblyPath = Join-Path $binDir "$moduleName.dll"
216+
$cmdlets = @(Get-CompiledCmdletNames -AssemblyPath $assemblyPath)
201217
if ($cmdlets.Count -eq 0) { $result.FailedAt = 'manifest'; $result.Error = 'no cmdlets emitted'; return $result }
202218

203-
$binDir = Join-Path $srcDir "bin\$Configuration\net10.0"
204219
$psd1Path = Join-Path $binDir "$moduleName.psd1"
205220
New-ModuleManifest -Path $psd1Path `
206221
-RootModule "$moduleName.dll" `
@@ -227,7 +242,7 @@ function Build-OneModule {
227242

228243
$results = foreach ($name in $Module) {
229244
Write-Host "=== $name ===" -ForegroundColor Cyan
230-
$r = Build-OneModule -Name $name
245+
$r = Build-Module -Name $name
231246
if ($r.Status -eq 'OK') {
232247
Write-Host " OK: $($r.CmdletCount) cmdlets -> $($r.Psd1) ($($r.Seconds)s)" -ForegroundColor Green
233248
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!-- Generated by tools/Build-WrapperModule.ps1. Regenerate rather than edit by hand. -->
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
4+
<PropertyGroup>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<LangVersion>latest</LangVersion>
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<AssemblyName>{ClientAssemblyName}</AssemblyName>
10+
<NoWarn>$(NoWarn);CS1591</NoWarn>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<PackageReference Include="Microsoft.Kiota.Bundle" Version="2.0.0" />
15+
</ItemGroup>
16+
17+
</Project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!-- Generated by tools/Build-WrapperModule.ps1. Regenerate rather than edit by hand. -->
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
4+
<PropertyGroup>
5+
<TargetFramework>net10.0</TargetFramework>
6+
<LangVersion>latest</LangVersion>
7+
<Nullable>enable</Nullable>
8+
<ImplicitUsings>enable</ImplicitUsings>
9+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
10+
<AssemblyName>{ModuleAssemblyName}</AssemblyName>
11+
<!-- Copy every dependency next to the module dll so Import-Module resolves them. -->
12+
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
13+
<NoWarn>$(NoWarn);CS1591</NoWarn>
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<Compile Include="Cmdlets\**\*.g.cs" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="all" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<ProjectReference Include="{ClientProjectPath}" />
26+
<ProjectReference Include="{AuthenticationProjectPath}" />
27+
</ItemGroup>
28+
29+
</Project>

0 commit comments

Comments
 (0)