Nulah.PowerShell/build/Tasks/CopyOutputTask.cs

172 lines
No EOL
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Cake.Core.Diagnostics;
using Cake.Core.IO;
using Cake.Frosting;
using Cake.Powershell;
using Path = System.IO.Path;
namespace Build.Tasks;
[TaskName("CopyOutput")]
public class CopyOutputTask : FrostingTask<BuildContext>
{
public override void Run(BuildContext context)
{
var powershellModuleName = "PowershellModule";
// TODO: probably don't create full file locations when I can pass the output dir in and have the script
// make the path
var scriptParams = new
{
PowershellModuleFileLocation = $"{context.PowershellModuleOutputDir}/{powershellModuleName}.psd1",
Guid = Guid.Parse("5cdf4635-edb0-428c-8d9b-92d0bcd47443"),
Author = "Me",
NestedModules = new[] { $"{powershellModuleName}.dll" },
RootModule = $"{powershellModuleName}.psm1",
CmdletsToExport = GetExportedCmdlets(),
ManifestFileLocation = $"{context.PowershellModuleOutputDir}/{powershellModuleName}.psm1",
OutputLocation = context.PowershellModuleOutputDir,
};
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("powershellModuleFileLocation", ToPowershellSafeString(scriptParams.PowershellModuleFileLocation));
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));
psSettings.Arguments.Append("outputDir", ToPowershellSafeString(scriptParams.OutputLocation));
context.StartPowershellFile(context.CreateModuleManifestScript, psSettings);
context.Log.Information($"Module output to: {context.PowershellModuleOutputDir.Path.MakeAbsolute(context.Environment)}");
BundleMinimalFiles(context);
base.Run(context);
}
private static string ToPowershellSafeString(string unescapedString) => $"'{unescapedString}'";
private static List<string> GetExportedCmdlets()
{
return ["Get-Calendar", .. GetGitRepoRegistrationVerbs()];
}
private static List<string> GetGitRepoRegistrationVerbs()
{
// TODO: I should make some reference file for these verbs and name but that'd pull in powershell dependencies
// to the build and I'd like to avoid that.
// This isn't great but commands are unlikely to change that frequently
string[] verbs = ["Get", "New", "Remove", "Show"];
var commandName = "GitRepoRegistration";
return [.. verbs.Select(x => $"{x}-{commandName}")];
}
private static void BundleMinimalFiles(BuildContext context)
{
// We're effectively replicating PostBuild.ps1 with all of this as Remove-Item at the end of it does not remove files the same way.
// TODO: See if that's something I can fix
var powershellModuleBuildLocation = context.PowershellModuleOutputDir.Path.MakeAbsolute(context.Environment).FullPath;
// PowershellModule is here so that the script that adds to the users $Env:PSModulePath correctly resolves when using
// Import-Module "PowershellModule"
// TODO: Generate the script to add to the PSModulePath in the bundleOutputLocation directory
var bundleOutputLocation = Directory.CreateDirectory(Path.Combine(powershellModuleBuildLocation, "bundle")).FullName;
var moduleOutputLocation = Directory.CreateDirectory(Path.Combine(bundleOutputLocation, "PowershellModule")).FullName;
context.Log.Information($"Bundle location: {bundleOutputLocation}");
context.Log.Information($"Built file location: {powershellModuleBuildLocation}");
// PostBuild.ps1 for PowershellModule should have already been run at this point - it won't remove files, but it will copy the
// correct e_sqlite3.dll that we need.
// TODO: Investigate this later as I'm not entirely happy with this now
// // Get the location for the specific version of e_sqlite3.dll we need - PowerShell binary modules resolve dependent dlls
// // from the executing assembly directory first, and the built output of the module includes a lot of other dlls that
// // are already available with the dotnet runtime so there's no need for us to include them.
// // I mean, we could, but we'd also be terrible software engineers if we couldn't do something as basic as reducing files we need.
// // TODO: Account for other runtimes such as linux based ones where the dotnet runtime might _not_ provide these files for free.
// // It'd require looking into how the module loads for PowerShell on those operating systems, and I might never
// // get around to doing that because I use windows (currently).
// // TODO: Update this later to handle other runtimes, for now we hardcode to win-x64 because it's what I use
// var nativeSqliteDllLocation = System.IO.Path.Combine(powershellModuleBuildLocation, "runtimes", "win-x64", "native", "e_sqlite3.dll");
//
// if (!File.Exists(nativeSqliteDllLocation))
// {
// context.Log.Error($"BUNDLE FAILED: Unable to locate ${nativeSqliteDllLocation}");
// return;
// }
//
// context.Log.Information($"Copying '${nativeSqliteDllLocation}' to '${bundleOutputLocation}'");
//
// File.Copy(nativeSqliteDllLocation, System.IO.Path.Combine(bundleOutputLocation, System.IO.Path.GetFileName(nativeSqliteDllLocation)));
var moduleCoreFiles = Directory.GetFiles(powershellModuleBuildLocation, "ModuleCore*", searchOption: SearchOption.TopDirectoryOnly);
var powershellModuleFiles = Directory.GetFiles(powershellModuleBuildLocation, "PowershellModule*", searchOption: SearchOption.TopDirectoryOnly);
var sqliteFiles = Directory.GetFiles(powershellModuleBuildLocation, "*SQLite*", searchOption: SearchOption.TopDirectoryOnly);
string[] allFiles = [.. moduleCoreFiles, .. powershellModuleFiles, .. sqliteFiles];
context.Log.Information($"Copying {allFiles.Length} files to the module folder {moduleOutputLocation}");
foreach (var file in allFiles)
{
context.Log.Information($"Copying {Path.GetFileName(file)}");
File.Copy(file, Path.Combine(moduleOutputLocation, Path.GetFileName(file)));
}
context.Log.Information($"Bundle files copied");
GenerateModuleImportScript(bundleOutputLocation, context);
// TODO: zip the bundle directory contents to NulahModule.zip, and have a top level folder inside that called NulahModule.
// The intent is that users will drop this zip next to their $profile, and then extract the contents directly so everything
// is contained as it should be
}
private static void GenerateModuleImportScript(string bundleLocation, BuildContext context)
{
context.Log.Information($"Generating module import script");
using var scriptFile = File.Create(Path.Combine(bundleLocation, "NulahPowershell.ps1"));
using var fileWriter = new StreamWriter(scriptFile);
// TODO: Document the set up function
// TODO: Create a ticket for fleshing out the set up function (or just do that work later)
fileWriter.Write(
"""
# To use, first copy all files to the same location as your $profile under ./NulahModule, then open your powershell profile located at $profile and add the following:
# Script setup style: Using the setup script to import as needed (this method will also set custom prompts and other alias functions)
# . "$PSScriptRoot/NulahModule/NulahPowershell.ps1"
# SetupNulahPowershell
# Just the module: Use the following to just import just the powershell module
# Import-Module -Name "$PSScriptRoot/NulahModule/PowershellModule"
function SetupNulahPowershell
{
# only add to our module path once
# The intent for this is that a user will have copied this bundle folder alongside their $profile
# location, eg, they'll have a folder called NulahModule that will contain everything within the bundle zip
$testBundleDirectory = $PSScriptRoot
$bundleInPath = ($Env:PSModulePath -split ';').TrimEnd('\') -contains $testBundleDirectory;
if ($false -eq $bundleInPath)
{
$env:PSModulePath = @(
$env:PSModulePath
$testBundleDirectory
) -Join [System.IO.Path]::PathSeparator
}
Import-Module "PowershellModule"
}
"""
);
context.Log.Information($"Module import script created");
}
}