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 Module.py 22 23 This file stores the generic implementation of the bake options. e.g. how 24 the download works, independently of the technology/repository used to 25 store the code. 26''' 27 28import copy 29import os 30import re 31import sys 32import shutil 33 34from bake.FilesystemMonitor import FilesystemMonitor 35from bake.Exceptions import TaskError 36from bake.Utils import ColorTool 37from bake.Exceptions import NotImplemented 38from bake.ModuleSource import SystemDependency 39from bake.Utils import ModuleAttributeBase 40 41class ModuleDependency(ModuleAttributeBase): 42 """ Dependency information. """ 43 instances = [] 44 def __init__(self, name = '' , optional = False): 45 46 ModuleAttributeBase.__init__(self) 47 48 self.add_attribute('name', name, 'Name of the Module', mandatory=True) 49 self.add_attribute('optional', str(optional), 'Whether module is optional', mandatory=True) 50 51 self.__class__.instances.append(self) 52 53 @classmethod 54 def subclasses(self): 55 return ModuleDependency.__subclasses__() 56 57 def name(self): 58 return self._name 59 60 @classmethod 61 def name(cls): 62 return 'default' 63 64 @property 65 def _name(self): 66 return self.attribute('name').value 67 68 @property 69 def _optional(self): 70 return self.is_optional() 71 72 @classmethod 73 def create(cls, build_type): 74 for subclass in ModuleDependency.subclasses(): 75 if subclass.name() == build_type: 76 instance = subclass() 77 return instance 78 79 return ModuleDependency() 80 81 def is_optional(self): 82 return bool(self.attribute('optional').value.upper() == "TRUE") 83 84 def configure_arguments(self): 85 raise NotImplemented() 86 87 @classmethod 88 def lookup_obj(cl,name): 89 for instance in cl.instances: 90 if instance._name == name: 91 return instance 92 93 return None 94 95class WafModuleDependency(ModuleDependency): 96 97 def __init__(self): 98 99 ModuleDependency.__init__(self) 100 self.add_attribute('configure_arguments', '', 'Arguments to pass to' 101 ' "waf configure"') 102 103 @classmethod 104 def name(cls): 105 return 'waf' 106 107 def configure_arguments(self): 108 return self.attribute('configure_arguments').value 109 110class Module: 111 followOptional = None 112 FAIL = 0 113 OK = 1 114 115 def __init__(self, name, 116 source, 117 build, 118 mtype, 119 min_ver, 120 max_ver, 121 dependencies = [], 122 built_once = False, 123 installed = []): 124 self._name = name 125 self._type = mtype 126 self._dependencies = copy.copy(dependencies) 127 self._source = source 128 self._build = build 129 self._built_once = built_once 130 self._installed = installed 131 self._minVersion = min_ver 132 self._maxVersion = max_ver 133 134 135 @property 136 def installed(self): 137 """ Returns if the module was already installed or not. """ 138 return self._installed 139 @installed.setter 140 def installed(self, value): 141 """ Stores the given value on the module installed option. """ 142 self._installed = copy.copy(value) 143 144 def _directory(self): 145 return self._name 146 147 def handleStopOnError(self, e): 148 """Handles the stop on error parameter, prints the standard 149 message and calls exist.""" 150 colorTool = ColorTool() 151 colorTool.cPrintln(colorTool.OK, " > Stop on error enabled (for more information call bake with -vv or -vvv)") 152 colorTool.cPrintln(colorTool.FAIL, " >> " + e._reason) 153 os._exit(1) 154 155 def printResult(self, env, operation, result): 156 """Prints the result of the operation.""" 157 colorTool = ColorTool() 158 resultStr = "OK" 159 color=colorTool.OK 160 if result == self.FAIL: 161 resultStr = "Problem" 162 color=colorTool.FAIL 163 164 if env._logger._verbose > 0: 165 print() 166 colorTool.cPrintln(color, " >> " + operation + " " + 167 self._name + " - " +resultStr) 168 else: 169 colorTool.cPrintln(color, resultStr) 170 171 def _do_download(self, env, source, name, forceDownload): 172 """ Recursive download function, do the download for each 173 target module. """ 174 175 srcDirTmp = name 176 if source.attribute('module_directory').value : 177 srcDirTmp = source.attribute('module_directory').value 178 179 env.start_source(name, srcDirTmp) 180 rt = source.check_version(env) 181 182 if forceDownload: 183 try: # when forced download, removes the repository if it exists 184 if os.path.isdir(env.srcdir): 185 shutil.rmtree(env.srcdir) 186 except OSError as e: 187 env._logger.commands.write('Could not remove source files' 188 ' %s for module: %s \n Error: %s\n' % 189 (env.srcdir, env._module_name, 190 str(e))) 191 aditionalModule=False 192 if source.attribute('additional-module'): 193 aditionalModule = source.attribute('additional-module').value 194 195 if os.path.isdir(env.srcdir) and not aditionalModule : 196 colorTool = ColorTool() 197 if env._logger._verbose == 0: 198 colorTool.cPrint(colorTool.OK, "(Nothing to do, source directory already exists) - ") 199 else: 200 colorTool.cPrintln(colorTool.OK, " >>> No actions needed, the source directory for " + 201 self._name + " already exists.") 202 sys.stdout.write (" If you want to update the module, use update instead download, or, if you want a fresh copy," + os.linesep 203 +" either remove it from the source directory, or use the --force_download option.") 204 205 if self._source.attribute('new_variable').value != '': 206 elements = env.replace_variables(self._source.attribute('new_variable').value).split(";") 207 env.add_variables(elements) 208 209 210 env.end_source() 211 else: 212 try: 213 source.download(env) 214 if self._source.attribute('patch').value != '': 215 self._build.threat_patch(env, self._source.attribute('patch').value) 216 if self._source.attribute('new_variable').value != '': 217 elements = env.replace_variables(self._source.attribute('new_variable').value).split(";") 218 env.add_variables(elements) 219 if self._source.attribute('post_download').value != '': 220 self._source.perform_post_download(env) 221 finally: 222 env.end_source() 223 for child, child_name in source.children(): 224 self._do_download(env, child, os.path.join(name, child_name)) 225 226 def download(self, env, forceDownload): 227 """ General download function. """ 228 229 if self._build.attribute('supported_os').value : 230 if not self._build.check_os(self._build.attribute('supported_os').value) : 231 import platform 232 import distro 233 osName = platform.system().lower() 234 (distname,version,ids)=distro.linux_distribution() 235 print(' Downloading, but this module works only on \"%s\"' 236 ' platform(s), %s is not supported for %s %s %s %s' % 237 (self._build.attribute('supported_os').value, 238 self._name, platform.system(), distname,version,ids)) 239 240 try: 241 self._do_download(env, self._source, self._name, forceDownload) 242 243 if isinstance(self._source, SystemDependency): 244 self.printResult(env, "Dependency ", self.OK) 245 else: 246 self.printResult(env, "Download", self.OK) 247 248 249 return True 250 except TaskError as e: 251 if isinstance(self._source, SystemDependency): 252 self.printResult(env, "Dependency ", self.FAIL) 253 else: 254 self.printResult(env, "Download", self.FAIL) 255 env._logger.commands.write(e.reason+'\n') 256 if env.debug : 257 import bake.Utils 258 bake.Utils.print_backtrace() 259 if env.stopOnErrorEnabled: 260 self.handleStopOnError(e) 261 return False 262 except: 263 if isinstance(self._source, SystemDependency): 264 self.printResult(env, "Install", self.FAIL) 265 else: 266 self.printResult(env, "Download", self.FAIL) 267 if env.debug : 268 import bake.Utils 269 bake.Utils.print_backtrace() 270 if env.stopOnErrorEnabled: 271 er = sys.exc_info()[1] 272 self.handleStopOnError(TaskError('Error: %s' % (er))) 273 return False 274 275 276 def _do_update(self, env, source, name): 277 """ Recursive update function, do the update for each 278 target module. """ 279 280 srcDirTmp = name 281 if source.attribute('module_directory').value : 282 srcDirTmp = source.attribute('module_directory').value 283 284 env.start_source(name, srcDirTmp) 285 286 try: 287 source.update(env) 288 finally: 289 env.end_source() 290 for child, child_name in source.children(): 291 self._do_update(env, child, os.path.join(name, child_name)) 292 293 def update(self, env): 294 """ Main update function. """ 295 296 try: 297 self._do_update(env, self._source, self._name) 298 self.printResult(env, " Update ", self.OK) 299 return True 300 except TaskError as e: 301 self.printResult(env, " Update ", self.FAIL) 302 env._logger.commands.write(e.reason+'\n') 303 if env.debug : 304 import bake.Utils 305 bake.Utils.print_backtrace() 306 if env.stopOnErrorEnabled: 307 self.handleStopOnError(e) 308 return False 309 except: 310 self.printResult(env, " Update ", self.FAIL) 311 env._logger.commands.write(e.reason+'\n') 312 if env.debug : 313 import bake.Utils 314 bake.Utils.print_backtrace() 315 if env.stopOnErrorEnabled: 316 er = sys.exc_info()[1] 317 self.handleStopOnError(TaskError('Error: %s' % (er))) 318 return False 319 320 def distclean(self, env): 321 """ Main distclean source function, call the modules distclean. """ 322 323 srcDirTmp = self._name 324 if self._source.attribute('module_directory').value : 325 srcDirTmp = self._source.attribute('module_directory').value 326 327 env.start_build(self._name, srcDirTmp, 328 self._build.supports_objdir) 329 if not os.path.isdir(env.objdir) or not os.path.isdir(env.srcdir): 330 env.end_build() 331 return 332 try: 333 self._build.distclean(env) 334 env.end_build() 335 self._built_once = False 336 self.printResult(env, "Distclean ", self.OK) 337 return True 338 except TaskError as e: 339 self.printResult(env, "Distclean ", self.FAIL) 340 env._logger.commands.write(e.reason+'\n') 341 if env.debug : 342 import bake.Utils 343 bake.Utils.print_backtrace() 344 return False 345 except: 346 env.end_build() 347 if env.debug : 348 import bake.Utils 349 bake.Utils.print_backtrace() 350 return False 351 352 def fullclean(self, env): 353 """ Main full clean function, deletes the source and installed files. """ 354 355 srcDirTmp = self._name 356 if self._source.attribute('module_directory').value : 357 srcDirTmp = self._source.attribute('module_directory').value 358 359 env.start_build(self._name, srcDirTmp, True) 360 sys.stdout.write(" >> Removing source: " + self._name + ": " + env.srcdir) 361 try: 362 shutil.rmtree(env.srcdir) 363 self.printResult(env, "Removing source: ", self.OK) 364 except Exception as e: 365 err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip() 366 env._logger.commands.write(" > " + err +'\n') 367# print (e) 368 pass 369 370 if os.path.isdir(env.objdir): 371 sys.stdout.write(" >> Removing build: " + env.objdir) 372 try: 373 shutil.rmtree(env.objdir) 374 self.printResult(env, "Removing build: ", self.OK) 375 except Exception as e: 376 self.printResult(env, "Removing build: ", self.FAIL) 377 err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip() 378 env._logger.commands.write(" > " + err +'\n') 379 380 if os.path.isdir(env.installdir): 381 sys.stdout.write(" >> Removing installation: " + env.installdir) 382 try: 383 shutil.rmtree(env.installdir) 384 self.printResult(env, "Installation removed", self.OK) 385 except Exception as e: 386 self.printResult(env, "Installation removed", self.FAIL) 387 err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip() 388 env._logger.commands.write(" > " + err +'\n') 389 390 return True 391 392 393 def uninstall(self, env): 394 """ Main uninstall function, deletes the installed files. """ 395 396 for installed in self._installed: 397 try: 398 os.remove(installed) 399 except OSError: 400 pass 401 402 # delete directories where files were installed if they are empty 403 dirs = [os.path.dirname(installed) for installed in self._installed] 404 def uniq(seq): 405 keys = {} 406 for e in seq: 407 keys[e] = 1 408 return keys.keys() 409 for d in uniq(dirs): 410 try: 411 os.removedirs(d) 412 except OSError: 413 pass 414 self._installed = [] 415 416 417 def build(self, env, jobs, force_clean): 418 """ Main build function. """ 419 420 # if there is no build we do not need to proceed 421 if self._build.name() == 'none' or self._source.name() == 'system_dependency': 422 srcDirTmp = self._name 423 if self._source.attribute('module_directory').value : 424 srcDirTmp = self._source.attribute('module_directory').value 425 426 if self._build.attribute('pre_installation').value != '': 427 self._build.perform_pre_installation(env) 428 if self._build.attribute('patch').value != '': 429 self._build.threat_patch(env, self._build.attribute('patch').value) 430 if self._build.attribute('post_installation').value != '': 431 self._build.perform_post_installation(env) 432 433 self._build.threat_variables(env) 434 435 return True 436 437 # delete in case this is a new build configuration 438 # and there are old files around 439 if force_clean: 440 self.uninstall(env) 441 if not self._built_once: 442 self.clean(env) 443 444 srcDirTmp = self._name 445 if self._source.attribute('module_directory').value : 446 srcDirTmp = self._source.attribute('module_directory').value 447 448 env.start_build(self._name, srcDirTmp, 449 self._build.supports_objdir) 450 451 # setup the monitor 452 monitor = FilesystemMonitor(env.installdir) 453 monitor.start() 454 455 if self._build.attribute('supported_os').value : 456 if not self._build.check_os(self._build.attribute('supported_os').value) : 457 import platform 458 import distro 459 osName = platform.system().lower() 460 (distname,version,ids)=distro.linux_distribution() 461 self.printResult(env, "Building", self.FAIL) 462 print(' This module works only on \"%s\"' 463 ' platform(s), %s is not supported for %s %s %s %s' % 464 (self._build.attribute('supported_os').value, 465 self._name, platform.system(), distname,version,ids)) 466 return 467 468 if not os.path.isdir(env.installdir): 469 os.mkdir(env.installdir) 470 if self._build.supports_objdir and not os.path.isdir(env.objdir): 471 os.mkdir(env.objdir) 472 473 try: 474 if not os.path.isdir(env.srcdir): 475 raise TaskError('Source is not available for module %s: ' 476 'directory %s not found. Try %s download first.' % 477 (env._module_name,env.srcdir, sys.argv[0])) 478 479 if self._build.attribute('pre_installation').value != '': 480 self._build.perform_pre_installation(env) 481 482 self._build.threat_variables(env) 483 484 if self._build.attribute('patch').value != '': 485 self._build.threat_patch(env, self._build.attribute('patch').value) 486 487 self._build.build(env, jobs) 488 self._installed = monitor.end() 489 if self._build.attribute('post_installation').value != '': 490 self._build.perform_post_installation(env) 491 env.end_build() 492 self._built_once = True 493 self.printResult(env, "Built", self.OK) 494 return True 495 except TaskError as e: 496 self.printResult(env, "Building", self.FAIL) 497 env._logger.commands.write(" > " + e.reason+'\n') 498 if env.debug : 499 import bake.Utils 500 bake.Utils.print_backtrace() 501 env.end_build() 502 503 if env.stopOnErrorEnabled: 504 self.handleStopOnError(e) 505 506 return False 507 except: 508 self._installed = monitor.end() 509 env.end_build() 510 if env.debug : 511 import bake.Utils 512 bake.Utils.print_backtrace() 513 if env.stopOnErrorEnabled: 514 er = sys.exc_info()[1] 515 self.handleStopOnError(TaskError('Error: %s' % (er))) 516 return False 517 518 def check_build_version(self, env): 519 """ Checks the version of the selected build tool in the machine. """ 520 521 srcDirTmp = self._name 522 if self._source.attribute('module_directory').value : 523 srcDirTmp = self._source.attribute('module_directory').value 524 525 env.start_build(self._name, srcDirTmp, 526 self._build.supports_objdir) 527 528 retval = self._build.check_version(env) 529 env.end_build() 530 return retval 531 532 def is_downloaded(self, env): 533 """ Checks if the source code is not already available. """ 534 535 srcDirTmp = self._name 536 if self._source.name() == 'system_dependency' : 537 return True 538 539 if self._source.name() == 'none' : 540 return True 541 542 if self._source.attribute('module_directory').value : 543 srcDirTmp = self._source.attribute('module_directory').value 544 545 env.start_source(self._name,srcDirTmp) 546 retval = os.path.isdir(env.srcdir) 547 env.end_source() 548 return retval 549 550 551 def check_source_version(self, env): 552 """ Checks if the version of the available version control tool. """ 553 554 srcDirTmp = self._name 555 if self._source.attribute('module_directory').value : 556 srcDirTmp = self._source.attribute('module_directory').value 557 env.start_source(self._name, srcDirTmp) 558 retval = self._source.check_version(env) 559 env.end_source() 560 return retval 561 562 563 def update_libpath(self, env): 564 """ Makes it available for the next modules the present libpath. """ 565 566 srcDirTmp = self._name 567 if self._source.attribute('module_directory').value : 568 srcDirTmp = self._source.attribute('module_directory').value 569 570 env.start_build(self._name, srcDirTmp, 571 self._build.supports_objdir) 572 env.add_libpaths([env._lib_path()]) 573 env.end_build() 574 575 def clean(self, env): 576 """ Main cleaning build option handler. """ 577 578 srcDirTmp = self._name 579 if self._source.attribute('module_directory').value : 580 srcDirTmp = self._source.attribute('module_directory').value 581 582 env.start_build(self._name, srcDirTmp, 583 self._build.supports_objdir) 584 if not os.path.isdir(env.objdir) or not os.path.isdir(env.srcdir): 585 env.end_build() 586 return 587 try: 588 self._build.clean(env) 589 env.end_build() 590 self._built_once = False 591 self.printResult(env, "Clean ", self.OK) 592 return True 593 except TaskError as e: 594 self.printResult(env, "Clean ", self.FAIL) 595 err = re.sub(r'\[\w+ \w+\]+', ' ', str(e)).strip() 596 env._logger.commands.write(err+'\n') 597 if env.debug : 598 import bake.Utils 599 bake.Utils.print_backtrace() 600 if env.stopOnErrorEnabled: 601 self.handleStopOnError(e) 602 return False 603 except: 604 env.end_build() 605 if env.debug : 606 import bake.Utils 607 bake.Utils.print_backtrace() 608 if env.stopOnErrorEnabled: 609 er = sys.exc_info()[1] 610 self.handleStopOnError(TaskError('Error: %s' % (er))) 611 return False 612 613 def is_built_once(self): 614 return self._built_once 615 def get_source(self): 616 return self._source 617 def get_build(self): 618 return self._build 619 def name(self): 620 return self._name 621 def dependencies(self): 622 return self._dependencies 623 def mtype(self): 624 return self._type 625 def minver(self): 626 return self._minVersion 627 def maxver(self): 628 return self._maxVersion 629 def addDependencies(self, depend): 630 for d in self._dependencies: 631 if d.name() == depend.name(): 632 return 633 self._dependencies.append(depend) 634