diff --git a/utilities/install-lmk b/utilities/install-lmk index 9a078d6..579bda0 100755 --- a/utilities/install-lmk +++ b/utilities/install-lmk @@ -46,25 +46,11 @@ if sys.version_info.major < 3: INSTALL_SRC = os.path.join(HOME, "linux-minetest-kit-rsync", "mtkit", "minetest") VARIANT = "rsync" -mode_of_path = { - os.path.join("bin", "finetest"): { - 'name': "finetest", - 'appender': "", - 'executable': "finetest", - }, - os.path.join("bin", "finetestserver"): { - 'name': "finetest", - 'appender': "server", - 'executable': "finetestserver", - }, - # indeterminate if bin/minetest: same exe for classic & trolltest -} - MINETEST_KEYWORDS = ("sandbox;world;mining;crafting;blocks;nodes;multiplayer;" "roleplaying;") project_metas = { - 'minetest': { # minetest is the project name (in mtsrc/newline dir) + 'classic': { # minetest is the project name (in mtsrc/newline dir) 'shortcut': { 'GenericName': "Final Minetest", 'Keywords': MINETEST_KEYWORDS, @@ -109,10 +95,10 @@ project_metas = { 'Keywords': MINETEST_KEYWORDS+"minetest;", }, 'dirname': "trolltest", - 'name_and_variant_fmt': "Trolltest ({}) (minetest.org)", + 'name_and_variant_fmt': "Trolltest ({}) (minetest.org build)", 'name': "Trolltest (minetest.org)", 'shortcut_exe_relpaths': [ - os.path.join("bin", "minetest"), + os.path.join("bin", "trolltest"), ], 'platform_icon_relpath': { 'Linux': os.path.join("misc", "minetest.svg"), @@ -124,11 +110,24 @@ project_metas = { }, } +arg_project_name = { + # 'final': "classic", + 'classic': "classic", + 'trolltest': "trolltest", + # 'troll': "trolltest", + 'finetest': "finetest", + # 'fine': "finetest", +} + +for _name, _meta in project_metas.items(): + _meta['project_name'] = _name + def write0(*args): sys.stderr.write(*args) sys.stderr.flush() + def echo0(*args): print(*args, file=sys.stderr) @@ -138,18 +137,24 @@ def usage(): def main(): + prefix = "[main] " required_bin_suffixes = None - # project_info = detect_project_info(INSTALL_SRC) + why_meta = "detected" + project_meta = detect_project_meta(INSTALL_SRC) + if project_meta is None: + why_meta = "undetected" key_arg = None install_from = None + project_name = None if len(sys.argv) < 2: usage() - # if project_info is None: - echo0("Error: You must specify one of the names above.") - return 1 - # TODO: maybe detect it: - # else: - # echo0("using detected project: {}".format(project_info)) + if project_meta is None: + echo0("Error: You must specify one of the names above" + " unless well-known executable files can be detected" + " to determine what project is being installed.") + return 1 + else: + echo0("using detected project: {}".format(project_meta)) elif len(sys.argv) == 2: pass # 1st arg (arg [1]) is always handled further down else: @@ -188,15 +193,76 @@ def main(): echo0("Error: {} must be followed by a value." "".format(key_arg)) return 1 - if required_bin_suffixes is None: - required_bin_suffixes = [""] # Always require at least the plain exe name. + + if len(sys.argv) > 1: + name_arg = sys.argv[1] + project_name = arg_project_name.get(name_arg) + if project_name is None: + raise ValueError( + "Got %s but expected one from %s" + % ( + pformat(name_arg), + pformat(list(arg_project_name.keys())) + ) + ) + if project_meta is not None: + echo0(prefix+"reverting detected meta due to %s argument." + % pformat(name_arg)) + project_meta = None + why_meta = "cleared by %s argument" % name_arg + elif project_meta is not None: + project_name = project_meta.get('project_name') + # ^ May differ from name. For example, project name for + # Final Minetest is "classic". + echo0(prefix+"detected %s" % project_name) + if install_from is None: install_from = INSTALL_SRC - project_name = sys.argv[1] + + if project_meta is None: + if project_name is None: + raise ValueError( + "You must either specify one of %" + " or the source must be a well-known project that can be" + " detected." % pformat(list(project_metas.keys())) + ) + project_meta = project_metas[project_name] + project_meta['required_relpaths'] = [] + if required_bin_suffixes is None: + required_bin_suffixes = [""] # only check for * not *server + # when no options were specified. + echo0("Warning: No --client or --server option was set, and" + " source was %s so only client binary will be verified" + " to exist." + % why_meta) + for relpath in project_meta['shortcut_exe_relpaths']: + for suffix in required_bin_suffixes: + # for each file such as suffix "" for minetest and + # suffix "server" for minetestserver, add to required + # files if specified (Instead of if exists, which + # only is behavior on detect, though in both cases + # they are verified to exist before install, later). + try_relpath = relpath + suffix + project_meta['required_relpaths'].append(try_relpath) + echo0("Generated relpaths: %s" % pformat(project_meta['required_relpaths'])) + else: + if project_meta.get('required_relpaths') is None: + raise NotImplementedError( + "Project %s was detected but required_relpaths was not set." + % pformat(project_meta.get('project_name')) + ) + if len(project_meta['required_relpaths']) == 0: + raise FileNotFoundError( + "None of the well-known executables for %s could be found: %s" + % ( + project_name, + pformat(project_meta.get('shortcut_exe_relpaths')) + ) + ) + results = install_minetest( - project_name, install_from, - required_bin_suffixes=required_bin_suffixes, + project_meta, ) error = results.get('error') if error is not None: @@ -204,20 +270,19 @@ def main(): return 0 -def install_minetest(project_name, src, dst=None, variant_dirname=None, - required_bin_suffixes=[""], variant=VARIANT): +def install_minetest(src, project_meta, dst=None, variant_dirname=None, + variant=VARIANT): """Install Minetest - Requires globals: + Args: project_meta (dict[string]): The information necessary to install the program. It must have the keys: - 'dirname' (string): The directory under the OS program files. + - 'required_files' (list): Paths relative to + src that are required (for ensuring src is intact). - There are more required keys for shortcut generation (See install_shortcut). - - Args: - project_name (string): Must be minetest, finetest, or trolltest. src (string): The location of the minetest install source to copy. dst (Optional[string]): Install here. If None, it will become @@ -229,44 +294,45 @@ def install_minetest(project_name, src, dst=None, variant_dirname=None, minetest-rsync). If VARIANT is blank or None, the variant_dirname will become the same as the dirname (such as minetest). - required_bin_suffixes (list): Install client if [""], or server if - ["server"]. Include both to require that both are in - os.path.join(src, "bin"). variant (string): Append this to the dirname. It also affects the shortcut--see "variant" under install_shortcut. + On desktops environments following the XDG standard, + also appended to the icon filename so the variant's can + co-exist with other variants (such as deb and AppImage and + so on). Returns: dict: "destination" is where it was installed if at all. See "warning" in case there was something incorrect about the install. """ - arg = project_name - project_name = arg_to_project_name(arg) - if project_name is None: - usage() - return { - 'error': "{} is not a valid project name.".format(arg), - } - src_files = expected_src_files(src, project_name, required_bin_suffixes) + project_name = project_meta.get('name') + project_msg = project_name + if project_msg is None: + project_msg = pformat(project_meta) + del project_name + + src_files = project_meta.get('required_relpaths') if src_files is None: usage() - error = "There are no source files for {}".format(project_name) + error = ("There are no specified source files for %s" + " so whether it is intact can't be checked." + "" % pformat(project_msg)) raise NotImplementedError(error) missing_files = [] for src_file in src_files: - if not os.path.isfile(src_file): + if not os.path.isfile(os.path.join(src, src_file)): missing_files.append(src_file) if len(missing_files) > 0: error = ("Error: The following files are required to be compiled" - " {} before install but are not present: {}" - "".format(project_name, missing_files)) + " for {} before install but are missing: {}" + "".format(project_msg, missing_files)) return { 'error': error, } - project_meta = project_metas[project_name] dirname = project_meta['dirname'] variant_dirname = dirname if (variant is not None) and (len(variant.strip()) > 0): @@ -274,7 +340,6 @@ def install_minetest(project_name, src, dst=None, variant_dirname=None, else: variant = None - if dst is None: if platform.system() == "Windows": GAMES = "C:\\games" @@ -285,9 +350,9 @@ def install_minetest(project_name, src, dst=None, variant_dirname=None, dst = os.path.join(HOME, variant_dirname) warning = None - if not os.path.isdir(dst): - write0('Installing {} to "{}"...'.format(project_name, dst)) + write0('Installing %s to %s...' + % (pformat(project_msg), pformat(dst))) shutil.copytree(src, dst) echo0("Done") result_path = dst @@ -295,6 +360,7 @@ def install_minetest(project_name, src, dst=None, variant_dirname=None, # Leave result_path as None warning = 'Skipping installed "{}".'.format(dst) echo0('WARNING: {}'.format(warning)) + for Exec_relpath in project_meta['shortcut_exe_relpaths']: Exec = os.path.join(dst, Exec_relpath) sc_results = install_shortcut(Exec, dst, project_meta, variant) @@ -433,68 +499,65 @@ def install_shortcut(Exec, dst, project_meta, variant): # """ -def arg_to_project_name(arg): - if arg == "final": - return "minetest" - elif arg == "classic": - return "minetest" - elif arg == "minetest": - return "minetest" - elif arg == "trolltest": - return "trolltest" - elif arg == "troll": - return "trolltest" - elif arg == "finetest": - return "finetest" - elif arg == "fine": - return "finetest" - return None - - -def detect_project_info(mt_share_path): - """Detect the project name from a source *or* destination. - """ - for sub, project_info in mode_of_path.items(): - sub_path = os.path.join(mt_share_path, sub) - if os.path.isfile(sub_path): - return project_info - return None - +def detect_project_meta(mt_share_path): + """Detect the project info from a source *or* destination. -def expected_src_files(src, project_name, required_bin_suffixes=[""]): - """Get full paths of required files. + Only first entry will be used & get "server" added. Args: - required_bin_suffixes (list): Usually either [""] or ["server"], but if - the list contains both, both will be required + mt_share_path (string): The path containing + project_meta['shortcut_exe_relpaths'] + filename(s). Returns: - list: Files that are required for install to continue. + A copy of the matching project_meta (from project_metas) with + an added entry 'required_relpaths'. + - *detecting errors*: If the list length is 0 or the key is not + present, no required files were found and the install source + is not understandable (There is no known binary such as for + different code to make a shortcut). + - If relpath+"server" exists in the case of the first entry in + 'shortcut_exe_relpaths', that server binary will be added to + the 'required_relpaths' list whether or not the filename + without "server" in the name exists. """ - if ((required_bin_suffixes is None) or (len(required_bin_suffixes) < 1) - or (not isinstance(required_bin_suffixes, list))): - required_bin_suffixes = [""] - echo0('Warning: reverting to required_bin_suffixes=[""] since {}' - ''.format(required_bin_suffixes)) - - src_files = None - - for appender in required_bin_suffixes: - src_file = None - if project_name == "minetest": - src_file = os.path.join(src, "bin", "minetest"+appender) - elif project_name == "trolltest": - src_file = os.path.join(src, "bin", "minetest"+appender) - elif project_name == "finetest": - src_file = os.path.join(src, "bin", "finetest"+appender) - else: - break - if src_file is not None: - if src_files is None: - src_files = [] - src_files.append(src_file) - # else caller must say project_name is not valid. - return src_files + prefix = "[detect_project_meta] " + matches = [] + for mode, meta in project_metas.items(): + new_meta = copy.deepcopy(meta) + if 'required_relpaths' not in new_meta: + new_meta['required_relpaths'] = \ + meta['shortcut_exe_relpaths'].copy() + for sub in meta.get('shortcut_exe_relpaths'): + sub_path = os.path.join(mt_share_path, sub) + try_extra_rel = sub+"server" + try_extra_exe = os.path.join(mt_share_path, try_extra_rel) + found_any = False + if os.path.isfile(try_extra_exe): + found_any = True + if try_extra_rel not in new_meta['required_relpaths']: + new_meta['required_relpaths'].append(try_extra_exe) + # For example, bin/minetestserver is required + # if in --server install mode (and this + # function detects that mode) + # but there is no shortcut to it in the GUI. + else: + echo0(prefix+"There is no %s" % try_extra_exe) + if os.path.isfile(sub_path): + found_any = True + else: + echo0(prefix+"There is no %s" % sub_path) + new_meta['required_relpaths'].remove(sub) + # For example, remove "minetest" if not present (but + # install can still proceed if "minetestserver" was + # added to the required list). + if found_any: + matches.append(new_meta) + break # only first entry will be used & get "server" added + if len(matches) == 1: + echo0(prefix+"found source files: %s" % pformat(matches[0]['required_relpaths'])) + return matches[0] + return None def rewrite_conf(src, dst, changes={}):