feat: add Cake build to produce powershell module (#2)

- Add Cake build project
- Change to CPM
- Package bumps for security bumps
- Reorganise solution structure to group harness projects
This commit is contained in:
Scott 2026-07-31 02:15:16 +00:00
commit ecb0a20cbb
27 changed files with 267 additions and 58 deletions

2
.gitignore vendored
View file

@ -7,4 +7,4 @@ riderModule.iml
/.idea
*.DotSettings.user
# Don't care about files created from the build script
PowershellModule/build/PowershellModule
output/

14
Directory.Packages.props Normal file
View file

@ -0,0 +1,14 @@
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="System.IO.Packaging" Version="10.0.10" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.10" />
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.6.4" />
<PackageVersion Include="PowerShellStandard.Library" Version="5.1.1" />
<PackageVersion Include="System.Management.Automation" Version="7.6.4" />
<PackageVersion Include="Cake.Frosting" Version="6.2.0" />
<PackageVersion Include="Cake.Powershell" Version="4.0.0" />
</ItemGroup>
</Project>

View file

@ -1,6 +1,17 @@
<Solution>
<Project Path="Harness/Harness.csproj" />
<Project Path="ModuleCore/ModuleCore.csproj" />
<Project Path="PowershellHarness/PowershellHarness.csproj" />
<Project Path="PowershellModule/PowershellModule.csproj" />
<Folder Name="/build/">
<Project Path="build/Build.csproj" />
</Folder>
<Folder Name="/harnesses/">
<Project Path="src/ModuleHarness/ModuleHarness.csproj" />
<Project Path="src/PowershellHarness/PowershellHarness.csproj" />
</Folder>
<Folder Name="/scripts/">
<File Path="build.ps1" />
</Folder>
<Folder Name="/solutionItems/">
<File Path="Directory.Packages.props" />
</Folder>
<Project Path="src/ModuleCore/ModuleCore.csproj" />
<Project Path="src/PowershellModule/PowershellModule.csproj" />
</Solution>

View file

@ -1,48 +0,0 @@
param (
[switch] $release
)
$wdir = $PSScriptRoot
$module = "PowershellModule"
$output = "$wdir/$module"
if (Test-Path $output)
{
Remove-Item $output -Recurse
}
New-Item $output -ItemType Directory
# copy the built dlls to this directory
if($release){
# For release we'll get a bunch of dependencies that we honestly do not need.
# This module is built to run assuming the dotnet 10 runtime exists, and powershell is 7.6 or higher,
# because of this we really don't need the system.* dlls that come with the publish, or the random ass
# newtonsoft.json.dll that comes from ???? publish fucking sucks for libraries even with framework-dependent
# selected as the publish option
Copy-Item -Path "$( $wdir )\..\bin\Release\net10.0\publish\$module.dll" -Destination $output
Copy-Item -Path "$( $wdir )\..\bin\Release\net10.0\publish\ModuleCore.dll" -Destination $output
} else
{
Copy-Item -Path "$( $wdir )\..\bin\Debug\net10.0\*.dll" -Destination $output
}
#Copy-Item -Path "$( $wdir )\..\bin\Debug\netstandard2.1\*.dll" -Destination $output
$manifestSplat = @{
Path = "$output/$module.psd1"
Author = 'Me'
# technically we only need the base module here and it'll locate any dlls it needs
NestedModules = @("$module.dll")#(Get-ChildItem "$output/*.dll" | Select-Object { $_.Name } -ExpandProperty Name)
RootModule = "$module.psm1"
# FunctionsToExport = @('Get-Calendar')
CmdletsToExport = @('Get-Calendar')
# These should all be empty arrays - otherwise they default to wildcard which the generated psd1 will helpfully
# tell you not to do for performance reasons, as the fallback if these are left out is to use wildcard...
FunctionsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
}
New-ModuleManifest @manifestSplat
# We don't actually need content here, it's just so PowerShell has an entry point
New-Item "$output/$module.psm1" -ItemType File

2
build.ps1 Normal file
View file

@ -0,0 +1,2 @@
dotnet run --project build/Build.csproj -- $args
exit $LASTEXITCODE;

1
build.sh Normal file
View file

@ -0,0 +1 @@
dotnet run --project ./build/Build.csproj -- "$@"

13
build/Build.csproj Normal file
View file

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake.Frosting"/>
<PackageReference Include="Cake.Powershell"/>
<PackageReference Include="System.IO.Packaging" />
<PackageReference Include="System.Security.Cryptography.Xml" />
</ItemGroup>
</Project>

55
build/Program.cs Normal file
View file

@ -0,0 +1,55 @@
using Cake.Common;
using Cake.Common.IO;
using Cake.Common.IO.Paths;
using Cake.Core;
using Cake.Frosting;
using Cake.Powershell;
public static class Program
{
public static int Main(string[] args)
{
return new CakeHost()
.UseContext<BuildContext>()
.Run(args);
}
}
public class BuildContext : FrostingContext
{
/// <summary>
/// Base source directory
/// </summary>
public ConvertableDirectoryPath BaseSourceLocation { get; set; }
/// <summary>
/// Powershell module project folder
/// </summary>
public ConvertableDirectoryPath PowershellModuleProjectDirectory { get; set; }
/// <summary>
/// Powershell module csproj location
/// </summary>
public ConvertableFilePath PowershellModuleCsproj { get; set; }
/// <summary>
/// Output directory for built DLLs and powershell module manifest files
/// </summary>
public ConvertableDirectoryPath PowershellModuleOutputDir { get; set; }
/// <summary>
/// Path to powershell script that creates the module manifest files
/// </summary>
public ConvertableFilePath CreateModuleManifestScript { get; set; }
public BuildContext(ICakeContext context)
: base(context)
{
BaseSourceLocation = context.Directory("../src");
PowershellModuleProjectDirectory = BaseSourceLocation + context.Directory("PowershellModule");
PowershellModuleCsproj = PowershellModuleProjectDirectory + context.File("PowershellModule.csproj");
PowershellModuleOutputDir = context.Directory("../") + context.Directory("output") + context.Directory("PowershellModule");
var buildScriptDirectory = context.Directory("./Scripts");
CreateModuleManifestScript = buildScriptDirectory + context.File("CreateModuleManifest.ps1");
}
}

View file

@ -0,0 +1,24 @@
param (
[string]$path,
[string]$guid,
[string]$author,
[string[]]$nestedModules,
[string]$rootModule,
[string[]]$cmdletsToExport,
[string]$manifestFileLocation
)
$manifestSplat = @{
Path = "$path"
GUID = "$guid"
Author = "$author"
NestedModules = @($nestedModules)
RootModule = "$rootModule"
CmdletsToExport = @($cmdletsToExport)
FunctionsToExport = @()
VariablesToExport = @()
AliasesToExport = @()
}
New-ModuleManifest @manifestSplat
New-Item "$manifestFileLocation" -ItemType File

36
build/Tasks/BuildTask.cs Normal file
View file

@ -0,0 +1,36 @@
using Cake.Common.Tools.DotNet;
using Cake.Common.Tools.DotNet.Build;
using Cake.Common.Tools.DotNet.MSBuild;
using Cake.Core.Diagnostics;
using Cake.Frosting;
namespace Build.Tasks;
[TaskName("Build")]
[IsDependentOn(typeof(CleanTask))]
[IsDependeeOf(typeof(CopyOutputTask))]
public class BuildTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
context.Log.Information($"Building: {context.PowershellModuleCsproj}");
var buildSettings = new DotNetBuildSettings()
{
MSBuildSettings = new DotNetMSBuildSettings()
{
VersionSuffix = "cake"
},
OutputDirectory = context.PowershellModuleOutputDir,
DiagnosticOutput = true
};
// /p:DebugSymbols=false
buildSettings.MSBuildSettings.Properties.Add("DebugSymbols", ["false"]);
// /p:DebugType=None
buildSettings.MSBuildSettings.Properties.Add("DebugType", ["None"]);
context.DotNetBuild(context.PowershellModuleCsproj, buildSettings);
base.Run(context);
}
}

