├── LICENSE.md ├── README.md ├── build ├── config.py ├── discord.py ├── list-changes ├── post-commit ├── push └── version.py /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 John Watson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Build system for Gravity Ace, a Godot Engine game 2 | 3 | I shared this with the thought that it might be useful to you. It may not work for your game and I made no effort to make it cross platform. That said, it's Python... results may vary. 4 | 5 | Please do not send pull requests. Take it and go in peace. 6 | 7 | # Setup 8 | 9 | Pushing to itch.io requires configuring butler: https://itch.io/docs/butler/ 10 | 11 | Pushing to Steam requires configuring the Steam SDK: https://partner.steamgames.com/doc/sdk/uploading 12 | 13 | For Steam you'll also need to create a file `BUILDDIR/.steam` where the first line is your Steam username and the second line is your Steam password. 14 | 15 | - Start with `config.py` and plug in your paths 16 | - Run `build` to build (builds and creates changelog) 17 | - Run `push` to upload to itch and steam 18 | - Run `list-changes` to print a changelog 19 | - Add `post-commit` to your git hooks to post commits to Discord 20 | 21 | Discord integration works via webhooks. Create a webhook URL for your channel and place in `BUILDDIR/.discord-git`. That webhook is used for the `post-commit` git hook. Create another webhook URL and place in `BUILDDIR/.discord-announce`. That webhook is used for posting changelogs from `push`. 22 | 23 | The `post-commit` script will ignore commits containing `#private`. 24 | 25 | # Buy Gravity Ace 26 | 27 | [Gravity Ace is available on Steam and Itch.io!](https://gravityace.com/) 28 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import version 4 | import subprocess 5 | import os 6 | import config 7 | 8 | # Update version.cfg with current commit 9 | branch = version.get_branch() 10 | print('On branch %s' % branch) 11 | if branch != 'master': 12 | print('*** NOT ON MASTER!') 13 | exit() 14 | 15 | commit = version.get_latest_commit() 16 | print('Updating build commit in version.cfg to %s' % commit) 17 | version.set_commit(commit) 18 | 19 | # Increment version.cfg version number and tag git 20 | old_version = version.get_version() 21 | print('Incrementing to version number %s and tagging git' % str(old_version + 1)) 22 | version.increment_version() 23 | new_version = version.get_version() 24 | print('Updating version number from %d to %d' % (old_version, new_version)) 25 | print('Tagging git commit %s with v%s' % (version.get_commit(), version.get_public_version())) 26 | version.tag_git() 27 | 28 | # Generate changelog since last build 29 | version.generate_changelog() 30 | 31 | builds = { 32 | "Linux/X11": "linux64/gravity.x86_64", 33 | "Windows Desktop 64 bit": "win64/gravity-win64.exe", 34 | #"Windows Desktop 32 bit": "win32/gravity-win32.exe", 35 | "MacOS": "macos/gravity-macos.app", 36 | #"HTML5": "html5/index.html" 37 | } 38 | 39 | for platform, file in builds.items(): 40 | print("Building %s..." % platform) 41 | command = [config.GODOT, '--path', config.PROJECT, config.EXPORT, platform, "%s/%s" % (config.BUILDDIR, file)] 42 | command.extend(config.OPTIONS) 43 | subprocess.check_output(command) 44 | 45 | if platform == "MacOS": 46 | os.rename("%s/%s" % (config.BUILDDIR, file), "%s/%s.zip" % (config.BUILDDIR, file)) 47 | 48 | print('\nAll done!') 49 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Full path to the Godot executable 2 | GODOT = '/path/to/godot' 3 | 4 | # Full path to the Godot project directory you want to build 5 | PROJECT = '/path/to/game/directory' 6 | 7 | # Full path where executables should be build 8 | BUILDDIR = '/path/to/build_directory' 9 | 10 | # Godot options for build process 11 | OPTIONS = ['--no-window', '--resolution', '640x360'] # --no-window doesn't work on linux 12 | EXPORT = '--export' # Use --export-debug for a debug release 13 | 14 | # Full path to steamcmd and the build vdf file (see https://partner.steamgames.com/doc/sdk/uploading) 15 | STEAM_COMMAND = '/path/to/steamcmd.sh' 16 | STEAM_APP_CONFIG = '/path/to/app.vdf' 17 | 18 | # Your itch.io project and full path to butler (see https://itch.io/docs/butler/) 19 | ITCH_PROJECT = 'username/game' 20 | BUTLER = '/path/to/butler' 21 | 22 | # Version cfg file for your game (see https://docs.godotengine.org/en/stable/classes/class_configfile.html) 23 | VERSION_FILE = '/path/to/version.cfg' 24 | 25 | # Changelog location 26 | CHANGELOG = '/tmp/changelog.md' -------------------------------------------------------------------------------- /discord.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import version 4 | import config 5 | 6 | 7 | def post_changelog(): 8 | ''' 9 | Post latest changelog to discord 10 | ''' 11 | log = version.get_changelog() 12 | print('Posting changelog to discord...\n') 13 | 14 | color = 38377 15 | title = log.split('\n')[0].replace('# ', '') 16 | description = '\n'.join(log.split('\n')[1:]).lstrip() 17 | 18 | discord_json = { 19 | "content": "A new build has just been uploaded!", 20 | "embeds": [ 21 | { 22 | "title": title, 23 | "description": description, 24 | "color": color, 25 | } 26 | ] 27 | } 28 | 29 | response = requests.post( 30 | get_discord_webhook('discord-announce'), 31 | data=json.dumps(discord_json), 32 | headers={ 'Content-Type': 'application/json' } 33 | ) 34 | 35 | if response.status_code < 200 or response.status_code > 299: 36 | raise ValueError('Discord error %s:\n%s' % (response.status_code, response.text)) 37 | 38 | 39 | def post_commit_message(title, message): 40 | ''' 41 | Post commit message to Discord 42 | ''' 43 | color = 38377 44 | 45 | discord_json = { 46 | "content": "**New commit: " + title + "**" 47 | } 48 | 49 | if message: 50 | discord_json["embeds"] = [ 51 | { 52 | "description": message, 53 | "color": color, 54 | } 55 | ] 56 | 57 | 58 | response = requests.post( 59 | get_discord_webhook('discord-git'), 60 | data=json.dumps(discord_json), 61 | headers={ 'Content-Type': 'application/json' } 62 | ) 63 | 64 | if response.status_code < 200 or response.status_code > 299: 65 | raise ValueError('Discord error %s:\n%s' % (response.status_code, response.text)) 66 | 67 | 68 | def get_discord_webhook(name): 69 | webhook = None 70 | with open(config.BUILDDIR + "/." + name, "r") as f: 71 | webhook = f.readline().rstrip() 72 | 73 | return webhook -------------------------------------------------------------------------------- /list-changes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import version 4 | 5 | print(version.get_commits_since_last_tag()) 6 | -------------------------------------------------------------------------------- /post-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import version 4 | import discord 5 | import sys 6 | 7 | # Get latest commit and commit message 8 | commit = version.get_latest_commit() 9 | (title, message) = version.get_latest_commit_message() 10 | 11 | # Get branch 12 | branch = version.get_branch() 13 | if branch != 'master': 14 | title = "[%s] %s" % (branch, title) 15 | 16 | # Create discord title 17 | title_with_commit = '%s (%s)' % (title, commit) 18 | 19 | # Quit if #private 20 | if title_with_commit.lower().find("#private") >= 0: 21 | sys.exit() 22 | 23 | # Post to discord 24 | discord.post_commit_message(title_with_commit, message) 25 | print("Posted commit %s to #git" % (commit)) 26 | -------------------------------------------------------------------------------- /push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import subprocess 4 | import discord 5 | import version 6 | import config 7 | 8 | # Check version 9 | ver = version.get_public_version() 10 | print('This is version %s\n' % ver) 11 | answer = input('Is that correct? (y/n/yes/no, default=no) ') 12 | if answer == 'y' or answer == 'yes': 13 | print('OK!\n') 14 | else: 15 | print('\nAborted') 16 | exit() 17 | 18 | # Check changelog 19 | print('Pushing a new public build! Exciting!\n') 20 | print('Here is the current changelog:\n\n%s\n' % version.get_changelog()) 21 | answer = input('Does the changelog look okay? (y/n/yes/no, default=no) ') 22 | if answer == 'y' or answer == 'yes': 23 | print('OK!\n') 24 | else: 25 | print('\nGet writing!') 26 | exit() 27 | 28 | # Push to itch.io 29 | 30 | builds = { 31 | "linux-64": "linux64", 32 | "windows-64": "win64", 33 | "macos": "macos/gravity-macos.app.zip" 34 | } 35 | 36 | for destination, folder in builds.items(): 37 | print("[itch] Pushing %s..." % destination) 38 | command = [config.BUTLER, 'push', '--fix-permissions', "%s/%s" % (config.BUILDDIR, folder), "%s:%s" % (config.ITCH_PROJECT, destination)] 39 | command.extend(['--userversion', str(version.get_version())]) 40 | subprocess.check_output(command) 41 | 42 | 43 | # Push to Steam 44 | username = None 45 | password = None 46 | with open(config.BUILDDIR + "/.steam", "r") as f: 47 | username = f.readline().rstrip() 48 | password = f.readline().rstrip() 49 | print("[steam] Pushing builds...") 50 | command = [config.STEAM_COMMAND, '+login', username, password, "+run_app_build", config.STEAM_APP_CONFIG, "+quit"] 51 | output = subprocess.check_output(command) 52 | print(output.decode('utf-8')) 53 | 54 | # Post changelog to discord 55 | # Check version 56 | print('Uploading is done!') 57 | answer = input('Post changelog to Discord? (y/n/yes/no, default=no) ') 58 | if answer == 'y' or answer == 'yes': 59 | print('OK!\n') 60 | discord.post_changelog() 61 | else: 62 | print('Skipping Discord post\n') 63 | 64 | print('\nAll done!') 65 | -------------------------------------------------------------------------------- /version.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import configparser 3 | import config 4 | 5 | 6 | def get_version(): 7 | cfg = configparser.ConfigParser() 8 | cfg.read(config.VERSION_FILE) 9 | 10 | return int(cfg['version']['version']) 11 | 12 | 13 | def get_status(): 14 | cfg = configparser.ConfigParser() 15 | cfg.read(config.VERSION_FILE) 16 | 17 | status = cfg['version']['status'] 18 | status = status.replace('"', '') # Godot does strings differently 19 | 20 | return status 21 | 22 | 23 | def get_commit(): 24 | cfg = configparser.ConfigParser() 25 | cfg.read(config.VERSION_FILE) 26 | 27 | commit = cfg['version']['commit'] 28 | commit = commit.replace('"', '') # Godot does strings differently 29 | 30 | return commit 31 | 32 | 33 | def get_public_version(): 34 | version = get_version() 35 | status = get_status() 36 | 37 | return '%s-%s' % (version, status) 38 | 39 | 40 | def increment_version(): 41 | cfg = configparser.ConfigParser() 42 | cfg.read(config.VERSION_FILE) 43 | 44 | version = int(cfg['version']['version']) + 1 45 | cfg['version']['version'] = str(version) 46 | 47 | with open(config.VERSION_FILE, 'w') as configfile: 48 | cfg.write(configfile) 49 | 50 | 51 | def get_latest_commit(): 52 | output = subprocess.check_output(['git', 'log', '-1', '--oneline', 'HEAD']) 53 | commit = output.split()[0].decode('utf-8') 54 | 55 | return commit 56 | 57 | 58 | def get_latest_commit_message(): 59 | output = subprocess.check_output(['git', 'log', '-1', '--format=%s']) 60 | title = output.decode('utf-8').strip() 61 | 62 | output = subprocess.check_output(['git', 'log', '-1', '--format=%b', '--shortstat']) 63 | message = output.decode('utf-8').strip() 64 | 65 | return (title, message) 66 | 67 | 68 | def get_branch(): 69 | output = subprocess.check_output(['git', 'status', '--short', '--branch']) 70 | branch = output.decode('utf-8').split('...')[0].replace('## ', '') 71 | 72 | return branch 73 | 74 | 75 | def set_commit(commit): 76 | cfg = configparser.ConfigParser() 77 | cfg.read(config.VERSION_FILE) 78 | 79 | # Godot does strings differently 80 | cfg['version']['commit'] = '"%s"' % commit 81 | 82 | with open(config.VERSION_FILE, 'w') as configfile: 83 | cfg.write(configfile) 84 | 85 | 86 | def tag_git(): 87 | subprocess.check_output(['git', 'tag', 'v%s' % get_public_version(), get_commit() ]) 88 | 89 | 90 | def get_last_tag(): 91 | tag = subprocess.check_output(['git', 'describe', '--tags', '--abbrev=0', '@^']) 92 | 93 | return tag.decode('utf-8').strip() 94 | 95 | 96 | def get_commits_since_last_tag(): 97 | SEP = "~~~" 98 | 99 | tag = get_last_tag() 100 | 101 | output = subprocess.check_output(['git', 'log', '--format=%s%n%b' + SEP, '%s..HEAD' % tag]) 102 | output = output.decode('utf-8').strip() 103 | output = output.replace("\n\n", "\n") 104 | lines = output.split(SEP) 105 | 106 | updates = "" 107 | fixes = "" 108 | private = "" 109 | for line in lines: 110 | inner_lines = line.strip().split("\n"); 111 | line = "" 112 | for inner in inner_lines: 113 | if inner.startswith("- "): 114 | inner = "; " + inner[2:] 115 | line += inner 116 | 117 | if len(line) == 0: 118 | continue 119 | 120 | if line.find("#private") >= 0: 121 | private += "- " + line + "\n" 122 | elif line.lower().startswith("fix"): 123 | fixes += "- " + line + "\n" 124 | else: 125 | updates += "- " + line + "\n" 126 | 127 | if len(updates) > 0: 128 | output = "New and updated:\n\n" + updates.strip() 129 | 130 | if len(fixes) > 0: 131 | if len(output) > 0: 132 | output += "\n\n" 133 | output += "Fixes:\n\n" + fixes.strip() 134 | 135 | if len(private) > 0: 136 | if len(output) > 0: 137 | output += "\n\n" 138 | output += "Private:\n\n" + private.strip() 139 | 140 | return output 141 | 142 | 143 | def generate_changelog(): 144 | ver = get_public_version() 145 | commits = get_commits_since_last_tag() 146 | 147 | with open(config.CHANGELOG, 'w') as changelog: 148 | changelog.write('# v%s\n\n' %ver) 149 | changelog.write(commits) 150 | 151 | 152 | def get_changelog(): 153 | with open(config.CHANGELOG, 'r') as changelog: 154 | lines = changelog.readlines() 155 | 156 | log = ''.join(lines) 157 | 158 | return log 159 | --------------------------------------------------------------------------------