build_arduino.py - command line build script
Author:  Ben Sasson

This script is intended to build and upload Arduino sketches.

Usage:

  1. Usage: build_arduino.py [options]
  2. Options:
  3. -h, --help show this help message and exit
  4. -d DIRECTORY, --directory=DIRECTORY
  5. project directory
  6. -v, --verbose be verbose
  7. --only-build only build, don't upload
  8. -u DEVICE, --upload-device=DEVICE
  9. use DEVICE to upload code
  10. -i DIRECTORY, --include=DIRECTORY
  11. append DIRECTORY to include list
  12. -l DIRECTORY, --libraries=DIRECTORY
  13. append DIRECTORY to libraries search & build path
  14. -W DIRECTORY, --WProgram-dir=DIRECTORY
  15. DIRECTORY of WProgram.h and the rest of core files
  16. --avr-path=DIRECTORY DIRECTORY where avr* programs located, if not
  17. specified - will assume found in default search path
  18. --dude-conf=FILE avrdude conf file, if not specified - will assume
  19. found in default location
  20. --simulate only simulate commands
  21. --core=CORE device core name [arduino]
  22. --arch=ARCH device architecture name [atmega328p]
  23. --baud=BAUD upload baud rate [57600]
  24. --cpu-clock=Hz target device CPU clock [16000000]

