diff --git a/HISTORY.rst b/HISTORY.rst index 5cdacecc..6ea89cfe 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -15,6 +15,7 @@ PlatformIO 4.0 * Renamed "enable_ssl" setting to `strict_ssl `__ * Fixed an issue with incorrect escaping of Windows slashes when using `PIO Unified Debugger `__ and "piped" openOCD * Fixed an issue when "debug", "home", "run", and "test" commands were not shown in "platformio --help" CLI +* Fixed an issue with PIO Home's "No JSON object could be decoded" (`issue #2823 `_) 4.0.0 (2019-07-10) ~~~~~~~~~~~~~~~~~~ diff --git a/platformio/commands/home/rpc/handlers/piocore.py b/platformio/commands/home/rpc/handlers/piocore.py index cb5651bf..4fa13b1a 100644 --- a/platformio/commands/home/rpc/handlers/piocore.py +++ b/platformio/commands/home/rpc/handlers/piocore.py @@ -21,6 +21,7 @@ from io import BytesIO, StringIO import click import jsonrpc # pylint: disable=import-error +from twisted.internet import defer # pylint: disable=import-error from twisted.internet import threads # pylint: disable=import-error from twisted.internet import utils # pylint: disable=import-error @@ -68,6 +69,10 @@ class MultiThreadingStdStream(object): class PIOCoreRPC(object): + @staticmethod + def version(): + return __version__ + @staticmethod def setup_multithreading_std_streams(): if isinstance(sys.stdout, MultiThreadingStdStream): @@ -79,41 +84,67 @@ class PIOCoreRPC(object): @staticmethod def call(args, options=None): - PIOCoreRPC.setup_multithreading_std_streams() - cwd = (options or {}).get("cwd") or os.getcwd() + return defer.maybeDeferred(PIOCoreRPC._call_generator, args, options) + + @staticmethod + @defer.inlineCallbacks + def _call_generator(args, options=None): for i, arg in enumerate(args): if isinstance(arg, string_types): args[i] = arg.encode(get_filesystem_encoding()) if PY2 else arg else: args[i] = str(arg) - def _call_inline(): + to_json = "--json-output" in args + + try: + if args and args[0] in ("account", "remote"): + result = yield PIOCoreRPC._call_subprocess(args, options) + defer.returnValue(PIOCoreRPC._process_result(result, to_json)) + else: + result = yield PIOCoreRPC._call_inline(args, options) + try: + defer.returnValue( + PIOCoreRPC._process_result(result, to_json)) + except ValueError: + # fall-back to subprocess method + result = yield PIOCoreRPC._call_subprocess(args, options) + defer.returnValue( + PIOCoreRPC._process_result(result, to_json)) + except Exception as e: # pylint: disable=bare-except + raise jsonrpc.exceptions.JSONRPCDispatchException( + code=4003, message="PIO Core Call Error", data=str(e)) + + @staticmethod + def _call_inline(args, options): + PIOCoreRPC.setup_multithreading_std_streams() + cwd = (options or {}).get("cwd") or os.getcwd() + + def _thread_task(): with fs.cd(cwd): exit_code = __main__.main(["-c"] + args) return (PIOCoreRPC.thread_stdout.get_value_and_reset(), PIOCoreRPC.thread_stderr.get_value_and_reset(), exit_code) - if args and args[0] in ("account", "remote"): - d = utils.getProcessOutputAndValue( - helpers.get_core_fullpath(), - args, - path=cwd, - env={k: v - for k, v in os.environ.items() if "%" not in k}) - else: - d = threads.deferToThread(_call_inline) - - d.addCallback(PIOCoreRPC._call_callback, "--json-output" in args) - d.addErrback(PIOCoreRPC._call_errback) - return d + return threads.deferToThread(_thread_task) @staticmethod - def _call_callback(result, json_output=False): + def _call_subprocess(args, options): + cwd = (options or {}).get("cwd") or os.getcwd() + return utils.getProcessOutputAndValue( + helpers.get_core_fullpath(), + args, + path=cwd, + env={k: v + for k, v in os.environ.items() if "%" not in k}) + + @staticmethod + def _process_result(result, to_json=False): out, err, code = result text = ("%s\n\n%s" % (out, err)).strip() if code != 0: raise Exception(text) - if not json_output: + if not to_json: return text try: return json.loads(out) @@ -129,14 +160,3 @@ class PIOCoreRPC(object): except ValueError: pass raise e - - @staticmethod - def _call_errback(failure): - raise jsonrpc.exceptions.JSONRPCDispatchException( - code=4003, - message="PIO Core Call Error", - data=failure.getErrorMessage()) - - @staticmethod - def version(): - return __version__