23
build/Tasks/CleanTask.cs Normal file
View file

@ -0,0 +1,23 @@
using Cake.Common;
using Cake.Common.Tools.DotNet;
using Cake.Core.Diagnostics;
using Cake.Frosting;
namespace Build.Tasks;
[TaskName("Clean")]
public class CleanTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
context.DotNetClean(context.PowershellModuleProjectDirectory);
var outDir = context.FileSystem.GetDirectory(context.PowershellModuleOutputDir);
if (outDir.Exists)
{
outDir.Delete(true);
}
base.Run(context);
}
}

View file

@ -0,0 +1,49 @@
using System;
using System.Linq;
using Cake.Core.Diagnostics;
using Cake.Core.IO;
using Cake.Frosting;
using Cake.Powershell;
namespace Build.Tasks;
[TaskName("CopyOutput")]
public class CopyOutputTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
var powershellModuleName = "PowershellModule";
var scriptParams = new
{
Path = $"{context.PowershellModuleOutputDir}/{powershellModuleName}.psd1",
Guid = Guid.Parse("5cdf4635-edb0-428c-8d9b-92d0bcd47443"),
Author = "Me",
NestedModules = new[] { $"{powershellModuleName}.dll" },
RootModule = $"{powershellModuleName}.psm1",
CmdletsToExport = new[] { "Get-Calendar" },
ManifestFileLocation = $"{context.PowershellModuleOutputDir}/{powershellModuleName}.psm1"
};
var psSettings = new PowershellSettings()
{
Arguments = new ProcessArgumentBuilder(),
};
// This feels a bit ugly/redundant seeing as I've defined the scriptParams above, but I'm leaving it as is
// until I want to come back and clean this up properly
psSettings.Arguments.Append("path", ToPowershellSafeString(scriptParams.Path));
psSettings.Arguments.Append("guid", ToPowershellSafeString(scriptParams.Guid.ToString()));
psSettings.Arguments.Append("author", ToPowershellSafeString(scriptParams.Author));
psSettings.Arguments.Append("nestedModules", $"@({string.Join(",", scriptParams.NestedModules.Select(ToPowershellSafeString))})");
psSettings.Arguments.Append("rootModule", ToPowershellSafeString(scriptParams.RootModule));
psSettings.Arguments.Append("cmdletsToExport", $"@({string.Join(",", scriptParams.CmdletsToExport.Select(ToPowershellSafeString))})");
psSettings.Arguments.Append("manifestFileLocation", ToPowershellSafeString(scriptParams.ManifestFileLocation));
context.StartPowershellFile(context.CreateModuleManifestScript, psSettings);
context.Log.Information($"Module output to: {context.PowershellModuleOutputDir.Path.MakeAbsolute(context.Environment)}");
base.Run(context);
}
private static string ToPowershellSafeString(string unescapedString) => $"'{unescapedString}'";
}

