#!/usr/bin/python3 # # ANY-LICENSE V1.0 # ---------------- # # You can use these files under any license approved by the # Open Source Initiative, preferrably one of the popular licenses, # as long as the license you choose is compatible to the # dependencies of these files. # # See http://www.opensource.org/licenses/ for a list of # approved licenses. # # Author: Martin Furter # Project: Tins AVR Lib # Repository: http://repos.borg.ch/projects/tins_avr_lib/trunk # Copyright: 2015,2016 # import os import os.path import subprocess import sys import traceback import xml.sax verbose = False line_limit = 20 avr_mcu_info = { # mcu RAM FLASH EEPROM u"atmega48": ( 512, 4*1024, 256 ), u"atmega48p": u"atmega48", u"atmega48pa": u"atmega48", u"atmega88": ( 1024, 8*1024, 512 ), u"atmega88p": u"atmega88", u"atmega88pa": u"atmega88", u"atmega168": ( 1024, 16*1024, 512 ), u"atmega168p": u"atmega168", u"atmega168pa": u"atmega168", u"atmega328": ( 2*1024, 32*1024, 1024 ), u"atmega328p": u"atmega328", } def get_avr_mcu_info( mcu ): info = mcu while type(info) is str: info = avr_mcu_info[info] return info def check_files( source, destination ): d = s = None try: d = os.stat( destination ) except: # print( "stat", destination, "failed" ) return False try: s = os.stat( source ) except: raise TmkException( "missing source file '%s'." % source ) return s.st_mtime <= d.st_mtime def print_action( action, filename ): print( "%s: %s" % ( action.rjust(5), filename ) ) def unlink_ignore( filename ): try: os.unlink( filename ) except: pass xml_filename = None xml_locator = None class TmkException( Exception ): def __init__( self, message ): if xml_filename != None and xml_locator != None: message = "%s:%d.%d: %s" % ( xml_filename, xml_locator.getLineNumber(), xml_locator.getColumnNumber(), message ) Exception.__init__( self, message ) class Command: def __init__( self, cmd, action, filename ): self.cmd = cmd print_action( action, filename ) sys.stdout.flush() def run( self ): sp = subprocess.Popen( self.cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) cmd_printed = verbose if cmd_printed: self.print_cmd() sys.stdout.flush() i = 0 for line in sp.stdout: if not cmd_printed: cmd_printed = True self.print_cmd() i += 1 if i < line_limit: print( line.rstrip() ) sys.stdout.flush() if cmd_printed: print() sys.stdout.flush() return sp.wait() == 0 def print_cmd( self ): line = "\nCOMMAND:\n" for s in self.cmd: if len( s.split( None, 1 ) ) > 1: s = "'" + s + "'" line += " " + s print( line ) class Attributes: def __init__( self, xml_attrs, obj ): self.attrs = {} for k, v in xml_attrs.items(): if obj != None: k = obj.replace_vars( k ) v = obj.replace_vars( v ) self.attrs[k] = v def remove( self, name, default=None ): try: v = self.attrs[name] del self.attrs[name] return v except: if default != None: return default raise TmkException( "Missing attribute '%s'." % name ) def empty( self ): return len(self.attrs) == 0 class EmptyElement: def __init__( self, name ): self.name = name def start_element( self, name, attrs ): raise TmkException( "Unexpected element '%s' in '%s'." % ( name, self.name ) ) def replace_vars( self, s ): return s empty_elements = { "variable": EmptyElement( "variable" ), "define": EmptyElement( "define" ), "inc-dir": EmptyElement( "inc-dir" ), "lib-dir": EmptyElement( "lib-dir" ), "library": EmptyElement( "library" ), "dependency": EmptyElement( "dependency" ), # "": EmptyElement( "" ), } class BaseClass: def __init__( self, objkind, parent ): self.objkind = objkind self.parent = parent self.variables = {} self.defines = {} self.inc_dirs = [] self.lib_dirs = [] def start_element( self, name, attrs ): if name == "variable": self.variables[attrs.remove( "name" )] = attrs.remove( "value" ) elif name == "define": self.defines[attrs.remove( "name" )] = attrs.remove( "value", "" ) elif name == "inc-dir": self.inc_dirs.append( attrs.remove( "path" ) ) elif name == "lib-dir": self.lib_dirs.append( attrs.remove( "path" ) ) else: raise TmkException( "Unknown element '%s'." % name ) return empty_elements[name] def set_var( self, name, value ): self.variables[name] = value def get_var( self, name ): if name in self.variables: return self.variables[name] if self.parent != None: return self.parent.get_var( name ) return "" def replace_vars( self, s ): while "%" in s: parts = s.split( "%" ) text = False s = parts[0] for p in parts[1:]: if text: s += p else: s += self.get_var( p ) text = not text return s def get_defines( self ): def define_iter( o ): while o != None: for k, v in o.defines.items(): yield k, v o = o.parent return define_iter( self ) def get_inc_dirs( self ): def inc_dir_iter( o ): while o != None: for i in o.inc_dirs: yield i o = o.parent return inc_dir_iter( self ) def get_lib_dirs( self ): def lib_dir_iter( o ): while o != None: for l in o.lib_dirs: yield l o = o.parent return lib_dir_iter( self ) def continue_on_error( self ): if self.parent != None: return self.parent.continue_on_error() return False class Source(BaseClass): def __init__( self, parent, attrs ): BaseClass.__init__( self, "source", parent ) self.name = attrs.remove( "file" ) self.dependencies = [] def start_element( self, name, attrs ): if name == "lib-dir": raise TmkException( "Element 'lib-dir' is not allowed in 'source'." ) elif name == "dependency": self.dependencies.append( attrs.remove( "file" ) ) return empty_elements[name] else: return BaseClass.start_element( self, name, attrs ) def compile( self ): target = self.parent obj = self.get_obj_name( target ) if check_files( self.name, obj ): uptodate = True for d in self.dependencies: if not check_files( d, obj ): uptodate = False break if uptodate: return True cmd = [ target.get_cc(), "-c", "-o", obj, self.name ] for arg in target.get_cc_args(): cmd.append( arg ) for inc in self.get_inc_dirs(): cmd.append( "-I%s" % inc ) for name, value in self.get_defines(): if value == "": cmd.append( "-D%s" % name ) else: cmd.append( "-D%s=%s" % ( name, value ) ) return Command( cmd, "C", self.name ).run() def clean( self ): target = self.parent obj = self.get_obj_name( target ) print_action( "RM", obj ) unlink_ignore( obj ) def get_obj_name( self, target ): target = self.parent basename = self.name.split( "/" )[-1].rsplit( ".", 1 )[0] obj = target.get_obj_filename( basename ) return obj class Target(BaseClass): def __init__( self, kind, parent, attrs ): BaseClass.__init__( self, kind, parent ) self.kind = kind self.name = attrs.remove( "name" ) self.sources = [] self.libraries = [] self.objdir = ".objs" def start_element( self, name, attrs ): if name == "source": source = self.create_source( attrs ) self.sources.append( source ) return source elif name == "library": self.libraries.append( attrs.remove( "name" ) ) return empty_elements["library"] else: return BaseClass.start_element( self, name, attrs ) def create_source( self, attrs ): return Source( self, attrs ) def get_cc( self ): raise TmkException( "Internal error: get_cc() is not implemented." ) def get_cc_args( self ): raise TmkException( "Internal error: get_cc_args() is not implemented." ) def build( self ): print( "BUILD:", self.name, self.get_extra_build_text() ) if not os.path.isdir( ".objs" ): os.makedirs( ".objs" ) if not self.compile(): return False rc = self.link() print() return rc def compile( self ): ok = True for src in self.sources: if not src.compile(): ok = False if not self.continue_on_error(): break return ok def link( self ): raise TmkException( "Internal error: link() is not implemented." ) def clean( self ): print( "CLEAN:", self.name, self.get_extra_build_text() ) for src in self.sources: src.clean() self.clean_impl() print() return True def clean_impl( self ): pass def get_obj_filename( self, basename ): obj = "%s.o" % ( basename, ) return os.path.join( self.objdir, obj ) def get_filename( self, ext ): if ext == None: return self.name return os.path.join( self.objdir, "%s.%s" % ( self.name, ext ) ) def get_extra_build_text( self ): return "" class TargetApp(Target): def __init__( self, kind, parent, attrs ): Target.__init__( self, kind, parent, attrs ) def get_cc( self ): return "gcc" def get_cc_args( self ): return ( "-g", "-std=c99", "-Wall", "-Wshadow", "-Winline", "-Werror" ) def link( self ): elf = self.get_filename( None ) cmd = [ self.get_cc(), "-o", elf ] for ld in self.get_lib_dirs(): cmd.append( "-L%s" % ld ) ok = True for src in self.sources: obj = src.get_obj_name( self ) if not check_files( obj, elf ): ok = False cmd.append( obj ) if ok: return True for lib in self.libraries: cmd.append( "-l" + lib ) return Command( cmd, "LINK", elf ).run() class TargetAppPlusplus(TargetApp): def __init__( self, kind, parent, attrs ): TargetApp.__init__( self, kind, parent, attrs ) def get_cc( self ): return "g++" def get_cc_args( self ): # std: c++0x c++11 return ( "-g", "-std=c++11", "-Wall", "-Wshadow", "-Winline", "-Werror" ) class TargetLib(Target): def __init__( self, kind, parent, attrs ): Target.__init__( self, kind, parent, attrs ) def get_cc( self ): return "gcc" def get_cc_args( self ): return ( "-g", "-std=c99", "-Wall", "-Wshadow", "-Winline", "-Werror" ) def link( self ): lib = "lib%s.a" % self.get_filename( None ) cmd = [ "ar", "cr", lib ] ok = True for src in self.sources: obj = src.get_obj_name( self ) if not check_files( obj, elf ): ok = False cmd.append( obj ) if ok: return True return Command( cmd, "LIB", elf ).run() class TargetAvr(Target): def __init__( self, kind, parent, attrs ): Target.__init__( self, kind, parent, attrs ) self.mcu = attrs.remove( "mcu" ) #def start_element( self, name, attrs ): # return Target.start_element( self, name, attrs ) def set_value( self, name, value ): if Target.set_value( self, name, value ): return True if name == "mcu": self.set_mcu( value ) else: return False return True def get_mcu( self ): return self.mcu def get_cc( self ): return "avr-gcc" def get_cc_args( self ): # return ( "-g", "-Wall", "-O2", "-mmcu=%s" % self.mcu ) return ( "-g", "-std=c99", "-Wall", "-Wshadow", "-Winline", "-Werror", "-O2", "-mmcu=%s" % self.mcu ) def link( self ): if not self.link_elf(): return False info = self.link_info() if info == None: print( "getting info failed." ) return False if not self.link_srec(): return False if info[2] > 0: if not self.link_eeprom_srec(): return False if not self.link_lst(): return False mcuinfo = get_avr_mcu_info( self.mcu ) names = ( "RAM", "Flash", "EEPROM" ) for i in range( 0, 3 ): p = info[i] * 100.0 / mcuinfo[i] print( "%-9s %7d %4.1f%%" % ( names[i], info[i], p ) ) return True def link_elf( self ): elf = self.get_filename( "elf" ) map = self.get_filename( "map" ) cmd = [ self.get_cc(), "-mmcu=%s" % self.mcu, "-Wl,-Map,%s" % map, "-o", elf ] for ld in self.get_lib_dirs(): cmd.append( "-L%s" % ld ) ok = True for src in self.sources: obj = src.get_obj_name( self ) if not check_files( obj, elf ): ok = False cmd.append( obj ) if ok: return True for lib in self.libraries: cmd.append( "-l" + lib ) return Command( cmd, "LINK", elf ).run() def link_info( self ): elf = self.get_filename( "elf" ) cmd = [ "avr-size", "-A", elf ] sp = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) out, err = sp.communicate() try: out = out.decode() except: pass try: err = err.decode() except: pass rc = sp.wait() if rc != 0: return None ram = 0 flash = 0 eeprom = 0 for line in out.split( "\n" ): line = line.strip() if line.startswith( "." ) and line[-1].isdigit(): section, size, addr = line.split() if section in ( ".data", ".bss", ".noinit" ): ram += int(size) elif section in ( ".text", ): flash += int(size) elif section in ( ".eeprom", ): eeprom += int(size) return ( ram, flash, eeprom ) def link_srec( self ): elf = self.get_filename( "elf" ) srec = self.get_filename( "srec" ) if check_files( elf, srec ): return True cmd = ( "avr-objcopy", "-j", ".text", "-j", ".data", "-O", "srec", elf, srec ) return Command( cmd, "SREC", srec ).run() def link_eeprom_srec( self ): elf = self.get_filename( "elf" ) srec = self.get_filename( "eeprom.srec" ) if check_files( elf, srec ): return True cmd = ( "avr-objcopy", "-j", ".eeprom", "--set-section-flags=.eeprom=alloc,load", "--change-section-lma", ".eeprom=0", "-O", "srec", elf, srec ) return Command( cmd, "SREC", srec ).run() def link_lst( self ): elf = self.get_filename( "elf" ) lst = self.get_filename( "lst" ) if check_files( elf, lst ): return True print( "LST", lst ) ofd = open( lst, "w" ) cmd = ( "avr-objdump", "-t", "-S", elf ) sp = subprocess.Popen( cmd, stdout=ofd ) sp.wait() cmd = ( "avr-objdump", "-t", "-j", ".bss", elf ) sp = subprocess.Popen( cmd, stdout=ofd ) sp.wait() cmd = ( "avr-objdump", "-t", "-j", ".comment", elf ) sp = subprocess.Popen( cmd, stdout=ofd ) sp.wait() ofd.close() return True def clean_impl( self ): for ext in ( "elf", "map", "lst", "srec" ): filename = self.get_filename( ext ) print_action( "RM", filename ) unlink_ignore( filename ) def get_obj_filename( self, basename ): obj = "%s.%s.o" % ( basename, self.mcu ) return os.path.join( self.objdir, obj ) def get_filename( self, ext ): name = "%s.%s" % ( self.name, ext ) if ext in ( "srec", "eeprom.srec" ): return name return os.path.join( self.objdir, name ) def get_extra_build_text( self ): return self.mcu class TargetAvrPlusplus(TargetAvr): def __init__( self, kind, parent, attrs ): TargetAvr.__init__( self, kind, parent, attrs ) def get_cc( self ): return "avr-g++" def get_cc_args( self ): # std: c++0x c++11 return ( "-g", "-std=c++11", "-Wall", "-Wshadow", "-Winline", "-Werror", "-O2", "-mmcu=%s" % self.mcu ) class Project(BaseClass): def __init__( self, attrs ): BaseClass.__init__( self, "project", None ) self.targets = [] version = attrs.remove( "version" ) if version != "1.0": raise TmkException( "Unsupported TMK file version '%s'." % version ) # remove schema attributes attrs.remove( "xmlns", "" ) attrs.remove( "xmlns:xsi", "" ) attrs.remove( "xsi:schemaLocation", "" ) def start_element( self, name, attrs ): if name == "target": kind = attrs.remove( "kind" ) if kind == "avr": target = TargetAvr( kind, self, attrs ) self.targets.append( target ) return target elif kind == "avr++": target = TargetAvrPlusplus( kind, self, attrs ) self.targets.append( target ) return target elif kind == "app": target = TargetApp( kind, self, attrs ) self.targets.append( target ) return target elif kind == "app++": target = TargetAppPlusplus( kind, self, attrs ) self.targets.append( target ) return target elif kind == "lib": target = TargetLib( kind, self, attrs ) self.targets.append( target ) return target else: raise TmkException( "Unsupported target kind '%s'." % kind ) else: return BaseClass.start_element( self, name, attrs ) def add_target( self, target ): self.targets.append( target ) def build( self, buildtarget=None ): ok = True for target in self.targets: if buildtarget == "all" or buildtarget == target.name: if not target.build(): ok = False if not self.continue_on_error(): break return ok def clean( self ): for target in self.targets: target.clean() return True class TMK: def __init__( self ): self.tmk_file = "tmk.xml" self.targets = [] self.project = None self.continue_on_error = False self.xml_state = None def doit( self ): if not self.parse_commandline(): return 1 if not self.read_tmk_file(): return 2 if not self.build(): return 3 return 0 def parse_commandline( self ): argc = len(sys.argv) i = 1 while i < argc: arg = sys.argv[i] i += 1 if arg == "-v": global verbose verbose = True #elif arg == "clean": else: self.targets.append( arg ) return True def build( self ): if len(self.targets) == 0: self.targets.append( "all" ) print( "=" * 60, "\n" ) # self.project.build() allok = True for target in self.targets: ok = True if target == "clean": self.project.clean() else: ok = self.project.build( target ) if not ok: allok = False if not self.continue_on_error: break return allok def read_tmk_file( self ): try: xml.sax.parse( self.tmk_file, self ) except: traceback.print_exc() return False finally: global xml_locator, xml_filename xml_filename = None xml_locator = None return True def setDocumentLocator( self, locator ): global xml_locator, xml_filename xml_filename = self.tmk_file xml_locator = locator def processingInstruction( self, target, data ): pass def startDocument( self ): self.xml_state = [] def endDocument( self ): pass def startPrefixMapping( self, prefix, uri ): pass def endPrefixMapping( self, prefix ): pass def startElement( self, name, attrs ): if len(self.xml_state) == 0: if name == "tinus-make": attrs = Attributes( attrs, None ) self.project = Project( attrs ) self.project.set_var( "HOME", os.getenv("HOME") ) self.project.set_var( "USER", os.getenv("USER") ) self.xml_state.append( self.project ) else: raise TmkException( "Invalid document element." ) else: obj = self.xml_state[-1] attrs = Attributes( attrs, obj ) obj = obj.start_element( name, attrs ) self.xml_state.append( obj ) if not attrs.empty(): raise TmkException( "Too many attributes." ) def endElement( self, name ): self.xml_state.pop() # def startElementNS( self, name, qname, attrs ): # pass # def endElementNS( self, name, qname ): # pass def characters( self, content ): pass def ignorableWhitespace( self, whitespace ): pass def skippedEntity( self, name ): pass if __name__ == "__main__": tmk = TMK() sys.exit( tmk.doit() )