#nullable enable #addin nuget:?package=Cake.FileHelpers&version=4.0.1 #addin nuget:?package=SharpZipLib&version=1.3.1 #addin nuget:?package=Cake.Compression&version=0.2.6 #addin nuget:?package=Cake.Json&version=6.0.1 #addin nuget:?package=Newtonsoft.Json&version=13.0.1 #addin nuget:?package=Cake.Http&version=1.3.0 var task = Argument("target", "Build"); // Note: "target" is special Cake argument, don't change var isBleedingEdge = Argument("bleeding_edge", false); var buildId = Argument("build_id", 0); var cleanDependencyCache = Argument("clean_build_cache", false); var lastBuildCommit = Argument("last_build_commit", ""); var nugetPushSource = Argument("nuget_push_source", "https://nuget.bepinex.dev/v3/index.json"); var nugetPushKey = Argument("nuget_push_key", ""); var buildVersion = ""; var currentCommit = RunGit("rev-parse HEAD"); var currentCommitShort = RunGit("log -n 1 --pretty=\"format:%h\"").Trim(); var currentBranch = RunGit("rev-parse --abbrev-ref HEAD"); var latestTag = RunGit("describe --tags --abbrev=0"); string RunGit(string command, string separator = "") { using var process = StartAndReturnProcess("git", new() { Arguments = command, RedirectStandardOutput = true }); process.WaitForExit(); return string.Join(separator, process.GetStandardOutput()); } readonly record struct Target(string Abi, string Os, string Cpu, bool NeedsRuntime = false) { public string Abi { get; } = Abi.Contains('-') ? throw new FormatException($"Target ABI cannot be hyphenated: {Abi}") : Abi; public string Os { get; } = Os.Contains('-') ? throw new FormatException($"Target OS cannot be hyphenated: {Os}") : Os; public string Cpu { get; } = Cpu.Contains('-') ? throw new FormatException($"Target CPU cannot be hyphenated: {Cpu}") : Cpu; public string RuntimeIdentifier => $"{Os}-{Cpu}"; public override string ToString() => $"{Abi}-{Os}-{Cpu}"; public bool IsUnix => Os is "unix" or "linux" or "macos"; public string LibExt => Os switch { "macos" => "dylib", "linux" => "so", "windows" => "dll", _ => throw new NotSupportedException($"Target OS {Os} is not supported") }; public string LibPrefix => IsUnix ? "lib" : ""; } var targets = new Target[] { new("UnityMono", "windows", "x86"), new("UnityMono", "windows", "x64"), new("UnityMono", "linux", "x86"), new("UnityMono", "linux", "x64"), new("UnityMono", "macos", "x64"), new("UnityIL2CPP", "windows", "x86", true), new("UnityIL2CPP", "windows", "x64", true), new("UnityIL2CPP", "linux", "x64", true), new("UnityIL2CPP", "macos", "x64", true), new("NetLauncher", "windows", "any"), }; const string DEP_CACHE_NAME = ".dep_cache"; Task("Cleanup") .Does(() => { Information("Removing old binaries"); CreateDirectory("./bin"); CleanDirectory("./bin", fi => { var cachePath = fi.Path.FullPath.Contains(DEP_CACHE_NAME); return !cachePath || cleanDependencyCache && cachePath; }); Information("Cleaning up old build objects"); CleanDirectories(GetDirectories(new GlobPattern("./BepInEx.*/**/bin/"))); CleanDirectories(GetDirectories(new GlobPattern("./BepInEx.*/**/obj/"))); }); Task("Build") .IsDependentOn("Cleanup") .Does(() => { // Used in later phases buildVersion = FindRegexMatchGroupInFile(File("Directory.Build.props"), @"\([0-9]+\.[0-9]+\.[0-9]+)\<\/VersionPrefix\>", 1, System.Text.RegularExpressions.RegexOptions.None).Value; var buildSettings = new DotNetBuildSettings { Configuration = "Release", MSBuildSettings = new() // Apparently needed in some versions of CakeBuild }; if (isBleedingEdge) { buildSettings.MSBuildSettings.Properties["VersionSuffix"] = new[] { $"be.{buildId}" }; buildSettings.MSBuildSettings.Properties["SourceRevisionId"] = new[] { currentCommit }; buildSettings.MSBuildSettings.Properties["RepositoryBranch"] = new[] { currentBranch }; buildVersion += $"-be.{buildId}+{currentCommitShort}"; } foreach (var file in GetFiles("./BepInEx.*/*.csproj")) { DotNetBuild(file.FullPath, buildSettings); } }); const string DOORSTOP_VER = "4.0.0-rc.7"; const string DOTNET_MINI_VER = "6.0.6"; const string DOORSTOP_PROXY_DLL = "winhttp.dll"; const string DOBBY_VER = "1.0.0"; var depCachePath = Directory($"./bin/{DEP_CACHE_NAME}"); var doorstopPath = depCachePath + Directory("doorstop"); var dotnetPath = depCachePath + Directory("dotnet"); var dobbyPath = depCachePath + Directory("dobby"); Task("DownloadDependencies") .Does(() => { var cacheFile = depCachePath + File("cache.json"); var cache = FileExists(cacheFile) ? DeserializeJsonFromFile>(cacheFile) : new Dictionary(); bool NeedsRedownload(string repo, string curVer) { if (!cache.TryGetValue(repo, out var ver) || ver != curVer) { cache[repo] = curVer; return true; } return false; } CreateDirectory(doorstopPath); CreateDirectory(dotnetPath); CreateDirectory(dobbyPath); if (NeedsRedownload("NeighTools/UnityDoorstop", DOORSTOP_VER)) { Information("Updating Doorstop"); var doorstopZipPathWin = depCachePath + File("doorstop_win.zip"); var doorstopZipPathLinux = depCachePath + File("doorstop_linux.zip"); var doorstopZipPathMacOs = depCachePath + File("doorstop_macos.zip"); DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER}/doorstop_win_release_{DOORSTOP_VER}.zip", doorstopZipPathWin); DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER}/doorstop_linux_release_{DOORSTOP_VER}.zip", doorstopZipPathLinux); DownloadFile($"https://github.com/NeighTools/UnityDoorstop/releases/download/v{DOORSTOP_VER}/doorstop_macos_release_{DOORSTOP_VER}.zip", doorstopZipPathMacOs); CleanDirectory(doorstopPath); ZipUncompress(doorstopZipPathWin, doorstopPath + Directory("windows")); ZipUncompress(doorstopZipPathLinux, doorstopPath + Directory("linux")); ZipUncompress(doorstopZipPathMacOs, doorstopPath + Directory("macos")); } if (NeedsRedownload("BepInEx/Dobby", DOBBY_VER)) { Information("Updating Dobby"); var dobbyZipPathWin = depCachePath + File("dobby_win.zip"); var dobbyZipPathLinux = depCachePath + File("dobby_linux.zip"); var dobbyZipPathMacOs = depCachePath + File("dobby_macos.zip"); DownloadFile($"https://github.com/BepInEx/Dobby/releases/download/v{DOBBY_VER}/dobby-win.zip", dobbyZipPathWin); DownloadFile($"https://github.com/BepInEx/Dobby/releases/download/v{DOBBY_VER}/dobby-linux.zip", dobbyZipPathLinux); DownloadFile($"https://github.com/BepInEx/Dobby/releases/download/v{DOBBY_VER}/dobby-macos.zip", dobbyZipPathMacOs); CleanDirectory(dobbyPath); ZipUncompress(dobbyZipPathWin, dobbyPath + Directory("windows")); ZipUncompress(dobbyZipPathLinux, dobbyPath + Directory("linux")); ZipUncompress(dobbyZipPathMacOs, dobbyPath + Directory("macos")); } if (NeedsRedownload("BepInEx/dotnet-runtime", DOTNET_MINI_VER)) { Information("Updating mini-dotnet, this might take a while"); var dotnetZipPath = depCachePath + File("dotnet-mini.zip"); DownloadFile($"https://github.com/BepInEx/dotnet-runtime/releases/download/{DOTNET_MINI_VER}/mini-coreclr-Release.zip", dotnetZipPath); CleanDirectory(dotnetPath); ZipUncompress(dotnetZipPath, dotnetPath); } SerializeJsonToFile(cacheFile, cache); }); Task("PushNuGet") .IsDependentOn("Build") .Does(() => { if (nugetPushSource is null or "") { Information("NuGet push source is missing"); return; } if (nugetPushKey is null or "") { Information("NuGet push key is missing"); return; } foreach (var file in GetFiles("./bin/NuGet/*.nupkg")) { Information($"Pushing {file}"); DotNetNuGetPush(file.FullPath, new() { Source = nugetPushSource, ApiKey = nugetPushKey, }); } }); Task("MakeDist") .IsDependentOn("Build") .IsDependentOn("DownloadDependencies") .Does(() => { var distDir = Directory("./bin/dist"); CreateDirectory(distDir); var changelog = TransformText("<%commit_count%> commits since <%last_tag%>\r\n\r\nChangelog (excluding merges):\r\n<%commit_log%>") .WithToken("commit_count", RunGit($"rev-list --count {latestTag}..HEAD")) .WithToken("last_tag", latestTag) .WithToken("commit_log", RunGit($"--no-pager log --no-merges --pretty=\"format:* (%h) [%an] %s\" {latestTag}..HEAD", "\r\n")) .ToString(); void PackageBepin(Target target, ConvertableDirectoryPath originDir, ConvertableFilePath? doorstopConfigFile = null) { Information($"Creating distributions for target \"{target}\"..."); string? doorstopTargetPath = null; ConvertableDirectoryPath? doorstopTargetDir = null; if (doorstopConfigFile is not null) { doorstopTargetDir = doorstopPath + Directory(target.Os) + Directory(target.Cpu); doorstopTargetPath = $"{doorstopTargetDir + File(target.IsUnix ? "libdoorstop.*" : DOORSTOP_PROXY_DLL)}"; } var distTargetDir = distDir + Directory($"{target}"); var distBepinDir = distTargetDir + Directory("BepInEx"); var distDoorstopDir = distTargetDir; CreateDirectory(distTargetDir); CreateDirectory(distDoorstopDir); CreateDirectory(distBepinDir + Directory("core")); CreateDirectory(distBepinDir + Directory("plugins")); CreateDirectory(distBepinDir + Directory("patchers")); if (doorstopConfigFile is not null) { CopyFile(Directory("./doorstop") + doorstopConfigFile, distTargetDir + File(target.IsUnix ? "run_bepinex.sh" : "doorstop_config.ini")); if (target.IsUnix) { CopyFiles(new GlobPattern(doorstopTargetPath), distDoorstopDir); } else { CopyFile(File(doorstopTargetPath), Directory(distDoorstopDir) + File(DOORSTOP_PROXY_DLL)); } CopyFile(doorstopTargetDir + File(".doorstop_version"), distDoorstopDir + File(".doorstop_version")); if (target.IsUnix) { ReplaceTextInFiles(distTargetDir + File("run_bepinex.sh"), "\r\n", "\n"); } if (target.NeedsRuntime) { CopyDirectory(dotnetPath + Directory(target.RuntimeIdentifier), Directory(distTargetDir) + Directory("dotnet")); CopyFile(dobbyPath + Directory(target.Os) + File($"{target.LibPrefix}dobby_{target.Cpu}.{target.LibExt}"), distBepinDir + Directory("core") + File($"{target.LibPrefix}dobby.{target.LibExt}")); } } CopyFiles(new GlobPattern($"{Directory("./bin") + originDir + File("*.*")}"), Directory(distBepinDir) + Directory("core")); FileWriteText(distTargetDir + File("changelog.txt"), changelog); if (target.Abi is "NetLauncher") { DeleteFile(Directory(distBepinDir) + Directory("core") + File("BepInEx.NetLauncher.exe.config")); MoveFiles(new GlobPattern($"{Directory(distBepinDir) + Directory("core") + File("BepInEx.NetLauncher.*")}"), Directory(distTargetDir)); } } foreach (Target target in targets) { (ConvertableDirectoryPath originDir, ConvertableFilePath? doorstopConfigFile) = (Directory(target.Abi), null); if (target.Abi is var abi and ("UnityIL2CPP" or "UnityMono")) { originDir = Directory(abi is "UnityIL2CPP" ? "IL2CPP" : "UnityMono"); // TODO: Move config file resolving to Target class doorstopConfigFile = File( target.IsUnix ? (abi is "UnityIL2CPP" ? "run_bepinex_il2cpp.sh" : "run_bepinex_mono.sh") : (abi is "UnityIL2CPP" ? "doorstop_config_il2cpp.ini" : "doorstop_config_mono.ini") ); } PackageBepin(target, originDir, doorstopConfigFile); } }); Task("Pack") .IsDependentOn("MakeDist") .IsDependentOn("PushNuGet") .Does(() => { var distDir = Directory("./bin/dist"); Information("Packing BepInEx"); foreach (Target target in targets) { ZipCompress(distDir + Directory($"{target}"), distDir + File($"BepInEx-{target}-{buildVersion}.zip")); } if(isBleedingEdge) { var changelog = lastBuildCommit is null or "" ? "" : TransformText("
    <%changelog%>
