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 { 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 GetExportedCmdlets() { return ["Get-Calendar", .. GetGitRepoRegistrationVerbs()]; } private static List 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"); } }