Teacup_Firmware/configtool/build.py

461 lines
14 KiB
Python

import wx.lib.newevent
import thread, shlex, subprocess
import os, re
from os.path import isfile, join
from sys import platform
if platform.startswith("win"):
from _subprocess import STARTF_USESHOWWINDOW
(scriptEvent, EVT_SCRIPT_UPDATE) = wx.lib.newevent.NewEvent()
SCRIPT_RUNNING = 1
SCRIPT_FINISHED = 2
SCRIPT_CANCELLED = 3
TOOLPATHS_INSIDE_ARDUINO = [
"hardware/tools/avr/bin/",
"hardware/tools/" # avrdude in Arduino 1.0.x
]
if platform.startswith("darwin"):
# That's an OS property, the Applicaton Bundle hierarchy.
pathsCopy = TOOLPATHS_INSIDE_ARDUINO
TOOLPATHS_INSIDE_ARDUINO = []
for path in pathsCopy:
TOOLPATHS_INSIDE_ARDUINO.append("Contents/Resources/Java/" + path)
TOOLPATHS_INSIDE_ARDUINO.append("Contents/Java/" + path)
class ScriptTools:
def __init__(self, settings):
self.settings = settings
def figureCommandPath(self, baseCommand):
findConf = False
if baseCommand == "avrdude":
findConf = True
if platform.startswith("win"):
baseCommand += ".exe"
if self.settings.arduinodir:
cmdpath = self.settings.arduinodir
for pathOption in TOOLPATHS_INSIDE_ARDUINO:
cmdpathTry = cmdpath
for dir in pathOption.split("/"):
cmdpathTry = os.path.join(cmdpathTry, dir)
cmdpathTry = os.path.join(cmdpathTry, baseCommand)
if os.path.exists(cmdpathTry):
cmdpath = "\"" + cmdpathTry + "\""
break
if findConf:
confpath = cmdpath.strip("\"")
exepos = confpath.rfind(".exe")
if exepos >= 0:
confpath = confpath[0:exepos]
confpath += ".conf"
if not os.path.exists(confpath):
confpath = os.path.split(confpath)[0]
confpath = os.path.split(confpath)[0]
confpath = os.path.join(confpath, "etc")
confpath = os.path.join(confpath, "avrdude.conf")
if os.path.exists(confpath):
cmdpath += " -C \"" + confpath + "\""
else:
cmdpath = baseCommand
# No need to search avrdude.conf in this case.
return cmdpath
class ScriptThread:
def __init__(self, win, script):
self.win = win
self.running = False
self.cancelled = False
self.script = script
def Start(self):
self.running = True
self.cancelled = False
thread.start_new_thread(self.Run, ())
def Stop(self):
self.cancelled = True
def IsRunning(self):
return self.running
def Run(self):
if platform.startswith("win"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= STARTF_USESHOWWINDOW
for cmd in self.script:
evt = scriptEvent(msg = cmd, state = SCRIPT_RUNNING)
wx.PostEvent(self.win, evt)
args = shlex.split(str(cmd))
try:
if platform.startswith("win"):
p = subprocess.Popen(args, stderr = subprocess.STDOUT,
stdout = subprocess.PIPE,
startupinfo = startupinfo)
else:
p = subprocess.Popen(args, stderr = subprocess.STDOUT,
stdout = subprocess.PIPE)
except:
evt = scriptEvent(msg = "Exception occurred trying to run\n\n%s" % cmd,
state = SCRIPT_CANCELLED)
wx.PostEvent(self.win, evt)
self.running = False
return
obuf = ''
while not self.cancelled:
o = p.stdout.read(1)
if o == '': break
if o == '\r' or o == '\n':
if obuf.strip() != "":
evt = scriptEvent(msg = obuf, state = SCRIPT_RUNNING)
wx.PostEvent(self.win, evt)
obuf = ''
elif ord(o) < 32:
pass
else:
obuf += o
if self.cancelled:
evt = scriptEvent(msg = None, state = SCRIPT_CANCELLED)
wx.PostEvent(self.win, evt)
p.kill()
self.running = False
p.wait()
return
rc = p.wait()
if rc != 0:
msg = "RC = " + str(rc) + " - Build terminated"
evt = scriptEvent(msg = msg, state = SCRIPT_CANCELLED)
wx.PostEvent(self.win, evt)
self.running = False
return
evt = scriptEvent(msg = "", state = SCRIPT_RUNNING)
wx.PostEvent(self.win, evt)
evt = scriptEvent(msg = None, state = SCRIPT_FINISHED)
wx.PostEvent(self.win, evt)
self.running = False
class Build(wx.Dialog):
def __init__(self, parent, settings, f_cpu, cpu):
wx.Dialog.__init__(self, parent, wx.ID_ANY, "Build teacup",
style = wx.RESIZE_BORDER + wx.DEFAULT_DIALOG_STYLE)
self.settings = settings
self.SetFont(self.settings.font)
self.root = self.settings.folder
self.f_cpu = f_cpu
self.cpu = cpu
self.Bind(wx.EVT_CLOSE, self.onExit)
self.cancelPending = False
hsz = wx.BoxSizer(wx.HORIZONTAL)
hsz.AddSpacer((10, 10))
sz = wx.BoxSizer(wx.VERTICAL)
sz.AddSpacer((10, 10))
tc = wx.TextCtrl(self, wx.ID_ANY, size = (900, 300),
style = wx.TE_READONLY + wx.TE_MULTILINE)
sz.Add(tc, 1, wx.EXPAND)
f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL,
wx.FONTWEIGHT_BOLD)
tc.SetFont(f)
self.log = tc
sz.AddSpacer((10, 10))
hsz.Add(sz, 1, wx.EXPAND)
hsz.AddSpacer((10, 10))
self.SetSizer(hsz)
self.Fit()
builddir = join(self.root, "build")
if not os.path.exists(builddir):
os.makedirs(builddir)
self.log.AppendText("Directory %s created.\n\n" % builddir)
self.compile()
def compile(self):
self.generateCompileScript()
if len(self.script) == 0:
self.log.AppendText("Nothing to compile!\n")
self.active = False
else:
self.Bind(EVT_SCRIPT_UPDATE, self.compileUpdate)
self.t = ScriptThread(self, self.script)
self.active = True
self.t.Start()
def link(self):
self.generateLinkScript()
if len(self.script) == 0:
self.log.AppendText("Nothing to link!\n")
self.active = False
else:
self.Bind(EVT_SCRIPT_UPDATE, self.linkUpdate)
t = ScriptThread(self, self.script)
self.active = True
t.Start()
def report(self):
self.script = []
self.reportLines = []
cmdpath = ScriptTools(self.settings).figureCommandPath("avr-objdump")
elfpath = "\"" + join(self.root, "build", "teacup.elf") + "\""
cmd = cmdpath + " -h " + elfpath
self.script.append(cmd)
self.Bind(EVT_SCRIPT_UPDATE, self.reportUpdate)
t = ScriptThread(self, self.script)
self.active = True
t.Start()
def generateCompileScript(self):
self.script = []
cmdpath = ScriptTools(self.settings).figureCommandPath("avr-gcc")
cfiles = [f for f in os.listdir(self.root)
if isfile(join(self.root,f)) and f.endswith(".c")]
for f in cfiles:
basename = f[:-2]
ofile = basename + ".o"
alfile = basename + ".al"
opath = "\"" + join(self.root, "build", ofile) + "\""
alpath = "\"" + join(self.root, "build", alfile) + "\""
cpath = "\"" + join(self.root, f) + "\""
opts = self.settings.cflags
opts = opts.replace("%ALNAME%", alpath)
opts = opts.replace("%F_CPU%", self.f_cpu)
opts = opts.replace("%CPU%", self.cpu)
cmd = cmdpath + " -c " + opts + " -o " + opath + " " + cpath
self.script.append(cmd)
def generateLinkScript(self):
self.script = []
cmdpath = ScriptTools(self.settings).figureCommandPath("avr-gcc")
# This is ugly:
# Work around a problem of avr-ld.exe coming with Arduino 1.6.4 for
# Windows. Without this it always drops this error message:
# collect2.exe: error: ld returned 5 exit status 255
# Just enabling verbose messages allows ld.exe to complete without failure.
if platform == "win32":
cmdpath += " -Wl,-V"
ofiles = ["\"" + join(self.root, "build", f) + "\""
for f in os.listdir(join(self.root, "build"))
if isfile(join(self.root, "build", f)) and f.endswith(".o")]
opath = " ".join(ofiles)
elfpath = "\"" + join(self.root, "build", "teacup.elf") + "\""
hexpath = "\"" + join(self.root, "teacup.hex") + "\""
opts = self.settings.cflags
opts = opts.replace("%ALNAME%", "teacup.elf")
opts = opts.replace("%F_CPU%", self.f_cpu)
opts = opts.replace("%CPU%", self.cpu)
cmd = cmdpath + " " + self.settings.ldflags + " " + opts + " -o " + \
elfpath + " " + opath + " -lm"
self.script.append(cmd)
cmdpath = ScriptTools(self.settings).figureCommandPath("avr-objcopy")
cmd = cmdpath + " " + self.settings.objcopyflags + " " + elfpath + \
" " + hexpath
self.script.append(cmd)
def compileUpdate(self, evt):
if evt.msg is not None:
self.log.AppendText(evt.msg + "\n")
if evt.state == SCRIPT_RUNNING:
pass
if evt.state == SCRIPT_CANCELLED:
self.active = False
if self.cancelPending:
self.EndModal(wx.ID_OK)
self.log.AppendText("Build terminated abnormally.\n")
if evt.state == SCRIPT_FINISHED:
self.log.AppendText("Compile completed normally.\n\n")
self.link()
def linkUpdate(self, evt):
if evt.msg is not None:
self.log.AppendText(evt.msg + "\n")
if evt.state == SCRIPT_RUNNING:
pass
if evt.state == SCRIPT_CANCELLED:
self.log.AppendText("Link terminated abnormally.\n")
self.active = False
if evt.state == SCRIPT_FINISHED:
self.log.AppendText("Link completed normally.\n")
self.report()
def reportUpdate(self, evt):
if evt.state == SCRIPT_RUNNING:
if evt.msg is not None:
self.reportLines.append(evt.msg)
if evt.state == SCRIPT_CANCELLED:
self.log.AppendText(evt.msg + "\n")
self.log.AppendText("Report terminated abnormally.\n")
self.active = False
if evt.state == SCRIPT_FINISHED:
self.formatReport()
self.log.AppendText("\nBuild completed normally.\n")
self.active = False
def formatReportLine(self, m, name, v168, v328, v644, v1280):
t = m.groups()
v = int(t[0], 16)
self.log.AppendText(("%12s: %6d bytes %6.2f%% %6.2f%%"
" %6.2f%% %6.2f%%\n") %
(name, v, v / float(v168 * 1024) * 100.0,
v / float(v328 * 1024) * 100.0,
v / float(v644 * 1024) * 100.0,
v / float(v1280 * 1024) * 100.0))
def formatReport(self):
reText = re.compile("\.text\s+([0-9a-f]+)")
reBss = re.compile("\.bss\s+([0-9a-f]+)")
reEEProm = re.compile("\.eeprom\s+([0-9a-f]+)")
self.log.AppendText("\n ATmega... '168 '328(P)"
" '644(P) '1280\n")
for l in self.reportLines:
m = reText.search(l)
if m:
self.formatReportLine(m, "FLASH", 14, 30, 62, 126)
else:
m = reBss.search(l)
if m:
self.formatReportLine(m, "RAM", 1, 2, 4, 8)
else:
m = reEEProm.search(l)
if m:
self.formatReportLine(m, "EEPROM", 1, 2, 2, 4)
def onExit(self, evt):
if self.active:
dlg = wx.MessageDialog(self, "Are you sure you want to cancel building?",
"Build active",
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION)
rc = dlg.ShowModal()
dlg.Destroy()
if rc == wx.ID_YES:
self.cancelPending = True
self.log.AppendText("Cancelling...\n")
self.t.Stop()
return
self.EndModal(wx.ID_OK)
class Upload(wx.Dialog):
def __init__(self, parent, settings, f_cpu, cpu):
wx.Dialog.__init__(self, parent, wx.ID_ANY, "Upload teacup",
style = wx.RESIZE_BORDER + wx.DEFAULT_DIALOG_STYLE)
self.settings = settings
self.SetFont(self.settings.font)
self.root = self.settings.folder
self.f_cpu = f_cpu
self.cpu = cpu
self.baud = self.settings.uploadspeed
self.Bind(wx.EVT_CLOSE, self.onExit)
self.cancelPending = False
hsz = wx.BoxSizer(wx.HORIZONTAL)
hsz.AddSpacer((10, 10))
sz = wx.BoxSizer(wx.VERTICAL)
sz.AddSpacer((10, 10))
tc = wx.TextCtrl(self, wx.ID_ANY, size = (900, 300),
style = wx.TE_READONLY + wx.TE_MULTILINE)
sz.Add(tc, 1, wx.EXPAND)
f = wx.Font(8, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL,
wx.FONTWEIGHT_BOLD)
tc.SetFont(f)
self.log = tc
sz.AddSpacer((10, 10))
hsz.Add(sz, 1, wx.EXPAND)
hsz.AddSpacer((10, 10))
self.SetSizer(hsz)
self.Fit()
self.generateUploadScript()
if len(self.script) == 0:
self.log.AppendText("Nothing to upload!\n")
self.active = False
else:
self.Bind(EVT_SCRIPT_UPDATE, self.uploadUpdate)
self.t = ScriptThread(self, self.script)
self.active = True
self.t.Start()
def generateUploadScript(self):
self.script = []
cmdpath = ScriptTools(self.settings).figureCommandPath("avrdude")
hexpath = "\"" + join(self.root, "teacup.hex") + "\""
cmd = cmdpath + " -c %s -b %s -p %s -P %s -U flash:w:%s:i" % \
(self.settings.programmer, self.baud, self.cpu, self.settings.port,
hexpath)
self.script.append(cmd)
def uploadUpdate(self, evt):
if evt.msg is not None:
self.log.AppendText(evt.msg + "\n")
if evt.state == SCRIPT_RUNNING:
pass
if evt.state == SCRIPT_CANCELLED:
self.active = False
if self.cancelPending:
self.EndModal(wx.ID_OK)
self.log.AppendText("Upload terminated abnormally.\n")
if evt.state == SCRIPT_FINISHED:
self.log.AppendText("Upload completed normally.\n")
self.active = False
def onExit(self, evt):
if self.active:
dlg = wx.MessageDialog(self, "Are you sure you want to cancel upload?",
"Upload active",
wx.YES_NO | wx.NO_DEFAULT | wx.ICON_INFORMATION)
rc = dlg.ShowModal()
dlg.Destroy()
if rc == wx.ID_YES:
self.cancelPending = True
self.log.AppendText("Cancelling...\n")
self.t.Stop()
return
self.EndModal(wx.ID_OK)