Skip to content

Commit

Permalink
Improve tooling and code (#25)
Browse files Browse the repository at this point in the history
* Package Powershell module during build
* Verify that all Cmdlets are exposed in the manifest as part of the build process
* Use common MSBuild properties and complete package metadata
  • Loading branch information
ChristopherMann authored Nov 21, 2024
1 parent e44057c commit 24ac8f4
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 75 deletions.
27 changes: 27 additions & 0 deletions build/Clean-PsModule.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#Requires -Version 7.4
<#
.SYNOPSIS
Remove the Powershell module from the build output.
.DESCRIPTION
This script removes the Powershell module from the build output.
It is intended to be called by MSBuild during the normal clean process.
The script might be called once for each target framework.
#>
[CmdletBinding()]
param(
[Parameter()]
[string]
[ValidateNotNullOrEmpty()]
$OutputDirectory
)

$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'

$modulePath = Join-Path $OutputDirectory "PsModule"

if (Test-Path $modulePath) {
# This script might be called in parallel for each target framework.
# Hence, we ignore errors as files might have been removed by another instance.
Remove-Item -Path $modulePath -Force -Recurse -ErrorAction SilentlyContinue
}
12 changes: 10 additions & 2 deletions build/Eryph.IdentityClient.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ ClrVersion = '4.0'
# ProcessorArchitecture = ''

# Modules that must be imported into the global environment prior to importing this module
RequiredModules = @(@{ModuleName="Eryph.ClientRuntime.Configuration"; ModuleVersion="0.1.0"; GUID="31a5834e-973e-478f-a48d-cea5f1e92962"})
RequiredModules = @(
@{ ModuleName="Eryph.ClientRuntime.Configuration"; ModuleVersion="0.8.1"; GUID="31a5834e-973e-478f-a48d-cea5f1e92962" }
)

# Assemblies that must be loaded prior to importing this module
# RequiredAssemblies = @()
Expand All @@ -72,7 +74,13 @@ RequiredModules = @(@{ModuleName="Eryph.ClientRuntime.Configuration"; ModuleVers
FunctionsToExport = @()

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @("Get-EryphClient", "New-EryphClient", "New-EryphClientKey", "Remove-EryphClient", "Set-EryphClient")
CmdletsToExport = @(
"Get-EryphClient",
"New-EryphClient",
"New-EryphClientKey",
"Remove-EryphClient",
"Set-EryphClient"
)

# Variables to export from this module
VariablesToExport = '*'
Expand Down
86 changes: 86 additions & 0 deletions build/Package-PsModule.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#Requires -Version 7.4
<#
.SYNOPSIS
Package the Powershell module.
.DESCRIPTION
This script packages the Powershell module for distribution.
It is intended to be called by MSBuild during the normal build
process. The script will be called once for each target framework.
#>
[CmdletBinding()]
param(
[Parameter()]
[string]
[ValidateScript({ $_ -match '[a-zA-Z\.]+' }, ErrorMessage = "The module name '{0}' is invalid.")]
$ModuleName,
[Parameter()]
[string]
[ValidateScript({ Test-Path $_ }, ErrorMessage = "The path '{0}' is invalid.")]
$TargetPath,
[Parameter()]
[string]
[ValidateScript({ $_ -match 'net\d+\.?\d+' }, ErrorMessage = "The target framework '{0}' is invalid.")]
$TargetFramework,
[Parameter()]
[string]
[ValidateScript({ Test-Path $_ }, ErrorMessage = "The path '{0}' is invalid.")]
$OutputDirectory,
[Parameter()]
[string]
[ValidateScript({ $_ -match '\d+\.\d+\.\d+' }, ErrorMessage = "The version '{0}' is invalid.")]
$MajorMinorPatch,
[Parameter()]
[string]
$NuGetPreReleaseTag
)

$PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'

$excludedFiles = @("System.Management.Automation.dll", "JetBrains.Annotations.dll")

$modulePath = Join-Path $OutputDirectory "PsModule" $ModuleName
$isWindowsPowershell = $TargetFramework -like 'net4*'
$moduleAssemblyPath = Join-Path $modulePath ($isWindowsPowershell ? 'desktop' : 'coreclr')

# Prepare the output directory
if (-not (Test-Path $modulePath)) {
$null = New-Item -ItemType Directory -Path $modulePath -ErrorAction SilentlyContinue
}

# Copy the build output
if (Test-Path $moduleAssemblyPath) {
Remove-Item -Path $moduleAssemblyPath -Force -Recurse
}
$null = New-Item -ItemType Directory -Path $moduleAssemblyPath
$targetDirectory = (Get-Item $TargetPath).Directory.FullName
Copy-Item -Path (Join-Path $targetDirectory "*") -Destination $moduleAssemblyPath -Exclude $excludedFiles -Recurse

# Prepare the module manifest
$config = Get-Content (Join-Path $PSScriptRoot "$ModuleName.psd1") -Raw
$config = $config.Replace("ModuleVersion = '0.1'", "ModuleVersion = '$MajorMinorPatch'");
if (-not [string]::IsNullOrWhiteSpace($NuGetPreReleaseTag)) {
$config = $config.Replace("# Prerelease = ''", "Prerelease = '$NuGetPreReleaseTag'");
}
Set-Content -Path (Join-Path $modulePath "$ModuleName.psd1") -Value $config
Copy-Item -Path (Join-Path $PSScriptRoot "$ModuleName.psm1") -Destination $modulePath

# This Powershell module requires the module Eryph.ClientRuntime.Configuration.
# We download that module first to ensure that it is available. Otherwise,
# the import during the test below would fail.
$configData = Import-PowerShellDataFile (Join-Path $modulePath "$ModuleName.psd1")
$clientRuntimeVersion = $configData.RequiredModules[0].ModuleVersion
$clientRuntimeModulePath = Join-Path $OutputDirectory "PsModuleDependencies" "Eryph.ClientRuntime.Configuration"
if (-not (Test-Path (Join-Path $clientRuntimeModulePath $clientRuntimeVersion))) {
Save-Module -Path (Join-Path $OutputDirectory "PsModuleDependencies") -Name 'Eryph.ClientRuntime.Configuration' -AllowPrerelease -Force
}

# Verify that all Cmdlets are exposed in the manifest. We must load the modules
# in separate Powershell processes to avoid conflicts.
$powershell = $isWindowsPowershell ? 'powershell.exe' : 'pwsh.exe'
$moduleCmdlets = (& $powershell -Command "Import-Module $clientRuntimeModulePath -RequiredVersion $clientRuntimeVersion; [array](Import-Module -Scope Local $modulePath -PassThru).ExportedCmdlets.Keys -join ','") -split ','
$assemblyCmdlets = (& $powershell -Command "[array](Import-Module -Scope Local $TargetPath -PassThru).ExportedCmdlets.Keys -join ','") -split ','
$missingCmdlets = [Linq.Enumerable]::Except($assemblyCmdlets, $moduleCmdlets)
if ($missingCmdlets.Count -gt 0) {
throw "The following Cmdlets are not exposed in the module manifest: $($missingCmdlets -join ', ')"
}
80 changes: 33 additions & 47 deletions build/build-cmdlet.ps1
Original file line number Diff line number Diff line change
@@ -1,49 +1,35 @@
param ($Configuration = "Debug", $OutputDir = ".")

$cmdletName = "Eryph.IdentityClient"
$excludedFiles = @("System.Management.Automation.dll", "JetBrains.Annotations.dll")

# If this script is not running on a build server, remind user to
# set environment variables so that this script can be debugged
if(-not ($Env:GITVERSION_MajorMinorPatch))
{
Write-Error "You must set the following environment variables"
Write-Error "to test this script interactively (values are examples)"
Write-Host '$Env:GITVERSION_MajorMinorPatch = "1.0.0"'
Write-Host '$Env:GITVERSION_NuGetPreReleaseTag = "ci0030"'
exit 1
}


Push-Location $PSScriptRoot
cd ..
$rootDir = Get-Location

Push-Location $OutputDir

if(Test-Path cmdlet ) {
rm cmdlet -Force -Recurse -ErrorAction Stop
#Requires -Version 7.4
<#
.SYNOPSIS
Prepare the Powershell module for publication.
.DESCRIPTION
This script moves the already built Powershell module to a location
where the release pipeline will pick it up for publication.
The name and output location of this script cannot be changed as
it would break the release pipeline.
#>
[CmdletBinding()]
param (
[Parameter()]
[string]
[ValidateNotNullOrEmpty()]
$Configuration,
[Parameter()]
[string]
[ValidateScript({ Test-Path $_ }, ErrorMessage = "The path '{0}' is invalid.")]
$OutputDir
)

$ErrorActionPreference = 'Stop'
$moduleName = "Eryph.IdentityClient"

$repositoryPath = Resolve-Path (Join-Path $PSScriptRoot "..")
$targetPath = Join-Path $OutputDir "cmdlet"

if (Test-Path $targetPath ) {
Remove-Item $targetPath -Force -Recurse
}
$null = New-Item -ItemType Directory $targetPath

mkdir cmdlet | Out-Null
cd cmdlet
mkdir ${cmdletName} | Out-Null
cd ${cmdletName}

mkdir coreclr | Out-Null
mkdir desktop | Out-Null

cp $rootDir\build\${cmdletName}* .
cp $rootDir\src\${cmdletName}.Commands\bin\${Configuration}\net6.0\* coreclr -Exclude $excludedFiles -Recurse
cp $rootDir\src\${cmdletName}.Commands\bin\${Configuration}\net462\* desktop -Exclude $excludedFiles -Recurse

$config = gc "${cmdletName}.psd1" -Raw
$config = $config.Replace("ModuleVersion = '0.1'", "ModuleVersion = '${Env:GITVERSION_MajorMinorPatch}'");

if(-not [string]::IsNullOrWhiteSpace($Env:GITVERSION_NuGetPreReleaseTag)) {
$config = $config.Replace("# Prerelease = ''", "Prerelease = '${Env:GITVERSION_NuGetPreReleaseTag}'");
}

$config | sc "${cmdletName}.psd1"

Pop-Location
$modulePath = Join-Path $repositoryPath "src" "$moduleName.Commands" "bin" $Configuration "PsModule"
Copy-Item $modulePath\* $targetPath -Recurse
4 changes: 2 additions & 2 deletions gen/generate_local.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ $PSNativeCommandUseErrorActionPreference = $true
$ErrorActionPreference = 'Stop'

# Update the version in the csproj when changing this
$autoRestCSharpVersion = "3.0.0-beta.20240527.2"
$autoRestCSharpVersion = "3.0.0-beta.20241108.1"

$settings = Get-Content -Raw -Path "$PSScriptRoot/config.json" | ConvertFrom-Json
$tag = $settings.tag
$spec = $settings.spec

npm exec --package="autorest@3.7.1" -- `
autorest `
--version="3.10.2" `
--version="3.10.3" `
--use="@autorest/csharp@$autoRestCSharpVersion" `
--use="@autorest/modelerfour@4.27.0" `
"$PSScriptRoot/../../eryph-api-spec/specification/$spec" `
Expand Down
37 changes: 37 additions & 0 deletions src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<Project>
<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://www.eryph.io</PackageProjectUrl>
<PackageReleaseNotes>https://github.com/eryph-org/dotnet-identityclient/releases</PackageReleaseNotes>
<Authors>dbosoft GmbH and Eryph contributors</Authors>
<Company>dbosoft GmbH</Company>
<Product>Eryph</Product>
<Copyright>dbosoft GmbH. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/eryph-org/dotnet-identityclient</RepositoryUrl>
<!-- Declare that the Repository URL can be published to NuSpec -->
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<!-- Embed source files that are not tracked by the source control manager to the PDB -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<!-- Include PDB in the built .nupkg -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<LangVersion>12</LangVersion>
<NoWarn>CS1591</NoWarn>
</PropertyGroup>

<PropertyGroup>
<ContinuousIntegrationBuild Condition="'$(TF_BUILD)' == 'true'">True</ContinuousIntegrationBuild>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">True</ContinuousIntegrationBuild>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="5.11.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All"/>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net462;net6.0</TargetFrameworks>
<TargetFrameworks>net462;net8.0</TargetFrameworks>
<IsPackable>false</IsPackable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<LangVersion>12</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Eryph.ClientRuntime.Powershell" Version="0.7.0" />
<PackageReference Include="GitVersion.MsBuild" Version="5.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Eryph.ClientRuntime.Powershell" Version="0.8.1-beta.1" />
<PackageReference Include="PowerShellStandard.Library" Version="5.1.1">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
Expand All @@ -22,4 +17,20 @@
<ProjectReference Include="..\Eryph.IdentityClient\Eryph.IdentityClient.csproj" />
</ItemGroup>

<!-- Custom properties and targets for packaging the Powershell module -->
<PropertyGroup>
<PsModuleName>Eryph.IdentityClient</PsModuleName>
<PowershellExecutable>pwsh.exe</PowershellExecutable>
</PropertyGroup>
<PropertyGroup Condition="$(TargetFramework.StartsWith('net4'))">
<PowershellExecutable>powershell.exe</PowershellExecutable>
</PropertyGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="pwsh.exe -NoProfile -File &quot;$(ProjectDir)../../build/Package-PsModule.ps1&quot; -ModuleName &quot;$(PsModuleName)&quot; -OutputDirectory &quot;$([System.IO.Path]::Combine($(ProjectDir), 'bin', $(Configuration)))&quot; -TargetPath &quot;$(TargetPath)&quot; -TargetFramework &quot;$(TargetFramework)&quot; -MajorMinorPatch &quot;$(GitVersion_MajorMinorPatch)&quot; -NuGetPreReleaseTag &quot;$(GitVersion_NuGetPreReleaseTag)&quot;" />
</Target>
<Target Name="PostClean" AfterTargets="Clean">
<Exec Command="pwsh.exe -NoProfile -File &quot;$(ProjectDir)../../build/Clean-PsModule.ps1&quot; -OutputDirectory &quot;$([System.IO.Path]::Combine($(ProjectDir), 'bin', $(Configuration)))&quot;" />
</Target>

</Project>
12 changes: 2 additions & 10 deletions src/Eryph.IdentityClient.Commands/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
{
"profiles": {
"Eryph.IdentityClient.Commands": {
"commandName": "Project"
},
"Run in Powershell": {
"commandName": "Executable",
"executablePath": "powershell.exe",
"commandLineArgs": "-NoProfile -NoExit -Command \"Import-Module $(TargetPath)\""
},
"Run in Powershell Core": {
"commandName": "Executable",
"executablePath": "pwsh.exe",
"commandLineArgs": "-NoProfile -NoExit -Command \"Import-Module $(TargetPath)\""
"executablePath": "$(PowershellExecutable)",
"commandLineArgs": "-NoProfile -NoExit -Command \"Import-Module '$(ProjectDir)bin/$(Configuration)/PsModule/$(PsModuleName)'\""
}
}
}
10 changes: 3 additions & 7 deletions src/Eryph.IdentityClient/Eryph.IdentityClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,16 @@
<TargetFramework>netstandard2.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>annotations</Nullable>
<description>Client library for the eryph identity API.</description>
</PropertyGroup>

<PropertyGroup>
<LangVersion>12</LangVersion>
<IncludeGeneratorSharedCode>true</IncludeGeneratorSharedCode>
<RestoreAdditionalProjectSources>https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-net/nuget/v3/index.json</RestoreAdditionalProjectSources>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Eryph.ClientRuntime.Authentication" Version="0.7.0" />
<PackageReference Include="GitVersion.MsBuild" Version="5.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Azure.AutoRest.CSharp" Version="3.0.0-beta.20240527.2" PrivateAssets="All" />
<PackageReference Include="Eryph.ClientRuntime.Authentication" Version="0.8.1-beta.1" />
<PackageReference Include="Microsoft.Azure.AutoRest.CSharp" Version="3.0.0-beta.20241108.1" PrivateAssets="All" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ public static RequestContent FromEnumerable(IEnumerable<BinaryData> enumerable)
return content;
}

public static RequestContent FromEnumerable<T>(ReadOnlySpan<T> span)
where T : notnull
{
Utf8JsonRequestContent content = new Utf8JsonRequestContent();
content.JsonWriter.WriteStartArray();
for (int i = 0; i < span.Length; i++)
{
content.JsonWriter.WriteObjectValue(span[i]);
}
content.JsonWriter.WriteEndArray();

return content;
}

public static RequestContent FromDictionary<TValue>(IDictionary<string, TValue> dictionary)
where TValue : notnull
{
Expand Down

0 comments on commit 24ac8f4

Please sign in to comment.