rewrite mod build package per new docs
This commit is contained in:
parent
cd93382c64
commit
475efa12fe
|
@ -71,6 +71,11 @@ Finally, you can disable the zip creation with this:
|
|||
<EnableModZip>False</EnableModZip>
|
||||
```
|
||||
|
||||
Or only create it in release builds with this:
|
||||
```xml
|
||||
<EnableModZip Condition="$(Configuration) != 'Release'">False</EnableModZip>
|
||||
```
|
||||
|
||||
### Game path
|
||||
The package usually detects where your game is installed automatically. If it can't find your game
|
||||
or you have multiple installs, you can specify the path yourself. There's two ways to do that:
|
||||
|
@ -118,14 +123,15 @@ still compile on a different computer).
|
|||
|
||||
## Troubleshoot
|
||||
### "Failed to find the game install path"
|
||||
That error means the package couldn't find your game. You need to specify the game path yourself;
|
||||
see _[Game path](#game-path)_ above.
|
||||
That error means the package couldn't find your game. You can specify the game path yourself; see
|
||||
_[Game path](#game-path)_ above.
|
||||
|
||||
## Release notes
|
||||
### 2.0
|
||||
* Mods are now copied into the `Mods` folder automatically (configurable).
|
||||
* The release zip is now created automatically in your build output folder (configurable).
|
||||
* 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.
|
||||
|
||||
|
|
|
@ -2,11 +2,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Web.Script.Serialization;
|
||||
using Microsoft.Build.Framework;
|
||||
using Microsoft.Build.Utilities;
|
||||
using StardewModdingAPI.Common;
|
||||
using StardewModdingAPI.ModBuildConfig.Framework;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig
|
||||
{
|
||||
|
@ -16,25 +14,53 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
/*********
|
||||
** Properties
|
||||
*********/
|
||||
/// <summary>The name of the manifest file.</summary>
|
||||
private readonly string ManifestFileName = "manifest.json";
|
||||
/// <summary>The MSBuild platforms recognised by the build configuration.</summary>
|
||||
private readonly HashSet<string> ValidPlatforms = new HashSet<string>(new[] { "OSX", "Unix", "Windows_NT" }, StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
/// <summary>The name of the game's main executable file.</summary>
|
||||
private string GameExeName => this.Platform == "Windows_NT"
|
||||
? "Stardew Valley.exe"
|
||||
: "StardewValley.exe";
|
||||
|
||||
/// <summary>The name of SMAPI's main executable file.</summary>
|
||||
private readonly string SmapiExeName = "StardewModdingAPI.exe";
|
||||
|
||||
|
||||
/*********
|
||||
** Accessors
|
||||
*********/
|
||||
/// <summary>The mod files to pack.</summary>
|
||||
/// <summary>The name of the mod folder.</summary>
|
||||
[Required]
|
||||
public ITaskItem[] Files { get; set; }
|
||||
|
||||
/// <summary>The name of the mod.</summary>
|
||||
[Required]
|
||||
public string ModName { get; set; }
|
||||
public string ModFolderName { get; set; }
|
||||
|
||||
/// <summary>The absolute or relative path to the folder which should contain the generated zip file.</summary>
|
||||
[Required]
|
||||
public string ModZipPath { get; set; }
|
||||
|
||||
/// <summary>The folder containing the project files.</summary>
|
||||
[Required]
|
||||
public string ProjectDir { get; set; }
|
||||
|
||||
/// <summary>The folder containing the build output.</summary>
|
||||
[Required]
|
||||
public string TargetDir { get; set; }
|
||||
|
||||
/// <summary>The folder containing the game files.</summary>
|
||||
[Required]
|
||||
public string GameDir { get; set; }
|
||||
|
||||
/// <summary>The MSBuild OS value.</summary>
|
||||
[Required]
|
||||
public string Platform { get; set; }
|
||||
|
||||
/// <summary>Whether to enable copying the mod files into the game's Mods folder.</summary>
|
||||
[Required]
|
||||
public bool EnableModDeploy { get; set; }
|
||||
|
||||
/// <summary>Whether to enable the release zip.</summary>
|
||||
[Required]
|
||||
public bool EnableModZip { get; set; }
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
|
@ -43,33 +69,80 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
/// <returns>true if the task successfully executed; otherwise, false.</returns>
|
||||
public override bool Execute()
|
||||
{
|
||||
if (!this.EnableModDeploy && !this.EnableModZip)
|
||||
return true; // nothing to do
|
||||
|
||||
try
|
||||
{
|
||||
string modVersion = this.GetManifestVersion();
|
||||
this.CreateReleaseZip(this.Files, this.ModName, modVersion, this.ModZipPath);
|
||||
// validate context
|
||||
if (!this.ValidPlatforms.Contains(this.Platform))
|
||||
throw new UserErrorException($"The mod build package doesn't recognise OS type '{this.Platform}'.");
|
||||
if (!Directory.Exists(this.GameDir))
|
||||
throw new UserErrorException("The mod build package can't find your game path. See https://github.com/Pathoschild/SMAPI/blob/develop/docs/mod-build-config.md for help specifying it.");
|
||||
if (!File.Exists(Path.Combine(this.GameDir, this.GameExeName)))
|
||||
throw new UserErrorException($"The mod build package found a game folder at {this.GameDir}, but it doesn't contain the {this.GameExeName} file. If this folder is invalid, delete it and the package will autodetect another game install path.");
|
||||
if (!File.Exists(Path.Combine(this.GameDir, this.SmapiExeName)))
|
||||
throw new UserErrorException($"The mod build package found a game folder at {this.GameDir}, but it doesn't contain SMAPI. You need to install SMAPI before building the mod.");
|
||||
|
||||
// get mod info
|
||||
ModFileManager package = new ModFileManager(this.ProjectDir, this.TargetDir);
|
||||
|
||||
// deploy mod files
|
||||
if (this.EnableModDeploy)
|
||||
{
|
||||
string outputPath = Path.Combine(this.GameDir, "Mods", this.EscapeInvalidFilenameCharacters(this.ModFolderName));
|
||||
this.Log.LogMessage(MessageImportance.High, $"The mod build package is copying the mod files to {outputPath}...");
|
||||
this.CreateModFolder(package.GetFiles(), outputPath);
|
||||
}
|
||||
|
||||
// create release zip
|
||||
if (this.EnableModZip)
|
||||
{
|
||||
this.Log.LogMessage(MessageImportance.High, $"The mod build package is generating a release zip at {this.ModZipPath} for {this.ModFolderName}...");
|
||||
this.CreateReleaseZip(package.GetFiles(), this.ModFolderName, package.GetManifestVersion(), this.ModZipPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (UserErrorException ex)
|
||||
{
|
||||
this.Log.LogErrorFromException(ex);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Log.LogError($"The mod build package failed trying to deploy the mod.\n{ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*********
|
||||
** Private methods
|
||||
*********/
|
||||
/// <summary>Copy the mod files into the game's mod folder.</summary>
|
||||
/// <param name="files">The files to include.</param>
|
||||
/// <param name="modFolderPath">The folder path to create with the mod files.</param>
|
||||
private void CreateModFolder(IDictionary<string, FileInfo> files, string modFolderPath)
|
||||
{
|
||||
Directory.CreateDirectory(modFolderPath);
|
||||
foreach (var entry in files)
|
||||
{
|
||||
string fromPath = entry.Value.FullName;
|
||||
string toPath = Path.Combine(modFolderPath, entry.Key);
|
||||
File.Copy(fromPath, toPath, overwrite: true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Create a release zip in the recommended format for uploading to mod sites.</summary>
|
||||
/// <param name="files">The files to include.</param>
|
||||
/// <param name="modName">The name of the mod.</param>
|
||||
/// <param name="modVersion">The mod version string.</param>
|
||||
/// <param name="outputFolderPath">The absolute or relative path to the folder which should contain the generated zip file.</param>
|
||||
private void CreateReleaseZip(ITaskItem[] files, string modName, string modVersion, string outputFolderPath)
|
||||
private void CreateReleaseZip(IDictionary<string, FileInfo> files, string modName, string modVersion, string outputFolderPath)
|
||||
{
|
||||
// get names
|
||||
string zipName = this.EscapeInvalidFilenameCharacters($"{modName}-{modVersion}.zip");
|
||||
string zipName = this.EscapeInvalidFilenameCharacters($"{modName} {modVersion}.zip");
|
||||
string folderName = this.EscapeInvalidFilenameCharacters(modName);
|
||||
string zipPath = Path.Combine(outputFolderPath, zipName);
|
||||
|
||||
|
@ -78,83 +151,24 @@ namespace StardewModdingAPI.ModBuildConfig
|
|||
using (Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write))
|
||||
using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create))
|
||||
{
|
||||
foreach (ITaskItem file in files)
|
||||
foreach (var fileEntry in files)
|
||||
{
|
||||
string relativePath = fileEntry.Key;
|
||||
FileInfo file = fileEntry.Value;
|
||||
|
||||
// get file info
|
||||
string filePath = file.ItemSpec;
|
||||
string entryName = folderName + '/' + file.GetMetadata("RecursiveDir") + file.GetMetadata("Filename") + file.GetMetadata("Extension");
|
||||
string filePath = file.FullName;
|
||||
string entryName = folderName + '/' + relativePath.Replace(Path.DirectorySeparatorChar, '/');
|
||||
if (new FileInfo(filePath).Directory.Name.Equals("i18n", StringComparison.InvariantCultureIgnoreCase))
|
||||
entryName = Path.Combine("i18n", entryName);
|
||||
|
||||
// add to zip
|
||||
using (Stream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||
using (Stream fileStreamInZip = archive.CreateEntry(entryName).Open())
|
||||
{
|
||||
fileStream.CopyTo(fileStreamInZip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Get a semantic version from the mod manifest (if available).</summary>
|
||||
/// <exception cref="InvalidOperationException">The manifest file wasn't found or is invalid.</exception>
|
||||
private string GetManifestVersion()
|
||||
{
|
||||
// find manifest file
|
||||
ITaskItem file = this.Files.FirstOrDefault(p => this.ManifestFileName.Equals(Path.GetFileName(p.ItemSpec), StringComparison.InvariantCultureIgnoreCase));
|
||||
if (file == null)
|
||||
throw new InvalidOperationException($"The mod must include a {this.ManifestFileName} file.");
|
||||
|
||||
// read content
|
||||
string json = File.ReadAllText(file.ItemSpec);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
throw new InvalidOperationException($"The mod's {this.ManifestFileName} file must not be empty.");
|
||||
|
||||
// parse JSON
|
||||
IDictionary<string, object> data;
|
||||
try
|
||||
{
|
||||
data = this.Parse(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new InvalidOperationException($"The mod's {this.ManifestFileName} couldn't be parsed. It doesn't seem to be valid JSON.", ex);
|
||||
}
|
||||
|
||||
// get version field
|
||||
object versionObj = data.ContainsKey("Version") ? data["Version"] : null;
|
||||
if (versionObj == null)
|
||||
throw new InvalidOperationException($"The mod's {this.ManifestFileName} must have a version field.");
|
||||
|
||||
// get version string
|
||||
if (versionObj is IDictionary<string, object> 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+
|
||||
}
|
||||
|
||||
/// <summary>Get a case-insensitive dictionary matching the given JSON.</summary>
|
||||
/// <param name="json">The JSON to parse.</param>
|
||||
private IDictionary<string, object> Parse(string json)
|
||||
{
|
||||
IDictionary<string, object> MakeCaseInsensitive(IDictionary<string, object> dict)
|
||||
{
|
||||
foreach (var field in dict.ToArray())
|
||||
{
|
||||
if (field.Value is IDictionary<string, object> value)
|
||||
dict[field.Key] = MakeCaseInsensitive(value);
|
||||
}
|
||||
return new Dictionary<string, object>(dict, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
IDictionary<string, object> data = (IDictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
|
||||
return MakeCaseInsensitive(data);
|
||||
}
|
||||
|
||||
/// <summary>Get a copy of a filename with all invalid filename characters substituted.</summary>
|
||||
/// <param name="name">The filename.</param>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>Manages the files that are part of a mod package.</summary>
|
||||
internal class ModFileManager
|
||||
{
|
||||
/*********
|
||||
** Properties
|
||||
*********/
|
||||
/// <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 IDictionary<string, FileInfo> Files;
|
||||
|
||||
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="projectDir">The folder containing the project files.</param>
|
||||
/// <param name="targetDir">The folder containing the build output.</param>
|
||||
/// <exception cref="UserErrorException">The mod package isn't valid.</exception>
|
||||
public ModFileManager(string projectDir, string targetDir)
|
||||
{
|
||||
this.Files = new Dictionary<string, FileInfo>(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.");
|
||||
}
|
||||
|
||||
/// <summary>Get the files in the mod package.</summary>
|
||||
public IDictionary<string, FileInfo> GetFiles()
|
||||
{
|
||||
return new Dictionary<string, FileInfo>(this.Files, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Get a semantic version from the mod manifest.</summary>
|
||||
/// <exception cref="UserErrorException">The manifest is missing or invalid.</exception>
|
||||
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<string, object> 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<string, object> 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
|
||||
*********/
|
||||
/// <summary>Get a case-insensitive dictionary matching the given JSON.</summary>
|
||||
/// <param name="json">The JSON to parse.</param>
|
||||
private IDictionary<string, object> Parse(string json)
|
||||
{
|
||||
IDictionary<string, object> MakeCaseInsensitive(IDictionary<string, object> dict)
|
||||
{
|
||||
foreach (var field in dict.ToArray())
|
||||
{
|
||||
if (field.Value is IDictionary<string, object> value)
|
||||
dict[field.Key] = MakeCaseInsensitive(value);
|
||||
}
|
||||
return new Dictionary<string, object>(dict, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
IDictionary<string, object> data = (IDictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json);
|
||||
return MakeCaseInsensitive(data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
|
||||
namespace StardewModdingAPI.ModBuildConfig.Framework
|
||||
{
|
||||
/// <summary>A user error whose message can be displayed to the user.</summary>
|
||||
internal class UserErrorException : Exception
|
||||
{
|
||||
/*********
|
||||
** Public methods
|
||||
*********/
|
||||
/// <summary>Construct an instance.</summary>
|
||||
/// <param name="message">The error message.</param>
|
||||
public UserErrorException(string message)
|
||||
: base(message) { }
|
||||
}
|
||||
}
|
|
@ -39,12 +39,15 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DeployModTask.cs" />
|
||||
<Compile Include="Framework\UserErrorException.cs" />
|
||||
<Compile Include="Framework\ModFileManager.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="assets\nuget-icon.pdn" />
|
||||
<None Include="build\smapi.targets">
|
||||
<SubType>Designer</SubType>
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="package.nuspec">
|
||||
<SubType>Designer</SubType>
|
||||
|
|
|
@ -7,15 +7,24 @@
|
|||
<!--*********************************************
|
||||
** Find the basic mod metadata
|
||||
**********************************************-->
|
||||
<!--######
|
||||
## import developer's custom settings (if any)
|
||||
#######-->
|
||||
<!-- import developer's custom settings (if any) -->
|
||||
<Import Condition="$(OS) != 'Windows_NT' AND Exists('$(HOME)\stardewvalley.targets')" Project="$(HOME)\stardewvalley.targets" />
|
||||
<Import Condition="$(OS) == 'Windows_NT' AND Exists('$(USERPROFILE)\stardewvalley.targets')" Project="$(USERPROFILE)\stardewvalley.targets" />
|
||||
|
||||
<!--######
|
||||
## find platform + game path
|
||||
#######-->
|
||||
<!-- set setting defaults -->
|
||||
<PropertyGroup>
|
||||
<!-- map legacy settings -->
|
||||
<ModFolderName Condition="'$(ModFolderName)' == '' AND '$(DeployModFolderName)' != ''">$(DeployModFolderName)</ModFolderName>
|
||||
<ModZipPath Condition="'$(ModZipPath)' == '' AND '$(DeployModZipTo)' != ''">$(DeployModZipTo)</ModZipPath>
|
||||
|
||||
<!-- set default settings -->
|
||||
<ModFolderName Condition="'$(ModFolderName)' == ''">$(MSBuildProjectName)</ModFolderName>
|
||||
<ModZipPath Condition="'$(ModZipPath)' == ''">$(TargetDir)</ModZipPath>
|
||||
<EnableModDeploy Condition="'$(EnableModDeploy)' == ''">True</EnableModDeploy>
|
||||
<EnableModZip Condition="'$(EnableModZip)' == ''">True</EnableModZip>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- find platform + game path -->
|
||||
<Choose>
|
||||
<When Condition="$(OS) == 'Unix' OR $(OS) == 'OSX'">
|
||||
<PropertyGroup>
|
||||
|
@ -106,52 +115,21 @@
|
|||
|
||||
|
||||
<!--*********************************************
|
||||
** Perform build logic
|
||||
** Deploy mod files & create release zip after build
|
||||
**********************************************-->
|
||||
<!--######
|
||||
## validate metadata before build
|
||||
#######-->
|
||||
<Target Name="BeforeBuild">
|
||||
<!-- show error for unknown platform -->
|
||||
<Error Condition="'$(OS)' != 'OSX' AND '$(OS)' != 'Unix' AND '$(OS)' != 'Windows_NT'" Text="The build config package doesn't recognise OS type '$(OS)'." />
|
||||
<Target Name="AfterBuild">
|
||||
<DeployModTask
|
||||
ModFolderName="$(ModFolderName)"
|
||||
ModZipPath="$(ModZipPath)"
|
||||
|
||||
<!-- if game path is invalid, show one user-friendly error instead of a slew of reference errors -->
|
||||
<Error Condition="!Exists('$(GamePath)')" Text="Failed to find the game install path. See https://github.com/Pathoschild/Stardew.ModBuildConfig#troubleshoot for help." />
|
||||
<Error Condition="'$(OS)' == 'Windows_NT' AND !Exists('$(GamePath)\Stardew Valley.exe')" Text="Found a game folder at $(GamePath), but it doesn't contain Stardew Valley. You should delete this folder if it's empty." />
|
||||
<Error Condition="'$(OS)' != 'Windows_NT' AND !Exists('$(GamePath)\StardewValley.exe')" Text="Found a game folder at $(GamePath), but it doesn't contain Stardew Valley. You should delete this folder if it's empty." />
|
||||
<Error Condition="!Exists('$(GamePath)\StardewModdingAPI.exe')" Text="Found a game folder at $(GamePath), but it doesn't contain SMAPI." />
|
||||
</Target>
|
||||
EnableModDeploy="$(EnableModDeploy)"
|
||||
EnableModZip="$(EnableModZip)"
|
||||
|
||||
<!--######
|
||||
## Deploy files after build
|
||||
#######-->
|
||||
<Target Name="AfterBuild" Condition="'$(DeployModFolderName)' != '' OR '$(DeployModZipTo)' != ''">
|
||||
<!--collect file paths-->
|
||||
<PropertyGroup>
|
||||
<ModDeployPath>$(GamePath)\Mods\$(DeployModFolderName)</ModDeployPath>
|
||||
<DeployModZipTo Condition="'$(OS)' != 'Windows_NT'"><!--disable on Linux/Mac where CodeTaskFactory doesn't seem to be available--></DeployModZipTo>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<BuildFiles Include="$(TargetDir)\**\*.*" Exclude="$(TargetDir)\manifest.json;$(TargetDir)\i18n\**\*.*" />
|
||||
ProjectDir="$(ProjectDir)"
|
||||
TargetDir="$(TargetDir)"
|
||||
GameDir="$(GamePath)"
|
||||
|
||||
<BuildFiles Include="$(ProjectDir)\manifest.json" Condition="'@(BuildFiles)' != ''" />
|
||||
<BuildFiles Include="$(TargetDir)\manifest.json" Condition="'@(BuildFiles)' != '' AND !EXISTS('$(ProjectDir)\manifest.json')" />
|
||||
|
||||
<I18nFiles Include="$(ProjectDir)\i18n\*.json" Condition="'@(BuildFiles)' != ''" />
|
||||
<I18nFiles Include="$(TargetDir)\i18n\*.json" Condition="'@(BuildFiles)' != '' AND !EXISTS('$(ProjectDir)\i18n')" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--validate paths-->
|
||||
<Error Text="Could not deploy mod automatically because no build output was found." Condition="'@(BuildFiles)' == ''" />
|
||||
<Error Text="Could not deploy mod automatically because no manifest.json was found in the project or build output." Condition="!Exists('$(TargetDir)\manifest.json') AND !Exists('$(ProjectDir)\manifest.json')" />
|
||||
|
||||
<!-- copy mod files into mod folder if <DeployModFolderName> property is set -->
|
||||
<Message Text="Deploying mod to $(ModDeployPath)..." Importance="high" Condition="'$(DeployModFolderName)' != ''" />
|
||||
<Copy SourceFiles="@(BuildFiles)" DestinationFolder="$(ModDeployPath)\%(RecursiveDir)" SkipUnchangedFiles="true" Condition="'$(DeployModFolderName)' != ''" />
|
||||
<Copy SourceFiles="@(I18nFiles)" DestinationFolder="$(ModDeployPath)\i18n" SkipUnchangedFiles="true" Condition="'$(DeployModFolderName)' != ''" />
|
||||
|
||||
<!-- create release zip if <DeployModZipTo> property is set -->
|
||||
<Message Text="Generating mod release at $(DeployModZipTo)\$(MSBuildProjectName).zip..." Importance="high" Condition="'$(DeployModZipTo)' != ''" />
|
||||
<DeployModTask ModName="$(MSBuildProjectName)" Files="@(BuildFiles);@(I18nFiles)" ModZipPath="$(ModZipPath)" Condition="'$(DeployModZipTo)' != ''" />
|
||||
Platform="$(OS)"
|
||||
/>
|
||||
</Target>
|
||||
</Project>
|
||||
|
|
Loading…
Reference in New Issue