1932 lines
62 KiB
Python
1932 lines
62 KiB
Python
# 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(metaclass=ABCMeta):
|
|
"""
|
|
Generic (abstract) container representing a line that will appear
|
|
in the BSP-generating program.
|
|
"""
|
|
|
|
def __init__(self, line):
|
|
self.line = line
|
|
self.generated_line = ""
|
|
self.prio = sys.maxsize
|
|
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.maxsize
|
|
|
|
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(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(input(\"" + msg + " \"), " + name + ")"
|
|
|
|
return line
|
|
|
|
|
|
class ListInputLine(InputLine, metaclass=ABCMeta):
|
|
"""
|
|
Base class for List-based Input lines. e.g. Choicelist, Checklist.
|
|
"""
|
|
|
|
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(input(msg + "\n" + choices_str), choices_val_list), default_choice)
|
|
else:
|
|
choiceval = default(find_choiceval(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(input(msg), name)
|
|
|
|
while True:
|
|
if verify_git_repo(giturl):
|
|
return giturl
|
|
giturl = default(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(input(msg), name)
|
|
|
|
while True:
|
|
if not filename and filename_can_be_null:
|
|
return filename
|
|
if os.path.isfile(filename):
|
|
return filename
|
|
filename = default(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.maxsize
|
|
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.items():
|
|
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")
|
|
properties = json.load(infile)
|
|
except IOError:
|
|
print("Couldn't open properties file %s for reading, exiting" % properties_file)
|
|
sys.exit(1)
|
|
except ValueError:
|
|
print("Wrong format on properties file %s, exiting" % properties_file)
|
|
sys.exit(1)
|
|
|
|
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.items():
|
|
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):
|
|
"""
|
|
Print available architectures, or the complete list of properties
|
|
defined by the BSP, or the possible values for a particular BSP
|
|
property.
|
|
"""
|
|
if args.karch == "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
|
|
|
|
if args.properties:
|
|
yocto_layer_list_properties(args.karch, scripts_path, args.properties_file)
|
|
elif args.property:
|
|
yocto_layer_list_property_values(args.karch, args.property, scripts_path, args.properties_file)
|
|
|
|
|
|
|
|
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"
|