poikilos
3 months ago
1 changed files with 35 additions and 772 deletions
@ -1,780 +1,43 @@ |
|||||
#!/usr/bin/env python |
#!/usr/bin/env python |
||||
""" |
# -*- coding: utf-8 -*- |
||||
Usage: |
# #!/usr/bin/python3 |
||||
install-lmk <project name> [--from <built dir>] [options] |
# based on EnlivenMinetest/utilities/install-lmk |
||||
|
# |
||||
The available project names are: |
# The copy in hierosoft is relicensed by Poikilos (original author) |
||||
classic (or final or minetest) to install ~/minetest-rsync, |
# under license of hierosoft |
||||
finetest (or fine) to install ~/finetest-rsync |
''' |
||||
(for the game that ripped off Multicraft.org's name), or |
install-lmk |
||||
trolltest (or troll) to install ~/trolltest-rsync (based on MT5). |
----------- |
||||
|
Use any "minetest" folder under the current working directory |
||||
If the current directory is not ~/minetest-rsync, the suffix "local" |
to install or upgrade. |
||||
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 |
Developers: If /opt/minebest/mtkit is present, that will be used. |
||||
rsync access to the build server). |
|
||||
|
See hierosoft.hminetestsrc documentation for more info. |
||||
Options: |
''' |
||||
--from <built dir> Install from this directory. Defaults to |
import re |
||||
"/opt/minebest/mtkit/minetest" (from home not opt |
|
||||
if using Windows). |
|
||||
--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 sys |
||||
import tempfile |
import os |
||||
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("/opt", "minebest-rsync", "mtkit", "minetest") |
|
||||
if platform.system() == "Windows": |
|
||||
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 default INSTALL_SRC |
|
||||
# - "local" copy of linux-minetest-kit.zip is for end users |
|
||||
# - "rsync" copy from /opt/minebest/ is for maintainers |
|
||||
# (linux-minetest-kit.zip is built by /assemble/util/buildskipwin.sh |
|
||||
# and then must be manually extracted to /opt/minebest/mtkit) |
|
||||
|
|
||||
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: |
scripts_dir = os.path.dirname(os.path.realpath(__file__)) |
||||
A copy of the matching project_meta (from project_metas) with |
repo_dir = os.path.dirname(scripts_dir) |
||||
an added entry 'required_relpaths'. |
repos_dir = os.path.dirname(repo_dir) |
||||
- *detecting errors*: If the list length is 0 or the key is not |
try_other_repo = os.path.join(repos_dir, "hierosoft") |
||||
present, no required files were found and the install source |
good_h_flag = os.path.join(try_other_repo, "hierosoft", "__init__.py") |
||||
is not understandable (There is no known binary such as for |
if os.path.isfile(good_h_flag): |
||||
different code to make a shortcut). |
sys.path.insert(0, try_other_repo) |
||||
- If relpath+"server" exists in the case of the first entry in |
print("Using {}".format(try_other_repo), file=sys.stderr) |
||||
'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: |
else: |
||||
echo0(prefix+"There is no %s" % sub_path) |
print("No {}. Trying installed copy...".format(good_h_flag), |
||||
new_meta['required_relpaths'].remove(sub) |
file=sys.stderr) |
||||
# 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: |
# if os.path.isfile(os.path.join(repos_dir, "hierosoft", "init.py")): |
||||
# Python 2 (strings are bytes) |
# sys.path.insert(repos_dir) |
||||
def rewrite_conf(src, dst, changes={}): |
# ^ implies both are installed (and that repos_dir is actually modules_dir), |
||||
"""Install a conf such as an XDG desktop shortcut with changes. |
# so leave path alone. |
||||
""" |
|
||||
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) |
|
||||
|
|
||||
|
from hierosoft.hminetestsrc import main # noqa E402 |
||||
|
|
||||
if __name__ == "__main__": |
if __name__ == '__main__': |
||||
|
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) |
||||
sys.exit(main()) |
sys.exit(main()) |
||||
|
Loading…
Reference in new issue