From 475efa12febcb1f1f0976cb6c84e445a263daed9 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 8 Oct 2017 18:05:47 -0400 Subject: rewrite mod build package per new docs --- .../Framework/ModFileManager.cs | 162 +++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs (limited to 'src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs') diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs new file mode 100644 index 00000000..9d9054f1 --- /dev/null +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Web.Script.Serialization; +using StardewModdingAPI.Common; + +namespace StardewModdingAPI.ModBuildConfig.Framework +{ + /// Manages the files that are part of a mod package. + internal class ModFileManager + { + /********* + ** Properties + *********/ + /// The name of the manifest file. + private readonly string ManifestFileName = "manifest.json"; + + /// The files that are part of the package. + private readonly IDictionary Files; + + + /********* + ** Public methods + *********/ + /// Construct an instance. + /// The folder containing the project files. + /// The folder containing the build output. + /// The mod package isn't valid. + public ModFileManager(string projectDir, string targetDir) + { + this.Files = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + + // validate paths + if (!Directory.Exists(projectDir)) + throw new UserErrorException("Could not create mod package because the project folder wasn't found."); + if (!Directory.Exists(targetDir)) + throw new UserErrorException("Could not create mod package because no build output was found."); + + // project manifest + bool hasProjectManifest = false; + { + FileInfo manifest = new FileInfo(Path.Combine(projectDir, "manifest.json")); + if (manifest.Exists) + { + this.Files[this.ManifestFileName] = manifest; + hasProjectManifest = true; + } + } + + // project i18n files + bool hasProjectTranslations = false; + DirectoryInfo translationsFolder = new DirectoryInfo(Path.Combine(projectDir, "i18n")); + if (translationsFolder.Exists) + { + foreach (FileInfo file in translationsFolder.EnumerateFiles()) + this.Files[Path.Combine("i18n", file.Name)] = file; + hasProjectTranslations = true; + } + + // build output + DirectoryInfo buildFolder = new DirectoryInfo(targetDir); + foreach (FileInfo file in buildFolder.EnumerateFiles("*", SearchOption.AllDirectories)) + { + // get relative paths + string relativePath = file.FullName.Replace(buildFolder.FullName, ""); + string relativeDirPath = file.Directory.FullName.Replace(buildFolder.FullName, ""); + + // prefer project manifest/i18n files + if (hasProjectManifest && relativePath.Equals(this.ManifestFileName, StringComparison.InvariantCultureIgnoreCase)) + continue; + if (hasProjectTranslations && relativeDirPath.Equals("i18n", StringComparison.InvariantCultureIgnoreCase)) + continue; + + // ignore release zips + if (file.Extension.Equals("zip", StringComparison.InvariantCultureIgnoreCase)) + continue; + + // add file + this.Files[relativePath] = file; + } + + // check for missing manifest + if (!this.Files.ContainsKey(this.ManifestFileName)) + throw new UserErrorException($"Could not create mod package because no {this.ManifestFileName} was found in the project or build output."); + + // check for missing DLL + // ReSharper disable once SimplifyLinqExpression + if (!this.Files.Any(p => !p.Key.EndsWith(".dll"))) + throw new UserErrorException("Could not create mod package because no .dll file was found in the project or build output."); + } + + /// Get the files in the mod package. + public IDictionary GetFiles() + { + return new Dictionary(this.Files, StringComparer.InvariantCultureIgnoreCase); + } + + /// Get a semantic version from the mod manifest. + /// The manifest is missing or invalid. + public string GetManifestVersion() + { + // get manifest file + if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile)) + throw new InvalidOperationException($"The mod does not have a {this.ManifestFileName} file."); // shouldn't happen since we validate in constructor + + // read content + string json = File.ReadAllText(manifestFile.FullName); + if (string.IsNullOrWhiteSpace(json)) + throw new UserErrorException("The mod's manifest must not be empty."); + + // parse JSON + IDictionary data; + try + { + data = this.Parse(json); + } + catch (Exception ex) + { + throw new UserErrorException($"The mod's manifest couldn't be parsed. It doesn't seem to be valid JSON.\n{ex}"); + } + + // get version field + object versionObj = data.ContainsKey("Version") ? data["Version"] : null; + if (versionObj == null) + throw new UserErrorException("The mod's manifest must have a version field."); + + // get version string + if (versionObj is IDictionary versionFields) // SMAPI 1.x + { + int major = versionFields.ContainsKey("MajorVersion") ? (int)versionFields["MajorVersion"] : 0; + int minor = versionFields.ContainsKey("MinorVersion") ? (int)versionFields["MinorVersion"] : 0; + int patch = versionFields.ContainsKey("PatchVersion") ? (int)versionFields["PatchVersion"] : 0; + string tag = versionFields.ContainsKey("Build") ? (string)versionFields["Build"] : null; + return new SemanticVersionImpl(major, minor, patch, tag).ToString(); + } + return new SemanticVersionImpl(versionObj.ToString()).ToString(); // SMAPI 2.0+ + } + + + /********* + ** Private methods + *********/ + /// Get a case-insensitive dictionary matching the given JSON. + /// The JSON to parse. + private IDictionary Parse(string json) + { + IDictionary MakeCaseInsensitive(IDictionary dict) + { + foreach (var field in dict.ToArray()) + { + if (field.Value is IDictionary value) + dict[field.Key] = MakeCaseInsensitive(value); + } + return new Dictionary(dict, StringComparer.InvariantCultureIgnoreCase); + } + + IDictionary data = (IDictionary)new JavaScriptSerializer().DeserializeObject(json); + return MakeCaseInsensitive(data); + } + } +} -- cgit From d47105a27841bcae80fcaa2351a2a658cd3d7fdb Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Sun, 8 Oct 2017 21:21:11 -0400 Subject: update mod build package nuspec --- src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs | 2 +- .../StardewModdingAPI.ModBuildConfig.csproj | 1 - src/SMAPI.ModBuildConfig/package.nuspec | 20 ++++++++++++-------- 3 files changed, 13 insertions(+), 10 deletions(-) (limited to 'src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs') diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index 9d9054f1..10c55d4c 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -73,7 +73,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework continue; // ignore release zips - if (file.Extension.Equals("zip", StringComparison.InvariantCultureIgnoreCase)) + if (file.Extension.Equals(".zip", StringComparison.InvariantCultureIgnoreCase)) continue; // add file diff --git a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj index 2a445f72..e04f09a7 100644 --- a/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj +++ b/src/SMAPI.ModBuildConfig/StardewModdingAPI.ModBuildConfig.csproj @@ -47,7 +47,6 @@ Designer - PreserveNewest Designer diff --git a/src/SMAPI.ModBuildConfig/package.nuspec b/src/SMAPI.ModBuildConfig/package.nuspec index 9d547e28..4242489e 100644 --- a/src/SMAPI.ModBuildConfig/package.nuspec +++ b/src/SMAPI.ModBuildConfig/package.nuspec @@ -2,22 +2,26 @@ Pathoschild.Stardew.ModBuildConfig - 1.7.1 - MSBuild config for Stardew Valley mods + 2.0-alpha + Build package for SMAPI mods Pathoschild Pathoschild false - https://github.com/Pathoschild/Stardew.ModBuildConfig/blob/1.7.1/LICENSE.txt - https://github.com/Pathoschild/Stardew.ModBuildConfig#readme - https://raw.githubusercontent.com/Pathoschild/Stardew.ModBuildConfig/1.7.1/assets/nuget-icon.png + https://github.com/Pathoschild/SMAPI/blob/develop/LICENSE.txt + https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md#readme + https://raw.githubusercontent.com/Pathoschild/SMAPI/develop/src/SMAPI.ModBuildConfig/assets/nuget-icon.png Automates the build configuration for crossplatform Stardew Valley SMAPI mods. - 1.7 added an option to create release zips on build and added a reference to XNA's XACT library for audio-related mods. - 1.7.1 fixed an issue where i18n folders were flattened, and ensures that the manifest/i18n files in the project take precedence over those in the build output if both are present. + - Added: mods are now copied into the `Mods` folder automatically (configurable). + - Added: release zips are now created automatically in your build output folder (configurable). + - Added mod's version to release zip filename. + - Improved errors to simplify troubleshooting. + - Fixed release zip not having a mod folder. + - Fixed release zip failing if mod name contains characters that aren't valid in a filename. + - -- cgit From c456a0f56ed0fba55042c167afa83c9922a5db33 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Mon, 9 Oct 2017 12:44:48 -0400 Subject: don't include Json.NET in mod deploy or release zip since it's loaded by SMAPI --- docs/mod-build-config.md | 1 + src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs | 20 ++++++++++++++++---- src/SMAPI.ModBuildConfig/package.nuspec | 3 ++- 3 files changed, 19 insertions(+), 5 deletions(-) (limited to 'src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs') diff --git a/docs/mod-build-config.md b/docs/mod-build-config.md index f02981b9..ca750c86 100644 --- a/docs/mod-build-config.md +++ b/docs/mod-build-config.md @@ -130,6 +130,7 @@ _[Game path](#game-path)_ above. ### 2.0 * Added: mods are now copied into the `Mods` folder automatically (configurable). * Added: release zips are now created automatically in your build output folder (configurable). +* Added: mod deploy and release zips now exclude Json.NET automatically, since it's provided by SMAPI. * Added mod's version to release zip filename. * Improved errors to simplify troubleshooting. * Fixed release zip not having a mod folder. diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index 10c55d4c..64262dc2 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -67,15 +67,19 @@ namespace StardewModdingAPI.ModBuildConfig.Framework string relativeDirPath = file.Directory.FullName.Replace(buildFolder.FullName, ""); // prefer project manifest/i18n files - if (hasProjectManifest && relativePath.Equals(this.ManifestFileName, StringComparison.InvariantCultureIgnoreCase)) + if (hasProjectManifest && this.EqualsInvariant(relativePath, this.ManifestFileName)) continue; - if (hasProjectTranslations && relativeDirPath.Equals("i18n", StringComparison.InvariantCultureIgnoreCase)) + if (hasProjectTranslations && this.EqualsInvariant(relativeDirPath, "i18n")) continue; // ignore release zips - if (file.Extension.Equals(".zip", StringComparison.InvariantCultureIgnoreCase)) + if (this.EqualsInvariant(file.Extension, ".zip")) continue; - + + // ignore Json.NET (bundled into SMAPI) + if (this.EqualsInvariant(file.Name, "Newtonsoft.Json.dll") || this.EqualsInvariant(file.Name, "Newtonsoft.Json.xml")) + continue; + // add file this.Files[relativePath] = file; } @@ -158,5 +162,13 @@ namespace StardewModdingAPI.ModBuildConfig.Framework IDictionary data = (IDictionary)new JavaScriptSerializer().DeserializeObject(json); return MakeCaseInsensitive(data); } + + /// Get whether a string is equal to another case-insensitively. + /// The string value. + /// The string to compare with. + private bool EqualsInvariant(string str, string other) + { + return str.Equals(other, StringComparison.InvariantCultureIgnoreCase); + } } } diff --git a/src/SMAPI.ModBuildConfig/package.nuspec b/src/SMAPI.ModBuildConfig/package.nuspec index 4242489e..3ab5db8e 100644 --- a/src/SMAPI.ModBuildConfig/package.nuspec +++ b/src/SMAPI.ModBuildConfig/package.nuspec @@ -2,7 +2,7 @@ Pathoschild.Stardew.ModBuildConfig - 2.0-alpha + 2.0-beta Build package for SMAPI mods Pathoschild Pathoschild @@ -14,6 +14,7 @@ - Added: mods are now copied into the `Mods` folder automatically (configurable). - Added: release zips are now created automatically in your build output folder (configurable). + - Added: mod deploy and release zips now exclude Json.NET automatically, since it's provided by SMAPI. - Added mod's version to release zip filename. - Improved errors to simplify troubleshooting. - Fixed release zip not having a mod folder. -- cgit