Code:

  1. #!/usr/bin/env python
  2. #
  3. # build_arduino.py - build, link and upload sketches script for Arduino
  4. # Copyright (c) 2010 Ben Sasson. All right reserved.
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import sys
  20. import os
  21. import optparse
  22. EXITCODE_OK = 0
  23. EXITCODE_NO_UPLOAD_DEVICE = 1
  24. EXITCODE_NO_WPROGRAM = 2
  25. EXITCODE_INVALID_AVR_PATH = 3
  26. CPU_CLOCK = 16000000
  27. ARCH = 'atmega328p'
  28. ENV_VERSION = 18
  29. BAUD = 57600
  30. CORE = 'arduino'
  31. COMPILERS = {
  32. '.c': 'avr-gcc',
  33. '.cpp': 'avr-g++',
  34. }
  35. def _exec(cmdline, debug=True, valid_exitcode=0, simulate=False):
  36. if debug or simulate:
  37. print(cmdline)
  38. if not simulate:
  39. exitcode = os.system(cmdline)
  40. if exitcode != valid_exitcode:
  41. print('-'*20 + ' exitcode %d ' % exitcode + '-'*20)
  42. sys.exit(exitcode)
  43. def compile_source(source, avr_path='', target_dir=None, arch=ARCH, clock=CPU_CLOCK, include_dirs=[], verbose=False, simulate=False):
  44. """
  45. compile a single source file, using compiler selected based on file extension and translating arguments
  46. to valid compiler flags.
  47. """
  48. filename, ext = os.path.splitext(source)
  49. compiler = COMPILERS.get(ext, None)
  50. if compiler is None:
  51. print(source, 'has no known compiler')
  52. return
  53. if target_dir is None:
  54. target_dir = os.path.dirname(source)
  55. target = os.path.join(target_dir, os.path.basename(source) + '.o')
  56. env = dict(source=source, target=target, arch=arch, clock=clock, env_version=ENV_VERSION, compiler=compiler, avr_path=avr_path)
  57. # create include list, don't use set() because order matters
  58. dirs = [os.path.dirname(source)]
  59. for d in include_dirs:
  60. if d not in dirs:
  61. dirs.append(d)
  62. env['include_dirs'] = ' '.join('-I%s' % d for d in dirs)
  63. if verbose:
  64. env['verbose'] = '-v'
  65. else:
  66. env['verbose'] = ''
  67. cmdline = '%(avr_path)s%(compiler)s -c %(verbose)s -g -Os -w -ffunction-sections -fdata-sections -mmcu=%(arch)s -DF_CPU=%(clock)dL -DARDUINO=%(env_version)d %(include_dirs)s %(source)s -o%(target)s' % env
  68. _exec(cmdline, simulate=simulate)
  69. return target
  70. def compile_directory(directory, target_dir=None, include_dirs=[], avr_path='', arch=ARCH, clock=CPU_CLOCK, verbose=False, simulate=False):
  71. """
  72. compile all source files in a given directory, return a list of all .obj files created
  73. """
  74. obj_files = []
  75. for fname in os.listdir(directory):
  76. if os.path.isfile(os.path.join(directory, fname)):
  77. obj_files.append(compile_source(os.path.join(directory, fname), include_dirs=include_dirs, avr_path=avr_path, target_dir=target_dir, arch=arch, clock=clock, verbose=verbose, simulate=simulate))
  78. return filter(lambda o: o, obj_files)
  79. def append_to_archive(obj_file, archive, avr_path='', verbose=False, simulate=False):
  80. """
  81. create an .a archive out of .obj files
  82. """
  83. env = dict(obj_file=obj_file, archive=archive, avr_path=avr_path)
  84. if verbose:
  85. env['verbose'] = 'v'
  86. else:
  87. env['verbose'] = ''
  88. cmdline = '%(avr_path)savr-ar rcs%(verbose)s %(archive)s %(obj_file)s' % env
  89. _exec(cmdline, simulate=simulate)
  90. def link(target, files, arch=ARCH, avr_path='', verbose=False, simulate=False):
  91. """
  92. link .obj files to a single .elf file
  93. """
  94. env = dict(target=target, files=' '.join(files), link_dir=os.path.dirname(target), arch=arch, avr_path=avr_path)
  95. if verbose:
  96. env['verbose'] = '-v'
  97. else:
  98. env['verbose'] = ''
  99. cmdline = '%(avr_path)savr-gcc %(verbose)s -Os -Wl,--gc-sections -mmcu=%(arch)s -o %(target)s %(files)s -L%(link_dir)s -lm' % env
  100. _exec(cmdline, simulate=simulate)
  101. def make_hex(elf, avr_path='', verbose=False, simulate=False):
  102. """
  103. slice elf to .hex (program) end .eep (EEProm) files
  104. """
  105. eeprom_section = os.path.splitext(elf)[0] + '.epp'
  106. hex_section = os.path.splitext(elf)[0] + '.hex'
  107. env = dict(elf=elf, eeprom_section=eeprom_section, hex_section=hex_section, avr_path=avr_path)
  108. if verbose:
  109. env['verbose'] = '-v'
  110. else:
  111. env['verbose'] = ''
  112. cmdline_make_eeprom = '%(avr_path)savr-objcopy %(verbose)s -O ihex -j .eeprom --set-section-flags=.eeprom=alloc,load --no-change-warnings --change-section-lma .eeprom=0 %(elf)s %(eeprom_section)s' % env
  113. _exec(cmdline_make_eeprom, simulate=simulate)
  114. cmdline_make_hex = '%(avr_path)savr-objcopy %(verbose)s -O ihex -R .eeprom %(elf)s %(hex_section)s' % env
  115. _exec(cmdline_make_hex, simulate=simulate)
  116. return hex_section, eeprom_section
  117. def upload(hex_section, dev, avr_path='', dude_conf=None, arch=ARCH, core=CORE, baud=BAUD, verbose=False, simulate=False):
  118. """
  119. Upload .hex file to arduino board
  120. """
  121. env = dict(hex_section=hex_section, dev=dev, arch=arch, core=core, baud=baud, avr_path=avr_path)
  122. if verbose:
  123. env['verbose'] = '-v'
  124. else:
  125. env['verbose'] = ''
  126. if dude_conf is not None:
  127. env['dude_conf'] = '-C' + dude_conf
  128. else:
  129. env['dude_conf'] = ''
  130. cmdline = '%(avr_path)savrdude %(verbose)s %(dude_conf)s -p%(arch)s -c%(core)s -P%(dev)s -b%(baud)d -D -Uflash:w:%(hex_section)s:i' % env
  131. _exec(cmdline, simulate=simulate)
  132. def main(argv):
  133. parser = optparse.OptionParser()
  134. parser.add_option('-d', '--directory', dest='directory', default='.', help='project directory')
  135. parser.add_option('-v', '--verbose', dest='verbose', default=False, action='store_true', help='be verbose')
  136. parser.add_option('--only-build', dest='only_build', default=False, action='store_true', help="only build, don't upload")
  137. parser.add_option('-u', '--upload-device', dest='upload_device', metavar='DEVICE', help='use DEVICE to upload code')
  138. parser.add_option('-i', '--include', dest='include_dirs', default=[], action='append', metavar='DIRECTORY', help='append DIRECTORY to include list')
  139. parser.add_option('-l', '--libraries', dest='libraries', default=[], action='append', metavar='DIRECTORY', help='append DIRECTORY to libraries search & build path')
  140. parser.add_option('-W', '--WProgram-dir', dest='wprogram_directory', metavar='DIRECTORY', help='DIRECTORY of Arduino.h and the rest of core files')
  141. parser.add_option('--avr-path', dest='avr_path', metavar='DIRECTORY', help='DIRECTORY where avr* programs located, if not specified - will assume found in default search path')
  142. parser.add_option('--dude-conf', dest='dude_conf', default=None, metavar='FILE', help='avrdude conf file, if not specified - will assume found in default location')
  143. parser.add_option('--simulate', dest='simulate', default=False, action='store_true', help='only simulate commands')
  144. parser.add_option('--core', dest='core', default=CORE, help='device core name [%s]' % CORE)
  145. parser.add_option('--arch', dest='arch', default=ARCH, help='device architecture name [%s]' % ARCH)
  146. parser.add_option('--baud', dest='baud', default=BAUD, type='int', help='upload baud rate [%d]' % BAUD)
  147. parser.add_option('--cpu-clock', dest='cpu_clock', default=CPU_CLOCK, metavar='Hz', action='store', type='int', help='target device CPU clock [%d]' % CPU_CLOCK)
  148. options, args = parser.parse_args(argv)
  149. if options.wprogram_directory is None or not os.path.exists(options.wprogram_directory) or not os.path.isdir(options.wprogram_directory):
  150. if options.verbose:
  151. print('WProgram directory was not specified or does not exist [%s]' % options.wprogram_directory)
  152. sys.exit(EXITCODE_NO_WPROGRAM)
  153. core_files = options.wprogram_directory
  154. if options.avr_path is not None:
  155. if not os.path.exists(options.avr_path) or not os.path.isdir(options.avr_path):
  156. if options.verbose:
  157. print('avr-path was specified but does not exist or not a directory [%s]' % options.avr_path)
  158. sys.exit(EXITCODE_INVALID_AVR_PATH)
  159. avr_path = os.path.join(options.avr_path, '')
  160. else:
  161. avr_path = ''
  162. # create build directory to store the compilation output files
  163. build_directory = os.path.join(options.directory, '_build')
  164. if not os.path.exists(build_directory):
  165. os.makedirs(build_directory)
  166. # compile arduino core files
  167. core_obj_files = compile_directory(core_files, build_directory, include_dirs=[core_files], avr_path=avr_path, arch=options.arch, clock=options.cpu_clock, verbose=options.verbose, simulate=options.simulate)
  168. # compile directories passed to program
  169. libraries_obj_files = []
  170. for library in options.libraries:
  171. libraries_obj_files.extend(compile_directory(library, build_directory, include_dirs=options.libraries + [core_files], avr_path=avr_path, arch=options.arch, clock=options.cpu_clock, verbose=options.verbose, simulate=options.simulate))
  172. # compile project
  173. project_obj_files = compile_directory(options.directory, build_directory, include_dirs=(options.include_dirs + options.libraries + [core_files]), avr_path=avr_path, arch=options.arch, clock=options.cpu_clock, verbose=options.verbose, simulate=options.simulate)
  174. # link project, libraries and core .obj files to a single .elf
  175. link_output = os.path.join(build_directory, os.path.basename(options.directory) + '.elf')
  176. link(link_output, project_obj_files + libraries_obj_files + core_obj_files, avr_path=avr_path, verbose=options.verbose, simulate=options.simulate)
  177. hex_section, eeprom_section = make_hex(link_output, avr_path=avr_path, verbose=options.verbose, simulate=options.simulate)
  178. # upload .hex file to arduino if needed
  179. if not options.only_build:
  180. if options.upload_device is None:
  181. if options.verbose:
  182. print('no upload device selected')
  183. sys.exit(EXITCODE_NO_UPLOAD_DEVICE)
  184. upload(hex_section, dev=options.upload_device, dude_conf=options.dude_conf, avr_path=avr_path, arch=options.arch, core=options.core, baud=options.baud, verbose=options.verbose, simulate=options.simulate)
  185. if __name__ == '__main__':
  186. main(sys.argv)

Share