mirror of
https://github.com/Pathoschild/SMAPI.git
synced 2026-10-12 02:08:51 +00:00
encapsulate mod file info in deploy task
This commit is contained in:
@@ -92,10 +92,10 @@ public class DeployModTask : Task
|
||||
{
|
||||
try
|
||||
{
|
||||
string manifestPath = Path.Combine(this.ProjectDir, "manifest.json");
|
||||
string manifestPath = Path.Combine(this.ProjectDir, BundleFile.ManifestFileName);
|
||||
if (!new JsonHelper().ReadJsonFileIfExists(manifestPath, out Manifest rawManifest))
|
||||
{
|
||||
this.Log.LogError("[mod build package] The mod's manifest.json file doesn't exist.");
|
||||
this.Log.LogError($"[mod build package] The mod's {BundleFile.ManifestFileName} file doesn't exist.");
|
||||
return false;
|
||||
}
|
||||
manifest = rawManifest;
|
||||
@@ -104,14 +104,14 @@ public class DeployModTask : Task
|
||||
{
|
||||
// log the inner exception, otherwise the message will be generic
|
||||
Exception exToShow = ex.InnerException ?? ex;
|
||||
this.Log.LogError($"[mod build package] The mod's manifest.json file isn't valid JSON: {exToShow.Message}");
|
||||
this.Log.LogError($"[mod build package] The mod's {BundleFile.ManifestFileName} file isn't valid JSON: {exToShow.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// validate manifest fields
|
||||
if (!ManifestValidator.TryValidateFields(manifest, out string error))
|
||||
{
|
||||
this.Log.LogError($"[mod build package] The mod's manifest.json file is invalid: {error}");
|
||||
this.Log.LogError($"[mod build package] The mod's {BundleFile.ManifestFileName} file is invalid: {error}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -301,19 +301,12 @@ public class DeployModTask : Task
|
||||
{
|
||||
foreach (var mod in modPackages)
|
||||
{
|
||||
string relativePath = modPackages.Count == 1
|
||||
string folderPath = modPackages.Count == 1
|
||||
? outputPath
|
||||
: Path.Combine(outputPath, this.EscapeInvalidFilenameCharacters(mod.Key));
|
||||
|
||||
foreach (var file in mod.Value.GetFiles())
|
||||
{
|
||||
string fromPath = file.Value.FullName;
|
||||
string toPath = Path.Combine(relativePath, file.Key);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(toPath)!);
|
||||
|
||||
File.Copy(fromPath, toPath, overwrite: true);
|
||||
}
|
||||
foreach (BundleFile from in mod.Value.GetFiles())
|
||||
from.CopyToFolder(folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,21 +323,19 @@ public class DeployModTask : Task
|
||||
foreach (var mod in modPackages)
|
||||
{
|
||||
string modFolder = this.EscapeInvalidFilenameCharacters(mod.Key);
|
||||
foreach (var file in mod.Value.GetFiles())
|
||||
foreach (BundleFile from in mod.Value.GetFiles())
|
||||
{
|
||||
string relativePath = file.Key;
|
||||
string relativePath = from.RelativePath;
|
||||
|
||||
if (relativePath.Contains('\\'))
|
||||
relativePath = string.Join("/", PathUtilities.GetSegments(relativePath)); // zip files use forward slashes regardless of OS
|
||||
|
||||
FileInfo fileInfo = file.Value;
|
||||
|
||||
string archivePath = modPackages.Count == 1
|
||||
? $"{modFolder}/{relativePath}"
|
||||
: $"{this.ModFolderName}/{modFolder}/{relativePath}";
|
||||
|
||||
using Stream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read);
|
||||
using Stream fileStreamInZip = archive.CreateEntry(archivePath).Open();
|
||||
fileStream.CopyTo(fileStreamInZip);
|
||||
from.CopyToStream(fileStreamInZip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig.Framework;
|
||||
|
||||
/// <summary>A file that should be deployed or zipped as part of a mod.</summary>
|
||||
internal class BundleFile
|
||||
{
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The name of the manifest file.</summary>
|
||||
public const string ManifestFileName = "manifest.json";
|
||||
|
||||
/// <summary>The file's relative path within the mod.</summary>
|
||||
public string RelativePath { get; }
|
||||
|
||||
/// <summary>The file to copy from.</summary>
|
||||
public FileInfo File { get; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="relativePath">The file's relative path within the mod.</param>
|
||||
/// <param name="file">The file to copy from.</param>
|
||||
public BundleFile(string relativePath, FileInfo file)
|
||||
{
|
||||
this.RelativePath = relativePath;
|
||||
this.File = file;
|
||||
}
|
||||
|
||||
/// <summary>Get whether this entry is for the mod's <samp>manifest.json</samp> file.</summary>
|
||||
public bool IsModManifest()
|
||||
{
|
||||
return BundleFile.IsModManifest(this.RelativePath);
|
||||
}
|
||||
|
||||
/// <summary>Copy the file into a destination folder.</summary>
|
||||
/// <param name="folderPath">The folder path to use as the base for the <see cref="RelativePath"/>.</param>
|
||||
public void CopyToFolder(string folderPath)
|
||||
{
|
||||
string toPath = Path.Combine(folderPath, this.RelativePath);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(toPath)!);
|
||||
this.File.CopyTo(toPath, overwrite: true);
|
||||
}
|
||||
|
||||
/// <summary>Copy the file's contents into a stream.</summary>
|
||||
/// <param name="stream">The stream into which to write the file's contents.</param>
|
||||
public void CopyToStream(Stream stream)
|
||||
{
|
||||
using FileStream fromStream = this.File.OpenRead();
|
||||
fromStream.CopyTo(stream);
|
||||
}
|
||||
|
||||
/// <summary>Get whether a relative path is for the mod's <samp>manifest.json</samp> file.</summary>
|
||||
/// <param name="relativePath">The relative path within the mod folder within the mod's folder.</param>
|
||||
public static bool IsModManifest(string relativePath)
|
||||
{
|
||||
return string.Equals(relativePath, BundleFile.ManifestFileName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
@@ -18,11 +18,8 @@ internal class ContentPackFileManager : IModFileManager
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The name of the manifest file.</summary>
|
||||
private readonly string ManifestFileName = "manifest.json";
|
||||
|
||||
/// <summary>The files that are part of the package.</summary>
|
||||
private readonly Dictionary<string, FileInfo> Files = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly List<BundleFile> Files = [];
|
||||
|
||||
|
||||
/*********
|
||||
@@ -44,37 +41,43 @@ internal class ContentPackFileManager : IModFileManager
|
||||
throw GetError($"that folder doesn't exist at {projectDirInfo.FullName}");
|
||||
|
||||
// collect files
|
||||
foreach (FileInfo entry in projectDirInfo.GetFiles("*", SearchOption.AllDirectories))
|
||||
BundleFile manifestEntry = null;
|
||||
foreach (FileInfo file in projectDirInfo.GetFiles("*", SearchOption.AllDirectories))
|
||||
{
|
||||
string relativePath = PathUtilities.GetRelativePath(projectDirInfo.FullName, entry.FullName);
|
||||
FileInfo file = entry;
|
||||
string relativePath = PathUtilities.GetRelativePath(projectDirInfo.FullName, file.FullName);
|
||||
|
||||
if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns))
|
||||
this.Files[relativePath] = file;
|
||||
{
|
||||
BundleFile entry = new(relativePath, file);
|
||||
this.Files.Add(entry);
|
||||
|
||||
if (manifestEntry is null && entry.IsModManifest())
|
||||
manifestEntry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
// validate manifest
|
||||
if (validateManifest)
|
||||
{
|
||||
// get manifest file
|
||||
if (!this.Files.TryGetValue(this.ManifestFileName, out FileInfo manifestFile))
|
||||
throw GetError($"it has no {this.ManifestFileName} file");
|
||||
if (manifestEntry is null)
|
||||
throw GetError($"it has no {BundleFile.ManifestFileName} file");
|
||||
|
||||
// parse file
|
||||
Manifest manifest;
|
||||
try
|
||||
{
|
||||
new JsonHelper().ReadJsonFileIfExists(manifestFile.FullName, out Manifest rawManifest);
|
||||
new JsonHelper().ReadJsonFileIfExists(manifestEntry.File.FullName, out Manifest rawManifest);
|
||||
manifest = rawManifest;
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
{
|
||||
throw GetError($"its {this.ManifestFileName} file isn't valid JSON: {ex.InnerException?.Message ?? ex.Message}");
|
||||
throw GetError($"its {BundleFile.ManifestFileName} file isn't valid JSON: {ex.InnerException?.Message ?? ex.Message}");
|
||||
}
|
||||
|
||||
// validate manifest fields
|
||||
if (!ManifestValidator.TryValidateFields(manifest, out string error))
|
||||
throw GetError($"its {this.ManifestFileName} file is invalid: {error}");
|
||||
throw GetError($"its {BundleFile.ManifestFileName} file is invalid: {error}");
|
||||
|
||||
// validate version
|
||||
if (version == null)
|
||||
@@ -82,7 +85,7 @@ internal class ContentPackFileManager : IModFileManager
|
||||
if (!SemanticVersion.TryParse(version, out ISemanticVersion requiredVersion))
|
||||
throw GetError($"the provided Version value '{version}' isn't a valid semantic version");
|
||||
if (manifest.Version.CompareTo(requiredVersion) != 0)
|
||||
throw GetError($"its {this.ManifestFileName} has version '{manifest.Version}' instead of the required '{requiredVersion}'");
|
||||
throw GetError($"its {BundleFile.ManifestFileName} has version '{manifest.Version}' instead of the required '{requiredVersion}'");
|
||||
}
|
||||
|
||||
UserErrorException GetError(string reasonPhrase)
|
||||
@@ -92,9 +95,9 @@ internal class ContentPackFileManager : IModFileManager
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public IDictionary<string, FileInfo> GetFiles()
|
||||
public IEnumerable<BundleFile> GetFiles()
|
||||
{
|
||||
return new Dictionary<string, FileInfo>(this.Files, StringComparer.OrdinalIgnoreCase);
|
||||
return this.Files;
|
||||
}
|
||||
|
||||
/// <summary>Get whether a content file should be ignored.</summary>
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig.Framework;
|
||||
|
||||
/// <summary>Manages the files that are part of a mod in the release package.</summary>
|
||||
public interface IModFileManager
|
||||
internal interface IModFileManager
|
||||
{
|
||||
/// <summary>Get the files in the mod package.</summary>
|
||||
public IDictionary<string, FileInfo> GetFiles();
|
||||
public IEnumerable<BundleFile> GetFiles();
|
||||
}
|
||||
|
||||
@@ -13,14 +13,11 @@ internal class MainModFileManager : IModFileManager
|
||||
/*********
|
||||
** Fields
|
||||
*********/
|
||||
/// <summary>The name of the manifest file.</summary>
|
||||
private readonly string ManifestFileName = "manifest.json";
|
||||
|
||||
/// <summary>The files that are part of the package.</summary>
|
||||
private readonly Dictionary<string, FileInfo> Files = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly List<BundleFile> Files = [];
|
||||
|
||||
/// <summary>The file extensions used by assembly files.</summary>
|
||||
private readonly ISet<string> AssemblyFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
private readonly HashSet<string> AssemblyFileExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".dll",
|
||||
".exe",
|
||||
@@ -29,8 +26,8 @@ internal class MainModFileManager : IModFileManager
|
||||
};
|
||||
|
||||
/// <summary>The DLLs which match the <see cref="ExtraAssemblyTypes.Game"/> type.</summary>
|
||||
private readonly ISet<string> GameDllNames = new HashSet<string>
|
||||
{
|
||||
private readonly HashSet<string> GameDllNames =
|
||||
[
|
||||
// SMAPI
|
||||
"0Harmony",
|
||||
"Mono.Cecil",
|
||||
@@ -56,7 +53,7 @@ internal class MainModFileManager : IModFileManager
|
||||
"Steamworks.NET",
|
||||
"TextCopy",
|
||||
"xTile"
|
||||
};
|
||||
];
|
||||
|
||||
|
||||
/*********
|
||||
@@ -80,33 +77,36 @@ internal class MainModFileManager : IModFileManager
|
||||
throw new UserErrorException("Could not create mod package because no build output was found.");
|
||||
|
||||
// collect files
|
||||
foreach (Tuple<string, FileInfo> entry in this.GetPossibleFiles(projectDir, targetDir))
|
||||
BundleFile manifestEntry = null;
|
||||
foreach (BundleFile entry in this.GetPossibleFiles(projectDir, targetDir))
|
||||
{
|
||||
string relativePath = entry.Item1;
|
||||
FileInfo file = entry.Item2;
|
||||
if (!this.ShouldIgnore(entry.File, entry.RelativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName))
|
||||
{
|
||||
this.Files.Add(entry);
|
||||
|
||||
if (!this.ShouldIgnore(file, relativePath, ignoreFilePaths, ignoreFilePatterns, bundleAssemblyTypes, modDllName))
|
||||
this.Files[relativePath] = file;
|
||||
if (manifestEntry is null && entry.IsModManifest())
|
||||
manifestEntry = entry;
|
||||
}
|
||||
}
|
||||
|
||||
// check for required files
|
||||
if (validateRequiredModFiles)
|
||||
{
|
||||
// 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.");
|
||||
if (manifestEntry is null)
|
||||
throw new UserErrorException($"Could not create mod package because no {BundleFile.ManifestFileName} was found in the project or build output.");
|
||||
|
||||
// DLL
|
||||
// ReSharper disable once SimplifyLinqExpression
|
||||
if (!this.Files.Any(p => !p.Key.EndsWith(".dll")))
|
||||
if (!this.Files.Any(p => !p.RelativePath.EndsWith(".dll")))
|
||||
throw new UserErrorException("Could not create mod package because no .dll file was found in the project or build output.");
|
||||
}
|
||||
}
|
||||
|
||||
///<inheritdoc/>
|
||||
public IDictionary<string, FileInfo> GetFiles()
|
||||
public IEnumerable<BundleFile> GetFiles()
|
||||
{
|
||||
return new Dictionary<string, FileInfo>(this.Files, StringComparer.OrdinalIgnoreCase);
|
||||
return this.Files;
|
||||
}
|
||||
|
||||
|
||||
@@ -117,15 +117,15 @@ internal class MainModFileManager : IModFileManager
|
||||
/// <param name="projectDir">The folder containing the project files.</param>
|
||||
/// <param name="targetDir">The folder containing the build output.</param>
|
||||
/// <returns>Returns tuples containing the relative path within the mod folder, and the file to copy to it.</returns>
|
||||
private IEnumerable<Tuple<string, FileInfo>> GetPossibleFiles(string projectDir, string targetDir)
|
||||
private IEnumerable<BundleFile> GetPossibleFiles(string projectDir, string targetDir)
|
||||
{
|
||||
// project manifest
|
||||
bool hasProjectManifest = false;
|
||||
{
|
||||
FileInfo manifest = new(Path.Combine(projectDir, this.ManifestFileName));
|
||||
FileInfo manifest = new(Path.Combine(projectDir, BundleFile.ManifestFileName));
|
||||
if (manifest.Exists)
|
||||
{
|
||||
yield return Tuple.Create(this.ManifestFileName, manifest);
|
||||
yield return new BundleFile(BundleFile.ManifestFileName, manifest);
|
||||
hasProjectManifest = true;
|
||||
}
|
||||
}
|
||||
@@ -138,7 +138,7 @@ internal class MainModFileManager : IModFileManager
|
||||
foreach (FileInfo file in translationsFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||
{
|
||||
string relativePath = PathUtilities.GetRelativePath(projectDir, file.FullName);
|
||||
yield return Tuple.Create(relativePath, file);
|
||||
yield return new BundleFile(relativePath, file);
|
||||
}
|
||||
hasProjectTranslations = true;
|
||||
}
|
||||
@@ -151,7 +151,7 @@ internal class MainModFileManager : IModFileManager
|
||||
foreach (FileInfo file in assetsFolder.EnumerateFiles("*", SearchOption.AllDirectories))
|
||||
{
|
||||
string relativePath = PathUtilities.GetRelativePath(projectDir, file.FullName);
|
||||
yield return Tuple.Create(relativePath, file);
|
||||
yield return new BundleFile(relativePath, file);
|
||||
}
|
||||
hasAssetsFolder = true;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ internal class MainModFileManager : IModFileManager
|
||||
string[] segments = PathUtilities.GetSegments(relativePath);
|
||||
|
||||
// prefer project manifest/i18n/assets files
|
||||
if (hasProjectManifest && this.EqualsInvariant(relativePath, this.ManifestFileName))
|
||||
if (hasProjectManifest && BundleFile.IsModManifest(relativePath))
|
||||
continue;
|
||||
if (hasProjectTranslations && this.EqualsInvariant(segments[0], "i18n"))
|
||||
continue;
|
||||
@@ -173,7 +173,7 @@ internal class MainModFileManager : IModFileManager
|
||||
continue;
|
||||
|
||||
// add file
|
||||
yield return Tuple.Create(relativePath, file);
|
||||
yield return new BundleFile(relativePath, file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user