diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1ab7a37 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,99 @@ +name: 🔨 Build + +on: + push: + branches: + - master + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +jobs: + build-windows: + runs-on: windows-2022 + name: 🪟 Windows (x64) + + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Install Python Dependencies + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install .NET Dependencies + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Build + run: | + # Create a virtual environment. + set PYTHONNOUSERSITE=1 + set "PYTHONPATH=" + python3 -m venv venv + venv/Scripts/activate.ps1 + + # Install dependencies. + python -m pip install -r requirements-build-windows.txt + python -m pip install -r requirements.txt + + # Build the bundle. + python setup.py build + shell: powershell + + - name: Set Artifact Name + id: set-artifact-name + run: echo "artifact_name=`ls build`" >> $GITHUB_OUTPUT + shell: bash + + - name: Upload Build + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.set-artifact-name.outputs.artifact_name }} + path: build/* + + build-linux: + runs-on: ubuntu-22.04 + name: 🐧 Linux (x64) + + steps: + - name: Check Out Code + uses: actions/checkout@v4 + + - name: Install Python Dependencies + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install .NET Dependencies + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '6.0.x' + + - name: Build + run: | + # Create a virtual environment. + export PYTHONNOUSERSITE=1 + unset PYTHONPATH + python3 -m venv venv + source venv/bin/activate + + # Install dependencies. + python -m pip install -r requirements-build-linux.txt + python -m pip install -r requirements.txt + + # Build the bundle. + python setup.py build + shell: bash + + - name: Set Artifact Name + id: set-artifact-name + run: echo "artifact_name=`ls build`" >> $GITHUB_OUTPUT + shell: bash + + - name: Upload Build + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.set-artifact-name.outputs.artifact_name }} + path: build/* diff --git a/mkdd_patcher.py b/mkdd_patcher.py index aa77068..7b189a9 100755 --- a/mkdd_patcher.py +++ b/mkdd_patcher.py @@ -45,7 +45,7 @@ def __init__(self): self._last_custom_tracks = config['paths'].get('custom_tracks', '') self._last_custom_tracks_picked = config['paths'].get('custom_tracks_picked', '') - self.title(f'MKDD Patcher {patcher.__version__}') + self.title(patcher.APP_NAME) if platform.system() == 'Windows': self.iconbitmap(get_ico_path('logo')) else: @@ -367,7 +367,11 @@ def _open_config_directory(self): ('open' if platform.system() == 'Darwin' else 'xdg-open', config_dir)) def _show_about_dialog(self): - text = textwrap.dedent(f'MKDD Patcher {patcher.__version__} by Yoshi2') + text = textwrap.dedent(f'{patcher.APP_NAME} by Yoshi2') + if patcher.BUILD_TIME and patcher.COMMIT_SHA: + text += '\n\n' + text += f'Revision: {patcher.COMMIT_SHA}\n' + text += f'Build time: {patcher.BUILD_TIME}' URL = 'https://github.com/RenolY2/mkdd-track-patcher' MessageBox( diff --git a/patcher-gui.py b/patcher-gui.py index 845c537..3a7b18d 100644 --- a/patcher-gui.py +++ b/patcher-gui.py @@ -249,19 +249,19 @@ def __init__(self, master=None): root = tk.Tk() root.geometry("350x150") def show_about(): - #about_text = f"MKDD Patcher {patcher.__version__} by Yoshi2" + #about_text = f"{patcher.APP_NAME} by Yoshi2" #about_text += "\nNew releases: https://github.com/RenolY2/mkdd-track-patcher/releases" #about_text += "\nReport bugs at: https://github.com/RenolY2/mkdd-track-patcher/issues" #messagebox.showinfo("About", about_text) about = tk.Toplevel(root) text = tk.Text(about, height=4) - text.insert(1.0, f"MKDD Patcher {patcher.__version__} by Yoshi2\n") + text.insert(1.0, f"{patcher.APP_NAME} by Yoshi2\n") text.insert(2.0, "New releases: https://github.com/RenolY2/mkdd-track-patcher/releases\n") text.insert(3.0, "Post suggestions or bug reports at: https://github.com/RenolY2/mkdd-track-patcher/issues") text.pack() text.configure(state="disabled") - root.title(f"MKDD Patcher {patcher.__version__}") + root.title(patcher.APP_NAME) try: root.iconbitmap(str(pathlib.Path(__file__).parent.absolute()) + '/resources/icon.ico') except: diff --git a/requirements-build-linux.txt b/requirements-build-linux.txt new file mode 100644 index 0000000..a3fffeb --- /dev/null +++ b/requirements-build-linux.txt @@ -0,0 +1,2 @@ +cx-Freeze==6.15.16 +patchelf==0.17.2.1 diff --git a/requirements-build-windows.txt b/requirements-build-windows.txt new file mode 100644 index 0000000..a9789c6 --- /dev/null +++ b/requirements-build-windows.txt @@ -0,0 +1,3 @@ +cx-Freeze==6.15.16 +cx-Logging==3.2.1 +lief==0.14.1 diff --git a/setup.bat b/setup.bat index d3ff45d..322d018 100644 --- a/setup.bat +++ b/setup.bat @@ -11,9 +11,7 @@ python -m venv venv call venv/Scripts/activate.bat rem Install cx_Freeze and its dependencies. -python -m pip install cx-Freeze==6.14.9 -python -m pip install cx-Logging==3.1.0 -python -m pip install lief==0.12.3 +python -m pip install -r requirements-build-windows.txt rem Retrieve a fresh checkout from the repository to avoid a potentially rem polluted local checkout. diff --git a/setup.py b/setup.py index a0b555e..020c3c5 100644 --- a/setup.py +++ b/setup.py @@ -1,28 +1,55 @@ import os +import platform import re import shutil import subprocess import sys +import time from cx_Freeze import setup, Executable + +def get_git_revision_hash() -> str: + return subprocess.check_output(('git', 'rev-parse', 'HEAD')).decode('ascii').strip() + + # To avoid importing the module, simply parse the file to find the version variable in it. with open('src/patcher.py', 'r', encoding='utf-8') as f: - data = f.read() -for line in data.splitlines(): + main_file_data = f.read() +for line in main_file_data.splitlines(): if '__version__' in line: version = re.search(r"'(.+)'", line).group(1) break else: raise RuntimeError('Unable to parse product version.') +is_ci = bool(os.getenv('CI')) +triggered_by_tag = os.getenv('GITHUB_REF_TYPE') == 'tag' +commit_sha = os.getenv('GITHUB_SHA') or get_git_revision_hash() +build_time = time.strftime("%Y-%m-%d %H-%M-%S") + +version_suffix = f'-{commit_sha[:8]}' if commit_sha and not triggered_by_tag else '' + +# Replace constants in source file. +main_file_data = main_file_data.replace('OFFICIAL = False', f"OFFICIAL = {triggered_by_tag}") +main_file_data = main_file_data.replace("COMMIT_SHA = ''", f"COMMIT_SHA = '{commit_sha}'") +main_file_data = main_file_data.replace("BUILD_TIME = None", f"BUILD_TIME = '{build_time}'") +with open('src/patcher.py', 'w', encoding='utf-8') as f: + f.write(main_file_data) + # Compile WSYSTool. subprocess.run((sys.executable, '-c', 'import wsystool; wsystool.compile_and_install_wsystool()'), cwd='src', check=True) +system = platform.system().lower() + +ARCH_USER_FRIENDLY_ALIASES = {'AMD64': 'x64', 'x86_64': 'x64'} +machine = platform.machine() +arch = ARCH_USER_FRIENDLY_ALIASES.get(machine) or machine.lower() + build_dirpath = 'build' -bundle_dirname = f'mkdd-patcher-{version}' +bundle_dirname = f'mkdd-patcher-{version}{version_suffix}-{system}-{arch}' bundle_dirpath = os.path.join(build_dirpath, bundle_dirname) include_files = ["src/resources", "src/tools"] @@ -43,11 +70,12 @@ os.remove(os.path.join(bundle_dirpath, 'frozen_application_license.txt')) -# Create the ZIP archive. -current_dirpath = os.getcwd() -os.chdir(build_dirpath) -try: - print('Creating ZIP archive...') - shutil.make_archive(bundle_dirname, 'zip', '.', bundle_dirname) -finally: - os.chdir(current_dirpath) +if not is_ci: + # Create the ZIP archive. + current_dirpath = os.getcwd() + os.chdir(build_dirpath) + try: + print('Creating ZIP archive...') + shutil.make_archive(bundle_dirname, 'zip', '.', bundle_dirname) + finally: + os.chdir(current_dirpath) diff --git a/src/patcher.py b/src/patcher.py index 7cbd172..aefabcf 100644 --- a/src/patcher.py +++ b/src/patcher.py @@ -35,6 +35,18 @@ __version__ = '2.2.1' +# These constants will be set by the build script on the fly. +OFFICIAL = False +COMMIT_SHA = '' +BUILD_TIME = None + +if OFFICIAL: + APP_NAME = f'MKDD Patcher {__version__}' +elif COMMIT_SHA: + APP_NAME = f'MKDD Patcher {__version__} ({COMMIT_SHA[:8]})' +else: + APP_NAME = f'MKDD Patcher {__version__} (development)' + logging.basicConfig(stream=sys.stdout, level=logging.INFO, format="> %(message)s") log = logging.getLogger(__name__)