Arduino Playground is read-only starting December 31st, 2018. For more info please look at this
Forum Post
build_arduino.py - command line build script
Author: Ben Sasson
This script is intended to build and upload Arduino sketches.
Usage:
- Usage: build_arduino.py [options]
- Options:
- -h, --help show this help message and exit
- -d DIRECTORY, --directory=DIRECTORY
- project directory
- -v, --verbose be verbose
- --only-build only build, don't upload
- -u DEVICE, --upload-device=DEVICE
- use DEVICE to upload code
- -i DIRECTORY, --include=DIRECTORY
- append DIRECTORY to include list
- -l DIRECTORY, --libraries=DIRECTORY
- append DIRECTORY to libraries search & build path
- -W DIRECTORY, --WProgram-dir=DIRECTORY
- DIRECTORY of WProgram.h and the rest of core files
- --avr-path=DIRECTORY DIRECTORY where avr* programs located, if not
- specified - will assume found in default search path
- --dude-conf=FILE avrdude conf file, if not specified - will assume
- found in default location
- --simulate only simulate commands
- --core=CORE device core name [arduino]
- --arch=ARCH device architecture name [atmega328p]
- --baud=BAUD upload baud rate [57600]
- --cpu-clock=Hz target device CPU clock [16000000]
Code:
- #!/usr/bin/env python
- #
- # build_arduino.py - build, link and upload sketches script for Arduino
- # Copyright (c) 2010 Ben Sasson. All right reserved.
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <https://www.gnu.org/licenses/>.
- #
- import sys
- import os
- import optparse
- EXITCODE_OK = 0
- EXITCODE_NO_UPLOAD_DEVICE = 1
- EXITCODE_NO_WPROGRAM = 2
- EXITCODE_INVALID_AVR_PATH = 3
- CPU_CLOCK = 16000000
- ARCH = 'atmega328p'
- ENV_VERSION = 18
- BAUD = 57600
- CORE = 'arduino'
- COMPILERS = {
- '.c': 'avr-gcc',
- '.cpp': 'avr-g++',
- }
- def _exec(cmdline, debug=True, valid_exitcode=0, simulate=False):
- if debug or simulate:
- print(cmdline)
- if not simulate:
- exitcode = os.system(cmdline)
- if exitcode != valid_exitcode:
- print('-'*20 + ' exitcode %d ' % exitcode + '-'*20)
- sys.exit(exitcode)
- def compile_source(source, avr_path='', target_dir=None, arch=ARCH, clock=CPU_CLOCK, include_dirs=[], verbose=False, simulate=False):
- """
- compile a single source file, using compiler selected based on file extension and translating arguments
- to valid compiler flags.
- """
- filename, ext = os.path.splitext(source)
- compiler = COMPILERS.get(ext, None)
- if compiler is None:
- print(source, 'has no known compiler')
- return
- if target_dir is None:
- target_dir = os.path.dirname(source)
- target = os.path.join(target_dir, os.path.basename(source) + '.o')
- env = dict(source=source, target=target, arch=arch, clock=clock, env_version=ENV_VERSION, compiler=compiler, avr_path=avr_path)
- # create include list, don't use set() because order matters
- dirs = [os.path.dirname(source)]
- for d in include_dirs:
- if d not in dirs:
- dirs.append(d)
- env['include_dirs'] = ' '.join('-I%s' % d for d in dirs)
- if verbose:
- env['verbose'] = '-v'
- else:
- env['verbose'] = ''
- 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
- _exec(cmdline, simulate=simulate)
- return target
- def compile_directory(directory, target_dir=None, include_dirs=[], avr_path='', arch=ARCH, clock=CPU_CLOCK, verbose=False, simulate=False):
- """
- compile all source files in a given directory, return a list of all .obj files created
- """
- obj_files = []
- for fname in os.listdir(directory):
- if os.path.isfile(os.path.join(directory, fname)):
- 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))
- return filter(lambda o: o, obj_files)
- def append_to_archive(obj_file, archive, avr_path='', verbose=False, simulate=False):
- """
- create an .a archive out of .obj files
- """
- env = dict(obj_file=obj_file, archive=archive, avr_path=avr_path)
- if verbose:
- env['verbose'] = 'v'
- else:
- env['verbose'] = ''
- cmdline = '%(avr_path)savr-ar rcs%(verbose)s %(archive)s %(obj_file)s' % env
- _exec(cmdline, simulate=simulate)
- def link(target, files, arch=ARCH, avr_path='', verbose=False, simulate=False):
- """
- link .obj files to a single .elf file
- """
- env = dict(target=target, files=' '.join(files), link_dir=os.path.dirname(target), arch=arch, avr_path=avr_path)
- if verbose:
- env['verbose'] = '-v'
- else:
- env['verbose'] = ''
- 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
- _exec(cmdline, simulate=simulate)
- def make_hex(elf, avr_path='', verbose=False, simulate=False):
- """
- slice elf to .hex (program) end .eep (EEProm) files
- """
- eeprom_section = os.path.splitext(elf)[0] + '.epp'
- hex_section = os.path.splitext(elf)[0] + '.hex'
- env = dict(elf=elf, eeprom_section=eeprom_section, hex_section=hex_section, avr_path=avr_path)
- if verbose:
- env['verbose'] = '-v'
- else:
- env['verbose'] = ''
- 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
- _exec(cmdline_make_eeprom, simulate=simulate)
- cmdline_make_hex = '%(avr_path)savr-objcopy %(verbose)s -O ihex -R .eeprom %(elf)s %(hex_section)s' % env
- _exec(cmdline_make_hex, simulate=simulate)
- return hex_section, eeprom_section
- def upload(hex_section, dev, avr_path='', dude_conf=None, arch=ARCH, core=CORE, baud=BAUD, verbose=False, simulate=False):
- """
- Upload .hex file to arduino board
- """
- env = dict(hex_section=hex_section, dev=dev, arch=arch, core=core, baud=baud, avr_path=avr_path)
- if verbose:
- env['verbose'] = '-v'
- else:
- env['verbose'] = ''
- if dude_conf is not None:
- env['dude_conf'] = '-C' + dude_conf
- else:
- env['dude_conf'] = ''
- 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
- _exec(cmdline, simulate=simulate)
- def main(argv):
- parser = optparse.OptionParser()
- parser.add_option('-d', '--directory', dest='directory', default='.', help='project directory')
- parser.add_option('-v', '--verbose', dest='verbose', default=False, action='store_true', help='be verbose')
- parser.add_option('--only-build', dest='only_build', default=False, action='store_true', help="only build, don't upload")
- parser.add_option('-u', '--upload-device', dest='upload_device', metavar='DEVICE', help='use DEVICE to upload code')
- parser.add_option('-i', '--include', dest='include_dirs', default=[], action='append', metavar='DIRECTORY', help='append DIRECTORY to include list')
- parser.add_option('-l', '--libraries', dest='libraries', default=[], action='append', metavar='DIRECTORY', help='append DIRECTORY to libraries search & build path')
- parser.add_option('-W', '--WProgram-dir', dest='wprogram_directory', metavar='DIRECTORY', help='DIRECTORY of Arduino.h and the rest of core files')
- 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')
- 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')
- parser.add_option('--simulate', dest='simulate', default=False, action='store_true', help='only simulate commands')
- parser.add_option('--core', dest='core', default=CORE, help='device core name [%s]' % CORE)
- parser.add_option('--arch', dest='arch', default=ARCH, help='device architecture name [%s]' % ARCH)
- parser.add_option('--baud', dest='baud', default=BAUD, type='int', help='upload baud rate [%d]' % BAUD)
- 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)
- options, args = parser.parse_args(argv)
- if options.wprogram_directory is None or not os.path.exists(options.wprogram_directory) or not os.path.isdir(options.wprogram_directory):
- if options.verbose:
- print('WProgram directory was not specified or does not exist [%s]' % options.wprogram_directory)
- sys.exit(EXITCODE_NO_WPROGRAM)
- core_files = options.wprogram_directory
- if options.avr_path is not None:
- if not os.path.exists(options.avr_path) or not os.path.isdir(options.avr_path):
- if options.verbose:
- print('avr-path was specified but does not exist or not a directory [%s]' % options.avr_path)
- sys.exit(EXITCODE_INVALID_AVR_PATH)
- avr_path = os.path.join(options.avr_path, '')
- else:
- avr_path = ''
- # create build directory to store the compilation output files
- build_directory = os.path.join(options.directory, '_build')
- if not os.path.exists(build_directory):
- os.makedirs(build_directory)
- # compile arduino core files
- 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)
- # compile directories passed to program
- libraries_obj_files = []
- for library in options.libraries:
- 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))
- # compile project
- 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)
- # link project, libraries and core .obj files to a single .elf
- link_output = os.path.join(build_directory, os.path.basename(options.directory) + '.elf')
- link(link_output, project_obj_files + libraries_obj_files + core_obj_files, avr_path=avr_path, verbose=options.verbose, simulate=options.simulate)
- hex_section, eeprom_section = make_hex(link_output, avr_path=avr_path, verbose=options.verbose, simulate=options.simulate)
- # upload .hex file to arduino if needed
- if not options.only_build:
- if options.upload_device is None:
- if options.verbose:
- print('no upload device selected')
- sys.exit(EXITCODE_NO_UPLOAD_DEVICE)
- 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)
- if __name__ == '__main__':
- main(sys.argv)