Advertisement
Guest User

platformio plus code

a guest
Nov 25th, 2018
274
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 111.53 KB | None | 0 0
  1. #$load_pysite
  2.  
  3. import imp
  4. import os
  5. import sys
  6. import warnings
  7. from os.path import dirname
  8. from site import addsitedir
  9. def _get_pio_site_packages_dir():
  10. if os.getenv("PIOCOREPYSITEDIR"):
  11. return os.getenv("PIOCOREPYSITEDIR")
  12. data=imp.find_module("platformio")
  13. if data:
  14. return dirname(data[1])
  15. return None
  16. pio_sp_dir=_get_pio_site_packages_dir()
  17. if pio_sp_dir:
  18. if pio_sp_dir in sys.path:
  19. sys.path.remove(pio_sp_dir)
  20. sys.path.insert(0,pio_sp_dir)
  21. del pio_sp_dir
  22. if os.getenv("PYTHONPYSITEDIR"):
  23. warnings.simplefilter("ignore")
  24. addsitedir(os.getenv("PYTHONPYSITEDIR"))
  25. sys.path.insert(0,os.getenv("PYTHONPYSITEDIR"))
  26. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  27.  
  28.  
  29.  
  30. #$config
  31.  
  32. import sys
  33. VERSION=(1,5,3)
  34. __version__=".".join([str(s)for s in VERSION])
  35. __title__="platformio-plus"
  36. __description__="PIO Plus"
  37. __url__="https://pioplus.com"
  38. __author__="Ivan Kravets"
  39. __email__="me@ikravets.com"
  40. __license__="Proprietary License"
  41. __copyright__="Copyright 2014-present PlatformIO"
  42. CLOUD_API_ENDPOINT="https://api.pioplus.com"
  43. CLOUD_API_VERSION=1
  44. PIOREMOTE_HOST="rs.pioplus.com"
  45. PIOREMOTE_PORT=8813
  46. PIOREMOTE_PING_DELAY=60
  47. PIOREMOTE_PING_MAX_FAILURES=3
  48. DEBUG=False
  49. if DEBUG:
  50. CLOUD_API_ENDPOINT="http://127.0.0.1:8013"
  51. PIOREMOTE_HOST="localhost"
  52. if sys.version_info<(2,7,0)or sys.version_info>=(3,0,0):
  53. msg=("PlatformIO version %s does not run under Python version %s.\n" "Python 3 is not yet supported.\n")
  54. sys.stderr.write(msg%(__version__,sys.version.split()[0]))
  55. sys.exit(1)
  56. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  57.  
  58.  
  59.  
  60. #$exception
  61.  
  62. from platformio.exception import PlatformioException
  63. class TestDirNotExists(PlatformioException):
  64. MESSAGE="A test folder '{0}' does not exist.\nPlease create 'test' " "directory in project's root and put a test set.\n" "More details about Unit " "Testing: http://docs.platformio.org/page/plus/" "unit-testing.html"
  65. class AccountPermissionError(PlatformioException):
  66. MESSAGE="You do not have permission for this operation. Please use " "`pio account show` command to check current permissions.\n" "Further details: https://pioplus.com/pricing.html " "or support@pioplus.com"
  67. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  68.  
  69.  
  70.  
  71. #$requests_threads
  72.  
  73. from requests import Session
  74. from twisted.internet import threads
  75. from twisted.internet.defer import ensureDeferred
  76. class AsyncSession(Session):
  77. def __init__(self,n=None,*args,**kwargs):
  78. if n:
  79. from twisted.internet import reactor
  80. pool=reactor.getThreadPool()
  81. pool.adjustPoolsize(0,n)
  82. super(AsyncSession,self).__init__(*args,**kwargs)
  83. def request(self,*args,**kwargs):
  84. func=super(AsyncSession,self).request
  85. return threads.deferToThread(func,*args,**kwargs)
  86. def wrap(self,*args,**kwargs):
  87. return ensureDeferred(*args,**kwargs)
  88. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  89.  
  90.  
  91.  
  92. #$cmd_account
  93.  
  94. import json
  95. import os
  96. import re
  97. import socket
  98. import struct
  99. from binascii import hexlify,unhexlify
  100. from datetime import datetime
  101. from hashlib import sha1
  102. from time import time
  103. import click
  104. from platformio import __author__,__title__,app
  105. from platformio.exception import ReturnErrorCode
  106. from platformio.util import get_api_result
  107. import config
  108. from exception import AccountPermissionError
  109. ACCOUNT_DATA_KEY="a8899e216a19f0646172ca5c13aa0ee19a808f3b"
  110. def get_host_id():
  111. id_=sha1(app.get_cid())
  112. id_.update(get_host_name())
  113. return id_.hexdigest()
  114. def get_host_name():
  115. return str(socket.gethostname())[:255]
  116. def store_account_data(data):
  117. with app.ContentCache()as cc:
  118. cc.set(ACCOUNT_DATA_KEY,json.dumps(data),"3d")
  119. def restore_account_data():
  120. with app.ContentCache()as cc:
  121. data=cc.get(ACCOUNT_DATA_KEY)
  122. if not data:
  123. return None
  124. if isinstance(data,dict):
  125. return data
  126. try:
  127. return json.loads(data)
  128. except ValueError:
  129. return None
  130. def delete_account_data():
  131. with app.ContentCache()as cc:
  132. return cc.delete(ACCOUNT_DATA_KEY)
  133. def store_session_token(token):
  134. app.set_state_item("stoken",token)
  135. def restore_session_token():
  136. return app.get_state_item("stoken")
  137. def delete_session_token():
  138. return app.delete_state_item("stoken")
  139. def store_username(username):
  140. app.set_state_item("username",username)
  141. def restore_username():
  142. return app.get_state_item("username")
  143. def delete_username():
  144. return app.delete_state_item("username")
  145. def pack_session_token(host_id,session_id,permission_bitmask,expire):
  146. assert isinstance(host_id,basestring)and len(host_id)==40
  147. hmac=sha1(str(host_id))
  148. hmac.update(str(session_id))
  149. hmac.update(str(permission_bitmask))
  150. hmac.update(str(expire))
  151. hmac.update("PlatformIO"+str(__title__))
  152. return "%s%s"%(hmac.hexdigest(),hexlify(struct.pack("!III",session_id,permission_bitmask,expire)))
  153. def unpack_session_token(token,host_id=None):
  154. if not token or len(token)!=64:
  155. return None
  156. try:
  157. (session_id,permission_bitmask,expire)=struct.unpack("!III",unhexlify(token[40:]))
  158. if(host_id and token[0:40]!=pack_session_token(host_id,session_id,permission_bitmask,expire)[0:40]):
  159. return None
  160. except(AssertionError,struct.error):
  161. return None
  162. return{"session_id":session_id,"permission_bitmask":permission_bitmask,"expire":expire}
  163. def get_session_data():
  164. data=unpack_session_token(restore_session_token(),get_host_id())
  165. if data and data['expire']>time():
  166. return data
  167. if os.getenv("PLATFORMIO_AUTH_TOKEN"):
  168. click.echo("Authenticating using token")
  169. if not account_login("token",os.getenv("PLATFORMIO_AUTH_TOKEN")):
  170. return None
  171. return unpack_session_token(restore_session_token(),get_host_id())
  172. return data
  173. def cmd_check_permission(cmd_ctx):
  174. assert cmd_ctx.args
  175. allowed_conds=[cmd_ctx.args[0]=="home",len(cmd_ctx.args)>1 and cmd_ctx.args[0]=="account" and cmd_ctx.args[1]in("login","register","forgot")]
  176. if any(allowed_conds):
  177. return True
  178. data=get_session_data()
  179. if not data or data['expire']<time():
  180. click.secho("You are not logged in. Please log in to PIO Account using " "PlatformIO IDE > PIO Home > Account or `pio account login` " "command and try again. \n\nIf you don't have PIO Account yet, " "please create it using PlatformIO IDE > PIO Home > Account " "or `pio account register` command.",fg="red",err=True)
  181. click.echo("Details: %s"%click.style("http://docs.platformio.org/page/" "userguide/account/index.html",fg="cyan"))
  182. raise ReturnErrorCode(1)
  183. permission_conds=[cmd_ctx.args[0]=="test" and not data['permission_bitmask']&1,(cmd_ctx.args[0]=="remote" and "test" in cmd_ctx.args and not data['permission_bitmask']&2),cmd_ctx.args[0]=="debug" and not data['permission_bitmask']&4]
  184. if any(permission_conds):
  185. raise AccountPermissionError()
  186. return True
  187. def cmd_validate_email(ctx,param,value):
  188. value=str(value).strip()
  189. if not re.match(r"^[^@]+@[^@]+\.[^@]+$",value):
  190. raise click.BadParameter("Invalid E-Mail address")
  191. return value
  192. def get_account_info(offline=False):
  193. info=restore_account_data()or{}
  194. info['username']=restore_username()
  195. if not info['username']:
  196. del info['username']
  197. if not offline and "groups" not in info:
  198. result=get_api_result("%s/v%d/account/info"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),params=dict(stoken=restore_session_token(),host_id=get_host_id()))
  199. info.update(result)
  200. store_account_data(info)
  201. return info
  202. @click.group("account",short_help="Manage PIO Account")
  203. def cli():
  204. pass
  205. @cli.command("register",short_help="Create new PIO Account")
  206. @click.option("-u","--username",prompt="E-Mail",callback=cmd_validate_email)
  207. def account_register(username,dummy=None):
  208. result=get_api_result("%s/v%d/account/register"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),data=dict(username=username,host_id=get_host_id(),host_name=get_host_name()))
  209. if "result" in result:
  210. click.secho("Successfully registered! \n" "Please check your E-Mail for the further instructions",fg="green")
  211. @cli.command("login",short_help="Log in to PIO Account")
  212. @click.option("-u","--username",prompt="E-Mail",callback=cmd_validate_email)
  213. @click.option("-p","--password",prompt=True,hide_input=True)
  214. def cmd_account_login(username,password):
  215. return account_login(username,password)
  216. def account_login(username,password):
  217. result=get_api_result("%s/v%d/account/login"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),data=dict(username=username,password=password,host_id=get_host_id(),host_name=get_host_name()))
  218. assert "stoken" in result
  219. store_username(username)
  220. store_session_token(result['stoken'])
  221. click.secho("Successfully authorized!",fg="green")
  222. return True
  223. @cli.command("logout",short_help="Log out of PIO Account")
  224. def account_logout():
  225. result=get_api_result("%s/v%d/account/logout"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),data=dict(stoken=restore_session_token(),host_id=get_host_id()))
  226. delete_session_token()
  227. delete_username()
  228. delete_account_data()
  229. if "result" in result:
  230. click.secho("Successfully unauthorized!",fg="green")
  231. @cli.command("password",short_help="Change password")
  232. @click.option("--old-password",prompt="Old password",hide_input=True)
  233. @click.option("--new-password",prompt="New password",hide_input=True,confirmation_prompt=True)
  234. def account_password(old_password,new_password):
  235. result=get_api_result("%s/v%d/account/password"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),data=dict(stoken=restore_session_token(),host_id=get_host_id(),old_password=old_password,new_password=new_password))
  236. if "result" in result:
  237. click.secho("Successfully updated password!",fg="green")
  238. @cli.command("token",short_help="Get or regenerate Personal Authentication Token")
  239. @click.option("-p","--password",prompt=True,hide_input=True)
  240. @click.option("--regenerate",is_flag=True)
  241. @click.option("--json-output",is_flag=True)
  242. def account_token(password,regenerate,json_output):
  243. result=get_api_result("%s/v%d/account/token"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),data=dict(stoken=restore_session_token(),host_id=get_host_id(),password=password,regenerate=int(regenerate)))
  244. if "token" not in result:
  245. return None
  246. if json_output:
  247. return click.echo(json.dumps({"status":"success","result":result['token']}))
  248. click.echo("Personal Authentication Token: %s"%result['token'])
  249. return True
  250. @cli.command("forgot",short_help="Forgot password")
  251. @click.option("-u","--username",prompt="E-Mail",callback=cmd_validate_email)
  252. def account_forgot(username,dummy=None):
  253. result=get_api_result("%s/v%d/account/forgot"%(config.CLOUD_API_ENDPOINT,config.CLOUD_API_VERSION),data=dict(username=username,host_id=get_host_id(),host_name=get_host_name()))
  254. if "result" in result:
  255. click.secho("If this account is registered, we will send the " "further instructions to your E-Mail.",fg="green")
  256. @cli.command("show",short_help="PIO Account information")
  257. @click.option("--offline",is_flag=True)
  258. @click.option("--json-output",is_flag=True)
  259. def account_show(offline,json_output):
  260. info=get_account_info(offline)
  261. if json_output:
  262. return click.echo(json.dumps(info))
  263. if "username" in info:
  264. click.echo()
  265. click.echo("Logged as: %s"%info['username'])
  266. if "currentPlan" in info:
  267. click.echo("PIO Plus Plan: %s"%info['currentPlan'])
  268. click.echo()
  269. if "groups" in info:
  270. for group in info['groups']:
  271. click.echo("Group %s"%click.style(group['name'],fg="cyan"))
  272. click.echo("-"*(6+len(group['name'])))
  273. if group['expire']:
  274. click.echo("Expire: %s"%datetime.fromtimestamp(int(group['expire'])).strftime("%Y-%m-%d %H:%M:%S"))
  275. else:
  276. click.echo("Expire: -")
  277. click.echo("Permissions: %s"%", ".join(group['permissions']))
  278. click.echo()
  279. if info.get("upgradePlan"):
  280. click.echo("UPGRADE: %s"%click.style("https://pioplus.com/pricing.html",fg="blue"))
  281. click.echo()
  282. return True
  283. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  284.  
  285.  
  286.  
  287. #$cmd_test
  288.  
  289. import atexit
  290. from fnmatch import fnmatch
  291. from os import getcwd,listdir,remove
  292. from os.path import isdir,isfile,join
  293. from string import Template
  294. from time import sleep,time
  295. import click
  296. import serial
  297. from platformio import exception,util
  298. from platformio.commands.run import check_project_envs
  299. from platformio.commands.run import cli as cmd_run
  300. from platformio.commands.run import print_header
  301. from platformio.managers.platform import PlatformFactory
  302. from exception import TestDirNotExists
  303. TRANSPORT_OPTIONS={"arduino":{"include":"#include <Arduino.h>","object":"","putchar":"Serial.write(c)","flush":"Serial.flush()","begin":"Serial.begin($baudrate)","end":"Serial.end()"},"mbed":{"include":"#include <mbed.h>","object":"Serial pc(USBTX, USBRX);","putchar":"pc.putc(c)","flush":"","begin":"pc.baud($baudrate)","end":""},"energia":{"include":"#include <Energia.h>","object":"","putchar":"Serial.write(c)","flush":"Serial.flush()","begin":"Serial.begin($baudrate)","end":"Serial.end()"},"espidf":{"include":"#include <stdio.h>","object":"","putchar":"putchar(c)","flush":"fflush(stdout)","begin":"","end":""},"native":{"include":"#include <stdio.h>","object":"","putchar":"putchar(c)","flush":"fflush(stdout)","begin":"","end":""},"custom":{"include":'#include "unittest_transport.h"',"object":"","putchar":"unittest_uart_putchar(c)","flush":"unittest_uart_flush()","begin":"unittest_uart_begin()","end":"unittest_uart_end()"}}
  304. @click.command("test",short_help="Unit Testing")
  305. @click.option("--environment","-e",multiple=True,metavar="<environment>")
  306. @click.option("--filter","-f",multiple=True,metavar="<pattern>",help="Filter tests by a pattern")
  307. @click.option("--ignore","-i",multiple=True,metavar="<pattern>",help="Ignore tests by a pattern")
  308. @click.option("--upload-port")
  309. @click.option("--test-port")
  310. @click.option("-d","--project-dir",default=getcwd,type=click.Path(exists=True,file_okay=False,dir_okay=True,writable=True,resolve_path=True))
  311. @click.option("--without-building",is_flag=True)
  312. @click.option("--without-uploading",is_flag=True)
  313. @click.option("--without-testing",is_flag=True)
  314. @click.option("--no-reset",is_flag=True)
  315. @click.option("--monitor-rts",default=None,type=click.IntRange(0,1),help="Set initial RTS line state for Serial Monitor")
  316. @click.option("--monitor-dtr",default=None,type=click.IntRange(0,1),help="Set initial DTR line state for Serial Monitor")
  317. @click.option("--verbose","-v",is_flag=True)
  318. @click.pass_context
  319. def cli(ctx,environment,ignore,filter,upload_port,test_port,project_dir,without_building,without_uploading,without_testing,no_reset,monitor_rts,monitor_dtr,verbose):
  320. with util.cd(project_dir):
  321. test_dir=util.get_projecttest_dir()
  322. if not isdir(test_dir):
  323. raise TestDirNotExists(test_dir)
  324. test_names=get_test_names(test_dir)
  325. projectconf=util.load_project_config()
  326. env_default=None
  327. if projectconf.has_option("platformio","env_default"):
  328. env_default=util.parse_conf_multi_values(projectconf.get("platformio","env_default"))
  329. assert check_project_envs(projectconf,environment or env_default)
  330. click.echo("Verbose mode can be enabled via `-v, --verbose` option")
  331. click.echo("Collected %d items"%len(test_names))
  332. start_time=time()
  333. results=[]
  334. for testname in test_names:
  335. for section in projectconf.sections():
  336. if not section.startswith("env:"):
  337. continue
  338. patterns=dict(filter=list(filter),ignore=list(ignore))
  339. for key in patterns:
  340. if projectconf.has_option(section,"test_%s"%key):
  341. patterns[key].extend([p.strip()for p in projectconf.get(section,"test_%s"%key).split(", ")if p.strip()])
  342. envname=section[4:]
  343. skip_conditions=[environment and envname not in environment,not environment and env_default and envname not in env_default,testname!="*" and patterns['filter']and not any([fnmatch(testname,p)for p in patterns['filter']]),testname!="*" and any([fnmatch(testname,p)for p in patterns['ignore']]),]
  344. if any(skip_conditions):
  345. results.append((None,testname,envname))
  346. continue
  347. cls=(NativeTestProcessor if projectconf.get(section,"platform")=="native" else EmbeddedTestProcessor)
  348. tp=cls(ctx,testname,envname,dict(project_config=projectconf,project_dir=project_dir,upload_port=upload_port,test_port=test_port,without_building=without_building,without_uploading=without_uploading,without_testing=without_testing,no_reset=no_reset,monitor_rts=monitor_rts,monitor_dtr=monitor_dtr,verbose=verbose))
  349. results.append((tp.process(),testname,envname))
  350. if without_testing:
  351. return
  352. click.echo()
  353. print_header("[%s]"%click.style("TEST SUMMARY"))
  354. passed=True
  355. for result in results:
  356. status,testname,envname=result
  357. status_str=click.style("PASSED",fg="green")
  358. if status is False:
  359. passed=False
  360. status_str=click.style("FAILED",fg="red")
  361. elif status is None:
  362. status_str=click.style("IGNORED",fg="yellow")
  363. click.echo("test/%s/env:%s\t[%s]"%(click.style(testname,fg="yellow"),click.style(envname,fg="cyan"),status_str),err=status is False)
  364. print_header("[%s] Took %.2f seconds"%((click.style("PASSED",fg="green",bold=True)if passed else click.style("FAILED",fg="red",bold=True)),time()-start_time),is_error=not passed)
  365. if not passed:
  366. raise exception.ReturnErrorCode(1)
  367. class TestProcessorBase(object):
  368. DEFAULT_BAUDRATE=115200
  369. def __init__(self,cmd_ctx,testname,envname,options):
  370. self.cmd_ctx=cmd_ctx
  371. self.cmd_ctx.meta['piotest_processor']=True
  372. self.test_name=testname
  373. self.options=options
  374. self.env_name=envname
  375. self.env_options={k:v for k,v in options['project_config'].items("env:"+envname)}
  376. self._run_failed=False
  377. self._outputcpp_generated=False
  378. def get_transport(self):
  379. transport=self.env_options.get("framework")
  380. if self.env_options.get("platform")=="native":
  381. transport="native"
  382. if "test_transport" in self.env_options:
  383. transport=self.env_options['test_transport']
  384. if transport not in TRANSPORT_OPTIONS:
  385. raise exception.PlatformioException("Unknown Unit Test transport `%s`"%transport)
  386. return transport.lower()
  387. def get_baudrate(self):
  388. return int(self.env_options.get("test_speed",self.DEFAULT_BAUDRATE))
  389. def print_progress(self,text,is_error=False):
  390. click.echo()
  391. print_header("[test/%s] %s"%(click.style(self.test_name,fg="yellow",bold=True),text),is_error=is_error)
  392. def build_or_upload(self,target):
  393. if not self._outputcpp_generated:
  394. self.generate_outputcpp(util.get_projecttest_dir())
  395. self._outputcpp_generated=True
  396. if self.test_name!="*":
  397. self.cmd_ctx.meta['piotest']=self.test_name
  398. if not self.options['verbose']:
  399. click.echo("Please wait...")
  400. return self.cmd_ctx.invoke(cmd_run,project_dir=self.options['project_dir'],upload_port=self.options['upload_port'],silent=not self.options['verbose'],environment=[self.env_name],disable_auto_clean="nobuild" in target,target=target)
  401. def process(self):
  402. raise NotImplementedError
  403. def run(self):
  404. raise NotImplementedError
  405. def on_run_out(self,line):
  406. if line.endswith(":PASS"):
  407. click.echo("%s\t[%s]"%(line[:-5],click.style("PASSED",fg="green")))
  408. elif ":FAIL" in line:
  409. self._run_failed=True
  410. click.echo("%s\t[%s]"%(line,click.style("FAILED",fg="red")))
  411. else:
  412. click.echo(line)
  413. def generate_outputcpp(self,test_dir):
  414. assert isdir(test_dir)
  415. cpp_tpl="\n".join(["$include","#include <output_export.h>","","$object","","void output_start(unsigned int baudrate)","{"," $begin;","}","","void output_char(int c)","{"," $putchar;","}","","void output_flush(void)","{"," $flush;","}","","void output_complete(void)","{"," $end;","}"])
  416. def delete_tmptest_file(file_):
  417. try:
  418. remove(file_)
  419. except:
  420. if isfile(file_):
  421. click.secho("Warning: Could not remove temporary file '%s'. " "Please remove it manually."%file_,fg="yellow")
  422. tpl=Template(cpp_tpl).substitute(TRANSPORT_OPTIONS[self.get_transport()])
  423. data=Template(tpl).substitute(baudrate=self.get_baudrate())
  424. tmp_file=join(test_dir,"output_export.cpp")
  425. with open(tmp_file,"w")as f:
  426. f.write(data)
  427. atexit.register(delete_tmptest_file,tmp_file)
  428. class NativeTestProcessor(TestProcessorBase):
  429. def process(self):
  430. if not self.options['without_building']:
  431. self.print_progress("Building... (1/2)")
  432. self.build_or_upload(["__test"])
  433. if self.options['without_testing']:
  434. return None
  435. self.print_progress("Testing... (2/2)")
  436. return self.run()
  437. def run(self):
  438. with util.cd(self.options['project_dir']):
  439. build_dir=util.get_projectbuild_dir()
  440. result=util.exec_command([join(build_dir,self.env_name,"program")],stdout=util.AsyncPipe(self.on_run_out),stderr=util.AsyncPipe(self.on_run_out))
  441. assert "returncode" in result
  442. return result['returncode']==0 and not self._run_failed
  443. class EmbeddedTestProcessor(TestProcessorBase):
  444. SERIAL_TIMEOUT=600
  445. def process(self):
  446. if not self.options['without_building']:
  447. self.print_progress("Building... (1/3)")
  448. target=["__test"]
  449. if self.options['without_uploading']:
  450. target.append("checkprogsize")
  451. self.build_or_upload(target)
  452. if not self.options['without_uploading']:
  453. self.print_progress("Uploading... (2/3)")
  454. target=["upload"]
  455. if self.options['without_building']:
  456. target.append("nobuild")
  457. else:
  458. target.append("__test")
  459. self.build_or_upload(target)
  460. if self.options['without_testing']:
  461. return None
  462. self.print_progress("Testing... (3/3)")
  463. return self.run()
  464. def run(self):
  465. click.echo("If you don't see any output for the first 10 secs, " "please reset board (press reset button)")
  466. click.echo()
  467. try:
  468. ser=serial.Serial(baudrate=self.get_baudrate(),timeout=self.SERIAL_TIMEOUT)
  469. ser.port=self.get_test_port()
  470. ser.rts=self.options['monitor_rts']
  471. ser.dtr=self.options['monitor_dtr']
  472. ser.open()
  473. except serial.SerialException as e:
  474. click.secho(str(e),fg="red",err=True)
  475. return False
  476. if not self.options['no_reset']:
  477. ser.flushInput()
  478. ser.setDTR(False)
  479. ser.setRTS(False)
  480. sleep(0.1)
  481. ser.setDTR(True)
  482. ser.setRTS(True)
  483. sleep(0.1)
  484. while True:
  485. line=ser.readline().strip()
  486. for i,c in enumerate(line[::-1]):
  487. if ord(c)>127:
  488. line=line[-i:]
  489. break
  490. if not line:
  491. continue
  492. self.on_run_out(line)
  493. if all([l in line for l in("Tests","Failures","Ignored")]):
  494. break
  495. ser.close()
  496. return not self._run_failed
  497. def get_test_port(self):
  498. if self.options.get("test_port"):
  499. return self.options.get("test_port")
  500. elif self.env_options.get("test_port"):
  501. return self.env_options.get("test_port")
  502. assert set(["platform","board"])&set(self.env_options.keys())
  503. p=PlatformFactory.newPlatform(self.env_options['platform'])
  504. board_hwids=p.board_config(self.env_options['board']).get("build.hwids",[])
  505. port=None
  506. elapsed=0
  507. while elapsed<5 and not port:
  508. for item in util.get_serialports():
  509. port=item['port']
  510. for hwid in board_hwids:
  511. hwid_str=("%s:%s"%(hwid[0],hwid[1])).replace("0x","")
  512. if hwid_str in item['hwid']:
  513. return port
  514. try:
  515. serial.Serial(port,timeout=self.SERIAL_TIMEOUT).close()
  516. except serial.SerialException:
  517. port=None
  518. if not port:
  519. sleep(0.25)
  520. elapsed+=0.25
  521. if not port:
  522. raise exception.PlatformioException("Please specify `test_port` for environment or use " "global `--test-port` option.")
  523. return port
  524. def get_test_names(test_dir):
  525. names=[]
  526. for item in sorted(listdir(test_dir)):
  527. if isdir(join(test_dir,item)):
  528. names.append(item)
  529. if not names:
  530. names=["*"]
  531. return names
  532. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  533.  
  534.  
  535.  
  536. #$project_sync
  537.  
  538. import os
  539. import tarfile
  540. from binascii import crc32
  541. from os.path import getmtime,getsize,isdir,isfile,join
  542. from twisted.python import constants
  543. class PROJECT_SYNC_STAGE(constants.Flags):
  544. INIT=constants.FlagConstant()
  545. DBINDEX=constants.FlagConstant()
  546. DELETE=constants.FlagConstant()
  547. UPLOAD=constants.FlagConstant()
  548. EXTRACTED=constants.FlagConstant()
  549. COMPLETED=constants.FlagConstant()
  550. class ProjectSync(object):
  551. def __init__(self,path):
  552. self.path=path
  553. if not isdir(self.path):
  554. os.makedirs(self.path)
  555. self.items=[]
  556. self._db={}
  557. def add_item(self,path,relpath,cb_filter=None):
  558. self.items.append((path,relpath,cb_filter))
  559. def get_items(self):
  560. return self.items
  561. def rebuild_dbindex(self):
  562. self._db={}
  563. for(path,relpath,cb_filter)in self.items:
  564. if cb_filter and not cb_filter(path):
  565. continue
  566. self._insert_to_db(path,relpath)
  567. if isdir(path):
  568. for(root,_,files)in os.walk(path,followlinks=True):
  569. for name in files:
  570. self._insert_to_db(join(root,name),join(relpath,root[len(path)+1:],name))
  571. def _insert_to_db(self,path,relpath):
  572. if not isfile(path):
  573. return
  574. index=crc32("%s-%s-%s"%(relpath,getmtime(path),getsize(path)))
  575. self._db[index]=(path,relpath)
  576. def get_dbindex(self):
  577. return self._db.keys()
  578. def delete_dbindex(self,dbindex):
  579. for index in dbindex:
  580. if index not in self._db:
  581. continue
  582. path=self._db[index][0]
  583. if isfile(path):
  584. os.remove(path)
  585. del self._db[index]
  586. self.delete_empty_folders()
  587. return True
  588. def delete_empty_folders(self):
  589. deleted=False
  590. for item in self.items:
  591. if not isdir(item[0]):
  592. continue
  593. for root,dirs,files in os.walk(item[0]):
  594. if not dirs and not files and root!=item[0]:
  595. deleted=True
  596. os.rmdir(root)
  597. if deleted:
  598. return self.delete_empty_folders()
  599. return True
  600. def compress_items(self,fileobj,dbindex,max_size):
  601. compressed=[]
  602. total_size=0
  603. tar_opts=dict(fileobj=fileobj,mode="w:gz",bufsize=0,dereference=True)
  604. with tarfile.open(**tar_opts)as tgz:
  605. for index in dbindex:
  606. compressed.append(index)
  607. if index not in self._db:
  608. continue
  609. path,relpath=self._db[index]
  610. tgz.add(path,relpath)
  611. total_size+=getsize(path)
  612. if total_size>max_size:
  613. break
  614. return compressed
  615. def decompress_items(self,fileobj):
  616. fileobj.seek(0)
  617. with tarfile.open(fileobj=fileobj,mode="r:gz")as tgz:
  618. tgz.extractall(self.path)
  619. return True
  620. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  621.  
  622.  
  623.  
  624. #$remote_client
  625.  
  626. import json
  627. import os
  628. import zlib
  629. from datetime import datetime
  630. from hashlib import sha1
  631. from io import BytesIO
  632. from os.path import basename,isdir,isfile,join
  633. from time import time
  634. import click
  635. from platformio import exception,maintenance,util
  636. from twisted.cred import credentials
  637. from twisted.internet import defer,protocol,reactor,task
  638. from twisted.logger import ILogObserver,Logger,LogLevel,formatEvent
  639. from twisted.python import failure
  640. from twisted.spread import pb
  641. from zope.interface import provider
  642. import cmd_account
  643. import config
  644. from project_sync import PROJECT_SYNC_STAGE,ProjectSync
  645. class PioAgentNotStartedError(pb.Error):
  646. pass
  647. class RemoteFactory(pb.PBClientFactory,protocol.ReconnectingClientFactory):
  648. def clientConnectionMade(self,broker):
  649. pb.PBClientFactory.clientConnectionMade(self,broker)
  650. protocol.ReconnectingClientFactory.resetDelay(self)
  651. self.remote_client.log.info("Successfully connected")
  652. self.remote_client.log.info("Authenticating")
  653. stoken=cmd_account.restore_session_token()
  654. d=self.login(credentials.UsernamePassword(stoken,cmd_account.get_host_id()),client=self.remote_client)
  655. d.addCallback(self.remote_client.cb_client_authorization_made)
  656. d.addErrback(self.remote_client.cb_client_authorization_failed)
  657. def clientConnectionFailed(self,connector,reason):
  658. self.remote_client.log.warn("Could not connect to PIO Remote Cloud. Reconnecting...")
  659. self.remote_client.cb_disconnected(reason)
  660. protocol.ReconnectingClientFactory.clientConnectionFailed(self,connector,reason)
  661. def clientConnectionLost(self,connector,unused_reason):
  662. if not reactor.running:
  663. self.remote_client.log.info("Successfully disconnected")
  664. return
  665. self.remote_client.log.warn("Connection is lost to PIO Remote Cloud. Reconnecting")
  666. pb.PBClientFactory.clientConnectionLost(self,connector,unused_reason,reconnecting=1)
  667. self.remote_client.cb_disconnected(unused_reason)
  668. protocol.ReconnectingClientFactory.clientConnectionLost(self,connector,unused_reason)
  669. class RemoteClientBase(pb.Referenceable):
  670. def __init__(self):
  671. self.log_level=LogLevel.warn
  672. self.log=Logger(namespace="remote",observer=self._log_observer)
  673. self.id=cmd_account.get_host_id()
  674. self.name=cmd_account.get_host_name()
  675. self.join_options={"cliver":config.__version__}
  676. self.perspective=None
  677. self.agentpool=None
  678. self._ping_id=0
  679. self._ping_caller=None
  680. self._ping_counter=0
  681. self._reactor_stopped=False
  682. self._exit_code=0
  683. @provider(ILogObserver)
  684. def _log_observer(self,event):
  685. if not config.DEBUG and(event['log_namespace']!=self.log.namespace or self.log_level>event['log_level']):
  686. return
  687. msg=formatEvent(event)
  688. click.echo("%s [%s] %s"%(datetime.fromtimestamp(event['log_time']).strftime('%Y-%m-%d %H:%M:%S'),event['log_level'].name,msg))
  689. def connect(self):
  690. self.log.info("Name: {name}",name=self.name)
  691. self.log.info("Connecting to PIO Remote Cloud")
  692. factory=RemoteFactory()
  693. factory.remote_client=self
  694. reactor.connectTCP(config.PIOREMOTE_HOST,config.PIOREMOTE_PORT,factory)
  695. reactor.run()
  696. if self._exit_code!=0:
  697. raise exception.ReturnErrorCode(self._exit_code)
  698. def remote_service(self,command,options):
  699. if command=="disconnect":
  700. self.log.error("PIO Remote Cloud disconnected: {msg}",msg=options.get("message"))
  701. self.disconnect()
  702. elif command=="cliupdate":
  703. self.log.info("Preparing for remote update of PIO Plus CLI")
  704. self.disconnect(13)
  705. def cb_client_authorization_failed(self,err):
  706. msg="Bad account credentials"
  707. if err.check(pb.Error):
  708. msg=err.getErrorMessage()
  709. self.log.error(msg)
  710. self.disconnect(exit_code=1)
  711. def cb_client_authorization_made(self,perspective):
  712. self.log.info("Successfully authorized")
  713. self.perspective=perspective
  714. d=perspective.callRemote("join",self.id,self.name,self.join_options)
  715. d.addCallback(self._cb_client_join_made)
  716. d.addErrback(self.cb_global_error)
  717. def _cb_client_join_made(self,result):
  718. code=result[0]
  719. if code==1:
  720. self.agentpool=result[1]
  721. self.agent_pool_ready()
  722. self.restart_ping()
  723. elif code==2:
  724. self.remote_service(*result[1:])
  725. def restart_ping(self,reset_counter=True):
  726. self.stop_ping(reset_counter)
  727. self._ping_caller=reactor.callLater(config.PIOREMOTE_PING_DELAY,self._do_ping)
  728. def _do_ping(self):
  729. self._ping_counter+=1
  730. self._ping_id=int(time())
  731. d=self.perspective.callRemote("service","ping",{"id":self._ping_id})
  732. d.addCallback(self._cb_pong)
  733. d.addErrback(self._cb_pong)
  734. def stop_ping(self,reset_counter=True):
  735. if reset_counter:
  736. self._ping_counter=0
  737. if not self._ping_caller or not self._ping_caller.active():
  738. return
  739. self._ping_caller.cancel()
  740. self._ping_caller=None
  741. def _cb_pong(self,result):
  742. if not isinstance(result,failure.Failure)and self._ping_id==result:
  743. self.restart_ping()
  744. return
  745. if self._ping_counter>=config.PIOREMOTE_PING_MAX_FAILURES:
  746. self.stop_ping()
  747. self.perspective.broker.transport.loseConnection()
  748. else:
  749. self.restart_ping(reset_counter=False)
  750. def agent_pool_ready(self):
  751. raise NotImplementedError
  752. def disconnect(self,exit_code=None):
  753. self.stop_ping()
  754. if exit_code is not None:
  755. self._exit_code=exit_code
  756. if reactor.running and not self._reactor_stopped:
  757. self._reactor_stopped=True
  758. reactor.stop()
  759. def cb_disconnected(self,_):
  760. self.stop_ping()
  761. self.perspective=None
  762. self.agentpool=None
  763. def cb_global_error(self,err):
  764. if err.check(pb.PBConnectionLost,defer.CancelledError):
  765. return
  766. msg=err.getErrorMessage()
  767. if err.check(pb.DeadReferenceError):
  768. msg="Remote Client has been terminated"
  769. elif "PioAgentNotStartedError" in str(err.type):
  770. msg=("Could not find active agents. Please start it before on " "a remote machine using `pio remote agent start` command.\n" "See http://docs.platformio.org/page/plus/pio-remote.html")
  771. click.secho(msg,fg="red",err=True)
  772. maintenance.on_platformio_exception(err)
  773. self.disconnect(exit_code=1)
  774. class ClientAgentReload(RemoteClientBase):
  775. def __init__(self,agents):
  776. RemoteClientBase.__init__(self)
  777. self.agents=agents
  778. def agent_pool_ready(self):
  779. d=self.agentpool.callRemote("cmd",self.agents,"reload")
  780. d.addCallback(self._cbResult)
  781. d.addErrback(self.cb_global_error)
  782. def _cbResult(self,result):
  783. for(success,value)in result:
  784. if not success:
  785. click.secho(value,fg="red",err=True)
  786. continue
  787. (id_,name,reloaded)=value
  788. click.secho(name,fg="cyan")
  789. click.echo("-"*len(name))
  790. click.echo("ID: %s"%id_)
  791. click.echo("Reloaded: %s"%datetime.fromtimestamp(reloaded).strftime('%Y-%m-%d %H:%M:%S'))
  792. click.echo("")
  793. self.disconnect()
  794. class ClientAgentList(RemoteClientBase):
  795. def agent_pool_ready(self):
  796. d=self.agentpool.callRemote("list",True)
  797. d.addCallback(self._cbResult)
  798. d.addErrback(self.cb_global_error)
  799. def _cbResult(self,result):
  800. for item in result:
  801. click.secho(item['name'],fg="cyan")
  802. click.echo("-"*len(item['name']))
  803. click.echo("ID: %s"%item['id'])
  804. click.echo("Started: %s"%datetime.fromtimestamp(item['started']).strftime('%Y-%m-%d %H:%M:%S'))
  805. click.echo("")
  806. self.disconnect()
  807. class ClientDeviceList(RemoteClientBase):
  808. def __init__(self,agents,json_output):
  809. RemoteClientBase.__init__(self)
  810. self.agents=agents
  811. self.json_output=json_output
  812. def agent_pool_ready(self):
  813. d=self.agentpool.callRemote("cmd",self.agents,"device.list")
  814. d.addCallback(self._cbResult)
  815. d.addErrback(self.cb_global_error)
  816. def _cbResult(self,result):
  817. data={}
  818. for(success,value)in result:
  819. if not success:
  820. click.secho(value,fg="red",err=True)
  821. continue
  822. (agent_name,devlist)=value
  823. data[agent_name]=devlist
  824. if self.json_output:
  825. click.echo(json.dumps(data))
  826. else:
  827. for agent_name,devlist in data.items():
  828. click.echo("Agent %s"%click.style(agent_name,fg="cyan",bold=True))
  829. click.echo("="*(6+len(agent_name)))
  830. for item in devlist:
  831. click.secho(item['port'],fg="cyan")
  832. click.echo("-"*len(item['port']))
  833. click.echo("Hardware ID: %s"%item['hwid'])
  834. click.echo("Description: %s"%item['description'])
  835. click.echo("")
  836. self.disconnect()
  837. class SMBridgeProtocol(protocol.Protocol):
  838. def connectionMade(self):
  839. self.factory.add_client(self)
  840. def connectionLost(self,reason):
  841. self.factory.remove_client(self)
  842. def dataReceived(self,data):
  843. self.factory.send_to_server(data)
  844. class SMBridgeFactory(protocol.ServerFactory):
  845. def __init__(self,cdm):
  846. self.cdm=cdm
  847. self._clients=[]
  848. def buildProtocol(self,addr):
  849. p=SMBridgeProtocol()
  850. p.factory=self
  851. return p
  852. def add_client(self,client):
  853. self.cdm.log.debug("SMBridge: Client connected")
  854. self._clients.append(client)
  855. self.cdm.acread_data()
  856. def remove_client(self,client):
  857. self.cdm.log.debug("SMBridge: Client disconnected")
  858. self._clients.remove(client)
  859. if not self._clients:
  860. self.cdm.client_terminal_stopped()
  861. def has_clients(self):
  862. return len(self._clients)
  863. def send_to_clients(self,data):
  864. if not self._clients:
  865. return None
  866. for client in self._clients:
  867. client.transport.write(data)
  868. return len(data)
  869. def send_to_server(self,data):
  870. self.cdm.acwrite_data(data)
  871. class ClientDeviceMonitor(RemoteClientBase):
  872. MAX_BUFFER_SIZE=1024*1024
  873. def __init__(self,agents,**kwargs):
  874. RemoteClientBase.__init__(self)
  875. self.agents=agents
  876. self.cmd_options=kwargs
  877. self._bridge_factory=SMBridgeFactory(self)
  878. self._agent_id=None
  879. self._ac_id=None
  880. self._d_acread=None
  881. self._d_acwrite=None
  882. self._acwrite_buffer=""
  883. def agent_pool_ready(self):
  884. d=task.deferLater(reactor,1,self.agentpool.callRemote,"cmd",self.agents,"device.list")
  885. d.addCallback(self._cb_device_list)
  886. d.addErrback(self.cb_global_error)
  887. def _cb_device_list(self,result):
  888. devices=[]
  889. hwid_devindexes=[]
  890. for(success,value)in result:
  891. if not success:
  892. click.secho(value,fg="red",err=True)
  893. continue
  894. (agent_name,ports)=value
  895. for port in ports:
  896. if "VID:PID" in port['hwid']:
  897. hwid_devindexes.append(len(devices))
  898. devices.append((agent_name,port))
  899. if len(result)==1 and self.cmd_options['port']:
  900. return self.start_remote_monitor(result[0][1][0])
  901. device=None
  902. if len(hwid_devindexes)==1:
  903. device=devices[hwid_devindexes[0]]
  904. else:
  905. click.echo("Available ports:")
  906. for i,device in enumerate(devices):
  907. click.echo("{index}. {host}{port} \t{description}".format(index=i+1,host=device[0]+":" if len(result)>1 else "",port=device[1]['port'],description=device[1]['description']if device[1]['description']!="n/a" else ""))
  908. device_index=click.prompt("Please choose a port (number in the list above)",type=click.Choice([str(i+1)for i,_ in enumerate(devices)]))
  909. device=devices[int(device_index)-1]
  910. self.start_remote_monitor(device[0],device[1]['port'])
  911. return None
  912. def start_remote_monitor(self,agent,port=None):
  913. options={}
  914. for key in("port","baud","parity","rtscts","xonxoff","rts","dtr"):
  915. options[key]=self.cmd_options[key]
  916. if port:
  917. options['port']=port
  918. click.echo("Starting Serial Monitor on {host}:{port}".format(host=agent,port=options['port']))
  919. d=self.agentpool.callRemote("cmd",[agent],"device.monitor",options)
  920. d.addCallback(self.cb_async_result)
  921. d.addErrback(self.cb_global_error)
  922. def cb_async_result(self,result):
  923. if len(result)!=1:
  924. raise pb.Error("Invalid response from Remote Cloud")
  925. success,value=result[0]
  926. if not success:
  927. raise pb.Error(value)
  928. reconnected=self._agent_id is not None
  929. self._agent_id,self._ac_id=value
  930. if reconnected:
  931. self.acread_data(force=True)
  932. self.acwrite_data("",force=True)
  933. return
  934. port=reactor.listenTCP(0,self._bridge_factory)
  935. address=port.getHost()
  936. self.log.debug("Serial Bridge is started on {address!r}",address=address)
  937. if "sock" in self.cmd_options:
  938. with open(join(self.cmd_options['sock'],"sock"),"w")as fp:
  939. fp.write("socket://localhost:%d"%address.port)
  940. def client_terminal_stopped(self):
  941. try:
  942. d=self.agentpool.callRemote("acclose",self._agent_id,self._ac_id)
  943. d.addCallback(lambda r:self.disconnect())
  944. d.addErrback(self.cb_global_error)
  945. except(AttributeError,pb.DeadReferenceError):
  946. self.disconnect(exit_code=1)
  947. def acread_data(self,force=False):
  948. if force and self._d_acread:
  949. self._d_acread.cancel()
  950. self._d_acread=None
  951. if(self._d_acread and not self._d_acread.called)or not self._bridge_factory.has_clients():
  952. return
  953. try:
  954. self._d_acread=self.agentpool.callRemote("acread",self._agent_id,self._ac_id)
  955. self._d_acread.addCallback(self.cb_acread_result)
  956. self._d_acread.addErrback(self.cb_global_error)
  957. except(AttributeError,pb.DeadReferenceError):
  958. self.disconnect(exit_code=1)
  959. def cb_acread_result(self,result):
  960. if result is None:
  961. self.disconnect(exit_code=1)
  962. else:
  963. self._bridge_factory.send_to_clients(result)
  964. self.acread_data()
  965. def acwrite_data(self,data,force=False):
  966. if force and self._d_acwrite:
  967. self._d_acwrite.cancel()
  968. self._d_acwrite=None
  969. self._acwrite_buffer+=data
  970. if len(self._acwrite_buffer)>self.MAX_BUFFER_SIZE:
  971. self._acwrite_buffer=self._acwrite_buffer[-1*self.MAX_BUFFER_SIZE:]
  972. if(self._d_acwrite and not self._d_acwrite.called)or not self._acwrite_buffer:
  973. return
  974. data=self._acwrite_buffer
  975. self._acwrite_buffer=""
  976. try:
  977. d=self.agentpool.callRemote("acwrite",self._agent_id,self._ac_id,data)
  978. d.addCallback(self.cb_acwrite_result)
  979. d.addErrback(self.cb_global_error)
  980. except(AttributeError,pb.DeadReferenceError):
  981. self.disconnect(exit_code=1)
  982. def cb_acwrite_result(self,result):
  983. assert result>0
  984. if self._acwrite_buffer:
  985. self.acwrite_data("")
  986. class ClientAsyncCmdBase(RemoteClientBase):
  987. def __init__(self,command,agents,options):
  988. RemoteClientBase.__init__(self)
  989. self.command=command
  990. self.agents=agents
  991. self.options=options
  992. self._acs_total=0
  993. self._acs_ended=0
  994. def cb_async_result(self,result):
  995. if self._acs_total==0:
  996. self._acs_total=len(result)
  997. for(success,value)in result:
  998. if not success:
  999. raise pb.Error(value)
  1000. self.acread_data(*value)
  1001. def acread_data(self,agent_id,ac_id,agent_name=None):
  1002. d=self.agentpool.callRemote("acread",agent_id,ac_id)
  1003. d.addCallback(self.cb_acread_result,agent_id,ac_id,agent_name)
  1004. d.addErrback(self.cb_global_error)
  1005. def cb_acread_result(self,result,agent_id,ac_id,agent_name):
  1006. if result is None:
  1007. self.acclose(agent_id,ac_id)
  1008. else:
  1009. if self._acs_total>1 and agent_name:
  1010. click.echo("[%s] "%agent_name,nl=False)
  1011. click.echo(result,nl=False)
  1012. self.acread_data(agent_id,ac_id,agent_name)
  1013. def acclose(self,agent_id,ac_id):
  1014. d=self.agentpool.callRemote("acclose",agent_id,ac_id)
  1015. d.addCallback(self.cb_acclose_result)
  1016. d.addErrback(self.cb_global_error)
  1017. def cb_acclose_result(self,exit_code):
  1018. self._acs_ended+=1
  1019. if self._acs_ended!=self._acs_total:
  1020. return
  1021. self.disconnect(exit_code)
  1022. class ClientUpdate(ClientAsyncCmdBase):
  1023. def agent_pool_ready(self):
  1024. d=self.agentpool.callRemote("cmd",self.agents,self.command,self.options)
  1025. d.addCallback(self.cb_async_result)
  1026. d.addErrback(self.cb_global_error)
  1027. class ClientRunOrTest(ClientAsyncCmdBase):
  1028. MAX_ARCHIVE_SIZE=50*1024*1024
  1029. UPLOAD_CHUNK_SIZE=256*1024
  1030. PSYNC_SRC_EXTS=["c","cpp","S","spp","SPP","sx","s","asm","ASM","h","hpp","ipp","ino","pde","json","properties"]
  1031. PSYNC_SKIP_DIRS=(".git",".svn",".hg","example","examples","test","tests")
  1032. def __init__(self,*args,**kwargs):
  1033. ClientAsyncCmdBase.__init__(self,*args,**kwargs)
  1034. self.project_id=self.generate_project_id(self.options['project_dir'])
  1035. self.psync=ProjectSync(self.options['project_dir'])
  1036. def generate_project_id(self,path):
  1037. id_=sha1(self.id)
  1038. id_.update(path)
  1039. return "%s-%s"%(basename(path),id_.hexdigest())
  1040. def add_project_items(self,psync):
  1041. project_dir=self.options['project_dir']
  1042. with util.cd(project_dir):
  1043. if self.options['force_remote']:
  1044. target_dirs={"lib":util.get_projectlib_dir(),"src":util.get_projectsrc_dir()}
  1045. if(set(["buildfs","uploadfs","uploadfsota"])&set(self.options.get("target",[]))):
  1046. target_dirs['data']=util.get_projectdata_dir()
  1047. for name,path in target_dirs.items():
  1048. if isdir(path):
  1049. psync.add_item(path,name,cb_filter=self._cb_tarfile_filter if name!="data" else None)
  1050. else:
  1051. build_dir=util.get_projectbuild_dir()
  1052. for env_name in os.listdir(build_dir):
  1053. env_dir=join(build_dir,env_name)
  1054. if not isdir(env_dir):
  1055. continue
  1056. for fname in os.listdir(env_dir):
  1057. bin_file=join(env_dir,fname)
  1058. bin_exts=(".elf",".bin",".hex",".eep","program")
  1059. if isfile(bin_file)and fname.endswith(bin_exts):
  1060. psync.add_item(bin_file,join(".pioenvs",env_name,fname))
  1061. if isdir(util.get_projectboards_dir()):
  1062. psync.add_item(util.get_projectboards_dir(),"boards")
  1063. if self.command=="test" and isdir(util.get_projecttest_dir()):
  1064. psync.add_item(util.get_projecttest_dir(),"test")
  1065. psync.add_item(join(project_dir,"platformio.ini"),"platformio.ini")
  1066. def _cb_tarfile_filter(self,path):
  1067. if isdir(path)and basename(path).lower()in self.PSYNC_SKIP_DIRS:
  1068. return None
  1069. if isfile(path)and not self.is_file_with_exts(path,self.PSYNC_SRC_EXTS):
  1070. return None
  1071. return path
  1072. @staticmethod
  1073. def is_file_with_exts(path,exts):
  1074. if path.endswith(tuple(".%s"%e for e in exts)):
  1075. return True
  1076. return False
  1077. def agent_pool_ready(self):
  1078. self.psync_init()
  1079. def psync_init(self):
  1080. self.add_project_items(self.psync)
  1081. d=self.agentpool.callRemote("cmd",self.agents,"psync",dict(id=self.project_id,items=[i[1]for i in self.psync.get_items()]))
  1082. d.addCallback(self.cb_psync_init_result)
  1083. d.addErrback(self.cb_global_error)
  1084. self.psync.rebuild_dbindex()
  1085. def cb_psync_init_result(self,result):
  1086. self._acs_total=len(result)
  1087. for(success,value)in result:
  1088. if not success:
  1089. raise pb.Error(value)
  1090. agent_id,ac_id=value
  1091. try:
  1092. d=self.agentpool.callRemote("acwrite",agent_id,ac_id,dict(stage=PROJECT_SYNC_STAGE.DBINDEX.value))
  1093. d.addCallback(self.cb_psync_dbindex_result,agent_id,ac_id)
  1094. d.addErrback(self.cb_global_error)
  1095. except(AttributeError,pb.DeadReferenceError):
  1096. self.disconnect(exit_code=1)
  1097. def cb_psync_dbindex_result(self,result,agent_id,ac_id):
  1098. result=set(json.loads(zlib.decompress(result)))
  1099. dbindex=set(self.psync.get_dbindex())
  1100. delete=list(result-dbindex)
  1101. delta=list(dbindex-result)
  1102. self.log.debug("PSync: stats, total={total}, delete={delete}, delta={delta}",total=len(dbindex),delete=len(delete),delta=len(delta))
  1103. if not delete and not delta:
  1104. return self.psync_finalize(agent_id,ac_id)
  1105. elif not delete:
  1106. return self.psync_upload(agent_id,ac_id,delta)
  1107. try:
  1108. d=self.agentpool.callRemote("acwrite",agent_id,ac_id,dict(stage=PROJECT_SYNC_STAGE.DELETE.value,dbindex=zlib.compress(json.dumps(delete))))
  1109. d.addCallback(self.cb_psync_delete_result,agent_id,ac_id,delta)
  1110. d.addErrback(self.cb_global_error)
  1111. except(AttributeError,pb.DeadReferenceError):
  1112. self.disconnect(exit_code=1)
  1113. return None
  1114. def cb_psync_delete_result(self,result,agent_id,ac_id,dbindex):
  1115. assert result
  1116. self.psync_upload(agent_id,ac_id,dbindex)
  1117. def psync_upload(self,agent_id,ac_id,dbindex):
  1118. assert dbindex
  1119. fileobj=BytesIO()
  1120. compressed=self.psync.compress_items(fileobj,dbindex,self.MAX_ARCHIVE_SIZE)
  1121. fileobj.seek(0)
  1122. self.log.debug("PSync: upload project, size={size}",size=len(fileobj.getvalue()))
  1123. self.psync_upload_chunk(agent_id,ac_id,list(set(dbindex)-set(compressed)),fileobj)
  1124. def psync_upload_chunk(self,agent_id,ac_id,dbindex,fileobj):
  1125. offset=fileobj.tell()
  1126. total=fileobj.seek(0,os.SEEK_END)
  1127. fileobj.seek(offset)
  1128. chunk=fileobj.read(self.UPLOAD_CHUNK_SIZE)
  1129. assert chunk
  1130. try:
  1131. d=self.agentpool.callRemote("acwrite",agent_id,ac_id,dict(stage=PROJECT_SYNC_STAGE.UPLOAD.value,chunk=chunk,length=len(chunk),total=total))
  1132. d.addCallback(self.cb_psync_upload_chunk_result,agent_id,ac_id,dbindex,fileobj)
  1133. d.addErrback(self.cb_global_error)
  1134. except(AttributeError,pb.DeadReferenceError):
  1135. self.disconnect(exit_code=1)
  1136. def cb_psync_upload_chunk_result(self,result,agent_id,ac_id,dbindex,fileobj):
  1137. result=PROJECT_SYNC_STAGE.lookupByValue(result)
  1138. self.log.debug("PSync: upload chunk result {r}",r=str(result))
  1139. assert result&(PROJECT_SYNC_STAGE.UPLOAD|PROJECT_SYNC_STAGE.EXTRACTED)
  1140. if result is PROJECT_SYNC_STAGE.EXTRACTED:
  1141. if dbindex:
  1142. self.psync_upload(agent_id,ac_id,dbindex)
  1143. else:
  1144. self.psync_finalize(agent_id,ac_id)
  1145. else:
  1146. self.psync_upload_chunk(agent_id,ac_id,dbindex,fileobj)
  1147. def psync_finalize(self,agent_id,ac_id):
  1148. try:
  1149. d=self.agentpool.callRemote("acclose",agent_id,ac_id)
  1150. d.addCallback(self.cb_psync_completed_result,agent_id)
  1151. d.addErrback(self.cb_global_error)
  1152. except(AttributeError,pb.DeadReferenceError):
  1153. self.disconnect(exit_code=1)
  1154. def cb_psync_completed_result(self,result,agent_id):
  1155. assert PROJECT_SYNC_STAGE.lookupByValue(result)
  1156. options=self.options.copy()
  1157. del options['project_dir']
  1158. options['project_id']=self.project_id
  1159. d=self.agentpool.callRemote("cmd",[agent_id],self.command,options)
  1160. d.addCallback(self.cb_async_result)
  1161. d.addErrback(self.cb_global_error)
  1162. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  1163.  
  1164.  
  1165.  
  1166. #$remote_agent
  1167.  
  1168. import json
  1169. import os
  1170. import zlib
  1171. from io import BytesIO
  1172. from os.path import getatime,getmtime,isdir,isfile,join
  1173. from time import time
  1174. from platformio import exception,util
  1175. from twisted.internet import defer,protocol,reactor
  1176. from twisted.internet.serialport import SerialPort
  1177. from twisted.logger import LogLevel
  1178. from twisted.spread import pb
  1179. from project_sync import PROJECT_SYNC_STAGE,ProjectSync
  1180. from remote_client import RemoteClientBase
  1181. class Agent(RemoteClientBase):
  1182. def __init__(self,name,share,working_dir=None):
  1183. RemoteClientBase.__init__(self)
  1184. self.log_level=LogLevel.info
  1185. self.working_dir=working_dir or join(util.get_home_dir(),"remote")
  1186. if not isdir(self.working_dir):
  1187. os.makedirs(self.working_dir)
  1188. if name:
  1189. self.name=str(name)[:50]
  1190. self.join_options.update({"agent":True,"share":[s.lower().strip()[:50]for s in share]})
  1191. self._acs={}
  1192. def agent_pool_ready(self):
  1193. pass
  1194. def cb_disconnected(self,reason):
  1195. for ac in self._acs.values():
  1196. ac.ac_close()
  1197. RemoteClientBase.cb_disconnected(self,reason)
  1198. def remote_acread(self,ac_id):
  1199. self.log.debug("Async Read: {id}",id=ac_id)
  1200. if ac_id not in self._acs:
  1201. raise pb.Error("Invalid Async Identifier")
  1202. return self._acs[ac_id].ac_read()
  1203. def remote_acwrite(self,ac_id,data):
  1204. self.log.debug("Async Write: {id}",id=ac_id)
  1205. if ac_id not in self._acs:
  1206. raise pb.Error("Invalid Async Identifier")
  1207. return self._acs[ac_id].ac_write(data)
  1208. def remote_acclose(self,ac_id):
  1209. self.log.debug("Async Close: {id}",id=ac_id)
  1210. if ac_id not in self._acs:
  1211. raise pb.Error("Invalid Async Identifier")
  1212. return_code=self._acs[ac_id].ac_close()
  1213. del self._acs[ac_id]
  1214. return return_code
  1215. def remote_cmd(self,cmd,options):
  1216. self.log.info("Remote command received: {cmd}",cmd=cmd)
  1217. self.log.debug("Command options: {options!r}",options=options)
  1218. callback="_process_cmd_%s"%cmd.replace(".","_")
  1219. return getattr(self,callback)(options)
  1220. def _defer_async_cmd(self,ac,pass_agent_name=True):
  1221. self._acs[ac.id]=ac
  1222. if pass_agent_name:
  1223. return(self.id,ac.id,self.name)
  1224. return(self.id,ac.id)
  1225. def _process_cmd_reload(self,_):
  1226. reactor.callLater(1,self.disconnect,14)
  1227. return(self.id,self.name,time())
  1228. def _process_cmd_device_list(self,_):
  1229. return(self.name,util.get_serialports())
  1230. def _process_cmd_device_monitor(self,options):
  1231. if not options['port']:
  1232. for item in util.get_serialports():
  1233. if "VID:PID" in item['hwid']:
  1234. options['port']=item['port']
  1235. break
  1236. if options['port']:
  1237. for ac in self._acs.values():
  1238. if(isinstance(ac,AsyncCmdSerial)and ac.options['port']==options['port']):
  1239. self.log.info("Terminate previously opened monitor at {port}",port=options['port'])
  1240. ac.ac_close()
  1241. del self._acs[ac.id]
  1242. if not options['port']:
  1243. raise pb.Error("Please specify serial port using `--port` option")
  1244. self.log.info("Starting serial monitor at {port}",port=options['port'])
  1245. return self._defer_async_cmd(AsyncCmdSerial(options),pass_agent_name=False)
  1246. def _process_cmd_psync(self,options):
  1247. for ac in self._acs.values():
  1248. if(isinstance(ac,AsyncCmdPSync)and ac.options['id']==options['id']):
  1249. self.log.info("Terminate previous Project Sync process")
  1250. ac.ac_close()
  1251. del self._acs[ac.id]
  1252. options['agent_working_dir']=self.working_dir
  1253. return self._defer_async_cmd(AsyncCmdPSync(options),pass_agent_name=False)
  1254. def _process_cmd_run(self,options):
  1255. return self._process_cmd_run_or_test("run",options)
  1256. def _process_cmd_test(self,options):
  1257. return self._process_cmd_run_or_test("test",options)
  1258. def _process_cmd_run_or_test(self,command,options):
  1259. assert options and "project_id" in options
  1260. project_dir=join(self.working_dir,"projects",options['project_id'])
  1261. origin_pio_ini=join(project_dir,"platformio.ini")
  1262. back_pio_ini=join(project_dir,"platformio.ini.bak")
  1263. try:
  1264. conf=util.load_project_config(project_dir)
  1265. if isfile(back_pio_ini):
  1266. os.remove(back_pio_ini)
  1267. os.rename(origin_pio_ini,back_pio_ini)
  1268. if conf.has_section("platformio"):
  1269. for opt in conf.options("platformio"):
  1270. if opt.endswith("_dir"):
  1271. conf.remove_option("platformio",opt)
  1272. with open(origin_pio_ini,"w")as fp:
  1273. conf.write(fp)
  1274. os.utime(origin_pio_ini,(getatime(back_pio_ini),getmtime(back_pio_ini)))
  1275. except exception.NotPlatformIOProject()as e:
  1276. raise pb.Error(str(e))
  1277. cmd_args=["platformio","--force",command,"-d",project_dir]
  1278. for env in options.get("environment",[]):
  1279. cmd_args.extend(["-e",env])
  1280. for target in options.get("target",[]):
  1281. cmd_args.extend(["-t",target])
  1282. for ignore in options.get("ignore",[]):
  1283. cmd_args.extend(["-i",ignore])
  1284. if options.get("upload_port",False):
  1285. cmd_args.extend(["--upload-port",options.get("upload_port")])
  1286. if options.get("test_port",False):
  1287. cmd_args.extend(["--test-port",options.get("test_port")])
  1288. if options.get("disable_auto_clean",False):
  1289. cmd_args.append("--disable-auto-clean")
  1290. if options.get("without_building",False):
  1291. cmd_args.append("--without-building")
  1292. if options.get("without_uploading",False):
  1293. cmd_args.append("--without-uploading")
  1294. if options.get("silent",False):
  1295. cmd_args.append("-s")
  1296. if options.get("verbose",False):
  1297. cmd_args.append("-v")
  1298. paused_acs=[]
  1299. for ac in self._acs.values():
  1300. if not isinstance(ac,AsyncCmdSerial):
  1301. continue
  1302. self.log.info("Pause active monitor at {port}",port=ac.options['port'])
  1303. ac.pause()
  1304. paused_acs.append(ac)
  1305. def _cb_on_end():
  1306. if isfile(back_pio_ini):
  1307. if isfile(origin_pio_ini):
  1308. os.remove(origin_pio_ini)
  1309. os.rename(back_pio_ini,origin_pio_ini)
  1310. for ac in paused_acs:
  1311. ac.unpause()
  1312. self.log.info("Unpause active monitor at {port}",port=ac.options['port'])
  1313. return self._defer_async_cmd(AsyncCmdProcess({"executable":util.where_is_program("platformio"),"args":cmd_args},on_end_callback=_cb_on_end))
  1314. def _process_cmd_update(self,options):
  1315. cmd_args=["platformio","--force","update"]
  1316. if options.get("only_check"):
  1317. cmd_args.append("--only-check")
  1318. return self._defer_async_cmd(AsyncCmdProcess({"executable":util.where_is_program("platformio"),"args":cmd_args}))
  1319. class AsyncCommandBase(object):
  1320. MAX_BUFFER_SIZE=1024*1024
  1321. def __init__(self,options=None,on_end_callback=None):
  1322. self.options=options or{}
  1323. self.on_end_callback=on_end_callback
  1324. self._buffer=""
  1325. self._return_code=None
  1326. self._d=None
  1327. self._paused=False
  1328. try:
  1329. self.start()
  1330. except Exception as e:
  1331. raise pb.Error(str(e))
  1332. @property
  1333. def id(self):
  1334. return id(self)
  1335. def pause(self):
  1336. self._paused=True
  1337. self.stop()
  1338. def unpause(self):
  1339. self._paused=False
  1340. self.start()
  1341. def start(self):
  1342. raise NotImplementedError
  1343. def stop(self):
  1344. self.transport.loseConnection()
  1345. def _ac_ended(self):
  1346. if self.on_end_callback:
  1347. self.on_end_callback()
  1348. if not self._d or self._d.called:
  1349. self._d=None
  1350. return
  1351. if self._buffer:
  1352. self._d.callback(self._buffer)
  1353. else:
  1354. self._d.callback(None)
  1355. def _ac_ondata(self,data):
  1356. self._buffer+=data
  1357. if len(self._buffer)>self.MAX_BUFFER_SIZE:
  1358. self._buffer=self._buffer[-1*self.MAX_BUFFER_SIZE:]
  1359. if self._paused:
  1360. return
  1361. if self._d and not self._d.called:
  1362. self._d.callback(self._buffer)
  1363. self._buffer=""
  1364. def ac_read(self):
  1365. if self._buffer:
  1366. result=self._buffer
  1367. self._buffer=""
  1368. return result
  1369. elif self._return_code is None:
  1370. self._d=defer.Deferred()
  1371. return self._d
  1372. return None
  1373. def ac_write(self,data):
  1374. self.transport.write(data)
  1375. return len(data)
  1376. def ac_close(self):
  1377. self.stop()
  1378. return self._return_code
  1379. class AsyncCmdProcess(protocol.ProcessProtocol,AsyncCommandBase):
  1380. def start(self):
  1381. env=dict(os.environ).copy()
  1382. env.update({'PLATFORMIO_FORCE_COLOR':'true'})
  1383. reactor.spawnProcess(self,self.options['executable'],self.options['args'],env)
  1384. def outReceived(self,data):
  1385. self._ac_ondata(data)
  1386. def errReceived(self,data):
  1387. self._ac_ondata(data)
  1388. def processExited(self,reason):
  1389. self._return_code=reason.value.exitCode
  1390. def processEnded(self,reason):
  1391. if self._return_code is None:
  1392. self._return_code=reason.value.exitCode
  1393. self._ac_ended()
  1394. class AsyncCmdSerial(protocol.Protocol,AsyncCommandBase):
  1395. def start(self):
  1396. SerialPort(self,reactor=reactor,**{"deviceNameOrPortNumber":self.options['port'],"baudrate":self.options['baud'],"parity":self.options['parity'],"rtscts":1 if self.options['rtscts']else 0,"xonxoff":1 if self.options['xonxoff']else 0})
  1397. def connectionMade(self):
  1398. self.reset_device()
  1399. if self.options.get("rts",None)is not None:
  1400. self.transport.setRTS(self.options.get("rts"))
  1401. if self.options.get("dtr",None)is not None:
  1402. self.transport.setDTR(self.options.get("dtr"))
  1403. def reset_device(self):
  1404. from time import sleep
  1405. self.transport.flushInput()
  1406. self.transport.setDTR(False)
  1407. self.transport.setRTS(False)
  1408. sleep(0.1)
  1409. self.transport.setDTR(True)
  1410. self.transport.setRTS(True)
  1411. sleep(0.1)
  1412. def dataReceived(self,data):
  1413. self._ac_ondata(data)
  1414. def connectionLost(self,reason):
  1415. if self._paused:
  1416. return
  1417. self._return_code=0
  1418. self._ac_ended()
  1419. class AsyncCmdPSync(AsyncCommandBase):
  1420. def __init__(self,*args,**kwargs):
  1421. self.psync=None
  1422. self._upstream=None
  1423. AsyncCommandBase.__init__(self,*args,**kwargs)
  1424. def start(self):
  1425. project_dir=join(self.options['agent_working_dir'],"projects",self.options['id'])
  1426. self.psync=ProjectSync(project_dir)
  1427. for name in self.options['items']:
  1428. self.psync.add_item(join(project_dir,name),name)
  1429. def stop(self):
  1430. self.psync=None
  1431. self._upstream=None
  1432. self._return_code=PROJECT_SYNC_STAGE.COMPLETED.value
  1433. def ac_write(self,data):
  1434. stage=PROJECT_SYNC_STAGE.lookupByValue(data.get("stage"))
  1435. if stage is PROJECT_SYNC_STAGE.DBINDEX:
  1436. self.psync.rebuild_dbindex()
  1437. return zlib.compress(json.dumps(self.psync.get_dbindex()))
  1438. elif stage is PROJECT_SYNC_STAGE.DELETE:
  1439. return self.psync.delete_dbindex(json.loads(zlib.decompress(data['dbindex'])))
  1440. elif stage is PROJECT_SYNC_STAGE.UPLOAD:
  1441. if not self._upstream:
  1442. self._upstream=BytesIO()
  1443. self._upstream.write(data['chunk'])
  1444. if self._upstream.tell()==data['total']:
  1445. self.psync.decompress_items(self._upstream)
  1446. self._upstream=None
  1447. return PROJECT_SYNC_STAGE.EXTRACTED.value
  1448. return PROJECT_SYNC_STAGE.UPLOAD.value
  1449. return None
  1450. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  1451.  
  1452.  
  1453.  
  1454. #$cmd_remote
  1455.  
  1456. import os
  1457. import click
  1458. from platformio.commands.run import cli as cmd_run
  1459. import cmd_test
  1460. import remote_agent
  1461. import remote_client
  1462. @click.group(short_help="PIO Remote")
  1463. @click.option("-a","--agent",multiple=True)
  1464. @click.pass_context
  1465. def cli(ctx,agent):
  1466. ctx.obj=agent
  1467. @cli.group("agent",short_help="Start new agent or list started")
  1468. def cmd_remote_agent():
  1469. pass
  1470. @cmd_remote_agent.command("start",short_help="Start agent")
  1471. @click.option("-n","--name")
  1472. @click.option("-s","--share",multiple=True,metavar="E-MAIL")
  1473. @click.option("-d","--working-dir",envvar="PLATFORMIO_REMOTE_AGENT_DIR",type=click.Path(file_okay=False,dir_okay=True,writable=True,resolve_path=True))
  1474. def cmd_remote_agent_start(name,share,working_dir):
  1475. remote_agent.Agent(name,share,working_dir).connect()
  1476. @cmd_remote_agent.command("reload",short_help="Reload agents")
  1477. @click.pass_obj
  1478. def cmd_remote_agent_reload(agents):
  1479. remote_client.ClientAgentReload(agents).connect()
  1480. @cmd_remote_agent.command("list",short_help="List active agents")
  1481. def cmd_remote_agent_list():
  1482. remote_client.ClientAgentList().connect()
  1483. @cli.command("update",short_help="Update installed Platforms, Packages and Libraries")
  1484. @click.option("-c","--only-check",is_flag=True,help="Do not update, only check for new version")
  1485. @click.pass_obj
  1486. def cmd_remote_update(agents,only_check):
  1487. remote_client.ClientUpdate("update",agents,dict(only_check=only_check)).connect()
  1488. @cli.command("run",short_help="Process project environments remotely")
  1489. @click.option("-e","--environment",multiple=True)
  1490. @click.option("-t","--target",multiple=True)
  1491. @click.option("--upload-port")
  1492. @click.option("-d","--project-dir",default=os.getcwd,type=click.Path(exists=True,file_okay=True,dir_okay=True,writable=True,resolve_path=True))
  1493. @click.option("--disable-auto-clean",is_flag=True)
  1494. @click.option("-r","--force-remote",is_flag=True)
  1495. @click.option("-s","--silent",is_flag=True)
  1496. @click.option("-v","--verbose",is_flag=True)
  1497. @click.pass_obj
  1498. @click.pass_context
  1499. def cmd_remote_run(ctx,agents,environment,target,upload_port,project_dir,disable_auto_clean,force_remote,silent,verbose):
  1500. cr=remote_client.ClientRunOrTest("run",agents,dict(environment=environment,target=target,upload_port=upload_port,project_dir=project_dir,disable_auto_clean=disable_auto_clean,force_remote=force_remote,silent=silent,verbose=verbose))
  1501. if force_remote:
  1502. return cr.connect()
  1503. click.secho("Building project locally",bold=True)
  1504. local_targets=[]
  1505. if "clean" in target:
  1506. local_targets=["clean"]
  1507. elif set(["buildfs","uploadfs","uploadfsota"])&set(target):
  1508. local_targets=["buildfs"]
  1509. else:
  1510. local_targets=["checkprogsize","buildprog"]
  1511. ctx.invoke(cmd_run,environment=environment,target=local_targets,project_dir=project_dir,silent=silent,verbose=verbose)
  1512. if any(["upload" in t for t in target]+["program" in target]):
  1513. click.secho("Uploading firmware remotely",bold=True)
  1514. cr.options['target']+=("nobuild",)
  1515. cr.options['disable_auto_clean']=True
  1516. cr.connect()
  1517. return True
  1518. @cli.command("test",short_help="Remote Unit Testing")
  1519. @click.option("--environment","-e",multiple=True,metavar="<environment>")
  1520. @click.option("--ignore","-i",multiple=True,metavar="<pattern>")
  1521. @click.option("--upload-port")
  1522. @click.option("--test-port")
  1523. @click.option("-d","--project-dir",default=os.getcwd,type=click.Path(exists=True,file_okay=False,dir_okay=True,writable=True,resolve_path=True))
  1524. @click.option("-r","--force-remote",is_flag=True)
  1525. @click.option("--without-building",is_flag=True)
  1526. @click.option("--without-uploading",is_flag=True)
  1527. @click.option("--verbose","-v",is_flag=True)
  1528. @click.pass_obj
  1529. @click.pass_context
  1530. def cmd_remote_test(ctx,agents,environment,ignore,upload_port,test_port,project_dir,force_remote,without_building,without_uploading,verbose):
  1531. cr=remote_client.ClientRunOrTest("test",agents,dict(environment=environment,ignore=ignore,upload_port=upload_port,test_port=test_port,project_dir=project_dir,force_remote=force_remote,without_building=without_building,without_uploading=without_uploading,verbose=verbose))
  1532. if force_remote:
  1533. return cr.connect()
  1534. click.secho("Building project locally",bold=True)
  1535. ctx.invoke(cmd_test.cli,environment=environment,ignore=ignore,project_dir=project_dir,without_uploading=True,without_testing=True,verbose=verbose)
  1536. click.secho("Testing project remotely",bold=True)
  1537. cr.options['without_building']=True
  1538. cr.connect()
  1539. return True
  1540. @cli.group("device",short_help="Monitor remote device or list existing")
  1541. def cmd_remote_device():
  1542. pass
  1543. @cmd_remote_device.command("list",short_help="List remote devices")
  1544. @click.option("--json-output",is_flag=True)
  1545. @click.pass_obj
  1546. def cmd_device_list(agents,json_output):
  1547. remote_client.ClientDeviceList(agents,json_output).connect()
  1548. @cmd_remote_device.command("monitor",short_help="Monitor remote device")
  1549. @click.option("--port","-p",help="Port, a number or a device name")
  1550. @click.option("--baud","-b",type=int,default=9600,help="Set baud rate, default=9600")
  1551. @click.option("--parity",default="N",type=click.Choice(["N","E","O","S","M"]),help="Set parity, default=N")
  1552. @click.option("--rtscts",is_flag=True,help="Enable RTS/CTS flow control, default=Off")
  1553. @click.option("--xonxoff",is_flag=True,help="Enable software flow control, default=Off")
  1554. @click.option("--rts",default=None,type=click.IntRange(0,1),help="Set initial RTS line state")
  1555. @click.option("--dtr",default=None,type=click.IntRange(0,1),help="Set initial DTR line state")
  1556. @click.option("--echo",is_flag=True,help="Enable local echo, default=Off")
  1557. @click.option("--encoding",default="UTF-8",help="Set the encoding for the serial port (e.g. hexlify, " "Latin1, UTF-8), default: UTF-8")
  1558. @click.option("--filter","-f",multiple=True,help="Add text transformation")
  1559. @click.option("--eol",default="CRLF",type=click.Choice(["CR","LF","CRLF"]),help="End of line mode, default=CRLF")
  1560. @click.option("--raw",is_flag=True,help="Do not apply any encodings/transformations")
  1561. @click.option("--exit-char",type=int,default=3,help="ASCII code of special character that is used to exit " "the application, default=3 (Ctrl+C)")
  1562. @click.option("--menu-char",type=int,default=20,help="ASCII code of special character that is used to " "control miniterm (menu), default=20 (DEC)")
  1563. @click.option("--quiet",is_flag=True,help="Diagnostics: suppress non-error messages, default=Off")
  1564. @click.option("--sock",type=click.Path(exists=True,file_okay=False,dir_okay=True,writable=True,resolve_path=True))
  1565. @click.pass_obj
  1566. def cmd_device_monitor(agents,**kwargs):
  1567. remote_client.ClientDeviceMonitor(agents,**kwargs).connect()
  1568. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  1569.  
  1570.  
  1571.  
  1572. #$cmd_debug
  1573.  
  1574. import json
  1575. import os
  1576. import re
  1577. import sys
  1578. import time
  1579. from contextlib import contextmanager
  1580. from fnmatch import fnmatch
  1581. from hashlib import sha1
  1582. from io import BytesIO
  1583. from os.path import abspath,basename,dirname,isdir,isfile,join,splitext
  1584. from tempfile import mkdtemp
  1585. import click
  1586. from platformio import VERSION,app,exception,util
  1587. from platformio.commands.platform import platform_install as cmd_platform_install
  1588. from platformio.commands.run import cli as cmd_run
  1589. from platformio.managers.platform import PlatformFactory
  1590. from platformio.telemetry import MeasurementProtocol
  1591. from twisted.internet import protocol,reactor,stdio,task
  1592. LOG_FILE=None
  1593. class GDBBytesIO(BytesIO):
  1594. STDOUT=sys.stdout
  1595. def write(self,text):
  1596. for line in text.strip().split("\n"):
  1597. self.STDOUT.write('~"%s\\n"\n'%line)
  1598. self.STDOUT.flush()
  1599. def is_mi_mode(args):
  1600. return "--interpreter" in " ".join(args)
  1601. @click.command("debug",context_settings=dict(ignore_unknown_options=True),short_help="PIO Unified Debugger")
  1602. @click.option("-d","--project-dir",default=os.getcwd,type=click.Path(exists=True,file_okay=False,dir_okay=True,writable=True,resolve_path=True))
  1603. @click.option("--environment","-e",metavar="<environment>")
  1604. @click.option("--verbose","-v",is_flag=True)
  1605. @click.option("--interface",type=click.Choice(["gdb"]))
  1606. @click.argument("__unprocessed",nargs=-1,type=click.UNPROCESSED)
  1607. @click.pass_context
  1608. def cli(ctx,project_dir,environment,verbose,interface,__unprocessed):
  1609. try:
  1610. util.ensure_udev_rules()
  1611. except NameError:
  1612. pass
  1613. except exception.InvalidUdevRules as e:
  1614. for line in str(e).split("\n")+[""]:
  1615. click.echo(('~"%s\\n"' if is_mi_mode(__unprocessed)else "%s")%line)
  1616. if not util.is_platformio_project(project_dir)and os.getenv("CWD"):
  1617. project_dir=os.getenv("CWD")
  1618. with util.cd(project_dir):
  1619. env_name=check_env_name(project_dir,environment)
  1620. env_options=get_env_options(project_dir,env_name)
  1621. if not set(env_options.keys())>=set(["platform","board"]):
  1622. raise exception.ProjectEnvsNotAvailable()
  1623. debug_options=validate_debug_options(ctx,env_options)
  1624. assert debug_options
  1625. if not interface:
  1626. return predebug_project(ctx,project_dir,env_name,False,verbose)
  1627. configuration=load_configuration(ctx,project_dir,env_name)
  1628. if not configuration:
  1629. raise exception.DebugInvalidOptions("Could not load debug configuration")
  1630. if "--version" in __unprocessed:
  1631. result=util.exec_command([configuration['gdb_path'],"--version"])
  1632. if result['returncode']==0:
  1633. return click.echo(result['out'])
  1634. raise exception.PlatformioException("\n".join([result['out'],result['err']]))
  1635. debug_options['load_cmd']=configure_esp32_load_cmd(debug_options,configuration)
  1636. rebuild_prog=False
  1637. preload=debug_options['load_cmd']=="preload"
  1638. load_mode=debug_options['load_mode']
  1639. if load_mode=="always":
  1640. rebuild_prog=(preload or not has_debug_symbols(configuration['prog_path']))
  1641. elif load_mode=="modified":
  1642. rebuild_prog=(is_prog_obsolete(configuration['prog_path'])or not has_debug_symbols(configuration['prog_path']))
  1643. else:
  1644. rebuild_prog=not isfile(configuration['prog_path'])
  1645. if preload or(not rebuild_prog and load_mode!="always"):
  1646. debug_options['load_cmd']=None
  1647. if rebuild_prog:
  1648. if is_mi_mode(__unprocessed):
  1649. output=GDBBytesIO()
  1650. click.echo('~"Preparing firmware for debugging...\\n"')
  1651. with capture_std_streams(output):
  1652. predebug_project(ctx,project_dir,env_name,preload,verbose)
  1653. output.close()
  1654. else:
  1655. click.echo("Preparing firmware for debugging...")
  1656. predebug_project(ctx,project_dir,env_name,preload,verbose)
  1657. if load_mode=="modified":
  1658. is_prog_obsolete(configuration['prog_path'])
  1659. if not isfile(configuration['prog_path']):
  1660. raise exception.DebugInvalidOptions("Program/firmware is missed")
  1661. client=GDBClient(project_dir,__unprocessed,debug_options,env_options)
  1662. client.spawn(configuration['gdb_path'],configuration['prog_path'])
  1663. reactor.run()
  1664. return True
  1665. def escape_path(path):
  1666. return path.replace("\\","/")
  1667. @contextmanager
  1668. def capture_std_streams(stdout,stderr=None):
  1669. _stdout=sys.stdout
  1670. _stderr=sys.stderr
  1671. sys.stdout=stdout
  1672. sys.stderr=stderr or stdout
  1673. yield
  1674. sys.stdout=_stdout
  1675. sys.stderr=_stderr
  1676. def is_prog_obsolete(prog_path):
  1677. prog_hash_path=prog_path+".sha1"
  1678. if not isfile(prog_path):
  1679. return True
  1680. shasum=sha1()
  1681. with open(prog_path,"rb")as fp:
  1682. while True:
  1683. data=fp.read(1024)
  1684. if not data:
  1685. break
  1686. shasum.update(data)
  1687. new_digest=shasum.hexdigest()
  1688. old_digest=None
  1689. if isfile(prog_hash_path):
  1690. with open(prog_hash_path,"r")as fp:
  1691. old_digest=fp.read()
  1692. if new_digest==old_digest:
  1693. return False
  1694. with open(prog_hash_path,"w")as fp:
  1695. fp.write(new_digest)
  1696. return True
  1697. def has_debug_symbols(prog_path):
  1698. if not isfile(prog_path):
  1699. return False
  1700. matched={".debug_info":False,".debug_abbrev":False," -Og":False," -g":False,"__PLATFORMIO_DEBUG__":(3,6)>VERSION[:2]}
  1701. with open(prog_path,"rb")as fp:
  1702. last_data=""
  1703. while True:
  1704. data=fp.read(1024)
  1705. if not data:
  1706. break
  1707. for pattern,found in matched.items():
  1708. if found:
  1709. continue
  1710. if pattern in last_data+data:
  1711. matched[pattern]=True
  1712. last_data=data
  1713. return all(matched.values())
  1714. def predebug_project(ctx,project_dir,env_name,preload,verbose):
  1715. ctx.invoke(cmd_run,project_dir=project_dir,environment=[env_name],target=["__debug"]+(["upload"]if preload else[]),verbose=verbose)
  1716. if preload:
  1717. time.sleep(5)
  1718. def get_env_options(project_dir,environment):
  1719. config=util.load_project_config(project_dir)
  1720. options={}
  1721. for k,v in config.items("env:%s"%environment):
  1722. options[k]=v
  1723. return options
  1724. def check_env_name(project_dir,environment):
  1725. config=util.load_project_config(project_dir)
  1726. envs=[]
  1727. for section in config.sections():
  1728. if section.startswith("env:"):
  1729. envs.append(section[4:])
  1730. if not envs:
  1731. raise exception.ProjectEnvsNotAvailable()
  1732. if not environment and config.has_option("platformio","env_default"):
  1733. environment=config.get("platformio","env_default").split(", ")[0]
  1734. if environment:
  1735. if environment in envs:
  1736. return environment
  1737. raise exception.UnknownEnvNames(environment,envs)
  1738. return envs[0]
  1739. def validate_debug_options(cmd_ctx,env_options):
  1740. def _cleanup_cmds(cmds):
  1741. if not cmds:
  1742. return[]
  1743. if not isinstance(cmds,list):
  1744. cmds=cmds.split("\n")
  1745. return[c.strip()for c in cmds if c.strip()]
  1746. try:
  1747. platform=PlatformFactory.newPlatform(env_options['platform'])
  1748. except exception.UnknownPlatform:
  1749. cmd_ctx.invoke(cmd_platform_install,platforms=[env_options['platform']],skip_default_package=True)
  1750. platform=PlatformFactory.newPlatform(env_options['platform'])
  1751. board_config=platform.board_config(env_options['board'])
  1752. tool_name=board_config.get_debug_tool_name(env_options.get("debug_tool"))
  1753. tool_settings=board_config.get("debug",{}).get("tools",{}).get(tool_name,{})
  1754. server_options=None
  1755. if isinstance(tool_settings.get("server",{}),list):
  1756. for item in tool_settings['server'][:]:
  1757. tool_settings['server']=item
  1758. if util.get_systype()in item.get("system",[]):
  1759. break
  1760. if env_options.get("debug_server"):
  1761. server_options={"cwd":None,"executable":None,"arguments":env_options.get("debug_server")}
  1762. if not isinstance(server_options['arguments'],list):
  1763. server_options['arguments']=server_options['arguments'].split("\n")
  1764. server_options['arguments']=[arg.strip()for arg in server_options['arguments']if arg.strip()]
  1765. server_options['executable']=server_options['arguments'][0]
  1766. server_options['arguments']=server_options['arguments'][1:]
  1767. elif "server" in tool_settings:
  1768. server_package=tool_settings['server'].get("package")
  1769. server_package_dir=platform.get_package_dir(server_package)if server_package else None
  1770. if server_package and not server_package_dir:
  1771. platform.install_packages(with_packages=[server_package],skip_default_package=True,silent=True)
  1772. server_package_dir=platform.get_package_dir(server_package)
  1773. server_options=dict(cwd=server_package_dir if server_package else None,executable=tool_settings['server'].get("executable"),arguments=[a.replace("$PACKAGE_DIR",escape_path(server_package_dir))if server_package_dir else a for a in tool_settings['server'].get("arguments",[])])
  1774. extra_cmds=_cleanup_cmds(env_options.get("debug_extra_cmds"))
  1775. extra_cmds.extend(_cleanup_cmds(tool_settings.get("extra_cmds")))
  1776. result=dict(tool=tool_name,upload_protocol=env_options.get("upload_protocol",board_config.get("upload",{}).get("protocol")),load_cmd=env_options.get("debug_load_cmd",tool_settings.get("load_cmd","load")),load_mode=env_options.get("debug_load_mode",tool_settings.get("load_mode","always")),init_break=env_options.get("debug_init_break",tool_settings.get("init_break","tbreak main")),init_cmds=_cleanup_cmds(env_options.get("debug_init_cmds",tool_settings.get("init_cmds"))),extra_cmds=extra_cmds,require_debug_port=tool_settings.get("require_debug_port",False),port=reveal_debug_port(env_options.get("debug_port",tool_settings.get("port")),tool_name,tool_settings),server=server_options)
  1777. return result
  1778. def reveal_debug_port(env_debug_port,tool_name,tool_settings):
  1779. def _get_pattern():
  1780. if not env_debug_port:
  1781. return None
  1782. if set(["*","?","[","]"])&set(env_debug_port):
  1783. return env_debug_port
  1784. return None
  1785. def _is_match_pattern(port):
  1786. pattern=_get_pattern()
  1787. if not pattern:
  1788. return True
  1789. return fnmatch(port,pattern)
  1790. def _look_for_serial_port(hwids):
  1791. for item in util.get_serialports(filter_hwid=True):
  1792. if not _is_match_pattern(item['port']):
  1793. continue
  1794. port=item['port']
  1795. if tool_name.startswith("blackmagic"):
  1796. if "windows" in util.get_systype()and port.startswith("COM")and len(port)>4:
  1797. port="\\\\.\\%s"%port
  1798. if "GDB" in item['description']:
  1799. return port
  1800. for hwid in hwids:
  1801. hwid_str=("%s:%s"%(hwid[0],hwid[1])).replace("0x","")
  1802. if hwid_str in item['hwid']:
  1803. return port
  1804. return None
  1805. if env_debug_port and not _get_pattern():
  1806. return env_debug_port
  1807. if not tool_settings.get("require_debug_port"):
  1808. return None
  1809. debug_port=_look_for_serial_port(tool_settings.get("hwids",[]))
  1810. if not debug_port:
  1811. raise exception.DebugInvalidOptions("Please specify `debug_port` for environment")
  1812. return debug_port
  1813. def load_configuration(ctx,project_dir,env_name):
  1814. output=BytesIO()
  1815. with capture_std_streams(output):
  1816. ctx.invoke(cmd_run,project_dir=project_dir,environment=[env_name],target=["idedata"])
  1817. result=output.getvalue()
  1818. output.close()
  1819. if '"includes":' not in result:
  1820. return None
  1821. for line in result.split("\n"):
  1822. line=line.strip()
  1823. if line.startswith('{"')and "cxx_path" in line:
  1824. return json.loads(line[:line.rindex("}")+1])
  1825. return None
  1826. def configure_esp32_load_cmd(debug_options,configuration):
  1827. ignore_conds=[debug_options['load_cmd']!="load","xtensa-esp32" not in configuration.get("cc_path",""),not configuration.get("flash_extra_images"),not all([isfile(item['path'])for item in configuration.get("flash_extra_images")])]
  1828. if any(ignore_conds):
  1829. return debug_options['load_cmd']
  1830. mon_cmds=['monitor program_esp32 "{{{path}}}" {offset} verify'.format(path=escape_path(item['path']),offset=item['offset'])for item in configuration.get("flash_extra_images")]
  1831. mon_cmds.append('monitor program_esp32 "{%s.bin}" 0x10000 verify'%escape_path(configuration['prog_path'][:-4]))
  1832. return "\n".join(mon_cmds)
  1833. class BaseProcess(protocol.ProcessProtocol):
  1834. STDOUT_CHUNK_SIZE=2048
  1835. COMMON_PATTERNS={"PLATFORMIO_HOME_DIR":escape_path(util.get_home_dir()),"PYTHONEXE":os.getenv("PYTHONEXEPATH","")}
  1836. def apply_patterns(self,source,patterns=None):
  1837. _patterns=self.COMMON_PATTERNS.copy()
  1838. _patterns.update(patterns or{})
  1839. def _replace(text):
  1840. for key,value in _patterns.items():
  1841. pattern="$%s"%key
  1842. text=text.replace(pattern,value or "")
  1843. return text
  1844. if isinstance(source,basestring):
  1845. source=_replace(source)
  1846. elif isinstance(source,(list,dict)):
  1847. items=enumerate(source)if isinstance(source,list)else source.items()
  1848. for key,value in items:
  1849. if isinstance(value,basestring):
  1850. source[key]=_replace(value)
  1851. elif isinstance(value,(list,dict)):
  1852. source[key]=self.apply_patterns(value,patterns)
  1853. return source
  1854. def outReceived(self,data):
  1855. if LOG_FILE:
  1856. with open(LOG_FILE,"a")as fp:
  1857. fp.write(data)
  1858. while data:
  1859. chunk=data[:self.STDOUT_CHUNK_SIZE]
  1860. click.echo(chunk,nl=False)
  1861. data=data[self.STDOUT_CHUNK_SIZE:]
  1862. def errReceived(self,data):
  1863. if LOG_FILE:
  1864. with open(LOG_FILE,"a")as fp:
  1865. fp.write(data)
  1866. click.echo(data,nl=False,err=True)
  1867. class DebugServer(BaseProcess):
  1868. def __init__(self,debug_options,env_options):
  1869. self.debug_options=debug_options
  1870. self.env_options=env_options
  1871. self._debug_port=None
  1872. self._transport=None
  1873. def spawn(self,patterns):
  1874. systype=util.get_systype()
  1875. server=self.debug_options.get("server")
  1876. if not server:
  1877. return None
  1878. server=self.apply_patterns(server,patterns)
  1879. server_executable=server['executable']
  1880. if not server_executable:
  1881. return None
  1882. if server['cwd']:
  1883. server_executable=join(server['cwd'],server_executable)
  1884. if("windows" in systype and not server_executable.endswith(".exe")and isfile(server_executable+".exe")):
  1885. server_executable=server_executable+".exe"
  1886. if not isfile(server_executable):
  1887. server_executable=util.where_is_program(server_executable)
  1888. if not isfile(server_executable):
  1889. raise exception.DebugInvalidOptions("\nCould not launch Debug Server '%s'. Please check that it " "is installed and is included in a system PATH\n\n" "See documentation or contact support@pioplus.com:\n" "http://docs.platformio.org/page/plus/debugging.html\n"%server_executable)
  1890. self._debug_port=":3333"
  1891. openocd_pipe_allowed=all([not self.debug_options['port'],"openocd" in server_executable,self.env_options['platform']!="riscv"])
  1892. if openocd_pipe_allowed:
  1893. args=[]
  1894. if server['cwd']:
  1895. args.extend(["-s",escape_path(server['cwd'])])
  1896. args.extend(["-c","gdb_port pipe; tcl_port disabled; telnet_port disabled"])
  1897. args.extend(server['arguments'])
  1898. str_args=" ".join([arg if arg.startswith("-")else '"%s"'%arg for arg in args])
  1899. self._debug_port='| "%s" %s'%(escape_path(server_executable),str_args)
  1900. else:
  1901. env=os.environ.copy()
  1902. if("windows" not in systype and server['cwd']and isdir(join(server['cwd'],"lib"))):
  1903. ld_key=("DYLD_LIBRARY_PATH" if "darwin" in systype else "LD_LIBRARY_PATH")
  1904. env[ld_key]=join(server['cwd'],"lib")
  1905. if os.environ.get(ld_key):
  1906. env[ld_key]="%s:%s"%(env[ld_key],os.environ.get(ld_key))
  1907. if server['cwd']and isdir(join(server['cwd'],"bin")):
  1908. env['PATH']="%s%s%s"%(join(server['cwd'],"bin"),os.pathsep,os.environ.get("PATH",os.environ.get("Path","")))
  1909. self._transport=reactor.spawnProcess(self,server_executable,[server_executable]+server['arguments'],path=server['cwd'],env=env)
  1910. if "mspdebug" in server_executable.lower():
  1911. self._debug_port=":2000"
  1912. elif "jlink" in server_executable.lower():
  1913. self._debug_port=":2331"
  1914. return self._transport
  1915. def get_debug_port(self):
  1916. return self._debug_port
  1917. def terminate(self):
  1918. if self._transport:
  1919. self._transport.signalProcess("KILL")
  1920. class GDBClient(BaseProcess):
  1921. PIO_SRC_NAME=".pioinit"
  1922. INIT_COMPLETED_BANNER="PlatformIO: Initialization completed"
  1923. DEFAULT_INIT_CONFIG="""
  1924. define pio_reset_halt_target
  1925. monitor reset halt
  1926. end
  1927. define pio_reset_target
  1928. monitor reset
  1929. end
  1930. target extended-remote $DEBUG_PORT
  1931. $INIT_BREAK
  1932. pio_reset_halt_target
  1933. $LOAD_CMD
  1934. monitor init
  1935. pio_reset_halt_target
  1936. """
  1937. STUTIL_INIT_CFG="""
  1938. define pio_reset_halt_target
  1939. monitor halt
  1940. monitor reset
  1941. end
  1942. define pio_reset_target
  1943. monitor reset
  1944. end
  1945. target extended-remote $DEBUG_PORT
  1946. $INIT_BREAK
  1947. pio_reset_halt_target
  1948. $LOAD_CMD
  1949. pio_reset_halt_target
  1950. """
  1951. JLINK_INIT_CONFIG="""
  1952. define pio_reset_halt_target
  1953. monitor halt
  1954. monitor reset
  1955. end
  1956. define pio_reset_target
  1957. monitor reset
  1958. end
  1959. target extended-remote $DEBUG_PORT
  1960. $INIT_BREAK
  1961. pio_reset_halt_target
  1962. $LOAD_CMD
  1963. pio_reset_halt_target
  1964. """
  1965. BLACKMAGIC_INIT_CONFIG="""
  1966. define pio_reset_halt_target
  1967. set language c
  1968. set *0xE000ED0C = 0x05FA0004
  1969. set $busy = (*0xE000ED0C & 0x4)
  1970. while ($busy)
  1971. set $busy = (*0xE000ED0C & 0x4)
  1972. end
  1973. set language auto
  1974. end
  1975. define pio_reset_target
  1976. pio_reset_halt_target
  1977. end
  1978. target extended-remote $DEBUG_PORT
  1979. monitor swdp_scan
  1980. attach 1
  1981. set mem inaccessible-by-default off
  1982. $INIT_BREAK
  1983. $LOAD_CMD
  1984. set language c
  1985. set *0xE000ED0C = 0x05FA0004
  1986. set $busy = (*0xE000ED0C & 0x4)
  1987. while ($busy)
  1988. set $busy = (*0xE000ED0C & 0x4)
  1989. end
  1990. set language auto
  1991. """
  1992. MSPDEBUG_INIT_CFG="""
  1993. define pio_reset_halt_target
  1994. end
  1995. define pio_reset_target
  1996. end
  1997. target extended-remote $DEBUG_PORT
  1998. $INIT_BREAK
  1999. monitor erase
  2000. $LOAD_CMD
  2001. pio_reset_halt_target
  2002. """
  2003. def __init__(self,project_dir,args,debug_options,env_options):
  2004. self.project_dir=project_dir
  2005. self.args=list(args)
  2006. self.debug_options=debug_options
  2007. self.env_options=env_options
  2008. self._debug_server=DebugServer(debug_options,env_options)
  2009. self._gdbsrc_dir=mkdtemp()
  2010. self._session_id=None
  2011. self._target_is_run=False
  2012. self._last_server_activity=0
  2013. self._auto_continue_timer=None
  2014. def spawn(self,gdb_path,prog_path):
  2015. self._session_id=sha1(gdb_path+prog_path).hexdigest()
  2016. self._kill_previous_session()
  2017. patterns={"PROJECT_DIR":escape_path(self.project_dir),"PROG_PATH":escape_path(prog_path),"PROG_DIR":escape_path(dirname(prog_path)),"PROG_NAME":basename(splitext(prog_path)[0]),"DEBUG_PORT":self.debug_options['port'],"UPLOAD_PROTOCOL":self.debug_options['upload_protocol'],"INIT_BREAK":self.debug_options['init_break']or "","LOAD_CMD":self.debug_options['load_cmd']or "",}
  2018. self._debug_server.spawn(patterns)
  2019. if not patterns['DEBUG_PORT']:
  2020. patterns['DEBUG_PORT']=self._debug_server.get_debug_port()
  2021. self.generate_pioinit(self._gdbsrc_dir,patterns)
  2022. args=["piogdb","-q","--directory",self._gdbsrc_dir,"--directory",self.project_dir,"-l","10"]
  2023. args.extend(self.args)
  2024. if not gdb_path:
  2025. raise exception.DebugInvalidOptions("GDB client is not configured")
  2026. gdb_data_dir=self._get_data_dir(gdb_path)
  2027. if gdb_data_dir:
  2028. args.extend(["--data-directory",gdb_data_dir])
  2029. args.append(patterns['PROG_PATH'])
  2030. return reactor.spawnProcess(self,gdb_path,args,path=self.project_dir,env=os.environ)
  2031. @staticmethod
  2032. def _get_data_dir(gdb_path):
  2033. if "msp430" in gdb_path:
  2034. return None
  2035. gdb_data_dir=abspath(join(dirname(gdb_path),"..","share","gdb"))
  2036. return gdb_data_dir if isdir(gdb_data_dir)else None
  2037. def generate_pioinit(self,dst_dir,patterns):
  2038. server_exe=(self.debug_options.get("server")or{}).get("executable","").lower()
  2039. if "jlink" in server_exe:
  2040. cfg=self.JLINK_INIT_CONFIG
  2041. elif "st-util" in server_exe:
  2042. cfg=self.STUTIL_INIT_CFG
  2043. elif "mspdebug" in server_exe:
  2044. cfg=self.MSPDEBUG_INIT_CFG
  2045. elif self.debug_options['require_debug_port']:
  2046. cfg=self.BLACKMAGIC_INIT_CONFIG
  2047. else:
  2048. cfg=self.DEFAULT_INIT_CONFIG
  2049. commands=cfg.split("\n")
  2050. if self.debug_options['init_cmds']:
  2051. commands=self.debug_options['init_cmds']
  2052. commands.extend(self.debug_options['extra_cmds'])
  2053. if not any("define pio_reset_target" in cmd for cmd in commands):
  2054. commands=["define pio_reset_target"," echo Warning! Undefined pio_reset_target command\\n"," mon reset","end"]+commands
  2055. if not any("define pio_reset_halt_target" in cmd for cmd in commands):
  2056. commands=["define pio_reset_halt_target"," echo Warning! Undefined pio_reset_halt_target command\\n"," mon reset halt","end"]+commands
  2057. if not any("define pio_restart_target" in cmd for cmd in commands):
  2058. commands+=["define pio_restart_target"," pio_reset_halt_target"," $INIT_BREAK"," %s"%("continue" if patterns['INIT_BREAK']else "next"),"end"]
  2059. banner=["echo PlatformIO Unified Debugger > http://bit.ly/pio-debug\\n","echo PlatformIO: Initializing remote target...\\n"]
  2060. footer=["echo %s\\n"%self.INIT_COMPLETED_BANNER]
  2061. commands=banner+commands+footer
  2062. with open(join(dst_dir,self.PIO_SRC_NAME),"w")as fp:
  2063. fp.write("\n".join(self.apply_patterns(commands,patterns)))
  2064. def connectionMade(self):
  2065. self._lock_session(self.transport.pid)
  2066. p=protocol.Protocol()
  2067. p.dataReceived=self.onStdInData
  2068. stdio.StandardIO(p)
  2069. def onStdInData(self,data):
  2070. self._last_server_activity=time.time()
  2071. if LOG_FILE:
  2072. with open(LOG_FILE,"a")as fp:
  2073. fp.write(data)
  2074. if "-exec-run" in data:
  2075. if self._target_is_run:
  2076. token,_=data.split("-",1)
  2077. self.outReceived("%s^running\n"%token)
  2078. return
  2079. data=data.replace("-exec-run","-exec-continue")
  2080. if "-exec-continue" in data:
  2081. self._target_is_run=True
  2082. if "-gdb-exit" in data or data=="quit":
  2083. self.transport.write("pio_reset_target\n")
  2084. self.transport.write(data)
  2085. def processEnded(self,reason):
  2086. self._unlock_session()
  2087. if self._gdbsrc_dir and isdir(self._gdbsrc_dir):
  2088. util.rmtree_(self._gdbsrc_dir)
  2089. if self._debug_server:
  2090. self._debug_server.terminate()
  2091. reactor.stop()
  2092. def outReceived(self,data):
  2093. self._last_server_activity=time.time()
  2094. BaseProcess.outReceived(self,data)
  2095. self._handle_error(data)
  2096. if self.INIT_COMPLETED_BANNER in data:
  2097. self._auto_continue_timer=task.LoopingCall(self._auto_exec_continue)
  2098. self._auto_continue_timer.start(0.1)
  2099. def errReceived(self,data):
  2100. BaseProcess.errReceived(self,data)
  2101. self._handle_error(data)
  2102. def console_log(self,msg):
  2103. if is_mi_mode(self.args):
  2104. self.outReceived('~"%s\\n"\n'%msg)
  2105. else:
  2106. self.outReceived("%s\n"%msg)
  2107. def _auto_exec_continue(self):
  2108. auto_exec_delay=0.5
  2109. if self._last_server_activity>(time.time()-auto_exec_delay):
  2110. return
  2111. if self._auto_continue_timer:
  2112. self._auto_continue_timer.stop()
  2113. self._auto_continue_timer=None
  2114. if not self.debug_options['init_break']or self._target_is_run:
  2115. return
  2116. self.console_log("PlatformIO: Resume the execution to `debug_init_break = %s`"%self.debug_options['init_break'])
  2117. self.transport.write("0-exec-continue\n" if is_mi_mode(self.args)else "continue\n")
  2118. self._target_is_run=True
  2119. def _handle_error(self,data):
  2120. if self.PIO_SRC_NAME not in data or "Error in sourced" not in data:
  2121. return
  2122. configuration={"debug":self.debug_options,"env":self.env_options}
  2123. exd=re.sub(r'\\(?!")',"/",json.dumps(configuration))
  2124. exd=re.sub(r'"(?:[a-z]\:)?((/[^"/]+)+)"',lambda m:'"%s"'%join(*m.group(1).split("/")[-2:]),exd,re.I|re.M)
  2125. mp=MeasurementProtocol()
  2126. mp['exd']="DebugGDBPioInitError: %s"%exd
  2127. mp['exf']=1
  2128. mp.send("exception")
  2129. self.transport.loseConnection()
  2130. def _kill_previous_session(self):
  2131. assert self._session_id
  2132. pid=None
  2133. with app.ContentCache()as cc:
  2134. pid=cc.get(self._session_id)
  2135. cc.delete(self._session_id)
  2136. if not pid:
  2137. return
  2138. if "windows" in util.get_systype():
  2139. kill=["Taskkill","/PID",pid,"/F"]
  2140. else:
  2141. kill=["kill",pid]
  2142. try:
  2143. util.exec_command(kill)
  2144. except:
  2145. pass
  2146. def _lock_session(self,pid):
  2147. if not self._session_id:
  2148. return
  2149. with app.ContentCache()as cc:
  2150. cc.set(self._session_id,str(pid),"1h")
  2151. def _unlock_session(self):
  2152. if not self._session_id:
  2153. return
  2154. with app.ContentCache()as cc:
  2155. cc.delete(self._session_id)
  2156. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  2157.  
  2158.  
  2159.  
  2160. #$cmd_home
  2161.  
  2162. import glob
  2163. import json
  2164. import os
  2165. import re
  2166. import shutil
  2167. import socket
  2168. import sys
  2169. import time
  2170. from os.path import(basename,expanduser,getmtime,isdir,isfile,join,realpath,sep)
  2171. import click
  2172. import requests
  2173. from bs4 import BeautifulSoup
  2174. from platformio import __version__,app,exception,util
  2175. from platformio.ide.projectgenerator import ProjectGenerator
  2176. from platformio.managers.core import get_core_package_dir
  2177. from platformio.managers.platform import PlatformManager
  2178. from twisted.internet import defer,reactor
  2179. from twisted.internet.utils import getProcessOutputAndValue
  2180. from twisted.web import server,static
  2181. from txjason import handler as jsonRPCHandler
  2182. from txjason.protocol import JSONRPCServerFactory
  2183. from txjason.service import JSONRPCError
  2184. from txsockjs.factory import SockJSResource
  2185. import cmd_account
  2186. import requests_threads
  2187. try:
  2188. from configparser import Error as ConfigParserError
  2189. except ImportError:
  2190. from ConfigParser import Error as ConfigParserError
  2191. @util.memoized(expire=5000)
  2192. def requests_session():
  2193. return requests_threads.AsyncSession(n=5)
  2194. @util.memoized()
  2195. def get_core_fullpath():
  2196. return util.where_is_program("platformio"+(".exe" if "windows" in util.get_systype()else ""))
  2197. @util.memoized(expire=10000)
  2198. def is_twitter_blocked():
  2199. ip="104.244.42.1"
  2200. timeout=2
  2201. try:
  2202. if os.getenv("HTTP_PROXY",os.getenv("HTTPS_PROXY")):
  2203. requests.get("http://%s"%ip,allow_redirects=False,timeout=timeout)
  2204. else:
  2205. socket.socket(socket.AF_INET,socket.SOCK_STREAM).connect((ip,80))
  2206. return False
  2207. except:
  2208. pass
  2209. return True
  2210. class AppRPC(jsonRPCHandler.Handler):
  2211. APPSTATE_PATH=join(util.get_home_dir(),"homestate.json")
  2212. @staticmethod
  2213. def load_state():
  2214. state=None
  2215. try:
  2216. if isfile(AppRPC.APPSTATE_PATH):
  2217. state=util.load_json(AppRPC.APPSTATE_PATH)
  2218. except exception.PlatformioException:
  2219. pass
  2220. if not isinstance(state,dict):
  2221. state={}
  2222. storage=state.get("storage",{})
  2223. caller_id=app.get_session_var("caller_id")
  2224. storage['cid']=app.get_cid()
  2225. storage['coreVersion']=__version__
  2226. storage['coreSystype']=util.get_systype()
  2227. storage['coreCaller']=(str(caller_id).lower()if caller_id else None)
  2228. storage['coreSettings']={name:{"description":data['description'],"default_value":data['value'],"value":app.get_setting(name)}for name,data in app.DEFAULT_SETTINGS.items()}
  2229. for key in storage['coreSettings']:
  2230. if not key.endswith("dir"):
  2231. continue
  2232. storage['coreSettings'][key]['default_value']=util.path_to_unicode(storage['coreSettings'][key]['default_value'])
  2233. storage['coreSettings'][key]['value']=util.path_to_unicode(storage['coreSettings'][key]['value'])
  2234. storage['homeDir']=util.path_to_unicode(expanduser("~"))
  2235. storage['projectsDir']=storage['coreSettings']['projects_dir']['value']
  2236. storage['recentProjects']=[p for p in storage.get("recentProjects",[])if util.is_platformio_project(p)]
  2237. state['storage']=storage
  2238. return state
  2239. @jsonRPCHandler.exportRPC()
  2240. def get_state(self):
  2241. try:
  2242. return AppRPC.load_state()
  2243. except Exception as e:
  2244. raise JSONRPCError(e)
  2245. @jsonRPCHandler.exportRPC()
  2246. def save_state(self,state):
  2247. with open(self.APPSTATE_PATH,"w")as fp:
  2248. json.dump(state,fp)
  2249. return True
  2250. class ProjectRPC(jsonRPCHandler.Handler):
  2251. def _get_projects(self,project_dirs=None):
  2252. def _get_project_data(project_dir):
  2253. data={"boards":[],"libExtraDirs":[]}
  2254. config=util.load_project_config(project_dir)
  2255. if config.has_section("platformio")and config.has_option("platformio","lib_extra_dirs"):
  2256. data['libExtraDirs'].extend(util.parse_conf_multi_values(config.get("platformio","lib_extra_dirs")))
  2257. for section in config.sections():
  2258. if not section.startswith("env:"):
  2259. continue
  2260. if config.has_option(section,"board"):
  2261. data['boards'].append(config.get(section,"board"))
  2262. if config.has_option(section,"lib_extra_dirs"):
  2263. data['libExtraDirs'].extend(util.parse_conf_multi_values(config.get(section,"lib_extra_dirs")))
  2264. with util.cd(project_dir):
  2265. data['libExtraDirs']=[expanduser(d)if d.startswith("~")else realpath(d)for d in data['libExtraDirs']]
  2266. data['libExtraDirs']=[d for d in data['libExtraDirs']if isdir(d)]
  2267. return data
  2268. def _path_to_name(path):
  2269. return(sep).join(path.split(sep)[-2:])
  2270. if not project_dirs:
  2271. project_dirs=AppRPC.load_state()['storage']['recentProjects']
  2272. result=[]
  2273. pm=PlatformManager()
  2274. for project_dir in project_dirs:
  2275. data={}
  2276. boards=[]
  2277. try:
  2278. data=_get_project_data(project_dir)
  2279. except exception.NotPlatformIOProject:
  2280. continue
  2281. except ConfigParserError:
  2282. pass
  2283. for board_id in data.get("boards",[]):
  2284. name=board_id
  2285. try:
  2286. name=pm.board_config(board_id)['name']
  2287. except(exception.UnknownBoard,exception.UnknownPlatform):
  2288. pass
  2289. boards.append({"id":board_id,"name":name})
  2290. result.append({"path":project_dir,"name":_path_to_name(project_dir),"modified":int(getmtime(project_dir)),"boards":boards,"extraLibStorages":[{"name":_path_to_name(d),"path":d}for d in data.get("libExtraDirs",[])]})
  2291. return result
  2292. @jsonRPCHandler.exportRPC()
  2293. def get_projects(self,project_dirs=None):
  2294. try:
  2295. return self._get_projects(project_dirs)
  2296. except Exception as e:
  2297. raise JSONRPCError(e)
  2298. @jsonRPCHandler.exportRPC()
  2299. def init(self,board,framework,project_dir):
  2300. assert project_dir
  2301. state=AppRPC.load_state()
  2302. if not isdir(project_dir):
  2303. os.makedirs(project_dir)
  2304. args=["init","--project-dir",project_dir,"--board",board]
  2305. if framework:
  2306. args.extend(["--project-option","framework = %s"%framework])
  2307. if(state['storage']['coreCaller']and state['storage']['coreCaller']in ProjectGenerator.get_supported_ides()):
  2308. args.extend(["--ide",state['storage']['coreCaller']])
  2309. d=PIOCoreRPC.spawn(args)
  2310. d.addCallback(self._generate_project_main,project_dir,framework)
  2311. return d
  2312. @staticmethod
  2313. def _generate_project_main(_,project_dir,framework):
  2314. main_content=None
  2315. if framework=="arduino":
  2316. main_content="\n".join(["#include <Arduino.h>","","void setup() {"," // put your setup code here, to run once:","}","","void loop() {"," // put your main code here, to run repeatedly:","}" ""])
  2317. elif framework=="mbed":
  2318. main_content="\n".join(["#include <mbed.h>","","int main() {",""," // put your setup code here, to run once:",""," while(1) {"," // put your main code here, to run repeatedly:"," }","}",""])
  2319. if not main_content:
  2320. return project_dir
  2321. with util.cd(project_dir):
  2322. src_dir=util.get_projectsrc_dir()
  2323. main_path=join(src_dir,"main.cpp")
  2324. if isfile(main_path):
  2325. return project_dir
  2326. if not isdir(src_dir):
  2327. os.makedirs(src_dir)
  2328. with open(main_path,"w")as f:
  2329. f.write(main_content.strip())
  2330. return project_dir
  2331. @jsonRPCHandler.exportRPC()
  2332. def import_arduino(self,board,use_arduino_libs,arduino_project_dir):
  2333. if util.is_platformio_project(arduino_project_dir):
  2334. return arduino_project_dir
  2335. is_arduino_project=any([isfile(join(arduino_project_dir,"%s.%s"%(basename(arduino_project_dir),ext)))for ext in("ino","pde")])
  2336. if not is_arduino_project:
  2337. raise JSONRPCError("Not an Arduino project: %s"%arduino_project_dir)
  2338. state=AppRPC.load_state()
  2339. project_dir=join(state['storage']['projectsDir'].decode("utf-8"),time.strftime("%y%m%d-%H%M%S-")+board)
  2340. if not isdir(project_dir):
  2341. os.makedirs(project_dir)
  2342. args=["init","--project-dir",project_dir,"--board",board]
  2343. args.extend(["--project-option","framework = arduino"])
  2344. if use_arduino_libs:
  2345. args.extend(["--project-option","lib_extra_dirs = ~/Documents/Arduino/libraries"])
  2346. if(state['storage']['coreCaller']and state['storage']['coreCaller']in ProjectGenerator.get_supported_ides()):
  2347. args.extend(["--ide",state['storage']['coreCaller']])
  2348. d=PIOCoreRPC.spawn(args)
  2349. d.addCallback(self._finalize_arduino_import,project_dir,arduino_project_dir)
  2350. return d
  2351. @staticmethod
  2352. def _finalize_arduino_import(_,project_dir,arduino_project_dir):
  2353. with util.cd(project_dir):
  2354. src_dir=util.get_projectsrc_dir()
  2355. if isdir(src_dir):
  2356. util.rmtree_(src_dir)
  2357. shutil.copytree(arduino_project_dir.encode(sys.getfilesystemencoding()),src_dir)
  2358. return project_dir
  2359. @jsonRPCHandler.exportRPC()
  2360. def get_project_examples(self):
  2361. result=[]
  2362. for manifest in PlatformManager().get_installed():
  2363. examples_dir=join(manifest['__pkg_dir'],"examples")
  2364. if not isdir(examples_dir):
  2365. continue
  2366. items=[]
  2367. for project_dir,_,__ in os.walk(examples_dir):
  2368. project_description=None
  2369. try:
  2370. config=util.load_project_config(project_dir)
  2371. if config.has_section("platformio")and config.has_option("platformio","description"):
  2372. project_description=config.get("platformio","description")
  2373. except(exception.NotPlatformIOProject,exception.InvalidProjectConf):
  2374. continue
  2375. path_tokens=project_dir.split(sep)
  2376. items.append({"name":"/".join(path_tokens[path_tokens.index("examples")+1:]),"path":project_dir,"description":project_description})
  2377. result.append({"platform":{"title":manifest['title'],"version":manifest['version']},"items":sorted(items)})
  2378. return sorted(result,key=lambda data:data['platform']['title'])
  2379. @jsonRPCHandler.exportRPC()
  2380. def import_pio(self,project_dir):
  2381. if not project_dir or not util.is_platformio_project(project_dir):
  2382. raise JSONRPCError("Not an PlatformIO project: %s"%project_dir)
  2383. new_project_dir=join(AppRPC.load_state()['storage']['projectsDir'].decode("utf-8"),time.strftime("%y%m%d-%H%M%S-")+basename(project_dir))
  2384. shutil.copytree(project_dir,new_project_dir)
  2385. state=AppRPC.load_state()
  2386. args=["init","--project-dir",new_project_dir]
  2387. if(state['storage']['coreCaller']and state['storage']['coreCaller']in ProjectGenerator.get_supported_ides()):
  2388. args.extend(["--ide",state['storage']['coreCaller']])
  2389. d=PIOCoreRPC.spawn(args)
  2390. d.addCallback(lambda _:new_project_dir)
  2391. return d
  2392. class OSRPC(jsonRPCHandler.Handler):
  2393. @staticmethod
  2394. @defer.inlineCallbacks
  2395. def fetch_content(uri,data=None,headers=None,cache_valid=None):
  2396. timeout=2
  2397. if not headers:
  2398. headers={"User-Agent":("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) " "AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 " "Safari/603.3.8")}
  2399. cache_key=(app.ContentCache.key_from_args(uri,data)if cache_valid else None)
  2400. with app.ContentCache()as cc:
  2401. if cache_key:
  2402. result=cc.get(cache_key)
  2403. if result is not None:
  2404. defer.returnValue(result)
  2405. util.internet_on(raise_exception=True)
  2406. session=requests_session()
  2407. if data:
  2408. r=yield session.post(uri,data=data,headers=headers,timeout=timeout)
  2409. else:
  2410. r=yield session.get(uri,headers=headers,timeout=timeout)
  2411. r.raise_for_status()
  2412. result=r.text
  2413. if cache_valid:
  2414. with app.ContentCache()as cc:
  2415. cc.set(cache_key,result,cache_valid)
  2416. defer.returnValue(result)
  2417. @jsonRPCHandler.exportRPC()
  2418. def request_content(self,uri,data=None,headers=None,cache_valid=None):
  2419. try:
  2420. if uri.startswith('http'):
  2421. return self.fetch_content(uri,data,headers,cache_valid)
  2422. elif isfile(uri):
  2423. with open(uri)as fp:
  2424. return fp.read()
  2425. except Exception as e:
  2426. e=JSONRPCError("Could not fetch content `%s`"%uri)
  2427. e.data=str(e)
  2428. raise e
  2429. return None
  2430. @jsonRPCHandler.exportRPC()
  2431. def open_url(self,url):
  2432. return click.launch(url)
  2433. @jsonRPCHandler.exportRPC()
  2434. def reveal_file(self,path):
  2435. return click.launch(path.encode(sys.getfilesystemencoding()),locate=True)
  2436. @jsonRPCHandler.exportRPC()
  2437. def is_file(self,path):
  2438. return isfile(path)
  2439. @jsonRPCHandler.exportRPC()
  2440. def is_dir(self,path):
  2441. return isdir(path)
  2442. @jsonRPCHandler.exportRPC()
  2443. def make_dirs(self,path):
  2444. return os.makedirs(path)
  2445. @jsonRPCHandler.exportRPC()
  2446. def rename(self,src,dst):
  2447. return os.rename(src,dst)
  2448. @jsonRPCHandler.exportRPC()
  2449. def copy(self,src,dst):
  2450. return shutil.copytree(src,dst)
  2451. @jsonRPCHandler.exportRPC()
  2452. def glob(self,pathnames,root=None):
  2453. if not isinstance(pathnames,list):
  2454. pathnames=[pathnames]
  2455. result=set()
  2456. for pathname in pathnames:
  2457. result|=set(glob.glob(join(root,pathname)if root else pathname))
  2458. return list(result)
  2459. @jsonRPCHandler.exportRPC()
  2460. def list_dir(self,path):
  2461. def _cmp(x,y):
  2462. if x[1]and not y[1]:
  2463. return-1
  2464. elif not x[1]and y[1]:
  2465. return 1
  2466. elif x[0].lower()>y[0].lower():
  2467. return 1
  2468. elif x[0].lower()<y[0].lower():
  2469. return-1
  2470. return 0
  2471. try:
  2472. items=[]
  2473. if path.startswith("~"):
  2474. path=expanduser(path)
  2475. if not isdir(path):
  2476. return items
  2477. for item in os.listdir(path):
  2478. try:
  2479. item_is_dir=isdir(join(path,item))
  2480. if item_is_dir:
  2481. os.listdir(join(path,item))
  2482. items.append((item,item_is_dir))
  2483. except OSError:
  2484. pass
  2485. return sorted(items,cmp=_cmp)
  2486. except Exception as e:
  2487. e=JSONRPCError("Could not list directory by `%s`"%path)
  2488. e.data=str(e)
  2489. raise e
  2490. @jsonRPCHandler.exportRPC()
  2491. def get_logical_devices(self):
  2492. items=[]
  2493. try:
  2494. for item in util.get_logical_devices():
  2495. if item['name']:
  2496. item['name']=util.path_to_unicode(item['name'])
  2497. items.append(item)
  2498. except Exception as e:
  2499. e=JSONRPCError("Could not fetch logical disks")
  2500. e.data=str(e)
  2501. raise e
  2502. return items
  2503. class PIOCoreRPC(jsonRPCHandler.Handler):
  2504. @staticmethod
  2505. def spawn(args,options=None):
  2506. try:
  2507. args=[arg.encode(sys.getfilesystemencoding())if isinstance(arg,basestring)else str(arg)for arg in args]
  2508. except UnicodeError:
  2509. raise JSONRPCError("PIO Core: non-ASCII chars in arguments")
  2510. d=getProcessOutputAndValue(get_core_fullpath(),args,path=(options or{}).get("cwd"),env={k:v for k,v in os.environ.items()if "%" not in k})
  2511. d.addCallback(PIOCoreRPC._spawn_callback,"--json-output" in args)
  2512. d.addErrback(PIOCoreRPC._spawn_errback)
  2513. return d
  2514. @staticmethod
  2515. def _spawn_callback(result,json_output=False):
  2516. out,err,code=result
  2517. out=out.strip()if out else ""
  2518. err=err.strip()if err else ""
  2519. result=("%s\n\n%s"%(out,err)).strip()
  2520. if code!=0:
  2521. raise Exception(result)
  2522. result=util.path_to_unicode(result)
  2523. if not json_output:
  2524. return result
  2525. try:
  2526. return json.loads(result)
  2527. except ValueError as e:
  2528. if "sh: " in result:
  2529. return json.loads(re.sub(r"^sh: [^\n]+$","",result,flags=re.M).strip())
  2530. raise e
  2531. @staticmethod
  2532. def _spawn_errback(failure):
  2533. e=JSONRPCError("PIO Core Call Error")
  2534. e.data=failure.getErrorMessage()
  2535. raise e
  2536. @jsonRPCHandler.exportRPC()
  2537. def call(self,args,options=None):
  2538. return self.spawn(args,options)
  2539. @jsonRPCHandler.exportRPC()
  2540. def version(self):
  2541. return __version__
  2542. @jsonRPCHandler.exportRPC()
  2543. def auth_info(self,extended=False):
  2544. data=cmd_account.get_session_data()
  2545. if not data or data['expire']<time.time():
  2546. return None
  2547. try:
  2548. return cmd_account.get_account_info(offline=not extended)
  2549. except exception.InternetIsOffline as e:
  2550. raise JSONRPCError(str(e))
  2551. class IDERPC(jsonRPCHandler.Handler):
  2552. def __init__(self):
  2553. jsonRPCHandler.Handler.__init__(self)
  2554. self._queue=[]
  2555. def send_command(self,command,params):
  2556. if not self._queue:
  2557. raise JSONRPCError("PIO Home IDE agent is not started")
  2558. while self._queue:
  2559. self._queue.pop().callback({"id":time.time(),"method":command,"params":params})
  2560. @jsonRPCHandler.exportRPC()
  2561. def listen_commands(self):
  2562. self._queue.append(defer.Deferred())
  2563. return self._queue[-1]
  2564. @jsonRPCHandler.exportRPC()
  2565. def open_project(self,project_dir):
  2566. return self.send_command("open_project",project_dir)
  2567. class MiscRPC(jsonRPCHandler.Handler):
  2568. @staticmethod
  2569. def _get_proxed_uri(uri):
  2570. index=uri.index("://")
  2571. return "https://dl.platformio.org/__prx__/"+uri[index+3:]
  2572. def _get_twitter_headers(self,username):
  2573. return{"Accept":"application/json, text/javascript, */*; q=0.01","Referer":"https://twitter.com/%s"%username,"User-Agent":("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit" "/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8"),"X-Twitter-Active-User":"yes","X-Requested-With":"XMLHttpRequest"}
  2574. @defer.inlineCallbacks
  2575. def _fetch_iframe_card(self,url,username):
  2576. if is_twitter_blocked():
  2577. url=self._get_proxed_uri(url)
  2578. html=yield OSRPC.fetch_content(url,headers=self._get_twitter_headers(username),cache_valid="7d")
  2579. soup=BeautifulSoup(html,"html.parser")
  2580. photo_node=soup.find("img",attrs={"data-src":True})
  2581. url_node=soup.find("a",class_="TwitterCard-container")
  2582. text_node=soup.find("div",class_="SummaryCard-content")
  2583. if text_node:
  2584. text_node.find("span",class_="SummaryCard-destination").decompose()
  2585. defer.returnValue({"photo":photo_node.get("data-src")if photo_node else None,"text_node":text_node,"url":url_node.get("href")if url_node else None})
  2586. @defer.inlineCallbacks
  2587. def _parse_tweet_node(self,tweet,username):
  2588. for node in tweet.find_all(class_=["invisible","u-hidden"]):
  2589. node.decompose()
  2590. twitter_url="https://twitter.com"
  2591. time_node=tweet.find("span",attrs={"data-time":True})
  2592. text_node=tweet.find(class_="tweet-text")
  2593. quote_text_node=tweet.find(class_="QuoteTweet-text")
  2594. if quote_text_node and not text_node.get_text().strip():
  2595. text_node=quote_text_node
  2596. photos=[node.get("data-image-url")for node in(tweet.find_all(class_=["AdaptiveMedia-photoContainer","QuoteMedia-photoContainer"])or[])]
  2597. urls=[node.get("data-expanded-url")for node in(quote_text_node or text_node).find_all(class_="twitter-timeline-link",attrs={"data-expanded-url":True})]
  2598. if(not photos or not urls)and tweet.get("data-card2-type"):
  2599. iframe_node=tweet.find("div",attrs={"data-full-card-iframe-url":True})
  2600. if iframe_node:
  2601. iframe_card=yield self._fetch_iframe_card(twitter_url+iframe_node.get("data-full-card-iframe-url"),username)
  2602. if not photos and iframe_card['photo']:
  2603. photos.append(iframe_card['photo'])
  2604. if not urls and iframe_card['url']:
  2605. urls.append(iframe_card['url'])
  2606. if iframe_card['text_node']:
  2607. text_node=iframe_card['text_node']
  2608. if not photos:
  2609. photos.append(tweet.find("img",class_="avatar").get("src"))
  2610. def _fetch_text(text_node):
  2611. text=text_node.decode_contents(formatter="html").strip()
  2612. text=re.sub(r'href="/','href="%s/'%twitter_url,text)
  2613. if "</p>" not in text and "<br" not in text:
  2614. text=re.sub(r"\n+","<br />",text)
  2615. return text
  2616. defer.returnValue({"tweetId":tweet.get("data-tweet-id"),"tweetUrl":twitter_url+tweet.get("data-permalink-path"),"author":tweet.get("data-name"),"time":int(time_node.get("data-time")),"timeFormatted":time_node.string,"text":_fetch_text(text_node),"entries":{"urls":urls,"photos":[self._get_proxed_uri(uri)if is_twitter_blocked()else uri for uri in photos]},"isPinned":"user-pinned" in tweet.get("class")})
  2617. @defer.inlineCallbacks
  2618. def _fetch_tweets(self,username):
  2619. api_url=("https://twitter.com/i/profiles/show/%s/timeline/tweets?" "include_available_features=1&include_entities=1&" "include_new_items_bar=true")%username
  2620. if is_twitter_blocked():
  2621. api_url=self._get_proxed_uri(api_url)
  2622. html_or_json=yield OSRPC.fetch_content(api_url,headers=self._get_twitter_headers(username))
  2623. if not isinstance(html_or_json,dict):
  2624. html_or_json=json.loads(html_or_json)
  2625. assert "items_html" in html_or_json
  2626. soup=BeautifulSoup(html_or_json['items_html'],"html.parser")
  2627. tweet_nodes=soup.find_all("div",attrs={"class":"tweet","data-tweet-id":True})
  2628. defer.returnValue([(yield self._parse_tweet_node(node,username))for node in tweet_nodes])
  2629. @defer.inlineCallbacks
  2630. def _preload_latest_tweets(self,username,cache_key,cache_valid):
  2631. result=yield self._fetch_tweets(username)
  2632. with app.ContentCache()as cc:
  2633. cc.set(cache_key,json.dumps({"time":int(time.time()),"result":result}),cache_valid)
  2634. defer.returnValue(result)
  2635. @jsonRPCHandler.exportRPC()
  2636. def load_latest_tweets(self,username):
  2637. cache_key="piohome_latest_tweets_%s"%username
  2638. cache_valid="7d"
  2639. with app.ContentCache()as cc:
  2640. cache_data=cc.get(cache_key)
  2641. if cache_data:
  2642. cache_data=json.loads(cache_data)
  2643. if cache_data['time']<(time.time()-(3600*12)):
  2644. reactor.callLater(5,self._preload_latest_tweets,username,cache_key,cache_valid)
  2645. return cache_data['result']
  2646. try:
  2647. return self._preload_latest_tweets(username,cache_key,cache_valid)
  2648. except Exception as e:
  2649. raise JSONRPCError(e)
  2650. class WebRoot(static.File):
  2651. def render_GET(self,request):
  2652. if request.args.get("__shutdown__",False):
  2653. reactor.stop()
  2654. return "Server has been stopped"
  2655. request.setHeader('cache-control','no-cache, no-store, must-revalidate')
  2656. request.setHeader('pragma','no-cache')
  2657. request.setHeader('expires',0)
  2658. return static.File.render_GET(self,request)
  2659. @click.command("home",short_help="PIO Home")
  2660. @click.option("--port",type=int,default=8008,help="HTTP port, default=8008")
  2661. @click.option("--host",default="127.0.0.1",help="HTTP host, default=127.0.0.1. " "You can open PIO Home for inbound connections with --host=0.0.0.0")
  2662. @click.option("--no-open",is_flag=True)
  2663. def cli(port,host,no_open):
  2664. factory=JSONRPCServerFactory()
  2665. factory.addHandler(AppRPC(),namespace="app")
  2666. factory.addHandler(ProjectRPC(),namespace="project")
  2667. factory.addHandler(PIOCoreRPC(),namespace="core")
  2668. factory.addHandler(OSRPC(),namespace="os")
  2669. factory.addHandler(IDERPC(),namespace="ide")
  2670. factory.addHandler(MiscRPC(),namespace="misc")
  2671. contrib_dir=get_core_package_dir("contrib-piohome")
  2672. if not isdir(contrib_dir):
  2673. raise exception.PlatformioException("Invalid path to PIO Home Contrib")
  2674. root=WebRoot(contrib_dir)
  2675. root.putChild("wsrpc",SockJSResource(factory))
  2676. root.putChild("rpc",factory)
  2677. site=server.Site(root)
  2678. if host=="__do_not_start__":
  2679. return
  2680. already_started=False
  2681. socket.setdefaulttimeout(1)
  2682. try:
  2683. socket.socket(socket.AF_INET,socket.SOCK_STREAM).connect((host,port))
  2684. already_started=True
  2685. except:
  2686. pass
  2687. home_url="http://%s:%d"%(host,port)
  2688. if not no_open:
  2689. if already_started:
  2690. click.launch(home_url)
  2691. else:
  2692. reactor.callLater(1,lambda:click.launch(home_url))
  2693. click.echo("\n".join([""," ___I_"," /\\-_--\\ PlatformIO Home","/ \\_-__\\","|[]| [] | %s"%home_url,"|__|____|______________%s"%("_"*len(host)),]))
  2694. if already_started:
  2695. return
  2696. reactor.listenTCP(port,site,interface=host)
  2697. reactor.run()
  2698. # Created by pyminifier (https://github.com/liftoff/pyminifier)
  2699.  
  2700.  
  2701.  
  2702. #$main
  2703.  
  2704. import load_pysite
  2705. import os
  2706. import sys
  2707. from platform import system
  2708. from traceback import format_exc
  2709. import click
  2710. from platformio import app,exception,maintenance
  2711. import cmd_account
  2712. import cmd_debug
  2713. import cmd_home
  2714. import cmd_remote
  2715. import cmd_test
  2716. from config import __version__
  2717. COMMANDS={"account":cmd_account,"debug":cmd_debug,"home":cmd_home,"remote":cmd_remote,"test":cmd_test}
  2718. class PlatformioPlusCLI(click.MultiCommand):
  2719. def list_commands(self,ctx):
  2720. return COMMANDS.keys()
  2721. def get_command(self,ctx,cmd_name):
  2722. if cmd_name not in COMMANDS:
  2723. raise exception.PlatformioException("Unknown command `%s`"%cmd_name)
  2724. return COMMANDS[cmd_name].cli
  2725. @click.command(cls=PlatformioPlusCLI,context_settings=dict(help_option_names=["-h","--help"]))
  2726. @click.version_option(__version__,prog_name="PIO Plus")
  2727. @click.option("--force","-f",is_flag=True,help="Force to accept any confirmation prompts.")
  2728. @click.option("--caller","-c",help="Caller ID (service).")
  2729. @click.pass_context
  2730. def cli(ctx,force,caller):
  2731. if not maintenance.in_silence(ctx):
  2732. banner="PIO Plus (https://pioplus.com) v%s"%__version__
  2733. ctx_args=ctx.args or[]
  2734. if ctx_args and ctx_args[0]=="debug" and "--interpreter" in " ".join(ctx_args):
  2735. click.echo('~"%s\\n"'%banner)
  2736. else:
  2737. click.echo(banner)
  2738. app.set_session_var("command_ctx",ctx)
  2739. app.set_session_var("force_option",force)
  2740. maintenance.set_caller(caller)
  2741. cmd_account.cmd_check_permission(ctx)
  2742. def configure():
  2743. if "cygwin" in system().lower():
  2744. raise exception.CygwinEnvDetected()
  2745. try:
  2746. import urllib3
  2747. urllib3.disable_warnings()
  2748. except(AttributeError,ImportError):
  2749. pass
  2750. if str(os.getenv("PLATFORMIO_FORCE_COLOR","")).lower()=="true":
  2751. try:
  2752. click._compat.isatty=lambda stream:True
  2753. except:
  2754. pass
  2755. click_echo_origin=[click.echo,click.secho]
  2756. def _safe_echo(origin,*args,**kwargs):
  2757. try:
  2758. click_echo_origin[origin](*args,**kwargs)
  2759. except IOError:
  2760. data=args[0]if args else ""
  2761. if kwargs.get("nl",True):
  2762. data+="\n"
  2763. (sys.stderr.write if kwargs.get("err")else sys.stdout.write)(data)
  2764. click.echo=lambda*args,**kwargs:_safe_echo(0,*args,**kwargs)
  2765. click.secho=lambda*args,**kwargs:_safe_echo(1,*args,**kwargs)
  2766. def main():
  2767. try:
  2768. configure()
  2769. cli(None,None,None)
  2770. except Exception as e:
  2771. if not isinstance(e,exception.ReturnErrorCode):
  2772. maintenance.on_platformio_exception(e)
  2773. error_str="Error: "
  2774. if isinstance(e,exception.PlatformioException):
  2775. error_str+=str(e)
  2776. else:
  2777. error_str+=format_exc()
  2778. error_str+="""
  2779. ============================================================
  2780. An unexpected error occurred. Further steps:
  2781. * Verify that you have the latest version of PlatformIO using
  2782. `pip install -U platformio` command
  2783. * Try to find answer in FAQ Troubleshooting section
  2784. http://docs.platformio.org/page/faq.html
  2785. * Report this problem to support@pioplus.com
  2786. ============================================================
  2787. """
  2788. click.secho(error_str,fg="red",err=True)
  2789. return int(str(e))if str(e).isdigit()else 1
  2790. return 0
  2791. if __name__=="__main__":
  2792. sys.exit(main())
  2793. # Created by pyminifier (https://github.com/liftoff/pyminifier)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement