2019-09-16 14:20:52 +00:00
|
|
|
import subprocess
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import platform
|
2019-09-18 09:10:36 +00:00
|
|
|
import hashlib
|
2019-09-16 14:20:52 +00:00
|
|
|
import click
|
|
|
|
import shutil
|
|
|
|
import toml
|
2019-10-12 08:16:34 +00:00
|
|
|
import hashlib
|
2020-02-29 19:56:47 +00:00
|
|
|
import glob
|
2019-09-30 20:40:19 +00:00
|
|
|
import urllib.request
|
2019-09-18 09:10:36 +00:00
|
|
|
from dataclasses import dataclass
|
2019-09-16 14:20:52 +00:00
|
|
|
|
|
|
|
PACKAGER_TARGET_DIR = "target/packager"
|
|
|
|
|
|
|
|
@dataclass
|
|
|
|
class PackageInfo:
|
|
|
|
name: str
|
|
|
|
version: str
|
|
|
|
description: str
|
|
|
|
publisher: str
|
|
|
|
url: str
|
2020-08-16 15:25:48 +00:00
|
|
|
modulo_version: str
|
2019-09-16 14:20:52 +00:00
|
|
|
|
|
|
|
@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
|
2019-09-18 09:10:36 +00:00
|
|
|
TARGET_OS = "macos"
|
2019-09-16 14:20:52 +00:00
|
|
|
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],
|
2020-08-16 15:25:48 +00:00
|
|
|
cargo_info["package"]["homepage"],
|
|
|
|
cargo_info["modulo"]["version"])
|
2019-09-16 14:20:52 +00:00
|
|
|
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)
|
2019-09-18 09:10:36 +00:00
|
|
|
elif TARGET_OS == "macos":
|
|
|
|
build_mac(package_info)
|
2019-09-16 14:20:52 +00:00
|
|
|
|
2020-08-16 15:25:48 +00:00
|
|
|
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
|
2019-09-16 14:20:52 +00:00
|
|
|
|
|
|
|
def build_windows(package_info):
|
2019-09-18 09:10:36 +00:00
|
|
|
print("Starting packaging process for Windows...")
|
2019-09-16 14:20:52 +00:00
|
|
|
|
2019-09-18 09:10:36 +00:00
|
|
|
# Check Inno Setup
|
2019-09-16 14:20:52 +00:00
|
|
|
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)
|
|
|
|
|
2020-08-16 15:25:48 +00:00
|
|
|
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")
|
|
|
|
|
2020-02-29 19:56:47 +00:00
|
|
|
print("Gathering CRT DLLs...")
|
|
|
|
msvc_dirs = glob.glob("C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\*\\VC\\Redist\\MSVC\\*")
|
|
|
|
print("Found Redists: ", msvc_dirs)
|
|
|
|
|
2020-06-24 18:32:21 +00:00
|
|
|
print("Determining best redist...")
|
|
|
|
|
2020-03-16 18:11:07 +00:00
|
|
|
if len(msvc_dirs) == 0:
|
2020-02-29 19:56:47 +00:00
|
|
|
raise Exception("Cannot find redistributable dlls")
|
2020-03-16 18:11:07 +00:00
|
|
|
|
2020-06-24 18:32:21 +00:00
|
|
|
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)
|
2020-03-16 18:11:07 +00:00
|
|
|
|
2020-02-29 19:56:47 +00:00
|
|
|
dll_files = glob.glob(msvc_dir + "\\x64\\*CRT\\*.dll")
|
|
|
|
|
|
|
|
print("Found DLLs:")
|
2020-08-16 15:25:48 +00:00
|
|
|
include_list = []
|
2020-02-29 19:56:47 +00:00
|
|
|
for dll in dll_files:
|
|
|
|
print("Including: "+dll)
|
2020-08-16 15:25:48 +00:00
|
|
|
include_list.append("Source: \""+dll+"\"; DestDir: \"{app}\"; Flags: ignoreversion")
|
|
|
|
|
|
|
|
print("Including modulo")
|
2020-08-16 16:56:07 +00:00
|
|
|
include_list.append("Source: \""+os.path.abspath(modulo_target_file)+"\"; DestDir: \"{app}\"; Flags: ignoreversion")
|
2020-02-29 19:56:47 +00:00
|
|
|
|
2020-08-16 15:25:48 +00:00
|
|
|
include = "\r\n".join(include_list)
|
2019-09-30 20:40:19 +00:00
|
|
|
|
2019-09-21 11:09:24 +00:00
|
|
|
INSTALLER_NAME = f"espanso-win-installer"
|
2019-09-16 14:20:52 +00:00
|
|
|
|
|
|
|
# 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)
|
2020-08-16 15:25:48 +00:00
|
|
|
content = content.replace("{{{dll_include}}}", include)
|
2019-09-16 14:20:52 +00:00
|
|
|
|
|
|
|
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"))])
|
|
|
|
|
2019-10-12 08:16:34 +00:00
|
|
|
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())
|
|
|
|
|
2019-09-18 09:10:36 +00:00
|
|
|
|
|
|
|
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...")
|
2019-09-21 11:09:24 +00:00
|
|
|
target_name = f"espanso-mac.tar.gz"
|
2019-09-18 09:10:36 +00:00
|
|
|
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}")
|
|
|
|
|
2019-10-12 08:16:34 +00:00
|
|
|
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())
|
|
|
|
|
2019-09-18 09:10:36 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
# 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!")
|
|
|
|
|
2020-02-29 17:47:36 +00:00
|
|
|
|
2019-09-16 14:20:52 +00:00
|
|
|
if __name__ == '__main__':
|
|
|
|
print("[[ espanso packager ]]")
|
|
|
|
|
|
|
|
# Check python version 3
|
|
|
|
if sys.version_info[0] < 3:
|
|
|
|
raise Exception("Must be using Python 3")
|
|
|
|
|
|
|
|
cli()
|