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