From b8288ca87015a33bf24a04a9bbd25e94aa2c43ed Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Tue, 15 Apr 2014 13:35:50 +0400 Subject: [PATCH 1/5] add: basic support for Arduino IDE 1.5.x --- ino/commands/build.py | 20 ++++---- ino/environment.py | 108 ++++++++++++++++++++++++++++-------------- requirements.txt | 1 + setup.py | 2 +- 4 files changed, 86 insertions(+), 45 deletions(-) diff --git a/ino/commands/build.py b/ino/commands/build.py index 7aea046..65a9f8e 100644 --- a/ino/commands/build.py +++ b/ino/commands/build.py @@ -118,18 +118,20 @@ def setup_arg_parser(self, parser): help='Verbose make output') def discover(self, args): - self.e.find_arduino_dir('arduino_core_dir', - ['hardware', 'arduino', 'cores', 'arduino'], - ['Arduino.h'] if self.e.arduino_lib_version.major else ['WProgram.h'], - 'Arduino core library') + board = self.e.board_model(args.board_model) - self.e.find_arduino_dir('arduino_libraries_dir', ['libraries'], - human_name='Arduino standard libraries') + core_place = os.path.join(board['_coredir'], 'cores', board['build']['core']) + core_header = 'Arduino.h' if self.e.arduino_lib_version.major else 'WProgram.h' + self.e.find_dir('arduino_core_dir', [core_header], [core_place], + human_name='Arduino core library') if self.e.arduino_lib_version.major: - self.e.find_arduino_dir('arduino_variants_dir', - ['hardware', 'arduino', 'variants'], - human_name='Arduino variants directory') + variants_place = os.path.join(board['_coredir'], 'variants') + self.e.find_dir('arduino_variants_dir', ['.'], [variants_place], + human_name='Arduino variants directory') + + self.e.find_arduino_dir('arduino_libraries_dir', ['libraries'], + human_name='Arduino standard libraries') toolset = [ ('make', args.make), diff --git a/ino/environment.py b/ino/environment.py index e538c99..40452bb 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -16,7 +16,7 @@ from ordereddict import OrderedDict from collections import namedtuple -from glob import glob +from glob2 import glob from ino.filters import colorize from ino.utils import format_available_options @@ -123,7 +123,7 @@ def __getattr__(self, attr): def hex_path(self): return os.path.join(self.build_dir, self.hex_filename) - def _find(self, key, items, places, human_name, join): + def _find(self, key, items, places, human_name, join, multi): if key in self: return self[key] @@ -133,39 +133,50 @@ def _find(self, key, items, places, human_name, join): 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) + print 'Searching for', human_name, '...', - for p in places: + 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 - print colorize(result, 'green') - self[key] = result - return result + if not multi: + print colorize(result, 'green') + self[key] = result + return result + results.append(result) + + if results: + formatted_results = ''.join(['\n - ' + x for x in results]) + print colorize('found multiple: %s' % formatted_results, 'green') + self[key] = results + return results print colorize('FAILED', 'red') raise Abort("%s not found. Searched in following places: %s" % (human_name, ''.join(['\n - ' + p for p in places]))) - def find_dir(self, key, items, places, human_name=None): - return self._find(key, items or ['.'], places, human_name, join=False) + def find_dir(self, key, items, places, human_name=None, multi=False): + return self._find(key, items or ['.'], places, human_name, join=False, multi=multi) - def find_file(self, key, items=None, places=None, human_name=None): - return self._find(key, items or [key], places, human_name, join=True) + def find_file(self, key, items=None, places=None, human_name=None, multi=False): + return self._find(key, items or [key], places, human_name, join=True, multi=multi) - def find_tool(self, key, items, places=None, human_name=None): - return self.find_file(key, items, places or ['$PATH'], human_name) + def find_tool(self, key, items, places=None, human_name=None, multi=False): + return self.find_file(key, items, places or ['$PATH'], human_name, multi=multi) - def find_arduino_dir(self, key, dirname_parts, items=None, human_name=None): - return self.find_dir(key, items, self.arduino_dist_places(dirname_parts), human_name) + def find_arduino_dir(self, key, dirname_parts, items=None, human_name=None, multi=False): + return self.find_dir(key, items, self.arduino_dist_places(dirname_parts), human_name, multi=multi) - def find_arduino_file(self, key, dirname_parts, items=None, human_name=None): - return self.find_file(key, items, self.arduino_dist_places(dirname_parts), human_name) + def find_arduino_file(self, key, dirname_parts, items=None, human_name=None, multi=False): + return self.find_file(key, items, self.arduino_dist_places(dirname_parts), human_name, multi=multi) - def find_arduino_tool(self, key, dirname_parts, items=None, human_name=None): + def find_arduino_tool(self, key, dirname_parts, items=None, human_name=None, multi=False): # if not bundled with Arduino Software the tool should be searched on PATH places = self.arduino_dist_places(dirname_parts) + ['$PATH'] - return self.find_file(key, items, places, human_name) + return self.find_file(key, items, places, human_name, multi=multi) def arduino_dist_places(self, dirname_parts): """ @@ -185,26 +196,53 @@ def board_models(self): if 'board_models' in self: return self['board_models'] - boards_txt = self.find_arduino_file('boards.txt', ['hardware', 'arduino'], - human_name='Board description file (boards.txt)') + # boards.txt can be placed in following places + # - hardware/arduino/boards.txt (Arduino IDE 0.xx, 1.0.x) + # - hardware/arduino/{chipset}/boards.txt (Arduino 1.5.x, chipset like `avr`, `sam`) + # - hardware/{platform}/boards.txt (MPIDE 0.xx, platform like `arduino`, `pic32`) + # we should find and merge them all + boards_txts = self.find_arduino_file('boards.txt', ['hardware', '**'], + human_name='Board description file (boards.txt)', + multi=True) self['board_models'] = BoardModels() self['board_models'].default = self.default_board_model - with open(boards_txt) as f: - for line in f: - line = line.strip() - if not line or line.startswith('#'): - continue - multikey, val = line.split('=') - multikey = multikey.split('.') - - subdict = self['board_models'] - for key in multikey[:-1]: - if key not in subdict: - subdict[key] = {} - subdict = subdict[key] - - subdict[multikey[-1]] = val + for boards_txt in boards_txts: + with open(boards_txt) as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + # Transform lines like: + # yun.upload.maximum_data_size=2560 + # into a nested dict `board_models` so that + # self['board_models']['yun']['upload']['maximum_data_size'] == 2560 + multikey, val = line.split('=') + multikey = multikey.split('.') + + # traverse into dictionary up to deepest level + # create nested dictionaries if they aren't exist yet + subdict = self['board_models'] + for key in multikey[:-1]: + if key not in subdict: + subdict[key] = {} + elif not isinstance(subdict[key], dict): + # it happens that a particular key + # has a value and has sublevels at same time. E.g.: + # diecimila.menu.cpu.atmega168=ATmega168 + # diecimila.menu.cpu.atmega168.upload.maximum_size=14336 + # diecimila.menu.cpu.atmega168.upload.maximum_data_size=1024 + # diecimila.menu.cpu.atmega168.upload.speed=19200 + # place value `ATmega168` into a special key `_` in such case + subdict[key] = {'_': subdict[key]} + subdict = subdict[key] + + subdict[multikey[-1]] = val + + # 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) return self['board_models'] diff --git a/requirements.txt b/requirements.txt index 3ad8bbd..a3007a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ pyserial configobj ordereddict argparse +glob2 diff --git a/setup.py b/setup.py index 864af51..bf7ef78 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ def gen_data_files(package_dir, subdir): setup( name='ino', - version='0.3.6', + version='0.3.7', description='Command line toolkit for working with Arduino hardware', long_description=readme_content, author='Victor Nakoryakov, Amperka Team', From c258f5fdd263de88abd04a3888237c10f7755a63 Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Tue, 15 Apr 2014 13:47:18 +0400 Subject: [PATCH 2/5] add: doc string --- ino/environment.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ino/environment.py b/ino/environment.py index 40452bb..540f682 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -124,6 +124,18 @@ def hex_path(self): return os.path.join(self.build_dir, self.hex_filename) def _find(self, key, items, places, human_name, join, multi): + """ + Search for file-system entry with any name passed in `items` on + all paths provided in `places`. Use `key` as a cache key. + + If `join` is True result will be a path join of place/item, + otherwise only place is taken as result. + + Return first found match unless `multi` is True. In that case + a list with all fount matches is returned. + + Raise `Abort` if no matches were found. + """ if key in self: return self[key] From edbeb3f9df5e1c6d640cb0cba257cf8a2f8d5a06 Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Tue, 15 Apr 2014 13:47:56 +0400 Subject: [PATCH 3/5] add: nicer formatting for found FS-entries --- ino/environment.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ino/environment.py b/ino/environment.py index 540f682..bc540f4 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -161,8 +161,12 @@ def _find(self, key, items, places, human_name, join, multi): results.append(result) if results: - formatted_results = ''.join(['\n - ' + x for x in results]) - print colorize('found multiple: %s' % formatted_results, 'green') + if len(results) > 1: + formatted_results = ''.join(['\n - ' + x for x in results]) + print colorize('found multiple: %s' % formatted_results, 'green') + else: + print colorize(results[0], 'green') + self[key] = results return results From 7b837cb2fda952bce2f582a2304bdee4909c3ef5 Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Tue, 15 Apr 2014 13:48:31 +0400 Subject: [PATCH 4/5] fix: list-models don't fail on boards without names --- ino/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ino/environment.py b/ino/environment.py index bc540f4..0a623be 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -347,5 +347,5 @@ def arduino_lib_version(self): class BoardModels(OrderedDict): def format(self): - map = [(key, val['name']) for key, val in self.iteritems()] + map = [(key, val['name']) for key, val in self.iteritems() if 'name' in val] return format_available_options(map, head_width=12, default=self.default) From ef1f6da7d1017c1a235b0d8a916b638c88cb0ae4 Mon Sep 17 00:00:00 2001 From: Victor Nakoryakov Date: Tue, 15 Apr 2014 15:46:02 +0400 Subject: [PATCH 5/5] fix: support for multiple '=' in boards.txt line --- ino/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ino/environment.py b/ino/environment.py index 0a623be..8cb27e8 100644 --- a/ino/environment.py +++ b/ino/environment.py @@ -234,7 +234,7 @@ def board_models(self): # yun.upload.maximum_data_size=2560 # into a nested dict `board_models` so that # self['board_models']['yun']['upload']['maximum_data_size'] == 2560 - multikey, val = line.split('=') + multikey, _, val = line.partition('=') multikey = multikey.split('.') # traverse into dictionary up to deepest level