diff --git a/README.md b/README.md index da1e64d..724b055 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,13 @@ A packaging tool for [löve](https://love2d.org) games * Proper handling of shared libraries (both Lua modules and FFI)! * Packaging of those binaries in archives, including extra files * Versioned builds +* Publish to [itch.io](https://itch.io) via butler * Control and customization along the way: - Configure which targets to build - Which files to include in the .love with a list of include/exclude patterns - Which löve binaries or AppImage to use as the base - Which artifacts to generate or keep - - pre- and postbuild hooks that are able to change the configuration on the fly. For example you can decide dynamically which files to include in the .love (e.g. through parsing asset lists), inject build metadata or just to upload your build automatically afterwards (e.g. via butler to [itch.io](https://itch.io))) + - pre- and postbuild hooks that are able to change the configuration on the fly. For example, you can decide dynamically which files to include in the .love (e.g. through parsing asset lists), inject build metadata, or just to upload your build automatically afterwards (e.g. via [steamcmd](https://partner.steamgames.com/doc/sdk/uploading) to Steam) ## Quickstart diff --git a/makelove/butler.py b/makelove/butler.py new file mode 100644 index 0000000..15eda7f --- /dev/null +++ b/makelove/butler.py @@ -0,0 +1,25 @@ +#! /usr/bin/env python + +import os +import subprocess +import sys + +def publish(itchapp, config, version, targets, build_directory): + if config.get("publish_love", False): + targets = targets.copy() + targets += ['love'] + for platform_target in targets: + target_root = os.path.join(build_directory, platform_target) + for file in os.listdir(target_root): + fpath = os.path.join(target_root, file) + if not os.path.isfile(fpath): + continue + + cmd = "butler push {fpath} {itchapp}:{platform_target} --userversion {version}".format( + fpath = fpath, + version = version, + itchapp = itchapp, + platform_target = platform_target + ) + subprocess.check_call(cmd.split(' ')) + diff --git a/makelove/config.py b/makelove/config.py index d744a56..09e5912 100644 --- a/makelove/config.py +++ b/makelove/config.py @@ -57,6 +57,12 @@ "parameters": val.Dict(val.Any(), val.Any()), } ), + "butler": val.Section( + { + "itchapp": val.UserProjectPair(), + "publish_love": val.Bool(), + } + ), "windows": val.Section( { "exe_metadata": val.Dict(val.String(), val.String()), diff --git a/makelove/makelove.py b/makelove/makelove.py index 7e54981..248d0b2 100644 --- a/makelove/makelove.py +++ b/makelove/makelove.py @@ -10,6 +10,7 @@ import re import pkg_resources +from .butler import publish from .config import get_config, all_targets, init_config_assistant from .hooks import execute_hook from .filelist import FileList @@ -91,6 +92,20 @@ def execute_hooks(hook, config, version, targets, build_directory): config.update(new_config) +def execute_butler(config, version, targets, build_directory): + # Flush so makelove build output is shown before butler errors or output. + sys.stdout.flush() + if "butler" not in config: + return + butler_cfg = config["butler"] + if "itchapp" not in butler_cfg: + # Skipping butler publish because itchapp parameter is missing: + # itchapp = "username/gamename" + return + itchapp = butler_cfg["itchapp"] + publish(itchapp, butler_cfg, version, targets, build_directory) + + def git_ls_tree(path=".", visited=None): p = os.path @@ -357,6 +372,8 @@ def main(): if not "postbuild" in args.disabled_hooks: execute_hooks("postbuild", config, version, targets, build_directory) + execute_butler(config, version, targets, build_directory) + if version != None: with JsonFile(build_log_path, indent=4) as build_log: build_log[-1]["completed"] = True diff --git a/makelove/validators.py b/makelove/validators.py index 443d437..0b035c3 100644 --- a/makelove/validators.py +++ b/makelove/validators.py @@ -89,6 +89,18 @@ def description(self): return "Command" +class UserProjectPair(object): + def validate(self, obj): + if not isinstance(obj, str): + raise ValueError + if '/' not in obj: + raise ValueError + return obj + + def description(self): + return "A username and project name separated by a slash (username/gamename)." + + class List(object): def __init__(self, value_validator): self.value_validator = value_validator diff --git a/makelove_full.toml b/makelove_full.toml index ad5c816..aa09bee 100644 --- a/makelove_full.toml +++ b/makelove_full.toml @@ -66,14 +66,26 @@ prebuild = [ ] postbuild = [ # {build_directory} and {version} will be replaced - # This is obviously only for illustrative purposes, you will probaly want to wrap this in a script - "butler push {build_directory}/win32/SuperGame-win32.zip pfirsich/supergame:win32 --userversion {version}", + # Assuming you have a publish_build script: + "publish_build --buildversion {version} --directory {build_directory}/win32/SuperGame-win32.zip", ] [hooks.parameters] # This section will not be checked when the config is validated and may contain # anything. It is intended as a place for configuration for your hooks. +[butler] +# Automatically push built targets to itch.io after builds. +# Requires butler installed and in your PATH. See https://itch.io/docs/butler/ +# +# For lovejs builds, "Kind of project" must be set to "HTML" on itch.io. After +# your first upload, set your lovejs build to be played in the browser. +itchapp = "pfirsich/supergame" + +# Additionally publish the .love file. Great for Linux users or people having +# problems with the love binaries. +publish_love = true + [windows] # This is the same as archive_files at the top level, but will be added to the archive additionally [windows.archive_files]