diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..0a5f2823 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +# workflow name +name: Build + +# fire on +on: [ push, pull_request ] + +# stuff to do +jobs: + # Install & Build + # Set up environment + # Build + # Run build-gui.py + # Run build-dr.py + install-build: + name: Install/Build + # cycle through os list + runs-on: ${{ matrix.os-name }} + + # VM settings + # os & python versions + strategy: + matrix: + os-name: [ ubuntu-latest, ubuntu-16.04, macOS-latest, windows-latest ] + python-version: [ 3.7 ] +# needs: [ install-test ] + steps: + # checkout commit + - name: Checkout commit + uses: actions/checkout@v1 + # install python + - name: Install python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + architecture: "x64" + - run: | + python --version + # install dependencies via pip + - name: Install dependencies via pip + env: + OS_NAME: ${{ matrix.os-name }} + run: | + python ./resources/ci/common/install.py + # try to get UPX + - name: Get UPX + env: + OS_NAME: ${{ matrix.os-name }} + run: | + python ./resources/ci/common/get_upx.py + # run build-gui.py + - name: Build GUI + run: | + pip install pyinstaller + python ./build-gui.py + # run build-dr.py + - name: Build DungeonRandomizer + run: | + python ./build-dr.py + # prepare binary artifacts for later step + - name: Prepare Binary Artifacts + env: + OS_NAME: ${{ matrix.os-name }} + run: | + python ./resources/ci/common/prepare_binary.py + # upload binary artifacts for later step + - name: Upload Binary Artifacts + uses: actions/upload-artifact@v1 + with: + name: binaries-${{ matrix.os-name }} + path: ../artifact diff --git a/resources/app/meta/manifests/pip_requirements.txt b/resources/app/meta/manifests/pip_requirements.txt new file mode 100644 index 00000000..ef376ca8 --- /dev/null +++ b/resources/app/meta/manifests/pip_requirements.txt @@ -0,0 +1 @@ +pyinstaller diff --git a/resources/ci/__init__.py b/resources/ci/__init__.py new file mode 100644 index 00000000..427ed90c --- /dev/null +++ b/resources/ci/__init__.py @@ -0,0 +1 @@ +#do nothing, just exist to make "common" package diff --git a/resources/ci/common.py b/resources/ci/common.py new file mode 100644 index 00000000..4e2cf50c --- /dev/null +++ b/resources/ci/common.py @@ -0,0 +1,131 @@ +import os # for env vars +import stat # file statistics + +# take number of bytes and convert to string with units measure +def convert_bytes(num): + for x in ["bytes","KB","MB","GB","TB","PB"]: + if num < 1024.0: + return "%3.1f %s" % (num,x) + num /= 1024.0 + +# get filesize of file at path +def file_size(file_path): + if os.path.isfile(file_path): + file_info = os.stat(file_path) + return convert_bytes(file_info.st_size) + +# prepare environment variables +def prepare_env(): + DEFAULT_EVENT = "event" + DEFAULT_REPO_SLUG = "miketrethewey/ALttPDoorRandomizer" + + env = {} + + # get app version + APP_VERSION = "" + APP_VERSION_FILE = "./resources/app/meta/manifests/app_version.txt" + if os.path.isfile(APP_VERSION_FILE): + with open(APP_VERSION_FILE,"r") as f: + APP_VERSION = f.readlines()[0].strip() + # ci data + env["CI_SYSTEM"] = os.getenv("CI_SYSTEM","") + # git data + env["BRANCH"] = os.getenv("TRAVIS_BRANCH","") + env["GITHUB_ACTOR"] = os.getenv("GITHUB_ACTOR","MegaMan.EXE") + env["GITHUB_SHA"] = os.getenv("GITHUB_SHA","") + env["GITHUB_SHA_SHORT"] = env["GITHUB_SHA"] + # commit data + env["COMMIT_ID"] = os.getenv("TRAVIS_COMMIT",os.getenv("GITHUB_SHA","")) + env["COMMIT_COMPARE"] = os.getenv("TRAVIS_COMMIT_RANGE","") + # event data + env["EVENT_MESSAGE"] = os.getenv("TRAVIS_COMMIT_MESSAGE","") + env["EVENT_LOG"] = os.getenv("GITHUB_EVENT_PATH","") + env["EVENT_TYPE"] = os.getenv("TRAVIS_EVENT_TYPE",os.getenv("GITHUB_EVENT_NAME",DEFAULT_EVENT)) + # repo data + env["REPO_SLUG"] = os.getenv("TRAVIS_REPO_SLUG",os.getenv("GITHUB_REPOSITORY",DEFAULT_REPO_SLUG)) + env["REPO_USERNAME"] = "" + env["REPO_NAME"] = "" + + # repo slug + if '/' in env["REPO_SLUG"]: + tmp = env["REPO_SLUG"].split('/') + env["REPO_USERNAME"] = tmp[0] + env["REPO_NAME"] = tmp[1] + + if not env["GITHUB_SHA"] == "": + env["GITHUB_SHA_SHORT"] = env["GITHUB_SHA"][:7] + + # ci data + env["BUILD_NUMBER"] = os.getenv("TRAVIS_BUILD_NUMBER",env["GITHUB_SHA_SHORT"]) + + GITHUB_TAG = os.getenv("TRAVIS_TAG",os.getenv("GITHUB_TAG","")) + OS_NAME = os.getenv("TRAVIS_OS_NAME",os.getenv("OS_NAME","")).replace("macOS","osx") + OS_DIST = os.getenv("TRAVIS_DIST","notset") + OS_VERSION = "" + + if '-' in OS_NAME: + OS_VERSION = OS_NAME[OS_NAME.find('-')+1:] + OS_NAME = OS_NAME[:OS_NAME.find('-')] + if OS_NAME == "linux" or OS_NAME == "ubuntu": + if OS_VERSION == "latest": + OS_VERSION = "bionic" + elif OS_VERSION == "16.04": + OS_VERSION = "xenial" + OS_DIST = OS_VERSION + + if OS_VERSION == "" and not OS_DIST == "" and not OS_DIST == "notset": + OS_VERSION = OS_DIST + + # if no tag + if GITHUB_TAG == "": + # if we haven't appended the build number, do it + if env["BUILD_NUMBER"] not in GITHUB_TAG: + GITHUB_TAG = APP_VERSION + # if the app version didn't have the build number, add it + # set to . + if env["BUILD_NUMBER"] not in GITHUB_TAG: + GITHUB_TAG += '.' + env["BUILD_NUMBER"] + + env["GITHUB_TAG"] = GITHUB_TAG + env["OS_NAME"] = OS_NAME + env["OS_DIST"] = OS_DIST + env["OS_VERSION"] = OS_VERSION + + return env + +# build filename based on metadata +def prepare_filename(BUILD_FILENAME): + env = prepare_env() + + DEST_FILENAME = "" + + # build the filename + if not BUILD_FILENAME == "": + os.chmod(BUILD_FILENAME,0o755) + fileparts = os.path.splitext(BUILD_FILENAME) + DEST_SLUG = fileparts[0] + DEST_EXTENSION = fileparts[1] + DEST_SLUG = DEST_SLUG + '-' + env["GITHUB_TAG"] + '-' + env["OS_NAME"] + if not env["OS_DIST"] == "" and not env["OS_DIST"] == "notset": + DEST_SLUG += '-' + env["OS_DIST"] + DEST_FILENAME = DEST_SLUG + DEST_EXTENSION + return DEST_FILENAME + +# find a binary file if it's executable +# failing that, assume it's over 10MB +def find_binary(listdir): + BUILD_FILENAMES = [] + executable = stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH + for filename in os.listdir(listdir): + if os.path.isfile(filename): + st = os.stat(filename) + mode = st.st_mode + big = st.st_size > (10 * 1024 * 1024) # 10MB + if (mode & executable) or big: + if "GUI" in filename or "DungeonRandomizer" in filename: + BUILD_FILENAMES.append(filename) + return BUILD_FILENAMES + +if __name__ == "__main__": + env = prepare_env() + print(env) diff --git a/resources/ci/get_upx.py b/resources/ci/get_upx.py new file mode 100644 index 00000000..8d71098e --- /dev/null +++ b/resources/ci/get_upx.py @@ -0,0 +1,42 @@ +import common +import os # for env vars +import sys # for path +import urllib.request # for downloads +from shutil import unpack_archive + +# only do stuff if we don't have a UPX folder + +if not os.path.isdir("./upx"): + # get env vars + env = common.prepare_env() + # set up download url + UPX_VERSION = os.getenv("UPX_VERSION") or "3.96" + UPX_SLUG = "" + UPX_FILE = "" + if "windows" in env["OS_NAME"]: + UPX_SLUG = "upx-" + UPX_VERSION + "-win64" + UPX_FILE = UPX_SLUG + ".zip" + else: + UPX_SLUG = "upx-" + UPX_VERSION + "-amd64_linux" + UPX_FILE = UPX_SLUG + ".tar.xz" + UPX_URL = "https://github.com/upx/upx/releases/download/v" + UPX_VERSION + '/' + UPX_FILE + + if "osx" not in env["OS_NAME"]: + + print("Getting UPX: " + UPX_FILE) + + with open("./" + UPX_FILE,"wb") as upx: + UPX_REQ = urllib.request.Request( + UPX_URL, + data=None + ) + UPX_REQ = urllib.request.urlopen(UPX_REQ) + UPX_DATA = UPX_REQ.read() + upx.write(UPX_DATA) + + unpack_archive(UPX_FILE,"./") + + os.rename("./" + UPX_SLUG,"./upx") + os.remove("./" + UPX_FILE) + +print("UPX should " + ("not " if not os.path.isdir("./upx") else "") + "be available.") diff --git a/resources/ci/install.py b/resources/ci/install.py new file mode 100644 index 00000000..8cd40df2 --- /dev/null +++ b/resources/ci/install.py @@ -0,0 +1,27 @@ +import common +import os # for env vars +import subprocess # do stuff at the shell level + +env = common.prepare_env() + +# get executables +# python +# linux/windows: python +# macosx: python3 +# pip +# linux/macosx: pip3 +# windows: pip +PYTHON_EXECUTABLE = "python3" if "osx" in env["OS_NAME"] else "python" +PIP_EXECUTABLE = "pip" if "windows" in env["OS_NAME"] else "pip3" +PIP_EXECUTABLE = "pip" if "osx" in env["OS_NAME"] and "actions" in env["CI_SYSTEM"] else PIP_EXECUTABLE + +# upgrade pip +subprocess.check_call([PYTHON_EXECUTABLE,"-m","pip","install","--upgrade","pip"]) + +# pip version +subprocess.check_call([PIP_EXECUTABLE,"--version"]) +# if pip3, install wheel +if PIP_EXECUTABLE == "pip3": + subprocess.check_call([PIP_EXECUTABLE,"install","-U","wheel"]) +# install listed dependencies +subprocess.check_call([PIP_EXECUTABLE,"install","-r","./resources/app/meta/manifests/pip_requirements.txt"]) diff --git a/resources/ci/prepare_binary.py b/resources/ci/prepare_binary.py new file mode 100644 index 00000000..583c7d7b --- /dev/null +++ b/resources/ci/prepare_binary.py @@ -0,0 +1,40 @@ +import distutils.dir_util # for copying trees +import os # for env vars +import stat # for file stats +import subprocess # do stuff at the shell level +import common +from shutil import copy, make_archive, move, rmtree # file manipulation + +env = common.prepare_env() + +# make dir to put the binary in +if not os.path.isdir(os.path.join("..","artifact")): + os.mkdir(os.path.join("..","artifact")) + +BUILD_FILENAME = "" + +# list executables +BUILD_FILENAME = common.find_binary('.') +if BUILD_FILENAME == "": + BUILD_FILENAME = common.find_binary(os.path.join("..","artifact")) + +if isinstance(BUILD_FILENAME,str): + BUILD_FILENAME = list(BUILD_FILENAME) + +BUILD_FILENAMES = BUILD_FILENAME + +for BUILD_FILENAME in BUILD_FILENAMES: + DEST_FILENAME = common.prepare_filename(BUILD_FILENAME) + + print("OS Name: " + env["OS_NAME"]) + print("OS Version: " + env["OS_VERSION"]) + print("Build Filename: " + BUILD_FILENAME) + print("Dest Filename: " + DEST_FILENAME) + if not BUILD_FILENAME == "": + print("Build Filesize: " + common.file_size(BUILD_FILENAME)) + + if not BUILD_FILENAME == "": + move( + os.path.join(".",BUILD_FILENAME), + os.path.join("..","artifact",BUILD_FILENAME) + )