diff --git a/.github/workflows/deploy-pypi.yml b/.github/workflows/deploy-pypi.yml index 77beaa4..b57f5cd 100644 --- a/.github/workflows/deploy-pypi.yml +++ b/.github/workflows/deploy-pypi.yml @@ -147,6 +147,22 @@ jobs: wget -P ${linuxdeploy_name} https://github.com/AppImage/appimagetool/releases/latest/download/appimagetool-${linuxdeploy_arch}.AppImage chmod +x ${linuxdeploy_name}/* if: runner.os == 'Linux' + - name: Install Windows dependencies + run: | + if [ "$RUNNER_ARCH" == "X64" ]; then + toolset_arch="x64" + elif [ "$RUNNER_ARCH" == "ARM64" ]; then + toolset_arch="arm64" + else + echo "Unknown runner architecture: $RUNNER_ARCH" + exit 1 + fi + + winget install --id WiXToolset.WiXCLI -e --source winget --accept-package-agreements --accept-source-agreements + wix --version + wix extension add WixToolset.UI.wixext + echo "C:/Program Files/WiX Toolset v6.0/bin/${toolset_arch}" >> $GITHUB_PATH + if: runner.os == 'Windows' - name: Set up Python for Windows7 uses: LorenEteval/setup-python-win7@v1 with: @@ -222,6 +238,7 @@ jobs: name: binary-distributions-${{ matrix.os }}-Python${{ matrix.python-version }} path: | Furious-*.zip + Furious-*.msi Furious-*.dmg Furious-*.AppImage Furious-*.deb diff --git a/.gitignore b/.gitignore index e70d35c..9ecf3c5 100644 --- a/.gitignore +++ b/.gitignore @@ -179,6 +179,10 @@ rpmbuild/ # zip files *.zip +# msi files +*.msi +# wixpdb files +*.wixpdb # dmg files *.dmg # AppImage files diff --git a/Deploy.py b/Deploy.py index 39b5c75..2092cc5 100644 --- a/Deploy.py +++ b/Deploy.py @@ -22,6 +22,7 @@ from Furious.Frozenlib import * import os import re import sys +import uuid import glob import shutil import logging @@ -33,6 +34,7 @@ import subprocess import urllib.request from typing import AnyStr +from xml.sax.saxutils import escape as xml_escape logging.basicConfig( format='[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s', @@ -191,37 +193,42 @@ else: ARTIFACT_NAME = '' -def downloadXrayAssets(url, filepath): +def downloadFromURL(url, filepath): try: import requests except ImportError: raise ModuleNotFoundError('missing requests module') - try: - # Make sure the save directory exists - if not os.path.exists(XRAY_ASSET_DIR): - os.makedirs(XRAY_ASSET_DIR) + # Send an HTTP GET request to the URL + response = requests.get(url) - # Send an HTTP GET request to the URL - response = requests.get(url) + try: response.raise_for_status() # Check if the request was successful # Open the save_path in write-binary mode and write the content of the response with open(filepath, 'wb') as file: file.write(response.content) - - except Exception as ex: + except Exception: # Any non-exit exceptions - logger.error( - f'failed to download xray asset from {url}. Status code: {response.status_code}' - ) - - return False + return response.status_code, False else: - logger.info(f'xray asset downloaded successfully and saved to {filepath}') + return response.status_code, True - return True + +def downloadXrayAssets(url, filepath): + # Make sure the save directory exists + if not os.path.exists(XRAY_ASSET_DIR): + os.makedirs(XRAY_ASSET_DIR) + + code, success = downloadFromURL(url, filepath) + + if success: + logger.info(f'xray asset downloaded successfully and saved to {filepath}') + else: + logger.error(f'failed to download xray asset from {url}. Status code: {code}') + + return success def downloadHy1Asset(): @@ -313,6 +320,26 @@ def cleanup(): else: logger.info(f'remove artifact success') + try: + # Remove artifact + os.remove(ROOT_DIR / f'{ARTIFACT_NAME}.msi') + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'remove artifact failed: {ex}') + else: + logger.info(f'remove artifact success') + + try: + # Remove artifact + os.remove(ROOT_DIR / f'{ARTIFACT_NAME}.wixpdb') + except Exception as ex: + # Any non-exit exceptions + + logger.error(f'remove artifact failed: {ex}') + else: + logger.info(f'remove artifact success') + try: # Remove unzipped folder shutil.rmtree(ROOT_DIR / WIN_UNZIPPED) @@ -534,6 +561,15 @@ def main(): raise + # Copy LICENSE to distribution folder + shutil.copy( + ROOT_DIR / 'LICENSE', + ROOT_DIR / DEPLOY_DIR_NAME / f'{APPLICATION_NAME}.dist', + ) + shutil.copy( + ROOT_DIR / 'LICENSE.rtf', + ROOT_DIR / DEPLOY_DIR_NAME, + ) shutil.copytree( ROOT_DIR / DEPLOY_DIR_NAME / f'{APPLICATION_NAME}.dist', ROOT_DIR / DEPLOY_DIR_NAME / WIN_UNZIPPED, @@ -545,6 +581,151 @@ def main(): WIN_UNZIPPED, logger=logger, ) + shutil.copytree( + ROOT_DIR / DEPLOY_DIR_NAME / f'{APPLICATION_NAME}.dist', + ROOT_DIR / DEPLOY_DIR_NAME / 'SourceDir', + ) + + msifolder = ROOT_DIR / DEPLOY_DIR_NAME / 'msi' + + os.makedirs(msifolder, exist_ok=True) + + try: + # Requires "heat" in PATH + result = runExternalCommand( + 'heat dir'.split() + + [ROOT_DIR / DEPLOY_DIR_NAME / 'SourceDir'] + + '-cg FuriousComponents -dr INSTALLFOLDER -srd -scom -sreg -gg -out'.split() + + [msifolder / 'FuriousFiles.wxs'], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error(f'run heat failed with returncode {err.returncode}') + + printStandardStream(err.stdout, err.stderr) + + sys.exit(EXIT_FAILURE) + else: + logger.info(f'run heat success') + + printStandardStream(result.stdout, result.stderr) + + # Create Product.wxs file + try: + with open( + msifolder / 'Product.wxs', + 'w', + encoding='utf-8', + ) as file: + file.write( + f'\n' + f'\n' + f'\n' + f' \n' + f'\n' + f' \n' + f' \n' + f' \n' + f'\n' + f' \n' + f' \n' + f' \n' + f'\n' + f' \n' + f' \n' + f'\n' + f' \n' + f' \n' + f' \n' + f' \n' + f'\n' + f' \n' + f' \n' + f' \n' + f' \n' + f' \n' + f'\n' + f' \n' + f' \n' + f'\n' + f' \n' + f' \n' + f'\n' + f' \n' + f'\n' + f'\n' + ) + except Exception: + # Any non-exit exceptions + + raise + + wixui = 'WixUI_InstallDir.wxs' + + code, success = downloadFromURL( + f'https://raw.githubusercontent.com/LorenEteval/wix/' + f'refs/heads/main/src/ext/UI/wixlib/{wixui}', + msifolder / wixui, + ) + + if success: + logger.info(f'download \'{wixui}\' success\'') + else: + logger.error(f'download \'{wixui}\' failed\'. Status code: {code}') + + raise + + cwd = os.getcwd() + + try: + os.chdir(ROOT_DIR / DEPLOY_DIR_NAME) + + # Requires "wix extension add WixToolset.UI.wixext" + runExternalCommand( + [ + 'wix', + 'build', + msifolder / 'FuriousFiles.wxs', + msifolder / 'Product.wxs', + '-o', + ROOT_DIR / f'{ARTIFACT_NAME}.msi', + '-ext', + 'WixToolset.UI.wixext', + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) + except subprocess.CalledProcessError as err: + logger.error(f'wix build failed with returncode {err.returncode}') + + printStandardStream(err.stdout, err.stderr) + + sys.exit(EXIT_FAILURE) + else: + logger.info(f'wix build success: {ARTIFACT_NAME}.msi') + + printStandardStream(result.stdout, result.stderr) + finally: + os.chdir(cwd) elif PLATFORM == 'Darwin': try: shutil.rmtree(MAC_APP_DIR) @@ -739,7 +920,7 @@ def main(): ) as file: file.write( f'#!/bin/bash\n' - f'exec /usr/share/{APPLICATION_NAME}/{APPLICATION_NAME}.bin "$@"\n' + f'exec /usr/share/{APPLICATION_NAME}/{APPLICATION_NAME}.bin \"$@\"\n' ) runExternalCommand(