View file

@ -0,0 +1,16 @@
using Cake.Core;
using Cake.Core.Diagnostics;
using Cake.Frosting;
namespace Build.Tasks;
// Consider this the "entry" point for builds, task order is defined by a chain of IsDependentOn
[TaskName("Default")]
[IsDependentOn(typeof(CopyOutputTask))]
public class DefaultTask : FrostingTask
{
public override void Run(ICakeContext context)
{
base.Run(context);
}
}

View file

@ -254,6 +254,6 @@ public sealed class Calendar
public class MonthCalendar
{
public List<string> Calendar { get; set; }
public List<string> Calendar { get; set; } = [];
public int WeeksInMonth { get; set; }
}

View file

@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>0.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
</PropertyGroup>
</Project>

View file

@ -1,7 +1,7 @@
using System.Text;
using ModuleCore.Calendar;
namespace Harness;
namespace ModuleHarness;
class Program
{

View file

@ -8,7 +8,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.PowerShell.SDK" Version="7.6.3" />
<PackageReference Include="Microsoft.PowerShell.SDK" />
<PackageReference Include="System.Security.Cryptography.Xml" />
</ItemGroup>
<ItemGroup>

View file

@ -0,0 +1,6 @@
<Project>
<PropertyGroup>
<VersionPrefix>0.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
</PropertyGroup>
</Project>

View file

@ -8,10 +8,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PowerShellStandard.Library" Version="5.1.0-preview-06">
<PackageReference Include="PowerShellStandard.Library" >
<PrivateAssets>All</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Management.Automation" Version="7.6.3" />
<PackageReference Include="System.Management.Automation" />
</ItemGroup>
<ItemGroup>