387 lines
16 KiB
Python
Executable File
387 lines
16 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
import argparse
|
|
import os
|
|
import platform
|
|
import sys
|
|
import subprocess
|
|
from shutil import which, rmtree
|
|
import json
|
|
import datetime
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--install-all-apps", action="store_true",
|
|
help='install/update all available applications')
|
|
parser.add_argument("--install-core-deps", action="store_true",
|
|
help='only install/update core dependencies')
|
|
parser.add_argument("--install", nargs='+', default=[],
|
|
help='only install/update apps listed here')
|
|
parser.add_argument("--install-new-apps", action="store_true",
|
|
help='also install previously uninstalled or new applications')
|
|
parser.add_argument("--force", action="store_true",
|
|
help="force install/update app dependencies even if apps are already up-to-date")
|
|
parser.add_argument("--ignore-core-deps", action="store_true",
|
|
help='ignore core dependencies')
|
|
parser.add_argument("--ignore-sys-deps", action="store_true",
|
|
help='ignore system dependencies')
|
|
parser.add_argument("--ignore-py-deps", action="store_true",
|
|
help='ignore python dependencies')
|
|
parser.add_argument("--ignore-node-deps", action="store_true",
|
|
help='ignore node dependencies')
|
|
parser.add_argument("--git-full-clone", action="store_true",
|
|
help='during installation, conduct a full clone to preserve git logs')
|
|
parser.add_argument("--app-drop-local-edit", action="store_true",
|
|
help='during installation, local changes to app repos will be hard reset')
|
|
parser.add_argument("--app-save-local-edit", action="store_true",
|
|
help='compared with --app-drop-local-edit, this option will stash your changes')
|
|
parser.add_argument("--use-mirror", action="store_true",
|
|
help='use mirror url instead of default url')
|
|
parser.add_argument("--use-gitee", action="store_true",
|
|
help='alias of --use-mirror')
|
|
args = parser.parse_args()
|
|
|
|
NPM_CMD = "npm.cmd" if platform.system() == "Windows" else "npm"
|
|
|
|
class bcolors:
|
|
HEADER = '\033[95m'
|
|
OKBLUE = '\033[94m'
|
|
OKCYAN = '\033[96m'
|
|
OKGREEN = '\033[92m'
|
|
WARNING = '\033[93m'
|
|
FAIL = '\033[91m'
|
|
ENDC = '\033[0m'
|
|
BOLD = '\033[1m'
|
|
UNDERLINE = '\033[4m'
|
|
|
|
script_path = os.path.dirname(os.path.realpath(__file__))
|
|
def get_available_apps_dict():
|
|
with open(os.path.join(script_path, 'applications.json')) as f:
|
|
info = json.load(f)
|
|
apps_dict = {}
|
|
for app_name, app_spec_dict in info.items():
|
|
if app_spec_dict["type"] == "app":
|
|
apps_dict[app_name] = app_spec_dict
|
|
return apps_dict
|
|
|
|
available_apps_dict = get_available_apps_dict()
|
|
|
|
important_messages = [
|
|
"[EAF] Please run both 'git pull' and 'install-eaf.py' (M-x eaf-install-and-update) to update EAF, applications and their dependencies."
|
|
]
|
|
|
|
def run_command(command, path=script_path, ensure_pass=True, get_result=False):
|
|
print("[EAF] Running", ' '.join(command), "@", path)
|
|
|
|
# Use LC_ALL=C to make sure command output use English.
|
|
# Then we can use English keyword to check command output.
|
|
english_env = os.environ.copy()
|
|
english_env['LC_ALL'] = 'C'
|
|
|
|
if get_result:
|
|
process = subprocess.Popen(command, env = english_env, stdin = subprocess.PIPE,
|
|
universal_newlines=True, text=True, cwd=path,
|
|
stdout = subprocess.PIPE)
|
|
else:
|
|
process = subprocess.Popen(command, env = english_env, stdin = subprocess.PIPE,
|
|
universal_newlines=True, text=True, cwd=path)
|
|
process.wait()
|
|
if process.returncode != 0 and ensure_pass:
|
|
sys.exit(process.returncode)
|
|
if get_result:
|
|
return process.stdout.readlines()
|
|
else:
|
|
return None
|
|
|
|
def prune_existing_sys_deps(deps_list):
|
|
remove_deps = []
|
|
for dep in deps_list:
|
|
if "node" in dep and which("node"):
|
|
remove_deps.append(dep)
|
|
elif "npm" in dep and which("npm"):
|
|
remove_deps.append(dep)
|
|
return list(set(deps_list) - set(remove_deps))
|
|
|
|
def install_sys_deps(distro: str, deps_list):
|
|
deps_list = prune_existing_sys_deps(deps_list)
|
|
command = []
|
|
if which("dnf"):
|
|
command = ['sudo', 'dnf', '-y', 'install']
|
|
elif distro == 'apt':
|
|
command = ['sudo', 'apt', '-y', 'install']
|
|
elif distro == 'pacman':
|
|
command = ['yay', '-Sy', '--noconfirm', '--needed']
|
|
elif which("pkg"):
|
|
command = ['doas', 'pkg', '-y', 'install']
|
|
elif which("zypper"):
|
|
command = ['sudo', 'zypper', 'install','-y']
|
|
command.extend(deps_list)
|
|
return run_command(command)
|
|
|
|
def install_py_deps(deps_list):
|
|
command = ['pip', 'install', '--user']
|
|
command.extend(deps_list)
|
|
return run_command(command)
|
|
|
|
def remove_node_modules_path(app_path_list):
|
|
for app_path in app_path_list:
|
|
node_modules_path = os.path.join(app_path, "node_modules")
|
|
if os.path.isdir(node_modules_path):
|
|
rmtree(node_modules_path)
|
|
print("[EAF] WARN: removing {}".format(node_modules_path))
|
|
|
|
def install_npm_install(app_path_list):
|
|
for app_path in app_path_list:
|
|
command = [NPM_CMD, "install"]
|
|
run_command(command, path=app_path)
|
|
|
|
def install_npm_rebuild(app_path_list):
|
|
for app_path in app_path_list:
|
|
command = [NPM_CMD, 'rebuild']
|
|
run_command(command, path=app_path)
|
|
|
|
def install_vue_install(app_path_list):
|
|
for app_path in app_path_list:
|
|
command = [NPM_CMD, 'install']
|
|
run_command(command, path=app_path)
|
|
command = [NPM_CMD, 'run', 'build']
|
|
run_command(command, path=app_path)
|
|
|
|
def add_or_update_app(app: str, app_spec_dict):
|
|
url = ""
|
|
path = os.path.join("app", app)
|
|
if args.use_mirror or args.use_gitee: # use_gitee is alias of use_mirror.
|
|
if 'mirror_url' not in app_spec_dict:
|
|
print("[EAF] There is no gitee mirror URL set in applications.json", app)
|
|
sys.exit(1)
|
|
url = app_spec_dict['mirror_url']
|
|
else:
|
|
url = app_spec_dict['url']
|
|
|
|
branch = app_spec_dict['branch']
|
|
time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M')
|
|
|
|
if os.path.exists(path):
|
|
print("[EAF] Updating", app, "to newest version...")
|
|
else:
|
|
print("[EAF] Adding", app, "application to EAF...")
|
|
|
|
updated = True
|
|
if os.path.exists(path):
|
|
if args.app_drop_local_edit:
|
|
print("[EAF] Clean {}'s local changed for pull code automatically.".format(app))
|
|
run_command(["git", "clean", "-df"], path=path, ensure_pass=False)
|
|
run_command(["git", "reset", "--hard", "origin"], path=path, ensure_pass=False)
|
|
elif args.app_save_local_edit:
|
|
print("[EAF] Clean {}'s local changed for pull code automatically.".format(app))
|
|
run_command(["git", "clean", "-df"], path=path, ensure_pass=False)
|
|
run_command(["git", "stash", "save", "[{}] Auto stashed by install-eaf.py".format(time)], path=path, ensure_pass=False)
|
|
run_command(["git", "reset", "--hard"], path=path, ensure_pass=False)
|
|
run_command(["git", "checkout", branch], path=path, ensure_pass=False)
|
|
run_command(["git", "reset", "--hard", "origin"], path=path, ensure_pass=False)
|
|
|
|
output_lines = run_command(["git", "pull"], path=path, get_result=True)
|
|
if output_lines is None:
|
|
raise Exception("git pull failed!")
|
|
for output in output_lines:
|
|
print(output.rstrip())
|
|
if "Already up to date." in output:
|
|
updated = False
|
|
|
|
elif args.git_full_clone:
|
|
run_command(["git", "clone", "--single-branch", url, path])
|
|
else:
|
|
run_command(["git", "clone", "--depth", "1", "--single-branch", url, path])
|
|
return updated
|
|
|
|
def get_distro():
|
|
distro = ""
|
|
if which("dnf"):
|
|
distro = "dnf"
|
|
elif which("apt"):
|
|
distro = "apt"
|
|
elif which("pacman"):
|
|
distro = "pacman"
|
|
if (not args.ignore_core_deps and not args.ignore_sys_deps and len(args.install) == 0) or args.install_core_deps:
|
|
run_command(['yay', '-Sy', '--noconfirm', '--needed'])
|
|
elif which("pkg"):
|
|
distro = "pkg"
|
|
elif which("zypper"):
|
|
distro = "zypper"
|
|
elif which("brew"):
|
|
distro = "brew"
|
|
elif sys.platform == "linux":
|
|
print("[EAF] Unsupported Linux distribution/package manager.")
|
|
print(" Please see dependencies.json for list of dependencies.")
|
|
if not (args.ignore_core_deps or args.ignore_sys_deps):
|
|
sys.exit(1)
|
|
|
|
return distro
|
|
|
|
def install_core_deps(distro, deps_dict):
|
|
print("[EAF] Installing core dependencies")
|
|
core_deps = []
|
|
if not args.ignore_sys_deps and sys.platform == "linux":
|
|
core_deps.extend(deps_dict[distro])
|
|
if len(core_deps) > 0:
|
|
install_sys_deps(distro, core_deps)
|
|
if (not args.ignore_py_deps or sys.platform != "linux") and sys.platform in deps_dict["pip"]:
|
|
install_py_deps(deps_dict["pip"][sys.platform])
|
|
print("[EAF] Finished installing core dependencies")
|
|
|
|
def yes_no(question, default_yes=False, default_no=False):
|
|
key = input(question)
|
|
if default_yes:
|
|
return key.lower() == 'y' or key == ""
|
|
elif default_no:
|
|
return key.lower() == 'y' or not (key == "" or key.lower() == 'n')
|
|
else:
|
|
return key.lower() == 'y'
|
|
|
|
def get_installed_apps(app_dir):
|
|
apps_installed = [f for f in os.listdir(app_dir) if os.path.isdir(os.path.join(app_dir, f))]
|
|
for app in apps_installed:
|
|
git_dir = os.path.join(app_dir, app, ".git")
|
|
if app not in get_available_apps_dict().keys():
|
|
apps_installed.remove(app)
|
|
if not os.path.isdir(git_dir):
|
|
important_messages.append("[EAF] *WARN* 'app/{}' is not a git repo installed by install-eaf.py!".format(app))
|
|
apps_installed.remove(app)
|
|
|
|
return apps_installed
|
|
|
|
def get_installed_apps_dict(apps_installed):
|
|
return {app_name: available_apps_dict[app_name] for app_name in apps_installed}
|
|
|
|
def get_new_apps_dict(apps_installed):
|
|
not_installed_apps_dict = {}
|
|
new_apps_dict = {}
|
|
num = 1
|
|
for app_name, app_spec_dict in available_apps_dict.items():
|
|
if app_name not in apps_installed:
|
|
not_installed_apps_dict[app_name] = app_spec_dict
|
|
for app_name, app_spec_dict in not_installed_apps_dict.items():
|
|
indicator = "({}/{})".format(num, len(not_installed_apps_dict))
|
|
if yes_no("[EAF] " + indicator + " " + app_name + ". Install? (Y/n): ", default_yes=True):
|
|
new_apps_dict[app_name] = app_spec_dict
|
|
num = num + 1
|
|
return new_apps_dict
|
|
|
|
def get_specific_install_apps_dict(apps_need_install):
|
|
need_install_apps_dict = {}
|
|
for app_name, app_spec_dict in available_apps_dict.items():
|
|
if app_name in apps_need_install:
|
|
need_install_apps_dict[app_name] = app_spec_dict
|
|
return need_install_apps_dict
|
|
|
|
def print_sample_config(app_dir):
|
|
for app in get_installed_apps(app_dir):
|
|
print("(require 'eaf-{})".format(app))
|
|
|
|
def get_install_apps(apps_installed):
|
|
if args.install_all_apps:
|
|
return [get_available_apps_dict()]
|
|
if len(args.install) > 0:
|
|
return [get_specific_install_apps_dict(args.install)]
|
|
|
|
pending_apps_dict_list = [get_installed_apps_dict(apps_installed)]
|
|
if args.install_new_apps or len(apps_installed) == 0:
|
|
pending_apps_dict_list.append(get_new_apps_dict(apps_installed))
|
|
elif not args.install_new_apps:
|
|
important_messages.append("[EAF] Use the flag '--install-new-apps' to install previously uninstalled or new apps.")
|
|
|
|
return pending_apps_dict_list
|
|
|
|
def install_app_deps(distro, deps_dict):
|
|
print("[EAF] Installing application dependencies")
|
|
|
|
app_dir = os.path.join(script_path, "app")
|
|
if not os.path.exists(app_dir):
|
|
os.makedirs(app_dir)
|
|
|
|
# TODO: REMOVE ME: Delete obsolete file, we don't use it anymore
|
|
prev_app_choices_file = os.path.join(script_path, '.eaf-installed-apps.json')
|
|
if os.path.exists(prev_app_choices_file):
|
|
print(prev_app_choices_file, "is obsolete, removing...")
|
|
os.remove(prev_app_choices_file)
|
|
|
|
apps_installed = get_installed_apps(app_dir)
|
|
pending_apps_dict_list = get_install_apps(apps_installed)
|
|
|
|
sys_deps = []
|
|
py_deps = []
|
|
npm_install_apps = []
|
|
vue_install_apps = []
|
|
npm_rebuild_apps = []
|
|
for pending_apps_dict in pending_apps_dict_list:
|
|
for app_name, app_spec_dict in pending_apps_dict.items():
|
|
updated = add_or_update_app(app_name, app_spec_dict)
|
|
app_path = os.path.join(app_dir, app_name)
|
|
app_dep_path = os.path.join(app_path, 'dependencies.json')
|
|
if (updated or args.force) and os.path.exists(app_dep_path):
|
|
with open(os.path.join(app_dep_path)) as f:
|
|
deps_dict = json.load(f)
|
|
if not args.ignore_sys_deps and sys.platform == "linux" and distro in deps_dict:
|
|
sys_deps.extend(deps_dict[distro])
|
|
if not args.ignore_py_deps and 'pip' in deps_dict and sys.platform in deps_dict['pip']:
|
|
py_deps.extend(deps_dict['pip'][sys.platform])
|
|
if not args.ignore_node_deps:
|
|
if 'npm_install' in deps_dict and deps_dict['npm_install']:
|
|
npm_install_apps.append(app_path)
|
|
if 'vue_install' in deps_dict and deps_dict['vue_install']:
|
|
vue_install_apps.append(app_path)
|
|
if 'npm_rebuild' in deps_dict and deps_dict['npm_rebuild']:
|
|
npm_rebuild_apps.append(app_path)
|
|
|
|
print("\n[EAF] Installing dependencies for installed applications")
|
|
if not args.ignore_sys_deps and sys.platform == "linux" and len(sys_deps) > 0:
|
|
print("[EAF] Installing system dependencies")
|
|
install_sys_deps(distro, sys_deps)
|
|
if not args.ignore_py_deps and len(py_deps) > 0:
|
|
print("[EAF] Installing python dependencies")
|
|
install_py_deps(py_deps)
|
|
if not args.ignore_node_deps:
|
|
if args.force:
|
|
if len(npm_install_apps) > 0:
|
|
remove_node_modules_path(npm_install_apps)
|
|
if len(vue_install_apps) > 0:
|
|
remove_node_modules_path(vue_install_apps)
|
|
if len(npm_install_apps) > 0:
|
|
install_npm_install(npm_install_apps)
|
|
if len(npm_rebuild_apps) > 0:
|
|
install_npm_rebuild(npm_rebuild_apps)
|
|
if len(vue_install_apps) > 0:
|
|
install_vue_install(vue_install_apps)
|
|
|
|
print("[EAF] Finished installing applications and their dependencies")
|
|
print("[EAF] Please ensure the following are added to your init.el:")
|
|
|
|
print_sample_config(app_dir)
|
|
|
|
for msg in important_messages:
|
|
print(bcolors.WARNING + msg + bcolors.ENDC)
|
|
|
|
def main():
|
|
try:
|
|
distro = get_distro()
|
|
with open(os.path.join(script_path, 'dependencies.json')) as f:
|
|
deps_dict = json.load(f)
|
|
|
|
if (not args.ignore_core_deps and len(args.install) == 0) or args.install_core_deps:
|
|
print("[EAF] ------------------------------------------")
|
|
install_core_deps(distro, deps_dict)
|
|
print("[EAF] ------------------------------------------")
|
|
|
|
if not args.install_core_deps:
|
|
print("[EAF] ------------------------------------------")
|
|
install_app_deps(distro, deps_dict)
|
|
print("[EAF] ------------------------------------------")
|
|
|
|
print("[EAF] install-eaf.py finished.")
|
|
except KeyboardInterrupt:
|
|
print("[EAF] install-eaf.py aborted!")
|
|
sys.exit()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|