") .WithToken("changelog", RunGit($"--no-pager log --no-merges --pretty=\"format:
  • (%h) [%an] %s
  • \" {lastBuildCommit}..HEAD")) .ToString(); FileWriteText(distDir + File("info.json"), SerializeJsonPretty(new Dictionary { ["id"] = buildId.ToString(), ["date"] = DateTime.Now.ToString("o"), ["changelog"] = changelog, ["hash"] = currentCommit, ["short_hash"] = currentCommitShort, ["artifacts"] = targets.Select(target => { (string abiName, string abiGamesPrefix) = target.Abi switch { "NetLauncher" => (".NET Launcher", ".NET Framework/XNA "), "UnityIL2CPP" => ("Unity IL2CPP", ""), "UnityMono" => ("Unity Mono", ""), var abi => (abi, $"{abi} "), }; (string osName, string osGamesSuffix) = target.Os switch { "unix" => ("Linux & macOS", " using GCC"), "linux" => ("Linux", " using GCC"), "macos" => ("macOS", ""), "windows" => ("Windows", ""), var os => (os, ""), }; string cpuOsNameSuffix = target.Cpu switch { "any" => "", var cpu => $" {cpu}", }; return new Dictionary() { ["file"] = $"BepInEx-{target}-{buildVersion}.zip", ["description"] = $"BepInEx {abiName} for {osName}{cpuOsNameSuffix} {abiGamesPrefix}games{osGamesSuffix}", }; }).ToArray(), })); } }); RunTarget(task);