diff options
author | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-10-07 23:20:36 -0400 |
---|---|---|
committer | Jesse Plamondon-Willard <github@jplamondonw.com> | 2017-10-07 23:20:36 -0400 |
commit | d0dd2f7ba729de6be749d326a2fed78988ba9d7b (patch) | |
tree | a22127da6a8900e9f29bbb847bfd5d3347f6b952 /src/ModBuildConfig | |
parent | 7889676ea24cafc945899bf25608784e3f5bc9e0 (diff) | |
parent | 5928f5f86c4493ddb6b89bce0b7d0fb73a884c09 (diff) | |
download | SMAPI-d0dd2f7ba729de6be749d326a2fed78988ba9d7b.tar.gz SMAPI-d0dd2f7ba729de6be749d326a2fed78988ba9d7b.tar.bz2 SMAPI-d0dd2f7ba729de6be749d326a2fed78988ba9d7b.zip |
Merge branch 'add-mod-build-config' into develop
Diffstat (limited to 'src/ModBuildConfig')
-rw-r--r-- | src/ModBuildConfig/README.md | 121 | ||||
-rw-r--r-- | src/ModBuildConfig/assets/nuget-icon.pdn | bin | 0 -> 7401 bytes | |||
-rw-r--r-- | src/ModBuildConfig/assets/nuget-icon.png | bin | 0 -> 5054 bytes | |||
-rw-r--r-- | src/ModBuildConfig/build/smapi.targets | 273 | ||||
-rw-r--r-- | src/ModBuildConfig/package.nuspec | 22 | ||||
-rw-r--r-- | src/ModBuildConfig/release-notes.md | 28 |
6 files changed, 444 insertions, 0 deletions
diff --git a/src/ModBuildConfig/README.md b/src/ModBuildConfig/README.md new file mode 100644 index 00000000..c261e705 --- /dev/null +++ b/src/ModBuildConfig/README.md @@ -0,0 +1,121 @@ +**Stardew.ModBuildConfig** is an open-source NuGet package which automates the build configuration +for [Stardew Valley](http://stardewvalley.net/) [SMAPI](https://github.com/Pathoschild/SMAPI) mods. + +The package... + +* lets you write your mod once, and compile it on any computer. It detects the current platform + (Linux, Mac, or Windows) and game install path, and injects the right references automatically. +* configures Visual Studio so you can debug into the mod code when the game is running (_Windows + only_). +* packages the mod automatically into the game's mod folder when you build the code (_optional_). + +## Contents +* [Install](#install) +* [Simplify mod development](#simplify-mod-development) +* [Troubleshoot](#troubleshoot) +* [Versions](#versions) + +## Install +**When creating a new mod:** + +1. Create an empty library project. +2. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig). +3. [Write your code](http://canimod.com/guides/creating-a-smapi-mod). +4. Compile on any platform. + +**When migrating an existing mod:** + +1. Remove any project references to `Microsoft.Xna.*`, `MonoGame`, Stardew Valley, + `StardewModdingAPI`, and `xTile`. +2. Reference the [`Pathoschild.Stardew.ModBuildConfig` NuGet package](https://www.nuget.org/packages/Pathoschild.Stardew.ModBuildConfig). +3. Compile on any platform. + +## Simplify mod development +### Package your mod into the game folder automatically +You can copy your mod files into the `Mods` folder automatically each time you build, so you don't +need to do it manually: + +1. Edit your mod's `.csproj` file. +2. Add this block above the first `</PropertyGroup>` line: + + ```xml + <DeployModFolderName>$(MSBuildProjectName)</DeployModFolderName> + ``` + +That's it! Each time you build, the files in `<game path>\Mods\<mod name>` will be updated with +your `manifest.json`, build output, and any `i18n` files. + +Notes: +* To add custom files, just [add them to the build output](https://stackoverflow.com/a/10828462/262123). +* To customise the folder name, just replace `$(MSBuildProjectName)` with the folder name you want. +* If your project references another mod, make sure the reference is [_not_ marked 'copy local'](https://msdn.microsoft.com/en-us/library/t1zz5y8c(v=vs.100).aspx). + +### Debug into the mod code (Windows-only) +Stepping into your mod code when the game is running is straightforward, since this package injects +the configuration automatically. To do it: + +1. [Package your mod into the game folder automatically](#package-your-mod-into-the-game-folder-automatically). +2. Launch the project with debugging in Visual Studio or MonoDevelop. + +This will deploy your mod files into the game folder, launch SMAPI, and attach a debugger +automatically. Now you can step through your code, set breakpoints, etc. + +### Create release zips automatically (Windows-only) +You can create the mod package automatically when you build: + +1. Edit your mod's `.csproj` file. +2. Add this block above the first `</PropertyGroup>` line: + + ```xml + <DeployModZipTo>$(SolutionDir)\_releases</DeployModZipTo> + ``` + +That's it! Each time you build, the mod files will be zipped into `_releases\<mod name>.zip`. (You +can change the value to save the zips somewhere else.) + +## Troubleshoot +### "Failed to find the game install path" +That error means the package couldn't figure out where the game is installed. You need to specify +the game location yourself. There's two ways to do that: + +* **Option 1: set the path globally.** + _This will apply to every project that uses version 1.5+ of package._ + + 1. Get the full folder path containing the Stardew Valley executable. + 2. Create this file path: + + platform | path + --------- | ---- + Linux/Mac | `~/stardewvalley.targets` + Windows | `%USERPROFILE%\stardewvalley.targets` + + 3. Save the file with this content: + + ```xml + <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <GamePath>PATH_HERE</GamePath> + </PropertyGroup> + </Project> + ``` + + 4. Replace `PATH_HERE` with your custom game install path. + +* **Option 2: set the path in the project file.** + _(You'll need to do it for every project that uses the package.)_ + 1. Get the folder path containing the Stardew Valley `.exe` file. + 2. Add this to your `.csproj` file under the `<Project` line: + + ```xml + <PropertyGroup> + <GamePath>PATH_HERE</GamePath> + </PropertyGroup> + ``` + + 3. Replace `PATH_HERE` with your custom game install path. + +The configuration will check your custom path first, then fall back to the default paths (so it'll +still compile on a different computer). + +## Versions +See [release notes](release-notes.md). diff --git a/src/ModBuildConfig/assets/nuget-icon.pdn b/src/ModBuildConfig/assets/nuget-icon.pdn Binary files differnew file mode 100644 index 00000000..7bd5c0c5 --- /dev/null +++ b/src/ModBuildConfig/assets/nuget-icon.pdn diff --git a/src/ModBuildConfig/assets/nuget-icon.png b/src/ModBuildConfig/assets/nuget-icon.png Binary files differnew file mode 100644 index 00000000..611cdf88 --- /dev/null +++ b/src/ModBuildConfig/assets/nuget-icon.png diff --git a/src/ModBuildConfig/build/smapi.targets b/src/ModBuildConfig/build/smapi.targets new file mode 100644 index 00000000..a1b6aab3 --- /dev/null +++ b/src/ModBuildConfig/build/smapi.targets @@ -0,0 +1,273 @@ +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <!--********************************************* + ** Define build tasks + **********************************************--> + <!--###### + ## create a release zip file for a mod (CodeTaskFactory only available on Windows?) + #######--> + <UsingTask TaskName="CreateModReleaseZip" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll" Condition="'$(OS)' == 'Windows_NT'"> + <ParameterGroup> + <ModName ParameterType="System.String" Required="true" /> + <Files ParameterType="Microsoft.Build.Framework.ITaskItem[]" Required="true" /> + <OutputFolderPath ParameterType="System.String" Required="true" /> + </ParameterGroup> + <Task> + <Reference Include="System.IO" /> + <Reference Include="System.IO.Compression" /> + <Reference Include="System.Web.Extensions"/> + <Code Type="Class" Language="cs"> + <![CDATA[ + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Compression; + using System.Web.Script.Serialization; + using Microsoft.Build.Framework; + using Microsoft.Build.Utilities; + + /// <summary>A build task which packs mod files into a conventional release zip.</summary> + public class CreateModReleaseZip : Task, ITask + { + /********* + ** Accessors + *********/ + /// <summary>The mod files to pack.</summary> + public ITaskItem[] Files { get; set; } + + /// <summary>The name of the mod.</param> + public string ModName { get; set; } + + /// <summary>The absolute or relative path to the folder which should contain the generated zip file.</summary> + public string OutputFolderPath { get; set; } + + + /********* + ** Public methods + *********/ + public override bool Execute() + { + try + { + // create output path if needed + Directory.CreateDirectory(OutputFolderPath); + + // get zip filename + string fileName = string.Format("{0}-{1}.zip", this.ModName, this.GetManifestVersion()); + + // clear old zip file if present + string zipPath = Path.Combine(OutputFolderPath, fileName); + if (File.Exists(zipPath)) + File.Delete(zipPath); + + // create zip file + using (Stream zipStream = new FileStream(zipPath, FileMode.Create, FileAccess.Write)) + using (ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create)) + { + foreach (ITaskItem file in Files) + { + // get file info + string filePath = file.ItemSpec; + string entryName = ModName + '/' + file.GetMetadata("RecursiveDir") + file.GetMetadata("Filename") + file.GetMetadata("Extension"); + 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); + } + } + } + + return true; + } + catch (Exception ex) + { + Log.LogErrorFromException(ex); + return false; + } + } + + /// <summary>Get a semantic version from the mod manifest (if available).</summary> + public string GetManifestVersion() + { + // Get the file JSON string + string json = ""; + foreach(ITaskItem file in Files) + { + if(Path.GetFileName(file.ItemSpec).ToLower() != "manifest.json") + continue; + json = File.ReadAllText(file.ItemSpec); + break; + } + + // Serialize the manifest json into a data object, then get a version object from that. + IDictionary<string, object> data = (IDictionary<string, object>)new JavaScriptSerializer().DeserializeObject(json); + IDictionary<string, object> version = (IDictionary<string, object>)data["Version"]; + + // Store our version numbers for ease of use + int major = (int)version["MajorVersion"]; + int minor = (int)version["MinorVersion"]; + int patch = (int)version["PatchVersion"]; + + return String.Format("{0}.{1}.{2}", major, minor, patch); + } + } + ]]> + </Code> + </Task> + </UsingTask> + + + <!--********************************************* + ** Find the basic mod metadata + **********************************************--> + <!--###### + ## 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 + #######--> + <Choose> + <When Condition="$(OS) == 'Unix' OR $(OS) == 'OSX'"> + <PropertyGroup> + <!-- Linux --> + <GamePath Condition="!Exists('$(GamePath)')">$(HOME)/GOG Games/Stardew Valley/game</GamePath> + <GamePath Condition="!Exists('$(GamePath)')">$(HOME)/.local/share/Steam/steamapps/common/Stardew Valley</GamePath> + + <!-- Mac (may be 'Unix' or 'OSX') --> + <GamePath Condition="!Exists('$(GamePath)')">/Applications/Stardew Valley.app/Contents/MacOS</GamePath> + <GamePath Condition="!Exists('$(GamePath)')">$(HOME)/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS</GamePath> + </PropertyGroup> + </When> + <When Condition="$(OS) == 'Windows_NT'"> + <PropertyGroup> + <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\GalaxyClient\Games\Stardew Valley</GamePath> + <GamePath Condition="!Exists('$(GamePath)')">C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley</GamePath> + <GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\GOG.com\Games\1453375253', 'PATH', null, RegistryView.Registry32))</GamePath> + <GamePath Condition="!Exists('$(GamePath)')">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 413150', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32))</GamePath> + </PropertyGroup> + </When> + </Choose> + + + <!--********************************************* + ** Inject the assembly references and debugging configuration + **********************************************--> + <Choose> + <When Condition="$(OS) == 'Windows_NT'"> + <!-- references --> + <ItemGroup> + <Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86"> + <Private>false</Private> + </Reference> + <Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86"> + <Private>false</Private> + </Reference> + <Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86"> + <Private>false</Private> + </Reference> + <Reference Include="Microsoft.Xna.Framework.Xact, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86"> + <Private>false</Private> + </Reference> + <Reference Include="Stardew Valley"> + <HintPath>$(GamePath)\Stardew Valley.exe</HintPath> + <Private>false</Private> + </Reference> + <Reference Include="StardewModdingAPI"> + <HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath> + <Private>false</Private> + </Reference> + <Reference Include="xTile, Version=2.0.4.0, Culture=neutral, processorArchitecture=x86"> + <HintPath>$(GamePath)\xTile.dll</HintPath> + <Private>false</Private> + <SpecificVersion>False</SpecificVersion> + </Reference> + </ItemGroup> + + <!-- launch game for debugging --> + <PropertyGroup> + <StartAction>Program</StartAction> + <StartProgram>$(GamePath)\StardewModdingAPI.exe</StartProgram> + <StartWorkingDirectory>$(GamePath)</StartWorkingDirectory> + </PropertyGroup> + </When> + <Otherwise> + <!-- references --> + <ItemGroup> + <Reference Include="MonoGame.Framework"> + <HintPath>$(GamePath)\MonoGame.Framework.dll</HintPath> + <Private>false</Private> + <SpecificVersion>False</SpecificVersion> + </Reference> + <Reference Include="StardewValley"> + <HintPath>$(GamePath)\StardewValley.exe</HintPath> + <Private>false</Private> + </Reference> + <Reference Include="StardewModdingAPI"> + <HintPath>$(GamePath)\StardewModdingAPI.exe</HintPath> + <Private>false</Private> + </Reference> + <Reference Include="xTile"> + <HintPath>$(GamePath)\xTile.dll</HintPath> + <Private>false</Private> + </Reference> + </ItemGroup> + </Otherwise> + </Choose> + + + <!--********************************************* + ** Perform build logic + **********************************************--> + <!--###### + ## 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)'." /> + + <!-- 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> + + <!--###### + ## 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\**\*.*" /> + + <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)' != ''" /> + <CreateModReleaseZip ModName="$(MSBuildProjectName)" Files="@(BuildFiles);@(I18nFiles)" OutputFolderPath="$(DeployModZipTo)" Condition="'$(DeployModZipTo)' != ''" /> + </Target> +</Project> diff --git a/src/ModBuildConfig/package.nuspec b/src/ModBuildConfig/package.nuspec new file mode 100644 index 00000000..b8e96481 --- /dev/null +++ b/src/ModBuildConfig/package.nuspec @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> + <metadata> + <id>Pathoschild.Stardew.ModBuildConfig</id> + <version>1.7.1</version> + <title>MSBuild config for Stardew Valley mods</title> + <authors>Pathoschild</authors> + <owners>Pathoschild</owners> + <requireLicenseAcceptance>false</requireLicenseAcceptance> + <licenseUrl>https://github.com/Pathoschild/Stardew.ModBuildConfig/blob/1.7.1/LICENSE.txt</licenseUrl> + <projectUrl>https://github.com/Pathoschild/Stardew.ModBuildConfig#readme</projectUrl> + <iconUrl>https://raw.githubusercontent.com/Pathoschild/Stardew.ModBuildConfig/1.7.1/assets/nuget-icon.png</iconUrl> + <description>Automates the build configuration for crossplatform Stardew Valley SMAPI mods.</description> + <releaseNotes> + 1.7 added an option to create release zips on build and added a reference to XNA's XACT library for audio-related mods. + 1.7.1 fixed an issue where i18n folders were flattened, and ensures that the manifest/i18n files in the project take precedence over those in the build output if both are present.</releaseNotes> + </metadata> + <files> + <file src="build/smapi.targets" target="build/Pathoschild.Stardew.ModBuildConfig.targets" /> + <file src="readme.md" /> + </files> +</package> diff --git a/src/ModBuildConfig/release-notes.md b/src/ModBuildConfig/release-notes.md new file mode 100644 index 00000000..ff2734f8 --- /dev/null +++ b/src/ModBuildConfig/release-notes.md @@ -0,0 +1,28 @@ +## Release notes +### 1.6 +* Added support for deploying mod files into `Mods` automatically. +* Added a build error if a game folder is found, but doesn't contain Stardew Valley or SMAPI. + +### 1.5 +* Added support for setting a custom game path globally. +* Added default GOG path on Mac. + +### 1.4 +* Fixed detection of non-default game paths on 32-bit Windows. +* Removed support for SilVerPLuM (discontinued). +* Removed support for overriding the target platform (no longer needed since SMAPI crossplatforms mods automatically). + +### 1.3 +* Added support for non-default game paths on Windows. + +### 1.2 +* Exclude game binaries from mod build output. + +### 1.1 +* Added support for overriding the target platform. + +### 1.0 +* Initial release. +* Added support for detecting the game path automatically. +* Added support for injecting XNA/MonoGame references automatically based on the OS. +* Added support for mod builders like SilVerPLuM. |