inet_featuretool 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. #!/usr/bin/env python
  2. #
  3. # opp_featuretool: This script manipulates omnetpp project features.
  4. #
  5. # Copyright (C) 2016 OpenSim Ltd.
  6. # Author: Zoltan Bojthe
  7. #
  8. # TODO: add a new flag (-f) to the 'defines' command to be able to generate a file
  9. # with WITH_XXX variables in gnu make format that can be included in makefrag
  10. # TODO: to generate the WITH_XXX macros, use the feature_id with a 'WITH_' prefix instead
  11. # of requiring the -DWITH_macro in the compiler flags in each feature.
  12. from __future__ import print_function
  13. import argparse
  14. import csv
  15. import os
  16. import sys
  17. import re
  18. import types
  19. import xml.dom.minidom
  20. FEATURESFILE = ".oppfeatures"
  21. FEATURESTATEFILE = ".oppfeaturestate"
  22. NEDFOLDERSFILE = ".nedfolders"
  23. NEDEXCLUSIONSFILE = ".nedexclusions"
  24. def fail(msg):
  25. print("opp_featuretool: Error: {}".format(msg), file=sys.stderr)
  26. sys.exit(1)
  27. def warn(msg):
  28. print("opp_featuretool: Warning: {}".format(msg), file=sys.stderr)
  29. def queryYesNo(question, default="yes"):
  30. """Ask a yes/no question via raw_input() and return their answer.
  31. "question" is a string that is presented to the user.
  32. "default" is the presumed answer if the user just hits <Enter>.
  33. It must be "yes" (the default), "no" or None (meaning
  34. an answer is required of the user).
  35. The "answer" return value is True for "yes" or False for "no".
  36. """
  37. valid = {"yes": True, "y": True, "ye": True,
  38. "no": False, "n": False}
  39. if default is None:
  40. prompt = " [y/n] "
  41. elif default == "yes":
  42. prompt = " [Y/n] "
  43. elif default == "no":
  44. prompt = " [y/N] "
  45. else:
  46. raise ValueError("invalid default answer: '%s'" % default)
  47. while True:
  48. sys.stdout.write(question + prompt)
  49. sys.stdout.flush()
  50. choice = raw_input().lower()
  51. if default is not None and choice == '':
  52. return valid[default]
  53. elif choice in valid:
  54. return valid[choice]
  55. else:
  56. sys.stdout.write("Please respond with 'yes' or 'no' "
  57. "(or 'y' or 'n').\n")
  58. ############################
  59. class Feature:
  60. def __init__(self, feature, ord):
  61. self.id = feature.getAttribute("id")
  62. self.name = feature.getAttribute("name")
  63. self.description = feature.getAttribute("description")
  64. self.initiallyEnabled = True
  65. if feature.hasAttribute("initiallyEnabled"):
  66. self.initiallyEnabled = feature.getAttribute("initiallyEnabled") == 'true'
  67. self.requires = feature.getAttribute("requires").split()
  68. self.labels = feature.getAttribute("labels").split()
  69. self.nedPackages = feature.getAttribute("nedPackages").split()
  70. self.extraSourceFolders = feature.getAttribute("extraSourceFolders").split()
  71. self.compileFlags = feature.getAttribute("compileFlags").split()
  72. self.linkerFlags = feature.getAttribute("linkerFlags").split()
  73. self.ord = ord
  74. ############################
  75. class FeatureState:
  76. def __init__(self, id, enabled, ord):
  77. self.id = id
  78. self.enabled = enabled
  79. self.ord = ord
  80. @classmethod
  81. def fromXML(cls, xmlelement, ord):
  82. return cls(xmlelement.getAttribute("id"), xmlelement.getAttribute("enabled").lower() == 'true', ord)
  83. def __repr__(self):
  84. return "<%s: id=%s, enabled=%s, ord=%d)" % (self.__class__.__name__, self.id, self.enabled, self.ord)
  85. ############################
  86. class NedFolder:
  87. def __init__(self, name, path, prefix, ord):
  88. self.name = name
  89. self.path = path
  90. self.prefix = prefix
  91. self.ord = ord
  92. def isSubpackage(self, subpkg):
  93. if self.prefix == '':
  94. return True
  95. return subpkg.startswith(self.prefix) and (len(subpkg) == len(self.prefix) or subpkg[len(self.prefix)] == '.')
  96. def __repr__(self):
  97. return "<%s: name=%s, path=%s, prefix=%s, ord=%d>" % (self.__class__.__name__, self.name, self.path, self.prefix, self.ord)
  98. ############################
  99. class FeatureTool:
  100. def __init__(self):
  101. self.fixingMode = False
  102. self.autoFixingMode = False
  103. self.errorOccurred = False
  104. self.fsChanged = False
  105. self.nedExclusionFileChanged = False
  106. self.nedfolders = []
  107. self.nedfoldersExcluded = []
  108. self.cppSourceRoots = []
  109. def printIfVerbose(self, verbosity, msg):
  110. if verbosity <= self.args.verbose:
  111. print(msg)
  112. def warnIfVerbose(self, verbosity, msg):
  113. if verbosity <= self.args.verbose:
  114. print("opp_featuretool: Warning: {}".format(msg), file=sys.stderr)
  115. def createParser(self):
  116. self.parser = argparse.ArgumentParser(description='''Turn project features on and off in an OMNeT++/OMNEST model. List the enablement
  117. of the features. Show the command line options needed to generarate a makefile
  118. when using the opp_makemake command. The tool must be executed in the project
  119. root directory where the following files are present:
  120. .oppfeatures, .oppfeaturestatus, .nedfolders, .nedexclusions''',
  121. formatter_class=argparse.RawDescriptionHelpFormatter)
  122. subparsers = self.parser.add_subparsers(help='', dest='command', metavar='COMMAND')
  123. # list command
  124. list_parser = subparsers.add_parser('list', help='List features')
  125. list_group = list_parser.add_mutually_exclusive_group()
  126. list_group.add_argument('-a', '--all', action='store_true',
  127. default=False,
  128. help='List all features (default)',
  129. )
  130. list_group.add_argument('-e', '--enabled', action='store_true',
  131. default=False,
  132. help='List enabled features',
  133. )
  134. list_group.add_argument('-d', '--disabled', action='store_true',
  135. default=False,
  136. help='List disabled features',
  137. )
  138. list_group.add_argument('-x', '--diff', action='store_true',
  139. default=False,
  140. help='List features not in default state',
  141. )
  142. # validate command
  143. validate_parser = subparsers.add_parser('validate', help='Validate feature enablements: Report inconsistencies and dependency problems in the '+
  144. FEATURESTATEFILE+' and '+NEDEXCLUSIONSFILE+' file.')
  145. # reset command
  146. reset_parser = subparsers.add_parser('reset', help='Reset all feature enablements to their default')
  147. # repair command
  148. repair_parser = subparsers.add_parser('repair', help='Repair feature enablements: missing features are added to the state file with the initiallyEnabled flag, non-existent features are removed from the '+FEATURESTATEFILE+' file')
  149. # enable command
  150. enable_parser = subparsers.add_parser('enable', help='Enable the specified features')
  151. enable_parser.add_argument('features', nargs='+',
  152. help='Enable the specified features, use \'all\' for all features',
  153. )
  154. enable_parser.add_argument('-f', '--with-dependencies', action='store_true',
  155. default=False,
  156. help='Enable all required features without asking for confirmation',
  157. )
  158. # disable command
  159. disable_parser = subparsers.add_parser('disable', help='Disable the specified features')
  160. disable_parser.add_argument('features', nargs='+',
  161. help='Disable the specified features, use \'all\' for all features',
  162. )
  163. disable_parser.add_argument('-f', '--with-dependencies', action='store_true',
  164. default=False,
  165. help='Disable all dependent features without asking for confirmation',
  166. )
  167. # options command:
  168. options_parser = subparsers.add_parser('options', help='Print opp_makemake command line arguments for creating a make file with the current feature enablement')
  169. options_parser.add_argument('-s', '--srcpath',
  170. help='Selects the source folder to print makemake options for. (The default is the first value in the cppSourceRoots attribute of the <features> tag in the FEATUREFILE.)',
  171. )
  172. options_parser.add_argument('-c', '--compiler-options', action='store_true', help='show compiler options (i.e. -D flags)')
  173. options_parser.add_argument('-l', '--linker-options', action='store_true', help='show linker options (i.e. -l and -L flags)')
  174. options_parser.add_argument('-f', '--folder-options', action='store_true', help='show excluded folders options (i.e. -X flags)')
  175. # defines command:
  176. defines_parser = subparsers.add_parser('defines', help='Print the compiler macros that can be used as a header file (i.e. the -DWITH_FEATURE options from the project features)')
  177. # isenabled command
  178. isenabled_parser = subparsers.add_parser('isenabled', help='Returns true if the specified feature(s) are enabled, and false otherwise.')
  179. isenabled_parser.add_argument('features', nargs='+',
  180. help='feature list',
  181. )
  182. # common arguments:
  183. self.parser.add_argument('-v', '--verbose', action='count', default=1, help='Verbose mode')
  184. self.parser.add_argument('-q', '--quiet', action='store_const', const=0, dest='verbose', help='Quiet mode')
  185. ############################
  186. def doResetCommand(self):
  187. # RESET command
  188. self.printIfVerbose(1, "Enablement of all project features reset to the default")
  189. self.featurestate = dict()
  190. for fk, feature in self.sortedFeatures:
  191. fs = FeatureState(feature.id, feature.initiallyEnabled, feature.ord)
  192. self.featurestate[fk] = fs
  193. self.writeFeatureState()
  194. self.nedfoldersExcluded = []
  195. for fk, feature in self.sortedFeatures:
  196. if not feature.initiallyEnabled:
  197. self.nedfoldersExcluded.extend(feature.nedPackages)
  198. self.writeNedExclusions()
  199. ############################
  200. def readFeatures(self):
  201. # read features xml file
  202. try:
  203. featurefile = open(FEATURESFILE, 'r')
  204. except IOError as e:
  205. fail("can't open {} file: {}".format(FEATURESFILE, e))
  206. try:
  207. DOMTree = xml.dom.minidom.parse(featurefile)
  208. featuresDom = DOMTree.documentElement
  209. self.cppSourceRoots = featuresDom.getAttribute("cppSourceRoots").split()
  210. featurelistDom = featuresDom.getElementsByTagName("feature")
  211. self.features = dict()
  212. ord = 0
  213. for featureDom in featurelistDom:
  214. feature = Feature(featureDom, ord)
  215. self.features[feature.id] = feature
  216. ord += 1
  217. except Exception as e:
  218. fail("cannot parse {} file: {}".format(FEATURESFILE, e))
  219. self.sortedFeatures = sorted(self.features.items(), key=lambda x:x[1].ord)
  220. featurefile.close()
  221. ############################
  222. def readNedFoldersFile(self):
  223. # read nedfolders file:
  224. self.nedfolders = []
  225. emptyline = re.compile(r'^\s*$')
  226. check = re.compile(r'^\-?([a-zA-Z0-9_]+\.)*[a-zA-Z0-9_]+$')
  227. if not os.path.isfile(NEDFOLDERSFILE):
  228. # warn("the "+NEDFOLDERSFILE+" file is missing.")
  229. nedfilename = './package.ned'
  230. prefix = self.getPrefixFromPackageNedFile(nedfilename)
  231. self.nedfolders.append(NedFolder('.', '.', prefix, 0))
  232. return
  233. try:
  234. nedfoldersfile = open(NEDFOLDERSFILE, 'r')
  235. except IOError as e:
  236. fail("can't open {} file: {}".format(NEDFOLDERSFILE, e))
  237. try:
  238. ord = 0
  239. for line in nedfoldersfile:
  240. ord += 1
  241. line = line.rstrip('\n')
  242. if emptyline.match(line):
  243. continue
  244. if not check.match(line):
  245. fail("invalid line at %s:%d : '%s'" % (NEDFOLDERSFILE, ord, line))
  246. elif line[0] == '-':
  247. pass # ignore omnet 4.x exclusion lines
  248. else:
  249. if line == '.':
  250. path = line
  251. else:
  252. path = line.replace('.', '/')
  253. prefix = ''
  254. nedfilename = path+'/package.ned'
  255. prefix = self.getPrefixFromPackageNedFile(nedfilename)
  256. self.nedfolders.append(NedFolder(line, path, prefix, ord))
  257. nedfoldersfile.close()
  258. except IOError as e:
  259. fail("error occurred when reading the {} file: {}".format(NEDFOLDERSFILE, e))
  260. ############################
  261. def getPrefixFromPackageNedFile(self, nedfilename):
  262. pkgchk = re.compile(r'^package\s+(([a-zA-Z0-9_]+\.)*[a-zA-Z0-9_]+);')
  263. if os.path.isfile(nedfilename):
  264. try:
  265. with open(nedfilename, 'r') as nedfile:
  266. for nedline in nedfile:
  267. m = pkgchk.match(nedline)
  268. if m:
  269. prefix = m.group(1)
  270. return prefix
  271. except IOError as e:
  272. fail("error reading {}: {}".format(nedfilename, e))
  273. return ''
  274. ############################
  275. def readNedExclusionsFile(self):
  276. # read nedexclusions file:
  277. self.nedfoldersExcluded = []
  278. if not os.path.isfile(NEDEXCLUSIONSFILE):
  279. # warn("the "+NEDEXCLUSIONSFILE+" file is missing.")
  280. return
  281. try:
  282. nedexclusionsfile = open(NEDEXCLUSIONSFILE, 'r')
  283. except IOError as e:
  284. fail(".nedexclusions file error: {}".format(e))
  285. check = re.compile(r'^([a-zA-Z0-9_]+\.)*[a-zA-Z0-9_]+$')
  286. try:
  287. ord = 0
  288. for line in nedexclusionsfile:
  289. ord += 1
  290. line = line.rstrip('\n')
  291. if not check.match(line):
  292. if self.fixingMode:
  293. warn("invalid line in %s:%d, removed: '%s'" % (NEDEXCLUSIONSFILE, ord, line))
  294. self.nedExclusionFileChanged = True
  295. elif self.autoFixingMode:
  296. warn("invalid line in %s:%d, ignored: '%s'" % (NEDEXCLUSIONSFILE, ord, line))
  297. else:
  298. fail("invalid line in %s:%d, '%s'" % (NEDEXCLUSIONSFILE, ord, line))
  299. else:
  300. self.nedfoldersExcluded.append(line)
  301. nedexclusionsfile.close()
  302. except IOError as e:
  303. fail("I/O error while reading the {} file: ({})".format(NEDEXCLUSIONSFILE, e))
  304. ############################
  305. def writeNedExclusions(self):
  306. try:
  307. nedexclusionsfile = open(NEDEXCLUSIONSFILE, 'w')
  308. for nf in sorted(self.nedfoldersExcluded):
  309. nedexclusionsfile.write("%s\n" % (nf))
  310. nedexclusionsfile.close()
  311. except IOError as e:
  312. fail("I/O error while writing the {} file: ({})".format(NEDEXCLUSIONSFILE, e))
  313. ############################
  314. def writeFeatureState(self):
  315. try:
  316. DOMTree = xml.dom.minidom.parseString("<featurestates/>")
  317. featurestateDom = DOMTree.documentElement
  318. for fk, fs in sorted(self.featurestate.items(), key=lambda x:x[1].ord):
  319. oneFS = DOMTree.createElement("feature")
  320. oneFS.setAttribute("id", fs.id)
  321. oneFS.setAttribute("enabled", str(fs.enabled).lower())
  322. featurestateDom.appendChild(oneFS)
  323. fsFile = open(FEATURESTATEFILE, 'w')
  324. DOMTree.writexml(fsFile, addindent=" ", newl="\n")
  325. fsFile.close()
  326. except IOError as e:
  327. fail("error occurred when writing {} file: {}".format(FEATURESTATEFILE, e))
  328. ############################
  329. def readFeatureState(self):
  330. # read featurestate xml file
  331. self.featurestate = dict()
  332. if not os.path.isfile(FEATURESTATEFILE):
  333. if (self.fixingMode or self.autoFixingMode):
  334. # warn("the .featurestate file does not exist. Using defaults.")
  335. # generate default featurestate
  336. for fk, feature in self.sortedFeatures:
  337. fs = FeatureState(feature.id, feature.initiallyEnabled, feature.ord)
  338. self.featurestate[fk] = fs
  339. if self.fixingMode:
  340. self.fsChanged = True
  341. else:
  342. fail("the "+FEATURESTATEFILE+" file does not exist.")
  343. else:
  344. try:
  345. fsFile = open(FEATURESTATEFILE, 'r')
  346. except IOError as e:
  347. fail("I/O error while reading the {} file: {}".format(FEATURESTATEFILE, e))
  348. if os.stat(FEATURESTATEFILE).st_size == 0:
  349. return
  350. try:
  351. DOMTree = xml.dom.minidom.parse(fsFile)
  352. except Exception as e:
  353. fail("cannot parse {} file: {}, to fix it: repair the file by hand, or delete {}".format(FEATURESTATEFILE, e, FEATURESTATEFILE))
  354. fsFile.close()
  355. featurestateDom = DOMTree.documentElement
  356. featurestatelistDom = featurestateDom.getElementsByTagName("feature")
  357. xord = len(self.features) + 1000
  358. for featureDom in featurestatelistDom:
  359. featureState = FeatureState.fromXML(featureDom, xord)
  360. if featureState.id in self.features:
  361. featureState.ord = self.features[featureState.id].ord
  362. elif self.fixingMode:
  363. warn(""+FEATURESTATEFILE+": Line '" + featureDom.toxml() + "' contains the unknown feature '" + featureState.id + "', removed")
  364. self.fsChanged = True
  365. xord += 1
  366. continue
  367. else:
  368. fail(""+FEATURESTATEFILE+": Line '" + featureDom.toxml() + "' contains the unknown feature '" + featureState.id + "'")
  369. if featureState.id in self.featurestate:
  370. if self.fixingMode:
  371. warn(""+FEATURESTATEFILE+": Line '" + featureDom.toxml() + "' contains the same feature more than one time '" + featureState.id + "', removed")
  372. self.fsChanged = True
  373. continue
  374. else:
  375. fail(""+FEATURESTATEFILE+": Line '" + featureDom.toxml() + "' contains the same feature more than one time '" + featureState.id + "'")
  376. else:
  377. self.featurestate[featureState.id] = featureState
  378. ############################
  379. def isCxxSourceFolder(self, folder):
  380. if len(self.cppSourceRoots) == 0:
  381. return True
  382. for cppSourceRoot in self.cppSourceRoots:
  383. if folder.startswith(cppSourceRoot) and (len(folder) == len(cppSourceRoot) or folder[len(cppSourceRoot)] == '/'):
  384. return True;
  385. return False;
  386. ############################
  387. def getNedBasedCxxSourceFolders(self, feature):
  388. result = []
  389. for nedPackage in feature.nedPackages:
  390. for nedfolder in self.nedfolders:
  391. if nedfolder.isSubpackage(nedPackage):
  392. packageSuffix = nedPackage[len(nedfolder.prefix):]
  393. folder = nedfolder.path + '/' + packageSuffix.replace('.', '/')
  394. if (os.path.exists(folder) and self.isCxxSourceFolder(folder)):
  395. result.append(folder)
  396. return result
  397. ############################
  398. def checkFeatureNedFolders(self, feature):
  399. retval = True
  400. for nedPackage in feature.nedPackages:
  401. foundNedPackageFolder = False
  402. for nedfolder in self.nedfolders:
  403. if nedfolder.isSubpackage(nedPackage):
  404. packageSuffix = nedPackage[len(nedfolder.prefix):]
  405. folder = nedfolder.path + '/' + packageSuffix.replace('.', '/')
  406. if os.path.exists(folder):
  407. foundNedPackageFolder = True
  408. if not foundNedPackageFolder:
  409. print("opp_featuretool: Error: NED package '{}' in feature '{}' was not found.".format(nedPackage, feature.id), file=sys.stderr)
  410. retval = False
  411. return retval
  412. ############################
  413. def verifyFeaturesNedFolders(self):
  414. ok = True
  415. for fid, feature in self.sortedFeatures:
  416. if not self.checkFeatureNedFolders(feature):
  417. ok = False
  418. if not ok:
  419. fail("Check whether all NED folders are set properly (in the {} file) and all directories corresponding to the NED packages defined in the {} file do exist.".format(NEDFOLDERSFILE, FEATURESFILE))
  420. ############################
  421. def doListCommand(self):
  422. self.doValidateCommand()
  423. # LIST command #
  424. if self.args.enabled:
  425. categ = "enabled "
  426. self.printIfVerbose(2, "List of enabled features:")
  427. elif self.args.disabled:
  428. categ = "disabled "
  429. self.printIfVerbose(2, "List of disabled features:")
  430. elif self.args.diff:
  431. categ = "changed "
  432. self.printIfVerbose(2, "List of changed features:")
  433. else:
  434. categ = ""
  435. self.printIfVerbose(2, "List of all features:")
  436. self.args.all = True
  437. cnt = 0
  438. for key, fs in sorted(self.featurestate.items(), key=lambda x:x[1].ord):
  439. if not fs.id in self.features:
  440. fail("unknown %s '%s' feature (not found in .oppfeature file)" % ('enabled' if fs.enabled else 'disabled', fs.id))
  441. elif self.args.all or (self.args.enabled and fs.enabled) or (self.args.disabled and not fs.enabled) or (self.args.diff and fs.enabled != self.features[fs.id].initiallyEnabled):
  442. print(" %s %s" % ('+' if fs.enabled else '-', fs.id))
  443. cnt += 1
  444. for key, fi in self.sortedFeatures:
  445. if not key in self.featurestate:
  446. fail("feature '{}' is missing from the {} file".format(key, FEATURESTATEFILE))
  447. self.printIfVerbose(2, "{} {}feature(s) found.".format(cnt, categ))
  448. ############################
  449. def updateRequirementsOf(self, featureid, featureOnList, requirements):
  450. for req in self.features[featureid].requires:
  451. if not self.featurestate[req].enabled:
  452. if not req in requirements and not req in featureOnList:
  453. self.updateRequirementsOf(req, featureOnList, requirements)
  454. requirements.add(req)
  455. ############################
  456. def updateRequirements(self, featureOnList, requirements):
  457. for featureid in featureOnList:
  458. self.updateRequirementsOf(featureid, featureOnList, requirements)
  459. ############################
  460. def doEnableCommand(self):
  461. self.doValidateCommand()
  462. # ENABLE command #
  463. if 'all' in self.args.features:
  464. if len(self.args.features) > 1:
  465. fail("'all' should not be used while individual features are mentioned on the command line.")
  466. else:
  467. for key,fs in self.sortedFeatures:
  468. if not self.featurestate[key].enabled:
  469. self.fsChanged = True
  470. self.featurestate[key].enabled = True
  471. else:
  472. featureOnList = set()
  473. requirements = set()
  474. for key in self.args.features:
  475. if not key in self.features:
  476. fail("unknown feature '%s'." % (key))
  477. if not self.featurestate[key].enabled:
  478. featureOnList.add(key)
  479. self.updateRequirements(featureOnList, requirements)
  480. update = True
  481. if len(featureOnList) == 0:
  482. self.printIfVerbose(1, "Feature(s) are already enabled.")
  483. sys.exit(0)
  484. self.printIfVerbose(1, "Enabling feature(s): " + ", ".join(featureOnList))
  485. if len(requirements):
  486. if self.args.with_dependencies:
  487. self.printIfVerbose(1, "Required features that are enabled, too: " + ", ".join(sorted(requirements)))
  488. else:
  489. print("Required features: " + ", ".join(sorted(requirements)))
  490. update = queryYesNo("Enable these features?", default="yes")
  491. if update:
  492. featureOnList.update(requirements)
  493. for key in featureOnList:
  494. self.featurestate[key].enabled = True
  495. self.fsChanged = True
  496. ############################
  497. def updateDependsOf(self, featureid, featureOffList, dependencyList):
  498. for fk,f in self.sortedFeatures:
  499. if (featureid in f.requires) and self.featurestate[f.id].enabled and (not (f.id in dependencyList)) and (not (f.id in featureOffList)):
  500. self.updateDependsOf(f.id, featureOffList, dependencyList)
  501. dependencyList.add(f.id)
  502. ############################
  503. def updateDependencies(self, featureOffList, dependencyList):
  504. for featureid in featureOffList:
  505. self.updateDependsOf(featureid, featureOffList, dependencyList)
  506. ############################
  507. def doDisableCommand(self):
  508. self.doValidateCommand()
  509. # DISABLE command #
  510. if 'all' in self.args.features:
  511. if len(self.args.features) > 1:
  512. fail("'all' should not be used while individual features are mentioned on the command line.")
  513. else:
  514. for key,fs in self.sortedFeatures:
  515. if self.featurestate[key].enabled:
  516. self.featurestate[key].enabled = False
  517. self.fsChanged = True
  518. else:
  519. featureOffList = set()
  520. dependencyList = set()
  521. for key in self.args.features:
  522. if not key in self.features:
  523. fail("unknown feature '%s'." % (key))
  524. if self.featurestate[key].enabled:
  525. featureOffList.add(key)
  526. self.updateDependencies(featureOffList, dependencyList)
  527. update = True
  528. if len(featureOffList) == 0:
  529. self.printIfVerbose(1, "All the selected features are already disabled.")
  530. sys.exit(0)
  531. self.printIfVerbose(1, "Disabling feature(s): " + ", ".join(featureOffList))
  532. if len(dependencyList):
  533. if self.args.with_dependencies:
  534. self.printIfVerbose(1, "Dependent features that are disabled, too: " + ", ".join(sorted(dependencyList)))
  535. else:
  536. print("Dependent features: " + ", ".join(sorted(dependencyList)))
  537. update = queryYesNo("Disable these features, too?", default="yes")
  538. if update:
  539. featureOffList.update(dependencyList)
  540. for key in featureOffList:
  541. self.featurestate[key].enabled = False
  542. self.fsChanged = True
  543. ############################
  544. def doRepairCommand(self):
  545. # "creation of default FEATURESTATEFILE file when it missing" for 'prepare' implemented in readFeatureState()
  546. self.doValidateCommand()
  547. ############################
  548. def doValidateCommand(self):
  549. # "syntax check in FEATURESTATEFILE file" for 'validate' implemented in readFeatureState()
  550. featureMissed = False
  551. # check feature existing in FEATURESTATEFILE file:
  552. for fid, feature in self.sortedFeatures:
  553. if not fid in self.featurestate:
  554. featureMissed = True
  555. fs = FeatureState(feature.id, feature.initiallyEnabled, feature.ord)
  556. self.featurestate[fid] = fs
  557. if self.fixingMode:
  558. warn("feature '%s' is missing from the %s file, adding it with default state." % (fid, FEATURESTATEFILE))
  559. self.fsChanged = True
  560. elif self.autoFixingMode:
  561. self.warnIfVerbose(2, "feature '%s' is missing from the %s file, using the default state." % (fid, FEATURESTATEFILE))
  562. else:
  563. fail("feature '%s' is missing from the %s file." % (fid, FEATURESTATEFILE))
  564. # check dependencies:
  565. dependencyErrorOccurred = False
  566. featureOnList = set()
  567. requirements = set()
  568. for fid, feature in self.sortedFeatures:
  569. if self.featurestate[fid].enabled:
  570. for r in feature.requires:
  571. if not self.featurestate[r].enabled:
  572. warn("feature '%s' is required for '%s', but it is disabled." % (r, fid))
  573. featureOnList.add(r)
  574. dependencyErrorOccurred = True
  575. if len(featureOnList):
  576. self.updateRequirements(featureOnList, requirements)
  577. if len(requirements):
  578. warn("these features are also required: " + ", ".join(requirements))
  579. featureOnList.update(requirements)
  580. nedfolderErrorOccurred = False
  581. for fid, feature in self.sortedFeatures:
  582. for np in feature.nedPackages:
  583. if self.featurestate[fid].enabled:
  584. if np in self.nedfoldersExcluded:
  585. if not (self.autoFixingMode or self.fixingMode):
  586. warn("NED package '%s' is part of the enabled feature '%s', but it is excluded." % (np, fid))
  587. nedfolderErrorOccurred = True
  588. else:
  589. if not np in self.nedfoldersExcluded:
  590. if not (self.autoFixingMode or self.fixingMode):
  591. warn("NED package '%s' is part of the disabled feature '%s', but it is not excluded." % (np, fid))
  592. nedfolderErrorOccurred = True
  593. if dependencyErrorOccurred:
  594. if self.fixingMode:
  595. for key in featureOnList:
  596. self.featurestate[key].enabled = True
  597. self.fsChanged = True
  598. else:
  599. fail("feature dependency error(s) occurred.") #FIXME what occurred???
  600. if nedfolderErrorOccurred:
  601. if self.fixingMode:
  602. self.fsChanged = True
  603. elif not self.autoFixingMode:
  604. fail("feature dependency error(s) found in the "+NEDEXCLUSIONSFILE+" file.") #FIXME what kind of errors???
  605. ############################
  606. def doOptionsCommand(self):
  607. self.doValidateCommand()
  608. if self.args.srcpath == None:
  609. self.args.srcpath = self.cppSourceRoots[0]
  610. elif self.args.srcpath not in self.cppSourceRoots:
  611. fail("the selected '%s' source path is not specified in the '%s' file. Choose any of ['%s']." % (self.args.srcpath, FEATURESFILE, "','".join(self.cppSourceRoots)))
  612. if (not (self.args.compiler_options or self.args.folder_options or self.args.linker_options)):
  613. # enable all
  614. self.args.compiler_options = True
  615. self.args.folder_options = True
  616. self.args.linker_options = True
  617. extraSourceFolders = []
  618. excludedExtraSourceFolders = []
  619. excludedSourceFolders = []
  620. compileFlags = []
  621. linkerFlags = []
  622. for fid,feature in self.sortedFeatures:
  623. if self.featurestate[fid].enabled:
  624. extraSourceFolders.extend(feature.extraSourceFolders)
  625. compileFlags.extend(feature.compileFlags)
  626. linkerFlags.extend(feature.linkerFlags)
  627. else:
  628. excludedExtraSourceFolders.extend(feature.extraSourceFolders)
  629. excludedSourceFolders.extend(self.getNedBasedCxxSourceFolders(feature))
  630. extraSourceFolders = ["-d"+x for x in extraSourceFolders]
  631. excludedSrcFolders = []
  632. s = self.args.srcpath+'/'
  633. for f in excludedSourceFolders + excludedExtraSourceFolders:
  634. if f.startswith(s):
  635. excludedSrcFolders.append("-X"+f[len(s):])
  636. flags = []
  637. if self.args.folder_options:
  638. flags.extend(sorted(excludedSrcFolders)) # sort the excluded list so it will match the command line generated by the IDE
  639. if self.args.compiler_options:
  640. flags.extend(compileFlags)
  641. if self.args.linker_options:
  642. flags.extend(linkerFlags)
  643. print(" ".join(flags))
  644. ############################
  645. def doDefinesCommand(self):
  646. self.doValidateCommand()
  647. print("//")
  648. print("// Generated file, do not edit!")
  649. print("//")
  650. print("// This file defines symbols contributed by the currently active project features,")
  651. print("// and it is regenerated every time a project feature is enabled or disabled.")
  652. print("// See the Project Features dialog in the IDE, and opp_featuretool.")
  653. print("//")
  654. compileFlags = []
  655. for fid,feature in self.sortedFeatures:
  656. if self.featurestate[fid].enabled:
  657. compileFlags.extend(feature.compileFlags)
  658. defline = re.compile(r'^-D([a-zA-Z0-9_]+)$')
  659. deflineeq = re.compile(r'^-D([a-zA-Z0-9_]+)=(.*)$')
  660. flags = sorted(" ".join(compileFlags).split(" "));
  661. for flag in flags:
  662. matchObj = defline.match(flag)
  663. if matchObj:
  664. print("#ifndef {}\n#define {}\n#endif\n".format(matchObj.group(1), matchObj.group(1)))
  665. continue
  666. matchObj = deflineeq.match(flag)
  667. if matchObj:
  668. print("#ifndef {}\n#define {} {}\n#endif\n".format(matchObj.group(1), matchObj.group(1), matchObj.group(2)))
  669. continue
  670. ############################
  671. def doIsEnabledCommand(self):
  672. self.doValidateCommand()
  673. # ISENABLED command #
  674. featureOnList = set()
  675. featureUnknownList = set()
  676. for key in self.args.features:
  677. if not key in self.features:
  678. featureUnknownList.add(key)
  679. elif not self.featurestate[key].enabled:
  680. featureOnList.add(key)
  681. if len(featureUnknownList) > 0:
  682. fail("Unknown feature(s): {}.".format((", ".join(sorted(featureUnknownList)))))
  683. if len(featureOnList) > 0:
  684. if self.args.verbose >= 2:
  685. print("Disabled feature(s): {}.".format((", ".join(sorted(featureOnList)))), file=sys.stderr)
  686. sys.exit(1)
  687. self.printIfVerbose(1, "Feature(s) {} are enabled.".format((", ".join(self.args.features))))
  688. sys.exit(0)
  689. ############################
  690. def run(self):
  691. self.errorOccurred = False
  692. self.fsChanged = False
  693. self.features = dict()
  694. self.createParser()
  695. try:
  696. self.args = self.parser.parse_args()
  697. except IOError as e:
  698. fail("{}".format(e))
  699. self.autoFixingMode = (self.args.command != "validate")
  700. self.fixingMode = (self.args.command == "repair")
  701. # read nedfolders file:
  702. self.readNedFoldersFile()
  703. # read feature file
  704. self.readFeatures()
  705. self.verifyFeaturesNedFolders()
  706. if self.args.command == 'reset':
  707. self.doResetCommand()
  708. return
  709. # Read featurestate file #
  710. self.readFeatureState()
  711. # read nedexclusions file:
  712. self.readNedExclusionsFile()
  713. if self.args.command == 'list':
  714. self.doListCommand()
  715. elif self.args.command == 'validate':
  716. self.doValidateCommand()
  717. elif self.args.command == 'repair':
  718. self.doRepairCommand()
  719. elif self.args.command == 'reset':
  720. fail("reset command already processed") #FIXME assert(false) # reset command already processed
  721. elif self.args.command == 'enable':
  722. self.doEnableCommand()
  723. elif self.args.command == 'disable':
  724. self.doDisableCommand()
  725. elif self.args.command == 'options':
  726. self.doOptionsCommand()
  727. elif self.args.command == 'defines':
  728. self.doDefinesCommand()
  729. elif self.args.command == 'isenabled':
  730. self.doIsEnabledCommand()
  731. else:
  732. fail("unknown command '%s'" % self.args.command)
  733. if self.fsChanged:
  734. self.writeFeatureState()
  735. self.printIfVerbose(1, "opp_featuretool: "+FEATURESTATEFILE+" file updated.")
  736. self.nedfoldersExcluded = []
  737. for key, fs in sorted(self.featurestate.items(), key=lambda x:x[1].ord):
  738. enabled = False
  739. if fs.id in self.features and not fs.enabled:
  740. self.nedfoldersExcluded.extend(self.features[fs.id].nedPackages)
  741. self.nedExclusionFileChanged = True
  742. if self.nedExclusionFileChanged:
  743. self.writeNedExclusions()
  744. self.printIfVerbose(1, "opp_featuretool: "+NEDEXCLUSIONSFILE+" file updated.")
  745. if self.errorOccurred:
  746. fail("an error ocurred")
  747. #####################################################
  748. tool = FeatureTool()
  749. tool.run()