From 1c0d22e82c4690069754d211179d8aef636a3e7a Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Tue, 10 Oct 2017 21:59:05 -0400 Subject: validate build context before build --- .../BuildTasks/DeployModTask.cs | 151 +++++++++++++++++ .../BuildTasks/ValidateInstallTask.cs | 70 ++++++++ src/SMAPI.ModBuildConfig/DeployModTask.cs | 180 --------------------- .../StardewModdingAPI.ModBuildConfig.csproj | 3 +- src/SMAPI.ModBuildConfig/build/smapi.targets | 6 +- 5 files changed, 227 insertions(+), 183 deletions(-) create mode 100644 src/SMAPI.ModBuildConfig/BuildTasks/DeployModTask.cs create mode 100644 src/SMAPI.ModBuildConfig/BuildTasks/ValidateInstallTask.cs delete mode 100644 src/SMAPI.ModBuildConfig/DeployModTask.cs (limited to 'src') diff --git a/src/SMAPI.ModBuildConfig/BuildTasks/DeployModTask.cs b/src/SMAPI.ModBuildConfig/BuildTasks/DeployModTask.cs new file mode 100644 index 00000000..433e602f --- /dev/null +++ b/src/SMAPI.ModBuildConfig/BuildTasks/DeployModTask.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using StardewModdingAPI.ModBuildConfig.Framework; + +namespace StardewModdingAPI.ModBuildConfig.BuildTasks +{ + /// A build task which deploys the mod files and prepares a release zip. + public class DeployModTask : Task + { + /********* + ** Accessors + *********/ + /// The name of the mod folder. + [Required] + public string ModFolderName { get; set; } + + /// The absolute or relative path to the folder which should contain the generated zip file. + [Required] + public string ModZipPath { get; set; } + + /// The folder containing the project files. + [Required] + public string ProjectDir { get; set; } + + /// The folder containing the build output. + [Required] + public string TargetDir { get; set; } + + /// The folder containing the game files. + [Required] + public string GameDir { get; set; } + + /// Whether to enable copying the mod files into the game's Mods folder. + [Required] + public bool EnableModDeploy { get; set; } + + /// Whether to enable the release zip. + [Required] + public bool EnableModZip { get; set; } + + + /********* + ** Public methods + *********/ + /// When overridden in a derived class, executes the task. + /// true if the task successfully executed; otherwise, false. + public override bool Execute() + { + if (!this.EnableModDeploy && !this.EnableModZip) + return true; // nothing to do + + try + { + // get mod info + ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir); + + // deploy mod files + if (this.EnableModDeploy) + { + string outputPath = Path.Combine(this.GameDir, "Mods", this.EscapeInvalidFilenameCharacters(this.ModFolderName)); + this.Log.LogMessage(MessageImportance.High, $"The mod build package is copying the mod files to {outputPath}..."); + this.CreateModFolder(package.GetFiles(), outputPath); + } + + // create release zip + if (this.EnableModZip) + { + this.Log.LogMessage(MessageImportance.High, $"The mod build package is generating a release zip at {this.ModZipPath} for {this.ModFolderName}..."); + this.CreateReleaseZip(package.GetFiles(), this.ModFolderName, package.GetManifestVersion(), this.ModZipPath); + } + + return true; + } + catch (UserErrorException ex) + { + this.Log.LogErrorFromException(ex); + return false; + } + catch (Exception ex) + { + this.Log.LogError($"The mod build package failed trying to deploy the mod.\n{ex}"); + return false; + } + } + + + /********* + ** Private methods + *********/ + /// Copy the mod files into the game's mod folder. + /// The files to include. + /// The folder path to create with the mod files. + private void CreateModFolder(IDictionary files, string modFolderPath) + { + Directory.CreateDirectory(modFolderPath); + foreach (var entry in files) + { + string fromPath = entry.Value.FullName; + string toPath = Path.Combine(modFolderPath, entry.Key); + File.Copy(fromPath, toPath, overwrite: true); + } + } + + /// Create a release zip in the recommended format for uploading to mod sites. + /// The files to include. + /// The name of the mod. + /// The mod version string. + /// The absolute or relative path to the folder which should contain the generated zip file. + private void CreateReleaseZip(IDictionary files, string modName, string modVersion, string outputFolderPath) + { + // get names + string zipName = this.EscapeInvalidFilenameCharacters($"{modName} {modVersion}.zip"); + string folderName = this.EscapeInvalidFilenameCharacters(modName); + string zipPath = Path.Combine(outputFolderPath, zipName); + + // create zip file + Directory.CreateDirectory(outputFolderPath); + using (Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write)) + using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) + { + foreach (var fileEntry in files) + { + string relativePath = fileEntry.Key; + FileInfo file = fileEntry.Value; + + // get file info + string filePath = file.FullName; + string entryName = folderName + '/' + relativePath.Replace(Path.DirectorySeparatorChar, '/'); + + // add to zip + using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) + using (Stream fileStreamInZip = archive.CreateEntry(entryName).Open()) + fileStream.CopyTo(fileStreamInZip); + } + } + } + + /// Get a copy of a filename with all invalid filename characters substituted. + /// The filename. + private string EscapeInvalidFilenameCharacters(string name) + { + foreach (char invalidChar in Path.GetInvalidFileNameChars()) + name = name.Replace(invalidChar, '.'); + return name; + } + } +} diff --git a/src/SMAPI.ModBuildConfig/BuildTasks/ValidateInstallTask.cs b/src/SMAPI.ModBuildConfig/BuildTasks/ValidateInstallTask.cs new file mode 100644 index 00000000..2cc7dc9c --- /dev/null +++ b/src/SMAPI.ModBuildConfig/BuildTasks/ValidateInstallTask.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using StardewModdingAPI.ModBuildConfig.Framework; + +namespace StardewModdingAPI.ModBuildConfig.BuildTasks +{ + /// A build task which validates the install context. + public class ValidateInstallTask : Task + { + /********* + ** Properties + *********/ + /// The MSBuild platforms recognised by the build configuration. + private readonly HashSet ValidPlatforms = new HashSet(new[] { "OSX", "Unix", "Windows_NT" }, StringComparer.InvariantCultureIgnoreCase); + + /// The name of the game's main executable file. + private string GameExeName => this.Platform == "Windows_NT" + ? "Stardew Valley.exe" + : "StardewValley.exe"; + + /// The name of SMAPI's main executable file. + private readonly string SmapiExeName = "StardewModdingAPI.exe"; + + + /********* + ** Accessors + *********/ + /// The folder containing the game files. + public string GameDir { get; set; } + + /// The MSBuild OS value. + public string Platform { get; set; } + + + /********* + ** Public methods + *********/ + /// When overridden in a derived class, executes the task. + /// true if the task successfully executed; otherwise, false. + public override bool Execute() + { + try + { + if (!this.ValidPlatforms.Contains(this.Platform)) + throw new UserErrorException($"The mod build package doesn't recognise OS type '{this.Platform}'."); + if (!Directory.Exists(this.GameDir)) + throw new UserErrorException("The mod build package can't find your game path. See https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md for help specifying it."); + if (!File.Exists(Path.Combine(this.GameDir, this.GameExeName))) + throw new UserErrorException($"The mod build package found a game folder at {this.GameDir}, but it doesn't contain the {this.GameExeName} file. If this folder is invalid, delete it and the package will autodetect another game install path."); + if (!File.Exists(Path.Combine(this.GameDir, this.SmapiExeName))) + throw new UserErrorException($"The mod build package found a game folder at {this.GameDir}, but it doesn't contain SMAPI. You need to install SMAPI before building the mod."); + + return true; + } + catch (UserErrorException ex) + { + this.Log.LogErrorFromException(ex); + return false; + } + catch (Exception ex) + { + this.Log.LogError($"The mod build package failed trying to deploy the mod.\n{ex}"); + return false; + } + } + } +} diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs deleted file mode 100644 index 0bfe9b2d..00000000 --- a/src/SMAPI.ModBuildConfig/DeployModTask.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; -using StardewModdingAPI.ModBuildConfig.Framework; - -namespace StardewModdingAPI.ModBuildConfig -{ - /// A build task which deploys the mod files and prepares a release zip. - public class DeployModTask : Task - { - /********* - ** Properties - *********/ - /// The MSBuild platforms recognised by the build configuration. - private readonly HashSet ValidPlatforms = new HashSet(new[] { "OSX", "Unix", "Windows_NT" }, StringComparer.InvariantCultureIgnoreCase); - - /// The name of the game's main executable file. - private string GameExeName => this.Platform == "Windows_NT" - ? "Stardew Valley.exe" - : "StardewValley.exe"; - - /// The name of SMAPI's main executable file. - private readonly string SmapiExeName = "StardewModdingAPI.exe"; - - - /********* - ** Accessors - *********/ - /// The name of the mod folder. - [Required] - public string ModFolderName { get; set; } - - /// The absolute or relative path to the folder which should contain the generated zip file. - [Required] - public string ModZipPath { get; set; } - - /// The folder containing the project files. - [Required] - public string ProjectDir { get; set; } - - /// The folder containing the build output. - [Required] - public string TargetDir { get; set; } - - /// The folder containing the game files. - [Required] - public string GameDir { get; set; } - - /// The MSBuild OS value. - [Required] - public string Platform { get; set; } - - /// Whether to enable copying the mod files into the game's Mods folder. - [Required] - public bool EnableModDeploy { get; set; } - - /// Whether to enable the release zip. - [Required] - public bool EnableModZip { get; set; } - - - /********* - ** Public methods - *********/ - /// When overridden in a derived class, executes the task. - /// true if the task successfully executed; otherwise, false. - public override bool Execute() - { - if (!this.EnableModDeploy && !this.EnableModZip) - return true; // nothing to do - - try - { - // validate context - if (!this.ValidPlatforms.Contains(this.Platform)) - throw new UserErrorException($"The mod build package doesn't recognise OS type '{this.Platform}'."); - if (!Directory.Exists(this.GameDir)) - throw new UserErrorException("The mod build package can't find your game path. See https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md for help specifying it."); - if (!File.Exists(Path.Combine(this.GameDir, this.GameExeName))) - throw new UserErrorException($"The mod build package found a game folder at {this.GameDir}, but it doesn't contain the {this.GameExeName} file. If this folder is invalid, delete it and the package will autodetect another game install path."); - if (!File.Exists(Path.Combine(this.GameDir, this.SmapiExeName))) - throw new UserErrorException($"The mod build package found a game folder at {this.GameDir}, but it doesn't contain SMAPI. You need to install SMAPI before building the mod."); - - // get mod info - ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir); - - // deploy mod files - if (this.EnableModDeploy) - { - string outputPath = Path.Combine(this.GameDir, "Mods", this.EscapeInvalidFilenameCharacters(this.ModFolderName)); - this.Log.LogMessage(MessageImportance.High, $"The mod build package is copying the mod files to {outputPath}..."); - this.CreateModFolder(package.GetFiles(), outputPath); - } - - // create release zip - if (this.EnableModZip) - { - this.Log.LogMessage(MessageImportance.High, $"The mod build package is generating a release zip at {this.ModZipPath} for {this.ModFolderName}..."); - this.CreateReleaseZip(package.GetFiles(), this.ModFolderName, package.GetManifestVersion(), this.ModZipPath); - } - - return true; - } - catch (UserErrorException ex) - { - this.Log.LogErrorFromException(ex); - return false; - } - catch (Exception ex) - { - this.Log.LogError($"The mod build package failed trying to deploy the mod.\n{ex}"); - return false; - } - } - - - /********* - ** Private methods - *********/ - /// Copy the mod files into the game's mod folder. - /// The files to include. - /// The folder path to create with the mod files. - private void CreateModFolder(IDictionary files, string modFolderPath) - { - Directory.CreateDirectory(modFolderPath); - foreach (var entry in files) - { - string fromPath = entry.Value.FullName; - string toPath = Path.Combine(modFolderPath, entry.Key); - File.Copy(fromPath, toPath, overwrite: true); - } - } - - /// Create a release zip in the recommended format for uploading to mod sites. - /// The files to include. - /// The name of the mod. - /// The mod version string. - /// The absolute or relative path to the folder which should contain the generated zip file. - private void CreateReleaseZip(IDictionary files, string modName, string modVersion, string outputFolderPath) - { - // get names - string zipName = this.EscapeInvalidFilenameCharacters($"{modName} {modVersion}.zip"); - string folderName = this.EscapeInvalidFilenameCharacters(modName); - string zipPath = Path.Combine(outputFolderPath, zipName); - - // create zip file - Directory.CreateDirectory(outputFolderPath); - using (Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write)) - using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) - { - foreach (var fileEntry in files) - { - string relativePath = fileEntry.Key; - FileInfo file = fileEntry.Value; - - // get file info - string filePath = file.FullName; - string entryName = folderName + '/' + relativePath.Replace(Path.DirectorySeparatorChar, '/'); - - // add to zip - using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read)) - using (Stream fileStreamInZip = archive.CreateEntry(entryName).Open()) - fileStream.CopyTo(fileStreamInZip); - } - } - } - - /// Get a copy of a filename with all invalid filename characters substituted. - /// The filename. - private string EscapeInvalidFilenameCharacters(string name) - { - foreach (char invalidChar in Path.GetInvalidFileNameChars()) - name = name.Replace(invalidChar, '.'); - return name; - } - } -} diff --git a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj index e04f09a7..0007e53e 100644 --- a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj @@ -38,7 +38,8 @@ - + + diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index 0010d8ff..9f3f13f5 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -3,6 +3,7 @@ ** Import build tasks **********************************************--> + + + + -- cgit