auto_build.py 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. #!/usr/bin/env python
  2. #######################################
  3. #
  4. # Marlin 3D Printer Firmware
  5. # Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
  6. #
  7. # Based on Sprinter and grbl.
  8. # Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
  9. #
  10. # This program is free software: you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation, either version 3 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. # You should have received a copy of the GNU General Public License
  21. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  22. #
  23. #######################################
  24. #######################################
  25. #
  26. # Revision: 2.1.0
  27. #
  28. # Description: script to automate PlatformIO builds
  29. # CLI: python auto_build.py build_option
  30. # build_option (required)
  31. # build executes -> platformio run -e target_env
  32. # clean executes -> platformio run --target clean -e target_env
  33. # upload executes -> platformio run --target upload -e target_env
  34. # traceback executes -> platformio run --target upload -e target_env
  35. # program executes -> platformio run --target program -e target_env
  36. # test executes -> platformio test upload -e target_env
  37. # remote executes -> platformio remote run --target upload -e target_env
  38. # debug executes -> platformio debug -e target_env
  39. #
  40. # 'traceback' just uses the debug variant of the target environment if one exists
  41. #
  42. #######################################
  43. #######################################
  44. #
  45. # General program flow
  46. #
  47. # 1. Scans Configuration.h for the motherboard name and Marlin version.
  48. # 2. Scans pins.h for the motherboard.
  49. # returns the CPU(s) and platformio environment(s) used by the motherboard
  50. # 3. If further info is needed then a popup gets it from the user.
  51. # 4. The OUTPUT_WINDOW class creates a window to display the output of the PlatformIO program.
  52. # 5. A thread is created by the OUTPUT_WINDOW class in order to execute the RUN_PIO function.
  53. # 6. The RUN_PIO function uses a subprocess to run the CLI version of PlatformIO.
  54. # 7. The "iter(pio_subprocess.stdout.readline, '')" function is used to stream the output of
  55. # PlatformIO back to the RUN_PIO function.
  56. # 8. Each line returned from PlatformIO is formatted to match the color coding seen in the
  57. # PlatformIO GUI.
  58. # 9. If there is a color change within a line then the line is broken at each color change
  59. # and sent separately.
  60. # 10. Each formatted segment (could be a full line or a split line) is put into the queue
  61. # IO_queue as it arrives from the platformio subprocess.
  62. # 11. The OUTPUT_WINDOW class periodically samples IO_queue. If data is available then it
  63. # is written to the window.
  64. # 12. The window stays open until the user closes it.
  65. # 13. The OUTPUT_WINDOW class continues to execute as long as the window is open. This allows
  66. # copying, saving, scrolling of the window. A right click popup is available.
  67. #
  68. #######################################
  69. from __future__ import print_function
  70. from __future__ import division
  71. import sys, os, re
  72. pwd = os.getcwd() # make sure we're executing from the correct directory level
  73. pwd = pwd.replace('\\', '/')
  74. if 0 <= pwd.find('buildroot/share/vscode'):
  75. pwd = pwd[:pwd.find('buildroot/share/vscode')]
  76. os.chdir(pwd)
  77. print('pwd: ', pwd)
  78. num_args = len(sys.argv)
  79. if num_args > 1:
  80. build_type = str(sys.argv[1])
  81. else:
  82. print('Please specify build type')
  83. exit()
  84. print('build_type: ', build_type)
  85. print('\nWorking\n')
  86. python_ver = sys.version_info[0] # major version - 2 or 3
  87. print("python version " + str(sys.version_info[0]) + "." + str(sys.version_info[1]) + "." + str(sys.version_info[2]))
  88. import platform
  89. current_OS = platform.system()
  90. #globals
  91. target_env = ''
  92. board_name = ''
  93. from datetime import datetime
  94. #########
  95. # Python 2 error messages:
  96. # Can't find a usable init.tcl in the following directories ...
  97. # error "invalid command name "tcl_findLibrary""
  98. #
  99. # Fix for the above errors on my Win10 system:
  100. # search all init.tcl files for the line "package require -exact Tcl" that has the highest 8.5.x number
  101. # copy it into the first directory listed in the error messages
  102. # set the environmental variables TCLLIBPATH and TCL_LIBRARY to the directory where you found the init.tcl file
  103. # reboot
  104. #########
  105. ##########################################################################################
  106. #
  107. # popup to get input from user
  108. #
  109. ##########################################################################################
  110. def get_answer(board_name, question_txt, options, default_value=1):
  111. if python_ver == 2:
  112. import Tkinter as tk
  113. else:
  114. import tkinter as tk
  115. def CPU_exit_3(): # forward declare functions
  116. CPU_exit_3_()
  117. def got_answer():
  118. got_answer_()
  119. def kill_session():
  120. kill_session_()
  121. root_get_answer = tk.Tk()
  122. root_get_answer.title('')
  123. #root_get_answer.withdraw()
  124. #root_get_answer.deiconify()
  125. root_get_answer.attributes("-topmost", True)
  126. def disable_event():
  127. pass
  128. root_get_answer.protocol("WM_DELETE_WINDOW", disable_event)
  129. root_get_answer.resizable(False, False)
  130. global get_answer_val
  131. get_answer_val = default_value # return get_answer_val, set default to match radio_state default
  132. radio_state = tk.IntVar()
  133. radio_state.set(get_answer_val)
  134. l1 = tk.Label(text=board_name, fg="light green", bg="dark green",
  135. font="default 14 bold").grid(row=0, columnspan=2, sticky='EW', ipadx=2, ipady=2)
  136. l2 = tk.Label(text=question_txt).grid(row=1, pady=4, columnspan=2, sticky='EW')
  137. buttons = []
  138. for index, val in enumerate(options):
  139. buttons.append(
  140. tk.Radiobutton(
  141. text=val,
  142. fg="black",
  143. bg="lightgray",
  144. relief=tk.SUNKEN,
  145. selectcolor="green",
  146. variable=radio_state,
  147. value=index + 1,
  148. indicatoron=0,
  149. command=CPU_exit_3
  150. ).grid(row=index + 2, pady=1, ipady=2, ipadx=10, columnspan=2))
  151. b6 = tk.Button(text="Cancel", fg="red", command=kill_session).grid(row=(2 + len(options)), column=0, padx=4, pady=4, ipadx=2, ipady=2)
  152. b7 = tk.Button(text="Continue", fg="green", command=got_answer).grid(row=(2 + len(options)), column=1, padx=4, pady=4, ipadx=2, ipady=2)
  153. def got_answer_():
  154. root_get_answer.destroy()
  155. def CPU_exit_3_():
  156. global get_answer_val
  157. get_answer_val = radio_state.get()
  158. def kill_session_():
  159. raise SystemExit(0) # kill everything
  160. root_get_answer.mainloop()
  161. # end - get answer
  162. #
  163. # move custom board definitions from project folder to PlatformIO
  164. #
  165. def resolve_path(path):
  166. import os
  167. # turn the selection into a partial path
  168. if 0 <= path.find('"'):
  169. path = path[path.find('"'):]
  170. if 0 <= path.find(', line '):
  171. path = path.replace(', line ', ':')
  172. path = path.replace('"', '')
  173. # get line and column numbers
  174. line_num = 1
  175. column_num = 1
  176. line_start = path.find(':', 2) # use 2 here so don't eat Windows full path
  177. column_start = path.find(':', line_start + 1)
  178. if column_start == -1:
  179. column_start = len(path)
  180. column_end = path.find(':', column_start + 1)
  181. if column_end == -1:
  182. column_end = len(path)
  183. if 0 <= line_start:
  184. line_num = path[line_start + 1:column_start]
  185. if line_num == '':
  186. line_num = 1
  187. if column_start != column_end:
  188. column_num = path[column_start + 1:column_end]
  189. if column_num == '':
  190. column_num = 0
  191. index_end = path.find(',')
  192. if 0 <= index_end:
  193. path = path[:index_end] # delete comma and anything after
  194. index_end = path.find(':', 2)
  195. if 0 <= index_end:
  196. path = path[:path.find(':', 2)] # delete the line number and anything after
  197. path = path.replace('\\', '/')
  198. if 1 == path.find(':') and current_OS == 'Windows':
  199. return path, line_num, column_num # found a full path - no need for further processing
  200. elif 0 == path.find('/') and (current_OS == 'Linux' or current_OS == 'Darwin'):
  201. return path, line_num, column_num # found a full path - no need for further processing
  202. else:
  203. # resolve as many '../' as we can
  204. while 0 <= path.find('../'):
  205. end = path.find('../') - 1
  206. start = path.find('/')
  207. while 0 <= path.find('/', start) < end:
  208. start = path.find('/', start) + 1
  209. path = path[0:start] + path[end + 4:]
  210. # this is an alternative to the above - it just deletes the '../' section
  211. # start_temp = path.find('../')
  212. # while 0 <= path.find('../',start_temp):
  213. # start = path.find('../',start_temp)
  214. # start_temp = start + 1
  215. # if 0 <= start:
  216. # path = path[start + 2 : ]
  217. start = path.find('/')
  218. if start != 0: # make sure path starts with '/'
  219. while 0 == path.find(' '): # eat any spaces at the beginning
  220. path = path[1:]
  221. path = '/' + path
  222. if current_OS == 'Windows':
  223. search_path = path.replace('/', '\\') # os.walk uses '\' in Windows
  224. else:
  225. search_path = path
  226. start_path = os.path.abspath('')
  227. # search project directory for the selection
  228. found = False
  229. full_path = ''
  230. for root, directories, filenames in os.walk(start_path):
  231. for filename in filenames:
  232. if 0 <= root.find('.git'): # don't bother looking in this directory
  233. break
  234. full_path = os.path.join(root, filename)
  235. if 0 <= full_path.find(search_path):
  236. found = True
  237. break
  238. if found:
  239. break
  240. return full_path, line_num, column_num
  241. # end - resolve_path
  242. #
  243. # Open the file in the preferred editor at the line & column number
  244. # If the preferred editor isn't already running then it tries the next.
  245. # If none are open then the system default is used.
  246. #
  247. # Editor order:
  248. # 1. Notepad++ (Windows only)
  249. # 2. Sublime Text
  250. # 3. Atom
  251. # 4. System default (opens at line 1, column 1 only)
  252. #
  253. def open_file(path):
  254. import subprocess
  255. file_path, line_num, column_num = resolve_path(path)
  256. if file_path == '':
  257. return
  258. if current_OS == 'Windows':
  259. editor_note = subprocess.check_output('wmic process where "name=' + "'notepad++.exe'" + '" get ExecutablePath')
  260. editor_sublime = subprocess.check_output('wmic process where "name=' + "'sublime_text.exe'" + '" get ExecutablePath')
  261. editor_atom = subprocess.check_output('wmic process where "name=' + "'atom.exe'" + '" get ExecutablePath')
  262. if 0 <= editor_note.find('notepad++.exe'):
  263. start = editor_note.find('\n') + 1
  264. end = editor_note.find('\n', start + 5) - 4
  265. editor_note = editor_note[start:end]
  266. command = file_path, ' -n' + str(line_num), ' -c' + str(column_num)
  267. subprocess.Popen([editor_note, command])
  268. elif 0 <= editor_sublime.find('sublime_text.exe'):
  269. start = editor_sublime.find('\n') + 1
  270. end = editor_sublime.find('\n', start + 5) - 4
  271. editor_sublime = editor_sublime[start:end]
  272. command = file_path + ':' + line_num + ':' + column_num
  273. subprocess.Popen([editor_sublime, command])
  274. elif 0 <= editor_atom.find('atom.exe'):
  275. start = editor_atom.find('\n') + 1
  276. end = editor_atom.find('\n', start + 5) - 4
  277. editor_atom = editor_atom[start:end]
  278. command = file_path + ':' + str(line_num) + ':' + str(column_num)
  279. subprocess.Popen([editor_atom, command])
  280. else:
  281. os.startfile(resolve_path(path)) # open file with default app
  282. elif current_OS == 'Linux':
  283. command = file_path + ':' + str(line_num) + ':' + str(column_num)
  284. index_end = command.find(',')
  285. if 0 <= index_end:
  286. command = command[:index_end] # sometimes a comma magically appears, don't want it
  287. running_apps = subprocess.Popen('ps ax -o cmd', stdout=subprocess.PIPE, shell=True)
  288. (output, err) = running_apps.communicate()
  289. temp = output.split('\n')
  290. def find_editor_linux(name, search_obj):
  291. for line in search_obj:
  292. if 0 <= line.find(name):
  293. path = line
  294. return True, path
  295. return False, ''
  296. (success_sublime, editor_path_sublime) = find_editor_linux('sublime_text', temp)
  297. (success_atom, editor_path_atom) = find_editor_linux('atom', temp)
  298. if success_sublime:
  299. subprocess.Popen([editor_path_sublime, command])
  300. elif success_atom:
  301. subprocess.Popen([editor_path_atom, command])
  302. else:
  303. os.system('xdg-open ' + file_path)
  304. elif current_OS == 'Darwin': # MAC
  305. command = file_path + ':' + str(line_num) + ':' + str(column_num)
  306. index_end = command.find(',')
  307. if 0 <= index_end:
  308. command = command[:index_end] # sometimes a comma magically appears, don't want it
  309. running_apps = subprocess.Popen('ps axwww -o command', stdout=subprocess.PIPE, shell=True)
  310. (output, err) = running_apps.communicate()
  311. temp = output.split('\n')
  312. def find_editor_mac(name, search_obj):
  313. for line in search_obj:
  314. if 0 <= line.find(name):
  315. path = line
  316. if 0 <= path.find('-psn'):
  317. path = path[:path.find('-psn') - 1]
  318. return True, path
  319. return False, ''
  320. (success_sublime, editor_path_sublime) = find_editor_mac('Sublime', temp)
  321. (success_atom, editor_path_atom) = find_editor_mac('Atom', temp)
  322. if success_sublime:
  323. subprocess.Popen([editor_path_sublime, command])
  324. elif success_atom:
  325. subprocess.Popen([editor_path_atom, command])
  326. else:
  327. os.system('open ' + file_path)
  328. # end - open_file
  329. # Get the last build environment
  330. def get_build_last():
  331. env_last = ''
  332. DIR_PWD = os.listdir('.')
  333. if '.pio' in DIR_PWD:
  334. date_last = 0.0
  335. DIR__pioenvs = os.listdir('.pio')
  336. for name in DIR__pioenvs:
  337. if 0 <= name.find('.') or 0 <= name.find('-'): # skip files in listing
  338. continue
  339. DIR_temp = os.listdir('.pio/build/' + name)
  340. for names_temp in DIR_temp:
  341. if 0 == names_temp.find('firmware.'):
  342. date_temp = os.path.getmtime('.pio/build/' + name + '/' + names_temp)
  343. if date_temp > date_last:
  344. date_last = date_temp
  345. env_last = name
  346. return env_last
  347. # Get the board being built from the Configuration.h file
  348. # return: board name, major version of Marlin being used (1 or 2)
  349. def get_board_name():
  350. board_name = ''
  351. # get board name
  352. with open('Marlin/Configuration.h', 'r') as myfile:
  353. Configuration_h = myfile.read()
  354. Configuration_h = Configuration_h.split('\n')
  355. Marlin_ver = 0 # set version to invalid number
  356. for lines in Configuration_h:
  357. if 0 == lines.find('#define CONFIGURATION_H_VERSION 01'):
  358. Marlin_ver = 1
  359. if 0 == lines.find('#define CONFIGURATION_H_VERSION 02'):
  360. Marlin_ver = 2
  361. board = lines.find(' BOARD_') + 1
  362. motherboard = lines.find(' MOTHERBOARD ') + 1
  363. define = lines.find('#define ')
  364. comment = lines.find('//')
  365. if (comment == -1 or comment > board) and \
  366. board > motherboard and \
  367. motherboard > define and \
  368. define >= 0 :
  369. spaces = lines.find(' ', board) # find the end of the board substring
  370. if spaces == -1:
  371. board_name = lines[board:]
  372. else:
  373. board_name = lines[board:spaces]
  374. break
  375. return board_name, Marlin_ver
  376. # extract first environment name found after the start position
  377. # return: environment name and position to start the next search from
  378. def get_env_from_line(line, start_position):
  379. env = ''
  380. next_position = -1
  381. env_position = line.find('env:', start_position)
  382. if 0 < env_position:
  383. next_position = line.find(' ', env_position + 4)
  384. if 0 < next_position:
  385. env = line[env_position + 4:next_position]
  386. else:
  387. env = line[env_position + 4:] # at the end of the line
  388. return env, next_position
  389. def invalid_board():
  390. print('ERROR - invalid board')
  391. print(board_name)
  392. raise SystemExit(0) # quit if unable to find board
  393. # scan pins.h for board name and return the environment(s) found
  394. def get_starting_env(board_name_full, version):
  395. # get environment starting point
  396. if version == 1:
  397. path = 'Marlin/pins.h'
  398. if version == 2:
  399. path = 'Marlin/src/pins/pins.h'
  400. with open(path, 'r') as myfile:
  401. pins_h = myfile.read()
  402. board_name = board_name_full[6:] # only use the part after "BOARD_" since we're searching the pins.h file
  403. pins_h = pins_h.split('\n')
  404. list_start_found = False
  405. possible_envs = None
  406. for i, line in enumerate(pins_h):
  407. if 0 < line.find("Unknown MOTHERBOARD value set in Configuration.h"):
  408. invalid_board()
  409. if list_start_found == False and 0 < line.find('1280'):
  410. list_start_found = True
  411. elif list_start_found == False: # skip lines until find start of CPU list
  412. continue
  413. # Use a regex to find the board. Make sure it is surrounded by separators so the full boardname
  414. # will be matched, even if multiple exist in a single MB macro. This avoids problems with boards
  415. # such as MALYAN_M200 and MALYAN_M200_V2 where one board is a substring of the other.
  416. if re.search(r'MB.*[\(, ]' + board_name + r'[, \)]', line):
  417. # need to look at the next line for environment info
  418. possible_envs = re.findall(r'env:([^ ]+)', pins_h[i + 1])
  419. break
  420. return possible_envs
  421. # get environment to be used for the build
  422. # return: environment
  423. def get_env(board_name, ver_Marlin):
  424. def no_environment():
  425. print('ERROR - no environment for this board')
  426. print(board_name)
  427. raise SystemExit(0) # no environment so quit
  428. possible_envs = get_starting_env(board_name, ver_Marlin)
  429. if not possible_envs:
  430. no_environment()
  431. # Proceed to ask questions based on the available environments to filter down to a smaller list.
  432. # If more then one remains after this filtering the user will be prompted to choose between
  433. # all remaining options.
  434. # Filter selection based on CPU choice
  435. CPU_questions = [
  436. {'options':['1280', '2560'], 'text':'1280 or 2560 CPU?', 'default':2},
  437. {'options':['644', '1284'], 'text':'644 or 1284 CPU?', 'default':2},
  438. {'options':['STM32F103RC', 'STM32F103RE'], 'text':'MCU Type?', 'default':1}]
  439. for question in CPU_questions:
  440. if any(question['options'][0] in env for env in possible_envs) and any(question['options'][1] in env for env in possible_envs):
  441. get_answer(board_name, question['text'], [question['options'][0], question['options'][1]], question['default'])
  442. possible_envs = [env for env in possible_envs if question['options'][get_answer_val - 1] in env]
  443. # Choose which STM32 framework to use, if both are available
  444. if [env for env in possible_envs if '_maple' in env] and [env for env in possible_envs if '_maple' not in env]:
  445. get_answer(board_name, 'Which STM32 Framework should be used?', ['ST STM32 (Preferred)', 'Maple (Deprecated)'])
  446. if 1 == get_answer_val:
  447. possible_envs = [env for env in possible_envs if '_maple' not in env]
  448. else:
  449. possible_envs = [env for env in possible_envs if '_maple' in env]
  450. # Both USB and non-USB STM32 options exist, filter based on these
  451. if any('STM32F103R' in env for env in possible_envs) and any('_USB' in env for env in possible_envs) and any('_USB' not in env for env in possible_envs):
  452. get_answer(board_name, 'USB Support?', ['USB', 'No USB'])
  453. if 1 == get_answer_val:
  454. possible_envs = [env for env in possible_envs if '_USB' in env]
  455. else:
  456. possible_envs = [env for env in possible_envs if '_USB' not in env]
  457. if not possible_envs:
  458. no_environment()
  459. if len(possible_envs) == 1:
  460. return possible_envs[0] # only one environment so finished
  461. target_env = None
  462. # A few environments require special behavior
  463. if 'LPC1768' in possible_envs:
  464. if build_type == 'traceback' or (build_type == 'clean' and get_build_last() == 'LPC1768_debug_and_upload'):
  465. target_env = 'LPC1768_debug_and_upload'
  466. else:
  467. target_env = 'LPC1768'
  468. elif 'DUE' in possible_envs:
  469. target_env = 'DUE'
  470. if build_type == 'traceback' or (build_type == 'clean' and get_build_last() == 'DUE_debug'):
  471. target_env = 'DUE_debug'
  472. elif 'DUE_USB' in possible_envs:
  473. get_answer(board_name, 'DUE Download Port?', ['(Native) USB port', 'Programming port'])
  474. if 1 == get_answer_val:
  475. target_env = 'DUE_USB'
  476. else:
  477. target_env = 'DUE'
  478. else:
  479. options = possible_envs
  480. # Perform some substitutions for environment names which follow a consistent
  481. # naming pattern and are very commonly used. This is fragile code, and replacements
  482. # should only be made here for stable environments unlikely to change often.
  483. for i, option in enumerate(options):
  484. if 'melzi' in option:
  485. options[i] = 'Melzi'
  486. elif 'sanguino1284p' in option:
  487. options[i] = 'sanguino1284p'
  488. if 'optiboot' in option:
  489. options[i] = options[i] + ' (Optiboot Bootloader)'
  490. if 'optimized' in option:
  491. options[i] = options[i] + ' (Optimized for Size)'
  492. get_answer(board_name, 'Which environment?', options)
  493. target_env = possible_envs[get_answer_val - 1]
  494. if build_type == 'traceback' and target_env != 'LPC1768_debug_and_upload' and target_env != 'DUE_debug' and Marlin_ver == 2:
  495. print("ERROR - this board isn't setup for traceback")
  496. print('board_name: ', board_name)
  497. print('target_env: ', target_env)
  498. raise SystemExit(0)
  499. return target_env
  500. # end - get_env
  501. # puts screen text into queue so that the parent thread can fetch the data from this thread
  502. if python_ver == 2:
  503. import Queue as queue
  504. else:
  505. import queue as queue
  506. IO_queue = queue.Queue()
  507. #PIO_queue = queue.Queue() not used!
  508. def write_to_screen_queue(text, format_tag='normal'):
  509. double_in = [text, format_tag]
  510. IO_queue.put(double_in, block=False)
  511. #
  512. # send one line to the terminal screen with syntax highlighting
  513. #
  514. # input: unformatted text, flags from previous run
  515. # return: formatted text ready to go to the terminal, flags from this run
  516. #
  517. # This routine remembers the status from call to call because previous
  518. # lines can affect how the current line is highlighted
  519. #
  520. # 'static' variables - init here and then keep updating them from within print_line
  521. warning = False
  522. warning_FROM = False
  523. error = False
  524. standard = True
  525. prev_line_COM = False
  526. next_line_warning = False
  527. warning_continue = False
  528. line_counter = 0
  529. def line_print(line_input):
  530. global warning
  531. global warning_FROM
  532. global error
  533. global standard
  534. global prev_line_COM
  535. global next_line_warning
  536. global warning_continue
  537. global line_counter
  538. # all '0' elements must precede all '1' elements or they'll be skipped
  539. platformio_highlights = [
  540. ['Environment', 0, 'highlight_blue'], ['[SKIP]', 1, 'warning'], ['[IGNORED]', 1, 'warning'], ['[ERROR]', 1, 'error'],
  541. ['[FAILED]', 1, 'error'], ['[SUCCESS]', 1, 'highlight_green']
  542. ]
  543. def write_to_screen_with_replace(text, highlights): # search for highlights & split line accordingly
  544. did_something = False
  545. for highlight in highlights:
  546. found = text.find(highlight[0])
  547. if did_something == True:
  548. break
  549. if found >= 0:
  550. did_something = True
  551. if 0 == highlight[1]:
  552. found_1 = text.find(' ')
  553. found_tab = text.find('\t')
  554. if not (0 <= found_1 <= found_tab):
  555. found_1 = found_tab
  556. write_to_screen_queue(text[:found_1 + 1])
  557. for highlight_2 in highlights:
  558. if highlight[0] == highlight_2[0]:
  559. continue
  560. found = text.find(highlight_2[0])
  561. if found >= 0:
  562. found_space = text.find(' ', found_1 + 1)
  563. found_tab = text.find('\t', found_1 + 1)
  564. if not (0 <= found_space <= found_tab):
  565. found_space = found_tab
  566. found_right = text.find(']', found + 1)
  567. write_to_screen_queue(text[found_1 + 1:found_space + 1], highlight[2])
  568. write_to_screen_queue(text[found_space + 1:found + 1])
  569. write_to_screen_queue(text[found + 1:found_right], highlight_2[2])
  570. write_to_screen_queue(text[found_right:] + '\n')
  571. break
  572. break
  573. if 1 == highlight[1]:
  574. found_right = text.find(']', found + 1)
  575. write_to_screen_queue(text[:found + 1])
  576. write_to_screen_queue(text[found + 1:found_right], highlight[2])
  577. write_to_screen_queue(text[found_right:] + '\n' + '\n')
  578. break
  579. if did_something == False:
  580. r_loc = text.find('\r') + 1
  581. if 0 < r_loc < len(text): # need to split this line
  582. text = text.split('\r')
  583. for line in text:
  584. if line != '':
  585. write_to_screen_queue(line + '\n')
  586. else:
  587. write_to_screen_queue(text + '\n')
  588. # end - write_to_screen_with_replace
  589. # scan the line
  590. line_counter = line_counter + 1
  591. max_search = len(line_input)
  592. if max_search > 3:
  593. max_search = 3
  594. beginning = line_input[:max_search]
  595. # set flags
  596. if 0 < line_input.find(': warning: '): # start of warning block
  597. warning = True
  598. warning_FROM = False
  599. error = False
  600. standard = False
  601. prev_line_COM = False
  602. prev_line_COM = False
  603. warning_continue = True
  604. if 0 < line_input.find('Thank you') or 0 < line_input.find('SUMMARY'):
  605. warning = False #standard line found
  606. warning_FROM = False
  607. error = False
  608. standard = True
  609. prev_line_COM = False
  610. warning_continue = False
  611. elif beginning == 'War' or \
  612. beginning == '#er' or \
  613. beginning == 'In ' or \
  614. (beginning != 'Com' and prev_line_COM == True and not(beginning == 'Arc' or beginning == 'Lin' or beginning == 'Ind') or \
  615. next_line_warning == True):
  616. warning = True #warning found
  617. warning_FROM = False
  618. error = False
  619. standard = False
  620. prev_line_COM = False
  621. elif beginning == 'Com' or \
  622. beginning == 'Ver' or \
  623. beginning == ' [E' or \
  624. beginning == 'Rem' or \
  625. beginning == 'Bui' or \
  626. beginning == 'Ind' or \
  627. beginning == 'PLA':
  628. warning = False #standard line found
  629. warning_FROM = False
  630. error = False
  631. standard = True
  632. prev_line_COM = False
  633. warning_continue = False
  634. elif beginning == '***':
  635. warning = False # error found
  636. warning_FROM = False
  637. error = True
  638. standard = False
  639. prev_line_COM = False
  640. elif 0 < line_input.find(': error:') or \
  641. 0 < line_input.find(': fatal error:'): # start of warning /error block
  642. warning = False # error found
  643. warning_FROM = False
  644. error = True
  645. standard = False
  646. prev_line_COM = False
  647. warning_continue = True
  648. elif beginning == 'fro' and warning == True or \
  649. beginning == '.pi' : # start of warning /error block
  650. warning_FROM = True
  651. prev_line_COM = False
  652. warning_continue = True
  653. elif warning_continue == True:
  654. warning = True
  655. warning_FROM = False # keep the warning status going until find a standard line or an error
  656. error = False
  657. standard = False
  658. prev_line_COM = False
  659. warning_continue = True
  660. else:
  661. warning = False # unknown so assume standard line
  662. warning_FROM = False
  663. error = False
  664. standard = True
  665. prev_line_COM = False
  666. warning_continue = False
  667. if beginning == 'Com':
  668. prev_line_COM = True
  669. # print based on flags
  670. if standard == True:
  671. write_to_screen_with_replace(line_input, platformio_highlights) #print white on black with substitutions
  672. if warning == True:
  673. write_to_screen_queue(line_input + '\n', 'warning')
  674. if error == True:
  675. write_to_screen_queue(line_input + '\n', 'error')
  676. # end - line_print
  677. ##########################################################################
  678. # #
  679. # run Platformio #
  680. # #
  681. ##########################################################################
  682. # build platformio run -e target_env
  683. # clean platformio run --target clean -e target_env
  684. # upload platformio run --target upload -e target_env
  685. # traceback platformio run --target upload -e target_env
  686. # program platformio run --target program -e target_env
  687. # test platformio test upload -e target_env
  688. # remote platformio remote run --target upload -e target_env
  689. # debug platformio debug -e target_env
  690. def sys_PIO():
  691. ##########################################################################
  692. # #
  693. # run Platformio inside the same shell as this Python script #
  694. # #
  695. ##########################################################################
  696. global build_type
  697. global target_env
  698. import os
  699. print('build_type: ', build_type)
  700. print('starting platformio')
  701. if build_type == 'build':
  702. # pio_result = os.system("echo -en '\033c'")
  703. pio_result = os.system('platformio run -e ' + target_env)
  704. elif build_type == 'clean':
  705. pio_result = os.system('platformio run --target clean -e ' + target_env)
  706. elif build_type == 'upload':
  707. pio_result = os.system('platformio run --target upload -e ' + target_env)
  708. elif build_type == 'traceback':
  709. pio_result = os.system('platformio run --target upload -e ' + target_env)
  710. elif build_type == 'program':
  711. pio_result = os.system('platformio run --target program -e ' + target_env)
  712. elif build_type == 'test':
  713. pio_result = os.system('platformio test upload -e ' + target_env)
  714. elif build_type == 'remote':
  715. pio_result = os.system('platformio remote run --target program -e ' + target_env)
  716. elif build_type == 'debug':
  717. pio_result = os.system('platformio debug -e ' + target_env)
  718. else:
  719. print('ERROR - unknown build type: ', build_type)
  720. raise SystemExit(0) # kill everything
  721. # stream output from subprocess and split it into lines
  722. #for line in iter(pio_subprocess.stdout.readline, ''):
  723. # line_print(line.replace('\n', ''))
  724. # append info used to run PlatformIO
  725. # write_to_screen_queue('\nBoard name: ' + board_name + '\n') # put build info at the bottom of the screen
  726. # write_to_screen_queue('Build type: ' + build_type + '\n')
  727. # write_to_screen_queue('Environment used: ' + target_env + '\n')
  728. # write_to_screen_queue(str(datetime.now()) + '\n')
  729. # end - sys_PIO
  730. def run_PIO(dummy):
  731. global build_type
  732. global target_env
  733. global board_name
  734. print('build_type: ', build_type)
  735. import subprocess
  736. print('starting platformio')
  737. if build_type == 'build':
  738. # platformio run -e target_env
  739. # combine stdout & stderr so all compile messages are included
  740. pio_subprocess = subprocess.Popen(
  741. ['platformio', 'run', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  742. )
  743. elif build_type == 'clean':
  744. # platformio run --target clean -e target_env
  745. # combine stdout & stderr so all compile messages are included
  746. pio_subprocess = subprocess.Popen(
  747. ['platformio', 'run', '--target', 'clean', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  748. )
  749. elif build_type == 'upload':
  750. # platformio run --target upload -e target_env
  751. # combine stdout & stderr so all compile messages are included
  752. pio_subprocess = subprocess.Popen(
  753. ['platformio', 'run', '--target', 'upload', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  754. )
  755. elif build_type == 'traceback':
  756. # platformio run --target upload -e target_env - select the debug environment if there is one
  757. # combine stdout & stderr so all compile messages are included
  758. pio_subprocess = subprocess.Popen(
  759. ['platformio', 'run', '--target', 'upload', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  760. )
  761. elif build_type == 'program':
  762. # platformio run --target program -e target_env
  763. # combine stdout & stderr so all compile messages are included
  764. pio_subprocess = subprocess.Popen(
  765. ['platformio', 'run', '--target', 'program', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  766. )
  767. elif build_type == 'test':
  768. #platformio test upload -e target_env
  769. # combine stdout & stderr so all compile messages are included
  770. pio_subprocess = subprocess.Popen(
  771. ['platformio', 'test', 'upload', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  772. )
  773. elif build_type == 'remote':
  774. # platformio remote run --target upload -e target_env
  775. # combine stdout & stderr so all compile messages are included
  776. pio_subprocess = subprocess.Popen(
  777. ['platformio', 'remote', 'run', '--target', 'program', '-e', target_env],
  778. stdout=subprocess.PIPE,
  779. stderr=subprocess.STDOUT
  780. )
  781. elif build_type == 'debug':
  782. # platformio debug -e target_env
  783. # combine stdout & stderr so all compile messages are included
  784. pio_subprocess = subprocess.Popen(
  785. ['platformio', 'debug', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
  786. )
  787. else:
  788. print('ERROR - unknown build type: ', build_type)
  789. raise SystemExit(0) # kill everything
  790. # stream output from subprocess and split it into lines
  791. if python_ver == 2:
  792. for line in iter(pio_subprocess.stdout.readline, ''):
  793. line_print(line.replace('\n', ''))
  794. else:
  795. for line in iter(pio_subprocess.stdout.readline, b''):
  796. line = line.decode('utf-8')
  797. line_print(line.replace('\n', ''))
  798. # append info used to run PlatformIO
  799. write_to_screen_queue('\nBoard name: ' + board_name + '\n') # put build info at the bottom of the screen
  800. write_to_screen_queue('Build type: ' + build_type + '\n')
  801. write_to_screen_queue('Environment used: ' + target_env + '\n')
  802. write_to_screen_queue(str(datetime.now()) + '\n')
  803. # end - run_PIO
  804. ########################################################################
  805. import threading
  806. if python_ver == 2:
  807. import Tkinter as tk
  808. import Queue as queue
  809. import ttk
  810. from Tkinter import Tk, Frame, Text, Scrollbar, Menu
  811. #from tkMessageBox import askokcancel this is not used: removed
  812. import tkFileDialog as fileDialog
  813. else:
  814. import tkinter as tk
  815. import queue as queue
  816. from tkinter import ttk, Tk, Frame, Text, Menu
  817. import sys
  818. que = queue.Queue()
  819. #IO_queue = queue.Queue()
  820. class output_window(Text):
  821. # based on Super Text
  822. global continue_updates
  823. continue_updates = True
  824. global search_position
  825. search_position = '' # start with invalid search position
  826. global error_found
  827. error_found = False # are there any errors?
  828. def __init__(self):
  829. self.root = tk.Tk()
  830. self.root.attributes("-topmost", True)
  831. self.frame = tk.Frame(self.root)
  832. self.frame.pack(fill='both', expand=True)
  833. # text widget
  834. #self.text = tk.Text(self.frame, borderwidth=3, relief="sunken")
  835. Text.__init__(self, self.frame, borderwidth=3, relief="sunken")
  836. self.config(tabs=(400, )) # configure Text widget tab stops
  837. self.config(background='black', foreground='white', font=("consolas", 12), wrap='word', undo='True')
  838. #self.config(background = 'black', foreground = 'white', font= ("consolas", 12), wrap = 'none', undo = 'True')
  839. self.config(height=24, width=100)
  840. self.config(insertbackground='pale green') # keyboard insertion point
  841. self.pack(side='left', fill='both', expand=True)
  842. self.tag_config('normal', foreground='white')
  843. self.tag_config('warning', foreground='yellow')
  844. self.tag_config('error', foreground='red')
  845. self.tag_config('highlight_green', foreground='green')
  846. self.tag_config('highlight_blue', foreground='cyan')
  847. self.tag_config('error_highlight_inactive', background='dim gray')
  848. self.tag_config('error_highlight_active', background='light grey')
  849. self.bind_class("Text", "<Control-a>", self.select_all) # required in windows, works in others
  850. self.bind_all("<Control-Shift-E>", self.scroll_errors)
  851. self.bind_class("<Control-Shift-R>", self.rebuild)
  852. # scrollbar
  853. scrb = tk.Scrollbar(self.frame, orient='vertical', command=self.yview)
  854. self.config(yscrollcommand=scrb.set)
  855. scrb.pack(side='right', fill='y')
  856. #self.scrb_Y = tk.Scrollbar(self.frame, orient='vertical', command=self.yview)
  857. #self.scrb_Y.config(yscrollcommand=self.scrb_Y.set)
  858. #self.scrb_Y.pack(side='right', fill='y')
  859. #self.scrb_X = tk.Scrollbar(self.frame, orient='horizontal', command=self.xview)
  860. #self.scrb_X.config(xscrollcommand=self.scrb_X.set)
  861. #self.scrb_X.pack(side='bottom', fill='x')
  862. #scrb_X = tk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.xview) # tk.HORIZONTAL now have a horizsontal scroll bar BUT... shrinks it to a postage stamp and hides far right behind the vertical scroll bar
  863. #self.config(xscrollcommand=scrb_X.set)
  864. #scrb_X.pack(side='bottom', fill='x')
  865. #scrb= tk.Scrollbar(self, orient='vertical', command=self.yview)
  866. #self.config(yscrollcommand=scrb.set)
  867. #scrb.pack(side='right', fill='y')
  868. #self.config(height = 240, width = 1000) # didn't get the size baCK TO NORMAL
  869. #self.pack(side='left', fill='both', expand=True) # didn't get the size baCK TO NORMAL
  870. # pop-up menu
  871. self.popup = tk.Menu(self, tearoff=0)
  872. self.popup.add_command(label='Copy', command=self._copy)
  873. self.popup.add_command(label='Paste', command=self._paste)
  874. self.popup.add_separator()
  875. self.popup.add_command(label='Cut', command=self._cut)
  876. self.popup.add_separator()
  877. self.popup.add_command(label='Select All', command=self._select_all)
  878. self.popup.add_command(label='Clear All', command=self._clear_all)
  879. self.popup.add_separator()
  880. self.popup.add_command(label='Save As', command=self._file_save_as)
  881. self.popup.add_separator()
  882. #self.popup.add_command(label='Repeat Build(CTL-shift-r)', command=self._rebuild)
  883. self.popup.add_command(label='Repeat Build', command=self._rebuild)
  884. self.popup.add_separator()
  885. self.popup.add_command(label='Scroll Errors (CTL-shift-e)', command=self._scroll_errors)
  886. self.popup.add_separator()
  887. self.popup.add_command(label='Open File at Cursor', command=self._open_selected_file)
  888. if current_OS == 'Darwin': # MAC
  889. self.bind('<Button-2>', self._show_popup) # macOS only
  890. else:
  891. self.bind('<Button-3>', self._show_popup) # Windows & Linux
  892. # threading & subprocess section
  893. def start_thread(self, ):
  894. global continue_updates
  895. # create then start a secondary thread to run an arbitrary function
  896. # must have at least one argument
  897. self.secondary_thread = threading.Thread(target=lambda q, arg1: q.put(run_PIO(arg1)), args=(que, ''))
  898. self.secondary_thread.start()
  899. continue_updates = True
  900. # check the Queue in 50ms
  901. self.root.after(50, self.check_thread)
  902. self.root.after(50, self.update)
  903. def check_thread(self): # wait for user to kill the window
  904. global continue_updates
  905. if continue_updates == True:
  906. self.root.after(10, self.check_thread)
  907. def update(self):
  908. global continue_updates
  909. if continue_updates == True:
  910. self.root.after(10, self.update) #method is called every 50ms
  911. temp_text = ['0', '0']
  912. if IO_queue.empty():
  913. if not (self.secondary_thread.is_alive()):
  914. continue_updates = False # queue is exhausted and thread is dead so no need for further updates
  915. else:
  916. try:
  917. temp_text = IO_queue.get(block=False)
  918. except queue.Empty:
  919. continue_updates = False # queue is exhausted so no need for further updates
  920. else:
  921. self.insert('end', temp_text[0], temp_text[1])
  922. self.see("end") # make the last line visible (scroll text off the top)
  923. # text editing section
  924. def _scroll_errors(self):
  925. global search_position
  926. global error_found
  927. if search_position == '': # first time so highlight all errors
  928. countVar = tk.IntVar()
  929. search_position = '1.0'
  930. search_count = 0
  931. while search_position != '' and search_count < 100:
  932. search_position = self.search("error", search_position, stopindex="end", count=countVar, nocase=1)
  933. search_count = search_count + 1
  934. if search_position != '':
  935. error_found = True
  936. end_pos = '{}+{}c'.format(search_position, 5)
  937. self.tag_add("error_highlight_inactive", search_position, end_pos)
  938. search_position = '{}+{}c'.format(search_position, 1) # point to the next character for new search
  939. else:
  940. break
  941. if error_found:
  942. if search_position == '':
  943. search_position = self.search("error", '1.0', stopindex="end", nocase=1) # new search
  944. else: # remove active highlight
  945. end_pos = '{}+{}c'.format(search_position, 5)
  946. start_pos = '{}+{}c'.format(search_position, -1)
  947. self.tag_remove("error_highlight_active", start_pos, end_pos)
  948. search_position = self.search(
  949. "error", search_position, stopindex="end", nocase=1
  950. ) # finds first occurrence AGAIN on the first time through
  951. if search_position == "": # wrap around
  952. search_position = self.search("error", '1.0', stopindex="end", nocase=1)
  953. end_pos = '{}+{}c'.format(search_position, 5)
  954. self.tag_add("error_highlight_active", search_position, end_pos) # add active highlight
  955. self.see(search_position)
  956. search_position = '{}+{}c'.format(search_position, 1) # point to the next character for new search
  957. def scroll_errors(self, event):
  958. self._scroll_errors()
  959. def _rebuild(self):
  960. #global board_name
  961. #global Marlin_ver
  962. #global target_env
  963. #board_name, Marlin_ver = get_board_name()
  964. #target_env = get_env(board_name, Marlin_ver)
  965. self.start_thread()
  966. def rebuild(self, event):
  967. print("event happened")
  968. self._rebuild()
  969. def _open_selected_file(self):
  970. current_line = self.index('insert')
  971. line_start = current_line[:current_line.find('.')] + '.0'
  972. line_end = current_line[:current_line.find('.')] + '.200'
  973. self.mark_set("path_start", line_start)
  974. self.mark_set("path_end", line_end)
  975. path = self.get("path_start", "path_end")
  976. from_loc = path.find('from ')
  977. colon_loc = path.find(': ')
  978. if 0 <= from_loc and ((colon_loc == -1) or (from_loc < colon_loc)):
  979. path = path[from_loc + 5:]
  980. if 0 <= colon_loc:
  981. path = path[:colon_loc]
  982. if 0 <= path.find('\\') or 0 <= path.find('/'): # make sure it really contains a path
  983. open_file(path)
  984. def _file_save_as(self):
  985. self.filename = fileDialog.asksaveasfilename(defaultextension='.txt')
  986. f = open(self.filename, 'w')
  987. f.write(self.get('1.0', 'end'))
  988. f.close()
  989. def copy(self, event):
  990. try:
  991. selection = self.get(*self.tag_ranges('sel'))
  992. self.clipboard_clear()
  993. self.clipboard_append(selection)
  994. except TypeError:
  995. pass
  996. def cut(self, event):
  997. try:
  998. selection = self.get(*self.tag_ranges('sel'))
  999. self.clipboard_clear()
  1000. self.clipboard_append(selection)
  1001. self.delete(*self.tag_ranges('sel'))
  1002. except TypeError:
  1003. pass
  1004. def _show_popup(self, event):
  1005. '''right-click popup menu'''
  1006. if self.root.focus_get() != self:
  1007. self.root.focus_set()
  1008. try:
  1009. self.popup.tk_popup(event.x_root, event.y_root, 0)
  1010. finally:
  1011. self.popup.grab_release()
  1012. def _cut(self):
  1013. try:
  1014. selection = self.get(*self.tag_ranges('sel'))
  1015. self.clipboard_clear()
  1016. self.clipboard_append(selection)
  1017. self.delete(*self.tag_ranges('sel'))
  1018. except TypeError:
  1019. pass
  1020. def cut(self, event):
  1021. self._cut()
  1022. def _copy(self):
  1023. try:
  1024. selection = self.get(*self.tag_ranges('sel'))
  1025. self.clipboard_clear()
  1026. self.clipboard_append(selection)
  1027. except TypeError:
  1028. pass
  1029. def copy(self, event):
  1030. self._copy()
  1031. def _paste(self):
  1032. self.insert('insert', self.selection_get(selection='CLIPBOARD'))
  1033. def _select_all(self):
  1034. self.tag_add('sel', '1.0', 'end')
  1035. def select_all(self, event):
  1036. self.tag_add('sel', '1.0', 'end')
  1037. def _clear_all(self):
  1038. #'''erases all text'''
  1039. #
  1040. #isok = askokcancel('Clear All', 'Erase all text?', frame=self,
  1041. # default='ok')
  1042. #if isok:
  1043. # self.delete('1.0', 'end')
  1044. self.delete('1.0', 'end')
  1045. # end - output_window
  1046. def main():
  1047. ##########################################################################
  1048. # #
  1049. # main program #
  1050. # #
  1051. ##########################################################################
  1052. global build_type
  1053. global target_env
  1054. global board_name
  1055. global Marlin_ver
  1056. board_name, Marlin_ver = get_board_name()
  1057. target_env = get_env(board_name, Marlin_ver)
  1058. # Re-use the VSCode terminal, if possible
  1059. if os.environ.get('PLATFORMIO_CALLER', '') == 'vscode':
  1060. sys_PIO()
  1061. else:
  1062. auto_build = output_window()
  1063. auto_build.start_thread() # executes the "run_PIO" function
  1064. auto_build.root.mainloop()
  1065. if __name__ == '__main__':
  1066. main()