diff options
Diffstat (limited to 'src/StardewModdingAPI')
-rw-r--r-- | src/StardewModdingAPI/Config.cs | 11 | ||||
-rw-r--r-- | src/StardewModdingAPI/Framework/DeprecationManager.cs | 124 | ||||
-rw-r--r-- | src/StardewModdingAPI/Manifest.cs | 10 | ||||
-rw-r--r-- | src/StardewModdingAPI/Mod.cs | 45 | ||||
-rw-r--r-- | src/StardewModdingAPI/Program.cs | 17 | ||||
-rw-r--r-- | src/StardewModdingAPI/StardewModdingAPI.csproj | 1 | ||||
-rw-r--r-- | src/StardewModdingAPI/Version.cs | 13 |
7 files changed, 212 insertions, 9 deletions
diff --git a/src/StardewModdingAPI/Config.cs b/src/StardewModdingAPI/Config.cs index d811d69d..91503a83 100644 --- a/src/StardewModdingAPI/Config.cs +++ b/src/StardewModdingAPI/Config.cs @@ -99,6 +99,17 @@ namespace StardewModdingAPI return this as T; } } + + + /********* + ** Protected methods + *********/ + /// <summary>Construct an instance.</summary> + protected Config() + { + Program.DeprecationManager.Warn("the Config class", "1.0"); + Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(Mod.BaseConfigPath)}", "1.0"); // typically used to construct config, avoid redundant warnings + } } /// <summary>Provides extension methods for <see cref="Config"/> classes.</summary> diff --git a/src/StardewModdingAPI/Framework/DeprecationManager.cs b/src/StardewModdingAPI/Framework/DeprecationManager.cs new file mode 100644 index 00000000..2d4ff614 --- /dev/null +++ b/src/StardewModdingAPI/Framework/DeprecationManager.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace StardewModdingAPI.Framework +{ + /// <summary>Manages deprecation warnings.</summary> + internal class DeprecationManager + { + /********* + ** Properties + *********/ + /// <summary>The friendly mod names treated as deprecation warning sources (assembly full name => mod name).</summary> + private readonly IDictionary<string, string> ModNamesByAssembly = new Dictionary<string, string>(); + + /// <summary>The deprecations which have already been logged (as 'mod name::noun phrase::version').</summary> + private readonly HashSet<string> LoggedDeprecations = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); + + + /********* + ** Public methods + *********/ + /// <summary>Register a mod as a possible source of deprecation warnings.</summary> + /// <param name="assembly">The mod assembly.</param> + /// <param name="name">The mod's friendly name.</param> + public void AddMod(Assembly assembly, string name) + { + this.ModNamesByAssembly[assembly.FullName] = name; + } + + /// <summary>Log a deprecation warning.</summary> + /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param> + /// <param name="version">The SMAPI version which deprecated it.</param> + public void Warn(string nounPhrase, string version) + { + this.Warn(this.GetSourceNameFromStack(), nounPhrase, version); + } + + /// <summary>Log a deprecation warning.</summary> + /// <param name="source">The friendly mod name which used the deprecated code.</param> + /// <param name="nounPhrase">A noun phrase describing what is deprecated.</param> + /// <param name="version">The SMAPI version which deprecated it.</param> + public void Warn(string source, string nounPhrase, string version) + { + if (source != null && !this.MarkWarned(source, nounPhrase, version)) + return; + + Log.Debug(source != null + ? $"NOTE: {source} used {nounPhrase}, which is deprecated since SMAPI {version}. It will work fine for now, but may be removed in a future version of SMAPI." + : $"NOTE: an unknown mod used {nounPhrase}, which is deprecated since SMAPI {version}. It will work fine for now, but may be removed in a future version of SMAPI.\n{Environment.StackTrace}" + ); + } + + /// <summary>Mark a deprecation warning as already logged.</summary> + /// <param name="nounPhrase">A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").</param> + /// <param name="version">The SMAPI version which deprecated it.</param> + /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns> + public bool MarkWarned(string nounPhrase, string version) + { + return this.MarkWarned(this.GetSourceNameFromStack(), nounPhrase, version); + } + + /// <summary>Mark a deprecation warning as already logged.</summary> + /// <param name="source">The friendly name of the assembly which used the deprecated code.</param> + /// <param name="nounPhrase">A noun phrase describing what is deprecated (e.g. "the Extensions.AsInt32 method").</param> + /// <param name="version">The SMAPI version which deprecated it.</param> + /// <returns>Returns whether the deprecation was successfully marked as warned. Returns <c>false</c> if it was already marked.</returns> + public bool MarkWarned(string source, string nounPhrase, string version) + { + if (string.IsNullOrWhiteSpace(source)) + throw new InvalidOperationException("The deprecation source cannot be empty."); + + string key = $"{source}::{nounPhrase}::{version}"; + if (this.LoggedDeprecations.Contains(key)) + return false; + this.LoggedDeprecations.Add(key); + return true; + } + + /// <summary>Get whether a type implements the given virtual method.</summary> + /// <param name="subtype">The type to check.</param> + /// <param name="baseType">The base type which declares the virtual method.</param> + /// <param name="name">The method name.</param> + public bool IsVirtualMethodImplemented(Type subtype, Type baseType, string name) + { + MethodInfo method = subtype.GetMethod(nameof(Mod.Entry), new[] { typeof(object[]) }); + return method.DeclaringType != baseType; + } + + + /********* + ** Private methods + *********/ + /// <summary>Get the friendly name for the closest assembly registered as a source of deprecation warnings.</summary> + /// <returns>Returns the source name, or <c>null</c> if no registered assemblies were found.</returns> + private string GetSourceNameFromStack() + { + // get stack frames + StackTrace stack = new StackTrace(); + StackFrame[] frames = stack.GetFrames(); + if (frames == null) + return null; + + // search stack for a source assembly + foreach (StackFrame frame in frames) + { + // get assembly name + MethodBase method = frame.GetMethod(); + Type type = method.ReflectedType; + if (type == null) + continue; + string assemblyName = type.Assembly.FullName; + + // get name if it's a registered source + if (this.ModNamesByAssembly.ContainsKey(assemblyName)) + return this.ModNamesByAssembly[assemblyName]; + } + + // no known assembly found + return null; + } + } +} diff --git a/src/StardewModdingAPI/Manifest.cs b/src/StardewModdingAPI/Manifest.cs index 89ce7904..6819cbd1 100644 --- a/src/StardewModdingAPI/Manifest.cs +++ b/src/StardewModdingAPI/Manifest.cs @@ -9,6 +9,10 @@ namespace StardewModdingAPI /********* ** Accessors *********/ + /// <summary>Whether the manifest defined the deprecated <see cref="Authour"/> field.</summary> + [JsonIgnore] + internal bool UsedAuthourField { get; private set; } + /// <summary>The mod name.</summary> public virtual string Name { get; set; } = ""; @@ -20,7 +24,11 @@ namespace StardewModdingAPI public virtual string Authour { get { return this.Author; } - set { this.Author = value; } + set + { + this.UsedAuthourField = true; + this.Author = value; + } } /// <summary>The mod version.</summary> diff --git a/src/StardewModdingAPI/Mod.cs b/src/StardewModdingAPI/Mod.cs index c1cc99d4..fa70d291 100644 --- a/src/StardewModdingAPI/Mod.cs +++ b/src/StardewModdingAPI/Mod.cs @@ -7,6 +7,12 @@ namespace StardewModdingAPI public class Mod { /********* + ** Properties + *********/ + /// <summary>The backing field for <see cref="Mod.PathOnDisk"/>.</summary> + private string _pathOnDisk; + + /********* ** Accessors *********/ /// <summary>Provides methods for interacting with the mod directory, such as read/writing a config file or custom JSON files.</summary> @@ -16,16 +22,44 @@ namespace StardewModdingAPI public Manifest Manifest { get; internal set; } /// <summary>The full path to the mod's directory on the disk.</summary> - public string PathOnDisk { get; internal set; } + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.DirectoryPath) + " instead")] + public string PathOnDisk + { + get + { + Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(PathOnDisk)}", "1.0"); + return this._pathOnDisk; + } + internal set { this._pathOnDisk = value; } + } /// <summary>The full path to the mod's <c>config.json</c> file on the disk.</summary> - public string BaseConfigPath => Path.Combine(this.PathOnDisk, "config.json"); + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.ReadConfig) + " instead")] + public string BaseConfigPath + { + get + { + Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(BaseConfigPath)}", "1.0"); + Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(PathOnDisk)}", "1.0"); // avoid redundant warnings + return Path.Combine(this.PathOnDisk, "config.json"); + } + } /// <summary>The full path to the per-save configs folder (if <see cref="StardewModdingAPI.Manifest.PerSaveConfigs"/> is <c>true</c>).</summary> + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.ReadJsonFile) + " instead")] public string PerSaveConfigFolder => this.GetPerSaveConfigFolder(); /// <summary>The full path to the per-save configuration file for the current save (if <see cref="StardewModdingAPI.Manifest.PerSaveConfigs"/> is <c>true</c>).</summary> - public string PerSaveConfigPath => Constants.CurrentSavePathExists ? Path.Combine(this.PerSaveConfigFolder, Constants.SaveFolderName + ".json") : ""; + [Obsolete("Use " + nameof(Mod.Helper) + "." + nameof(ModHelper.ReadJsonFile) + " instead")] + public string PerSaveConfigPath + { + get + { + Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(PerSaveConfigPath)}", "1.0"); + Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(PerSaveConfigFolder)}", "1.0"); // avoid redundant warnings + return Constants.CurrentSavePathExists ? Path.Combine(this.PerSaveConfigFolder, Constants.SaveFolderName + ".json") : ""; + } + } /********* @@ -46,9 +80,12 @@ namespace StardewModdingAPI /// <summary>Get the full path to the per-save configuration file for the current save (if <see cref="StardewModdingAPI.Manifest.PerSaveConfigs"/> is <c>true</c>).</summary> private string GetPerSaveConfigFolder() { + Program.DeprecationManager.Warn($"{nameof(Mod)}.{nameof(PerSaveConfigFolder)}", "1.0"); + Program.DeprecationManager.MarkWarned($"{nameof(Mod)}.{nameof(PathOnDisk)}", "1.0"); // avoid redundant warnings + if (!this.Manifest.PerSaveConfigs) { - Log.AsyncR($"The mod [{this.Manifest.Name}] is not configured to use per-save configs."); + Log.Error($"The mod [{this.Manifest.Name}] is not configured to use per-save configs."); return ""; } return Path.Combine(this.PathOnDisk, "psconfigs"); diff --git a/src/StardewModdingAPI/Program.cs b/src/StardewModdingAPI/Program.cs index c0129036..3831e3d4 100644 --- a/src/StardewModdingAPI/Program.cs +++ b/src/StardewModdingAPI/Program.cs @@ -54,6 +54,8 @@ namespace StardewModdingAPI /// <summary>The game's build type (i.e. GOG vs Steam).</summary> public static int BuildType => (int)Program.StardewProgramType.GetField("buildType", BindingFlags.Public | BindingFlags.Static).GetValue(null); + /// <summary>Manages deprecation warnings.</summary> + internal static readonly DeprecationManager DeprecationManager = new DeprecationManager(); /********* ** Public methods @@ -267,6 +269,10 @@ namespace StardewModdingAPI Log.Error($"{errorPrefix}: manifest doesn't specify an entry DLL."); continue; } + + // log deprecated fields + if(manifest.UsedAuthourField) + Program.DeprecationManager.Warn(manifest.Name, $"{nameof(Manifest)}.{nameof(Manifest.Authour)}", "1.0"); } catch (Exception ex) { @@ -277,6 +283,7 @@ namespace StardewModdingAPI // create per-save directory if (manifest.PerSaveConfigs) { + Program.DeprecationManager.Warn($"{nameof(Manifest)}.{nameof(Manifest.PerSaveConfigs)}", "1.0"); try { string psDir = Path.Combine(directory, "psconfigs"); @@ -312,13 +319,21 @@ namespace StardewModdingAPI Mod modEntry = (Mod)modAssembly.CreateInstance(modEntryType.ToString()); if (modEntry != null) { + // add as possible source of deprecation warnings + Program.DeprecationManager.AddMod(modAssembly, manifest.Name); + + // hook up mod modEntry.Helper = helper; modEntry.PathOnDisk = directory; modEntry.Manifest = manifest; Log.Info($"Loaded mod: {modEntry.Manifest.Name} by {modEntry.Manifest.Author}, v{modEntry.Manifest.Version} | {modEntry.Manifest.Description}\n@ {targDll}"); Program.ModsLoaded += 1; - modEntry.Entry(); // obsolete + modEntry.Entry(); // deprecated modEntry.Entry(modEntry.Helper); + + // raise deprecation warning for old Entry() method + if (Program.DeprecationManager.IsVirtualMethodImplemented(modEntryType, typeof(Mod), nameof(Mod.Entry))) + Program.DeprecationManager.Warn(manifest.Name, $"an old version of {nameof(Mod)}.{nameof(Mod.Entry)}", "1.0"); } } else diff --git a/src/StardewModdingAPI/StardewModdingAPI.csproj b/src/StardewModdingAPI/StardewModdingAPI.csproj index 451e5961..0b55a925 100644 --- a/src/StardewModdingAPI/StardewModdingAPI.csproj +++ b/src/StardewModdingAPI/StardewModdingAPI.csproj @@ -195,6 +195,7 @@ <Compile Include="Events\MineEvents.cs" /> <Compile Include="Events\PlayerEvents.cs" /> <Compile Include="Events\TimeEvents.cs" /> + <Compile Include="Framework\DeprecationManager.cs" /> <Compile Include="Framework\UpdateHelper.cs" /> <Compile Include="Framework\GitRelease.cs" /> <Compile Include="Inheritance\ChangeType.cs" /> diff --git a/src/StardewModdingAPI/Version.cs b/src/StardewModdingAPI/Version.cs index b1903d7b..75195820 100644 --- a/src/StardewModdingAPI/Version.cs +++ b/src/StardewModdingAPI/Version.cs @@ -32,8 +32,15 @@ namespace StardewModdingAPI /// <summary>Obsolete.</summary> [JsonIgnore] - [Obsolete("Use `Version.ToString()` instead.")] - public string VersionString => this.ToString(); + [Obsolete("Use " + nameof(Version) + "." + nameof(Version.ToString) + " instead.")] + public string VersionString + { + get + { + Program.DeprecationManager.Warn($"{nameof(Version)}.{nameof(Version.VersionString)}", "1.0"); + return this.ToString(); + } + } /********* @@ -59,7 +66,7 @@ namespace StardewModdingAPI var match = Version.Regex.Match(version); if (!match.Success) throw new FormatException($"The input '{version}' is not a semantic version."); - + this.MajorVersion = int.Parse(match.Groups["major"].Value); this.MinorVersion = match.Groups["minor"].Success ? int.Parse(match.Groups["minor"].Value) : 0; this.PatchVersion = match.Groups["patch"].Success ? int.Parse(match.Groups["patch"].Value) : 0; |