diff --git a/ino/__main__.py b/ino/__main__.py new file mode 100644 index 0000000..82cceaf --- /dev/null +++ b/ino/__main__.py @@ -0,0 +1,4 @@ +from ino.runner import main + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ino/commands/build.py b/ino/commands/build.py index 65a9f8e..f699e6b 100644 --- a/ino/commands/build.py +++ b/ino/commands/build.py @@ -40,11 +40,24 @@ class Build(Command): name = 'build' help_line = "Build firmware from the current directory project" - default_make = 'make' - default_cc = 'avr-gcc' - default_cxx = 'avr-g++' - default_ar = 'avr-ar' - default_objcopy = 'avr-objcopy' + if platform.system() == 'Windows': + default_make= 'make.exe' + default_cc = 'avr-gcc.exe' + default_cxx = 'avr-g++.exe' + default_ar = 'avr-ar.exe' + default_objcopy = 'avr-objcopy.exe' + elif platform.system().startswith('CYGWIN'): + default_make= '/usr/bin/make' + default_cc = 'avr-gcc' + default_cxx = 'avr-g++' + default_ar = 'avr-ar' + default_objcopy = 'avr-objcopy' + else: + default_make= 'make' + default_cc = 'avr-gcc' + default_cxx = 'avr-g++' + default_ar = 'avr-ar' + default_objcopy = 'avr-objcopy' default_cppflags = '-ffunction-sections -fdata-sections -g -Os -w' default_cflags = '' @@ -126,7 +139,8 @@ def discover(self, args): human_name='Arduino core library') if self.e.arduino_lib_version.major: - variants_place = os.path.join(board['_coredir'], 'variants') + variants_place = os.path.join( board['_coredir'], 'variants', + board['build']['variant']) self.e.find_dir('arduino_variants_dir', ['.'], [variants_place], human_name='Arduino variants directory') @@ -165,10 +179,12 @@ def setup_flags(self, args): self.e['cppflags'].append('-DUSB_PID=%s' % board['build']['pid']) if self.e.arduino_lib_version.major: - variant_dir = os.path.join(self.e.arduino_variants_dir, - board['build']['variant']) + variant_dir = self.e.arduino_variants_dir self.e.cppflags.append('-I' + variant_dir) + self.e.cppflags.append('-I' + self.e.src_dir) + self.e.cppflags.append('-I' + self.e.lib_dir) + self.e['cflags'] = SpaceList(shlex.split(args.cflags)) self.e['cxxflags'] = SpaceList(shlex.split(args.cxxflags)) @@ -207,13 +223,22 @@ def render_template(self, source, target, **ctx): contents = template.render(**ctx) out_path = os.path.join(self.e.build_dir, target) with open(out_path, 'wt') as f: + contents = contents.split('\\') + contents = '/'.join(contents) f.write(contents) return out_path def make(self, makefile, **kwargs): + + old_path = os.environ["PATH"] + os.environ['PATH'] = os.path.dirname(os.path.abspath(self.e.make)) + os.pathsep + old_path + makefile = self.render_template(makefile + '.jinja', makefile, **kwargs) - ret = subprocess.call([self.e.make, '-f', makefile, 'all']) + print "*** RUN %s ..." % (makefile) + ret = subprocess.call([self.e.make , '-f', makefile, 'all']) + + os.environ['PATH'] = old_path if ret != 0: raise Abort("Make failed with code %s" % ret) @@ -233,13 +258,16 @@ def _scan_dependencies(self, dir, lib_dirs, inc_flags): # for this scan dependency file generated by make # with regexes to find entries that start with # libraries dirname - regexes = dict((lib, re.compile(r'\s' + lib + re.escape(os.path.sep))) for lib in lib_dirs) + regexes = dict((lib, re.compile(r'\s' + lib + re.escape('/'))) for lib in lib_dirs) + #regexes = dict((lib, re.compile(r'\s' + lib + re.escape(os.path.sep))) for lib in lib_dirs) used_libs = set() with open(output_filepath) as f: for line in f: for lib, regex in regexes.iteritems(): - if regex.search(line) and lib != dir: - used_libs.add(lib) + if regex.search(line) and lib != dir: + lib = os.path.abspath(lib) + lib = os.path.relpath(lib, os.getcwd()) + used_libs.add(lib) return used_libs diff --git a/ino/commands/clean.py b/ino/commands/clean.py index d40d858..a6d131d 100644 --- a/ino/commands/clean.py +++ b/ino/commands/clean.py @@ -4,6 +4,7 @@ import shutil from ino.commands.base import Command +from ino.exc import Abort class Clean(Command): @@ -15,7 +16,13 @@ class Clean(Command): name = 'clean' help_line = "Remove intermediate compilation files completely" + error=0 def run(self, args): if os.path.isdir(self.e.output_dir): - shutil.rmtree(self.e.output_dir) + shutil.rmtree(self.e.output_dir,onerror=self.onerror) + if self.error > 0: + raise Abort('Can\'t remove the build directory - ' + self.e.output_dir) + + def onerror(self, func, path, excinfo): + self.error += 1 diff --git a/ino/commands/upload.py b/ino/commands/upload.py index 0659e05..f39b6d1 100644 --- a/ino/commands/upload.py +++ b/ino/commands/upload.py @@ -36,40 +36,51 @@ def setup_arg_parser(self, parser): self.e.add_arduino_dist_arg(parser) def discover(self): - self.e.find_tool('stty', ['stty']) if platform.system() == 'Linux': + self.e.find_tool('stty', ['stty']) self.e.find_arduino_tool('avrdude', ['hardware', 'tools']) conf_places = self.e.arduino_dist_places(['hardware', 'tools']) conf_places.append('/etc/avrdude') # fallback to system-wide conf on Fedora self.e.find_file('avrdude.conf', places=conf_places) + + elif platform.system()== 'Windows': + self.e.find_arduino_tool('avrdude.exe', ['hardware', 'tools', 'avr', 'bin']) + self.e.find_arduino_file('avrdude.conf', ['hardware', 'tools', 'avr', 'etc']) + + elif platform.system().startswith('CYGWIN'): + self.e.find_arduino_tool('avrdude', ['hardware', 'tools', 'avr', 'bin']) + self.e.find_arduino_file('avrdude.conf', ['hardware', 'tools', 'avr', 'etc']) + else: + self.e.find_tool('stty', ['stty']) self.e.find_arduino_tool('avrdude', ['hardware', 'tools', 'avr', 'bin']) self.e.find_arduino_file('avrdude.conf', ['hardware', 'tools', 'avr', 'etc']) def run(self, args): self.discover() + port = args.serial_port or self.e.guess_serial_port() board = self.e.board_model(args.board_model) - - protocol = board['upload']['protocol'] + protocol = board['upload']['protocol'] if protocol == 'stk500': # if v1 is not specifid explicitly avrdude will # try v2 first and fail protocol = 'stk500v1' - if not os.path.exists(port): - raise Abort("%s doesn't exist. Is Arduino connected?" % port) + if port is None: + raise Abort("%s doesn't exist. Is Arduino connected?" % args.serial_port) - # send a hangup signal when the last process closes the tty - file_switch = '-f' if platform.system() == 'Darwin' else '-F' - ret = subprocess.call([self.e['stty'], file_switch, port, 'hupcl']) - if ret: - raise Abort("stty failed") + if 'stty' in self.e: + # send a hangup signal when the last process closes the tty + file_switch = '-f' if platform.system() == 'Darwin' else '-F' + ret = subprocess.call([self.e['stty'], file_switch, port[0], 'hupcl']) + if ret: + raise Abort("stty failed") # pulse on DTR try: - s = Serial(port, 115200) + s = Serial(port[0], 115200) except SerialException as e: raise Abort(str(e)) s.setDTR(False) @@ -92,7 +103,7 @@ def run(self, args): before = self.e.list_serial_ports() if port in before: ser = Serial() - ser.port = port + ser.port = port[0] ser.baudrate = 1200 ser.open() ser.close() @@ -124,13 +135,16 @@ def run(self, args): "button after initiating the upload.") port = new_port + + print "Used %s for programming." % port[0] + print "Used %s protocol" % protocol # call avrdude to upload .hex subprocess.call([ - self.e['avrdude'], + self.e['avrdude'] if 'avrdude' in self.e else self.e['avrdude.exe'], '-C', self.e['avrdude.conf'], '-p', board['build']['mcu'], - '-P', port, + '-P', port[0], '-c', protocol, '-b', board['upload']['speed'], '-D', diff --git a/ino/environment.py b/ino/environment.py index 8cb27e8..950ef44 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -8,6 +8,8 @@ import platform import hashlib import re +from serial.tools.list_ports import comports +import serial try: from collections import OrderedDict @@ -67,22 +69,37 @@ def __str__(self): class Environment(dict): templates_dir = os.path.join(os.path.dirname(__file__), 'templates') - output_dir = '.build' + output_dir = '.build_' + sys.platform src_dir = 'src' lib_dir = 'lib' hex_filename = 'firmware.hex' arduino_dist_dir = None - arduino_dist_dir_guesses = [ - '/usr/local/share/arduino', - '/usr/share/arduino', - ] - - if platform.system() == 'Darwin': + arduino_dist_dir_guesses = [] + + if platform.system().startswith('CYGWIN'): + arduino_dist_dir_guesses.insert(0, '/cygdrive/c/Arduino') + arduino_dist_dir_guesses.insert(0, '/cygdrive/c/Progra~1/Arduino') + arduino_dist_dir_guesses.insert(0, '/cygdrive/c/Progra~2/Arduino') + arduino_dist_dir_guesses.insert(0, '/cygdrive/c/Progra~3/Arduino') + arduino_dist_dir_guesses.insert(0, '/cygdrive/c/Progra~4/Arduino') + elif platform.system() == 'Windows': + arduino_dist_dir_guesses.insert(0, 'c:\\Arduino') + arduino_dist_dir_guesses.insert(0, 'c:\\Progra~1\\Arduino') + arduino_dist_dir_guesses.insert(0, 'c:\\Progra~2\\Arduino') + arduino_dist_dir_guesses.insert(0, 'c:\\Progra~3\\Arduino') + arduino_dist_dir_guesses.insert(0, 'c:\\Progra~4\\Arduino') + elif platform.system() == 'Darwin': arduino_dist_dir_guesses.insert(0, '/Applications/Arduino.app/Contents/Resources/Java') + else: + arduino_dist_dir_guesses = [ + '/usr/local/share/arduino', + '/usr/share/arduino', + ] - default_board_model = 'uno' - ino = sys.argv[0] + default_board_model = 'promicro16' + ino = os.path.abspath(sys.argv[0]) + ino = 'python ' + os.path.relpath(ino, os.getcwd()) def dump(self): if not os.path.isdir(self.output_dir): @@ -140,20 +157,35 @@ def _find(self, key, items, places, human_name, join, multi): return self[key] human_name = human_name or key + + # make sure search on current directy first + #places.insert(0,'.') # expand env variables in `places` and split on colons places = itertools.chain.from_iterable(os.path.expandvars(p).split(os.pathsep) for p in places) places = map(os.path.expanduser, places) - glob_places = itertools.chain.from_iterable(glob(p) for p in places) + glob_places = itertools.chain.from_iterable(glob(os.path.abspath(p)) for p in places) print 'Searching for', human_name, '...', + test_func = os.path.isfile if join else os.path.exists results = [] for p in glob_places: for i in items: path = os.path.join(p, i) if os.path.exists(path): result = path if join else p + #KHAI: Convert to relative path for compatible with + # windows and Linux system + result = os.path.abspath(result) + result = os.path.relpath(result, os.getcwd()) + + #KHAI added for convert window path to Linux path for + # make.exe work in window + if platform.system() == "Windows": + result = result.split('\\') + result = '/'.join(result) + if not multi: print colorize(result, 'green') self[key] = result @@ -259,6 +291,18 @@ def board_models(self): # store spectial `_coredir` value on top level so we later can build # paths relative to a core directory of a specific board model self['board_models'][multikey[0]]['_coredir'] = os.path.dirname(boards_txt) + + # Convert core and variant path in Arduino specification to system + # paths + subdict = self['board_models'] + for model in subdict: + if 'build' in subdict[model]: + modeldict = subdict[model]['build'] + for val, dir in (('core','cores'), ('variant','variants')): + if val in modeldict: + a, b, c = modeldict[val].partition(':') + if b==':': + modeldict[val] = os.path.join('..', '..', a, dir, c) return self['board_models'] @@ -285,27 +329,52 @@ def serial_port_patterns(self): return ['/dev/ttyACM*', '/dev/ttyUSB*'] if system == 'Darwin': return ['/dev/tty.usbmodem*', '/dev/tty.usbserial*'] - raise NotImplementedError("Not implemented for Windows") + elif system.startswith('CYGWIN'): + return [] + elif system == 'Windows': + return [] + raise NotImplementedError("Not implemented for %s" % system) - def list_serial_ports(self): + def list_serial_ports(self, serial_port=None): ports = [] + + for p, desc, hwid in comports(): + if hwid.startswith('USB'): + if (serial_port==str(p)): + ports.insert(0, (p,"%s: %s" % (p, desc))) + elif serial_port is None: + if ( desc.startswith('Teensy USB Serial') + or desc.startswith('Sparkfun Pro Micro')): + ports.insert(0, (p,"%s: %s" % (p, desc))) + else: + ports.append((p,"%s: %s" % (p, desc))) + for p in self.serial_port_patterns(): matches = glob(p) - ports.extend(matches) + for m in matches: + if m==serial_port: + ports.insert(0, (m,'%s' % m)) + elif serial_port is None: + ports.append((m,'%s' % m)) + return ports - def guess_serial_port(self): - print 'Guessing serial port ...', + def guess_serial_port(self, serial_port=None): + if serial_port is None: + print 'Guessing serial port ...', + else: + print 'Check serial port %s ...' % serial_port, - ports = self.list_serial_ports() + ports = self.list_serial_ports(serial_port) if ports: result = ports[0] - print colorize(result, 'yellow') + print colorize(ports[0][1], 'yellow') return result print colorize('FAILED', 'red') - raise Abort("No device matching following was found: %s" % - (''.join(['\n - ' + p for p in self.serial_port_patterns()]))) + return None + # raise Abort("No device matching following was found: %s" % + # (''.join(['\n - ' + p for p in self.serial_port_patterns()]))) def process_args(self, args): arduino_dist = getattr(args, 'arduino_dist', None) diff --git a/ino/filters.py b/ino/filters.py index fe2bf6c..3de7854 100644 --- a/ino/filters.py +++ b/ino/filters.py @@ -4,6 +4,7 @@ import os.path import fnmatch import functools +import platform from ino.utils import FileMap, SpaceList @@ -98,6 +99,9 @@ def libmap(source_dirs, target_dir): @filter def colorize(s, color): + if platform.system() == 'Windows': + return s + if not sys.stdout.isatty(): return s @@ -106,7 +110,7 @@ def colorize(s, color): 'purple': '95', 'blue': '94', 'green': '92', - 'yellow': '93', + 'yellow': '33', # 93 was too hard to see on the white background terminal 'red': '91', } diff --git a/requirements.txt b/requirements.txt index a3007a7..e832c3a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,11 @@ -jinja2 -pyserial -configobj +# Third-party module not installed with python installation +Jinja2 #last used version 2.7.3 +pyserial #last used version 2.7 +configobj #last used version 5.0.5 +glob2 #last used version 0.4.1 +setuptools #last used version 5.1 +six #last used version 1.7.2 + +# Modules installed with python installation ordereddict -argparse -glob2 +argparse \ No newline at end of file diff --git a/setup.py b/setup.py index bf7ef78..fb61711 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,32 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# run following command to install this module: +# python setup.py install +# +# run following command to uninstall this module: +# python setup.py uninstall +# OR +# cat install_*.txt | xargs rm -vrf from setuptools import setup +import sys + +# Check for uninstall in argument and do uninstall +i=0 +for e in sys.argv: + if e=='uninstall': + print "running uninstall" + import glob + import os + for a in glob.glob('install_*_%s.txt' % sys.platform): + for f in file(a).read().split('\n'): + if os.path.isfile(f): + print "Remove ", f + os.remove(f) + sys.argv.pop(i) + else: + i += 1 +if len(sys.argv)<=1: quit() install_requires = open("requirements.txt").read().split('\n') readme_content = open("README.rst").read() @@ -15,9 +40,24 @@ def gen_data_files(package_dir, subdir): ino_package_data = gen_data_files('ino', 'make') + gen_data_files('ino', 'templates') +# Look for install and --record install_*.txt file into argument for +# installation record +__version__ = '0.3.8' +for i, e in enumerate(sys.argv): + if e=='install': + # Import to verify all dependencies are satisfy, and obtain __version__ + try: + import ino.runner + sys.argv.insert(i+1,'--record') + sys.argv.insert(i+2,'install_%s_%s.txt' % (__version__, sys.platform)) + except ImportError as e: + print "Require module is not found: ", e.message + quit() + break + setup( name='ino', - version='0.3.7', + version=__version__, description='Command line toolkit for working with Arduino hardware', long_description=readme_content, author='Victor Nakoryakov, Amperka Team',