espanso/packager.py

254 lines
9.2 KiB
Python

import subprocess
import sys
import os
import platform
import hashlib
import click
import shutil
import toml
import hashlib
import glob
import urllib.request
from dataclasses import dataclass
PACKAGER_TARGET_DIR = "target/packager"
@dataclass
class PackageInfo:
name: str
version: str
description: str
publisher: str
url: str
modulo_version: str
@click.group()
def cli():
pass
@cli.command()
@click.option('--skipcargo', default=False, is_flag=True, help="Skip cargo release build")
def build(skipcargo):
"""Build espanso distribution"""
# Check operating system
TARGET_OS = "macos"
if platform.system() == "Windows":
TARGET_OS = "windows"
elif platform.system() == "Linux":
TARGET_OS = "linux"
print("Detected OS:", TARGET_OS)
print("Loading info from Cargo.toml")
cargo_info = toml.load("Cargo.toml")
package_info = PackageInfo(cargo_info["package"]["name"],
cargo_info["package"]["version"],
cargo_info["package"]["description"],
cargo_info["package"]["authors"][0],
cargo_info["package"]["homepage"],
cargo_info["modulo"]["version"])
print(package_info)
if not skipcargo:
print("Building release version...")
subprocess.run(["cargo", "build", "--release"])
else:
print("Skipping build")
if TARGET_OS == "windows":
build_windows(package_info)
elif TARGET_OS == "macos":
build_mac(package_info)
def calculate_sha256(file):
with open(file, "rb") as f:
b = f.read() # read entire file as bytes
readable_hash = hashlib.sha256(b).hexdigest()
return readable_hash
def build_windows(package_info):
print("Starting packaging process for Windows...")
# Check Inno Setup
try:
subprocess.run(["iscc"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
except FileNotFoundError:
raise Exception("Could not find Inno Setup compiler. Please install it from here: http://www.jrsoftware.org/isdl.php")
print("Clearing target dirs")
# Clearing previous build directory
if os.path.isdir(PACKAGER_TARGET_DIR):
print("Cleaning packager temp directory...")
shutil.rmtree(PACKAGER_TARGET_DIR)
TARGET_DIR = os.path.join(PACKAGER_TARGET_DIR, "win")
os.makedirs(TARGET_DIR, exist_ok=True)
modulo_url = "https://github.com/federico-terzi/modulo/releases/download/v{0}/modulo-win.exe".format(package_info.modulo_version)
modulo_sha_url = "https://github.com/federico-terzi/modulo/releases/download/v{0}/modulo-win.exe.sha256.txt".format(package_info.modulo_version)
print("Pulling modulo depencency from:", modulo_url)
modulo_target_file = os.path.join(TARGET_DIR, "modulo.exe")
urllib.request.urlretrieve(modulo_url, modulo_target_file)
print("Pulling SHA signature from:", modulo_sha_url)
modulo_sha_file = os.path.join(TARGET_DIR, "modulo.sha256")
urllib.request.urlretrieve(modulo_sha_url, modulo_sha_file)
print("Checking signatures...")
expected_sha = None
with open(modulo_sha_file, "r") as sha_f:
expected_sha = sha_f.read()
actual_sha = calculate_sha256(modulo_target_file)
if actual_sha != expected_sha:
raise Exception("Modulo SHA256 is not matching")
print("Gathering CRT DLLs...")
msvc_dirs = glob.glob("C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\*\\VC\\Redist\\MSVC\\*")
print("Found Redists: ", msvc_dirs)
print("Determining best redist...")
if len(msvc_dirs) == 0:
raise Exception("Cannot find redistributable dlls")
msvc_dir = None
for curr_dir in msvc_dirs:
dll_files = glob.glob(curr_dir + "\\x64\\*CRT\\*.dll")
print("Found dlls", dll_files, "in", curr_dir)
if any("vcruntime140_1.dll" in x.lower() for x in dll_files):
msvc_dir = curr_dir
break
if msvc_dir is None:
raise Exception("Cannot find redist with VCRUNTIME140_1.dll")
print("Using: ", msvc_dir)
dll_files = glob.glob(msvc_dir + "\\x64\\*CRT\\*.dll")
print("Found DLLs:")
include_list = []
for dll in dll_files:
print("Including: "+dll)
include_list.append("Source: \""+dll+"\"; DestDir: \"{app}\"; Flags: ignoreversion")
print("Including modulo")
include_list.append("Source: \""+os.path.abspath(modulo_target_file)+"\"; DestDir: \"{app}\"; Flags: ignoreversion")
include = "\r\n".join(include_list)
INSTALLER_NAME = f"espanso-win-installer"
# Inno setup
shutil.copy("packager/win/modpath.iss", os.path.join(TARGET_DIR, "modpath.iss"))
print("Processing inno setup template")
with open("packager/win/setupscript.iss", "r") as iss_script:
content = iss_script.read()
# Replace variables
content = content.replace("{{{app_name}}}", package_info.name)
content = content.replace("{{{app_version}}}", package_info.version)
content = content.replace("{{{app_publisher}}}", package_info.publisher)
content = content.replace("{{{app_url}}}", package_info.url)
content = content.replace("{{{app_license}}}", os.path.abspath("LICENSE"))
content = content.replace("{{{app_icon}}}", os.path.abspath("packager/win/icon.ico"))
content = content.replace("{{{executable_path}}}", os.path.abspath("target/release/espanso.exe"))
content = content.replace("{{{output_dir}}}", os.path.abspath(TARGET_DIR))
content = content.replace("{{{output_name}}}", INSTALLER_NAME)
content = content.replace("{{{dll_include}}}", include)
with open(os.path.join(TARGET_DIR, "setupscript.iss"), "w") as output_script:
output_script.write(content)
print("Compiling installer with Inno setup")
subprocess.run(["iscc", os.path.abspath(os.path.join(TARGET_DIR, "setupscript.iss"))])
print("Calculating the SHA256")
sha256_hash = hashlib.sha256()
with open(os.path.abspath(os.path.join(TARGET_DIR, INSTALLER_NAME+".exe")),"rb") as f:
# Read and update hash string value in blocks of 4K
for byte_block in iter(lambda: f.read(4096),b""):
sha256_hash.update(byte_block)
hash_file = os.path.abspath(os.path.join(TARGET_DIR, "espanso-win-installer-sha256.txt"))
with open(hash_file, "w") as hf:
hf.write(sha256_hash.hexdigest())
def build_mac(package_info):
print("Starting packaging process for MacOS...")
print("Clearing target dirs")
# Clearing previous build directory
if os.path.isdir(PACKAGER_TARGET_DIR):
print("Cleaning packager temp directory...")
shutil.rmtree(PACKAGER_TARGET_DIR)
TARGET_DIR = os.path.join(PACKAGER_TARGET_DIR, "mac")
os.makedirs(TARGET_DIR, exist_ok=True)
print("Compressing release to archive...")
target_name = f"espanso-mac.tar.gz"
archive_target = os.path.abspath(os.path.join(TARGET_DIR, target_name))
subprocess.run(["tar",
"-C", os.path.abspath("target/release"),
"-cvf",
archive_target,
"espanso",
])
print(f"Created archive: {archive_target}")
print("Calculating the SHA256")
sha256_hash = hashlib.sha256()
with open(archive_target,"rb") as f:
# Read and update hash string value in blocks of 4K
for byte_block in iter(lambda: f.read(4096),b""):
sha256_hash.update(byte_block)
hash_file = os.path.abspath(os.path.join(TARGET_DIR, "espanso-mac-sha256.txt"))
with open(hash_file, "w") as hf:
hf.write(sha256_hash.hexdigest())
modulo_sha_url = "https://github.com/federico-terzi/modulo/releases/download/v{0}/modulo-mac.sha256.txt".format(package_info.modulo_version)
print("Pulling SHA signature from:", modulo_sha_url)
modulo_sha_file = os.path.join(TARGET_DIR, "modulo.sha256")
urllib.request.urlretrieve(modulo_sha_url, modulo_sha_file)
modulo_sha = None
with open(modulo_sha_file, "r") as sha_f:
modulo_sha = sha_f.read()
if modulo_sha is None:
raise Exception("Cannot determine modulo SHA")
print("Processing Homebrew formula template")
with open("packager/mac/espanso.rb", "r") as formula_template:
content = formula_template.read()
# Replace variables
content = content.replace("{{{app_desc}}}", package_info.description)
content = content.replace("{{{app_url}}}", package_info.url)
content = content.replace("{{{app_version}}}", package_info.version)
content = content.replace("{{{modulo_version}}}", package_info.modulo_version)
content = content.replace("{{{modulo_sha}}}", modulo_sha)
# Calculate hash
with open(archive_target, "rb") as f:
bytes = f.read()
readable_hash = hashlib.sha256(bytes).hexdigest()
content = content.replace("{{{release_hash}}}", readable_hash)
with open(os.path.join(TARGET_DIR, "espanso.rb"), "w") as output_script:
output_script.write(content)
print("Done!")
if __name__ == '__main__':
print("[[ espanso packager ]]")
# Check python version 3
if sys.version_info[0] < 3:
raise Exception("Must be using Python 3")
cli()