From bae6fdbd6716ddcc27e9816ca098fbbb72defab6 Mon Sep 17 00:00:00 2001 From: nhatkhai Date: Tue, 1 Jul 2014 15:49:15 -0400 Subject: [PATCH 1/4] Make ino build, clean, upload, list-models compatible with windows and partially with cygwin (problem with serial access) --- ino/commands/build.py | 56 +++++++++++++++++----- ino/commands/clean.py | 10 +++- ino/commands/upload.py | 35 +++++++++----- ino/environment.py | 105 ++++++++++++++++++++++++++++++++--------- ino/filters.py | 6 ++- 5 files changed, 163 insertions(+), 49 deletions(-) diff --git a/ino/commands/build.py b/ino/commands/build.py index 65a9f8e..b383986 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') @@ -135,6 +149,14 @@ def discover(self, args): toolset = [ ('make', args.make), + ] + + for tool_key, tool_binary in toolset: + self.e.find_arduino_tool( + tool_key, ['hardware', 'tools', 'avr', 'utils','bin'], + items=[tool_binary], human_name=tool_binary) + + toolset = [ ('cc', args.cc), ('cxx', args.cxx), ('ar', args.ar), @@ -145,6 +167,7 @@ def discover(self, args): self.e.find_arduino_tool( tool_key, ['hardware', 'tools', 'avr', 'bin'], items=[tool_binary], human_name=tool_binary) + def setup_flags(self, args): board = self.e.board_model(args.board_model) @@ -165,8 +188,7 @@ 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['cflags'] = SpaceList(shlex.split(args.cflags)) @@ -207,13 +229,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,12 +264,15 @@ 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: + 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..ac6d518 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,14 @@ 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..597df7b 100644 --- a/ino/commands/upload.py +++ b/ino/commands/upload.py @@ -36,20 +36,26 @@ 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']) 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() + + port = self.e.guess_serial_port(args.serial_port) board = self.e.board_model(args.board_model) protocol = board['upload']['protocol'] @@ -58,18 +64,19 @@ def run(self, args): # 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]) except SerialException as e: raise Abort(str(e)) s.setDTR(False) @@ -92,7 +99,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 +131,15 @@ def run(self, args): "button after initiating the upload.") port = new_port + if platform.system().startswith("CYGWIN"): + port = ('COM' + str(int(port[0][9:])+1), port[1]) # 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..1114871 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -8,6 +8,7 @@ import platform import hashlib import re +from serial.tools.list_ports import comports try: from collections import OrderedDict @@ -67,22 +68,29 @@ 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.insert(0, '/Applications/Arduino.app/Contents/Resources/Java') - - default_board_model = 'uno' - ino = sys.argv[0] + arduino_dist_dir_guesses = [] + + if platform.system().startswith('CYGWIN'): + arduino_dist_dir_guesses.insert(0, '/cygdrive/c/Arduino') + elif platform.system() == 'Windows': + arduino_dist_dir_guesses.insert(0, 'c:\\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 = 'promicro16' + ino = os.path.abspath(sys.argv[0]) + ino = os.path.relpath(ino, os.getcwd()) def dump(self): if not os.path.isdir(self.output_dir): @@ -141,19 +149,35 @@ def _find(self, key, items, places, human_name, join, multi): 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 @@ -260,6 +284,18 @@ def board_models(self): # 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'] def board_model(self, key): @@ -285,27 +321,50 @@ def serial_port_patterns(self): return ['/dev/ttyACM*', '/dev/ttyUSB*'] if system == 'Darwin': return ['/dev/tty.usbmodem*', '/dev/tty.usbserial*'] + elif system.startswith('CYGWIN'): + return ['/dev/ttyS*'] raise NotImplementedError("Not implemented for Windows") - def list_serial_ports(self): + def list_serial_ports(self, serial_port=None): ports = [] - for p in self.serial_port_patterns(): - matches = glob(p) - ports.extend(matches) + + for p, desc, hwid in comports(): + if hwid.startswith('USB'): + if (serial_port==p): + ports.insert(0, (p,"%s: %s" % (p, desc))) + elif serial_port is None: + if desc.startswith('Teensy'): + ports.insert(0, (p,"%s: %s" % (p, desc))) + else: + ports.append((p,"%s: %s" % (p, desc))) + + if platform.system() != "Windows": + for p in self.serial_port_patterns(): + matches = glob(p) + 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..56cd0c6 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', # Too hard to see on the white background terminal 'red': '91', } From 76bf4e1514622946a9b7e7fecf09afde057e1b29 Mon Sep 17 00:00:00 2001 From: nhatkhai Date: Mon, 4 Aug 2014 12:04:01 -0400 Subject: [PATCH 2/4] Fully support on cygwin, and window. Assumming new revision of pyserial fully support cygwin --- ino/commands/build.py | 5 ++++- ino/commands/upload.py | 13 +++++++++---- ino/environment.py | 27 +++++++++++++++------------ requirements.txt | 14 ++++++++++---- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/ino/commands/build.py b/ino/commands/build.py index b383986..8e76605 100644 --- a/ino/commands/build.py +++ b/ino/commands/build.py @@ -190,7 +190,10 @@ def setup_flags(self, args): if self.e.arduino_lib_version.major: 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)) diff --git a/ino/commands/upload.py b/ino/commands/upload.py index 597df7b..6656724 100644 --- a/ino/commands/upload.py +++ b/ino/commands/upload.py @@ -47,6 +47,11 @@ def discover(self): 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']) @@ -57,7 +62,6 @@ def run(self, args): port = self.e.guess_serial_port(args.serial_port) board = self.e.board_model(args.board_model) - protocol = board['upload']['protocol'] if protocol == 'stk500': # if v1 is not specifid explicitly avrdude will @@ -76,7 +80,7 @@ def run(self, args): # pulse on DTR try: - s = Serial(port[0]) + s = Serial(port[0], 115200) except SerialException as e: raise Abort(str(e)) s.setDTR(False) @@ -131,8 +135,9 @@ def run(self, args): "button after initiating the upload.") port = new_port - if platform.system().startswith("CYGWIN"): - port = ('COM' + str(int(port[0][9:])+1), port[1]) + + print "Used %s for programming." % port[0] + print "Used %s protocol" % protocol # call avrdude to upload .hex subprocess.call([ diff --git a/ino/environment.py b/ino/environment.py index 1114871..1ad7383 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -9,6 +9,7 @@ import hashlib import re from serial.tools.list_ports import comports +import serial try: from collections import OrderedDict @@ -322,30 +323,32 @@ def serial_port_patterns(self): if system == 'Darwin': return ['/dev/tty.usbmodem*', '/dev/tty.usbserial*'] elif system.startswith('CYGWIN'): - return ['/dev/ttyS*'] - raise NotImplementedError("Not implemented for Windows") + return [] + elif system == 'Windows': + return [] + raise NotImplementedError("Not implemented for %s" % system) def list_serial_ports(self, serial_port=None): ports = [] for p, desc, hwid in comports(): if hwid.startswith('USB'): - if (serial_port==p): + if (serial_port==str(p)): ports.insert(0, (p,"%s: %s" % (p, desc))) elif serial_port is None: - if desc.startswith('Teensy'): + 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))) - if platform.system() != "Windows": - for p in self.serial_port_patterns(): - matches = glob(p) - for m in matches: - if m==serial_port: - ports.insert(0, (m,'%s' % m)) - elif serial_port is None: - ports.append((m,'%s' % m)) + for p in self.serial_port_patterns(): + matches = glob(p) + 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 diff --git a/requirements.txt b/requirements.txt index a3007a7..0e0fcc8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,12 @@ -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 + From 2b4f5f36dab274b1cec041bef8398c94117051b2 Mon Sep 17 00:00:00 2001 From: nhatkhai Date: Tue, 5 Aug 2014 09:13:44 -0400 Subject: [PATCH 3/4] Add __main__ for pytho -m ino to work --- ino/__main__.py | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 ino/__main__.py diff --git a/ino/__main__.py b/ino/__main__.py new file mode 100644 index 0000000..eb9d5de --- /dev/null +++ b/ino/__main__.py @@ -0,0 +1,4 @@ +from ino.runner import main + +if __name__ == '__main__': + main() From 2ab10e7e72b7ab152f4428984f68c8be1530c63c Mon Sep 17 00:00:00 2001 From: nhatkhai Date: Wed, 20 Aug 2014 16:16:34 -0400 Subject: [PATCH 4/4] Add uninstall option for setup.py, and add Arduino search paths for windows and cygwin. --- ino/environment.py | 10 +++++++++- setup.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/ino/environment.py b/ino/environment.py index 1ad7383..3215dbe 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -79,8 +79,16 @@ class Environment(dict): 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: @@ -91,7 +99,7 @@ class Environment(dict): default_board_model = 'promicro16' ino = os.path.abspath(sys.argv[0]) - ino = os.path.relpath(ino, os.getcwd()) + ino = 'python ' + os.path.relpath(ino, os.getcwd()) def dump(self): if not os.path.isdir(self.output_dir): diff --git a/setup.py b/setup.py index bf7ef78..010c2ae 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,25 @@ 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',