feat: 让 pyscript 支持指定安装目录

This commit is contained in:
寂静的羽夏 2025-07-27 11:15:26 +08:00
parent 6678c7b9ff
commit 2ea09fa7eb
1 changed files with 124 additions and 157 deletions

281
mkinstaller/pyscript/installer.py Executable file → Normal file
View File

@ -14,7 +14,9 @@ import subprocess
from colorama import Fore, Style from colorama import Fore, Style
PACKAGE_NAME = "WingHexExplorer2" PACKAGE_NAME = "WingHexExplorer2"
INSTALL_PATH = f"/opt/{PACKAGE_NAME}" DEFAULT_INSTALL_PATH = f"/opt/{PACKAGE_NAME}"
INSTALL_PATH = DEFAULT_INSTALL_PATH
APP_DESKTOP_PATH = "/usr/share/applications" APP_DESKTOP_PATH = "/usr/share/applications"
DESKTOP_FILE_NAME = "com.wingsummer.winghexexplorer2.desktop" DESKTOP_FILE_NAME = "com.wingsummer.winghexexplorer2.desktop"
@ -59,163 +61,137 @@ def run_command_interactive(command):
if output: if output:
print(Fore.GREEN + output.strip() + Style.RESET_ALL) print(Fore.GREEN + output.strip() + Style.RESET_ALL)
return_code = process.wait() return process.wait()
return return_code
def create_dir(dir): def create_dir(dir_path):
if not os.path.exists(dir): if not os.path.exists(dir_path):
os.mkdir(dir) os.mkdir(dir_path)
def update(build_path): def update(build_path):
if (os.path.exists(INSTALL_PATH) == False): if not os.path.exists(INSTALL_PATH):
print( print(Fore.RED + "[Error] The installing path not exists! Please use 'install' instead." + Style.RESET_ALL)
Fore.RED + "[Error] The installing path not exists! Please use 'install' instead." + Style.RESET_ALL)
exit(3) exit(3)
installer_path = os.path.dirname(os.path.abspath(__file__)) installer_path = os.path.dirname(os.path.abspath(__file__))
projectbase = os.path.abspath(os.path.join(installer_path, "../..")) project_base = os.path.abspath(os.path.join(installer_path, "../.."))
print(Fore.GREEN + ">> Checking file integrity..." + Style.RESET_ALL) print(Fore.GREEN + ">> Checking file integrity..." + Style.RESET_ALL)
if (os.path.exists(build_path) == False): # 验证构建目录
print( if not os.path.isdir(build_path) or not os.path.exists(os.path.join(build_path, "CMakeCache.txt")):
Fore.RED + "[Error] Not found a CMake build directory!" + Style.RESET_ALL) print(Fore.RED + "[Error] Not a valid CMake build directory!" + Style.RESET_ALL)
exit(-1) exit(-1)
if (os.path.exists(os.path.join(build_path, "CMakeCache.txt")) == False): # 版本文件检查
print( for vf in ("WINGHEX_VERSION", "QT_VERSION"):
Fore.RED + "[Error] This is not a CMake build directory!" + Style.RESET_ALL) if not os.path.exists(os.path.join(build_path, vf)):
exit(-1) print(Fore.RED + f"[Error] {vf} file not found!" + Style.RESET_ALL)
exit(-1)
version_file_src = os.path.join(build_path, "WINGHEX_VERSION") # 可执行文件检查
if (os.path.exists(version_file_src) == False): exe_src = os.path.join(build_path, PACKAGE_NAME)
print( if not os.path.exists(exe_src):
Fore.RED + "[Error] WINGHEX_VERSION file not found, maybe not a CMake build directory!" + Style.RESET_ALL) print(Fore.RED + f"[Error] {PACKAGE_NAME} executable not found!" + Style.RESET_ALL)
exit(-1)
version_qt_src = os.path.join(build_path, "QT_VERSION")
if (os.path.exists(version_qt_src) == False):
print(
Fore.RED + "[Error] QT_VERSION file not found, maybe not a CMake build directory!" + Style.RESET_ALL)
exit(-1)
# ok, start copying files
exemain_src = os.path.join(build_path, PACKAGE_NAME)
if (os.path.exists(exemain_src) == False):
print(
Fore.RED + f"[Error] {PACKAGE_NAME} is not found!" + Style.RESET_ALL)
exit(-3) exit(-3)
# calculate the md5 checksum # 计算 MD5
with open(exemain_src, 'rb') as file_to_check: with open(exe_src, 'rb') as f:
data = file_to_check.read() md5sum = hashlib.md5(f.read()).hexdigest().upper()
md5_returned = hashlib.md5(data).hexdigest().upper() print(Fore.GREEN + ">> Get MD5: " + md5sum + Style.RESET_ALL)
print(Fore.GREEN + ">> Get MD5: " + md5_returned + Style.RESET_ALL)
print(Fore.GREEN + ">> Installing..." + Style.RESET_ALL) print(Fore.GREEN + ">> Installing..." + Style.RESET_ALL)
shutil.copy2(exemain_src, os.path.join(INSTALL_PATH, PACKAGE_NAME)) # 复制核心文件
shutil.copy2(exe_src, os.path.join(INSTALL_PATH, PACKAGE_NAME))
shutil.copy2(os.path.join(build_path, "WingPlugin", "libWingPlugin.so"), shutil.copy2(os.path.join(build_path, "WingPlugin", "libWingPlugin.so"),
os.path.join(INSTALL_PATH, "libWingPlugin.so")) os.path.join(INSTALL_PATH, "libWingPlugin.so"))
create_dir(os.path.join(INSTALL_PATH, "plugin")) # 目录结构
create_dir(os.path.join(INSTALL_PATH, "scripts")) for sub in ("plugin", "scripts", "aslib"):
create_dir(os.path.join(INSTALL_PATH, "aslib")) create_dir(os.path.join(INSTALL_PATH, sub))
# 共享资源与多语言
shutil.copytree(os.path.join(installer_path, "share"), shutil.copytree(os.path.join(installer_path, "share"),
os.path.join(INSTALL_PATH, "share"), dirs_exist_ok=True) os.path.join(INSTALL_PATH, "share"), dirs_exist_ok=True)
shutil.copytree(os.path.join(build_path, "lang"), shutil.copytree(os.path.join(build_path, "lang"),
os.path.join(INSTALL_PATH, "lang"), dirs_exist_ok=True) os.path.join(INSTALL_PATH, "lang"), dirs_exist_ok=True)
# 其他材料
print(Fore.GREEN + ">> Copying License and other materials..." + Style.RESET_ALL) print(Fore.GREEN + ">> Copying License and other materials..." + Style.RESET_ALL)
material_files = ["LICENSE", "authorband.svg", "licenseband.svg", "screenshot.png", "README.md"]
material_files = ["LICENSE", "authorband.svg",
"licenseband.svg", "screenshot.png", "README.md"]
for f in material_files: for f in material_files:
shutil.copyfile(os.path.join(projectbase, f), shutil.copyfile(os.path.join(project_base, f),
os.path.join(INSTALL_PATH, f)) os.path.join(INSTALL_PATH, f))
shutil.copyfile(os.path.join(project_base, "images", "author.jpg"),
shutil.copyfile(os.path.join(projectbase, "images", "author.jpg"),
os.path.join(INSTALL_PATH, "author.jpg")) os.path.join(INSTALL_PATH, "author.jpg"))
with open(os.path.join(INSTALL_PATH, "md5sums"), 'w') as md5_file: # 写 md5sums
md5_file.write(md5_returned) with open(os.path.join(INSTALL_PATH, "md5sums"), 'w') as md5f:
md5f.write(md5sum)
# 桌面文件
shutil.copyfile(os.path.join(installer_path, DESKTOP_FILE_NAME), shutil.copyfile(os.path.join(installer_path, DESKTOP_FILE_NAME),
os.path.join(APP_DESKTOP_PATH, DESKTOP_FILE_NAME)) os.path.join(APP_DESKTOP_PATH, DESKTOP_FILE_NAME))
run_command_interactive( # 更新 mime 和图标缓存
["xdg-mime", "install", "/opt/WingHexExplorer2/share/x-winghex.xml"]) cmds = [
run_command_interactive( ["xdg-mime", "install", os.path.join(INSTALL_PATH, "share", "x-winghex.xml")],
["xdg-mime", "default", "/usr/share/applications/com.wingsummer.winghexexplorer2.desktop", "application/x-winghex"]) ["xdg-mime", "default", DESKTOP_FILE_NAME, "application/x-winghex"],
run_command_interactive( ["xdg-icon-resource", "install", "--context", "mimetypes", "--size", "32",
["xdg-icon-resource", "install", "--context", "mimetypes", "--size", "32", "/opt/WingHexExplorer2/share/winghexpro32.png", "application-x-winghex"]) os.path.join(INSTALL_PATH, "share", "winghexpro32.png"), "application-x-winghex"],
run_command_interactive( ["xdg-icon-resource", "install", "--context", "mimetypes", "--size", "64",
["xdg-icon-resource", "install", "--context", "mimetypes", "--size", "64", "/opt/WingHexExplorer2/share/winghexpro64.png", "application-x-winghex"]) os.path.join(INSTALL_PATH, "share", "winghexpro64.png"), "application-x-winghex"],
run_command_interactive( ["xdg-icon-resource", "install", "--context", "mimetypes", "--size", "128",
["xdg-icon-resource", "install", "--context", "mimetypes", "--size", "128", "/opt/WingHexExplorer2/share/winghexpro128.png", "application-x-winghex"]) os.path.join(INSTALL_PATH, "share", "winghexpro128.png"), "application-x-winghex"],
run_command_interactive( ["update-mime-database", "/usr/share/mime"],
["update-mime-database", "/usr/share/mime"]) ["xdg-icon-resource", "forceupdate"],
run_command_interactive( ["gtk-update-icon-cache", "/usr/share/icons/hicolor"],
["xdg-icon-resource", "forceupdate"]) ["update-desktop-database", "/usr/share/applications"],
run_command_interactive( ]
["gtk-update-icon-cache", "/usr/share/icons/hicolor"]) for cmd in cmds:
run_command_interactive( run_command_interactive(cmd)
["update-desktop-database", "/usr/share/applications"])
print(Fore.GREEN + ">> Installation finished..." + Style.RESET_ALL) print(Fore.GREEN + ">> Installation finished..." + Style.RESET_ALL)
def install(build_path): def install(build_path):
if (os.path.exists(INSTALL_PATH)): if os.path.exists(INSTALL_PATH):
print( print(Fore.RED + "[Error] The installing path exists! Please use 'update' or 'uninstall' first." + Style.RESET_ALL)
Fore.RED + "[Error] The installing path exists! Please use 'update' instead or 'uninstall' before it." + Style.RESET_ALL)
exit(3) exit(3)
os.makedirs(INSTALL_PATH) os.makedirs(INSTALL_PATH)
update(build_path) update(build_path)
def remove(path): def remove(path):
""" param <path> could either be relative or absolute. """ """Remove file or directory recursively."""
if os.path.isfile(path) or os.path.islink(path): if os.path.isfile(path) or os.path.islink(path):
os.remove(path) # remove the file os.remove(path)
elif os.path.isdir(path): elif os.path.isdir(path):
shutil.rmtree(path) # remove dir and all contains shutil.rmtree(path)
def is_empty(path): def is_empty(path):
if os.path.exists(path) and not os.path.isfile(path): return os.path.isdir(path) and not os.listdir(path)
# Checking if the directory is empty or not
if not os.listdir(path):
return True
else:
return False
else:
return False
def uninstall(): def uninstall():
run_command_interactive( cmds = [
["xdg-mime", "uninstall", "/opt/WingHexExplorer2/share/x-winghex.xml"]) ["xdg-mime", "uninstall", os.path.join(INSTALL_PATH, "share", "x-winghex.xml")],
run_command_interactive( ["xdg-icon-resource", "uninstall", "--context", "mimetypes", "--size", "32", "application-x-winghex"],
["xdg-icon-resource", "uninstall", "--context", "mimetypes", "--size", "32", "application-x-winghex"]) ["xdg-icon-resource", "uninstall", "--context", "mimetypes", "--size", "64", "application-x-winghex"],
run_command_interactive( ["xdg-icon-resource", "uninstall", "--context", "mimetypes", "--size", "128", "application-x-winghex"],
["xdg-icon-resource", "uninstall", "--context", "mimetypes", "--size", "64", "application-x-winghex"]) ["update-mime-database", "/usr/share/mime"],
run_command_interactive( ["xdg-icon-resource", "forceupdate"],
["xdg-icon-resource", "uninstall", "--context", "mimetypes", "--size", "128", "application-x-winghex"]) ["update-icon-caches", "/usr/share/icons/hicolor"],
run_command_interactive( ["update-desktop-database", "/usr/share/applications"],
["update-mime-database", "/usr/share/mime"]) ]
run_command_interactive( for cmd in cmds:
["xdg-icon-resource", "forceupdate"]) run_command_interactive(cmd)
run_command_interactive(
["update-icon-caches", "/usr/share/icons/hicolor"])
remove(os.path.join(APP_DESKTOP_PATH, DESKTOP_FILE_NAME)) remove(os.path.join(APP_DESKTOP_PATH, DESKTOP_FILE_NAME))
run_command_interactive(
["update-desktop-database", "/usr/share/applications"])
remove(INSTALL_PATH) remove(INSTALL_PATH)
print(Fore.GREEN + ">> Uninstallation finished..." + Style.RESET_ALL) print(Fore.GREEN + ">> Uninstallation finished..." + Style.RESET_ALL)
@ -225,82 +201,74 @@ CONF_FILE = "WingHexExplorer2.conf"
CONTENT_PATH = ".local/share/WingCloudStudio" CONTENT_PATH = ".local/share/WingCloudStudio"
def clear_usrdata(usr): def clear_usrdata(user):
usr_path = "" if user == "root":
if (usr == "root"): home = "/root"
usr_path = "/root"
else: else:
usr_path = f"/home/{usr}" home = f"/home/{user}"
desktop_file = os.path.join(usr_path, AUTO_START, DESKTOP_FILE_NAME) remove(os.path.join(home, AUTO_START, DESKTOP_FILE_NAME))
remove(desktop_file) cfgdir = os.path.join(home, CFG_PATH)
remove(os.path.join(cfgdir, CONF_FILE))
if is_empty(cfgdir):
remove(cfgdir)
cfgpath = os.path.join(usr_path, CFG_PATH) contentdir = os.path.join(home, CONTENT_PATH, PACKAGE_NAME)
remove(contentdir)
parent = os.path.join(home, CONTENT_PATH)
if is_empty(parent):
remove(parent)
remove(os.path.join(cfgpath, CONF_FILE)) print(Fore.GREEN + f">> Purged {user}..." + Style.RESET_ALL)
if is_empty(cfgpath):
remove(cfgpath)
cfgpath = os.path.join(usr_path, CONTENT_PATH, PACKAGE_NAME)
remove(cfgpath)
cfgpath = os.path.join(usr_path, CONTENT_PATH)
if is_empty(cfgpath):
remove(cfgpath)
print(Fore.GREEN + f">> Purged ${usr}..." + Style.RESET_ALL)
def purge(): def purge():
uninstall() uninstall()
for p in os.listdir("/home"): for user in os.listdir("/home"):
if p == "lost+found": if user == "lost+found":
continue continue
clear_usrdata(p) clear_usrdata(user)
clear_usrdata("root") clear_usrdata("root")
print(Fore.GREEN + ">> Clean-up finished..." + Style.RESET_ALL) print(Fore.GREEN + ">> Clean-up finished..." + Style.RESET_ALL)
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(prog="installer.py",
prog="installer.py", description=f"A installing manager for {PACKAGE_NAME}") description=f"A installing manager for {PACKAGE_NAME}")
parser.add_argument( parser.add_argument("action",
"action", choices=["install", "update", "uninstall", "purge"],
choices=["install", "update", "uninstall", "purge"], nargs="?",
nargs="?", default="install",
default="install", help="Action to perform: install (default), update, uninstall, or purge.")
help="Action to perform: install (default), update, uninstall, or purge." parser.add_argument("-b", "--build",
) type=str,
parser.add_argument( required=False,
"-b", "--build", help="Path to the build directory (required for install/update).")
type=str, parser.add_argument("-p", "--prefix",
required=False, dest="install_path",
help="Path to the building files for installation (required for 'install' and 'update' action)." type=str,
) default=DEFAULT_INSTALL_PATH,
help=f"Installation directory (default: {DEFAULT_INSTALL_PATH})")
args = parser.parse_args() args = parser.parse_args()
if check_is_running(PACKAGE_NAME): # 覆盖全局变量
print( global INSTALL_PATH
Fore.RED + f"[Error] You should exit {PACKAGE_NAME} berfore installation." + Style.RESET_ALL) INSTALL_PATH = args.install_path
exit(1)
if check_is_running(PACKAGE_NAME):
print(Fore.RED + f"[Error] Please exit {PACKAGE_NAME} before installation." + Style.RESET_ALL)
exit(1)
if not is_root(): if not is_root():
print( print(Fore.RED + "[Error] root privilege is required." + Style.RESET_ALL)
Fore.RED + "[Error] root is required for installation." + Style.RESET_ALL) exit(2)
if args.action in ("install", "update") and not args.build:
print(Fore.RED + "[Error] --build path is required for install/update." + Style.RESET_ALL)
exit(2) exit(2)
# checking build toolkits
if args.action == "install": if args.action == "install":
if not args.build:
print(
Fore.RED + "[Error] --build path is required for installation." + Style.RESET_ALL)
exit(2)
install(args.build) install(args.build)
elif args.action == "update": elif args.action == "update":
if not args.build:
print(
Fore.RED + "[Error] --build path is required for installation." + Style.RESET_ALL)
exit(2)
update(args.build) update(args.build)
elif args.action == "uninstall": elif args.action == "uninstall":
uninstall() uninstall()
@ -313,5 +281,4 @@ def main():
if __name__ == "__main__": if __name__ == "__main__":
main() main()
else: else:
print( print(Fore.RED + "[Error] Please run this script in main mode" + Style.RESET_ALL)
Fore.RED + "[Error] Please run this script in main mode" + Style.RESET_ALL)