Skip to content

Instantly share code, notes, and snippets.

@Jerakin
Last active July 9, 2018 18:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Jerakin/7bfb553cc080dbac3b1709405807d253 to your computer and use it in GitHub Desktop.
Save Jerakin/7bfb553cc080dbac3b1709405807d253 to your computer and use it in GitHub Desktop.
For building and deploying Defold projects directly to your phone
"""
Builder is built by wrapping adb and storing some data locally
I would recommend to add an alias to your ~.bash_profile to use it easier
alias builder="python3.5 ~/Documents/repo/builder/builder.py"
Usage:
builder.py [command] [arguments]
Available Commands:
build build [location of a project]
install install [path to APK]
uninstall uninstall [bundle id]
start start [bundle id]
listen listen
bob bob commands
In addition to specify the location or id you can chose "this project" or "latest project" when supplying arguments to
build, install, uninstall and start. By using "builder build ." you say "use this project" and with just "builder build"
you say "use latest project"
"""
import sys
assert sys.version_info >= (3, 2), "Requires a python version of 3.2 or higher"
from subprocess import call
import argparse
import os
import configparser
try:
import requests
except ImportError:
print("requests not found, install with `pip install requests`")
sys.exit(1)
__version__ = "2.0.1"
CACHE_DIR = os.path.join(os.path.expanduser("~"), ".builder", "cache")
def get_session_value(project, key):
session = get_session_file()
if project in session.sections():
return session.get(project, key) if session.has_option(project, key) else None
def get_session_file():
path = os.path.join(CACHE_DIR, "session")
if not os.path.exists(path):
if not os.path.exists(CACHE_DIR):
os.makedirs(CACHE_DIR, exist_ok=True)
config = configparser.RawConfigParser()
config.add_section('config')
config.set('config', 'latest', ".")
config.set('config', 'output', os.path.join(CACHE_DIR, "output"))
with open(path, 'w') as f:
config.write(f)
session = configparser.ConfigParser()
session.read(path)
return session
def get_project_file(folder):
for x in os.listdir(folder):
if x.endswith(".project"):
return os.path.join(folder, x)
print("Can not find project file in this location {}".format(folder))
sys.exit(1)
def project_info(project_file):
config = configparser.ConfigParser()
config.read(project_file)
title = config.get("project", "title", fallback="Unnamed")
android_id = config.get("android", "package", fallback="com.example.todo")
return title, android_id
def _update_config(name, key, value):
path = os.path.join(CACHE_DIR, "session")
session = get_session_file()
if name not in session.sections():
session.add_section(name)
session.remove_option(name, key)
session.set(name, key, value)
with open(path, 'w') as f:
session.write(f)
def this_project(args):
project = os.path.abspath(args.file[0])
name, android_id = project_info(get_project_file(project))
return name, project, android_id
def latest_project(_):
name = get_session_value("config", "latest")
project = get_session_value(name, "root")
android_id = get_session_value(name, "android_id")
if name and project and android_id:
return name, project, android_id
print("Project with name '{}' have never been built, try 'build .'".format(name))
sys.exit(1)
# ------------------ B U I L D ------------------ #
def do_build(args):
bob_warning()
bob = get_session_value("config", "bob")
save_config = True
if not bob or not os.path.exists(bob):
print('Please update or set bob "builder bob --update"')
sys.exit(0)
output = get_session_value("config", "output")
if not args.file:
name, project, android_id = latest_project(args)
elif args.file[0] == ".":
name, project, android_id = this_project(args)
_update_config("config", "latest", name)
else:
save_config = False
project = args.file[0]
name, android_id = project_info(get_project_file(project))
print("Building project '{}'".format(name))
os.chdir(project)
call(["java", "-jar", bob, "--archive", "--platform", "armv7-android", "--bundle-output", output,
"--texture-compression", "true", "distclean", "build", "bundle"])
if save_config:
title, android = project_info(get_project_file(project))
_update_config(name, "root", project)
_update_config(name, "android_id", android)
_update_config(name, "build", os.path.join(output, title, "{}.apk".format(title)))
# ------------------ I N S T A L L ------------------ #
def do_install(args):
if args.force:
do_uninstall(args)
if not args.file:
name, _, _ = latest_project(args)
bundle = get_session_value(name, "build")
elif args.file[0] == ".":
name, _, _ = this_project(args)
bundle = os.path.join(get_session_value("config", "output"), name, "{}.apk".format(name))
_update_config("config", "latest", name)
else:
bundle = args.file[0]
call(["adb", "install", bundle])
# ------------------ L I S T E N ------------------ #
def do_listen(_):
call(["adb", "logcat", "-s", "defold"])
# ------------------ S T A R T ------------------ #
def do_start(args):
if not args.file:
name, _, _ = latest_project(args)
android_id = get_session_value(name, "android_id")
elif args.file[0] == ".":
name, _, android_id = this_project(args)
_update_config("config", "latest", name)
else:
android_id = args.file[0]
call(["adb", "shell", "am", "start", "-n", "{}/com.dynamo.android.DefoldActivity".format(android_id)])
# ------------------ U N I N S T A L L ------------------ #
def do_uninstall(args):
if not args.file:
name, _, android_id = latest_project(args)
elif args.file[0] == ".":
name, _, android_id = this_project(args)
_update_config("config", "latest", name)
else:
android_id = args.file[0]
if not android_id:
print("Please specify a bundle id to uninstall")
sys.exit(0)
call(["adb", "uninstall", android_id])
# ------------------ B U I L D E R ------------------ #
def builder():
parser = argparse.ArgumentParser(description='Builder')
sub_parsers = parser.add_subparsers()
sub_build = sub_parsers.add_parser("build")
sub_build.add_argument("file", help="working directory", nargs="?")
sub_build.set_defaults(func=do_build)
sub_install = sub_parsers.add_parser("install")
sub_install.add_argument("file", help="what to install", nargs="?")
sub_install.add_argument("--force", help="force installation by uninstalling first", action='store_true')
sub_install.set_defaults(func=do_install)
sub_uninstall = sub_parsers.add_parser("uninstall")
sub_uninstall.add_argument("file", help="which app to uninstall", nargs="?")
sub_uninstall.set_defaults(func=do_uninstall)
sub_start = sub_parsers.add_parser("start")
sub_start.add_argument("file", help="which app to start", nargs="?")
sub_start.set_defaults(func=do_start)
sub_listen = sub_parsers.add_parser("listen")
sub_listen.set_defaults(func=do_listen)
sub_bob = sub_parsers.add_parser("bob")
sub_bob.add_argument("--update", help="update bob", action='store_true')
sub_bob.add_argument("--force", help="force download of bob", action='store_true')
sub_bob.add_argument("--set", help="download a specific version of bob", nargs=1)
sub_bob.add_argument("--no-warning", help="stop builder from warning if bob is out of date", nargs=1)
sub_bob.set_defaults(func=do_bob)
input_args = parser.parse_args()
input_args.func(input_args)
# ------------------ B O B ------------------ #
def download_bob(sha):
print("Downloading new bob {}".format(sha))
bob_url = "http://d.defold.com/archive/{}/bob/bob.jar".format(sha)
if not os.path.exists(os.path.join(CACHE_DIR, "bob")):
os.makedirs(os.path.join(CACHE_DIR, "bob"), exist_ok=True)
target = os.path.join(CACHE_DIR, "bob", "bob_{}.jar".format(sha))
r = requests.get(bob_url, stream=True)
with open(target, "wb") as f:
total_size = int(r.headers.get('content-length', 0))
if total_size:
dl = 0
for data in r.iter_content(chunk_size=4096):
dl += len(data)
f.write(data)
# Progressbar
done = int(50 * dl / total_size)
sys.stdout.write("\r[%s%s]" % ('=' * done, ' ' * (50 - done)))
sys.stdout.flush()
else:
f.write(r.content)
_update_config("config", "bob", target)
def update_bob(sha, force):
target = os.path.join(CACHE_DIR, "bob", "bob_{}.jar".format(sha))
if force or not os.path.exists(target):
download_bob(sha)
else:
_update_config("config", "bob", target)
def do_bob(args):
if args.no_warning:
if args.no_warning[0] in ["True", "true", True, 1]:
_update_config("config", "no_warning", True)
else:
_update_config("config", "no_warning", False)
if args.update:
latest = requests.get("http://d.defold.com/stable/info.json").json()["sha1"]
update_bob(latest, args.force)
elif args.set:
if not requests.head("http://d.defold.com/archive/{}/bob/bob.jar".format(args.set[-1])).status_code < 400:
print("Can't find specified bob version")
else:
update_bob(args.set[-1], args.force)
else:
v = get_session_value("config", "bob")
v = v.split("bob_")[-1]
v = v.split(".jar")[0]
print("Using version {}".format(v))
def bob_warning():
if get_session_value("config", "no_warning"):
return
latest = requests.get("http://d.defold.com/stable/info.json").json()["sha1"]
_bob = get_session_value("config", "bob")
if not _bob or latest not in _bob:
print(
"bob is out of date update with 'builder bob --update'. Suppress this with 'builder bob --no-warning'")
if __name__ == '__main__':
try:
builder()
except KeyboardInterrupt:
sys.exit()
except:
raise
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment