1############################################################################### 2# Copyright (c) 2013 INRIA 3# 4# This program is free software; you can redistribute it and/or modify 5# it under the terms of the GNU General Public License version 2 as 6# published by the Free Software Foundation; 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16# 17# Authors: Daniel Camara <daniel.camara@inria.fr> 18# Mathieu Lacage <mathieu.lacage@sophia.inria.fr> 19############################################################################### 20''' 21 Configuration.py 22 23 The main purpose of this file is to store all the classes related 24 to the configuration of Bake. 25''' 26 27import os 28import re 29import sys 30import xml.etree.ElementTree as ET 31try: 32 from xml.etree.ElementTree import ParseError 33except ImportError: 34 from xml.parsers.expat import ExpatError as ParseError 35from bake.Module import Module, ModuleDependency 36from bake.ModuleSource import ModuleSource, InlineModuleSource 37from bake.ModuleBuild import ModuleBuild, InlineModuleBuild 38from bake.Exceptions import MetadataError 39from bake.Exceptions import TaskError 40 41class MetadataFile: 42 """Stores the meta information of a given file.""" 43 44 def __init__(self, filename, h=''): 45 self._filename = os.path.realpath(filename) 46 self._h = h 47 48 def filename(self): 49 return self._filename 50 51 def h(self): 52 import hashlib 53 m = hashlib.sha256() 54 try: 55 f = open(self._filename) 56 m.update(f.read()) 57 f.close() 58 return m.hexdigest() 59 except: 60 return '' 61 62 def is_hash_ok(self): 63 """Verifies if the hash of the configuration file is OK, to avoid 64 manual and transmission changes. 65 """ 66 67 return self.h() == self._h 68 69class PredefinedConfiguration: 70 """Stores the information of predefined options.""" 71 72 def __init__(self, name, enable, disable, variables_set, variables_append, 73 directories): 74 self.name = name 75 self.enable = enable 76 self.disable = disable 77 self.variables_set = variables_set 78 self.variables_append = variables_append 79 self.directories = directories 80 81class Configuration: 82 """Main configuration class.""" 83 84 def __init__(self, bakefile, relative_directory_root=None): 85 self._enabled = [] 86 self._disabled = [] 87 self._modules = [] 88 self._configured = [] 89 self._installdir = None 90 self._objdir = None 91 self._sourcedir = None 92 self._metadata_file = None 93# self._bakefile = os.path.abspath(bakefile) 94 if bakefile.startswith(os.sep): 95 self._bakefile = os.path.abspath(bakefile) 96 else: 97 self._bakefile = os.getcwd()+os.sep+bakefile 98 if relative_directory_root is None: 99 self._relative_directory_root = os.path.relpath(os.getcwd(), 100 os.path.dirname(self._bakefile)) 101 else: 102 self._relative_directory_root = relative_directory_root 103 104 def read_metadata(self, filename): 105 """ Reads the list of meta-data defined in the XML config file""" 106 107 if not os.path.exists(filename): 108 self._error('Could not find "%s"' % filename) 109 110 self._metadata_file = MetadataFile(filename) 111 et = ET.parse(filename) 112 self._read_metadata(et) 113 114 def read_predefined(self, filename): 115 """ Creates the list of predefined entries defined in the XML 116 configuration file 117 """ 118 predefined = [] 119 120 try: 121 et = ET.parse(filename) 122 except ParseError: 123 return predefined 124 125 for pred_node in et.getroot().findall('predefined'): 126 name = pred_node.get('name', None) 127 128 if not name: 129 self._error('<predefined> must define a "name" attribute.') 130 131 enable = [] 132 for enable_node in pred_node.findall('enable'): 133 enable_name = enable_node.get('name', None) 134 135 if not enable_name: 136 self._error('<enable> must define a "name" attribute.') 137 enable.append(enable_name) 138 139 disable = [] 140 for disable_node in pred_node.findall('disable'): 141 disable_name = disable_node.get('name', None) 142 143 if not disable_name: 144 self._error('<disable> must define a "name" attribute.') 145 disable.append(disable_name) 146 147 variables_set = [] 148 for set_node in pred_node.findall('set'): 149 set_name = set_node.get('name', None) 150 set_value = set_node.get('value', None) 151 set_module = set_node.get('module', None) 152 153 if not set_name or not set_value: 154 self._error('<set> must define a "name" and a "value" attribute.') 155 variables_set.append((set_module, set_name, set_value)) 156 157 variables_append = [] 158 for append_node in pred_node.findall('append'): 159 append_name = append_node.get('name', None) 160 append_value = append_node.get('value', None) 161 append_module = append_node.get('module', None) 162 163 if not append_name or not append_value: 164 self._error('<append> must define a "name" and a "value" attribute.') 165 variables_append.append((append_module, append_name, append_value)) 166 167 directories = {} 168 for config_node in pred_node.findall('configuration'): 169 objdir = config_node.get('objdir', None) 170 installdir = config_node.get('installdir', None) 171 sourcedir = config_node.get('sourcedir', None) 172 173 if objdir: 174 directories['objdir'] = objdir 175 176 if installdir: 177 directories['installdir'] = installdir 178 179 if sourcedir: 180 directories['sourcedir'] = sourcedir 181 182 predefined.append(PredefinedConfiguration(name, enable, disable, 183 variables_set, 184 variables_append, 185 directories)) 186 return predefined 187 188 def _error(self, string): 189 """ Handles the exceptions """ 190 raise Exception(string) 191 192 def _check_mandatory_attributes(self, attribute_base, node, type_string, 193 module_string): 194 """ Checks the existence of the mandatory attributes for each 195 configuration. 196 """ 197 198 # get list of names in <attribute name="" value=""> tags 199 attributes_present = [child.get('name') for child in node.findall('attribute')] 200 # get list of names in <type_string name="value"> attributes 201 attributes_present = attributes_present + list(node.attrib) 202 203 for attribute in attribute_base.attributes(): 204 if attribute.is_mandatory and not attribute.name in attributes_present: 205 sys.stderr.write('Error: mandatory attribute "%s" is missing from ' 206 'module "%s" in node "%s"\n' % (attribute.name, 207 module_string, 208 type_string)) 209 sys.exit(1) 210 211 def _read_attributes(self, obj, node, type_string, module_string): 212 """ Reads the list of attributes on the configuration configuration.""" 213 214 # read <type_string><attribute name="" value=""></type_string> tags 215 for attribute_node in node.findall('attribute'): 216 attr_name = attribute_node.get('name') 217 attr_value = attribute_node.get('value', None) 218 if obj.attribute(attr_name) is None: 219 sys.stderr.write('Error: attribute "%s" is not supported by' 220 ' %s node of type "%s"\n' % 221 (attr_name, type_string, node.get('type'))) 222 sys.exit(1) 223 obj.attribute(attr_name).value = attr_value 224 225 # as a fallback, read <type_string name="value"> attributes 226 # note: this will not generate errors upon invalid attribute names 227 # because certain kinds of <foo name="value"/> XML attributes are 228 # not handled as bake attributes. 229 for attr_name in node.attrib.keys(): 230 if not obj.attribute(attr_name) is None: 231 obj.attribute(attr_name).value = node.get(attr_name) 232 233 def _write_attributes(self, attribute_base, obj_node): 234 """ Creates the XML elements, reflecting the listed attributes.""" 235 236 # generate <attribute name="" value=""/> tags 237 for attribute in attribute_base.attributes(): 238 if not attribute.value is None: 239 attribute_node = ET.Element('attribute', {'name' : attribute.name, 240 'value' : attribute.value}) 241 obj_node.append(attribute_node) 242 243 def _create_obj_from_node(self, node, classBase, node_string, module_name): 244 """ Translates the XML elements on the correct bake object.""" 245 246 # read <node_string type=""> tag: handle type="inline" specially by 247 # looking up a child node <code></code> 248 if node.get('type') == 'inline': 249 code_node = node.find('code') 250 if code_node is None: 251 sys.stderr.write('Error: no code tag in inline module\n') 252 sys.exit(1) 253 254 classname = node.get('classname') 255 import codeop 256 exec(code_node.text, globals(), locals()) 257 obj = eval(classname + '()') 258 obj.__hidden_source_code = code_node.text 259 else: 260 obj = classBase.create(node.get('type')) 261 262 self._check_mandatory_attributes(obj, node, node_string, module_name) 263 self._read_attributes(obj, node, node_string, module_name) 264 265 # if <type_string> has <child> nodes, look them up. 266 for child_node in node.findall('child'): 267 child_name = child_node.get('name') 268 child = self._create_obj_from_node(child_node, classBase, 'child', 269 module_name) 270 obj.add_child(child, child_name) 271 272 return obj 273 274 def _create_node_from_obj(self, obj, node_string): 275 """ Generates the XML node based on the XML object passed as parameter""" 276 277 # inline is when one uses Python as build configuration to create a 278 # small build script 279 if obj.__class__.name() == 'inline': 280 node = ET.Element(node_string, {'type' : 'inline', 281 'classname' : obj.__class__.__name__}) 282 code = ET.Element('code') 283 code.text = obj.__hidden_source_code 284 node.append(code) 285 else: 286 node = ET.Element(node_string, {'type' : obj.__class__.name()}) 287 288 self._write_attributes(obj, node) 289 290 for child, child_name in obj.children(): 291 child_node = self._create_node_from_obj(child, 'child') 292 child_node.attrib['name'] = child_name 293 node.append(child_node) 294 295 return node 296 297 def _read_installed(self, node): 298 """ Reads the installed modules from the XML.""" 299 300 installed = [] 301 for installed_node in node.findall('installed'): 302 installed.append(installed_node.get('value', None)) 303 return installed 304 305 def _write_installed(self, node, installed): 306 """ Generates the XML nodes to register the installed modules.""" 307 308 for installed in installed: 309 installed_node = ET.Element('installed', {'value' : installed}) 310 node.append(installed_node) 311 312 313 def _read_metadata(self, et): 314 """ Reads the elements from the xml configuration files and add it to 315 the internal list of modules. 316 """ 317 318 # function designed to be called on two kinds of xml files. 319 modules = et.findall('modules/module') 320 for module_node in modules: 321 name = module_node.get('name') 322 mtype = module_node.get('type') 323 min_ver = module_node.get('min_version') 324 max_ver = module_node.get('max_version') 325 installed = self._read_installed(module_node) 326 327 source_node = module_node.find('source') 328 source = self._create_obj_from_node(source_node, ModuleSource, 329 'source', name) 330 331 build_node = module_node.find('build') 332 build = self._create_obj_from_node(build_node, ModuleBuild, 333 'build', name) 334# self._read_libpath(build_node, build) 335 336 dependencies = [] 337 for dep_node in module_node.findall('depends_on'): 338 dependencies.append(self._create_obj_from_node(dep_node,ModuleDependency,'depends_on',name)) 339 340 module = Module(name, source, build, mtype, min_ver, max_ver, dependencies=dependencies, 341 built_once=bool(module_node.get('built_once', '').upper()=='TRUE'), 342 installed=installed) 343 self._modules.append(module) 344 345 def _write_metadata(self, root): 346 """ Saves modules data to the XML configuration file.""" 347 348 modules_node = ET.Element('modules') 349 root.append(modules_node) 350 351 for module in self._modules: 352 module_attrs = {'name' : module.name()} 353 if module.mtype(): 354 module_attrs['type'] = module.mtype() 355 if module.minver(): 356 module_attrs['min_version'] = module.minver() 357 if module.is_built_once(): 358 module_attrs['built_once'] = 'True' 359 module_node = ET.Element('module', module_attrs) 360 self._write_installed(module_node, module.installed) 361 362 # registers the values, possible changed ones, from the source and 363 # build XML tags of each module 364 source_node = self._create_node_from_obj(module.get_source(), 365 'source') 366 module_node.append(source_node) 367 368 build_node = self._create_node_from_obj(module.get_build(), 'build') 369 module_node.append(build_node) 370# self._write_libpath(build_node, module.get_build()) 371 372 # handles the dependencies for the module and register them 373 # into module node 374 for dependency in module.dependencies(): 375 dep_node = self._create_node_from_obj(dependency, 'depends_on') 376 module_node.append(dep_node) 377 modules_node.append(module_node) 378 379 def defineXml(self): 380 """ Creates the basic XML structure for the configuration file.""" 381 382 root = ET.Element('configuration', {'installdir':self._installdir, 383 'sourcedir':self._sourcedir, 384 'objdir':self._objdir, 385 'relative_directory_root':self._relative_directory_root, 386 'bakefile':self._bakefile}) 387 388 if not self._metadata_file is None: 389 metadata = ET.Element('metadata', 390 {'filename':self._metadata_file.filename(), 391 'hash':self._metadata_file.h()}) 392 root.append(metadata) 393 394 # write enabled nodes 395 for e in self._enabled: 396 enable_node = ET.Element('enabled', {'name':e.name()}) 397 root.append(enable_node) 398 399 # write disabled nodes 400 for e in self._disabled: 401 disable_node = ET.Element('disabled', {'name':e.name()}) 402 root.append(disable_node) 403 404 # add modules information 405 self._write_metadata(root) 406 et = ET.ElementTree(element=root) 407 return et 408 409 def write(self): 410 """ Creates the target configuration XML file.""" 411 412 et = self.defineXml() 413 414 try: 415 et.write(self._bakefile) 416 except IOError as e: 417 raise TaskError('Problems writing the file, error: %s' % e) 418 419 def read(self): 420 """ Reads the XML customized configuration file.""" 421 422 try: 423 et = ET.parse(self._bakefile) 424 except IOError as e: 425 err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip() 426 raise TaskError('>> Problems reading the configuration file, verify if' 427 ' it exists or try calling bake.py configure. \n' 428 ' Error: %s' % err) 429 430 self._read_metadata(et) 431 root = et.getroot() 432 self._installdir = root.get('installdir') 433 self._objdir = root.get('objdir') 434 self._sourcedir = root.get('sourcedir') 435 self._relative_directory_root = root.get('relative_directory_root') 436 original_bakefile = root.get('bakefile') 437 metadata = root.find('metadata') 438 439 if metadata is not None: 440 self._metadata_file = MetadataFile (metadata.get('filename'), 441 h=metadata.get('hash')) 442 443 # read which modules are enabled 444 modules = root.findall('enabled') 445 for module in modules: 446 self._configured.append(self.lookup(module.get('name'))) 447 enabled = self.lookup(module.get('name')) 448 self.enable(enabled) 449 450 # read which modules are disabled 451 modules = root.findall('disabled') 452 for module in modules: 453 disabled = self.lookup(module.get('name')) 454 self.disable(disabled) 455 456 if metadata is not None: 457 return self._metadata_file.is_hash_ok() #and original_bakefile == self._bakefile 458 else : 459 return True 460 461 def set_installdir(self, installdir): 462 self._installdir = installdir 463 464 def get_installdir(self): 465 return self._installdir 466 467 def set_objdir(self, objdir): 468 self._objdir = objdir 469 470 def get_objdir(self): 471 return self._objdir 472 473 def set_sourcedir(self, sourcedir): 474 self._sourcedir = sourcedir 475 476 def get_sourcedir(self): 477 return self._sourcedir 478 479 def get_relative_directory_root(self): 480 return self._relative_directory_root 481 482 def _compute_path(self, p): 483 """Returns the full path""" 484 485 if os.path.isabs(p): 486 return p 487 else: 488 tmp = os.path.join(os.path.dirname(self._bakefile), 489 self._relative_directory_root, p) 490 return os.path.normpath(tmp) 491 492 def compute_sourcedir(self): 493 return self._compute_path(self._sourcedir) 494 495 def compute_installdir(self): 496 return self._compute_path(self._installdir) 497 498 def enable(self, module): 499 """ Set the module as enabled, but if it is disabled, simply removes 500 it from the disabled list. 501 """ 502 503 if module in self._disabled: 504 self._disabled.remove(module) 505 elif module not in self._enabled: 506 self._enabled.append(module) 507 508 def disable(self, module): 509 """ Set the module as disabled, but if it is enabled, simply removes 510 it from the enabled list. 511 """ 512 513 if module in self._enabled: 514 self._enabled.remove(module) 515 else: 516 self._disabled.append(module) 517 518 def lookup(self, name): 519 """ Finds the module in the modules list.""" 520 521 for module in self._modules: 522 if module.name() == name: 523 return module 524 return None 525 526 def enabled(self): 527 return self._enabled 528 529 def disabled(self): 530 return self._disabled 531 532 def modules(self): 533 return self._modules 534 535 def configured(self): 536 return self._configured 537