summaryrefslogtreecommitdiff
path: root/src/ModBuildConfig
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2017-10-07 23:20:36 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2017-10-07 23:20:36 -0400
commitd0dd2f7ba729de6be749d326a2fed78988ba9d7b (patch)
treea22127da6a8900e9f29bbb847bfd5d3347f6b952 /src/ModBuildConfig
parent7889676ea24cafc945899bf25608784e3f5bc9e0 (diff)
parent5928f5f86c4493ddb6b89bce0b7d0fb73a884c09 (diff)
downloadSMAPI-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.md121
-rw-r--r--src/ModBuildConfig/assets/nuget-icon.pdnbin0 -> 7401 bytes
-rw-r--r--src/ModBuildConfig/assets/nuget-icon.pngbin0 -> 5054 bytes
-rw-r--r--src/ModBuildConfig/build/smapi.targets273
-rw-r--r--src/ModBuildConfig/package.nuspec22
-rw-r--r--src/ModBuildConfig/release-notes.md28
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
new file mode 100644
index 00000000..7bd5c0c5
--- /dev/null
+++ b/src/ModBuildConfig/assets/nuget-icon.pdn
Binary files differ
diff --git a/src/ModBuildConfig/assets/nuget-icon.png b/src/ModBuildConfig/assets/nuget-icon.png
new file mode 100644
index 00000000..611cdf88
--- /dev/null
+++ b/src/ModBuildConfig/assets/nuget-icon.png
Binary files differ
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.