|
@@ -0,0 +1,935 @@
|
|
1
|
+#######################################
|
|
2
|
+#
|
|
3
|
+# Marlin 3D Printer Firmware
|
|
4
|
+# Copyright (C) 2018 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
|
5
|
+#
|
|
6
|
+# Based on Sprinter and grbl.
|
|
7
|
+# Copyright (C) 2011 Camiel Gubbels / Erik van der Zalm
|
|
8
|
+#
|
|
9
|
+# This program is free software: you can redistribute it and/or modify
|
|
10
|
+# it under the terms of the GNU General Public License as published by
|
|
11
|
+# the Free Software Foundation, either version 3 of the License, or
|
|
12
|
+# (at your option) any later version.
|
|
13
|
+#
|
|
14
|
+# This program is distributed in the hope that it will be useful,
|
|
15
|
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+# GNU General Public License for more details.
|
|
18
|
+#
|
|
19
|
+# You should have received a copy of the GNU General Public License
|
|
20
|
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
21
|
+#
|
|
22
|
+#######################################
|
|
23
|
+
|
|
24
|
+#######################################
|
|
25
|
+#
|
|
26
|
+# Description: script to automate PlatformIO builds
|
|
27
|
+# CLI: python auto_build.py build_option
|
|
28
|
+# build_option (required)
|
|
29
|
+# build executes -> platformio run -e target_env
|
|
30
|
+# clean executes -> platformio run --target clean -e target_env
|
|
31
|
+# upload executes -> platformio run --target upload -e target_env
|
|
32
|
+# traceback executes -> platformio run --target upload -e target_env
|
|
33
|
+# program executes -> platformio run --target program -e target_env
|
|
34
|
+# test executes -> platformio test upload -e target_env
|
|
35
|
+# remote executes -> platformio remote run --target upload -e target_env
|
|
36
|
+# debug executes -> platformio debug -e target_env
|
|
37
|
+#
|
|
38
|
+# 'traceback' just uses the debug variant of the target environment if one exists
|
|
39
|
+#
|
|
40
|
+#######################################
|
|
41
|
+
|
|
42
|
+#######################################
|
|
43
|
+#
|
|
44
|
+# General program flow
|
|
45
|
+#
|
|
46
|
+# 1. Scans Configuration.h for the motherboard name and Marlin version.
|
|
47
|
+# 2. Scans pins.h for the motherboard.
|
|
48
|
+# returns the CPU(s) and platformio environment(s) used by the motherboard
|
|
49
|
+# 3. If further info is needed then a popup gets it from the user.
|
|
50
|
+# 4. The OUTPUT_WINDOW class creates a window to display the output of the PlatformIO program.
|
|
51
|
+# 5. A thread is created by the OUTPUT_WINDOW class in order to execute the RUN_PIO function.
|
|
52
|
+# 6. The RUN_PIO function uses a subprocess to run the CLI version of PlatformIO.
|
|
53
|
+# 7. The "iter(pio_subprocess.stdout.readline, '')" function is used to stream the output of
|
|
54
|
+# PlatformIO back to the RUN_PIO function.
|
|
55
|
+# 8. Each line returned from PlatformIO is formatted to match the color coding seen in the
|
|
56
|
+# PlatformIO GUI.
|
|
57
|
+# 9. If there is a color change within a line then the line is broken at each color change
|
|
58
|
+# and sent separately.
|
|
59
|
+# 10. Each formatted segment (could be a full line or a split line) is put into the queue
|
|
60
|
+# IO_queue as it arrives from the platformio subprocess.
|
|
61
|
+# 11. The OUTPUT_WINDOW class periodically samples IO_queue. If data is available then it
|
|
62
|
+# is written to the window.
|
|
63
|
+# 12. The window stays open until the user closes it.
|
|
64
|
+# 13. The OUTPUT_WINDOW class continues to execute as long as the window is open. This allows
|
|
65
|
+# copying, saving, scrolling of the window. A right click popup is available.
|
|
66
|
+#
|
|
67
|
+#######################################
|
|
68
|
+
|
|
69
|
+import sys
|
|
70
|
+import os
|
|
71
|
+
|
|
72
|
+num_args = len(sys.argv)
|
|
73
|
+if num_args > 1:
|
|
74
|
+ build_type = str(sys.argv[1])
|
|
75
|
+else:
|
|
76
|
+ print 'Please specify build type'
|
|
77
|
+ exit()
|
|
78
|
+
|
|
79
|
+print'build_type: ', build_type
|
|
80
|
+
|
|
81
|
+print '\nWorking\n'
|
|
82
|
+
|
|
83
|
+python_ver = sys.version_info[0] # major version - 2 or 3
|
|
84
|
+
|
|
85
|
+if python_ver == 2:
|
|
86
|
+ print "python version " + str(sys.version_info[0]) + "." + str(sys.version_info[1]) + "." + str(sys.version_info[2])
|
|
87
|
+else:
|
|
88
|
+ print "python version " + str(sys.version_info[0])
|
|
89
|
+ print "This script only runs under python 2"
|
|
90
|
+ exit()
|
|
91
|
+
|
|
92
|
+#########
|
|
93
|
+# Python 2 error messages:
|
|
94
|
+# Can't find a usable init.tcl in the following directories ...
|
|
95
|
+# error "invalid command name "tcl_findLibrary""
|
|
96
|
+#
|
|
97
|
+# Fix for the above errors on my Win10 system:
|
|
98
|
+# search all init.tcl files for the line "package require -exact Tcl" that has the highest 8.5.x number
|
|
99
|
+# copy it into the first directory listed in the error messages
|
|
100
|
+# set the environmental variables TCLLIBPATH and TCL_LIBRARY to the directory where you found the init.tcl file
|
|
101
|
+# reboot
|
|
102
|
+#########
|
|
103
|
+
|
|
104
|
+#globals
|
|
105
|
+target_env = ''
|
|
106
|
+board_name = ''
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+##########################################################################################
|
|
111
|
+#
|
|
112
|
+# popup to get input from user
|
|
113
|
+#
|
|
114
|
+##########################################################################################
|
|
115
|
+
|
|
116
|
+def get_answer(board_name, cpu_label_txt, cpu_a_txt, cpu_b_txt):
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+ if python_ver == 2:
|
|
120
|
+ import Tkinter as tk
|
|
121
|
+ else:
|
|
122
|
+ import tkinter as tk
|
|
123
|
+
|
|
124
|
+ def CPU_exit_3(): # forward declare functions
|
|
125
|
+
|
|
126
|
+ CPU_exit_3_()
|
|
127
|
+ def CPU_exit_4():
|
|
128
|
+
|
|
129
|
+ CPU_exit_4_()
|
|
130
|
+ def kill_session():
|
|
131
|
+ kill_session_()
|
|
132
|
+
|
|
133
|
+ root_get_answer = tk.Tk()
|
|
134
|
+
|
|
135
|
+ root_get_answer.chk_state_1 = 1 # declare variables used by TK and enable
|
|
136
|
+
|
|
137
|
+ chk_state_1 = 0 # set initial state of check boxes
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+ global get_answer_val
|
|
141
|
+ get_answer_val = 2 # return get_answer_val, set default to match chk_state_1 default
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+ l1 = tk.Label(text=board_name,
|
|
145
|
+ fg = "light green",
|
|
146
|
+ bg = "dark green",
|
|
147
|
+ font = "Helvetica 12 bold").grid(row=1)
|
|
148
|
+
|
|
149
|
+ l2 = tk.Label(text=cpu_label_txt,
|
|
150
|
+ fg = "light green",
|
|
151
|
+ bg = "dark green",
|
|
152
|
+ font = "Helvetica 16 bold italic").grid(row=2)
|
|
153
|
+
|
|
154
|
+ b4 = tk.Checkbutton(text=cpu_a_txt,
|
|
155
|
+ fg = "black",
|
|
156
|
+ font = "Times 20 bold ",
|
|
157
|
+ variable=chk_state_1, onvalue=1, offvalue=0,
|
|
158
|
+
|
|
159
|
+ command = CPU_exit_3).grid(row=3)
|
|
160
|
+
|
|
161
|
+ b5 = tk.Checkbutton(text=cpu_b_txt,
|
|
162
|
+ fg = "black",
|
|
163
|
+ font = "Times 20 bold ",
|
|
164
|
+ variable=chk_state_1, onvalue=0, offvalue=1,
|
|
165
|
+
|
|
166
|
+ command = CPU_exit_4).grid(row=4) # use same variable but inverted so they will track
|
|
167
|
+ b6 = tk.Button(text="CONFIRM",
|
|
168
|
+ fg = "blue",
|
|
169
|
+ font = "Times 20 bold ",
|
|
170
|
+ command = root_get_answer.destroy).grid(row=5, pady=4)
|
|
171
|
+
|
|
172
|
+ b7 = tk.Button(text="CANCEL",
|
|
173
|
+ fg = "red",
|
|
174
|
+ font = "Times 12 bold ",
|
|
175
|
+ command = kill_session).grid(row=6, pady=4)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+ def CPU_exit_3_():
|
|
179
|
+ global get_answer_val
|
|
180
|
+ get_answer_val = 1
|
|
181
|
+
|
|
182
|
+ def CPU_exit_4_():
|
|
183
|
+ global get_answer_val
|
|
184
|
+ get_answer_val = 2
|
|
185
|
+
|
|
186
|
+ def kill_session_():
|
|
187
|
+ raise SystemExit(0) # kill everything
|
|
188
|
+
|
|
189
|
+ root_get_answer.mainloop()
|
|
190
|
+
|
|
191
|
+# end - get answer
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+def env_name_check(argument):
|
|
196
|
+ name_check = {
|
|
197
|
+ 'teensy35' : True,
|
|
198
|
+ 'teensy20' : True,
|
|
199
|
+ 'STM32F4' : True,
|
|
200
|
+ 'STM32F1' : True,
|
|
201
|
+ 'sanguino_atmega644p' : True,
|
|
202
|
+ 'sanguino_atmega1284p' : True,
|
|
203
|
+ 'rambo' : True,
|
|
204
|
+ 'melzi_optiboot' : True,
|
|
205
|
+ 'melzi' : True,
|
|
206
|
+ 'megaatmega2560' : True,
|
|
207
|
+ 'megaatmega1280' : True,
|
|
208
|
+ 'malyanm200' : True,
|
|
209
|
+ 'LPC1768' : True,
|
|
210
|
+ 'DUE_debug' : True,
|
|
211
|
+ 'DUE_USB' : True,
|
|
212
|
+ 'DUE' : True
|
|
213
|
+ }
|
|
214
|
+
|
|
215
|
+ return name_check.get(argument, False)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+# gets the last build environment
|
|
219
|
+def get_build_last():
|
|
220
|
+ env_last = ''
|
|
221
|
+ DIR_PWD = os.listdir('.')
|
|
222
|
+ if '.pioenvs' in DIR_PWD:
|
|
223
|
+ date_last = 0.0
|
|
224
|
+ DIR__pioenvs = os.listdir('.pioenvs')
|
|
225
|
+ for name in DIR__pioenvs:
|
|
226
|
+ if env_name_check(name):
|
|
227
|
+ DIR_temp = os.listdir('.pioenvs/' + name)
|
|
228
|
+ for names_temp in DIR_temp:
|
|
229
|
+ if 0 == names_temp.find('firmware.'):
|
|
230
|
+ date_temp = os.path.getmtime('.pioenvs/' + name + '/' + names_temp)
|
|
231
|
+ if date_temp > date_last:
|
|
232
|
+ date_last = date_temp
|
|
233
|
+ env_last = name
|
|
234
|
+ return env_last
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+# gets the board being built from the Configuration.h file
|
|
238
|
+# returns: board name, major version of Marlin being used (1 or 2)
|
|
239
|
+def get_board_name():
|
|
240
|
+ board_name = ''
|
|
241
|
+ # get board name
|
|
242
|
+
|
|
243
|
+ with open('Marlin/Configuration.h', 'r') as myfile:
|
|
244
|
+ Configuration_h = myfile.read()
|
|
245
|
+
|
|
246
|
+ Configuration_h = Configuration_h.split('\n')
|
|
247
|
+ Marlin_ver = 0 # set version to invalid number
|
|
248
|
+ for lines in Configuration_h:
|
|
249
|
+ if 0 == lines.find('#define CONFIGURATION_H_VERSION 01'):
|
|
250
|
+ Marlin_ver = 1
|
|
251
|
+ if 0 == lines.find('#define CONFIGURATION_H_VERSION 02'):
|
|
252
|
+ Marlin_ver = 2
|
|
253
|
+ board = lines.find(' BOARD_') + 1
|
|
254
|
+ motherboard = lines.find(' MOTHERBOARD ') + 1
|
|
255
|
+ define = lines.find('#define ')
|
|
256
|
+ comment = lines.find('//')
|
|
257
|
+ if (comment == -1 or comment > board) and \
|
|
258
|
+ board > motherboard and \
|
|
259
|
+ motherboard > define and \
|
|
260
|
+ define >= 0 :
|
|
261
|
+ spaces = lines.find(' ', board) # find the end of the board substring
|
|
262
|
+ if spaces == -1:
|
|
263
|
+ board_name = lines[board : ]
|
|
264
|
+ else:
|
|
265
|
+ board_name = lines[board : spaces]
|
|
266
|
+ break
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+ return board_name, Marlin_ver
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+# extract first environment name it finds after the start position
|
|
273
|
+# returns: environment name and position to start the next search from
|
|
274
|
+def get_env_from_line(line, start_position):
|
|
275
|
+ env = ''
|
|
276
|
+ next_position = -1
|
|
277
|
+ env_position = line.find('env:', start_position)
|
|
278
|
+ if 0 < env_position:
|
|
279
|
+ next_position = line.find(' ', env_position + 4)
|
|
280
|
+ if 0 < next_position:
|
|
281
|
+ env = line[env_position + 4 : next_position]
|
|
282
|
+ else:
|
|
283
|
+ env = line[env_position + 4 : ] # at the end of the line
|
|
284
|
+ return env, next_position
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+#scans pins.h for board name and returns the environment(s) it finds
|
|
289
|
+def get_starting_env(board_name_full, version):
|
|
290
|
+ # get environment starting point
|
|
291
|
+
|
|
292
|
+ if version == 1:
|
|
293
|
+ path = 'Marlin/pins.h'
|
|
294
|
+ if version == 2:
|
|
295
|
+ path = 'Marlin/src/pins/pins.h'
|
|
296
|
+ with open(path, 'r') as myfile:
|
|
297
|
+ pins_h = myfile.read()
|
|
298
|
+
|
|
299
|
+ board_name = board_name_full[ 6 : ] # only use the part after "BOARD_" since we're searching the pins.h file
|
|
300
|
+ pins_h = pins_h.split('\n')
|
|
301
|
+ environment = ''
|
|
302
|
+ board_line = ''
|
|
303
|
+ cpu_A = ''
|
|
304
|
+ cpu_B = ''
|
|
305
|
+ i = 0
|
|
306
|
+ list_start_found = False
|
|
307
|
+ for lines in pins_h:
|
|
308
|
+ i = i + 1 # i is always one ahead of the index into pins_h
|
|
309
|
+ if 0 < lines.find("Unknown MOTHERBOARD value set in Configuration.h"):
|
|
310
|
+ break # no more
|
|
311
|
+ if 0 < lines.find('1280'):
|
|
312
|
+ list_start_found = True
|
|
313
|
+ if list_start_found == False: # skip lines until find start of CPU list
|
|
314
|
+ continue
|
|
315
|
+ board = lines.find(board_name)
|
|
316
|
+ comment_start = lines.find('// ')
|
|
317
|
+ cpu_A_loc = comment_start
|
|
318
|
+ cpu_B_loc = 0
|
|
319
|
+ if board > 0: # need to look at the next line for environment info
|
|
320
|
+ cpu_line = pins_h[i]
|
|
321
|
+ comment_start = cpu_line.find('// ')
|
|
322
|
+ env_A, next_position = get_env_from_line(cpu_line, comment_start) # get name of environment & start of search for next
|
|
323
|
+ env_B, next_position = get_env_from_line(cpu_line, next_position) # get next environment, if it exists
|
|
324
|
+ env_C, next_position = get_env_from_line(cpu_line, next_position) # get next environment, if it exists
|
|
325
|
+ break
|
|
326
|
+ return env_A, env_B, env_C
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+# scans input string for CPUs that the users may need to select from
|
|
330
|
+# returns: CPU name
|
|
331
|
+def get_CPU_name(environment):
|
|
332
|
+ CPU_list = ('1280', '2560','644', '1284', 'LPC1768', 'DUE')
|
|
333
|
+ CPU_name = ''
|
|
334
|
+ for CPU in CPU_list:
|
|
335
|
+ if 0 < environment.find(CPU):
|
|
336
|
+ return CPU
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+# get environment to be used for the build
|
|
340
|
+# returns: environment
|
|
341
|
+def get_env(board_name, ver_Marlin):
|
|
342
|
+ def no_environment():
|
|
343
|
+ print 'ERROR - no environment for this board'
|
|
344
|
+ print board_name
|
|
345
|
+ raise SystemExit(0) # no environment so quit
|
|
346
|
+
|
|
347
|
+ def invalid_board():
|
|
348
|
+ print 'ERROR - invalid board'
|
|
349
|
+ print board_name
|
|
350
|
+ raise SystemExit(0) # quit if unable to find board
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+ CPU_question = ( ('1280', '2560', "1280 or 2560 CPU?"), ('644', '1284', "644 or 1284 CPU?") )
|
|
354
|
+
|
|
355
|
+ if 0 < board_name.find('MELZI') :
|
|
356
|
+ get_answer(board_name, "Which flavor of Melzi?", "Melzi (Optiboot bootloader)", "Melzi ")
|
|
357
|
+ if 1 == get_answer_val:
|
|
358
|
+ target_env = 'melzi_optiboot'
|
|
359
|
+ else:
|
|
360
|
+ target_env = 'melzi'
|
|
361
|
+ else:
|
|
362
|
+ env_A, env_B, env_C = get_starting_env(board_name, ver_Marlin)
|
|
363
|
+
|
|
364
|
+ if env_A == '':
|
|
365
|
+ no_environment()
|
|
366
|
+ if env_B == '':
|
|
367
|
+ return env_A # only one environment so finished
|
|
368
|
+
|
|
369
|
+ CPU_A = get_CPU_name(env_A)
|
|
370
|
+ CPU_B = get_CPU_name(env_B)
|
|
371
|
+
|
|
372
|
+ for item in CPU_question:
|
|
373
|
+ if CPU_A == item[0]:
|
|
374
|
+ get_answer(board_name, item[2], item[0], item[1])
|
|
375
|
+ if 2 == get_answer_val:
|
|
376
|
+ target_env = env_B
|
|
377
|
+ else:
|
|
378
|
+ target_env = env_A
|
|
379
|
+ return target_env
|
|
380
|
+
|
|
381
|
+ if env_A == 'LPC1768':
|
|
382
|
+ if build_type == 'traceback' or (build_type == 'clean' and get_build_last() == 'LPC1768_debug_and_upload'):
|
|
383
|
+ target_env = 'LPC1768_debug_and_upload'
|
|
384
|
+ else:
|
|
385
|
+ target_env = 'LPC1768'
|
|
386
|
+ elif env_A == 'DUE':
|
|
387
|
+ target_env = 'DUE'
|
|
388
|
+ if build_type == 'traceback' or (build_type == 'clean' and get_build_last() == 'DUE_debug'):
|
|
389
|
+ target_env = 'DUE_debug'
|
|
390
|
+ elif env_B == 'DUE_USB':
|
|
391
|
+ get_answer(board_name, "DUE: need download port", "USB (native USB) port", "Programming port ")
|
|
392
|
+ if 1 == get_answer_val:
|
|
393
|
+ target_env = 'DUE_USB'
|
|
394
|
+ else:
|
|
395
|
+ target_env = 'DUE'
|
|
396
|
+ else:
|
|
397
|
+ invalid_board()
|
|
398
|
+
|
|
399
|
+ if build_type == 'traceback' and not(target_env == 'LPC1768_debug_and_upload' or target_env == 'DUE_debug') and Marlin_ver == 2:
|
|
400
|
+ print "ERROR - this board isn't setup for traceback"
|
|
401
|
+ print 'board_name: ', board_name
|
|
402
|
+ print 'target_env: ', target_env
|
|
403
|
+ raise SystemExit(0)
|
|
404
|
+
|
|
405
|
+ return target_env
|
|
406
|
+# end - get_env
|
|
407
|
+
|
|
408
|
+# puts screen text into queue so that the parent thread can fetch the data from this thread
|
|
409
|
+import Queue
|
|
410
|
+IO_queue = Queue.Queue()
|
|
411
|
+def write_to_screen_queue(text, format_tag = 'normal'):
|
|
412
|
+ double_in = [text, format_tag]
|
|
413
|
+ IO_queue.put(double_in, block = False)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+#
|
|
417
|
+# send one line to the terminal screen with syntax highlighting
|
|
418
|
+#
|
|
419
|
+# input: unformatted text, flags from previous run
|
|
420
|
+# returns: formatted text ready to go to the terminal, flags from this run
|
|
421
|
+#
|
|
422
|
+# This routine remembers the status from call to call because previous
|
|
423
|
+# lines can affect how the current line is highlighted
|
|
424
|
+#
|
|
425
|
+
|
|
426
|
+# 'static' variables - init here and then keep updating them from within print_line
|
|
427
|
+warning = False
|
|
428
|
+warning_FROM = False
|
|
429
|
+error = False
|
|
430
|
+standard = True
|
|
431
|
+prev_line_COM = False
|
|
432
|
+next_line_warning = False
|
|
433
|
+warning_continue = False
|
|
434
|
+
|
|
435
|
+def line_print(line_input):
|
|
436
|
+
|
|
437
|
+ global warning
|
|
438
|
+ global warning_FROM
|
|
439
|
+ global error
|
|
440
|
+ global standard
|
|
441
|
+ global prev_line_COM
|
|
442
|
+ global next_line_warning
|
|
443
|
+ global warning_continue
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+ # all '0' elements must precede all '1' elements or they'll be skipped
|
|
449
|
+ platformio_highlights = [
|
|
450
|
+ ['Environment', 0, 'highlight_blue'],
|
|
451
|
+ ['[SKIP]', 1, 'warning'],
|
|
452
|
+ ['[ERROR]', 1, 'error'],
|
|
453
|
+ ['[SUCCESS]', 1, 'highlight_green']
|
|
454
|
+ ]
|
|
455
|
+
|
|
456
|
+ def write_to_screen_with_replace(text, highlights): # search for highlights & split line accordingly
|
|
457
|
+ did_something = False
|
|
458
|
+ for highlight in highlights:
|
|
459
|
+ found = text.find(highlight[0])
|
|
460
|
+ if did_something == True:
|
|
461
|
+ break
|
|
462
|
+ if found >= 0 :
|
|
463
|
+ did_something = True
|
|
464
|
+ if 0 == highlight[1]:
|
|
465
|
+ found_1 = text.find(' ')
|
|
466
|
+ found_tab = text.find('\t')
|
|
467
|
+ if found_1 < 0 or found_1 > found_tab:
|
|
468
|
+ found_1 = found_tab
|
|
469
|
+ write_to_screen_queue(text[ : found_1 + 1 ])
|
|
470
|
+ for highlight_2 in highlights:
|
|
471
|
+ if highlight[0] == highlight_2[0] :
|
|
472
|
+ continue
|
|
473
|
+ found = text.find(highlight_2[0])
|
|
474
|
+ if found >= 0 :
|
|
475
|
+ found_space = text.find(' ', found_1 + 1)
|
|
476
|
+ found_tab = text.find('\t', found_1 + 1)
|
|
477
|
+ if found_space < 0 or found_space > found_tab:
|
|
478
|
+ found_space = found_tab
|
|
479
|
+ found_right = text.find(']', found + 1)
|
|
480
|
+ write_to_screen_queue(text[found_1 + 1 : found_space + 1 ], highlight[2])
|
|
481
|
+ write_to_screen_queue(text[found_space + 1 : found + 1 ])
|
|
482
|
+ write_to_screen_queue(text[found + 1 : found_right], highlight_2[2])
|
|
483
|
+ write_to_screen_queue(text[found_right : ] + '\n')
|
|
484
|
+ break
|
|
485
|
+ break
|
|
486
|
+ if 1 == highlight[1]:
|
|
487
|
+ found_right = text.find(']', found + 1)
|
|
488
|
+ write_to_screen_queue(text[ : found + 1 ])
|
|
489
|
+ write_to_screen_queue(text[found + 1 : found_right ], highlight[2])
|
|
490
|
+ write_to_screen_queue(text[found_right : ] + '\n')
|
|
491
|
+ break
|
|
492
|
+ if did_something == False:
|
|
493
|
+ write_to_screen_queue(text + '\n')
|
|
494
|
+ # end - write_to_screen_with_replace
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+ # scan the line
|
|
499
|
+ max_search = len(line_input)
|
|
500
|
+ if max_search > 3 :
|
|
501
|
+ max_search = 3
|
|
502
|
+ beginning = line_input[:max_search]
|
|
503
|
+
|
|
504
|
+ # set flags
|
|
505
|
+ if 0 < line_input.find(': warning: '): # start of warning block
|
|
506
|
+ warning = True
|
|
507
|
+ warning_FROM = False
|
|
508
|
+ error = False
|
|
509
|
+ standard = False
|
|
510
|
+ prev_line_COM = False
|
|
511
|
+ prev_line_COM = False
|
|
512
|
+ warning_continue = True
|
|
513
|
+ if beginning == 'War' or \
|
|
514
|
+ beginning == '#er' or \
|
|
515
|
+ beginning == 'In ' or \
|
|
516
|
+ (beginning != 'Com' and prev_line_COM == True and not(beginning == 'Arc' or beginning == 'Lin' or beginning == 'Ind') or \
|
|
517
|
+ next_line_warning == True):
|
|
518
|
+ warning = True #warning found
|
|
519
|
+ warning_FROM = False
|
|
520
|
+ error = False
|
|
521
|
+ standard = False
|
|
522
|
+ prev_line_COM = False
|
|
523
|
+ elif beginning == 'Com' or \
|
|
524
|
+ beginning == 'Ver' or \
|
|
525
|
+ beginning == ' [E' or \
|
|
526
|
+ beginning == 'Rem' or \
|
|
527
|
+ beginning == 'Bui' or \
|
|
528
|
+ beginning == 'Ind' or \
|
|
529
|
+ beginning == 'PLA':
|
|
530
|
+ warning = False #standard line found
|
|
531
|
+ warning_FROM = False
|
|
532
|
+ error = False
|
|
533
|
+ standard = True
|
|
534
|
+ prev_line_COM = False
|
|
535
|
+ warning_continue = False
|
|
536
|
+ elif beginning == '***':
|
|
537
|
+ warning = False # error found
|
|
538
|
+ warning_FROM = False
|
|
539
|
+ error = True
|
|
540
|
+ standard = False
|
|
541
|
+ prev_line_COM = False
|
|
542
|
+
|
|
543
|
+ elif beginning == 'fro' and warning == True : # start of warning /error block
|
|
544
|
+ warning_FROM = True
|
|
545
|
+ prev_line_COM = False
|
|
546
|
+ warning_continue = True
|
|
547
|
+ elif 0 < line_input.find(': error:') or \
|
|
548
|
+ 0 < line_input.find(': fatal error:'): # start of warning /error block
|
|
549
|
+ warning = False # error found
|
|
550
|
+ warning_FROM = False
|
|
551
|
+ error = True
|
|
552
|
+ standard = False
|
|
553
|
+ prev_line_COM = False
|
|
554
|
+ warning_continue = True
|
|
555
|
+ elif warning_continue == True:
|
|
556
|
+ warning = True
|
|
557
|
+ warning_FROM = False # keep the warning status going until find a standard line
|
|
558
|
+ error = False
|
|
559
|
+ standard = False
|
|
560
|
+ prev_line_COM = False
|
|
561
|
+ warning_continue = True
|
|
562
|
+
|
|
563
|
+ else:
|
|
564
|
+ warning = False # unknown so assume standard line
|
|
565
|
+ warning_FROM = False
|
|
566
|
+ error = False
|
|
567
|
+ standard = True
|
|
568
|
+ prev_line_COM = False
|
|
569
|
+ warning_continue = False
|
|
570
|
+
|
|
571
|
+ if beginning == 'Com':
|
|
572
|
+ prev_line_COM = True
|
|
573
|
+
|
|
574
|
+ # print based on flags
|
|
575
|
+ if standard == True:
|
|
576
|
+ write_to_screen_with_replace(line_input, platformio_highlights) #print white on black with substitutions
|
|
577
|
+ if warning == True:
|
|
578
|
+ write_to_screen_queue(line_input + '\n', 'warning')
|
|
579
|
+ if error == True:
|
|
580
|
+ write_to_screen_queue(line_input + '\n', 'error')
|
|
581
|
+# end - line_print
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+def run_PIO(dummy):
|
|
586
|
+
|
|
587
|
+ ##########################################################################
|
|
588
|
+ # #
|
|
589
|
+ # run Platformio #
|
|
590
|
+ # #
|
|
591
|
+ ##########################################################################
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+ # build platformio run -e target_env
|
|
595
|
+ # clean platformio run --target clean -e target_env
|
|
596
|
+ # upload platformio run --target upload -e target_env
|
|
597
|
+ # traceback platformio run --target upload -e target_env
|
|
598
|
+ # program platformio run --target program -e target_env
|
|
599
|
+ # test platformio test upload -e target_env
|
|
600
|
+ # remote platformio remote run --target upload -e target_env
|
|
601
|
+ # debug platformio debug -e target_env
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+ global build_type
|
|
605
|
+ global target_env
|
|
606
|
+ global board_name
|
|
607
|
+ print 'build_type: ', build_type
|
|
608
|
+
|
|
609
|
+ import subprocess
|
|
610
|
+ import sys
|
|
611
|
+ print 'starting platformio'
|
|
612
|
+
|
|
613
|
+ if build_type == 'build':
|
|
614
|
+ # platformio run -e target_env
|
|
615
|
+ # combine stdout & stderr so all compile messages are included
|
|
616
|
+ pio_subprocess = subprocess.Popen(['platformio', 'run', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+ elif build_type == 'clean':
|
|
620
|
+ # platformio run --target clean -e target_env
|
|
621
|
+ # combine stdout & stderr so all compile messages are included
|
|
622
|
+ pio_subprocess = subprocess.Popen(['platformio', 'run', '--target', 'clean', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+ elif build_type == 'upload':
|
|
626
|
+ # platformio run --target upload -e target_env
|
|
627
|
+ # combine stdout & stderr so all compile messages are included
|
|
628
|
+ pio_subprocess = subprocess.Popen(['platformio', 'run', '--target', 'upload', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+ elif build_type == 'traceback':
|
|
632
|
+ # platformio run --target upload -e target_env - select the debug environment if there is one
|
|
633
|
+ # combine stdout & stderr so all compile messages are included
|
|
634
|
+ pio_subprocess = subprocess.Popen(['platformio', 'run', '--target', 'upload', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+ elif build_type == 'program':
|
|
638
|
+ # platformio run --target program -e target_env
|
|
639
|
+ # combine stdout & stderr so all compile messages are included
|
|
640
|
+ pio_subprocess = subprocess.Popen(['platformio', 'run', '--target', 'program', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+ elif build_type == 'test':
|
|
644
|
+ #platformio test upload -e target_env
|
|
645
|
+ # combine stdout & stderr so all compile messages are included
|
|
646
|
+ pio_subprocess = subprocess.Popen(['platformio', 'test', 'upload', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+ elif build_type == 'remote':
|
|
650
|
+ # platformio remote run --target upload -e target_env
|
|
651
|
+ # combine stdout & stderr so all compile messages are included
|
|
652
|
+ pio_subprocess = subprocess.Popen(['platformio', 'remote', 'run', '--target', 'program', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+ elif build_type == 'debug':
|
|
656
|
+ # platformio debug -e target_env
|
|
657
|
+ # combine stdout & stderr so all compile messages are included
|
|
658
|
+ pio_subprocess = subprocess.Popen(['platformio', 'debug', '-e', target_env], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+ else:
|
|
662
|
+ print 'ERROR - unknown build type: ', build_type
|
|
663
|
+ raise SystemExit(0) # kill everything
|
|
664
|
+
|
|
665
|
+ # stream output from subprocess and split it into lines
|
|
666
|
+ for line in iter(pio_subprocess.stdout.readline, ''):
|
|
667
|
+ line_print(line.replace('\n', ''))
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+ # append info used to run PlatformIO
|
|
671
|
+ write_to_screen_queue('\nBoard name: ' + board_name + '\n') # put build info at the bottom of the screen
|
|
672
|
+ write_to_screen_queue('Build type: ' + build_type + '\n')
|
|
673
|
+ write_to_screen_queue('Environment used: ' + target_env + '\n')
|
|
674
|
+# end - run_PIO
|
|
675
|
+
|
|
676
|
+
|
|
677
|
+########################################################################
|
|
678
|
+
|
|
679
|
+import time
|
|
680
|
+import threading
|
|
681
|
+import Tkinter as tk
|
|
682
|
+import ttk
|
|
683
|
+import Queue
|
|
684
|
+import subprocess
|
|
685
|
+import sys
|
|
686
|
+que = Queue.Queue()
|
|
687
|
+#IO_queue = Queue.Queue()
|
|
688
|
+
|
|
689
|
+from Tkinter import Tk, Frame, Text, Scrollbar, Menu
|
|
690
|
+from tkMessageBox import askokcancel
|
|
691
|
+
|
|
692
|
+import tkFileDialog
|
|
693
|
+from tkMessageBox import askokcancel
|
|
694
|
+import tkFileDialog
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+class output_window(Text):
|
|
699
|
+
|
|
700
|
+ global continue_updates
|
|
701
|
+ continue_updates = True
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+ def __init__(self):
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+ self.root = tk.Tk()
|
|
708
|
+ self.frame = tk.Frame(self.root)
|
|
709
|
+ self.frame.pack(fill='both', expand=True)
|
|
710
|
+
|
|
711
|
+ # text widget
|
|
712
|
+ #self.text = tk.Text(self.frame, borderwidth=3, relief="sunken")
|
|
713
|
+ Text.__init__(self, self.frame, borderwidth=3, relief="sunken")
|
|
714
|
+ self.config(tabs=(400,)) # configure Text widget tab stops
|
|
715
|
+ self.config(background = 'black', foreground = 'white', font= ("consolas", 12), wrap = 'word', undo = 'True')
|
|
716
|
+ self.config(height = 24, width = 120)
|
|
717
|
+ self.pack(side='left', fill='both', expand=True)
|
|
718
|
+
|
|
719
|
+ self.tag_config('normal', foreground = 'white')
|
|
720
|
+ self.tag_config('warning', foreground = 'yellow' )
|
|
721
|
+ self.tag_config('error', foreground = 'red')
|
|
722
|
+ self.tag_config('highlight_green', foreground = 'green')
|
|
723
|
+ self.tag_config('highlight_blue', foreground = 'cyan')
|
|
724
|
+
|
|
725
|
+# self.bind('<Control-Key-a>', self.select_all) # the event happens but the action doesn't
|
|
726
|
+
|
|
727
|
+ # scrollbar
|
|
728
|
+
|
|
729
|
+ scrb = tk.Scrollbar(self.frame, orient='vertical', command=self.yview)
|
|
730
|
+ self.config(yscrollcommand=scrb.set)
|
|
731
|
+ scrb.pack(side='right', fill='y')
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+ # pop-up menu
|
|
735
|
+ self.popup = tk.Menu(self, tearoff=0)
|
|
736
|
+ self.popup.add_command(label='Cut', command=self._cut)
|
|
737
|
+ self.popup.add_command(label='Copy', command=self._copy)
|
|
738
|
+ self.popup.add_command(label='Paste', command=self._paste)
|
|
739
|
+ self.popup.add_separator()
|
|
740
|
+ self.popup.add_command(label='Select All', command=self._select_all)
|
|
741
|
+ self.popup.add_command(label='Clear All', command=self._clear_all)
|
|
742
|
+ self.popup.add_separator()
|
|
743
|
+ self.popup.add_command(label='Save As', command=self._file_save_as)
|
|
744
|
+ self.bind('<Button-3>', self._show_popup)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+ # threading & subprocess section
|
|
748
|
+
|
|
749
|
+ def start_thread(self, ):
|
|
750
|
+ global continue_updates
|
|
751
|
+ # create then start a secondary thread to run an arbitrary function
|
|
752
|
+ # must have at least one argument
|
|
753
|
+ self.secondary_thread = threading.Thread(target = lambda q, arg1: q.put(run_PIO(arg1)), args=(que, ''))
|
|
754
|
+ self.secondary_thread.start()
|
|
755
|
+ continue_updates = True
|
|
756
|
+ # check the Queue in 50ms
|
|
757
|
+ self.root.after(50, self.check_thread)
|
|
758
|
+ self.root.after(50, self.update)
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+ def check_thread(self): # wait for user to kill the window
|
|
762
|
+ global continue_updates
|
|
763
|
+ if continue_updates == True:
|
|
764
|
+ self.root.after(20, self.check_thread)
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+ def update(self):
|
|
768
|
+ global continue_updates
|
|
769
|
+ if continue_updates == True:
|
|
770
|
+ self.root.after(20, self.update)#method is called every 50ms
|
|
771
|
+ temp_text = ['0','0']
|
|
772
|
+ if IO_queue.empty():
|
|
773
|
+ if not(self.secondary_thread.is_alive()):
|
|
774
|
+ continue_updates = False # queue is exhausted and thread is dead so no need for further updates
|
|
775
|
+ self.tag_add('sel', '1.0', 'end')
|
|
776
|
+ else:
|
|
777
|
+ try:
|
|
778
|
+ temp_text = IO_queue.get(block = False)
|
|
779
|
+ except Queue.Empty:
|
|
780
|
+ continue_updates = False # queue is exhausted so no need for further updates
|
|
781
|
+ else:
|
|
782
|
+ self.insert('end', temp_text[0], temp_text[1])
|
|
783
|
+ self.see("end") # make the last line visible (scroll text off the top)
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+ # text editing section
|
|
787
|
+
|
|
788
|
+ def _file_save_as(self):
|
|
789
|
+ self.filename = tkFileDialog.asksaveasfilename(defaultextension = '.txt')
|
|
790
|
+ f = open(self.filename, 'w')
|
|
791
|
+ f.write(self.get('1.0', 'end'))
|
|
792
|
+ f.close()
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+ def copy(self, event):
|
|
797
|
+ try:
|
|
798
|
+ selection = self.get(*self.tag_ranges('sel'))
|
|
799
|
+ self.clipboard_clear()
|
|
800
|
+ self.clipboard_append(selection)
|
|
801
|
+ except TypeError:
|
|
802
|
+ pass
|
|
803
|
+
|
|
804
|
+ def cut(self, event):
|
|
805
|
+
|
|
806
|
+ try:
|
|
807
|
+ selection = self.get(*self.tag_ranges('sel'))
|
|
808
|
+ self.clipboard_clear()
|
|
809
|
+ self.clipboard_append(selection)
|
|
810
|
+ self.delete(*self.tag_ranges('sel'))
|
|
811
|
+ except TypeError:
|
|
812
|
+ pass
|
|
813
|
+
|
|
814
|
+ def _show_popup(self, event):
|
|
815
|
+ '''right-click popup menu'''
|
|
816
|
+
|
|
817
|
+ if self.root.focus_get() != self:
|
|
818
|
+ self.root.focus_set()
|
|
819
|
+
|
|
820
|
+ try:
|
|
821
|
+ self.popup.tk_popup(event.x_root, event.y_root, 0)
|
|
822
|
+ finally:
|
|
823
|
+ self.popup.grab_release()
|
|
824
|
+
|
|
825
|
+ def _cut(self):
|
|
826
|
+
|
|
827
|
+ try:
|
|
828
|
+ selection = self.get(*self.tag_ranges('sel'))
|
|
829
|
+ self.clipboard_clear()
|
|
830
|
+ self.clipboard_append(selection)
|
|
831
|
+ self.delete(*self.tag_ranges('sel'))
|
|
832
|
+ except TypeError:
|
|
833
|
+ pass
|
|
834
|
+
|
|
835
|
+ def cut(self, event):
|
|
836
|
+ _cut(self)
|
|
837
|
+
|
|
838
|
+ def _copy(self):
|
|
839
|
+
|
|
840
|
+ try:
|
|
841
|
+ selection = self.get(*self.tag_ranges('sel'))
|
|
842
|
+ self.clipboard_clear()
|
|
843
|
+ self.clipboard_append(selection)
|
|
844
|
+ except TypeError:
|
|
845
|
+ pass
|
|
846
|
+
|
|
847
|
+ def copy(self, event):
|
|
848
|
+ _copy(self)
|
|
849
|
+
|
|
850
|
+ def _paste(self):
|
|
851
|
+
|
|
852
|
+ self.insert('insert', self.selection_get(selection='CLIPBOARD'))
|
|
853
|
+
|
|
854
|
+ def _select_all(self):
|
|
855
|
+ self.tag_add('sel', '1.0', 'end')
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+ def select_all(self, event):
|
|
859
|
+ self.tag_add('sel', '1.0', 'end')
|
|
860
|
+
|
|
861
|
+
|
|
862
|
+ def _clear_all(self):
|
|
863
|
+ '''erases all text'''
|
|
864
|
+
|
|
865
|
+ isok = askokcancel('Clear All', 'Erase all text?', frame=self,
|
|
866
|
+ default='ok')
|
|
867
|
+ if isok:
|
|
868
|
+ self.delete('1.0', 'end')
|
|
869
|
+
|
|
870
|
+ def _place_cursor(self): # theme: terminal
|
|
871
|
+ '''check the position of the cursor against the last known position
|
|
872
|
+ every 15ms and update the cursorblock tag as needed'''
|
|
873
|
+
|
|
874
|
+ current_index = self.index('insert')
|
|
875
|
+
|
|
876
|
+ if self.cursor != current_index:
|
|
877
|
+ self.cursor = current_index
|
|
878
|
+ self.tag_delete('cursorblock')
|
|
879
|
+
|
|
880
|
+ start = self.index('insert')
|
|
881
|
+ end = self.index('insert+1c')
|
|
882
|
+
|
|
883
|
+ if start[0] != end[0]:
|
|
884
|
+ self.insert(start, ' ')
|
|
885
|
+ end = self.index('insert')
|
|
886
|
+
|
|
887
|
+ self.tag_add('cursorblock', start, end)
|
|
888
|
+ self.mark_set('insert', self.cursor)
|
|
889
|
+
|
|
890
|
+ self.after(15, self._place_cursor)
|
|
891
|
+
|
|
892
|
+ def _blink_cursor(self): # theme: terminal
|
|
893
|
+ '''alternate the background color of the cursorblock tagged text
|
|
894
|
+ every 600 milliseconds'''
|
|
895
|
+
|
|
896
|
+ if self.switch == self.fg:
|
|
897
|
+ self.switch = self.bg
|
|
898
|
+ else:
|
|
899
|
+ self.switch = self.fg
|
|
900
|
+
|
|
901
|
+ self.tag_config('cursorblock', background=self.switch)
|
|
902
|
+
|
|
903
|
+ self.after(600, self._blink_cursor)
|
|
904
|
+# end - output_window
|
|
905
|
+
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+def main():
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+ ##########################################################################
|
|
912
|
+ # #
|
|
913
|
+ # main program #
|
|
914
|
+ # #
|
|
915
|
+ ##########################################################################
|
|
916
|
+
|
|
917
|
+ global build_type
|
|
918
|
+ global target_env
|
|
919
|
+ global board_name
|
|
920
|
+
|
|
921
|
+ board_name, Marlin_ver = get_board_name()
|
|
922
|
+
|
|
923
|
+ target_env = get_env(board_name, Marlin_ver)
|
|
924
|
+
|
|
925
|
+ auto_build = output_window()
|
|
926
|
+ auto_build.start_thread() # executes the "run_PIO" function
|
|
927
|
+
|
|
928
|
+ auto_build.root.mainloop()
|
|
929
|
+
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+if __name__ == '__main__':
|
|
934
|
+
|
|
935
|
+ main()
|