| # ex:ts=4:sw=4:sts=4:et |
| # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- |
| # |
| # Copyright (c) 2012, Intel Corporation. |
| # All rights reserved. |
| # |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License version 2 as |
| # published by the Free Software Foundation. |
| # |
| # 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, write to the Free Software Foundation, Inc., |
| # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| # |
| # DESCRIPTION |
| # This module implements the templating engine used by 'yocto-bsp' to |
| # create BSPs. The BSP templates are simply the set of files expected |
| # to appear in a generated BSP, marked up with a small set of tags |
| # used to customize the output. The engine parses through the |
| # templates and generates a Python program containing all the logic |
| # and input elements needed to display and retrieve BSP-specific |
| # information from the user. The resulting program uses those results |
| # to generate the final BSP files. |
| # |
| # AUTHORS |
| # Tom Zanussi <tom.zanussi (at] intel.com> |
| # |
| |
| import os |
| import sys |
| from abc import ABCMeta, abstractmethod |
| from tags import * |
| import shlex |
| import json |
| import subprocess |
| import shutil |
| |
| class Line(): |
| """ |
| Generic (abstract) container representing a line that will appear |
| in the BSP-generating program. |
| """ |
| __metaclass__ = ABCMeta |
| |
| def __init__(self, line): |
| self.line = line |
| self.generated_line = "" |
| self.prio = sys.maxint |
| self.discard = False |
| |
| @abstractmethod |
| def gen(self, context = None): |
| """ |
| Generate the final executable line that will appear in the |
| BSP-generation program. |
| """ |
| pass |
| |
| def escape(self, line): |
| """ |
| Escape single and double quotes and backslashes until I find |
| something better (re.escape() escapes way too much). |
| """ |
| return line.replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'") |
| |
| def parse_error(self, msg, lineno, line): |
| raise SyntaxError("%s: %s" % (msg, line)) |
| |
| |
| class NormalLine(Line): |
| """ |
| Container for normal (non-tag) lines. |
| """ |
| def __init__(self, line): |
| Line.__init__(self, line) |
| self.is_filename = False |
| self.is_dirname = False |
| self.out_filebase = None |
| |
| def gen(self, context = None): |
| if self.is_filename: |
| line = "current_file = \"" + os.path.join(self.out_filebase, self.escape(self.line)) + "\"; of = open(current_file, \"w\")" |
| elif self.is_dirname: |
| dirname = os.path.join(self.out_filebase, self.escape(self.line)) |
| line = "if not os.path.exists(\"" + dirname + "\"): os.mkdir(\"" + dirname + "\")" |
| else: |
| line = "of.write(\"" + self.escape(self.line) + "\\n\")" |
| return line |
| |
| |
| class CodeLine(Line): |
| """ |
| Container for Python code tag lines. |
| """ |
| def __init__(self, line): |
| Line.__init__(self, line) |
| |
| def gen(self, context = None): |
| return self.line |
| |
| |
| class Assignment: |
| """ |
| Representation of everything we know about {{=name }} tags. |
| Instances of these are used by Assignment lines. |
| """ |
| def __init__(self, start, end, name): |
| self.start = start |
| self.end = end |
| self.name = name |
| |
| |
| class AssignmentLine(NormalLine): |
| """ |
| Container for normal lines containing assignment tags. Assignment |
| tags must be in ascending order of 'start' value. |
| """ |
| def __init__(self, line): |
| NormalLine.__init__(self, line) |
| self.assignments = [] |
| |
| def add_assignment(self, start, end, name): |
| self.assignments.append(Assignment(start, end, name)) |
| |
| def gen(self, context = None): |
| line = self.escape(self.line) |
| |
| for assignment in self.assignments: |
| replacement = "\" + " + assignment.name + " + \"" |
| idx = line.find(ASSIGN_TAG) |
| line = line[:idx] + replacement + line[idx + assignment.end - assignment.start:] |
| if self.is_filename: |
| return "current_file = \"" + os.path.join(self.out_filebase, line) + "\"; of = open(current_file, \"w\")" |
| elif self.is_dirname: |
| dirname = os.path.join(self.out_filebase, line) |
| return "if not os.path.exists(\"" + dirname + "\"): os.mkdir(\"" + dirname + "\")" |
| else: |
| return "of.write(\"" + line + "\\n\")" |
| |
| |
| class InputLine(Line): |
| """ |
| Base class for Input lines. |
| """ |
| def __init__(self, props, tag, lineno): |
| Line.__init__(self, tag) |
| self.props = props |
| self.lineno = lineno |
| |
| try: |
| self.prio = int(props["prio"]) |
| except KeyError: |
| self.prio = sys.maxint |
| |
| def gen(self, context = None): |
| try: |
| depends_on = self.props["depends-on"] |
| try: |
| depends_on_val = self.props["depends-on-val"] |
| except KeyError: |
| self.parse_error("No 'depends-on-val' for 'depends-on' property", |
| self.lineno, self.line) |
| except KeyError: |
| pass |
| |
| |
| class EditBoxInputLine(InputLine): |
| """ |
| Base class for 'editbox' Input lines. |
| |
| props: |
| name: example - "Load address" |
| msg: example - "Please enter the load address" |
| result: |
| Sets the value of the variable specified by 'name' to |
| whatever the user typed. |
| """ |
| def __init__(self, props, tag, lineno): |
| InputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| InputLine.gen(self, context) |
| name = self.props["name"] |
| if not name: |
| self.parse_error("No input 'name' property found", |
| self.lineno, self.line) |
| msg = self.props["msg"] |
| if not msg: |
| self.parse_error("No input 'msg' property found", |
| self.lineno, self.line) |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| msg += " [default: " + default_choice + "]" |
| |
| line = name + " = default(raw_input(\"" + msg + " \"), " + name + ")" |
| |
| return line |
| |
| |
| class GitRepoEditBoxInputLine(EditBoxInputLine): |
| """ |
| Base class for 'editbox' Input lines for user input of remote git |
| repos. This class verifies the existence and connectivity of the |
| specified git repo. |
| |
| props: |
| name: example - "Load address" |
| msg: example - "Please enter the load address" |
| result: |
| Sets the value of the variable specified by 'name' to |
| whatever the user typed. |
| """ |
| def __init__(self, props, tag, lineno): |
| EditBoxInputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| EditBoxInputLine.gen(self, context) |
| name = self.props["name"] |
| if not name: |
| self.parse_error("No input 'name' property found", |
| self.lineno, self.line) |
| msg = self.props["msg"] |
| if not msg: |
| self.parse_error("No input 'msg' property found", |
| self.lineno, self.line) |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| msg += " [default: " + default_choice + "]" |
| |
| line = name + " = get_verified_git_repo(\"" + msg + "\"," + name + ")" |
| |
| return line |
| |
| |
| class FileEditBoxInputLine(EditBoxInputLine): |
| """ |
| Base class for 'editbox' Input lines for user input of existing |
| files. This class verifies the existence of the specified file. |
| |
| props: |
| name: example - "Load address" |
| msg: example - "Please enter the load address" |
| result: |
| Sets the value of the variable specified by 'name' to |
| whatever the user typed. |
| """ |
| def __init__(self, props, tag, lineno): |
| EditBoxInputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| EditBoxInputLine.gen(self, context) |
| name = self.props["name"] |
| if not name: |
| self.parse_error("No input 'name' property found", |
| self.lineno, self.line) |
| msg = self.props["msg"] |
| if not msg: |
| self.parse_error("No input 'msg' property found", |
| self.lineno, self.line) |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| msg += " [default: " + default_choice + "]" |
| |
| line = name + " = get_verified_file(\"" + msg + "\"," + name + ", True)" |
| |
| return line |
| |
| |
| class BooleanInputLine(InputLine): |
| """ |
| Base class for boolean Input lines. |
| props: |
| name: example - "keyboard" |
| msg: example - "Got keyboard?" |
| result: |
| Sets the value of the variable specified by 'name' to "yes" or "no" |
| example - keyboard = "yes" |
| """ |
| def __init__(self, props, tag, lineno): |
| InputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| InputLine.gen(self, context) |
| name = self.props["name"] |
| if not name: |
| self.parse_error("No input 'name' property found", |
| self.lineno, self.line) |
| msg = self.props["msg"] |
| if not msg: |
| self.parse_error("No input 'msg' property found", |
| self.lineno, self.line) |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| msg += " [default: " + default_choice + "]" |
| |
| line = name + " = boolean(raw_input(\"" + msg + " \"), " + name + ")" |
| |
| return line |
| |
| |
| class ListInputLine(InputLine): |
| """ |
| Base class for List-based Input lines. e.g. Choicelist, Checklist. |
| """ |
| __metaclass__ = ABCMeta |
| |
| def __init__(self, props, tag, lineno): |
| InputLine.__init__(self, props, tag, lineno) |
| self.choices = [] |
| |
| def gen_choicepair_list(self): |
| """Generate a list of 2-item val:desc lists from self.choices.""" |
| if not self.choices: |
| return None |
| |
| choicepair_list = list() |
| |
| for choice in self.choices: |
| choicepair = [] |
| choicepair.append(choice.val) |
| choicepair.append(choice.desc) |
| choicepair_list.append(choicepair) |
| |
| return choicepair_list |
| |
| def gen_degenerate_choicepair_list(self, choices): |
| """Generate a list of 2-item val:desc with val=desc from passed-in choices.""" |
| choicepair_list = list() |
| |
| for choice in choices: |
| choicepair = [] |
| choicepair.append(choice) |
| choicepair.append(choice) |
| choicepair_list.append(choicepair) |
| |
| return choicepair_list |
| |
| def exec_listgen_fn(self, context = None): |
| """ |
| Execute the list-generating function contained as a string in |
| the "gen" property. |
| """ |
| retval = None |
| try: |
| fname = self.props["gen"] |
| modsplit = fname.split('.') |
| mod_fn = modsplit.pop() |
| mod = '.'.join(modsplit) |
| |
| __import__(mod) |
| # python 2.7 has a better way to do this using importlib.import_module |
| m = sys.modules[mod] |
| |
| fn = getattr(m, mod_fn) |
| if not fn: |
| self.parse_error("couldn't load function specified for 'gen' property ", |
| self.lineno, self.line) |
| retval = fn(context) |
| if not retval: |
| self.parse_error("function specified for 'gen' property returned nothing ", |
| self.lineno, self.line) |
| except KeyError: |
| pass |
| |
| return retval |
| |
| def gen_choices_str(self, choicepairs): |
| """ |
| Generate a numbered list of choices from a list of choicepairs |
| for display to the user. |
| """ |
| choices_str = "" |
| |
| for i, choicepair in enumerate(choicepairs): |
| choices_str += "\t" + str(i + 1) + ") " + choicepair[1] + "\n" |
| |
| return choices_str |
| |
| def gen_choices_val_str(self, choicepairs): |
| """ |
| Generate an array of choice values corresponding to the |
| numbered list generated by gen_choices_str(). |
| """ |
| choices_val_list = "[" |
| |
| for i, choicepair in enumerate(choicepairs): |
| choices_val_list += "\"" + choicepair[0] + "\"," |
| choices_val_list += "]" |
| |
| return choices_val_list |
| |
| def gen_choices_val_list(self, choicepairs): |
| """ |
| Generate an array of choice values corresponding to the |
| numbered list generated by gen_choices_str(). |
| """ |
| choices_val_list = [] |
| |
| for i, choicepair in enumerate(choicepairs): |
| choices_val_list.append(choicepair[0]) |
| |
| return choices_val_list |
| |
| def gen_choices_list(self, context = None, checklist = False): |
| """ |
| Generate an array of choice values corresponding to the |
| numbered list generated by gen_choices_str(). |
| """ |
| choices = self.exec_listgen_fn(context) |
| if choices: |
| if len(choices) == 0: |
| self.parse_error("No entries available for input list", |
| self.lineno, self.line) |
| choicepairs = self.gen_degenerate_choicepair_list(choices) |
| else: |
| if len(self.choices) == 0: |
| self.parse_error("No entries available for input list", |
| self.lineno, self.line) |
| choicepairs = self.gen_choicepair_list() |
| |
| return choicepairs |
| |
| def gen_choices(self, context = None, checklist = False): |
| """ |
| Generate an array of choice values corresponding to the |
| numbered list generated by gen_choices_str(), display it to |
| the user, and process the result. |
| """ |
| msg = self.props["msg"] |
| if not msg: |
| self.parse_error("No input 'msg' property found", |
| self.lineno, self.line) |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| msg += " [default: " + default_choice + "]" |
| |
| choicepairs = self.gen_choices_list(context, checklist) |
| |
| choices_str = self.gen_choices_str(choicepairs) |
| choices_val_list = self.gen_choices_val_list(choicepairs) |
| if checklist: |
| choiceval = default(find_choicevals(raw_input(msg + "\n" + choices_str), choices_val_list), default_choice) |
| else: |
| choiceval = default(find_choiceval(raw_input(msg + "\n" + choices_str), choices_val_list), default_choice) |
| |
| return choiceval |
| |
| |
| def find_choiceval(choice_str, choice_list): |
| """ |
| Take number as string and return val string from choice_list, |
| empty string if oob. choice_list is a simple python list. |
| """ |
| choice_val = "" |
| |
| try: |
| choice_idx = int(choice_str) |
| if choice_idx <= len(choice_list): |
| choice_idx -= 1 |
| choice_val = choice_list[choice_idx] |
| except ValueError: |
| pass |
| |
| return choice_val |
| |
| |
| def find_choicevals(choice_str, choice_list): |
| """ |
| Take numbers as space-separated string and return vals list from |
| choice_list, empty list if oob. choice_list is a simple python |
| list. |
| """ |
| choice_vals = [] |
| |
| choices = choice_str.split() |
| for choice in choices: |
| choice_vals.append(find_choiceval(choice, choice_list)) |
| |
| return choice_vals |
| |
| |
| def default(input_str, name): |
| """ |
| Return default if no input_str, otherwise stripped input_str. |
| """ |
| if not input_str: |
| return name |
| |
| return input_str.strip() |
| |
| |
| def verify_git_repo(giturl): |
| """ |
| Verify that the giturl passed in can be connected to. This can be |
| used as a check for the existence of the given repo and/or basic |
| git remote connectivity. |
| |
| Returns True if the connection was successful, fals otherwise |
| """ |
| if not giturl: |
| return False |
| |
| gitcmd = "git ls-remote %s > /dev/null 2>&1" % (giturl) |
| rc = subprocess.call(gitcmd, shell=True) |
| if rc == 0: |
| return True |
| |
| return False |
| |
| |
| def get_verified_git_repo(input_str, name): |
| """ |
| Return git repo if verified, otherwise loop forever asking user |
| for filename. |
| """ |
| msg = input_str.strip() + " " |
| |
| giturl = default(raw_input(msg), name) |
| |
| while True: |
| if verify_git_repo(giturl): |
| return giturl |
| giturl = default(raw_input(msg), name) |
| |
| |
| def get_verified_file(input_str, name, filename_can_be_null): |
| """ |
| Return filename if the file exists, otherwise loop forever asking |
| user for filename. |
| """ |
| msg = input_str.strip() + " " |
| |
| filename = default(raw_input(msg), name) |
| |
| while True: |
| if not filename and filename_can_be_null: |
| return filename |
| if os.path.isfile(filename): |
| return filename |
| filename = default(raw_input(msg), name) |
| |
| |
| def replace_file(replace_this, with_this): |
| """ |
| Replace the given file with the contents of filename, retaining |
| the original filename. |
| """ |
| try: |
| replace_this.close() |
| shutil.copy(with_this, replace_this.name) |
| except IOError: |
| pass |
| |
| |
| def boolean(input_str, name): |
| """ |
| Return lowercase version of first char in string, or value in name. |
| """ |
| if not input_str: |
| return name |
| |
| str = input_str.lower().strip() |
| if str and str[0] == "y" or str[0] == "n": |
| return str[0] |
| else: |
| return name |
| |
| |
| def strip_base(input_str): |
| """ |
| strip '/base' off the end of input_str, so we can use 'base' in |
| the branch names we present to the user. |
| """ |
| if input_str and input_str.endswith("/base"): |
| return input_str[:-len("/base")] |
| return input_str.strip() |
| |
| |
| deferred_choices = {} |
| |
| def gen_choices_defer(input_line, context, checklist = False): |
| """ |
| Save the context hashed the name of the input item, which will be |
| passed to the gen function later. |
| """ |
| name = input_line.props["name"] |
| |
| try: |
| nameappend = input_line.props["nameappend"] |
| except KeyError: |
| nameappend = "" |
| |
| try: |
| branches_base = input_line.props["branches_base"] |
| except KeyError: |
| branches_base = "" |
| |
| filename = input_line.props["filename"] |
| |
| closetag_start = filename.find(CLOSE_TAG) |
| |
| if closetag_start != -1: |
| filename = filename[closetag_start + len(CLOSE_TAG):] |
| |
| filename = filename.strip() |
| filename = os.path.splitext(filename)[0] |
| |
| captured_context = capture_context(context) |
| context["filename"] = filename |
| captured_context["filename"] = filename |
| context["nameappend"] = nameappend |
| captured_context["nameappend"] = nameappend |
| context["branches_base"] = branches_base |
| captured_context["branches_base"] = branches_base |
| |
| deferred_choice = (input_line, captured_context, checklist) |
| key = name + "_" + filename + "_" + nameappend |
| deferred_choices[key] = deferred_choice |
| |
| |
| def invoke_deferred_choices(name): |
| """ |
| Invoke the choice generation function using the context hashed by |
| 'name'. |
| """ |
| deferred_choice = deferred_choices[name] |
| input_line = deferred_choice[0] |
| context = deferred_choice[1] |
| checklist = deferred_choice[2] |
| |
| context["name"] = name |
| |
| choices = input_line.gen_choices(context, checklist) |
| |
| return choices |
| |
| |
| class ChoicelistInputLine(ListInputLine): |
| """ |
| Base class for choicelist Input lines. |
| props: |
| name: example - "xserver_choice" |
| msg: example - "Please select an xserver for this machine" |
| result: |
| Sets the value of the variable specified by 'name' to whichever Choice was chosen |
| example - xserver_choice = "xserver_vesa" |
| """ |
| def __init__(self, props, tag, lineno): |
| ListInputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| InputLine.gen(self, context) |
| |
| gen_choices_defer(self, context) |
| name = self.props["name"] |
| nameappend = context["nameappend"] |
| filename = context["filename"] |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| line = name + " = default(invoke_deferred_choices(\"" + name + "_" + filename + "_" + nameappend + "\"), \"" + default_choice + "\")" |
| |
| return line |
| |
| |
| class ListValInputLine(InputLine): |
| """ |
| Abstract base class for choice and checkbox Input lines. |
| """ |
| def __init__(self, props, tag, lineno): |
| InputLine.__init__(self, props, tag, lineno) |
| |
| try: |
| self.val = self.props["val"] |
| except KeyError: |
| self.parse_error("No input 'val' property found", self.lineno, self.line) |
| |
| try: |
| self.desc = self.props["msg"] |
| except KeyError: |
| self.parse_error("No input 'msg' property found", self.lineno, self.line) |
| |
| |
| class ChoiceInputLine(ListValInputLine): |
| """ |
| Base class for choicelist item Input lines. |
| """ |
| def __init__(self, props, tag, lineno): |
| ListValInputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| return None |
| |
| |
| class ChecklistInputLine(ListInputLine): |
| """ |
| Base class for checklist Input lines. |
| """ |
| def __init__(self, props, tag, lineno): |
| ListInputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| InputLine.gen(self, context) |
| |
| gen_choices_defer(self, context, True) |
| name = self.props["name"] |
| nameappend = context["nameappend"] |
| filename = context["filename"] |
| |
| try: |
| default_choice = self.props["default"] |
| except KeyError: |
| default_choice = "" |
| |
| line = name + " = default(invoke_deferred_choices(\"" + name + "_" + filename + "_" + nameappend + "\"), \"" + default_choice + "\")" |
| |
| return line |
| |
| |
| class CheckInputLine(ListValInputLine): |
| """ |
| Base class for checklist item Input lines. |
| """ |
| def __init__(self, props, tag, lineno): |
| ListValInputLine.__init__(self, props, tag, lineno) |
| |
| def gen(self, context = None): |
| return None |
| |
| |
| dirname_substitutions = {} |
| |
| class SubstrateBase(object): |
| """ |
| Base class for both expanded and unexpanded file and dir container |
| objects. |
| """ |
| def __init__(self, filename, filebase, out_filebase): |
| self.filename = filename |
| self.filebase = filebase |
| self.translated_filename = filename |
| self.out_filebase = out_filebase |
| self.raw_lines = [] |
| self.expanded_lines = [] |
| self.prev_choicelist = None |
| |
| def parse_error(self, msg, lineno, line): |
| raise SyntaxError("%s: [%s: %d]: %s" % (msg, self.filename, lineno, line)) |
| |
| def expand_input_tag(self, tag, lineno): |
| """ |
| Input tags consist of the word 'input' at the beginning, |
| followed by name:value property pairs which are converted into |
| a dictionary. |
| """ |
| propstr = tag[len(INPUT_TAG):] |
| |
| props = dict(prop.split(":", 1) for prop in shlex.split(propstr)) |
| props["filename"] = self.filename |
| |
| input_type = props[INPUT_TYPE_PROPERTY] |
| if not props[INPUT_TYPE_PROPERTY]: |
| self.parse_error("No input 'type' property found", lineno, tag) |
| |
| if input_type == "boolean": |
| return BooleanInputLine(props, tag, lineno) |
| if input_type == "edit": |
| return EditBoxInputLine(props, tag, lineno) |
| if input_type == "edit-git-repo": |
| return GitRepoEditBoxInputLine(props, tag, lineno) |
| if input_type == "edit-file": |
| return FileEditBoxInputLine(props, tag, lineno) |
| elif input_type == "choicelist": |
| self.prev_choicelist = ChoicelistInputLine(props, tag, lineno) |
| return self.prev_choicelist |
| elif input_type == "choice": |
| if not self.prev_choicelist: |
| self.parse_error("Found 'choice' input tag but no previous choicelist", |
| lineno, tag) |
| choice = ChoiceInputLine(props, tag, lineno) |
| self.prev_choicelist.choices.append(choice) |
| return choice |
| elif input_type == "checklist": |
| return ChecklistInputLine(props, tag, lineno) |
| elif input_type == "check": |
| return CheckInputLine(props, tag, lineno) |
| |
| def expand_assignment_tag(self, start, line, lineno): |
| """ |
| Expand all tags in a line. |
| """ |
| expanded_line = AssignmentLine(line.rstrip()) |
| |
| while start != -1: |
| end = line.find(CLOSE_TAG, start) |
| if end == -1: |
| self.parse_error("No close tag found for assignment tag", lineno, line) |
| else: |
| name = line[start + len(ASSIGN_TAG):end].strip() |
| expanded_line.add_assignment(start, end + len(CLOSE_TAG), name) |
| start = line.find(ASSIGN_TAG, end) |
| |
| return expanded_line |
| |
| def expand_tag(self, line, lineno): |
| """ |
| Returns a processed tag line, or None if there was no tag |
| |
| The rules for tags are very simple: |
| - No nested tags |
| - Tags start with {{ and end with }} |
| - An assign tag, {{=, can appear anywhere and will |
| be replaced with what the assignment evaluates to |
| - Any other tag occupies the whole line it is on |
| - if there's anything else on the tag line, it's an error |
| - if it starts with 'input', it's an input tag and |
| will only be used for prompting and setting variables |
| - anything else is straight Python |
| - tags are in effect only until the next blank line or tag or 'pass' tag |
| - we don't have indentation in tags, but we need some way to end a block |
| forcefully without blank lines or other tags - that's the 'pass' tag |
| - todo: implement pass tag |
| - directories and filenames can have tags as well, but only assignment |
| and 'if' code lines |
| - directories and filenames are the only case where normal tags can |
| coexist with normal text on the same 'line' |
| """ |
| start = line.find(ASSIGN_TAG) |
| if start != -1: |
| return self.expand_assignment_tag(start, line, lineno) |
| |
| start = line.find(OPEN_TAG) |
| if start == -1: |
| return None |
| |
| end = line.find(CLOSE_TAG, 0) |
| if end == -1: |
| self.parse_error("No close tag found for open tag", lineno, line) |
| |
| tag = line[start + len(OPEN_TAG):end].strip() |
| |
| if not tag.lstrip().startswith(INPUT_TAG): |
| return CodeLine(tag) |
| |
| return self.expand_input_tag(tag, lineno) |
| |
| def append_translated_filename(self, filename): |
| """ |
| Simply append filename to translated_filename |
| """ |
| self.translated_filename = os.path.join(self.translated_filename, filename) |
| |
| def get_substituted_file_or_dir_name(self, first_line, tag): |
| """ |
| If file or dir names contain name substitutions, return the name |
| to substitute. Note that this is just the file or dirname and |
| doesn't include the path. |
| """ |
| filename = first_line.find(tag) |
| if filename != -1: |
| filename += len(tag) |
| substituted_filename = first_line[filename:].strip() |
| this = substituted_filename.find(" this") |
| if this != -1: |
| head, tail = os.path.split(self.filename) |
| substituted_filename = substituted_filename[:this + 1] + tail |
| if tag == DIRNAME_TAG: # get rid of .noinstall in dirname |
| substituted_filename = substituted_filename.split('.')[0] |
| |
| return substituted_filename |
| |
| def get_substituted_filename(self, first_line): |
| """ |
| If a filename contains a name substitution, return the name to |
| substitute. Note that this is just the filename and doesn't |
| include the path. |
| """ |
| return self.get_substituted_file_or_dir_name(first_line, FILENAME_TAG) |
| |
| def get_substituted_dirname(self, first_line): |
| """ |
| If a dirname contains a name substitution, return the name to |
| substitute. Note that this is just the dirname and doesn't |
| include the path. |
| """ |
| return self.get_substituted_file_or_dir_name(first_line, DIRNAME_TAG) |
| |
| def substitute_filename(self, first_line): |
| """ |
| Find the filename in first_line and append it to translated_filename. |
| """ |
| substituted_filename = self.get_substituted_filename(first_line) |
| self.append_translated_filename(substituted_filename); |
| |
| def substitute_dirname(self, first_line): |
| """ |
| Find the dirname in first_line and append it to translated_filename. |
| """ |
| substituted_dirname = self.get_substituted_dirname(first_line) |
| self.append_translated_filename(substituted_dirname); |
| |
| def is_filename_substitution(self, line): |
| """ |
| Do we have a filename subustition? |
| """ |
| if line.find(FILENAME_TAG) != -1: |
| return True |
| return False |
| |
| def is_dirname_substitution(self, line): |
| """ |
| Do we have a dirname subustition? |
| """ |
| if line.find(DIRNAME_TAG) != -1: |
| return True |
| return False |
| |
| def translate_dirname(self, first_line): |
| """ |
| Just save the first_line mapped by filename. The later pass |
| through the directories will look for a dirname.noinstall |
| match and grab the substitution line. |
| """ |
| dirname_substitutions[self.filename] = first_line |
| |
| def translate_dirnames_in_path(self, path): |
| """ |
| Translate dirnames below this file or dir, not including tail. |
| dirname_substititions is keyed on actual untranslated filenames. |
| translated_path contains the subsititutions for each element. |
| """ |
| remainder = path[len(self.filebase)+1:] |
| translated_path = untranslated_path = self.filebase |
| |
| untranslated_dirs = remainder.split(os.sep) |
| |
| for dir in untranslated_dirs: |
| key = os.path.join(untranslated_path, dir + '.noinstall') |
| try: |
| first_line = dirname_substitutions[key] |
| except KeyError: |
| translated_path = os.path.join(translated_path, dir) |
| untranslated_path = os.path.join(untranslated_path, dir) |
| continue |
| substituted_dir = self.get_substituted_dirname(first_line) |
| translated_path = os.path.join(translated_path, substituted_dir) |
| untranslated_path = os.path.join(untranslated_path, dir) |
| |
| return translated_path |
| |
| def translate_file_or_dir_name(self): |
| """ |
| Originally we were allowed to use open/close/assign tags and python |
| code in the filename, which fit in nicely with the way we |
| processed the templates and generated code. Now that we can't |
| do that, we make those tags proper file contents and have this |
| pass substitute the nice but non-functional names with those |
| 'strange' ones, and then proceed as usual. |
| |
| So, if files or matching dir<.noinstall> files contain |
| filename substitutions, this function translates them into the |
| corresponding 'strange' names, which future passes will expand |
| as they always have. The resulting pathname is kept in the |
| file or directory's translated_filename. Another way to think |
| about it is that self.filename is the input filename, and |
| translated_filename is the output filename before expansion. |
| """ |
| # remove leaf file or dirname |
| head, tail = os.path.split(self.filename) |
| translated_path = self.translate_dirnames_in_path(head) |
| self.translated_filename = translated_path |
| |
| # This is a dirname - does it have a matching .noinstall with |
| # a substitution? If so, apply the dirname subsititution. |
| if not os.path.isfile(self.filename): |
| key = self.filename + ".noinstall" |
| try: |
| first_line = dirname_substitutions[key] |
| except KeyError: |
| self.append_translated_filename(tail) |
| return |
| self.substitute_dirname(first_line) |
| return |
| |
| f = open(self.filename) |
| first_line = f.readline() |
| f.close() |
| |
| # This is a normal filename not needing translation, just use |
| # it as-is. |
| if not first_line or not first_line.startswith("#"): |
| self.append_translated_filename(tail) |
| return |
| |
| # If we have a filename substitution (first line in the file |
| # is a FILENAME_TAG line) do the substitution now. If we have |
| # a dirname substitution (DIRNAME_TAG in dirname.noinstall |
| # meta-file), hash it so we can apply it when we see the |
| # matching dirname later. Otherwise we have a regular |
| # filename, just use it as-is. |
| if self.is_filename_substitution(first_line): |
| self.substitute_filename(first_line) |
| elif self.is_dirname_substitution(first_line): |
| self.translate_dirname(first_line) |
| else: |
| self.append_translated_filename(tail) |
| |
| def expand_file_or_dir_name(self): |
| """ |
| Expand file or dir names into codeline. Dirnames and |
| filenames can only have assignments or if statements. First |
| translate if statements into CodeLine + (dirname or filename |
| creation). |
| """ |
| lineno = 0 |
| |
| line = self.translated_filename[len(self.filebase):] |
| if line.startswith("/"): |
| line = line[1:] |
| opentag_start = -1 |
| |
| start = line.find(OPEN_TAG) |
| while start != -1: |
| if not line[start:].startswith(ASSIGN_TAG): |
| opentag_start = start |
| break |
| start += len(ASSIGN_TAG) |
| start = line.find(OPEN_TAG, start) |
| |
| if opentag_start != -1: |
| end = line.find(CLOSE_TAG, opentag_start) |
| if end == -1: |
| self.parse_error("No close tag found for open tag", lineno, line) |
| # we have a {{ tag i.e. code |
| tag = line[opentag_start + len(OPEN_TAG):end].strip() |
| if not tag.lstrip().startswith(IF_TAG): |
| self.parse_error("Only 'if' tags are allowed in file or directory names", |
| lineno, line) |
| self.expanded_lines.append(CodeLine(tag)) |
| |
| # everything after }} is the actual filename (possibly with assignments) |
| # everything before is the pathname |
| line = line[:opentag_start] + line[end + len(CLOSE_TAG):].strip() |
| |
| assign_start = line.find(ASSIGN_TAG) |
| if assign_start != -1: |
| assignment_tag = self.expand_assignment_tag(assign_start, line, lineno) |
| if isinstance(self, SubstrateFile): |
| assignment_tag.is_filename = True |
| assignment_tag.out_filebase = self.out_filebase |
| elif isinstance(self, SubstrateDir): |
| assignment_tag.is_dirname = True |
| assignment_tag.out_filebase = self.out_filebase |
| self.expanded_lines.append(assignment_tag) |
| return |
| |
| normal_line = NormalLine(line) |
| if isinstance(self, SubstrateFile): |
| normal_line.is_filename = True |
| normal_line.out_filebase = self.out_filebase |
| elif isinstance(self, SubstrateDir): |
| normal_line.is_dirname = True |
| normal_line.out_filebase = self.out_filebase |
| self.expanded_lines.append(normal_line) |
| |
| def expand(self): |
| """ |
| Expand the file or dir name first, eventually this ends up |
| creating the file or dir. |
| """ |
| self.translate_file_or_dir_name() |
| self.expand_file_or_dir_name() |
| |
| |
| class SubstrateFile(SubstrateBase): |
| """ |
| Container for both expanded and unexpanded substrate files. |
| """ |
| def __init__(self, filename, filebase, out_filebase): |
| SubstrateBase.__init__(self, filename, filebase, out_filebase) |
| |
| def read(self): |
| if self.raw_lines: |
| return |
| f = open(self.filename) |
| self.raw_lines = f.readlines() |
| |
| def expand(self): |
| """Expand the contents of all template tags in the file.""" |
| SubstrateBase.expand(self) |
| self.read() |
| |
| for lineno, line in enumerate(self.raw_lines): |
| # only first line can be a filename substitition |
| if lineno == 0 and line.startswith("#") and FILENAME_TAG in line: |
| continue # skip it - we've already expanded it |
| expanded_line = self.expand_tag(line, lineno + 1) # humans not 0-based |
| if not expanded_line: |
| expanded_line = NormalLine(line.rstrip()) |
| self.expanded_lines.append(expanded_line) |
| |
| def gen(self, context = None): |
| """Generate the code that generates the BSP.""" |
| base_indent = 0 |
| |
| indent = new_indent = base_indent |
| |
| for line in self.expanded_lines: |
| genline = line.gen(context) |
| if not genline: |
| continue |
| if isinstance(line, InputLine): |
| line.generated_line = genline |
| continue |
| if genline.startswith(OPEN_START): |
| if indent == 1: |
| base_indent = 1 |
| if indent: |
| if genline == BLANKLINE_STR or (not genline.startswith(NORMAL_START) |
| and not genline.startswith(OPEN_START)): |
| indent = new_indent = base_indent |
| if genline.endswith(":"): |
| new_indent = base_indent + 1 |
| line.generated_line = (indent * INDENT_STR) + genline |
| indent = new_indent |
| |
| |
| class SubstrateDir(SubstrateBase): |
| """ |
| Container for both expanded and unexpanded substrate dirs. |
| """ |
| def __init__(self, filename, filebase, out_filebase): |
| SubstrateBase.__init__(self, filename, filebase, out_filebase) |
| |
| def expand(self): |
| SubstrateBase.expand(self) |
| |
| def gen(self, context = None): |
| """Generate the code that generates the BSP.""" |
| indent = new_indent = 0 |
| for line in self.expanded_lines: |
| genline = line.gen(context) |
| if not genline: |
| continue |
| if genline.endswith(":"): |
| new_indent = 1 |
| else: |
| new_indent = 0 |
| line.generated_line = (indent * INDENT_STR) + genline |
| indent = new_indent |
| |
| |
| def expand_target(target, all_files, out_filebase): |
| """ |
| Expand the contents of all template tags in the target. This |
| means removing tags and categorizing or creating lines so that |
| future passes can process and present input lines and generate the |
| corresponding lines of the Python program that will be exec'ed to |
| actually produce the final BSP. 'all_files' includes directories. |
| """ |
| for root, dirs, files in os.walk(target): |
| for file in files: |
| if file.endswith("~") or file.endswith("#"): |
| continue |
| f = os.path.join(root, file) |
| sfile = SubstrateFile(f, target, out_filebase) |
| sfile.expand() |
| all_files.append(sfile) |
| |
| for dir in dirs: |
| d = os.path.join(root, dir) |
| sdir = SubstrateDir(d, target, out_filebase) |
| sdir.expand() |
| all_files.append(sdir) |
| |
| |
| def gen_program_machine_lines(machine, program_lines): |
| """ |
| Use the input values we got from the command line. |
| """ |
| line = "machine = \"" + machine + "\"" |
| program_lines.append(line) |
| |
| line = "layer_name = \"" + machine + "\"" |
| program_lines.append(line) |
| |
| |
| def sort_inputlines(input_lines): |
| """Sort input lines according to priority (position).""" |
| input_lines.sort(key = lambda l: l.prio) |
| |
| |
| def find_parent_dependency(lines, depends_on): |
| for i, line in lines: |
| if isinstance(line, CodeLine): |
| continue |
| if line.props["name"] == depends_on: |
| return i |
| |
| return -1 |
| |
| |
| def process_inputline_dependencies(input_lines, all_inputlines): |
| """If any input lines depend on others, put the others first.""" |
| for line in input_lines: |
| if isinstance(line, InputLineGroup): |
| group_inputlines = [] |
| process_inputline_dependencies(line.group, group_inputlines) |
| line.group = group_inputlines |
| all_inputlines.append(line) |
| continue |
| |
| if isinstance(line, CodeLine) or isinstance(line, NormalLine): |
| all_inputlines.append(line) |
| continue |
| |
| try: |
| depends_on = line.props["depends-on"] |
| depends_codeline = "if " + line.props["depends-on"] + " == \"" + line.props["depends-on-val"] + "\":" |
| all_inputlines.append(CodeLine(depends_codeline)) |
| all_inputlines.append(line) |
| except KeyError: |
| all_inputlines.append(line) |
| |
| |
| def conditional_filename(filename): |
| """ |
| Check if the filename itself contains a conditional statement. If |
| so, return a codeline for it. |
| """ |
| opentag_start = filename.find(OPEN_TAG) |
| |
| if opentag_start != -1: |
| if filename[opentag_start:].startswith(ASSIGN_TAG): |
| return None |
| end = filename.find(CLOSE_TAG, opentag_start) |
| if end == -1: |
| print "No close tag found for open tag in filename %s" % filename |
| sys.exit(1) |
| |
| # we have a {{ tag i.e. code |
| tag = filename[opentag_start + len(OPEN_TAG):end].strip() |
| if not tag.lstrip().startswith(IF_TAG): |
| print "Only 'if' tags are allowed in file or directory names, filename: %s" % filename |
| sys.exit(1) |
| |
| return CodeLine(tag) |
| |
| return None |
| |
| |
| class InputLineGroup(InputLine): |
| """ |
| InputLine that does nothing but group other input lines |
| corresponding to all the input lines in a SubstrateFile so they |
| can be generated as a group. prio is the only property used. |
| """ |
| def __init__(self, codeline): |
| InputLine.__init__(self, {}, "", 0) |
| self.group = [] |
| self.prio = sys.maxint |
| self.group.append(codeline) |
| |
| def append(self, line): |
| self.group.append(line) |
| if line.prio < self.prio: |
| self.prio = line.prio |
| |
| def len(self): |
| return len(self.group) |
| |
| |
| def gather_inputlines(files): |
| """ |
| Gather all the InputLines - we want to generate them first. |
| """ |
| all_inputlines = [] |
| input_lines = [] |
| |
| for file in files: |
| if isinstance(file, SubstrateFile): |
| group = None |
| basename = os.path.basename(file.translated_filename) |
| |
| codeline = conditional_filename(basename) |
| if codeline: |
| group = InputLineGroup(codeline) |
| |
| have_condition = False |
| condition_to_write = None |
| for line in file.expanded_lines: |
| if isinstance(line, CodeLine): |
| have_condition = True |
| condition_to_write = line |
| continue |
| if isinstance(line, InputLine): |
| if group: |
| if condition_to_write: |
| condition_to_write.prio = line.prio |
| condition_to_write.discard = True |
| group.append(condition_to_write) |
| condition_to_write = None |
| group.append(line) |
| else: |
| if condition_to_write: |
| condition_to_write.prio = line.prio |
| condition_to_write.discard = True |
| input_lines.append(condition_to_write) |
| condition_to_write = None |
| input_lines.append(line) |
| else: |
| if condition_to_write: |
| condition_to_write = None |
| if have_condition: |
| if not line.line.strip(): |
| line.discard = True |
| input_lines.append(line) |
| have_condition = False |
| |
| if group and group.len() > 1: |
| input_lines.append(group) |
| |
| sort_inputlines(input_lines) |
| process_inputline_dependencies(input_lines, all_inputlines) |
| |
| return all_inputlines |
| |
| |
| def run_program_lines(linelist, codedump): |
| """ |
| For a single file, print all the python code into a buf and execute it. |
| """ |
| buf = "\n".join(linelist) |
| |
| if codedump: |
| of = open("bspgen.out", "w") |
| of.write(buf) |
| of.close() |
| exec buf |
| |
| |
| def gen_target(files, context = None): |
| """ |
| Generate the python code for each file. |
| """ |
| for file in files: |
| file.gen(context) |
| |
| |
| def gen_program_header_lines(program_lines): |
| """ |
| Generate any imports we need. |
| """ |
| program_lines.append("current_file = \"\"") |
| |
| |
| def gen_supplied_property_vals(properties, program_lines): |
| """ |
| Generate user-specified entries for input values instead of |
| generating input prompts. |
| """ |
| for name, val in properties.iteritems(): |
| program_line = name + " = \"" + val + "\"" |
| program_lines.append(program_line) |
| |
| |
| def gen_initial_property_vals(input_lines, program_lines): |
| """ |
| Generate null or default entries for input values, so we don't |
| have undefined variables. |
| """ |
| for line in input_lines: |
| if isinstance(line, InputLineGroup): |
| gen_initial_property_vals(line.group, program_lines) |
| continue |
| |
| if isinstance(line, InputLine): |
| try: |
| name = line.props["name"] |
| try: |
| default_val = "\"" + line.props["default"] + "\"" |
| except: |
| default_val = "\"\"" |
| program_line = name + " = " + default_val |
| program_lines.append(program_line) |
| except KeyError: |
| pass |
| |
| |
| def gen_program_input_lines(input_lines, program_lines, context, in_group = False): |
| """ |
| Generate only the input lines used for prompting the user. For |
| that, we only have input lines and CodeLines that affect the next |
| input line. |
| """ |
| indent = new_indent = 0 |
| |
| for line in input_lines: |
| if isinstance(line, InputLineGroup): |
| gen_program_input_lines(line.group, program_lines, context, True) |
| continue |
| if not line.line.strip(): |
| continue |
| |
| genline = line.gen(context) |
| if not genline: |
| continue |
| if genline.endswith(":"): |
| new_indent += 1 |
| else: |
| if indent > 1 or (not in_group and indent): |
| new_indent -= 1 |
| |
| line.generated_line = (indent * INDENT_STR) + genline |
| program_lines.append(line.generated_line) |
| |
| indent = new_indent |
| |
| |
| def gen_program_lines(target_files, program_lines): |
| """ |
| Generate the program lines that make up the BSP generation |
| program. This appends the generated lines of all target_files to |
| program_lines, and skips input lines, which are dealt with |
| separately, or omitted. |
| """ |
| for file in target_files: |
| if file.filename.endswith("noinstall"): |
| continue |
| |
| for line in file.expanded_lines: |
| if isinstance(line, InputLine): |
| continue |
| if line.discard: |
| continue |
| |
| program_lines.append(line.generated_line) |
| |
| |
| def create_context(machine, arch, scripts_path): |
| """ |
| Create a context object for use in deferred function invocation. |
| """ |
| context = {} |
| |
| context["machine"] = machine |
| context["arch"] = arch |
| context["scripts_path"] = scripts_path |
| |
| return context |
| |
| |
| def capture_context(context): |
| """ |
| Create a context object for use in deferred function invocation. |
| """ |
| captured_context = {} |
| |
| captured_context["machine"] = context["machine"] |
| captured_context["arch"] = context["arch"] |
| captured_context["scripts_path"] = context["scripts_path"] |
| |
| return captured_context |
| |
| |
| def expand_targets(context, bsp_output_dir, expand_common=True): |
| """ |
| Expand all the tags in both the common and machine-specific |
| 'targets'. |
| |
| If expand_common is False, don't expand the common target (this |
| option is used to create special-purpose layers). |
| """ |
| target_files = [] |
| |
| machine = context["machine"] |
| arch = context["arch"] |
| scripts_path = context["scripts_path"] |
| |
| lib_path = scripts_path + '/lib' |
| bsp_path = lib_path + '/bsp' |
| arch_path = bsp_path + '/substrate/target/arch' |
| |
| if expand_common: |
| common = os.path.join(arch_path, "common") |
| expand_target(common, target_files, bsp_output_dir) |
| |
| arches = os.listdir(arch_path) |
| if arch not in arches or arch == "common": |
| print "Invalid karch, exiting\n" |
| sys.exit(1) |
| |
| target = os.path.join(arch_path, arch) |
| expand_target(target, target_files, bsp_output_dir) |
| |
| gen_target(target_files, context) |
| |
| return target_files |
| |
| |
| def yocto_common_create(machine, target, scripts_path, layer_output_dir, codedump, properties_file, properties_str="", expand_common=True): |
| """ |
| Common layer-creation code |
| |
| machine - user-defined machine name (if needed, will generate 'machine' var) |
| target - the 'target' the layer will be based on, must be one in |
| scripts/lib/bsp/substrate/target/arch |
| scripts_path - absolute path to yocto /scripts dir |
| layer_output_dir - dirname to create for layer |
| codedump - dump generated code to bspgen.out |
| properties_file - use values from this file if nonempty i.e no prompting |
| properties_str - use values from this string if nonempty i.e no prompting |
| expand_common - boolean, use the contents of (for bsp layers) arch/common |
| """ |
| if os.path.exists(layer_output_dir): |
| print "\nlayer output dir already exists, exiting. (%s)" % layer_output_dir |
| sys.exit(1) |
| |
| properties = None |
| |
| if properties_file: |
| try: |
| infile = open(properties_file, "r") |
| except IOError: |
| print "Couldn't open properties file %s for reading, exiting" % properties_file |
| sys.exit(1) |
| |
| properties = json.load(infile) |
| |
| if properties_str and not properties: |
| properties = json.loads(properties_str) |
| |
| os.mkdir(layer_output_dir) |
| |
| context = create_context(machine, target, scripts_path) |
| target_files = expand_targets(context, layer_output_dir, expand_common) |
| |
| input_lines = gather_inputlines(target_files) |
| |
| program_lines = [] |
| |
| gen_program_header_lines(program_lines) |
| |
| gen_initial_property_vals(input_lines, program_lines) |
| |
| if properties: |
| gen_supplied_property_vals(properties, program_lines) |
| |
| gen_program_machine_lines(machine, program_lines) |
| |
| if not properties: |
| gen_program_input_lines(input_lines, program_lines, context) |
| |
| gen_program_lines(target_files, program_lines) |
| |
| run_program_lines(program_lines, codedump) |
| |
| |
| def yocto_layer_create(layer_name, scripts_path, layer_output_dir, codedump, properties_file, properties=""): |
| """ |
| Create yocto layer |
| |
| layer_name - user-defined layer name |
| scripts_path - absolute path to yocto /scripts dir |
| layer_output_dir - dirname to create for layer |
| codedump - dump generated code to bspgen.out |
| properties_file - use values from this file if nonempty i.e no prompting |
| properties - use values from this string if nonempty i.e no prompting |
| """ |
| yocto_common_create(layer_name, "layer", scripts_path, layer_output_dir, codedump, properties_file, properties, False) |
| |
| print "\nNew layer created in %s.\n" % (layer_output_dir) |
| print "Don't forget to add it to your BBLAYERS (for details see %s\README)." % (layer_output_dir) |
| |
| |
| def yocto_bsp_create(machine, arch, scripts_path, bsp_output_dir, codedump, properties_file, properties=None): |
| """ |
| Create bsp |
| |
| machine - user-defined machine name |
| arch - the arch the bsp will be based on, must be one in |
| scripts/lib/bsp/substrate/target/arch |
| scripts_path - absolute path to yocto /scripts dir |
| bsp_output_dir - dirname to create for BSP |
| codedump - dump generated code to bspgen.out |
| properties_file - use values from this file if nonempty i.e no prompting |
| properties - use values from this string if nonempty i.e no prompting |
| """ |
| yocto_common_create(machine, arch, scripts_path, bsp_output_dir, codedump, properties_file, properties) |
| |
| print "\nNew %s BSP created in %s" % (arch, bsp_output_dir) |
| |
| |
| def print_dict(items, indent = 0): |
| """ |
| Print the values in a possibly nested dictionary. |
| """ |
| for key, val in items.iteritems(): |
| print " "*indent + "\"%s\" :" % key, |
| if type(val) == dict: |
| print "{" |
| print_dict(val, indent + 1) |
| print " "*indent + "}" |
| else: |
| print "%s" % val |
| |
| |
| def get_properties(input_lines): |
| """ |
| Get the complete set of properties for all the input items in the |
| BSP, as a possibly nested dictionary. |
| """ |
| properties = {} |
| |
| for line in input_lines: |
| if isinstance(line, InputLineGroup): |
| statement = line.group[0].line |
| group_properties = get_properties(line.group) |
| properties[statement] = group_properties |
| continue |
| |
| if not isinstance(line, InputLine): |
| continue |
| |
| if isinstance(line, ChoiceInputLine): |
| continue |
| |
| props = line.props |
| item = {} |
| name = props["name"] |
| for key, val in props.items(): |
| if not key == "name": |
| item[key] = val |
| properties[name] = item |
| |
| return properties |
| |
| |
| def yocto_layer_list_properties(arch, scripts_path, properties_file, expand_common=True): |
| """ |
| List the complete set of properties for all the input items in the |
| layer. If properties_file is non-null, write the complete set of |
| properties as a nested JSON object corresponding to a possibly |
| nested dictionary. |
| """ |
| context = create_context("unused", arch, scripts_path) |
| target_files = expand_targets(context, "unused", expand_common) |
| |
| input_lines = gather_inputlines(target_files) |
| |
| properties = get_properties(input_lines) |
| if properties_file: |
| try: |
| of = open(properties_file, "w") |
| except IOError: |
| print "Couldn't open properties file %s for writing, exiting" % properties_file |
| sys.exit(1) |
| |
| json.dump(properties, of, indent=1) |
| else: |
| print_dict(properties) |
| |
| |
| def split_nested_property(property): |
| """ |
| A property name of the form x.y describes a nested property |
| i.e. the property y is contained within x and can be addressed |
| using standard JSON syntax for nested properties. Note that if a |
| property name itself contains '.', it should be contained in |
| double quotes. |
| """ |
| splittable_property = "" |
| in_quotes = False |
| for c in property: |
| if c == '.' and not in_quotes: |
| splittable_property += '\n' |
| continue |
| if c == '"': |
| in_quotes = not in_quotes |
| splittable_property += c |
| |
| split_properties = splittable_property.split('\n') |
| |
| if len(split_properties) > 1: |
| return split_properties |
| |
| return None |
| |
| |
| def find_input_line_group(substring, input_lines): |
| """ |
| Find and return the InputLineGroup containing the specified substring. |
| """ |
| for line in input_lines: |
| if isinstance(line, InputLineGroup): |
| if substring in line.group[0].line: |
| return line |
| |
| return None |
| |
| |
| def find_input_line(name, input_lines): |
| """ |
| Find the input line with the specified name. |
| """ |
| for line in input_lines: |
| if isinstance(line, InputLineGroup): |
| l = find_input_line(name, line.group) |
| if l: |
| return l |
| |
| if isinstance(line, InputLine): |
| try: |
| if line.props["name"] == name: |
| return line |
| if line.props["name"] + "_" + line.props["nameappend"] == name: |
| return line |
| except KeyError: |
| pass |
| |
| return None |
| |
| |
| def print_values(type, values_list): |
| """ |
| Print the values in the given list of values. |
| """ |
| if type == "choicelist": |
| for value in values_list: |
| print "[\"%s\", \"%s\"]" % (value[0], value[1]) |
| elif type == "boolean": |
| for value in values_list: |
| print "[\"%s\", \"%s\"]" % (value[0], value[1]) |
| |
| |
| def yocto_layer_list_property_values(arch, property, scripts_path, properties_file, expand_common=True): |
| """ |
| List the possible values for a given input property. If |
| properties_file is non-null, write the complete set of properties |
| as a JSON object corresponding to an array of possible values. |
| """ |
| context = create_context("unused", arch, scripts_path) |
| context["name"] = property |
| |
| target_files = expand_targets(context, "unused", expand_common) |
| |
| input_lines = gather_inputlines(target_files) |
| |
| properties = get_properties(input_lines) |
| |
| nested_properties = split_nested_property(property) |
| if nested_properties: |
| # currently the outer property of a nested property always |
| # corresponds to an input line group |
| input_line_group = find_input_line_group(nested_properties[0], input_lines) |
| if input_line_group: |
| input_lines[:] = input_line_group.group[1:] |
| # The inner property of a nested property name is the |
| # actual property name we want, so reset to that |
| property = nested_properties[1] |
| |
| input_line = find_input_line(property, input_lines) |
| if not input_line: |
| print "Couldn't find values for property %s" % property |
| return |
| |
| values_list = [] |
| |
| type = input_line.props["type"] |
| if type == "boolean": |
| values_list.append(["y", "n"]) |
| elif type == "choicelist" or type == "checklist": |
| try: |
| gen_fn = input_line.props["gen"] |
| if nested_properties: |
| context["filename"] = nested_properties[0] |
| try: |
| context["branches_base"] = input_line.props["branches_base"] |
| except KeyError: |
| context["branches_base"] = None |
| values_list = input_line.gen_choices_list(context, False) |
| except KeyError: |
| for choice in input_line.choices: |
| choicepair = [] |
| choicepair.append(choice.val) |
| choicepair.append(choice.desc) |
| values_list.append(choicepair) |
| |
| if properties_file: |
| try: |
| of = open(properties_file, "w") |
| except IOError: |
| print "Couldn't open properties file %s for writing, exiting" % properties_file |
| sys.exit(1) |
| |
| json.dump(values_list, of) |
| |
| print_values(type, values_list) |
| |
| |
| def yocto_bsp_list(args, scripts_path, properties_file): |
| """ |
| Print available architectures, or the complete list of properties |
| defined by the BSP, or the possible values for a particular BSP |
| property. |
| """ |
| if len(args) < 1: |
| return False |
| |
| if args[0] == "karch": |
| lib_path = scripts_path + '/lib' |
| bsp_path = lib_path + '/bsp' |
| arch_path = bsp_path + '/substrate/target/arch' |
| print "Architectures available:" |
| for arch in os.listdir(arch_path): |
| if arch == "common" or arch == "layer": |
| continue |
| print " %s" % arch |
| return True |
| else: |
| arch = args[0] |
| |
| if len(args) < 2 or len(args) > 3: |
| return False |
| |
| if len(args) == 2: |
| if args[1] == "properties": |
| yocto_layer_list_properties(arch, scripts_path, properties_file) |
| else: |
| return False |
| |
| if len(args) == 3: |
| if args[1] == "property": |
| yocto_layer_list_property_values(arch, args[2], scripts_path, properties_file) |
| else: |
| return False |
| |
| return True |
| |
| |
| def yocto_layer_list(args, scripts_path, properties_file): |
| """ |
| Print the complete list of input properties defined by the layer, |
| or the possible values for a particular layer property. |
| """ |
| if len(args) < 1: |
| return False |
| |
| if len(args) < 1 or len(args) > 2: |
| return False |
| |
| if len(args) == 1: |
| if args[0] == "properties": |
| yocto_layer_list_properties("layer", scripts_path, properties_file, False) |
| else: |
| return False |
| |
| if len(args) == 2: |
| if args[0] == "property": |
| yocto_layer_list_property_values("layer", args[1], scripts_path, properties_file, False) |
| else: |
| return False |
| |
| return True |
| |
| |
| def map_standard_kbranch(need_new_kbranch, new_kbranch, existing_kbranch): |
| """ |
| Return the linux-yocto bsp branch to use with the specified |
| kbranch. This handles the -standard variants for 3.4 and 3.8; the |
| other variants don't need mappings. |
| """ |
| if need_new_kbranch == "y": |
| kbranch = new_kbranch |
| else: |
| kbranch = existing_kbranch |
| |
| if kbranch.startswith("standard/common-pc-64"): |
| return "bsp/common-pc-64/common-pc-64-standard.scc" |
| if kbranch.startswith("standard/common-pc"): |
| return "bsp/common-pc/common-pc-standard.scc" |
| else: |
| return "ktypes/standard/standard.scc" |
| |
| |
| def map_preempt_rt_kbranch(need_new_kbranch, new_kbranch, existing_kbranch): |
| """ |
| Return the linux-yocto bsp branch to use with the specified |
| kbranch. This handles the -preempt-rt variants for 3.4 and 3.8; |
| the other variants don't need mappings. |
| """ |
| if need_new_kbranch == "y": |
| kbranch = new_kbranch |
| else: |
| kbranch = existing_kbranch |
| |
| if kbranch.startswith("standard/preempt-rt/common-pc-64"): |
| return "bsp/common-pc-64/common-pc-64-preempt-rt.scc" |
| if kbranch.startswith("standard/preempt-rt/common-pc"): |
| return "bsp/common-pc/common-pc-preempt-rt.scc" |
| else: |
| return "ktypes/preempt-rt/preempt-rt.scc" |
| |
| |
| def map_tiny_kbranch(need_new_kbranch, new_kbranch, existing_kbranch): |
| """ |
| Return the linux-yocto bsp branch to use with the specified |
| kbranch. This handles the -tiny variants for 3.4 and 3.8; the |
| other variants don't need mappings. |
| """ |
| if need_new_kbranch == "y": |
| kbranch = new_kbranch |
| else: |
| kbranch = existing_kbranch |
| |
| if kbranch.startswith("standard/tiny/common-pc"): |
| return "bsp/common-pc/common-pc-tiny.scc" |
| else: |
| return "ktypes/tiny/tiny.scc" |