poikilos
1 year ago
2 changed files with 215 additions and 0 deletions
@ -0,0 +1,20 @@ |
|||||
|
{ |
||||
|
"folders": [ |
||||
|
{ |
||||
|
"path": "../../../minetest-rsync" |
||||
|
} |
||||
|
], |
||||
|
"settings": {}, |
||||
|
"launch": { |
||||
|
"version": "0.2.0", |
||||
|
"configurations": [ |
||||
|
{ |
||||
|
"type":"python", |
||||
|
"request": "launch", |
||||
|
"name": "Launch minetest-rsync", |
||||
|
"program": "../git/EnlivenMinetest/utilities/run-any", |
||||
|
"args": ["./bin/minetest"], |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
} |
@ -0,0 +1,195 @@ |
|||||
|
#!/usr/bin/env python |
||||
|
""" |
||||
|
Run any program and show the output. The purpose is to allow VSCode's |
||||
|
python runner to run a minetest binary (such as if you are using VSCode |
||||
|
to edit Lua but want to run Minetest to test the Lua). |
||||
|
|
||||
|
Usage: |
||||
|
run-any <minetest_executable_path> |
||||
|
""" |
||||
|
from __future__ import print_function |
||||
|
import os |
||||
|
import sys |
||||
|
import subprocess |
||||
|
import copy |
||||
|
import shlex |
||||
|
from collections import OrderedDict |
||||
|
|
||||
|
|
||||
|
def echo0(*args): |
||||
|
print(*args, file=sys.stderr) |
||||
|
return True |
||||
|
|
||||
|
|
||||
|
def usage(): |
||||
|
echo0(__doc__) |
||||
|
|
||||
|
|
||||
|
def show_and_return(cmd, enable_collect=False, cwd=None, shell=False): |
||||
|
"""Show the output of a process while it is running & get return code. |
||||
|
|
||||
|
Warning: Minetest doesn't start flushing data until the server |
||||
|
starts. Output from mainmenu is flushed around when server starts |
||||
|
(or when user exits main menu, whichever comes first). This behavior |
||||
|
is the same whether or not shell is True. |
||||
|
|
||||
|
If running minetest, set cmd to [minetest_path, "--logfile", ""] |
||||
|
and set cwd to minetest directory (not "bin"!). |
||||
|
|
||||
|
Args: |
||||
|
cmd (Union[list[string], string]): The command to run, either as |
||||
|
list of args where first is command and no quotes are |
||||
|
necessary, or space-separated string formatted the same way |
||||
|
except with quotes where necessary (if an arg |
||||
|
contains spaces). |
||||
|
- If is *not* str and shell is True, cmd is converted to |
||||
|
str. |
||||
|
enable_collect (Optional[boolean]): Enable collecting lines |
||||
|
in the 'lines' key of the return. |
||||
|
cwd (Optional[string]): Set a new current working dir. |
||||
|
shell (boolean): If True, run a shell (processes globs and |
||||
|
shell options that occur within cmd). |
||||
|
|
||||
|
Returns: |
||||
|
dict: 'code' is return code of the command. |
||||
|
""" |
||||
|
# (See |
||||
|
# <https://cyberciti.biz/faq/python-run-external-command-and-get-output/>) |
||||
|
if shell not in [None, True, False]: |
||||
|
raise ValueError("Expected None, True, or False for shell but got %s" |
||||
|
% pformat(shell)) |
||||
|
if cwd is None: |
||||
|
cwd = os.getcwd() |
||||
|
if shell and not hasattr(cmd, "split"): |
||||
|
# shell cannot correctly utilize a list/tuple (somehow program |
||||
|
# runs but doesn't get the blank argument in |
||||
|
# [path, "--logfile, ""] because debug.txt still gets created) |
||||
|
# so convert to string: |
||||
|
cmd = shlex.join(cmd) |
||||
|
proc = subprocess.Popen(cmd, shell=shell, stderr=subprocess.PIPE, |
||||
|
stdout=subprocess.PIPE, cwd=cwd) |
||||
|
code = None |
||||
|
# Do not wait for finish--start displaying output immediately |
||||
|
out = { |
||||
|
'bytes': None, # current line bytes |
||||
|
'string': "", # current line string (same as bytes if Python 2 running) |
||||
|
'buffer': "", # cumulative buffer |
||||
|
} |
||||
|
if enable_collect: |
||||
|
out['lines'] = [] # already-shown lines |
||||
|
err = copy.deepcopy(out) |
||||
|
if enable_collect: |
||||
|
if id(err['lines']) == id(out['lines']): |
||||
|
raise RuntimeError( |
||||
|
"deepcopy filed. lines are same in both streams" |
||||
|
) |
||||
|
out['source'] = proc.stdout |
||||
|
err['source'] = proc.stderr |
||||
|
out['stream'] = sys.stdout |
||||
|
err['stream'] = sys.stderr |
||||
|
stream_metas = OrderedDict({ |
||||
|
'out': out, |
||||
|
'err': err, |
||||
|
}) |
||||
|
|
||||
|
while True: |
||||
|
for name, meta in stream_metas.items(): |
||||
|
# meta['bytes'] = err['source'].read(1) |
||||
|
# ^ scrambles letters (mixes stderr and stdout) somehow, so: |
||||
|
if name == "out": |
||||
|
meta['bytes'] = proc.stdout.read(1) |
||||
|
elif name == "err": |
||||
|
meta['bytes'] = proc.stderr.read(1) |
||||
|
meta['string'] = meta['bytes'] |
||||
|
if (meta['string'] is not None) and (sys.version_info.major >= 3): |
||||
|
meta['string'] = meta['bytes'].decode("utf-8") |
||||
|
meta['buffer'] += meta['string'] |
||||
|
|
||||
|
code = proc.poll() # None if command isn't finished |
||||
|
if out['string'] == '' and err['string'] == '' and code is not None: |
||||
|
# If no more to write *and* there is a return code (finished) |
||||
|
for name, meta in stream_metas.items(): |
||||
|
if len(meta['buffer']) > 0: |
||||
|
if name == "out": |
||||
|
print(meta['buffer']) |
||||
|
else: |
||||
|
echo0(meta['buffer']) |
||||
|
break |
||||
|
|
||||
|
for _, meta in stream_metas.items(): |
||||
|
# Write out['string'] to stdout then err['string'] to stderr |
||||
|
if meta['buffer'] == '': |
||||
|
continue |
||||
|
lastI = meta['buffer'].rfind("\n") |
||||
|
if lastI >= 0: |
||||
|
# Only show finished lines (avoid mixing stdout and |
||||
|
# stderr on the same line). |
||||
|
if code is not None: |
||||
|
# The program ended, so show everything. |
||||
|
lastI = len(meta['buffer']) - 1 |
||||
|
# ^ -1 since inclusive (+1 below) |
||||
|
|
||||
|
this_chunk = meta['buffer'][:lastI+1] |
||||
|
if enable_collect: |
||||
|
meta['lines'] += this_chunk.split("\n") |
||||
|
meta['stream'].write(this_chunk) |
||||
|
meta['stream'].flush() |
||||
|
meta['buffer'] = meta['buffer'][lastI+1:] |
||||
|
# else: |
||||
|
# echo0("Incomplete line:%s" % meta['buffer']) |
||||
|
for _, meta in stream_metas.items(): |
||||
|
del meta['source'] |
||||
|
del meta['stream'] |
||||
|
del meta['bytes'] |
||||
|
# del meta['string'] |
||||
|
return { |
||||
|
'code': code, |
||||
|
'streams': stream_metas, |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
if len(sys.argv) < 2: |
||||
|
usage() |
||||
|
echo0("Error: You didn't specify what program to run.") |
||||
|
return 1 |
||||
|
# subprocess.call(sys.argv[1]) |
||||
|
path = sys.argv[1] |
||||
|
cwd = None |
||||
|
if os.path.isfile(path): |
||||
|
cwd = os.path.dirname(path) |
||||
|
if os.path.basename(cwd) == "bin": |
||||
|
# Minetest must run from minetest not from minetest/bin |
||||
|
# (especially when minetest is a script in minetest.org |
||||
|
# builds) |
||||
|
cwd = os.path.dirname(cwd) |
||||
|
# else: |
||||
|
# cwd = os.dirname(hierosoft.which(path)) # TODO: uncomment this case? |
||||
|
basename = os.path.basename(path) |
||||
|
project_name = basename.replace("server", "") |
||||
|
cmd = path |
||||
|
if project_name in ["minetest", "finetest", "trolltest", "multicraft"]: |
||||
|
cmd = [path, "--logfile", ""] |
||||
|
# Do not write debug.txt to cwd, since Python code will read |
||||
|
# stdout and stderr. |
||||
|
echo0("Running %s" % shlex.join(cmd)) |
||||
|
else: |
||||
|
echo0("Running %s" % path) |
||||
|
enable_collect = False |
||||
|
results = show_and_return( |
||||
|
cmd, |
||||
|
enable_collect=enable_collect, |
||||
|
cwd=cwd |
||||
|
) |
||||
|
if enable_collect: |
||||
|
echo0("\nLINES:") |
||||
|
for line in results['streams']['out']['lines']: |
||||
|
print(line) |
||||
|
for line in results['streams']['err']['lines']: |
||||
|
echo0(line) |
||||
|
# else 'lines' lists will not be in the streams! |
||||
|
return results['code'] |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
sys.exit(main()) |
Loading…
Reference in new issue