engine.py 62 KB

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