mirror of
https://github.com/platformio/platformio-core.git
synced 2025-07-30 10:07:14 +02:00
Merge branch 'feature/performance-fixes' into develop
This commit is contained in:
@ -119,8 +119,7 @@ An unexpected error occurred. Further steps:
|
|||||||
|
|
||||||
|
|
||||||
def debug_gdb_main():
|
def debug_gdb_main():
|
||||||
sys.argv = [sys.argv[0], "debug", "--interface", "gdb"] + sys.argv[1:]
|
return main([sys.argv[0], "debug", "--interface", "gdb"] + sys.argv[1:])
|
||||||
return main()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -16,7 +16,6 @@ import codecs
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
from copy import deepcopy
|
|
||||||
from os import environ, getenv, listdir, remove
|
from os import environ, getenv, listdir, remove
|
||||||
from os.path import abspath, dirname, expanduser, isdir, isfile, join
|
from os.path import abspath, dirname, expanduser, isdir, isfile, join
|
||||||
from time import time
|
from time import time
|
||||||
@ -93,28 +92,26 @@ class State(object):
|
|||||||
self.lock = lock
|
self.lock = lock
|
||||||
if not self.path:
|
if not self.path:
|
||||||
self.path = join(get_project_core_dir(), "appstate.json")
|
self.path = join(get_project_core_dir(), "appstate.json")
|
||||||
self._state = {}
|
self._storage = {}
|
||||||
self._prev_state = {}
|
|
||||||
self._lockfile = None
|
self._lockfile = None
|
||||||
|
self._modified = False
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
try:
|
try:
|
||||||
self._lock_state_file()
|
self._lock_state_file()
|
||||||
if isfile(self.path):
|
if isfile(self.path):
|
||||||
self._state = util.load_json(self.path)
|
self._storage = util.load_json(self.path)
|
||||||
assert isinstance(self._state, dict)
|
assert isinstance(self._storage, dict)
|
||||||
except (AssertionError, UnicodeDecodeError,
|
except (AssertionError, ValueError, UnicodeDecodeError,
|
||||||
exception.PlatformioException):
|
exception.InvalidJSONFile):
|
||||||
self._state = {}
|
self._storage = {}
|
||||||
self._prev_state = deepcopy(self._state)
|
return self
|
||||||
return self._state
|
|
||||||
|
|
||||||
def __exit__(self, type_, value, traceback):
|
def __exit__(self, type_, value, traceback):
|
||||||
new_state = dump_json_to_unicode(self._state)
|
if self._modified:
|
||||||
if self._prev_state != new_state:
|
|
||||||
try:
|
try:
|
||||||
with open(self.path, "w") as fp:
|
with open(self.path, "w") as fp:
|
||||||
fp.write(new_state)
|
fp.write(dump_json_to_unicode(self._storage))
|
||||||
except IOError:
|
except IOError:
|
||||||
raise exception.HomeDirPermissionsError(get_project_core_dir())
|
raise exception.HomeDirPermissionsError(get_project_core_dir())
|
||||||
self._unlock_state_file()
|
self._unlock_state_file()
|
||||||
@ -132,8 +129,31 @@ class State(object):
|
|||||||
if hasattr(self, "_lockfile") and self._lockfile:
|
if hasattr(self, "_lockfile") and self._lockfile:
|
||||||
self._lockfile.release()
|
self._lockfile.release()
|
||||||
|
|
||||||
def __del__(self):
|
# Dictionary Proxy
|
||||||
self._unlock_state_file()
|
|
||||||
|
def as_dict(self):
|
||||||
|
return self._storage
|
||||||
|
|
||||||
|
def get(self, key, default=True):
|
||||||
|
return self._storage.get(key, default)
|
||||||
|
|
||||||
|
def update(self, *args, **kwargs):
|
||||||
|
self._modified = True
|
||||||
|
return self._storage.update(*args, **kwargs)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._storage[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._modified = True
|
||||||
|
self._storage[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
self._modified = True
|
||||||
|
del self._storage[key]
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self._storage
|
||||||
|
|
||||||
|
|
||||||
class ContentCache(object):
|
class ContentCache(object):
|
||||||
|
@ -123,7 +123,7 @@ def cli(ctx, project_dir, project_conf, environment, verbose, interface,
|
|||||||
if helpers.is_mi_mode(__unprocessed):
|
if helpers.is_mi_mode(__unprocessed):
|
||||||
click.echo('~"Preparing firmware for debugging...\\n"')
|
click.echo('~"Preparing firmware for debugging...\\n"')
|
||||||
output = helpers.GDBBytesIO()
|
output = helpers.GDBBytesIO()
|
||||||
with helpers.capture_std_streams(output):
|
with util.capture_std_streams(output):
|
||||||
helpers.predebug_project(ctx, project_dir, env_name, preload,
|
helpers.predebug_project(ctx, project_dir, env_name, preload,
|
||||||
verbose)
|
verbose)
|
||||||
output.close()
|
output.close()
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from contextlib import contextmanager
|
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@ -41,17 +40,6 @@ class GDBBytesIO(BytesIO): # pylint: disable=too-few-public-methods
|
|||||||
self.STDOUT.flush()
|
self.STDOUT.flush()
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def capture_std_streams(stdout, stderr=None):
|
|
||||||
_stdout = sys.stdout
|
|
||||||
_stderr = sys.stderr
|
|
||||||
sys.stdout = stdout
|
|
||||||
sys.stderr = stderr or stdout
|
|
||||||
yield
|
|
||||||
sys.stdout = _stdout
|
|
||||||
sys.stderr = _stderr
|
|
||||||
|
|
||||||
|
|
||||||
def is_mi_mode(args):
|
def is_mi_mode(args):
|
||||||
return "--interpreter" in " ".join(args)
|
return "--interpreter" in " ".join(args)
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ class AppRPC(object):
|
|||||||
]
|
]
|
||||||
|
|
||||||
state['storage'] = storage
|
state['storage'] = storage
|
||||||
return state
|
return state.as_dict()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_state():
|
def get_state():
|
||||||
@ -66,6 +66,6 @@ class AppRPC(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def save_state(state):
|
def save_state(state):
|
||||||
with app.State(AppRPC.APPSTATE_PATH, lock=True) as s:
|
with app.State(AppRPC.APPSTATE_PATH, lock=True) as s:
|
||||||
s.clear()
|
# s.clear()
|
||||||
s.update(state)
|
s.update(state)
|
||||||
return True
|
return True
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import codecs
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
@ -67,10 +68,10 @@ class OSRPC(object):
|
|||||||
def request_content(self, uri, data=None, headers=None, cache_valid=None):
|
def request_content(self, uri, data=None, headers=None, cache_valid=None):
|
||||||
if uri.startswith('http'):
|
if uri.startswith('http'):
|
||||||
return self.fetch_content(uri, data, headers, cache_valid)
|
return self.fetch_content(uri, data, headers, cache_valid)
|
||||||
if isfile(uri):
|
if not isfile(uri):
|
||||||
with open(uri) as fp:
|
return None
|
||||||
return fp.read()
|
with codecs.open(uri, encoding="utf-8") as fp:
|
||||||
return None
|
return fp.read()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def open_url(url):
|
def open_url(url):
|
||||||
|
@ -16,21 +16,64 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import sys
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
import jsonrpc # pylint: disable=import-error
|
import jsonrpc # pylint: disable=import-error
|
||||||
from twisted.internet import utils # pylint: disable=import-error
|
from twisted.internet import threads # pylint: disable=import-error
|
||||||
|
|
||||||
from platformio import __version__
|
from platformio import __main__, __version__, util
|
||||||
from platformio.commands.home import helpers
|
|
||||||
from platformio.compat import string_types
|
from platformio.compat import string_types
|
||||||
|
|
||||||
|
try:
|
||||||
|
from thread import get_ident as thread_get_ident
|
||||||
|
except ImportError:
|
||||||
|
from threading import get_ident as thread_get_ident
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadSafeStdBuffer(object):
|
||||||
|
|
||||||
|
def __init__(self, parent_stream, parent_thread_id):
|
||||||
|
self.parent_stream = parent_stream
|
||||||
|
self.parent_thread_id = parent_thread_id
|
||||||
|
self._buffer = {}
|
||||||
|
|
||||||
|
def write(self, value):
|
||||||
|
thread_id = thread_get_ident()
|
||||||
|
if thread_id == self.parent_thread_id:
|
||||||
|
return self.parent_stream.write(
|
||||||
|
value if isinstance(value, string_types) else value.decode())
|
||||||
|
if thread_id not in self._buffer:
|
||||||
|
self._buffer[thread_id] = BytesIO()
|
||||||
|
return self._buffer[thread_id].write(value)
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
return (self.parent_stream.flush()
|
||||||
|
if thread_get_ident() == self.parent_thread_id else None)
|
||||||
|
|
||||||
|
def getvalue_and_close(self, thread_id=None):
|
||||||
|
thread_id = thread_id or thread_get_ident()
|
||||||
|
if thread_id not in self._buffer:
|
||||||
|
return ""
|
||||||
|
result = self._buffer.get(thread_id).getvalue()
|
||||||
|
self._buffer.get(thread_id).close()
|
||||||
|
del self._buffer[thread_id]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class PIOCoreRPC(object):
|
class PIOCoreRPC(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
cur_thread_id = thread_get_ident()
|
||||||
|
PIOCoreRPC.thread_stdout = ThreadSafeStdBuffer(sys.stdout,
|
||||||
|
cur_thread_id)
|
||||||
|
PIOCoreRPC.thread_stderr = ThreadSafeStdBuffer(sys.stderr,
|
||||||
|
cur_thread_id)
|
||||||
|
sys.stdout = PIOCoreRPC.thread_stdout
|
||||||
|
sys.stderr = PIOCoreRPC.thread_stderr
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def call(args, options=None):
|
def call(args, options=None):
|
||||||
json_output = "--json-output" in args
|
|
||||||
try:
|
try:
|
||||||
args = [
|
args = [
|
||||||
str(arg) if not isinstance(arg, string_types) else arg
|
str(arg) if not isinstance(arg, string_types) else arg
|
||||||
@ -39,13 +82,15 @@ class PIOCoreRPC(object):
|
|||||||
except UnicodeError:
|
except UnicodeError:
|
||||||
raise jsonrpc.exceptions.JSONRPCDispatchException(
|
raise jsonrpc.exceptions.JSONRPCDispatchException(
|
||||||
code=4002, message="PIO Core: non-ASCII chars in arguments")
|
code=4002, message="PIO Core: non-ASCII chars in arguments")
|
||||||
d = utils.getProcessOutputAndValue(
|
|
||||||
helpers.get_core_fullpath(),
|
def _call_cli():
|
||||||
args,
|
with util.cd((options or {}).get("cwd") or os.getcwd()):
|
||||||
path=(options or {}).get("cwd"),
|
exit_code = __main__.main(["-c"] + args)
|
||||||
env={k: v
|
return (PIOCoreRPC.thread_stdout.getvalue_and_close(),
|
||||||
for k, v in os.environ.items() if "%" not in k})
|
PIOCoreRPC.thread_stderr.getvalue_and_close(), exit_code)
|
||||||
d.addCallback(PIOCoreRPC._call_callback, json_output)
|
|
||||||
|
d = threads.deferToThread(_call_cli)
|
||||||
|
d.addCallback(PIOCoreRPC._call_callback, "--json-output" in args)
|
||||||
d.addErrback(PIOCoreRPC._call_errback)
|
d.addErrback(PIOCoreRPC._call_errback)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
@ -55,15 +100,7 @@ class PIOCoreRPC(object):
|
|||||||
text = ("%s\n\n%s" % (out, err)).strip()
|
text = ("%s\n\n%s" % (out, err)).strip()
|
||||||
if code != 0:
|
if code != 0:
|
||||||
raise Exception(text)
|
raise Exception(text)
|
||||||
if not json_output:
|
return json.loads(out) if json_output else text
|
||||||
return text
|
|
||||||
try:
|
|
||||||
return json.loads(out)
|
|
||||||
except ValueError as e:
|
|
||||||
if "sh: " in out:
|
|
||||||
return json.loads(
|
|
||||||
re.sub(r"^sh: [^\n]+$", "", out, flags=re.M).strip())
|
|
||||||
raise e
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _call_errback(failure):
|
def _call_errback(failure):
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
# pylint: disable=import-error
|
# pylint: disable=import-error
|
||||||
|
|
||||||
|
import click
|
||||||
import jsonrpc
|
import jsonrpc
|
||||||
from autobahn.twisted.websocket import (WebSocketServerFactory,
|
from autobahn.twisted.websocket import (WebSocketServerFactory,
|
||||||
WebSocketServerProtocol)
|
WebSocketServerProtocol)
|
||||||
@ -26,7 +27,7 @@ from platformio.compat import PY2, dump_json_to_unicode, is_bytes
|
|||||||
class JSONRPCServerProtocol(WebSocketServerProtocol):
|
class JSONRPCServerProtocol(WebSocketServerProtocol):
|
||||||
|
|
||||||
def onMessage(self, payload, isBinary): # pylint: disable=unused-argument
|
def onMessage(self, payload, isBinary): # pylint: disable=unused-argument
|
||||||
# print("> %s" % payload)
|
# click.echo("> %s" % payload)
|
||||||
response = jsonrpc.JSONRPCResponseManager.handle(
|
response = jsonrpc.JSONRPCResponseManager.handle(
|
||||||
payload, self.factory.dispatcher).data
|
payload, self.factory.dispatcher).data
|
||||||
# if error
|
# if error
|
||||||
@ -52,11 +53,12 @@ class JSONRPCServerProtocol(WebSocketServerProtocol):
|
|||||||
message=failure.getErrorMessage())
|
message=failure.getErrorMessage())
|
||||||
del response["result"]
|
del response["result"]
|
||||||
response['error'] = e.error._data # pylint: disable=protected-access
|
response['error'] = e.error._data # pylint: disable=protected-access
|
||||||
print(response['error'])
|
|
||||||
self.sendJSONResponse(response)
|
self.sendJSONResponse(response)
|
||||||
|
|
||||||
def sendJSONResponse(self, response):
|
def sendJSONResponse(self, response):
|
||||||
# print("< %s" % response)
|
# click.echo("< %s" % response)
|
||||||
|
if "error" in response:
|
||||||
|
click.secho("Error: %s" % response['error'], fg="red", err=True)
|
||||||
response = dump_json_to_unicode(response)
|
response = dump_json_to_unicode(response)
|
||||||
if not PY2 and not is_bytes(response):
|
if not PY2 and not is_bytes(response):
|
||||||
response = response.encode("utf-8")
|
response = response.encode("utf-8")
|
||||||
|
@ -54,9 +54,12 @@ if PY2:
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
def dump_json_to_unicode(obj):
|
def dump_json_to_unicode(obj):
|
||||||
|
if isinstance(obj, unicode):
|
||||||
|
return obj
|
||||||
return json.dumps(obj,
|
return json.dumps(obj,
|
||||||
encoding=get_filesystem_encoding(),
|
encoding=get_filesystem_encoding(),
|
||||||
ensure_ascii=False).encode("utf8")
|
ensure_ascii=False,
|
||||||
|
sort_keys=True).encode("utf8")
|
||||||
|
|
||||||
_magic_check = re.compile('([*?[])')
|
_magic_check = re.compile('([*?[])')
|
||||||
_magic_check_bytes = re.compile(b'([*?[])')
|
_magic_check_bytes = re.compile(b'([*?[])')
|
||||||
@ -100,4 +103,6 @@ else:
|
|||||||
return data.encode()
|
return data.encode()
|
||||||
|
|
||||||
def dump_json_to_unicode(obj):
|
def dump_json_to_unicode(obj):
|
||||||
return json.dumps(obj, ensure_ascii=False)
|
if isinstance(obj, string_types):
|
||||||
|
return obj
|
||||||
|
return json.dumps(obj, ensure_ascii=False, sort_keys=True)
|
||||||
|
@ -20,6 +20,7 @@ import socket
|
|||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
from contextlib import contextmanager
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from os.path import abspath, basename, dirname, isfile, join
|
from os.path import abspath, basename, dirname, isfile, join
|
||||||
@ -107,6 +108,17 @@ def singleton(cls):
|
|||||||
return get_instance
|
return get_instance
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def capture_std_streams(stdout, stderr=None):
|
||||||
|
_stdout = sys.stdout
|
||||||
|
_stderr = sys.stderr
|
||||||
|
sys.stdout = stdout
|
||||||
|
sys.stderr = stderr or stdout
|
||||||
|
yield
|
||||||
|
sys.stdout = _stdout
|
||||||
|
sys.stderr = _stderr
|
||||||
|
|
||||||
|
|
||||||
def load_json(file_path):
|
def load_json(file_path):
|
||||||
try:
|
try:
|
||||||
with open(file_path, "r") as f:
|
with open(file_path, "r") as f:
|
||||||
|
Reference in New Issue
Block a user