From f5807e22be157d908e3b8c65f19acaa16c3588e7 Mon Sep 17 00:00:00 2001 From: Jesse Plamondon-Willard Date: Thu, 16 Sep 2021 22:47:05 -0400 Subject: add BundleExtraAssemblies package option for new .NET 5 reference model --- src/SMAPI.ModBuildConfig/DeployModTask.cs | 34 +++++- .../Framework/ExtraAssemblyType.cs | 21 ++++ .../Framework/ModFileManager.cs | 124 ++++++++++++++++----- src/SMAPI.ModBuildConfig/build/smapi.targets | 22 ++-- 4 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs (limited to 'src') diff --git a/src/SMAPI.ModBuildConfig/DeployModTask.cs b/src/SMAPI.ModBuildConfig/DeployModTask.cs index 0c64aed3..140933bd 100644 --- a/src/SMAPI.ModBuildConfig/DeployModTask.cs +++ b/src/SMAPI.ModBuildConfig/DeployModTask.cs @@ -18,6 +18,10 @@ namespace StardewModdingAPI.ModBuildConfig /********* ** Accessors *********/ + /// The name (without extension or path) of the current mod's DLL. + [Required] + public string ModDllName { get; set; } + /// The name of the mod folder. [Required] public string ModFolderName { get; set; } @@ -52,6 +56,9 @@ namespace StardewModdingAPI.ModBuildConfig /// A comma-separated list of relative file paths to ignore when deploying or zipping the mod. public string IgnoreModFilePaths { get; set; } + /// A comma-separated list of values which indicate which extra DLLs to bundle. + public string BundleExtraAssemblies { get; set; } + /********* ** Public methods @@ -73,12 +80,15 @@ namespace StardewModdingAPI.ModBuildConfig try { + // parse extra DLLs to bundle + ExtraAssemblyTypes bundleAssemblyTypes = this.GetExtraAssembliesToBundleOption(); + // parse ignore patterns string[] ignoreFilePaths = this.GetCustomIgnoreFilePaths().ToArray(); Regex[] ignoreFilePatterns = this.GetCustomIgnorePatterns().ToArray(); // get mod info - ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip); + ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, this.ModDllName, validateRequiredModFiles: this.EnableModDeploy || this.EnableModZip); // deploy mod files if (this.EnableModDeploy) @@ -139,6 +149,28 @@ namespace StardewModdingAPI.ModBuildConfig } } + /// Parse the extra assembly types which should be bundled with the mod. + private ExtraAssemblyTypes GetExtraAssembliesToBundleOption() + { + ExtraAssemblyTypes flags = ExtraAssemblyTypes.None; + + if (!string.IsNullOrWhiteSpace(this.BundleExtraAssemblies)) + { + foreach (string raw in this.BundleExtraAssemblies.Split(',')) + { + if (!Enum.TryParse(raw, out ExtraAssemblyTypes type)) + { + this.Log.LogWarning($"[mod build package] Ignored invalid <{nameof(this.BundleExtraAssemblies)}> value '{raw}', expected one of '{string.Join("', '", Enum.GetNames(typeof(ExtraAssemblyTypes)))}'."); + continue; + } + + flags |= type; + } + } + + return flags; + } + /// Get the custom ignore patterns provided by the user. private IEnumerable GetCustomIgnorePatterns() { diff --git a/src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs b/src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs new file mode 100644 index 00000000..571bf7c7 --- /dev/null +++ b/src/SMAPI.ModBuildConfig/Framework/ExtraAssemblyType.cs @@ -0,0 +1,21 @@ +using System; + +namespace StardewModdingAPI.ModBuildConfig.Framework +{ + /// An extra assembly type for the field. + [Flags] + internal enum ExtraAssemblyTypes + { + /// Don't include extra assemblies. + None = 0, + + /// Assembly files which are part of MonoGame, SMAPI, or Stardew Valley. + Game = 1, + + /// Assembly files whose names start with Microsoft.* or System.*. + System = 2, + + /// Assembly files which don't match any other category. + ThirdParty = 4 + } +} diff --git a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs index fbb91193..ad4ffdf9 100644 --- a/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs +++ b/src/SMAPI.ModBuildConfig/Framework/ModFileManager.cs @@ -21,6 +21,45 @@ namespace StardewModdingAPI.ModBuildConfig.Framework /// The files that are part of the package. private readonly IDictionary Files; + /// The file extensions used by assembly files. + private readonly ISet AssemblyFileExtensions = new HashSet(StringComparer.OrdinalIgnoreCase) + { + ".dll", + ".exe", + ".pdb", + ".xml" + }; + + /// The DLLs which match the type. + private readonly ISet GameDllNames = new HashSet + { + // SMAPI + "0Harmony", + "Mono.Cecil", + "Mono.Cecil.Mdb", + "Mono.Cecil.Pdb", + "MonoMod.Common", + "Newtonsoft.Json", + "StardewModdingAPI", + "SMAPI.Toolkit", + "SMAPI.Toolkit.CoreInterfaces", + "TMXTile", + + // game + framework + "BmFont", + "FAudio-CS", + "GalaxyCSharp", + "GalaxyCSharpGlue", + "Lidgren.Network", + "MonoGame.Framework", + "SkiaSharp", + "Stardew Valley", + "StardewValley.GameData", + "Steamworks.NET", + "TextCopy", + "xTile" + }; + /********* ** Public methods @@ -30,9 +69,11 @@ namespace StardewModdingAPI.ModBuildConfig.Framework /// The folder containing the build output. /// The custom relative file paths provided by the user to ignore. /// Custom regex patterns matching files to ignore when deploying or zipping the mod. + /// The extra assembly types which should be bundled with the mod. + /// The name (without extension or path) for the current mod's DLL. /// Whether to validate that required mod files like the manifest are present. /// The mod package isn't valid. - public ModFileManager(string projectDir, string targetDir, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, bool validateRequiredModFiles) + public ModFileManager(string projectDir, string targetDir, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, ExtraAssemblyTypes bundleAssemblyTypes, string modDllName, bool validateRequiredModFiles) { this.Files = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -48,7 +89,7 @@ namespace StardewModdingAPI.ModBuildConfig.Framework string relativePath = entry.Item1; FileInfo file = entry.Item2; - if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns)) + if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName)) this.Files[relativePath] = file; } @@ -152,39 +193,70 @@ namespace StardewModdingAPI.ModBuildConfig.Framework /// The file's relative path in the package. /// The custom relative file paths provided by the user to ignore. /// Custom regex patterns matching files to ignore when deploying or zipping the mod. - private bool ShouldIgnore(FileInfo file, string relativePath, string[] ignoreFilePaths, Regex[] ignoreFilePatterns) + /// The extra assembly types which should be bundled with the mod. + /// The name (without extension or path) for the current mod's DLL. + private bool ShouldIgnore(FileInfo file, string relativePath, string[] ignoreFilePaths, Regex[] ignoreFilePatterns, ExtraAssemblyTypes bundleAssemblyTypes, string modDllName) { - bool IsAssemblyFile(string baseName) + // apply custom patterns + if (ignoreFilePaths.Any(p => p == relativePath) || ignoreFilePatterns.Any(p => p.IsMatch(relativePath))) + return true; + + // ignore unneeded files + { + bool shouldIgnore = + // release zips + this.EqualsInvariant(file.Extension, ".zip") + + // *.deps.json (only SMAPI's top-level one is used) + || file.Name.EndsWith(".deps.json") + + // code analysis files + || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase) + || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase) + + // translation class builder (not used at runtime) + || ( + file.Name.StartsWith("Pathoschild.Stardew.ModTranslationClassBuilder") + && this.AssemblyFileExtensions.Contains(file.Extension) + ) + + // OS metadata files + || this.EqualsInvariant(file.Name, ".DS_Store") + || this.EqualsInvariant(file.Name, "Thumbs.db"); + if (shouldIgnore) + return true; + } + + // check for bundled assembly types + // When bundleAssemblyTypes is set, *all* dependencies are copied into the build output but only those which match the given assembly types should be bundled. + if (bundleAssemblyTypes != ExtraAssemblyTypes.None) { - return - this.EqualsInvariant(file.Name, $"{baseName}.dll") - || this.EqualsInvariant(file.Name, $"{baseName}.pdb") - || this.EqualsInvariant(file.Name, $"{baseName}.xnb"); + var type = this.GetExtraAssemblyType(file, modDllName); + if (type != ExtraAssemblyTypes.None && !bundleAssemblyTypes.HasFlag(type)) + return true; } - return - // release zips - this.EqualsInvariant(file.Extension, ".zip") + return false; + } - // unneeded *.deps.json (only SMAPI's top-level one is used) - || file.Name.EndsWith(".deps.json") + /// Get the extra assembly type for a file, assuming that the user specified one or more extra types to bundle. + /// The file to check. + /// The name (without extension or path) for the current mod's DLL. + private ExtraAssemblyTypes GetExtraAssemblyType(FileInfo file, string modDllName) + { + string baseName = Path.GetFileNameWithoutExtension(file.Name); + string extension = file.Extension; - // dependencies bundled with SMAPI - || IsAssemblyFile("0Harmony") - || IsAssemblyFile("Newtonsoft.Json") - || IsAssemblyFile("Pathoschild.Stardew.ModTranslationClassBuilder") // not used at runtime + if (baseName == modDllName || !this.AssemblyFileExtensions.Contains(extension)) + return ExtraAssemblyTypes.None; - // code analysis files - || file.Name.EndsWith(".CodeAnalysisLog.xml", StringComparison.OrdinalIgnoreCase) - || file.Name.EndsWith(".lastcodeanalysissucceeded", StringComparison.OrdinalIgnoreCase) + if (this.GameDllNames.Contains(baseName)) + return ExtraAssemblyTypes.Game; - // OS metadata files - || this.EqualsInvariant(file.Name, ".DS_Store") - || this.EqualsInvariant(file.Name, "Thumbs.db") + if (baseName.StartsWith("System.", StringComparison.OrdinalIgnoreCase) || baseName.StartsWith("Microsoft.", StringComparison.OrdinalIgnoreCase)) + return ExtraAssemblyTypes.System; - // custom ignore patterns - || ignoreFilePaths.Any(p => p == relativePath) - || ignoreFilePatterns.Any(p => p.IsMatch(relativePath)); + return ExtraAssemblyTypes.ThirdParty; } /// Get whether a string is equal to another case-insensitively. diff --git a/src/SMAPI.ModBuildConfig/build/smapi.targets b/src/SMAPI.ModBuildConfig/build/smapi.targets index 8ad298d0..de00cbe8 100644 --- a/src/SMAPI.ModBuildConfig/build/smapi.targets +++ b/src/SMAPI.ModBuildConfig/build/smapi.targets @@ -29,7 +29,10 @@ true false true - false + + + + true @@ -45,17 +48,17 @@ **********************************************--> - - - - + + + + - - + + - + @@ -81,6 +84,7 @@ **********************************************--> -- cgit