blob: 66e2162ea866f171d7d55d760e97c3bd98253a22 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001# 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
40import subprocess
41import shutil
42
43class Line():
44 """
45 Generic (abstract) container representing a line that will appear
46 in the BSP-generating program.
47 """
48 __metaclass__ = ABCMeta
49
50 def __init__(self, line):
51 self.line = line
52 self.generated_line = ""
53 self.prio = sys.maxint
54 self.discard = False
55
56 @abstractmethod
57 def gen(self, context = None):
58 """
59 Generate the final executable line that will appear in the
60 BSP-generation program.
61 """
62 pass
63
64 def escape(self, line):
65 """
66 Escape single and double quotes and backslashes until I find
67 something better (re.escape() escapes way too much).
68 """
69 return line.replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'")
70
71 def parse_error(self, msg, lineno, line):
72 raise SyntaxError("%s: %s" % (msg, line))
73
74
75class NormalLine(Line):
76 """
77 Container for normal (non-tag) lines.
78 """
79 def __init__(self, line):
80 Line.__init__(self, line)
81 self.is_filename = False
82 self.is_dirname = False
83 self.out_filebase = None
84
85 def gen(self, context = None):
86 if self.is_filename:
87 line = "current_file = \"" + os.path.join(self.out_filebase, self.escape(self.line)) + "\"; of = open(current_file, \"w\")"
88 elif self.is_dirname:
89 dirname = os.path.join(self.out_filebase, self.escape(self.line))
90 line = "if not os.path.exists(\"" + dirname + "\"): os.mkdir(\"" + dirname + "\")"
91 else:
92 line = "of.write(\"" + self.escape(self.line) + "\\n\")"
93 return line
94
95
96class CodeLine(Line):
97 """
98 Container for Python code tag lines.
99 """
100 def __init__(self, line):
101 Line.__init__(self, line)
102
103 def gen(self, context = None):
104 return self.line
105
106
107class Assignment:
108 """
109 Representation of everything we know about {{=name }} tags.
110 Instances of these are used by Assignment lines.
111 """
112 def __init__(self, start, end, name):
113 self.start = start
114 self.end = end
115 self.name = name
116
117
118class AssignmentLine(NormalLine):
119 """
120 Container for normal lines containing assignment tags. Assignment
121 tags must be in ascending order of 'start' value.
122 """
123 def __init__(self, line):
124 NormalLine.__init__(self, line)
125 self.assignments = []
126
127 def add_assignment(self, start, end, name):
128 self.assignments.append(Assignment(start, end, name))
129
130 def gen(self, context = None):
131 line = self.escape(self.line)
132
133 for assignment in self.assignments:
134 replacement = "\" + " + assignment.name + " + \""
135 idx = line.find(ASSIGN_TAG)
136 line = line[:idx] + replacement + line[idx + assignment.end - assignment.start:]
137 if self.is_filename:
138 return "current_file = \"" + os.path.join(self.out_filebase, line) + "\"; of = open(current_file, \"w\")"
139 elif self.is_dirname:
140 dirname = os.path.join(self.out_filebase, line)
141 return "if not os.path.exists(\"" + dirname + "\"): os.mkdir(\"" + dirname + "\")"
142 else:
143 return "of.write(\"" + line + "\\n\")"
144
145
146class InputLine(Line):
147 """
148 Base class for Input lines.
149 """
150 def __init__(self, props, tag, lineno):
151 Line.__init__(self, tag)
152 self.props = props
153 self.lineno = lineno
154
155 try:
156 self.prio = int(props["prio"])
157 except KeyError:
158 self.prio = sys.maxint
159
160 def gen(self, context = None):
161 try:
162 depends_on = self.props["depends-on"]
163 try:
164 depends_on_val = self.props["depends-on-val"]
165 except KeyError:
166 self.parse_error("No 'depends-on-val' for 'depends-on' property",
167 self.lineno, self.line)
168 except KeyError:
169 pass
170
171
172class EditBoxInputLine(InputLine):
173 """
174 Base class for 'editbox' Input lines.
175
176 props:
177 name: example - "Load address"
178 msg: example - "Please enter the load address"
179 result:
180 Sets the value of the variable specified by 'name' to
181 whatever the user typed.
182 """
183 def __init__(self, props, tag, lineno):
184 InputLine.__init__(self, props, tag, lineno)
185
186 def gen(self, context = None):
187 InputLine.gen(self, context)
188 name = self.props["name"]
189 if not name:
190 self.parse_error("No input 'name' property found",
191 self.lineno, self.line)
192 msg = self.props["msg"]
193 if not msg:
194 self.parse_error("No input 'msg' property found",
195 self.lineno, self.line)
196
197 try:
198 default_choice = self.props["default"]
199 except KeyError:
200 default_choice = ""
201
202 msg += " [default: " + default_choice + "]"
203
204 line = name + " = default(raw_input(\"" + msg + " \"), " + name + ")"
205
206 return line
207
208
209class GitRepoEditBoxInputLine(EditBoxInputLine):
210 """
211 Base class for 'editbox' Input lines for user input of remote git
212 repos. This class verifies the existence and connectivity of the
213 specified git repo.
214
215 props:
216 name: example - "Load address"
217 msg: example - "Please enter the load address"
218 result:
219 Sets the value of the variable specified by 'name' to
220 whatever the user typed.
221 """
222 def __init__(self, props, tag, lineno):
223 EditBoxInputLine.__init__(self, props, tag, lineno)
224
225 def gen(self, context = None):
226 EditBoxInputLine.gen(self, context)
227 name = self.props["name"]
228 if not name:
229 self.parse_error("No input 'name' property found",
230 self.lineno, self.line)
231 msg = self.props["msg"]
232 if not msg:
233 self.parse_error("No input 'msg' property found",
234 self.lineno, self.line)
235
236 try:
237 default_choice = self.props["default"]
238 except KeyError:
239 default_choice = ""
240
241 msg += " [default: " + default_choice + "]"
242
243 line = name + " = get_verified_git_repo(\"" + msg + "\"," + name + ")"
244
245 return line
246
247
248class FileEditBoxInputLine(EditBoxInputLine):
249 """
250 Base class for 'editbox' Input lines for user input of existing
251 files. This class verifies the existence of the specified file.
252
253 props:
254 name: example - "Load address"
255 msg: example - "Please enter the load address"
256 result:
257 Sets the value of the variable specified by 'name' to
258 whatever the user typed.
259 """
260 def __init__(self, props, tag, lineno):
261 EditBoxInputLine.__init__(self, props, tag, lineno)
262
263 def gen(self, context = None):
264 EditBoxInputLine.gen(self, context)
265 name = self.props["name"]
266 if not name:
267 self.parse_error("No input 'name' property found",
268 self.lineno, self.line)
269 msg = self.props["msg"]
270 if not msg:
271 self.parse_error("No input 'msg' property found",
272 self.lineno, self.line)
273
274 try:
275 default_choice = self.props["default"]
276 except KeyError:
277 default_choice = ""
278
279 msg += " [default: " + default_choice + "]"
280
281 line = name + " = get_verified_file(\"" + msg + "\"," + name + ", True)"
282
283 return line
284
285
286class BooleanInputLine(InputLine):
287 """
288 Base class for boolean Input lines.
289 props:
290 name: example - "keyboard"
291 msg: example - "Got keyboard?"
292 result:
293 Sets the value of the variable specified by 'name' to "yes" or "no"
294 example - keyboard = "yes"
295 """
296 def __init__(self, props, tag, lineno):
297 InputLine.__init__(self, props, tag, lineno)
298
299 def gen(self, context = None):
300 InputLine.gen(self, context)
301 name = self.props["name"]
302 if not name:
303 self.parse_error("No input 'name' property found",
304 self.lineno, self.line)
305 msg = self.props["msg"]
306 if not msg:
307 self.parse_error("No input 'msg' property found",
308 self.lineno, self.line)
309
310 try:
311 default_choice = self.props["default"]
312 except KeyError:
313 default_choice = ""
314
315 msg += " [default: " + default_choice + "]"
316
317 line = name + " = boolean(raw_input(\"" + msg + " \"), " + name + ")"
318
319 return line
320
321
322class ListInputLine(InputLine):
323 """
324 Base class for List-based Input lines. e.g. Choicelist, Checklist.
325 """
326 __metaclass__ = ABCMeta
327
328 def __init__(self, props, tag, lineno):
329 InputLine.__init__(self, props, tag, lineno)
330 self.choices = []
331
332 def gen_choicepair_list(self):
333 """Generate a list of 2-item val:desc lists from self.choices."""
334 if not self.choices:
335 return None
336
337 choicepair_list = list()
338
339 for choice in self.choices:
340 choicepair = []
341 choicepair.append(choice.val)
342 choicepair.append(choice.desc)
343 choicepair_list.append(choicepair)
344
345 return choicepair_list
346
347 def gen_degenerate_choicepair_list(self, choices):
348 """Generate a list of 2-item val:desc with val=desc from passed-in choices."""
349 choicepair_list = list()
350
351 for choice in choices:
352 choicepair = []
353 choicepair.append(choice)
354 choicepair.append(choice)
355 choicepair_list.append(choicepair)
356
357 return choicepair_list
358
359 def exec_listgen_fn(self, context = None):
360 """
361 Execute the list-generating function contained as a string in
362 the "gen" property.
363 """
364 retval = None
365 try:
366 fname = self.props["gen"]
367 modsplit = fname.split('.')
368 mod_fn = modsplit.pop()
369 mod = '.'.join(modsplit)
370
371 __import__(mod)
372 # python 2.7 has a better way to do this using importlib.import_module
373 m = sys.modules[mod]
374
375 fn = getattr(m, mod_fn)
376 if not fn:
377 self.parse_error("couldn't load function specified for 'gen' property ",
378 self.lineno, self.line)
379 retval = fn(context)
380 if not retval:
381 self.parse_error("function specified for 'gen' property returned nothing ",
382 self.lineno, self.line)
383 except KeyError:
384 pass
385
386 return retval
387
388 def gen_choices_str(self, choicepairs):
389 """
390 Generate a numbered list of choices from a list of choicepairs
391 for display to the user.
392 """
393 choices_str = ""
394
395 for i, choicepair in enumerate(choicepairs):
396 choices_str += "\t" + str(i + 1) + ") " + choicepair[1] + "\n"
397
398 return choices_str
399
400 def gen_choices_val_str(self, choicepairs):
401 """
402 Generate an array of choice values corresponding to the
403 numbered list generated by gen_choices_str().
404 """
405 choices_val_list = "["
406
407 for i, choicepair in enumerate(choicepairs):
408 choices_val_list += "\"" + choicepair[0] + "\","
409 choices_val_list += "]"
410
411 return choices_val_list
412
413 def gen_choices_val_list(self, choicepairs):
414 """
415 Generate an array of choice values corresponding to the
416 numbered list generated by gen_choices_str().
417 """
418 choices_val_list = []
419
420 for i, choicepair in enumerate(choicepairs):
421 choices_val_list.append(choicepair[0])
422
423 return choices_val_list
424
425 def gen_choices_list(self, context = None, checklist = False):
426 """
427 Generate an array of choice values corresponding to the
428 numbered list generated by gen_choices_str().
429 """
430 choices = self.exec_listgen_fn(context)
431 if choices:
432 if len(choices) == 0:
433 self.parse_error("No entries available for input list",
434 self.lineno, self.line)
435 choicepairs = self.gen_degenerate_choicepair_list(choices)
436 else:
437 if len(self.choices) == 0:
438 self.parse_error("No entries available for input list",
439 self.lineno, self.line)
440 choicepairs = self.gen_choicepair_list()
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500441
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500442 return choicepairs
443
444 def gen_choices(self, context = None, checklist = False):
445 """
446 Generate an array of choice values corresponding to the
447 numbered list generated by gen_choices_str(), display it to
448 the user, and process the result.
449 """
450 msg = self.props["msg"]
451 if not msg:
452 self.parse_error("No input 'msg' property found",
453 self.lineno, self.line)
454
455 try:
456 default_choice = self.props["default"]
457 except KeyError:
458 default_choice = ""
459
460 msg += " [default: " + default_choice + "]"
461
462 choicepairs = self.gen_choices_list(context, checklist)
463
464 choices_str = self.gen_choices_str(choicepairs)
465 choices_val_list = self.gen_choices_val_list(choicepairs)
466 if checklist:
467 choiceval = default(find_choicevals(raw_input(msg + "\n" + choices_str), choices_val_list), default_choice)
468 else:
469 choiceval = default(find_choiceval(raw_input(msg + "\n" + choices_str), choices_val_list), default_choice)
470
471 return choiceval
472
473
474def find_choiceval(choice_str, choice_list):
475 """
476 Take number as string and return val string from choice_list,
477 empty string if oob. choice_list is a simple python list.
478 """
479 choice_val = ""
480
481 try:
482 choice_idx = int(choice_str)
483 if choice_idx <= len(choice_list):
484 choice_idx -= 1
485 choice_val = choice_list[choice_idx]
486 except ValueError:
487 pass
488
489 return choice_val
490
491
492def find_choicevals(choice_str, choice_list):
493 """
494 Take numbers as space-separated string and return vals list from
495 choice_list, empty list if oob. choice_list is a simple python
496 list.
497 """
498 choice_vals = []
499
500 choices = choice_str.split()
501 for choice in choices:
502 choice_vals.append(find_choiceval(choice, choice_list))
503
504 return choice_vals
505
506
507def default(input_str, name):
508 """
509 Return default if no input_str, otherwise stripped input_str.
510 """
511 if not input_str:
512 return name
513
514 return input_str.strip()
515
516
517def verify_git_repo(giturl):
518 """
519 Verify that the giturl passed in can be connected to. This can be
520 used as a check for the existence of the given repo and/or basic
521 git remote connectivity.
522
523 Returns True if the connection was successful, fals otherwise
524 """
525 if not giturl:
526 return False
527
528 gitcmd = "git ls-remote %s > /dev/null 2>&1" % (giturl)
529 rc = subprocess.call(gitcmd, shell=True)
530 if rc == 0:
531 return True
532
533 return False
534
535
536def get_verified_git_repo(input_str, name):
537 """
538 Return git repo if verified, otherwise loop forever asking user
539 for filename.
540 """
541 msg = input_str.strip() + " "
542
543 giturl = default(raw_input(msg), name)
544
545 while True:
546 if verify_git_repo(giturl):
547 return giturl
548 giturl = default(raw_input(msg), name)
549
550
551def get_verified_file(input_str, name, filename_can_be_null):
552 """
553 Return filename if the file exists, otherwise loop forever asking
554 user for filename.
555 """
556 msg = input_str.strip() + " "
557
558 filename = default(raw_input(msg), name)
559
560 while True:
561 if not filename and filename_can_be_null:
562 return filename
563 if os.path.isfile(filename):
564 return filename
565 filename = default(raw_input(msg), name)
566
567
568def replace_file(replace_this, with_this):
569 """
570 Replace the given file with the contents of filename, retaining
571 the original filename.
572 """
573 try:
574 replace_this.close()
575 shutil.copy(with_this, replace_this.name)
576 except IOError:
577 pass
578
579
580def boolean(input_str, name):
581 """
582 Return lowercase version of first char in string, or value in name.
583 """
584 if not input_str:
585 return name
586
587 str = input_str.lower().strip()
588 if str and str[0] == "y" or str[0] == "n":
589 return str[0]
590 else:
591 return name
592
593
594def strip_base(input_str):
595 """
596 strip '/base' off the end of input_str, so we can use 'base' in
597 the branch names we present to the user.
598 """
599 if input_str and input_str.endswith("/base"):
600 return input_str[:-len("/base")]
601 return input_str.strip()
602
603
604deferred_choices = {}
605
606def gen_choices_defer(input_line, context, checklist = False):
607 """
608 Save the context hashed the name of the input item, which will be
609 passed to the gen function later.
610 """
611 name = input_line.props["name"]
612
613 try:
614 nameappend = input_line.props["nameappend"]
615 except KeyError:
616 nameappend = ""
617
618 try:
619 branches_base = input_line.props["branches_base"]
620 except KeyError:
621 branches_base = ""
622
623 filename = input_line.props["filename"]
624
625 closetag_start = filename.find(CLOSE_TAG)
626
627 if closetag_start != -1:
628 filename = filename[closetag_start + len(CLOSE_TAG):]
629
630 filename = filename.strip()
631 filename = os.path.splitext(filename)[0]
632
633 captured_context = capture_context(context)
634 context["filename"] = filename
635 captured_context["filename"] = filename
636 context["nameappend"] = nameappend
637 captured_context["nameappend"] = nameappend
638 context["branches_base"] = branches_base
639 captured_context["branches_base"] = branches_base
640
641 deferred_choice = (input_line, captured_context, checklist)
642 key = name + "_" + filename + "_" + nameappend
643 deferred_choices[key] = deferred_choice
644
645
646def invoke_deferred_choices(name):
647 """
648 Invoke the choice generation function using the context hashed by
649 'name'.
650 """
651 deferred_choice = deferred_choices[name]
652 input_line = deferred_choice[0]
653 context = deferred_choice[1]
654 checklist = deferred_choice[2]
655
656 context["name"] = name
657
658 choices = input_line.gen_choices(context, checklist)
659
660 return choices
661
662
663class ChoicelistInputLine(ListInputLine):
664 """
665 Base class for choicelist Input lines.
666 props:
667 name: example - "xserver_choice"
668 msg: example - "Please select an xserver for this machine"
669 result:
670 Sets the value of the variable specified by 'name' to whichever Choice was chosen
671 example - xserver_choice = "xserver_vesa"
672 """
673 def __init__(self, props, tag, lineno):
674 ListInputLine.__init__(self, props, tag, lineno)
675
676 def gen(self, context = None):
677 InputLine.gen(self, context)
678
679 gen_choices_defer(self, context)
680 name = self.props["name"]
681 nameappend = context["nameappend"]
682 filename = context["filename"]
683
684 try:
685 default_choice = self.props["default"]
686 except KeyError:
687 default_choice = ""
688
689 line = name + " = default(invoke_deferred_choices(\"" + name + "_" + filename + "_" + nameappend + "\"), \"" + default_choice + "\")"
690
691 return line
692
693
694class ListValInputLine(InputLine):
695 """
696 Abstract base class for choice and checkbox Input lines.
697 """
698 def __init__(self, props, tag, lineno):
699 InputLine.__init__(self, props, tag, lineno)
700
701 try:
702 self.val = self.props["val"]
703 except KeyError:
704 self.parse_error("No input 'val' property found", self.lineno, self.line)
705
706 try:
707 self.desc = self.props["msg"]
708 except KeyError:
709 self.parse_error("No input 'msg' property found", self.lineno, self.line)
710
711
712class ChoiceInputLine(ListValInputLine):
713 """
714 Base class for choicelist item Input lines.
715 """
716 def __init__(self, props, tag, lineno):
717 ListValInputLine.__init__(self, props, tag, lineno)
718
719 def gen(self, context = None):
720 return None
721
722
723class ChecklistInputLine(ListInputLine):
724 """
725 Base class for checklist Input lines.
726 """
727 def __init__(self, props, tag, lineno):
728 ListInputLine.__init__(self, props, tag, lineno)
729
730 def gen(self, context = None):
731 InputLine.gen(self, context)
732
733 gen_choices_defer(self, context, True)
734 name = self.props["name"]
735 nameappend = context["nameappend"]
736 filename = context["filename"]
737
738 try:
739 default_choice = self.props["default"]
740 except KeyError:
741 default_choice = ""
742
743 line = name + " = default(invoke_deferred_choices(\"" + name + "_" + filename + "_" + nameappend + "\"), \"" + default_choice + "\")"
744
745 return line
746
747
748class CheckInputLine(ListValInputLine):
749 """
750 Base class for checklist item Input lines.
751 """
752 def __init__(self, props, tag, lineno):
753 ListValInputLine.__init__(self, props, tag, lineno)
754
755 def gen(self, context = None):
756 return None
757
758
759dirname_substitutions = {}
760
761class SubstrateBase(object):
762 """
763 Base class for both expanded and unexpanded file and dir container
764 objects.
765 """
766 def __init__(self, filename, filebase, out_filebase):
767 self.filename = filename
768 self.filebase = filebase
769 self.translated_filename = filename
770 self.out_filebase = out_filebase
771 self.raw_lines = []
772 self.expanded_lines = []
773 self.prev_choicelist = None
774
775 def parse_error(self, msg, lineno, line):
776 raise SyntaxError("%s: [%s: %d]: %s" % (msg, self.filename, lineno, line))
777
778 def expand_input_tag(self, tag, lineno):
779 """
780 Input tags consist of the word 'input' at the beginning,
781 followed by name:value property pairs which are converted into
782 a dictionary.
783 """
784 propstr = tag[len(INPUT_TAG):]
785
786 props = dict(prop.split(":", 1) for prop in shlex.split(propstr))
787 props["filename"] = self.filename
788
789 input_type = props[INPUT_TYPE_PROPERTY]
790 if not props[INPUT_TYPE_PROPERTY]:
791 self.parse_error("No input 'type' property found", lineno, tag)
792
793 if input_type == "boolean":
794 return BooleanInputLine(props, tag, lineno)
795 if input_type == "edit":
796 return EditBoxInputLine(props, tag, lineno)
797 if input_type == "edit-git-repo":
798 return GitRepoEditBoxInputLine(props, tag, lineno)
799 if input_type == "edit-file":
800 return FileEditBoxInputLine(props, tag, lineno)
801 elif input_type == "choicelist":
802 self.prev_choicelist = ChoicelistInputLine(props, tag, lineno)
803 return self.prev_choicelist
804 elif input_type == "choice":
805 if not self.prev_choicelist:
806 self.parse_error("Found 'choice' input tag but no previous choicelist",
807 lineno, tag)
808 choice = ChoiceInputLine(props, tag, lineno)
809 self.prev_choicelist.choices.append(choice)
810 return choice
811 elif input_type == "checklist":
812 return ChecklistInputLine(props, tag, lineno)
813 elif input_type == "check":
814 return CheckInputLine(props, tag, lineno)
815
816 def expand_assignment_tag(self, start, line, lineno):
817 """
818 Expand all tags in a line.
819 """
820 expanded_line = AssignmentLine(line.rstrip())
821
822 while start != -1:
823 end = line.find(CLOSE_TAG, start)
824 if end == -1:
825 self.parse_error("No close tag found for assignment tag", lineno, line)
826 else:
827 name = line[start + len(ASSIGN_TAG):end].strip()
828 expanded_line.add_assignment(start, end + len(CLOSE_TAG), name)
829 start = line.find(ASSIGN_TAG, end)
830
831 return expanded_line
832
833 def expand_tag(self, line, lineno):
834 """
835 Returns a processed tag line, or None if there was no tag
836
837 The rules for tags are very simple:
838 - No nested tags
839 - Tags start with {{ and end with }}
840 - An assign tag, {{=, can appear anywhere and will
841 be replaced with what the assignment evaluates to
842 - Any other tag occupies the whole line it is on
843 - if there's anything else on the tag line, it's an error
844 - if it starts with 'input', it's an input tag and
845 will only be used for prompting and setting variables
846 - anything else is straight Python
847 - tags are in effect only until the next blank line or tag or 'pass' tag
848 - we don't have indentation in tags, but we need some way to end a block
849 forcefully without blank lines or other tags - that's the 'pass' tag
850 - todo: implement pass tag
851 - directories and filenames can have tags as well, but only assignment
852 and 'if' code lines
853 - directories and filenames are the only case where normal tags can
854 coexist with normal text on the same 'line'
855 """
856 start = line.find(ASSIGN_TAG)
857 if start != -1:
858 return self.expand_assignment_tag(start, line, lineno)
859
860 start = line.find(OPEN_TAG)
861 if start == -1:
862 return None
863
864 end = line.find(CLOSE_TAG, 0)
865 if end == -1:
866 self.parse_error("No close tag found for open tag", lineno, line)
867
868 tag = line[start + len(OPEN_TAG):end].strip()
869
870 if not tag.lstrip().startswith(INPUT_TAG):
871 return CodeLine(tag)
872
873 return self.expand_input_tag(tag, lineno)
874
875 def append_translated_filename(self, filename):
876 """
877 Simply append filename to translated_filename
878 """
879 self.translated_filename = os.path.join(self.translated_filename, filename)
880
881 def get_substituted_file_or_dir_name(self, first_line, tag):
882 """
883 If file or dir names contain name substitutions, return the name
884 to substitute. Note that this is just the file or dirname and
885 doesn't include the path.
886 """
887 filename = first_line.find(tag)
888 if filename != -1:
889 filename += len(tag)
890 substituted_filename = first_line[filename:].strip()
891 this = substituted_filename.find(" this")
892 if this != -1:
893 head, tail = os.path.split(self.filename)
894 substituted_filename = substituted_filename[:this + 1] + tail
895 if tag == DIRNAME_TAG: # get rid of .noinstall in dirname
896 substituted_filename = substituted_filename.split('.')[0]
897
898 return substituted_filename
899
900 def get_substituted_filename(self, first_line):
901 """
902 If a filename contains a name substitution, return the name to
903 substitute. Note that this is just the filename and doesn't
904 include the path.
905 """
906 return self.get_substituted_file_or_dir_name(first_line, FILENAME_TAG)
907
908 def get_substituted_dirname(self, first_line):
909 """
910 If a dirname contains a name substitution, return the name to
911 substitute. Note that this is just the dirname and doesn't
912 include the path.
913 """
914 return self.get_substituted_file_or_dir_name(first_line, DIRNAME_TAG)
915
916 def substitute_filename(self, first_line):
917 """
918 Find the filename in first_line and append it to translated_filename.
919 """
920 substituted_filename = self.get_substituted_filename(first_line)
921 self.append_translated_filename(substituted_filename);
922
923 def substitute_dirname(self, first_line):
924 """
925 Find the dirname in first_line and append it to translated_filename.
926 """
927 substituted_dirname = self.get_substituted_dirname(first_line)
928 self.append_translated_filename(substituted_dirname);
929
930 def is_filename_substitution(self, line):
931 """
932 Do we have a filename subustition?
933 """
934 if line.find(FILENAME_TAG) != -1:
935 return True
936 return False
937
938 def is_dirname_substitution(self, line):
939 """
940 Do we have a dirname subustition?
941 """
942 if line.find(DIRNAME_TAG) != -1:
943 return True
944 return False
945
946 def translate_dirname(self, first_line):
947 """
948 Just save the first_line mapped by filename. The later pass
949 through the directories will look for a dirname.noinstall
950 match and grab the substitution line.
951 """
952 dirname_substitutions[self.filename] = first_line
953
954 def translate_dirnames_in_path(self, path):
955 """
956 Translate dirnames below this file or dir, not including tail.
957 dirname_substititions is keyed on actual untranslated filenames.
958 translated_path contains the subsititutions for each element.
959 """
960 remainder = path[len(self.filebase)+1:]
961 translated_path = untranslated_path = self.filebase
962
963 untranslated_dirs = remainder.split(os.sep)
964
965 for dir in untranslated_dirs:
966 key = os.path.join(untranslated_path, dir + '.noinstall')
967 try:
968 first_line = dirname_substitutions[key]
969 except KeyError:
970 translated_path = os.path.join(translated_path, dir)
971 untranslated_path = os.path.join(untranslated_path, dir)
972 continue
973 substituted_dir = self.get_substituted_dirname(first_line)
974 translated_path = os.path.join(translated_path, substituted_dir)
975 untranslated_path = os.path.join(untranslated_path, dir)
976
977 return translated_path
978
979 def translate_file_or_dir_name(self):
980 """
981 Originally we were allowed to use open/close/assign tags and python
982 code in the filename, which fit in nicely with the way we
983 processed the templates and generated code. Now that we can't
984 do that, we make those tags proper file contents and have this
985 pass substitute the nice but non-functional names with those
986 'strange' ones, and then proceed as usual.
987
988 So, if files or matching dir<.noinstall> files contain
989 filename substitutions, this function translates them into the
990 corresponding 'strange' names, which future passes will expand
991 as they always have. The resulting pathname is kept in the
992 file or directory's translated_filename. Another way to think
993 about it is that self.filename is the input filename, and
994 translated_filename is the output filename before expansion.
995 """
996 # remove leaf file or dirname
997 head, tail = os.path.split(self.filename)
998 translated_path = self.translate_dirnames_in_path(head)
999 self.translated_filename = translated_path
1000
1001 # This is a dirname - does it have a matching .noinstall with
1002 # a substitution? If so, apply the dirname subsititution.
1003 if not os.path.isfile(self.filename):
1004 key = self.filename + ".noinstall"
1005 try:
1006 first_line = dirname_substitutions[key]
1007 except KeyError:
1008 self.append_translated_filename(tail)
1009 return
1010 self.substitute_dirname(first_line)
1011 return
1012
1013 f = open(self.filename)
1014 first_line = f.readline()
1015 f.close()
1016
1017 # This is a normal filename not needing translation, just use
1018 # it as-is.
1019 if not first_line or not first_line.startswith("#"):
1020 self.append_translated_filename(tail)
1021 return
1022
1023 # If we have a filename substitution (first line in the file
1024 # is a FILENAME_TAG line) do the substitution now. If we have
1025 # a dirname substitution (DIRNAME_TAG in dirname.noinstall
1026 # meta-file), hash it so we can apply it when we see the
1027 # matching dirname later. Otherwise we have a regular
1028 # filename, just use it as-is.
1029 if self.is_filename_substitution(first_line):
1030 self.substitute_filename(first_line)
1031 elif self.is_dirname_substitution(first_line):
1032 self.translate_dirname(first_line)
1033 else:
1034 self.append_translated_filename(tail)
1035
1036 def expand_file_or_dir_name(self):
1037 """
1038 Expand file or dir names into codeline. Dirnames and
1039 filenames can only have assignments or if statements. First
1040 translate if statements into CodeLine + (dirname or filename
1041 creation).
1042 """
1043 lineno = 0
1044
1045 line = self.translated_filename[len(self.filebase):]
1046 if line.startswith("/"):
1047 line = line[1:]
1048 opentag_start = -1
1049
1050 start = line.find(OPEN_TAG)
1051 while start != -1:
1052 if not line[start:].startswith(ASSIGN_TAG):
1053 opentag_start = start
1054 break
1055 start += len(ASSIGN_TAG)
1056 start = line.find(OPEN_TAG, start)
1057
1058 if opentag_start != -1:
1059 end = line.find(CLOSE_TAG, opentag_start)
1060 if end == -1:
1061 self.parse_error("No close tag found for open tag", lineno, line)
1062 # we have a {{ tag i.e. code
1063 tag = line[opentag_start + len(OPEN_TAG):end].strip()
1064 if not tag.lstrip().startswith(IF_TAG):
1065 self.parse_error("Only 'if' tags are allowed in file or directory names",
1066 lineno, line)
1067 self.expanded_lines.append(CodeLine(tag))
1068
1069 # everything after }} is the actual filename (possibly with assignments)
1070 # everything before is the pathname
1071 line = line[:opentag_start] + line[end + len(CLOSE_TAG):].strip()
1072
1073 assign_start = line.find(ASSIGN_TAG)
1074 if assign_start != -1:
1075 assignment_tag = self.expand_assignment_tag(assign_start, line, lineno)
1076 if isinstance(self, SubstrateFile):
1077 assignment_tag.is_filename = True
1078 assignment_tag.out_filebase = self.out_filebase
1079 elif isinstance(self, SubstrateDir):
1080 assignment_tag.is_dirname = True
1081 assignment_tag.out_filebase = self.out_filebase
1082 self.expanded_lines.append(assignment_tag)
1083 return
1084
1085 normal_line = NormalLine(line)
1086 if isinstance(self, SubstrateFile):
1087 normal_line.is_filename = True
1088 normal_line.out_filebase = self.out_filebase
1089 elif isinstance(self, SubstrateDir):
1090 normal_line.is_dirname = True
1091 normal_line.out_filebase = self.out_filebase
1092 self.expanded_lines.append(normal_line)
1093
1094 def expand(self):
1095 """
1096 Expand the file or dir name first, eventually this ends up
1097 creating the file or dir.
1098 """
1099 self.translate_file_or_dir_name()
1100 self.expand_file_or_dir_name()
1101
1102
1103class SubstrateFile(SubstrateBase):
1104 """
1105 Container for both expanded and unexpanded substrate files.
1106 """
1107 def __init__(self, filename, filebase, out_filebase):
1108 SubstrateBase.__init__(self, filename, filebase, out_filebase)
1109
1110 def read(self):
1111 if self.raw_lines:
1112 return
1113 f = open(self.filename)
1114 self.raw_lines = f.readlines()
1115
1116 def expand(self):
1117 """Expand the contents of all template tags in the file."""
1118 SubstrateBase.expand(self)
1119 self.read()
1120
1121 for lineno, line in enumerate(self.raw_lines):
1122 # only first line can be a filename substitition
1123 if lineno == 0 and line.startswith("#") and FILENAME_TAG in line:
1124 continue # skip it - we've already expanded it
1125 expanded_line = self.expand_tag(line, lineno + 1) # humans not 0-based
1126 if not expanded_line:
1127 expanded_line = NormalLine(line.rstrip())
1128 self.expanded_lines.append(expanded_line)
1129
1130 def gen(self, context = None):
1131 """Generate the code that generates the BSP."""
1132 base_indent = 0
1133
1134 indent = new_indent = base_indent
1135
1136 for line in self.expanded_lines:
1137 genline = line.gen(context)
1138 if not genline:
1139 continue
1140 if isinstance(line, InputLine):
1141 line.generated_line = genline
1142 continue
1143 if genline.startswith(OPEN_START):
1144 if indent == 1:
1145 base_indent = 1
1146 if indent:
1147 if genline == BLANKLINE_STR or (not genline.startswith(NORMAL_START)
1148 and not genline.startswith(OPEN_START)):
1149 indent = new_indent = base_indent
1150 if genline.endswith(":"):
1151 new_indent = base_indent + 1
1152 line.generated_line = (indent * INDENT_STR) + genline
1153 indent = new_indent
1154
1155
1156class SubstrateDir(SubstrateBase):
1157 """
1158 Container for both expanded and unexpanded substrate dirs.
1159 """
1160 def __init__(self, filename, filebase, out_filebase):
1161 SubstrateBase.__init__(self, filename, filebase, out_filebase)
1162
1163 def expand(self):
1164 SubstrateBase.expand(self)
1165
1166 def gen(self, context = None):
1167 """Generate the code that generates the BSP."""
1168 indent = new_indent = 0
1169 for line in self.expanded_lines:
1170 genline = line.gen(context)
1171 if not genline:
1172 continue
1173 if genline.endswith(":"):
1174 new_indent = 1
1175 else:
1176 new_indent = 0
1177 line.generated_line = (indent * INDENT_STR) + genline
1178 indent = new_indent
1179
1180
1181def expand_target(target, all_files, out_filebase):
1182 """
1183 Expand the contents of all template tags in the target. This
1184 means removing tags and categorizing or creating lines so that
1185 future passes can process and present input lines and generate the
1186 corresponding lines of the Python program that will be exec'ed to
1187 actually produce the final BSP. 'all_files' includes directories.
1188 """
1189 for root, dirs, files in os.walk(target):
1190 for file in files:
1191 if file.endswith("~") or file.endswith("#"):
1192 continue
1193 f = os.path.join(root, file)
1194 sfile = SubstrateFile(f, target, out_filebase)
1195 sfile.expand()
1196 all_files.append(sfile)
1197
1198 for dir in dirs:
1199 d = os.path.join(root, dir)
1200 sdir = SubstrateDir(d, target, out_filebase)
1201 sdir.expand()
1202 all_files.append(sdir)
1203
1204
1205def gen_program_machine_lines(machine, program_lines):
1206 """
1207 Use the input values we got from the command line.
1208 """
1209 line = "machine = \"" + machine + "\""
1210 program_lines.append(line)
1211
1212 line = "layer_name = \"" + machine + "\""
1213 program_lines.append(line)
1214
1215
1216def sort_inputlines(input_lines):
1217 """Sort input lines according to priority (position)."""
1218 input_lines.sort(key = lambda l: l.prio)
1219
1220
1221def find_parent_dependency(lines, depends_on):
1222 for i, line in lines:
1223 if isinstance(line, CodeLine):
1224 continue
1225 if line.props["name"] == depends_on:
1226 return i
1227
1228 return -1
1229
1230
1231def process_inputline_dependencies(input_lines, all_inputlines):
1232 """If any input lines depend on others, put the others first."""
1233 for line in input_lines:
1234 if isinstance(line, InputLineGroup):
1235 group_inputlines = []
1236 process_inputline_dependencies(line.group, group_inputlines)
1237 line.group = group_inputlines
1238 all_inputlines.append(line)
1239 continue
1240
1241 if isinstance(line, CodeLine) or isinstance(line, NormalLine):
1242 all_inputlines.append(line)
1243 continue
1244
1245 try:
1246 depends_on = line.props["depends-on"]
1247 depends_codeline = "if " + line.props["depends-on"] + " == \"" + line.props["depends-on-val"] + "\":"
1248 all_inputlines.append(CodeLine(depends_codeline))
1249 all_inputlines.append(line)
1250 except KeyError:
1251 all_inputlines.append(line)
1252
1253
1254def conditional_filename(filename):
1255 """
1256 Check if the filename itself contains a conditional statement. If
1257 so, return a codeline for it.
1258 """
1259 opentag_start = filename.find(OPEN_TAG)
1260
1261 if opentag_start != -1:
1262 if filename[opentag_start:].startswith(ASSIGN_TAG):
1263 return None
1264 end = filename.find(CLOSE_TAG, opentag_start)
1265 if end == -1:
1266 print "No close tag found for open tag in filename %s" % filename
1267 sys.exit(1)
1268
1269 # we have a {{ tag i.e. code
1270 tag = filename[opentag_start + len(OPEN_TAG):end].strip()
1271 if not tag.lstrip().startswith(IF_TAG):
1272 print "Only 'if' tags are allowed in file or directory names, filename: %s" % filename
1273 sys.exit(1)
1274
1275 return CodeLine(tag)
1276
1277 return None
1278
1279
1280class InputLineGroup(InputLine):
1281 """
1282 InputLine that does nothing but group other input lines
1283 corresponding to all the input lines in a SubstrateFile so they
1284 can be generated as a group. prio is the only property used.
1285 """
1286 def __init__(self, codeline):
1287 InputLine.__init__(self, {}, "", 0)
1288 self.group = []
1289 self.prio = sys.maxint
1290 self.group.append(codeline)
1291
1292 def append(self, line):
1293 self.group.append(line)
1294 if line.prio < self.prio:
1295 self.prio = line.prio
1296
1297 def len(self):
1298 return len(self.group)
1299
1300
1301def gather_inputlines(files):
1302 """
1303 Gather all the InputLines - we want to generate them first.
1304 """
1305 all_inputlines = []
1306 input_lines = []
1307
1308 for file in files:
1309 if isinstance(file, SubstrateFile):
1310 group = None
1311 basename = os.path.basename(file.translated_filename)
1312
1313 codeline = conditional_filename(basename)
1314 if codeline:
1315 group = InputLineGroup(codeline)
1316
1317 have_condition = False
1318 condition_to_write = None
1319 for line in file.expanded_lines:
1320 if isinstance(line, CodeLine):
1321 have_condition = True
1322 condition_to_write = line
1323 continue
1324 if isinstance(line, InputLine):
1325 if group:
1326 if condition_to_write:
1327 condition_to_write.prio = line.prio
1328 condition_to_write.discard = True
1329 group.append(condition_to_write)
1330 condition_to_write = None
1331 group.append(line)
1332 else:
1333 if condition_to_write:
1334 condition_to_write.prio = line.prio
1335 condition_to_write.discard = True
1336 input_lines.append(condition_to_write)
1337 condition_to_write = None
1338 input_lines.append(line)
1339 else:
1340 if condition_to_write:
1341 condition_to_write = None
1342 if have_condition:
1343 if not line.line.strip():
1344 line.discard = True
1345 input_lines.append(line)
1346 have_condition = False
1347
1348 if group and group.len() > 1:
1349 input_lines.append(group)
1350
1351 sort_inputlines(input_lines)
1352 process_inputline_dependencies(input_lines, all_inputlines)
1353
1354 return all_inputlines
1355
1356
1357def run_program_lines(linelist, codedump):
1358 """
1359 For a single file, print all the python code into a buf and execute it.
1360 """
1361 buf = "\n".join(linelist)
1362
1363 if codedump:
1364 of = open("bspgen.out", "w")
1365 of.write(buf)
1366 of.close()
1367 exec buf
1368
1369
1370def gen_target(files, context = None):
1371 """
1372 Generate the python code for each file.
1373 """
1374 for file in files:
1375 file.gen(context)
1376
1377
1378def gen_program_header_lines(program_lines):
1379 """
1380 Generate any imports we need.
1381 """
1382 program_lines.append("current_file = \"\"")
1383
1384
1385def gen_supplied_property_vals(properties, program_lines):
1386 """
1387 Generate user-specified entries for input values instead of
1388 generating input prompts.
1389 """
1390 for name, val in properties.iteritems():
1391 program_line = name + " = \"" + val + "\""
1392 program_lines.append(program_line)
1393
1394
1395def gen_initial_property_vals(input_lines, program_lines):
1396 """
1397 Generate null or default entries for input values, so we don't
1398 have undefined variables.
1399 """
1400 for line in input_lines:
1401 if isinstance(line, InputLineGroup):
1402 gen_initial_property_vals(line.group, program_lines)
1403 continue
1404
1405 if isinstance(line, InputLine):
1406 try:
1407 name = line.props["name"]
1408 try:
1409 default_val = "\"" + line.props["default"] + "\""
1410 except:
1411 default_val = "\"\""
1412 program_line = name + " = " + default_val
1413 program_lines.append(program_line)
1414 except KeyError:
1415 pass
1416
1417
1418def gen_program_input_lines(input_lines, program_lines, context, in_group = False):
1419 """
1420 Generate only the input lines used for prompting the user. For
1421 that, we only have input lines and CodeLines that affect the next
1422 input line.
1423 """
1424 indent = new_indent = 0
1425
1426 for line in input_lines:
1427 if isinstance(line, InputLineGroup):
1428 gen_program_input_lines(line.group, program_lines, context, True)
1429 continue
1430 if not line.line.strip():
1431 continue
1432
1433 genline = line.gen(context)
1434 if not genline:
1435 continue
1436 if genline.endswith(":"):
1437 new_indent += 1
1438 else:
1439 if indent > 1 or (not in_group and indent):
1440 new_indent -= 1
1441
1442 line.generated_line = (indent * INDENT_STR) + genline
1443 program_lines.append(line.generated_line)
1444
1445 indent = new_indent
1446
1447
1448def gen_program_lines(target_files, program_lines):
1449 """
1450 Generate the program lines that make up the BSP generation
1451 program. This appends the generated lines of all target_files to
1452 program_lines, and skips input lines, which are dealt with
1453 separately, or omitted.
1454 """
1455 for file in target_files:
1456 if file.filename.endswith("noinstall"):
1457 continue
1458
1459 for line in file.expanded_lines:
1460 if isinstance(line, InputLine):
1461 continue
1462 if line.discard:
1463 continue
1464
1465 program_lines.append(line.generated_line)
1466
1467
1468def create_context(machine, arch, scripts_path):
1469 """
1470 Create a context object for use in deferred function invocation.
1471 """
1472 context = {}
1473
1474 context["machine"] = machine
1475 context["arch"] = arch
1476 context["scripts_path"] = scripts_path
1477
1478 return context
1479
1480
1481def capture_context(context):
1482 """
1483 Create a context object for use in deferred function invocation.
1484 """
1485 captured_context = {}
1486
1487 captured_context["machine"] = context["machine"]
1488 captured_context["arch"] = context["arch"]
1489 captured_context["scripts_path"] = context["scripts_path"]
1490
1491 return captured_context
1492
1493
1494def expand_targets(context, bsp_output_dir, expand_common=True):
1495 """
1496 Expand all the tags in both the common and machine-specific
1497 'targets'.
1498
1499 If expand_common is False, don't expand the common target (this
1500 option is used to create special-purpose layers).
1501 """
1502 target_files = []
1503
1504 machine = context["machine"]
1505 arch = context["arch"]
1506 scripts_path = context["scripts_path"]
1507
1508 lib_path = scripts_path + '/lib'
1509 bsp_path = lib_path + '/bsp'
1510 arch_path = bsp_path + '/substrate/target/arch'
1511
1512 if expand_common:
1513 common = os.path.join(arch_path, "common")
1514 expand_target(common, target_files, bsp_output_dir)
1515
1516 arches = os.listdir(arch_path)
1517 if arch not in arches or arch == "common":
1518 print "Invalid karch, exiting\n"
1519 sys.exit(1)
1520
1521 target = os.path.join(arch_path, arch)
1522 expand_target(target, target_files, bsp_output_dir)
1523
1524 gen_target(target_files, context)
1525
1526 return target_files
1527
1528
1529def yocto_common_create(machine, target, scripts_path, layer_output_dir, codedump, properties_file, properties_str="", expand_common=True):
1530 """
1531 Common layer-creation code
1532
1533 machine - user-defined machine name (if needed, will generate 'machine' var)
1534 target - the 'target' the layer will be based on, must be one in
1535 scripts/lib/bsp/substrate/target/arch
1536 scripts_path - absolute path to yocto /scripts dir
1537 layer_output_dir - dirname to create for layer
1538 codedump - dump generated code to bspgen.out
1539 properties_file - use values from this file if nonempty i.e no prompting
1540 properties_str - use values from this string if nonempty i.e no prompting
1541 expand_common - boolean, use the contents of (for bsp layers) arch/common
1542 """
1543 if os.path.exists(layer_output_dir):
1544 print "\nlayer output dir already exists, exiting. (%s)" % layer_output_dir
1545 sys.exit(1)
1546
1547 properties = None
1548
1549 if properties_file:
1550 try:
1551 infile = open(properties_file, "r")
1552 except IOError:
1553 print "Couldn't open properties file %s for reading, exiting" % properties_file
1554 sys.exit(1)
1555
1556 properties = json.load(infile)
1557
1558 if properties_str and not properties:
1559 properties = json.loads(properties_str)
1560
1561 os.mkdir(layer_output_dir)
1562
1563 context = create_context(machine, target, scripts_path)
1564 target_files = expand_targets(context, layer_output_dir, expand_common)
1565
1566 input_lines = gather_inputlines(target_files)
1567
1568 program_lines = []
1569
1570 gen_program_header_lines(program_lines)
1571
1572 gen_initial_property_vals(input_lines, program_lines)
1573
1574 if properties:
1575 gen_supplied_property_vals(properties, program_lines)
1576
1577 gen_program_machine_lines(machine, program_lines)
1578
1579 if not properties:
1580 gen_program_input_lines(input_lines, program_lines, context)
1581
1582 gen_program_lines(target_files, program_lines)
1583
1584 run_program_lines(program_lines, codedump)
1585
1586
1587def yocto_layer_create(layer_name, scripts_path, layer_output_dir, codedump, properties_file, properties=""):
1588 """
1589 Create yocto layer
1590
1591 layer_name - user-defined layer name
1592 scripts_path - absolute path to yocto /scripts dir
1593 layer_output_dir - dirname to create for layer
1594 codedump - dump generated code to bspgen.out
1595 properties_file - use values from this file if nonempty i.e no prompting
1596 properties - use values from this string if nonempty i.e no prompting
1597 """
1598 yocto_common_create(layer_name, "layer", scripts_path, layer_output_dir, codedump, properties_file, properties, False)
1599
1600 print "\nNew layer created in %s.\n" % (layer_output_dir)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001601 print "Don't forget to add it to your BBLAYERS (for details see %s/README)." % (layer_output_dir)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001602
1603
1604def yocto_bsp_create(machine, arch, scripts_path, bsp_output_dir, codedump, properties_file, properties=None):
1605 """
1606 Create bsp
1607
1608 machine - user-defined machine name
1609 arch - the arch the bsp will be based on, must be one in
1610 scripts/lib/bsp/substrate/target/arch
1611 scripts_path - absolute path to yocto /scripts dir
1612 bsp_output_dir - dirname to create for BSP
1613 codedump - dump generated code to bspgen.out
1614 properties_file - use values from this file if nonempty i.e no prompting
1615 properties - use values from this string if nonempty i.e no prompting
1616 """
1617 yocto_common_create(machine, arch, scripts_path, bsp_output_dir, codedump, properties_file, properties)
1618
1619 print "\nNew %s BSP created in %s" % (arch, bsp_output_dir)
1620
1621
1622def print_dict(items, indent = 0):
1623 """
1624 Print the values in a possibly nested dictionary.
1625 """
1626 for key, val in items.iteritems():
1627 print " "*indent + "\"%s\" :" % key,
1628 if type(val) == dict:
1629 print "{"
1630 print_dict(val, indent + 1)
1631 print " "*indent + "}"
1632 else:
1633 print "%s" % val
1634
1635
1636def get_properties(input_lines):
1637 """
1638 Get the complete set of properties for all the input items in the
1639 BSP, as a possibly nested dictionary.
1640 """
1641 properties = {}
1642
1643 for line in input_lines:
1644 if isinstance(line, InputLineGroup):
1645 statement = line.group[0].line
1646 group_properties = get_properties(line.group)
1647 properties[statement] = group_properties
1648 continue
1649
1650 if not isinstance(line, InputLine):
1651 continue
1652
1653 if isinstance(line, ChoiceInputLine):
1654 continue
1655
1656 props = line.props
1657 item = {}
1658 name = props["name"]
1659 for key, val in props.items():
1660 if not key == "name":
1661 item[key] = val
1662 properties[name] = item
1663
1664 return properties
1665
1666
1667def yocto_layer_list_properties(arch, scripts_path, properties_file, expand_common=True):
1668 """
1669 List the complete set of properties for all the input items in the
1670 layer. If properties_file is non-null, write the complete set of
1671 properties as a nested JSON object corresponding to a possibly
1672 nested dictionary.
1673 """
1674 context = create_context("unused", arch, scripts_path)
1675 target_files = expand_targets(context, "unused", expand_common)
1676
1677 input_lines = gather_inputlines(target_files)
1678
1679 properties = get_properties(input_lines)
1680 if properties_file:
1681 try:
1682 of = open(properties_file, "w")
1683 except IOError:
1684 print "Couldn't open properties file %s for writing, exiting" % properties_file
1685 sys.exit(1)
1686
Patrick Williamsf1e5d692016-03-30 15:21:19 -05001687 json.dump(properties, of, indent=1)
1688 else:
1689 print_dict(properties)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001690
1691
1692def split_nested_property(property):
1693 """
1694 A property name of the form x.y describes a nested property
1695 i.e. the property y is contained within x and can be addressed
1696 using standard JSON syntax for nested properties. Note that if a
1697 property name itself contains '.', it should be contained in
1698 double quotes.
1699 """
1700 splittable_property = ""
1701 in_quotes = False
1702 for c in property:
1703 if c == '.' and not in_quotes:
1704 splittable_property += '\n'
1705 continue
1706 if c == '"':
1707 in_quotes = not in_quotes
1708 splittable_property += c
1709
1710 split_properties = splittable_property.split('\n')
1711
1712 if len(split_properties) > 1:
1713 return split_properties
1714
1715 return None
1716
1717
1718def find_input_line_group(substring, input_lines):
1719 """
1720 Find and return the InputLineGroup containing the specified substring.
1721 """
1722 for line in input_lines:
1723 if isinstance(line, InputLineGroup):
1724 if substring in line.group[0].line:
1725 return line
1726
1727 return None
1728
1729
1730def find_input_line(name, input_lines):
1731 """
1732 Find the input line with the specified name.
1733 """
1734 for line in input_lines:
1735 if isinstance(line, InputLineGroup):
1736 l = find_input_line(name, line.group)
1737 if l:
1738 return l
1739
1740 if isinstance(line, InputLine):
1741 try:
1742 if line.props["name"] == name:
1743 return line
1744 if line.props["name"] + "_" + line.props["nameappend"] == name:
1745 return line
1746 except KeyError:
1747 pass
1748
1749 return None
1750
1751
1752def print_values(type, values_list):
1753 """
1754 Print the values in the given list of values.
1755 """
1756 if type == "choicelist":
1757 for value in values_list:
1758 print "[\"%s\", \"%s\"]" % (value[0], value[1])
1759 elif type == "boolean":
1760 for value in values_list:
1761 print "[\"%s\", \"%s\"]" % (value[0], value[1])
1762
1763
1764def yocto_layer_list_property_values(arch, property, scripts_path, properties_file, expand_common=True):
1765 """
1766 List the possible values for a given input property. If
1767 properties_file is non-null, write the complete set of properties
1768 as a JSON object corresponding to an array of possible values.
1769 """
1770 context = create_context("unused", arch, scripts_path)
1771 context["name"] = property
1772
1773 target_files = expand_targets(context, "unused", expand_common)
1774
1775 input_lines = gather_inputlines(target_files)
1776
1777 properties = get_properties(input_lines)
1778
1779 nested_properties = split_nested_property(property)
1780 if nested_properties:
1781 # currently the outer property of a nested property always
1782 # corresponds to an input line group
1783 input_line_group = find_input_line_group(nested_properties[0], input_lines)
1784 if input_line_group:
1785 input_lines[:] = input_line_group.group[1:]
1786 # The inner property of a nested property name is the
1787 # actual property name we want, so reset to that
1788 property = nested_properties[1]
1789
1790 input_line = find_input_line(property, input_lines)
1791 if not input_line:
1792 print "Couldn't find values for property %s" % property
1793 return
1794
1795 values_list = []
1796
1797 type = input_line.props["type"]
1798 if type == "boolean":
1799 values_list.append(["y", "n"])
1800 elif type == "choicelist" or type == "checklist":
1801 try:
1802 gen_fn = input_line.props["gen"]
1803 if nested_properties:
1804 context["filename"] = nested_properties[0]
1805 try:
1806 context["branches_base"] = input_line.props["branches_base"]
1807 except KeyError:
1808 context["branches_base"] = None
1809 values_list = input_line.gen_choices_list(context, False)
1810 except KeyError:
1811 for choice in input_line.choices:
1812 choicepair = []
1813 choicepair.append(choice.val)
1814 choicepair.append(choice.desc)
1815 values_list.append(choicepair)
1816
1817 if properties_file:
1818 try:
1819 of = open(properties_file, "w")
1820 except IOError:
1821 print "Couldn't open properties file %s for writing, exiting" % properties_file
1822 sys.exit(1)
1823
1824 json.dump(values_list, of)
Patrick Williamsd8c66bc2016-06-20 12:57:21 -05001825
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001826 print_values(type, values_list)
1827
1828
1829def yocto_bsp_list(args, scripts_path, properties_file):
1830 """
1831 Print available architectures, or the complete list of properties
1832 defined by the BSP, or the possible values for a particular BSP
1833 property.
1834 """
1835 if len(args) < 1:
1836 return False
1837
1838 if args[0] == "karch":
1839 lib_path = scripts_path + '/lib'
1840 bsp_path = lib_path + '/bsp'
1841 arch_path = bsp_path + '/substrate/target/arch'
1842 print "Architectures available:"
1843 for arch in os.listdir(arch_path):
1844 if arch == "common" or arch == "layer":
1845 continue
1846 print " %s" % arch
1847 return True
1848 else:
1849 arch = args[0]
1850
1851 if len(args) < 2 or len(args) > 3:
1852 return False
1853
1854 if len(args) == 2:
1855 if args[1] == "properties":
1856 yocto_layer_list_properties(arch, scripts_path, properties_file)
1857 else:
1858 return False
1859
1860 if len(args) == 3:
1861 if args[1] == "property":
1862 yocto_layer_list_property_values(arch, args[2], scripts_path, properties_file)
1863 else:
1864 return False
1865
1866 return True
1867
1868
1869def yocto_layer_list(args, scripts_path, properties_file):
1870 """
1871 Print the complete list of input properties defined by the layer,
1872 or the possible values for a particular layer property.
1873 """
1874 if len(args) < 1:
1875 return False
1876
1877 if len(args) < 1 or len(args) > 2:
1878 return False
1879
1880 if len(args) == 1:
1881 if args[0] == "properties":
1882 yocto_layer_list_properties("layer", scripts_path, properties_file, False)
1883 else:
1884 return False
1885
1886 if len(args) == 2:
1887 if args[0] == "property":
1888 yocto_layer_list_property_values("layer", args[1], scripts_path, properties_file, False)
1889 else:
1890 return False
1891
1892 return True
1893
1894
1895def map_standard_kbranch(need_new_kbranch, new_kbranch, existing_kbranch):
1896 """
1897 Return the linux-yocto bsp branch to use with the specified
1898 kbranch. This handles the -standard variants for 3.4 and 3.8; the
1899 other variants don't need mappings.
1900 """
1901 if need_new_kbranch == "y":
1902 kbranch = new_kbranch
1903 else:
1904 kbranch = existing_kbranch
1905
1906 if kbranch.startswith("standard/common-pc-64"):
1907 return "bsp/common-pc-64/common-pc-64-standard.scc"
1908 if kbranch.startswith("standard/common-pc"):
1909 return "bsp/common-pc/common-pc-standard.scc"
1910 else:
1911 return "ktypes/standard/standard.scc"
1912
1913
1914def map_preempt_rt_kbranch(need_new_kbranch, new_kbranch, existing_kbranch):
1915 """
1916 Return the linux-yocto bsp branch to use with the specified
1917 kbranch. This handles the -preempt-rt variants for 3.4 and 3.8;
1918 the other variants don't need mappings.
1919 """
1920 if need_new_kbranch == "y":
1921 kbranch = new_kbranch
1922 else:
1923 kbranch = existing_kbranch
1924
1925 if kbranch.startswith("standard/preempt-rt/common-pc-64"):
1926 return "bsp/common-pc-64/common-pc-64-preempt-rt.scc"
1927 if kbranch.startswith("standard/preempt-rt/common-pc"):
1928 return "bsp/common-pc/common-pc-preempt-rt.scc"
1929 else:
1930 return "ktypes/preempt-rt/preempt-rt.scc"
1931
1932
1933def map_tiny_kbranch(need_new_kbranch, new_kbranch, existing_kbranch):
1934 """
1935 Return the linux-yocto bsp branch to use with the specified
1936 kbranch. This handles the -tiny variants for 3.4 and 3.8; the
1937 other variants don't need mappings.
1938 """
1939 if need_new_kbranch == "y":
1940 kbranch = new_kbranch
1941 else:
1942 kbranch = existing_kbranch
1943
1944 if kbranch.startswith("standard/tiny/common-pc"):
1945 return "bsp/common-pc/common-pc-tiny.scc"
1946 else:
1947 return "ktypes/tiny/tiny.scc"