using System.IO; using System.IO.Compression; using Cake.Core.Diagnostics; using Cake.Frosting; namespace Build.Tasks; [TaskName("CreateBundleArchive")] [IsDependentOn(typeof(CopyOutputTask))] public class CreateBundleArchiveTask : FrostingTask { public override void Run(BuildContext context) { context.Log.Information($"Creating bundle archive"); BundleMinimalFiles(context); base.Run(context); } 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 // Top level bundle output location var bundleRootLocation = Directory.CreateDirectory(Path.Combine(powershellModuleBuildLocation, "bundle")).FullName; // Module folder location that will be zipped up and will contain any setup scripts/readme etc var moduleFolderLocation = Directory.CreateDirectory(Path.Combine(bundleRootLocation, "NulahModule")).FullName; // Location to put all the module dlls and other files var powershelModuleOutputLocation = Directory.CreateDirectory(Path.Combine(moduleFolderLocation, "PowershellModule")).FullName; context.Log.Information($"Bundle location: {bundleRootLocation}"); 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. // This code is all commented out because I'm not entirely satisfied with PostBuild.ps1 doing some things but // this build doing slightly different. // // 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 {powershelModuleOutputLocation}"); foreach (var file in allFiles) { context.Log.Information($"Copying {Path.GetFileName(file)}"); File.Copy(file, Path.Combine(powershelModuleOutputLocation, Path.GetFileName(file))); } context.Log.Information($"Bundle files copied"); GenerateModuleImportScript(moduleFolderLocation, context); CreateBundleZip(bundleRootLocation, moduleFolderLocation, context); } private static void GenerateModuleImportScript(string bundleLocation, BuildContext context) { context.Log.Information($"Generating module import script"); var scriptFileName = Path.Combine(bundleLocation, "NulahPowershell.ps1"); using var scriptFile = File.Create(scriptFileName); 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 at '{scriptFileName}'"); } private static void CreateBundleZip(string bundleLocation, string moduleFolderLocation, BuildContext context) { var bundleZipFileLocaiton = Path.Combine(bundleLocation, "NulahModule.zip"); context.Log.Information($"Bundling module into archive '{bundleZipFileLocaiton}'"); ZipFile.CreateFromDirectory(moduleFolderLocation, bundleZipFileLocaiton, CompressionLevel.Fastest, true); context.Log.Information($"Archive created at '{bundleZipFileLocaiton}'"); } }