Compare commits
6 Commits
f6974d9a65
...
142e6f097a
Author | SHA1 | Date |
---|---|---|
poikilos | 142e6f097a | 3 months ago |
poikilos | f82ddcb649 | 3 months ago |
poikilos | 5b1691ca07 | 3 months ago |
poikilos | 16fe0946ca | 3 months ago |
poikilos | a7bbeb09de | 3 months ago |
poikilos | ea23a01056 | 3 months ago |
6 changed files with 175 additions and 816 deletions
@ -1,780 +1,43 @@ |
|||
#!/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 |
|||
"/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 |
|||
# -*- coding: utf-8 -*- |
|||
# #!/usr/bin/python3 |
|||
# based on EnlivenMinetest/utilities/install-lmk |
|||
# |
|||
# The copy in hierosoft is relicensed by Poikilos (original author) |
|||
# under license of hierosoft |
|||
''' |
|||
install-lmk |
|||
----------- |
|||
Use any "minetest" folder under the current working directory |
|||
to install or upgrade. |
|||
|
|||
Developers: If /opt/minebest/mtkit is present, that will be used. |
|||
|
|||
See hierosoft.hminetestsrc documentation for more info. |
|||
''' |
|||
import re |
|||
import sys |
|||
import tempfile |
|||
from pprint import pformat |
|||
import os |
|||
|
|||
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") |
|||
scripts_dir = os.path.dirname(os.path.realpath(__file__)) |
|||
repo_dir = os.path.dirname(scripts_dir) |
|||
repos_dir = os.path.dirname(repo_dir) |
|||
try_other_repo = os.path.join(repos_dir, "hierosoft") |
|||
good_h_flag = os.path.join(try_other_repo, "hierosoft", "__init__.py") |
|||
if os.path.isfile(good_h_flag): |
|||
sys.path.insert(0, try_other_repo) |
|||
print("Using {}".format(try_other_repo), file=sys.stderr) |
|||
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: |
|||
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) |
|||
|
|||
print("No {}. Trying installed copy...".format(good_h_flag), |
|||
file=sys.stderr) |
|||
|
|||
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 os.path.isfile(os.path.join(repos_dir, "hierosoft", "init.py")): |
|||
# sys.path.insert(repos_dir) |
|||
# ^ implies both are installed (and that repos_dir is actually modules_dir), |
|||
# so leave path alone. |
|||
|
|||
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()) |
|||
|
Loading…
Reference in new issue