#!/usr/bin/env python # Copyright JS Foundation and other contributors, http://js.foundation # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Generate pins.cpp for a specified target, using target definitions from the mbed OS source tree. It's expecting to be run from the targets/mbedos5 directory. """ from __future__ import print_function import argparse import ast import sys import os from pycparserext.ext_c_parser import GnuCParser from pycparser import parse_file, c_ast # import mbed tools sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'mbed-os')) from tools.targets import Target LICENSE = '''/* Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the \"License\"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an \"AS IS\" BASIS * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * This file is generated by generate_pins.py. Please do not modify. */ ''' def find_file(root_dir, directories, name): """ Find the first instance of file with name 'name' in the directory tree starting with 'root_dir'. Filter out directories that are not in directories, or do not start with TARGET_. Since this looks in (essentially )the same directories as the compiler would when compiling mbed OS, we should only find one PinNames.h. """ for root, dirs, files in os.walk(root_dir, topdown=True): # modify dirs in place dirs[:] = [directory for directory in dirs if directory in directories or not directory.startswith('TARGET_')] if name in files: return os.path.join(root, name) def enumerate_includes(root_dir, directories): """ Walk through the directory tree, starting at root_dir, and enumerate all valid include directories. """ for root, dirs, _ in os.walk(root_dir, topdown=True): # modify dirs in place dirs[:] = [dir_label for dir_label in dirs if dir_label in directories or (not dir_label.startswith('TARGET_') and not dir_label.startswith('TOOLCHAIN_'))] yield root class TypeDeclVisitor(c_ast.NodeVisitor): """ A TypeDecl visitor class that walks the ast and calls a visitor function for every node found. """ def __init__(self, filter_names=None): self.names = filter_names or [] def visit(self, node): value = None if node.__class__.__name__ == "TypeDecl": value = self.visit_typedecl(node) if value is None: for _, child_node in node.children(): value = value or self.visit(child_node) return value def visit_typedecl(self, node): """ Visit a node. """ if node.declname in self.names: return [pin.name for pin in node.type.values.enumerators] def enumerate_pins(c_source_file, include_dirs, definitions): """ Enumerate pins specified in PinNames.h, by looking for a PinName enum typedef somewhere in the file. """ definitions += ['__attribute(x)__=', '__extension__(x)=', 'register=', '__IO=', 'uint32_t=unsigned int'] gcc_args = ['-E', '-fmerge-all-constants'] gcc_args += ['-I' + directory for directory in include_dirs] gcc_args += ['-D' + definition for definition in definitions] parsed_ast = parse_file(c_source_file, use_cpp=True, cpp_path='arm-none-eabi-gcc', cpp_args=gcc_args, parser=GnuCParser()) # now, walk the AST visitor = TypeDeclVisitor(['PinName']) return visitor.visit(parsed_ast) def write_pins_to_file(pins, pins_file, out_cpp_file): """ Write the generated pins for a specified mbed board into the output C++ file. """ include = '\n#include "../{}"'.format(pins_file) count = ''' unsigned int jsmbed_js_magic_string_count = {}; '''.format(len(pins)) lengths = ',\n '.join(str(len(pin)) for pin in pins) lenghts_source = ''' unsigned int jsmbed_js_magic_string_lengths[] = { %s }; ''' % lengths magic_values = ',\n '.join(pins) magic_source = ''' unsigned int jsmbed_js_magic_string_values[] = { %s }; ''' % magic_values magic_strings = ',\n '.join('"' + pin + '"' for pin in pins) magic_string_source = ''' const char * jsmbed_js_magic_strings[] = { %s }; ''' % magic_strings out_cpp_file.write(LICENSE + include + count + lenghts_source + magic_source + magic_string_source) def main(): """ Perform the main function of this program """ if not os.path.exists('./mbed-os'): print("Fatal: mbed-os directory does not exist.") print("Try running 'make getlibs'") sys.exit(1) description = """ Generate pins.cpp for a specified mbed board, using target definitions from the mbed OS source tree. """ parser = argparse.ArgumentParser(description=description) parser.add_argument('board', help='mbed board name') parser.add_argument('-c', help='Output C++ file (default: %(default)s)', default='source/pins.cpp', type=argparse.FileType('w')) args = parser.parse_args() board_name = args.board.upper() target = Target.get_target(board_name) directory_labels = ['TARGET_' + label for label in target.labels] + target.macros targets_dir = os.path.join('.', 'mbed-os', 'targets') pins_file = find_file(targets_dir, directory_labels, 'PinNames.h') includes = enumerate_includes(targets_dir, directory_labels) defines = list(directory_labels) # enumerate pins from PinNames.h pins = enumerate_pins(pins_file, ['./tools'] + list(includes), defines) # first sort alphabetically, then by length. pins = sorted(pins, key=lambda x: (len(x), x.lower())) write_pins_to_file(pins, pins_file, args.c) if __name__ == "__main__": main()