add deprecation warnings (#165)
This commit is contained in:
parent
0749fdcbe5
commit
8d8b640779
|
@ -6,7 +6,7 @@
|
|||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForBuiltInTypes/@EntryValue">UseVarWhenEvident</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EAddAccessorOwnerDeclarationBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -6,6 +6,12 @@ namespace StardewModdingAPI
|
|||
/// <summary>The base class for a mod.</summary>
|
||||
public class Mod
|
||||
{
|
||||
/*********
|
||||
** Properties
|
||||
*********/
|
||||
/// <summary>The backing field for <see cref="Mod.PathOnDisk"/>.</summary>
|
||||
private string _pathOnDisk;
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue