You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
774 lines
30 KiB
774 lines
30 KiB
#!/usr/bin/env python
|
|
"""
|
|
Usage:
|
|
install-lmk <project name> [--from <built dir>] [options]
|
|
|
|
The available project names are:
|
|
classic (or final or minetest) to install ~/minetest-rsync,
|
|
finetest (or fine) to install ~/finetest-rsync
|
|
(for the game that ripped off Multicraft.org's name), or
|
|
trolltest (or troll) to install ~/trolltest-rsync (based on MT5).
|
|
|
|
If the current directory is not ~/minetest-rsync, the suffix "local"
|
|
will be used for installed directories and shortcuts instead of rsync to
|
|
indicate you are using a downloaded copy (not using a copy obtained via
|
|
rsync access to the build server).
|
|
|
|
Options:
|
|
--from <built dir> Install from this directory. Defaults to
|
|
"$HOME/minebest-rsync/mtkit/minetest".
|
|
--server Require a binary ending with "server" to be
|
|
present in built dir. Defaults to False.
|
|
--client Require a binary ending with "server" to be
|
|
present in built dir. Defaults to True, but False
|
|
if --server is used without --client.
|
|
"""
|
|
from __future__ import print_function
|
|
import copy
|
|
import json
|
|
import os
|
|
import platform
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
import tempfile
|
|
from pprint import pformat
|
|
|
|
if platform.system() == "Windows":
|
|
HOME = os.environ['USERPROFILE']
|
|
SHORTCUTS_DIR = os.path.join(HOME, "Desktop")
|
|
elif platform.system() == "Darwin":
|
|
HOME = os.environ['HOME']
|
|
SHORTCUTS_DIR = os.path.join(HOME, "Desktop")
|
|
else:
|
|
HOME = os.environ['HOME']
|
|
SHORTCUTS_DIR = os.path.join(HOME, ".local", "share", "applications")
|
|
|
|
if sys.version_info.major < 3:
|
|
FileNotFoundError = IOError
|
|
ModuleNotFoundError = ImportError
|
|
|
|
|
|
INSTALL_SRC = os.path.join(HOME, "minebest-rsync", "mtkit", "minetest")
|
|
# ^ Changed later if detected in current dir (in use_if_source).
|
|
DETECT_KIT_SUBDIRS = ["minetest", "mtsrc"] # Use via detect_source
|
|
# ^ First entry of DETECT_KIT_SUBDIRS has to be the new INSTALL_SRC!
|
|
|
|
VARIANT = "rsync" # Changed to "local" if not in universal directory!
|
|
|
|
MINETEST_KEYWORDS = ("sandbox;world;mining;crafting;blocks;nodes;multiplayer;"
|
|
"roleplaying;")
|
|
|
|
project_metas = {
|
|
'classic': { # minetest is the project name (in mtsrc/newline dir)
|
|
'shortcut': {
|
|
'GenericName': "Final Minetest",
|
|
'Keywords': MINETEST_KEYWORDS,
|
|
},
|
|
'dirname': "minetest",
|
|
'name_and_variant_fmt': "Final Minetest ({})",
|
|
'name': "Final Minetest",
|
|
'shortcut_exe_relpaths': [
|
|
os.path.join("bin", "minetest"),
|
|
],
|
|
'platform_icon_relpath': {
|
|
'Linux': os.path.join("misc", "minetest.svg"),
|
|
'Darwin': os.path.join("misc", "minetest-icon.icns"),
|
|
'Windows': os.path.join("misc", "minetest-icon-24x24.png"),
|
|
},
|
|
'shortcut_relpath': os.path.join("misc", "net.minetest.minetest.desktop"),
|
|
'shortcut_name_noext': "org.minetest.minetest",
|
|
},
|
|
'finetest': {
|
|
'shortcut': {
|
|
'GenericName': "Finetest",
|
|
'Keywords': MINETEST_KEYWORDS+"minetest;",
|
|
},
|
|
'dirname': "finetest",
|
|
'name_and_variant_fmt': "Finetest ({})",
|
|
'name': "Finetest",
|
|
'shortcut_exe_relpaths': [
|
|
# os.path.join("bin", "multicraft"),
|
|
os.path.join("bin", "finetest"),
|
|
],
|
|
'platform_icon_relpath': {
|
|
'Linux': os.path.join("misc", "multicraft-xorg-icon-128.png"),
|
|
'Darwin': os.path.join("misc", "minetest-icon.icns"),
|
|
'Windows': os.path.join("misc", "multicraft-xorg-icon-128.png"),
|
|
},
|
|
'shortcut_relpath': os.path.join("misc", "net.minetest.minetest.desktop"),
|
|
'shortcut_name_noext': "org.minetest.finetest",
|
|
},
|
|
'trolltest': {
|
|
'shortcut': {
|
|
'GenericName': "Trolltest",
|
|
'Keywords': MINETEST_KEYWORDS+"minetest;",
|
|
},
|
|
'dirname': "trolltest",
|
|
'name_and_variant_fmt': "Trolltest ({}) (minetest.org build)",
|
|
'name': "Trolltest (minetest.org)",
|
|
'shortcut_exe_relpaths': [
|
|
os.path.join("bin", "trolltest"),
|
|
],
|
|
'platform_icon_relpath': {
|
|
'Linux': os.path.join("misc", "minetest.svg"),
|
|
'Darwin': os.path.join("misc", "minetest-icon.icns"),
|
|
'Windows': os.path.join("misc", "minetest-icon-24x24.png"),
|
|
},
|
|
'shortcut_relpath': os.path.join("misc", "net.minetest.minetest.desktop"),
|
|
'shortcut_name_noext': "org.minetest.trolltest",
|
|
},
|
|
}
|
|
|
|
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)
|
|
|
|
|
|
def usage():
|
|
echo0(__doc__)
|
|
|
|
|
|
|
|
def detect_source(path):
|
|
"""Get a built minetest directory inside of path if present.
|
|
It must contain all DETECT_KIT_SUBDIRS for the subdirectory to be
|
|
detected.
|
|
|
|
Returns:
|
|
str: minetest subdirectory. If path does not have
|
|
all DETECT_KIT_SUBDIRS, result is None.
|
|
"""
|
|
for sub in DETECT_KIT_SUBDIRS:
|
|
sub_path = os.path.join(path, sub)
|
|
if not os.path.isdir(sub_path):
|
|
return None
|
|
return os.path.join(path, DETECT_KIT_SUBDIRS[0])
|
|
|
|
|
|
def use_if_source(path):
|
|
"""Use the path as INSTALL_SRC if it contains a minetest install.
|
|
See detect_source for details. A message is shown regarding the
|
|
status.
|
|
|
|
Affects globals:
|
|
- INSTALL_SRC
|
|
- VARIANT
|
|
|
|
Returns:
|
|
bool: True if is a source (even if INSTALL_SRC is already the
|
|
same).
|
|
"""
|
|
global INSTALL_SRC
|
|
global VARIANT
|
|
detected_src = detect_source(path)
|
|
if detected_src:
|
|
if detected_src != INSTALL_SRC:
|
|
echo0('Switching from "{}" to local copy:'
|
|
'\n "{}"'
|
|
''.format(INSTALL_SRC, detected_src))
|
|
INSTALL_SRC = detected_src
|
|
VARIANT = "local"
|
|
else:
|
|
echo0('Using standard source location (same as current dir):'
|
|
'\n "{}"'
|
|
''.format(INSTALL_SRC))
|
|
return True
|
|
else:
|
|
echo0('Using standard source location'
|
|
' (since current dir does not have both "mtsrc and "minetest"):'
|
|
'\n "{}"'
|
|
''.format(INSTALL_SRC))
|
|
return False
|
|
|
|
|
|
def main():
|
|
prefix = "[main] "
|
|
use_if_source(os.getcwd())
|
|
required_bin_suffixes = None
|
|
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_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(
|
|
json.dumps(project_meta, indent=2),
|
|
))
|
|
# NOTE: ^ shows name_and_variant_fmt with literal "{}" still
|
|
# (unavoidable without messing with it), so see
|
|
# "Name={}" further down for that output (Only possible
|
|
# after `variant` is set).
|
|
elif len(sys.argv) == 2:
|
|
pass # 1st arg (arg [1]) is always handled further down
|
|
else:
|
|
for argi in range(2, len(sys.argv)):
|
|
arg = sys.argv[argi]
|
|
if key_arg is not None:
|
|
if arg.startswith("--"):
|
|
usage()
|
|
echo0("Error: {} must be followed by a value but got {}."
|
|
"".format(key_arg, arg))
|
|
return 1
|
|
if key_arg == "--from":
|
|
install_from = arg
|
|
else:
|
|
usage()
|
|
echo0("Error: unknown argument {}".format(key_arg))
|
|
return 1
|
|
elif arg == "--server":
|
|
if required_bin_suffixes is None:
|
|
required_bin_suffixes = ["server"]
|
|
else:
|
|
required_bin_suffixes.append("server")
|
|
elif arg == "--client":
|
|
if required_bin_suffixes is None:
|
|
required_bin_suffixes = [""]
|
|
else:
|
|
required_bin_suffixes.append("")
|
|
elif arg == "--from":
|
|
key_arg = arg
|
|
else:
|
|
usage()
|
|
echo0('Error: The 2nd argument must be "server" or left out')
|
|
return 1
|
|
if key_arg is not None:
|
|
usage()
|
|
echo0("Error: {} must be followed by a value."
|
|
"".format(key_arg))
|
|
return 1
|
|
|
|
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
|
|
|
|
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(
|
|
install_from,
|
|
project_meta,
|
|
)
|
|
error = results.get('error')
|
|
if error is not None:
|
|
echo0("Error: %s" % error)
|
|
return 0
|
|
|
|
|
|
def install_minetest(src, project_meta, dst=None, variant_dirname=None,
|
|
variant=None):
|
|
"""Install Minetest
|
|
|
|
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).
|
|
src (string): The location of the minetest install source to
|
|
copy.
|
|
dst (Optional[string]): Install here. If None, it will become
|
|
the default. Defaults to variant_dirname under C:\games on
|
|
Windows, otherwise under HOME.
|
|
variant_dirname (Optional[string]): Set the install directory
|
|
name (ignored if dst is set). If None, it will become the
|
|
default. Defaults to project_name + "-" + VARIANT (such as
|
|
minetest-rsync). If VARIANT is blank or None, the
|
|
variant_dirname will become the same as the dirname
|
|
(such as minetest).
|
|
variant (str): 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). Defaults to VARIANT (which is set automatically to
|
|
"rsync" or "local" elsewhere).
|
|
|
|
Returns:
|
|
dict: "destination" is where it was installed if at all. See
|
|
"warning" in case there was something incorrect about the
|
|
install.
|
|
"""
|
|
if variant is None:
|
|
variant = VARIANT
|
|
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 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(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"
|
|
" for {} before install but are missing: {}"
|
|
"".format(project_msg, missing_files))
|
|
return {
|
|
'error': error,
|
|
}
|
|
|
|
dirname = project_meta['dirname']
|
|
variant_dirname = dirname
|
|
if (variant is not None) and (len(variant.strip()) > 0):
|
|
variant_dirname += "-" + variant
|
|
else:
|
|
variant = None
|
|
|
|
if dst is None:
|
|
if platform.system() == "Windows":
|
|
GAMES = "C:\\games"
|
|
if not os.path.isdir(GAMES):
|
|
os.mkdir(GAMES)
|
|
dst = os.path.join(GAMES, variant_dirname)
|
|
else:
|
|
dst = os.path.join(HOME, variant_dirname)
|
|
warning = None
|
|
|
|
if not os.path.isdir(dst):
|
|
write0('Installing %s to %s...'
|
|
% (pformat(project_msg), pformat(dst)))
|
|
shutil.copytree(src, dst)
|
|
version_path = project_meta.get('version_path')
|
|
if version_path and os.path.isfile(version_path):
|
|
version_name = os.path.basename(version_path)
|
|
shutil.copy(
|
|
version_path,
|
|
os.path.join(dst, version_name),
|
|
)
|
|
echo0("Done")
|
|
result_path = dst
|
|
else:
|
|
# 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)
|
|
sc_warning = sc_results.get('warning')
|
|
if sc_warning is not None:
|
|
if warning is not None:
|
|
warning += "; " + sc_warning
|
|
else:
|
|
warning = sc_warning
|
|
return {
|
|
'dst': dst,
|
|
'warning': warning,
|
|
}
|
|
|
|
|
|
def generate_caption(project_meta, variant):
|
|
"""Generate the icon caption.
|
|
|
|
Args:
|
|
project_meta (dict): The dict containing 'name' and
|
|
'name_and_variant_fmt' where 'name' is like
|
|
"Trolltest (minetest.org)", and 'name_and_variant_fmt' is
|
|
like 'Trolltest ({}) (minetest.org build)'.
|
|
"""
|
|
Name = project_meta['name']
|
|
if variant is not None:
|
|
name_and_variant_fmt = project_meta.get('name_and_variant_fmt')
|
|
if name_and_variant_fmt is not None:
|
|
Name = name_and_variant_fmt.format(variant)
|
|
else:
|
|
Name += " (" + project_meta['variant'] + ")" # raise if None
|
|
return Name
|
|
|
|
|
|
def install_shortcut(Exec, dst, project_meta, variant):
|
|
"""Install a shortcut to any program on any understood platform.
|
|
|
|
- sc_template_path is determined based on dst and shortcut_relpath
|
|
- sc_installed_path (path) is determined from OS and shortcut_name_noext
|
|
(and variant if not None).
|
|
- sc_template_path is read, Exec string is filled based on dst
|
|
(the selected destination where the program is installed)
|
|
then the resulting shortcut is saved to sc_installed_path
|
|
(only after temp file is complete).
|
|
|
|
Args:
|
|
Exec (string): The executable path where the shortcut
|
|
should point.
|
|
dst (string): The directory path where the program is
|
|
installed.
|
|
project_meta (dict): All metadata describing the program.
|
|
For this method, it must have the keys:
|
|
- 'name': The entire name (except variant) that
|
|
should be displayed as the shortcut's caption.
|
|
- 'name_and_variant_fmt': Should either be not
|
|
present or contain the name and the placeholder
|
|
"{}" where the variant should go. If not present,
|
|
" " and variant will be added to the end of Name.
|
|
- 'shortcut' (dict): contains:
|
|
- 'GenericName' (Optional[string]): A simplified
|
|
name for the program. If None, the GenericName
|
|
line will be removed from the shortcut. This
|
|
option is only for GNU/Linux systems or other
|
|
systems using XDG.
|
|
- 'Keywords' (Optional[string]): If None, Keywords
|
|
line will be removed from the shortcut. This
|
|
option is only for GNU/Linux systems or other
|
|
systems using XDG.
|
|
- 'shortcut_relpath': The location of an existing
|
|
shortcut file to use and modify.
|
|
- 'platform_icon_relpath' (dict[string]): A dict
|
|
where the key is platform.system() (Must have
|
|
at least 'Linux', 'Windows', *AND* 'Darwin')
|
|
and the value is the relative path from
|
|
dst to the icon image file.
|
|
variant (string): The special string to put in parenthesis
|
|
after the name to denote what kind of package or source was
|
|
used to obtain the program, such as "rsync" if a local
|
|
custom build, or more commonly "git", "deb", etc. If it is
|
|
an official binary archive, set this to "release". However,
|
|
if the package type (such as deb) is native to your distro,
|
|
set this to None to indicate it is the package supported
|
|
for your distro.
|
|
- Name is constructed using
|
|
project_meta['name_and_variant_fmt'] if present, otherwise
|
|
Name will be project_meta['name] + " (" + 'variant' + ")".
|
|
If variant is None, name is project_meta['name'].
|
|
Raises:
|
|
FileNotFoundError: If src does not exist.
|
|
"""
|
|
warning = None
|
|
Name = generate_caption(project_meta, variant)
|
|
echo0("Name={}".format(Name))
|
|
platform_icon_relpath = project_meta.get('platform_icon_relpath')
|
|
icon_relpath = None
|
|
if platform_icon_relpath is not None:
|
|
icon_relpath = platform_icon_relpath.get(platform.system())
|
|
if icon_relpath is None:
|
|
raise NotImplementedError(
|
|
"There is no platform icon for {}.".format(platform.system())
|
|
)
|
|
Icon = os.path.join(dst, icon_relpath)
|
|
shortcut_meta = copy.deepcopy(project_meta.get('shortcut'))
|
|
shortcut_meta['Name'] = Name
|
|
shortcut_meta['Exec'] = Exec
|
|
shortcut_meta['Icon'] = Icon
|
|
# ^ rewrite_conf *removes* any lines where value is None
|
|
|
|
if platform.system() == "Linux":
|
|
sc_template_path = os.path.join(dst, project_meta['shortcut_relpath'])
|
|
shortcut_name = "{}.{}.desktop".format(
|
|
project_meta['shortcut_name_noext'],
|
|
variant,
|
|
)
|
|
sc_installed_path = os.path.join(SHORTCUTS_DIR, shortcut_name)
|
|
if not os.path.isdir(SHORTCUTS_DIR):
|
|
os.makedirs(SHORTCUTS_DIR) # default mode is 511
|
|
write0('Installing icon to "{}"...'.format(sc_installed_path))
|
|
rewrite_conf(
|
|
sc_template_path,
|
|
sc_installed_path,
|
|
changes=shortcut_meta,
|
|
)
|
|
echo0("OK")
|
|
elif platform.system() == "Darwin":
|
|
shortcut_name = Name + ".command"
|
|
sc_installed_path = os.path.join(SHORTCUTS_DIR, shortcut_name)
|
|
with open(sc_installed_path) as stream:
|
|
stream.write('"%s"\n' % Exec)
|
|
# ^ Run the game & close Command Prompt immediately.
|
|
# ^ First arg is Command Prompt title, so leave it blank.
|
|
st = os.stat(sc_installed_path)
|
|
os.chmod(sc_installed_path, st.st_mode | stat.S_IXUSR)
|
|
# ^ same as stat.S_IEXEC: "Unix V7 synonym for S_IXUSR."
|
|
elif platform.system() == "Windows":
|
|
shortcut_name = Name + ".bat"
|
|
sc_installed_path = os.path.join(SHORTCUTS_DIR, shortcut_name)
|
|
with open(sc_installed_path) as stream:
|
|
stream.write('start "" "%s"\n' % Exec)
|
|
# ^ Run the game & close Command Prompt immediately.
|
|
# ^ First arg is Command Prompt title, so leave it blank.
|
|
else:
|
|
warning = ("Icon install isn't implemented for {}."
|
|
"".format(platform.system()))
|
|
return {
|
|
"warning": warning, # may be None
|
|
"destination": dst,
|
|
}
|
|
|
|
|
|
# def get_missing_subs(mt_share_path, subs):
|
|
# """Get a list of any missing files for a source *or* destination.
|
|
# """
|
|
|
|
|
|
def detect_project_meta(mt_share_path):
|
|
"""Detect the project info from a source *or* destination.
|
|
|
|
Only first entry will be used & get "server" added.
|
|
|
|
Args:
|
|
mt_share_path (string): The path containing
|
|
project_meta['shortcut_exe_relpaths']
|
|
filename(s).
|
|
|
|
Returns:
|
|
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.
|
|
"""
|
|
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).
|
|
mt_share_path = os.path.realpath(mt_share_path)
|
|
version_paths = [
|
|
os.path.join(mt_share_path, "release.txt"),
|
|
os.path.join(os.path.dirname(mt_share_path), "release.txt"),
|
|
]
|
|
for version_path in version_paths:
|
|
if os.path.isfile(version_path):
|
|
new_meta['version_path'] = version_path
|
|
version = None
|
|
with open(version_path, 'r') as stream:
|
|
for rawL in stream:
|
|
if version is None:
|
|
version = rawL.strip()
|
|
# ^ Use `for` to avoid Exception on empty file.
|
|
if version is None:
|
|
echo0('Warning: "{}" is empty!'.format(version_path))
|
|
continue
|
|
elif not version:
|
|
echo0('Warning: "{}" had a blank line not version'
|
|
''.format(version_path))
|
|
version = None
|
|
continue
|
|
if version:
|
|
new_meta['version'] = version
|
|
break
|
|
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={}):
|
|
"""Install a conf such as an XDG desktop shortcut with changes.
|
|
|
|
Args:
|
|
src (string): The conf file to read.
|
|
dst (string): The conf file to write or overwrite.
|
|
changes (dict): A set of values to change by name. For any value
|
|
that is None, the line will be removed!
|
|
"""
|
|
# This function is redefined further down in the case of Python 2.
|
|
fd, path = tempfile.mkstemp()
|
|
try:
|
|
with os.fdopen(fd, 'wb') as tmp:
|
|
# ^ ensure still exists when moving
|
|
write0("Generating temporary icon %s..." % path)
|
|
# NOTE: tmp.name is just some number (int)!
|
|
with open(src, "rb") as stream:
|
|
for rawL in stream:
|
|
signI = rawL.find(b'=')
|
|
# commentI = rawl.find(b'#')
|
|
if rawL.strip().startswith(b"#"):
|
|
tmp.write(rawL)
|
|
continue
|
|
if rawL.strip().startswith(b"["):
|
|
tmp.write(rawL)
|
|
continue
|
|
if signI < 0:
|
|
tmp.write(rawL)
|
|
continue
|
|
key_bytes = rawL[:signI].strip()
|
|
key = key_bytes.decode("utf-8")
|
|
value = changes.get(key)
|
|
if key not in changes:
|
|
# The value wasn't changed so write it as-is
|
|
# echo0("%s not in %s" % (key, changes))
|
|
tmp.write(rawL)
|
|
continue
|
|
if value is None:
|
|
echo0("%s was excluded from the icon" % key)
|
|
continue
|
|
line = "%s=%s\n" % (key, value)
|
|
tmp.write(line.encode("utf-8"))
|
|
shutil.copy(path, dst)
|
|
finally:
|
|
write0("removing tmp file...")
|
|
os.remove(path)
|
|
|
|
|
|
if sys.version_info.major < 3:
|
|
# Python 2 (strings are bytes)
|
|
def rewrite_conf(src, dst, changes={}):
|
|
"""Install a conf such as an XDG desktop shortcut with changes.
|
|
"""
|
|
fd, path = tempfile.mkstemp()
|
|
try:
|
|
with os.fdopen(fd, 'wb') as tmp:
|
|
write0("Generating temporary icon %s..." % path)
|
|
with open(src, "rb") as stream:
|
|
for rawL in stream:
|
|
signI = rawL.find('=')
|
|
# commentI = rawl.find('#')
|
|
if rawL.strip().startswith("#"):
|
|
tmp.write(rawL)
|
|
continue
|
|
if rawL.strip().startswith("["):
|
|
tmp.write(rawL)
|
|
continue
|
|
if signI < 0:
|
|
tmp.write(rawL)
|
|
continue
|
|
key_bytes = rawL[:signI].strip()
|
|
key = key_bytes
|
|
value = changes.get(key)
|
|
if key not in changes:
|
|
# The value wasn't changed so write it as-is
|
|
tmp.write(rawL)
|
|
continue
|
|
if value is None:
|
|
echo0("%s was excluded from the icon" % key)
|
|
continue
|
|
line = "%s=%s\n" % (key, value)
|
|
tmp.write(line)
|
|
shutil.copy(path, dst)
|
|
finally:
|
|
write0("removing tmp file...")
|
|
os.remove(path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|
|
|