summaryrefslogtreecommitdiffstats
path: root/scripts
diff options
context:
space:
mode:
authorTom Zanussi <tom.zanussi@intel.com>2012-01-24 00:20:15 -0600
committerRichard Purdie <richard.purdie@linuxfoundation.org>2012-03-22 19:21:15 +0000
commitcd8182e6892986d73a1f0252d38682b9d5c07b22 (patch)
treeefcf627128c9139075d88de3fff42193f5240db3 /scripts
parentcf80db1c85ebdf2c74bf71c41c5b211e8a395f06 (diff)
downloadpoky-cd8182e6892986d73a1f0252d38682b9d5c07b22.tar.gz
yocto-bsp: add templating engine
The main implementation of the Yocto BSP templating engine, essentially containing the internal implementation of the 'yocto-bsp create' and yocto-bsp list' commands. Signed-off-by: Tom Zanussi <tom.zanussi@intel.com>
Diffstat (limited to 'scripts')
-rw-r--r--scripts/lib/bsp/engine.py1436
1 files changed, 1436 insertions, 0 deletions
diff --git a/scripts/lib/bsp/engine.py b/scripts/lib/bsp/engine.py
new file mode 100644
index 0000000000..d2f0735a65
--- /dev/null
+++ b/scripts/lib/bsp/engine.py
@@ -0,0 +1,1436 @@
1# ex:ts=4:sw=4:sts=4:et
2# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
3#
4# Copyright (c) 2012, Intel Corporation.
5# All rights reserved.
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License version 2 as
9# published by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program; if not, write to the Free Software Foundation, Inc.,
18# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19#
20# DESCRIPTION
21# This module implements the templating engine used by 'yocto-bsp' to
22# create BSPs. The BSP templates are simply the set of files expected
23# to appear in a generated BSP, marked up with a small set of tags
24# used to customize the output. The engine parses through the
25# templates and generates a Python program containing all the logic
26# and input elements needed to display and retrieve BSP-specific
27# information from the user. The resulting program uses those results
28# to generate the final BSP files.
29#
30# AUTHORS
31# Tom Zanussi <tom.zanussi (at] intel.com>
32#
33
34import os
35import sys
36from abc import ABCMeta, abstractmethod
37from tags import *
38import shlex
39import json
40
41class Line():
42 """
43 Generic (abstract) container representing a line that will appear
44 in the BSP-generating program.
45 """
46 __metaclass__ = ABCMeta
47
48 def __init__(self, line):
49 self.line = line
50 self.generated_line = ""
51 self.prio = sys.maxint
52 self.discard = False
53
54 @abstractmethod
55 def gen(self, context = None):
56 """
57 Generate the final executable line that will appear in the
58 BSP-generation program.
59 """
60 pass
61
62 def escape(self, line):
63 """
64 Escape single and double quotes and backslashes until I find
65 something better (re.escape() escapes way too much).
66 """
67 return line.replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'")
68
69 def parse_error(self, msg, lineno, line):
70 raise SyntaxError("%s: %s" % (msg, line))
71
72
73class NormalLine(Line):
74 """
75 Container for normal (non-tag) lines.
76 """
77 def __init__(self, line):
78 Line.__init__(self, line)
79 self.is_filename = False
80 self.is_dirname = False
81 self.out_filebase = None
82
83 def gen(self, context = None):
84 if self.is_filename:
85 line = "of = open(\"" + os.path.join(self.out_filebase, self.escape(self.line)) + "\", \"w\")"
86 elif self.is_dirname:
87 dirname = os.path.join(self.out_filebase, self.escape(self.line))
88 line = "if not os.path.exists(\"" + dirname + "\"): os.mkdir(\"" + dirname + "\")"
89 else:
90 line = "of.write(\"" + self.escape(self.line) + "\\n\")"
91 return line
92
93
94class CodeLine(Line):
95 """
96 Container for Python code tag lines.
97 """
98 def __init__(self, line):
99 Line.__init__(self, line)
100
101 def gen(self, context = None):
102 return self.line
103
104
105class Assignment:
106 """
107 Representation of everything we know about {{=name }} tags.
108 Instances of these are used by Assignment lines.
109 """
110 def __init__(self, start, end, name):
111 self.start = start
112 self.end = end
113 self.name = name
114
115
116class AssignmentLine(NormalLine):
117 """
118 Container for normal lines containing assignment tags. Assignment
119 tags must be in ascending order of 'start' value.
120 """
121 def __init__(self, line):
122 NormalLine.__init__(self, line)
123 self.assignments = []
124
125 def add_assignment(self, start, end, name):
126 self.assignments.append(Assignment(start, end, name))
127
128 def gen(self, context = None):
129 line = self.escape(self.line)
130
131 for assignment in self.assignments:
132 replacement = "\" + " + assignment.name + " + \""
133 idx = line.find(ASSIGN_TAG)
134 line = line[:idx] + replacement + line[idx + assignment.end - assignment.start:]
135 if self.is_filename:
136 return "of = open(\"" + os.path.join(self.out_filebase, line) + "\", \"w\")"
137 elif self.is_dirname:
138 dirname = os.path.join(self.out_filebase, line)
139 return "if not os.path.exists(\"" + dirname + "\"): os.mkdir(\"" + dirname + "\")"
140 else:
141 return "of.write(\"" + line + "\\n\")"
142
143
144class InputLine(Line):
145 """
146 Base class for Input lines.
147 """
148 def __init__(self, props, tag, lineno):
149 Line.__init__(self, tag)
150 self.props = props
151 self.lineno = lineno
152
153 try:
154 self.prio = int(props["prio"])
155 except KeyError:
156 self.prio = sys.maxint
157
158 def gen(self, context = None):
159 try:
160 depends_on = self.props["depends-on"]
161 try:
162 depends_on_val = self.props["depends-on-val"]
163 except KeyError:
164 self.parse_error("No 'depends-on-val' for 'depends-on' property",
165 self.lineno, self.line)
166 except KeyError:
167 pass
168
169
170class EditBoxInputLine(InputLine):
171 """
172 Base class for 'editbox' Input lines.
173
174 props:
175 name: example - "Load address"
176 msg: example - "Please enter the load address"
177 result:
178 Sets the value of the variable specified by 'name' to
179 whatever the user typed.
180 """
181 def __init__(self, props, tag, lineno):
182 InputLine.__init__(self, props, tag, lineno)
183
184 def gen(self, context = None):
185 InputLine.gen(self, context)
186 name = self.props["name"]
187 if not name:
188 self.parse_error("No input 'name' property found",
189 self.lineno, self.line)
190 msg = self.props["msg"]
191 if not msg:
192 self.parse_error("No input 'msg' property found",
193 self.lineno, self.line)
194
195 try:
196 default_choice = self.props["default"]
197 except KeyError:
198 default_choice = ""
199
200 msg += " [default: " + default_choice + "]"
201
202 line = name + " = default(raw_input(\"" + msg + " \"), " + name + ")"
203
204 return line
205
206
207class BooleanInputLine(InputLine):
208 """
209 Base class for boolean Input lines.
210 props:
211 name: example - "keyboard"
212 msg: example - "Got keyboard?"
213 result:
214 Sets the value of the variable specified by 'name' to "yes" or "no"
215 example - keyboard = "yes"
216 """
217 def __init__(self, props, tag, lineno):
218 InputLine.__init__(self, props, tag, lineno)
219
220 def gen(self, context = None):
221 InputLine.gen(self, context)
222 name = self.props["name"]
223 if not name:
224 self.parse_error("No input 'name' property found",
225 self.lineno, self.line)
226 msg = self.props["msg"]
227 if not msg:
228 self.parse_error("No input 'msg' property found",
229 self.lineno, self.line)
230
231 try:
232 default_choice = self.props["default"]
233 except KeyError:
234 default_choice = ""
235
236 msg += " [default: " + default_choice + "]"
237
238 line = name + " = boolean(raw_input(\"" + msg + " \"), " + name + ")"
239
240 return line
241
242
243class ListInputLine(InputLine):
244 """
245 Base class for List-based Input lines. e.g. Choicelist, Checklist.
246 """
247 __metaclass__ = ABCMeta
248
249 def __init__(self, props, tag, lineno):
250 InputLine.__init__(self, props, tag, lineno)
251 self.choices = []
252
253 def gen_choicepair_list(self):
254 """Generate a list of 2-item val:desc lists from self.choices."""
255 if not self.choices:
256 return None
257
258 choicepair_list = list()
259
260 for choice in self.choices:
261 choicepair = []
262 choicepair.append(choice.val)
263 choicepair.append(choice.desc)
264 choicepair_list.append(choicepair)
265
266 return choicepair_list
267
268 def gen_degenerate_choicepair_list(self, choices):
269 """Generate a list of 2-item val:desc with val=desc from passed-in choices."""
270 choicepair_list = list()
271
272 for choice in choices:
273 choicepair = []
274 choicepair.append(choice)
275 choicepair.append(choice)
276 choicepair_list.append(choicepair)
277
278 return choicepair_list
279
280 def exec_listgen_fn(self, context = None):
281 """
282 Execute the list-generating function contained as a string in
283 the "gen" property.
284 """
285 retval = None
286 try:
287 fname = self.props["gen"]
288 modsplit = fname.split('.')
289 mod_fn = modsplit.pop()
290 mod = '.'.join(modsplit)
291
292 __import__(mod)
293 # python 2.7 has a better way to do this using importlib.import_module
294 m = sys.modules[mod]
295
296 fn = getattr(m, mod_fn)
297 if not fn:
298 self.parse_error("couldn't load function specified for 'gen' property ",
299 self.lineno, self.line)
300 retval = fn(context)
301 if not retval:
302 self.parse_error("function specified for 'gen' property returned nothing ",
303 self.lineno, self.line)
304 except KeyError:
305 pass
306
307 return retval
308
309 def gen_choices_str(self, choicepairs):
310 """
311 Generate a numbered list of choices from a list of choicepairs
312 for display to the user.
313 """
314 choices_str = ""
315
316 for i, choicepair in enumerate(choicepairs):
317 choices_str += "\t" + str(i + 1) + ") " + choicepair[1] + "\n"
318
319 return choices_str
320
321 def gen_choices_val_str(self, choicepairs):
322 """
323 Generate an array of choice values corresponding to the
324 numbered list generated by gen_choices_str().
325 """
326 choices_val_list = "["
327
328 for i, choicepair in enumerate(choicepairs):
329 choices_val_list += "\"" + choicepair[0] + "\","
330 choices_val_list += "]"
331
332 return choices_val_list
333
334 def gen_choices_val_list(self, choicepairs):
335 """
336 Generate an array of choice values corresponding to the
337 numbered list generated by gen_choices_str().
338 """
339 choices_val_list = []
340
341 for i, choicepair in enumerate(choicepairs):
342 choices_val_list.append(choicepair[0])
343
344 return choices_val_list
345
346 def gen_choices_list(self, context = None, checklist = False):
347 """
348 Generate an array of choice values corresponding to the
349 numbered list generated by gen_choices_str().
350 """
351 choices = self.exec_listgen_fn(context)
352 if choices:
353 if len(choices) == 0:
354 self.parse_error("No entries available for input list",
355 self.lineno, self.line)
356 choicepairs = self.gen_degenerate_choicepair_list(choices)
357 else:
358 if len(self.choices) == 0:
359 self.parse_error("No entries available for input list",
360 self.lineno, self.line)
361 choicepairs = self.gen_choicepair_list()
362
363 return choicepairs
364
365 def gen_choices(self, context = None, checklist = False):
366 """
367 Generate an array of choice values corresponding to the
368 numbered list generated by gen_choices_str(), display it to
369 the user, and process the result.
370 """
371 msg = self.props["msg"]
372 if not msg:
373 self.parse_error("No input 'msg' property found",
374 self.lineno, self.line)
375
376 try:
377 default_choice = self.props["default"]
378 except KeyError:
379 default_choice = ""
380
381 msg += " [default: " + default_choice + "]"
382
383 choicepairs = self.gen_choices_list(context, checklist)
384
385 choices_str = self.gen_choices_str(choicepairs)
386 choices_val_list = self.gen_choices_val_list(choicepairs)
387 if checklist:
388 choiceval = default(find_choicevals(raw_input(msg + "\n" + choices_str), choices_val_list), default_choice)
389 else:
390 choiceval = default(find_choiceval(raw_input(msg + "\n" + choices_str), choices_val_list), default_choice)
391
392 return choiceval
393
394
395def find_choiceval(choice_str, choice_list):
396 """
397 Take number as string and return val string from choice_list,
398 empty string if oob. choice_list is a simple python list.
399 """
400 choice_val = ""
401
402 try:
403 choice_idx = int(choice_str)
404 if choice_idx <= len(choice_list):
405 choice_idx -= 1
406 choice_val = choice_list[choice_idx]
407 except ValueError:
408 pass
409
410 return choice_val
411
412
413def find_choicevals(choice_str, choice_list):
414 """
415 Take numbers as space-separated string and return vals list from
416 choice_list, empty list if oob. choice_list is a simple python
417 list.
418 """
419 choice_vals = []
420
421 choices = choice_str.split()
422 for choice in choices:
423 choice_vals.append(find_choiceval(choice, choice_list))
424
425 return choice_vals
426
427
428def default(input_str, name):
429 """
430 Return default if no input_str, otherwise stripped input_str.
431 """
432 if not input_str:
433 return name
434
435 return input_str.strip()
436
437
438def boolean(input_str, name):
439 """
440 Return lowercase version of first char in string, or value in name.
441 """
442 if not input_str:
443 return name
444
445 str = input_str.lower().strip()
446 if str and str[0] == "y" or str[0] == "n":
447 return str[0]
448 else:
449 return name
450
451
452deferred_choices = {}
453
454def gen_choices_defer(input_line, context, checklist = False):
455 """
456 Save the context hashed the name of the input item, which will be
457 passed to the gen function later.
458 """
459 name = input_line.props["name"]
460
461 try:
462 nameappend = input_line.props["nameappend"]
463 except KeyError:
464 nameappend = ""
465
466 filename = input_line.props["filename"]
467
468 closetag_start = filename.find(CLOSE_TAG)
469
470 if closetag_start != -1:
471 filename = filename[closetag_start + len(CLOSE_TAG):]
472
473 filename = filename.strip()
474 filename = os.path.splitext(filename)[0]
475
476 captured_context = capture_context(context)
477 context["filename"] = filename
478 captured_context["filename"] = filename
479 context["nameappend"] = nameappend
480 captured_context["nameappend"] = nameappend
481
482 deferred_choice = (input_line, captured_context, checklist)
483 key = name + "_" + filename + "_" + nameappend
484 deferred_choices[key] = deferred_choice
485
486
487def invoke_deferred_choices(name):
488 """
489 Invoke the choice generation function using the context hashed by
490 'name'.
491 """
492 deferred_choice = deferred_choices[name]
493 input_line = deferred_choice[0]
494 context = deferred_choice[1]
495 checklist = deferred_choice[2]
496
497 context["name"] = name
498
499 choices = input_line.gen_choices(context, checklist)
500
501 return choices
502
503
504class ChoicelistInputLine(ListInputLine):
505 """
506 Base class for choicelist Input lines.
507 props:
508 name: example - "xserver_choice"
509 msg: example - "Please select an xserver for this machine"
510 result:
511 Sets the value of the variable specified by 'name' to whichever Choice was chosen
512 example - xserver_choice = "xserver_vesa"
513 """
514 def __init__(self, props, tag, lineno):
515 ListInputLine.__init__(self, props, tag, lineno)
516
517 def gen(self, context = None):
518 InputLine.gen(self, context)
519
520 gen_choices_defer(self, context)
521 name = self.props["name"]
522 nameappend = context["nameappend"]
523 filename = context["filename"]
524
525 try:
526 default_choice = self.props["default"]
527 except KeyError:
528 default_choice = ""
529
530 line = name + " = default(invoke_deferred_choices(\"" + name + "_" + filename + "_" + nameappend + "\"), \"" + default_choice + "\")"
531
532 return line
533
534
535class ListValInputLine(InputLine):
536 """
537 Abstract base class for choice and checkbox Input lines.
538 """
539 def __init__(self, props, tag, lineno):
540 InputLine.__init__(self, props, tag, lineno)
541
542 try:
543 self.val = self.props["val"]
544 except KeyError:
545 self.parse_error("No input 'val' property found", self.lineno, self.line)
546
547 try:
548 self.desc = self.props["msg"]
549 except KeyError:
550 self.parse_error("No input 'msg' property found", self.lineno, self.line)
551
552
553class ChoiceInputLine(ListValInputLine):
554 """
555 Base class for choicelist item Input lines.
556 """
557 def __init__(self, props, tag, lineno):
558 ListValInputLine.__init__(self, props, tag, lineno)
559
560 def gen(self, context = None):
561 return None
562
563
564class ChecklistInputLine(ListInputLine):
565 """
566 Base class for checklist Input lines.
567 """
568 def __init__(self, props, tag, lineno):
569 ListInputLine.__init__(self, props, tag, lineno)
570
571 def gen(self, context = None):
572 InputLine.gen(self, context)
573
574 gen_choices_defer(self, context, True)
575 name = self.props["name"]
576 nameappend = context["nameappend"]
577 filename = context["filename"]
578
579 try:
580 default_choice = self.props["default"]
581 except KeyError:
582 default_choice = ""
583
584 line = name + " = default(invoke_deferred_choices(\"" + name + "_" + filename + "_" + nameappend + "\"), \"" + default_choice + "\")"
585
586 return line
587
588
589class CheckInputLine(ListValInputLine):
590 """
591 Base class for checklist item Input lines.
592 """
593 def __init__(self, props, tag, lineno):
594 ListValInputLine.__init__(self, props, tag, lineno)
595
596 def gen(self, context = None):
597 return None
598
599
600class SubstrateBase(object):
601 """
602 Base class for both expanded and unexpanded file and dir container
603 objects.
604 """
605 def __init__(self, filename, filebase, out_filebase):
606 self.filename = filename
607 self.filebase = filebase
608 self.out_filebase = out_filebase
609 self.raw_lines = []
610 self.expanded_lines = []
611 self.prev_choicelist = None
612
613 def parse_error(self, msg, lineno, line):
614 raise SyntaxError("%s: [%s: %d]: %s" % (msg, self.filename, lineno, line))
615
616 def expand_input_tag(self, tag, lineno):
617 """
618 Input tags consist of the word 'input' at the beginning,
619 followed by name:value property pairs which are converted into
620 a dictionary.
621 """
622 propstr = tag[len(INPUT_TAG):]
623
624 props = dict(prop.split(":", 1) for prop in shlex.split(propstr))
625 props["filename"] = self.filename
626
627 input_type = props[INPUT_TYPE_PROPERTY]
628 if not props[INPUT_TYPE_PROPERTY]:
629 self.parse_error("No input 'type' property found", lineno, tag)
630
631 if input_type == "boolean":
632 return BooleanInputLine(props, tag, lineno)
633 if input_type == "edit":
634 return EditBoxInputLine(props, tag, lineno)
635 elif input_type == "choicelist":
636 self.prev_choicelist = ChoicelistInputLine(props, tag, lineno)
637 return self.prev_choicelist
638 elif input_type == "choice":
639 if not self.prev_choicelist:
640 self.parse_error("Found 'choice' input tag but no previous choicelist",
641 lineno, tag)
642 choice = ChoiceInputLine(props, tag, lineno)
643 self.prev_choicelist.choices.append(choice)
644 return choice
645 elif input_type == "checklist":
646 return ChecklistInputLine(props, tag, lineno)
647 elif input_type == "check":
648 return CheckInputLine(props, tag, lineno)
649
650 def expand_assignment_tag(self, start, line, lineno):
651 """
652 Expand all tags in a line.
653 """
654 expanded_line = AssignmentLine(line.strip())
655
656 while start != -1:
657 end = line.find(CLOSE_TAG, start)
658 if end == -1:
659 self.parse_error("No close tag found for assignment tag", lineno, line)
660 else:
661 name = line[start + len(ASSIGN_TAG):end].strip()
662 expanded_line.add_assignment(start, end + len(CLOSE_TAG), name)
663 start = line.find(ASSIGN_TAG, end)
664
665 return expanded_line
666
667 def expand_tag(self, line, lineno):
668 """
669 Returns a processed tag line, or None if there was no tag
670
671 The rules for tags are very simple:
672 - No nested tags
673 - Tags start with {{ and end with }}
674 - An assign tag, {{=, can appear anywhere and will
675 be replaced with what the assignment evaluates to
676 - Any other tag occupies the whole line it is on
677 - if there's anything else on the tag line, it's an error
678 - if it starts with 'input', it's an input tag and
679 will only be used for prompting and setting variables
680 - anything else is straight Python
681 - tags are in effect only until the next blank line or tag or 'pass' tag
682 - we don't have indentation in tags, but we need some way to end a block
683 forcefully without blank lines or other tags - that's the 'pass' tag
684 - todo: implement pass tag
685 - directories and filenames can have tags as well, but only assignment
686 and 'if' code lines
687 - directories and filenames are the only case where normal tags can
688 coexist with normal text on the same 'line'
689 """
690 start = line.find(ASSIGN_TAG)
691 if start != -1:
692 return self.expand_assignment_tag(start, line, lineno)
693
694 start = line.find(OPEN_TAG)
695 if start == -1:
696 return None
697
698 end = line.find(CLOSE_TAG, 0)
699 if end == -1:
700 self.parse_error("No close tag found for open tag", lineno, line)
701
702 tag = line[start + len(OPEN_TAG):end].strip()
703
704 if not tag.lstrip().startswith(INPUT_TAG):
705 return CodeLine(tag)
706
707 return self.expand_input_tag(tag, lineno)
708
709 def expand_file_or_dir_name(self):
710 """
711 Expand file or dir names into codeline. Dirnames and
712 filenames can only have assignments or if statements. First
713 translate if statements into CodeLine + (dirname or filename
714 creation).
715 """
716 lineno = 0
717
718 line = self.filename[len(self.filebase):]
719 if line.startswith("/"):
720 line = line[1:]
721 opentag_start = -1
722
723 start = line.find(OPEN_TAG)
724 while start != -1:
725 if not line[start:].startswith(ASSIGN_TAG):
726 opentag_start = start
727 break
728 start += len(ASSIGN_TAG)
729 start = line.find(OPEN_TAG, start)
730
731 if opentag_start != -1:
732 end = line.find(CLOSE_TAG, opentag_start)
733 if end == -1:
734 self.parse_error("No close tag found for open tag", lineno, line)
735 # we have a {{ tag i.e. code
736 tag = line[opentag_start + len(OPEN_TAG):end].strip()
737
738 if not tag.lstrip().startswith(IF_TAG):
739 self.parse_error("Only 'if' tags are allowed in file or directory names",
740 lineno, line)
741 self.expanded_lines.append(CodeLine(tag))
742
743 # everything after }} is the actual filename (possibly with assignments)
744 # everything before is the pathname
745 line = line[:opentag_start] + line[end + len(CLOSE_TAG):].strip()
746
747 assign_start = line.find(ASSIGN_TAG)
748 if assign_start != -1:
749 assignment_tag = self.expand_assignment_tag(assign_start, line, lineno)
750 if isinstance(self, SubstrateFile):
751 assignment_tag.is_filename = True
752 assignment_tag.out_filebase = self.out_filebase
753 elif isinstance(self, SubstrateDir):
754 assignment_tag.is_dirname = True
755 assignment_tag.out_filebase = self.out_filebase
756 self.expanded_lines.append(assignment_tag)
757 return
758
759 normal_line = NormalLine(line)
760 if isinstance(self, SubstrateFile):
761 normal_line.is_filename = True
762 normal_line.out_filebase = self.out_filebase
763 elif isinstance(self, SubstrateDir):
764 normal_line.is_dirname = True
765 normal_line.out_filebase = self.out_filebase
766 self.expanded_lines.append(normal_line)
767
768 def expand(self):
769 """
770 Expand the file or dir name first, eventually this ends up
771 creating the file or dir.
772 """
773 self.expand_file_or_dir_name()
774
775
776class SubstrateFile(SubstrateBase):
777 """
778 Container for both expanded and unexpanded substrate files.
779 """
780 def __init__(self, filename, filebase, out_filebase):
781 SubstrateBase.__init__(self, filename, filebase, out_filebase)
782
783 def read(self):
784 if self.raw_lines:
785 return
786 f = open(self.filename)
787 self.raw_lines = f.readlines()
788
789 def expand(self):
790 """Expand the contents of all template tags in the file."""
791 SubstrateBase.expand(self)
792 self.read()
793
794 for lineno, line in enumerate(self.raw_lines):
795 expanded_line = self.expand_tag(line, lineno + 1) # humans not 0-based
796 if not expanded_line:
797 expanded_line = NormalLine(line.rstrip())
798 self.expanded_lines.append(expanded_line)
799
800 def gen(self, context = None):
801 """Generate the code that generates the BSP."""
802 base_indent = 0
803
804 indent = new_indent = base_indent
805
806 for line in self.expanded_lines:
807 genline = line.gen(context)
808 if not genline:
809 continue
810 if isinstance(line, InputLine):
811 line.generated_line = genline
812 continue
813 if genline.startswith(OPEN_START):
814 if indent == 1:
815 base_indent = 1
816 if indent:
817 if genline == BLANKLINE_STR or (not genline.startswith(NORMAL_START)
818 and not genline.startswith(OPEN_START)):
819 indent = new_indent = base_indent
820 if genline.endswith(":"):
821 new_indent = base_indent + 1
822 line.generated_line = (indent * INDENT_STR) + genline
823 indent = new_indent
824
825
826class SubstrateDir(SubstrateBase):
827 """
828 Container for both expanded and unexpanded substrate dirs.
829 """
830 def __init__(self, filename, filebase, out_filebase):
831 SubstrateBase.__init__(self, filename, filebase, out_filebase)
832
833 def expand(self):
834 SubstrateBase.expand(self)
835
836 def gen(self, context = None):
837 """Generate the code that generates the BSP."""
838 indent = new_indent = 0
839 for line in self.expanded_lines:
840 genline = line.gen(context)
841 if not genline:
842 continue
843 if genline.endswith(":"):
844 new_indent = 1
845 else:
846 new_indent = 0
847 line.generated_line = (indent * INDENT_STR) + genline
848 indent = new_indent
849
850
851def expand_target(target, all_files, out_filebase):
852 """
853 Expand the contents of all template tags in the target. This
854 means removing tags and categorizing or creating lines so that
855 future passes can process and present input lines and generate the
856 corresponding lines of the Python program that will be exec'ed to
857 actually produce the final BSP. 'all_files' includes directories.
858 """
859 for root, dirs, files in os.walk(target):
860 for file in files:
861 if file.endswith("~") or file.endswith("#"):
862 continue
863 f = os.path.join(root, file)
864 sfile = SubstrateFile(f, target, out_filebase)
865 sfile.expand()
866 all_files.append(sfile)
867
868 for dir in dirs:
869 d = os.path.join(root, dir)
870 sdir = SubstrateDir(d, target, out_filebase)
871 sdir.expand()
872 all_files.append(sdir)
873
874
875def gen_program_machine_lines(machine, program_lines):
876 """
877 Use the input values we got from the command line.
878 """
879 line = "machine = \"" + machine + "\""
880
881 program_lines.append(line)
882
883
884def sort_inputlines(input_lines):
885 """Sort input lines according to priority (position)."""
886 input_lines.sort(key = lambda l: l.prio)
887
888
889def find_parent_dependency(lines, depends_on):
890 for i, line in lines:
891 if isinstance(line, CodeLine):
892 continue
893 if line.props["name"] == depends_on:
894 return i
895
896 return -1
897
898
899def process_inputline_dependencies(input_lines, all_inputlines):
900 """If any input lines depend on others, put the others first."""
901 for line in input_lines:
902 if isinstance(line, InputLineGroup):
903 group_inputlines = []
904 process_inputline_dependencies(line.group, group_inputlines)
905 line.group = group_inputlines
906 all_inputlines.append(line)
907 continue
908
909 if isinstance(line, CodeLine) or isinstance(line, NormalLine):
910 all_inputlines.append(line)
911 continue
912
913 try:
914 depends_on = line.props["depends-on"]
915 depends_codeline = "if " + line.props["depends-on"] + " == \"" + line.props["depends-on-val"] + "\":"
916 all_inputlines.append(CodeLine(depends_codeline))
917 all_inputlines.append(line)
918 except KeyError:
919 all_inputlines.append(line)
920
921
922def conditional_filename(filename):
923 """
924 Check if the filename itself contains a conditional statement. If
925 so, return a codeline for it.
926 """
927 opentag_start = filename.find(OPEN_TAG)
928
929 if opentag_start != -1:
930 if filename[opentag_start:].startswith(ASSIGN_TAG):
931 return None
932 end = filename.find(CLOSE_TAG, opentag_start)
933 if end == -1:
934 print "No close tag found for open tag in filename %s" % filename
935 sys.exit(1)
936
937 # we have a {{ tag i.e. code
938 tag = filename[opentag_start + len(OPEN_TAG):end].strip()
939 if not tag.lstrip().startswith(IF_TAG):
940 print "Only 'if' tags are allowed in file or directory names, filename: %s" % filename
941 sys.exit(1)
942
943 return CodeLine(tag)
944
945 return None
946
947
948class InputLineGroup(InputLine):
949 """
950 InputLine that does nothing but group other input lines
951 corresponding to all the input lines in a SubstrateFile so they
952 can be generated as a group. prio is the only property used.
953 """
954 def __init__(self, codeline):
955 InputLine.__init__(self, {}, "", 0)
956 self.group = []
957 self.prio = sys.maxint
958 self.group.append(codeline)
959
960 def append(self, line):
961 self.group.append(line)
962 if line.prio < self.prio:
963 self.prio = line.prio
964
965 def len(self):
966 return len(self.group)
967
968
969def gather_inputlines(files):
970 """
971 Gather all the InputLines - we want to generate them first.
972 """
973 all_inputlines = []
974 input_lines = []
975
976 for file in files:
977 if isinstance(file, SubstrateFile):
978 group = None
979 basename = os.path.basename(file.filename)
980
981 codeline = conditional_filename(basename)
982 if codeline:
983 group = InputLineGroup(codeline)
984
985 have_condition = False
986 condition_to_write = None
987 for line in file.expanded_lines:
988 if isinstance(line, CodeLine):
989 have_condition = True
990 condition_to_write = line
991 continue
992 if isinstance(line, InputLine):
993 if group:
994 if condition_to_write:
995 condition_to_write.prio = line.prio
996 condition_to_write.discard = True
997 group.append(condition_to_write)
998 condition_to_write = None
999 group.append(line)
1000 else:
1001 if condition_to_write:
1002 condition_to_write.prio = line.prio
1003 condition_to_write.discard = True
1004 input_lines.append(condition_to_write)
1005 condition_to_write = None
1006 input_lines.append(line)
1007 else:
1008 if condition_to_write:
1009 condition_to_write = None
1010 if have_condition:
1011 if not line.line.strip():
1012 line.discard = True
1013 input_lines.append(line)
1014 have_condition = False
1015
1016 if group and group.len() > 1:
1017 input_lines.append(group)
1018
1019 sort_inputlines(input_lines)
1020 process_inputline_dependencies(input_lines, all_inputlines)
1021
1022 return all_inputlines
1023
1024
1025def run_program_lines(linelist, codedump):
1026 """
1027 For a single file, print all the python code into a buf and execute it.
1028 """
1029 buf = "\n".join(linelist)
1030
1031 if codedump:
1032 of = open("bspgen.out", "w")
1033 of.write(buf)
1034 of.close()
1035 exec buf
1036
1037
1038def gen_target(files, context = None):
1039 """
1040 Generate the python code for each file.
1041 """
1042 for file in files:
1043 file.gen(context)
1044
1045
1046def gen_program_header_lines(program_lines):
1047 """
1048 Generate any imports we need.
1049 """
1050 pass
1051
1052
1053def gen_supplied_property_vals(properties, program_lines):
1054 """
1055 Generate user-specified entries for input values instead of
1056 generating input prompts.
1057 """
1058 for name, val in properties.iteritems():
1059 program_line = name + " = \"" + val + "\""
1060 program_lines.append(program_line)
1061
1062
1063def gen_initial_property_vals(input_lines, program_lines):
1064 """
1065 Generate null or default entries for input values, so we don't
1066 have undefined variables.
1067 """
1068 for line in input_lines:
1069 if isinstance(line, InputLineGroup):
1070 gen_initial_property_vals(line.group, program_lines)
1071 continue
1072
1073 if isinstance(line, InputLine):
1074 try:
1075 name = line.props["name"]
1076 try:
1077 default_val = "\"" + line.props["default"] + "\""
1078 except:
1079 default_val = "\"\""
1080 program_line = name + " = " + default_val
1081 program_lines.append(program_line)
1082 except KeyError:
1083 pass
1084
1085
1086def gen_program_input_lines(input_lines, program_lines, context, in_group = False):
1087 """
1088 Generate only the input lines used for prompting the user. For
1089 that, we only have input lines and CodeLines that affect the next
1090 input line.
1091 """
1092 indent = new_indent = 0
1093
1094 for line in input_lines:
1095 if isinstance(line, InputLineGroup):
1096 gen_program_input_lines(line.group, program_lines, context, True)
1097 continue
1098 if not line.line.strip():
1099 continue
1100
1101 genline = line.gen(context)
1102 if not genline:
1103 continue
1104 if genline.endswith(":"):
1105 new_indent += 1
1106 else:
1107 if indent > 1 or (not in_group and indent):
1108 new_indent -= 1
1109
1110 line.generated_line = (indent * INDENT_STR) + genline
1111 program_lines.append(line.generated_line)
1112
1113 indent = new_indent
1114
1115
1116def gen_program_lines(target_files, program_lines):
1117 """
1118 Generate the program lines that make up the BSP generation
1119 program. This appends the generated lines of all target_files to
1120 program_lines, and skips input lines, which are dealt with
1121 separately, or omitted.
1122 """
1123 for file in target_files:
1124 if file.filename.endswith("noinstall"):
1125 continue
1126
1127 for line in file.expanded_lines:
1128 if isinstance(line, InputLine):
1129 continue
1130 if line.discard:
1131 continue
1132
1133 program_lines.append(line.generated_line)
1134
1135
1136def create_context(machine, arch, scripts_path):
1137 """
1138 Create a context object for use in deferred function invocation.
1139 """
1140 context = {}
1141
1142 context["machine"] = machine
1143 context["arch"] = arch
1144 context["scripts_path"] = scripts_path
1145
1146 return context
1147
1148
1149def capture_context(context):
1150 """
1151 Create a context object for use in deferred function invocation.
1152 """
1153 captured_context = {}
1154
1155 captured_context["machine"] = context["machine"]
1156 captured_context["arch"] = context["arch"]
1157 captured_context["scripts_path"] = context["scripts_path"]
1158
1159 return captured_context
1160
1161
1162def expand_targets(context, bsp_output_dir):
1163 """
1164 Expand all the tags in both the common and machine-specific
1165 'targets'.
1166 """
1167 target_files = []
1168
1169 machine = context["machine"]
1170 arch = context["arch"]
1171 scripts_path = context["scripts_path"]
1172
1173 lib_path = scripts_path + '/lib'
1174 bsp_path = lib_path + '/bsp'
1175 arch_path = bsp_path + '/substrate/target/arch'
1176
1177 common = os.path.join(arch_path, "common")
1178 expand_target(common, target_files, bsp_output_dir)
1179
1180 arches = os.listdir(arch_path)
1181 if arch not in arches or arch == "common":
1182 print "Invalid karch, exiting\n"
1183 sys.exit(1)
1184
1185 target = os.path.join(arch_path, arch)
1186 expand_target(target, target_files, bsp_output_dir)
1187
1188 gen_target(target_files, context)
1189
1190 return target_files
1191
1192
1193def yocto_bsp_create(machine, arch, scripts_path, bsp_output_dir, codedump, properties_file):
1194 """
1195 Create bsp
1196
1197 machine - user-defined machine name
1198 arch - the arch the bsp will be based on, must be one in
1199 scripts/lib/bsp/substrate/target/arch
1200 scripts_path - absolute path to yocto /scripts dir
1201 bsp_output_dir - dirname to create for BSP
1202 codedump - dump generated code to bspgen.out
1203 properties_file - use values from here if nonempty i.e no prompting
1204 """
1205 if os.path.exists(bsp_output_dir):
1206 print "\nBSP output dir already exists, exiting. (%s)" % bsp_output_dir
1207 sys.exit(1)
1208
1209 properties = None
1210
1211 if properties_file:
1212 try:
1213 infile = open(properties_file, "r")
1214 except IOError:
1215 print "Couldn't open properties file %s for reading, exiting" % properties_file
1216 sys.exit(1)
1217
1218 properties = json.load(infile)
1219
1220 os.mkdir(bsp_output_dir)
1221
1222 context = create_context(machine, arch, scripts_path)
1223 target_files = expand_targets(context, bsp_output_dir)
1224
1225 if not properties:
1226 input_lines = gather_inputlines(target_files)
1227
1228 program_lines = []
1229
1230 gen_program_header_lines(program_lines)
1231
1232 if properties:
1233 gen_supplied_property_vals(properties, program_lines)
1234 else:
1235 gen_initial_property_vals(input_lines, program_lines)
1236
1237 gen_program_machine_lines(machine, program_lines)
1238
1239 if not properties:
1240 gen_program_input_lines(input_lines, program_lines, context)
1241
1242 gen_program_lines(target_files, program_lines)
1243
1244 run_program_lines(program_lines, codedump)
1245
1246 print "New %s BSP created in %s" % (arch, bsp_output_dir)
1247
1248
1249def print_dict(items, indent = 0):
1250 """
1251 Print the values in a possibly nested dictionary.
1252 """
1253 for key, val in items.iteritems():
1254 print " "*indent + "\"%s\" :" % key,
1255 if type(val) == dict:
1256 print "{"
1257 print_dict(val, indent + 1)
1258 print " "*indent + "}"
1259 else:
1260 print "%s" % val
1261
1262
1263def get_properties(input_lines):
1264 """
1265 Get the complete set of properties for all the input items in the
1266 BSP, as a possibly nested dictionary.
1267 """
1268 properties = {}
1269
1270 for line in input_lines:
1271 if isinstance(line, InputLineGroup):
1272 statement = line.group[0].line
1273 group_properties = get_properties(line.group)
1274 properties[statement] = group_properties
1275 continue
1276
1277 if not isinstance(line, InputLine):
1278 continue
1279
1280 if isinstance(line, ChoiceInputLine):
1281 continue
1282
1283 props = line.props
1284 item = {}
1285 name = props["name"]
1286 for key, val in props.items():
1287 if not key == "name":
1288 item[key] = val
1289 properties[name] = item
1290
1291 return properties
1292
1293
1294def yocto_bsp_list_properties(arch, scripts_path, properties_file):
1295 """
1296 List the complete set of properties for all the input items in the
1297 BSP. If properties_file is non-null, write the complete set of
1298 properties as a nested JSON object corresponding to a possibly
1299 nested dictionary.
1300 """
1301 context = create_context("unused", arch, scripts_path)
1302 target_files = expand_targets(context, "unused")
1303
1304 input_lines = gather_inputlines(target_files)
1305
1306 properties = get_properties(input_lines)
1307 if properties_file:
1308 try:
1309 of = open(properties_file, "w")
1310 except IOError:
1311 print "Couldn't open properties file %s for writing, exiting" % properties_file
1312 sys.exit(1)
1313
1314 json.dump(properties, of)
1315
1316 print_dict(properties)
1317
1318
1319def find_input_line(name, input_lines):
1320 """
1321 Find the input line with the specified name.
1322 """
1323 for line in input_lines:
1324 if isinstance(line, InputLineGroup):
1325 l = find_input_line(name, line.group)
1326 if l:
1327 return l
1328
1329 if isinstance(line, InputLine):
1330 try:
1331 if line.props["name"] == name:
1332 return line
1333 except KeyError:
1334 pass
1335
1336 return None
1337
1338
1339def print_values(type, values_list):
1340 """
1341 Print the values in the given list of values.
1342 """
1343 if type == "choicelist":
1344 for value in values_list:
1345 print "[\"%s\", \"%s\"]" % (value[0], value[1])
1346 elif type == "boolean":
1347 for value in values_list:
1348 print "[\"%s\", \"%s\"]" % (value[0], value[1])
1349
1350
1351def yocto_bsp_list_property_values(arch, property, scripts_path, properties_file):
1352 """
1353 List the possible values for a given input property. If
1354 properties_file is non-null, write the complete set of properties
1355 as a JSON object corresponding to an array of possible values.
1356 """
1357 context = create_context("unused", arch, scripts_path)
1358 context["name"] = property
1359
1360 target_files = expand_targets(context, "unused")
1361
1362 input_lines = gather_inputlines(target_files)
1363
1364 properties = get_properties(input_lines)
1365
1366 input_line = find_input_line(property, input_lines)
1367 if not input_line:
1368 print "Couldn't find values for property %s" % property
1369 return
1370
1371 values_list = []
1372
1373 type = input_line.props["type"]
1374 if type == "boolean":
1375 values_list.append(["y", "n"])
1376 elif type == "choicelist" or type == "checklist":
1377 try:
1378 gen_fn = input_line.props["gen"]
1379 values_list = input_line.gen_choices_list(context, False)
1380 except KeyError:
1381 for choice in input_line.choices:
1382 choicepair = []
1383 choicepair.append(choice.val)
1384 choicepair.append(choice.desc)
1385 values_list.append(choicepair)
1386
1387 if properties_file:
1388 try:
1389 of = open(properties_file, "w")
1390 except IOError:
1391 print "Couldn't open properties file %s for writing, exiting" % properties_file
1392 sys.exit(1)
1393
1394 json.dump(values_list, of)
1395
1396 print_values(type, values_list)
1397
1398
1399def yocto_bsp_list(args, scripts_path, properties_file):
1400 """
1401 Print available architectures, or the complete list of properties
1402 defined by the BSP, or the possible values for a particular BSP
1403 property.
1404 """
1405 if len(args) < 1:
1406 return False
1407
1408 if args[0] == "karch":
1409 lib_path = scripts_path + '/lib'
1410 bsp_path = lib_path + '/bsp'
1411 arch_path = bsp_path + '/substrate/target/arch'
1412 print "Architectures available:"
1413 for arch in os.listdir(arch_path):
1414 if arch == "common":
1415 continue
1416 print " %s" % arch
1417 return True
1418 else:
1419 arch = args[0]
1420
1421 if len(args) < 2 or len(args) > 3:
1422 return False
1423
1424 if len(args) == 2:
1425 if args[1] == "properties":
1426 yocto_bsp_list_properties(arch, scripts_path, properties_file)
1427 else:
1428 return False
1429
1430 if len(args) == 3:
1431 if args[1] == "property":
1432 yocto_bsp_list_property_values(arch, args[2], scripts_path, properties_file)
1433 else:
1434 return False
1435
1436 return True