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 ModuleEnvironment.py 22 23 This file stores the class Module Environment responsible for the interaction 24 between Bake and the execution of third party softwares and the operating 25 system. 26''' 27 28import os 29import subprocess 30import sys 31import platform 32 33from bake.Exceptions import TaskError 34from bake.Utils import ColorTool 35 36class ModuleEnvironment: 37 ''' Main class to interact with the host system to execute the external 38 tools. 39 ''' 40 _stopOnError = False 41 _libpaths = set([]) 42 _binpaths = set([]) 43 _pkgpaths = set([]) 44 _variables = set([]) 45 46 (HIGHER, LOWER, EQUAL) = range(0,3) 47 48 def __init__(self, logger, installdir, sourcedir, objdir, debug=False): 49 ''' Internal variables initialization.''' 50 51 self._logger = logger 52 self._installdir = installdir 53 self._sourcedir = sourcedir 54 self._objdir = objdir 55 self._module_name = None 56 self._module_dir = None 57 self._module_supports_objdir = None 58# self._libpaths = set([]) 59# self._binpaths = set([]) 60# self._pkgpaths = set([]) 61# self._variables = set([]) 62 self._debug = debug 63 self._sudoEnabled = False 64 65 def _module_directory(self): 66 ''' Returns the name of the directory of the on use module.''' 67 68 if not self._module_dir : 69 return self._module_name 70 return self._module_dir 71 72 @property 73 def installdir(self): 74 ''' Returns the name of the set installation directory.''' 75 76 return self._installdir 77 78 @property 79 def debug(self): 80 ''' Returns if this execution was set to show the debug messages or not.''' 81 82 return self._debug 83 84 @property 85 def srcdir(self): 86 ''' Returns the directory where Bake stores the source of the present 87 module. 88 ''' 89 90 try: 91 return os.path.join(self._sourcedir, self._module_directory()) 92 except AttributeError as e: 93 raise TaskError('Missing configuration: sourcedir= %s, ' 94 'module_directory= %s, Error: %s' 95 % (self._sourcedir,self._module_directory(), e)) 96 97 @property 98 def srcrepo(self): 99 ''' The root of the source repository, where all the sources for all 100 the modules will be stored. 101 ''' 102 103 return self._sourcedir 104 105 @property 106 def objdir(self): 107 ''' Returns the directory where Bake stores the object code of the 108 present module. 109 ''' 110 111 if not self._module_supports_objdir: 112 obj = self.srcdir 113 else: 114 try: 115 obj = os.path.join(self.srcdir, self._objdir) 116 except AttributeError as e: 117 raise TaskError('Missing configuration: sourcedir= %s, ' 118 'objdir= %s, Error: %s' 119 % (self._sourcedir, self._module_directory(), e)) 120 return obj 121 122 @property 123 def sudoEnabled(self): 124 ''' Returns the setting of the --sudo option''' 125 126 return self._sudoEnabled 127 128 @property 129 def stopOnErrorEnabled(self): 130 ''' Returns the setting of the --stop_on_error option''' 131 132 return ModuleEnvironment._stopOnError 133 134 def _pkgconfig_var(self): 135 ''' Returns the PKG_CONFIG_PATH configured environment variable.''' 136 137 return 'PKG_CONFIG_PATH' 138 139 def _pkgconfig_path(self): 140 ''' Returns the PKG_CONFIG_PATH configured path. ''' 141 142 return os.path.join(self._lib_path(), 'pkgconfig') 143 144 def _lib_var(self): 145 ''' Returns the value of the system configured library path.''' 146 147 lib_var = {'Linux' : 'LD_LIBRARY_PATH', 148 'FreeBSD' : 'LD_LIBRARY_PATH', 149 'Darwin' : 'DYLD_LIBRARY_PATH', 150 'Windows' : 'PATH'} 151 if not platform.system() in lib_var: 152 sys.stderr('Error: Unsupported platform. Send email to ' 153 'bake_support@inria.fr (%s)' % platform.system()) 154 sys.exit(1) 155 return lib_var[platform.system()] 156 157 def _lib_path(self): 158 ''' Returns the value of the library path for the in-use module.''' 159 160 return os.path.join(self._installdir, 'lib') 161 162 def _bin_var(self): 163 return 'PATH' 164 def _bin_path(self): 165 ''' Returns the value of the binary path for the in-use module.''' 166 167 return os.path.join(self._installdir, 'bin') 168 169 def _py_var(self): 170 return 'PYTHONPATH' 171 def _py_path(self): 172 ''' Returns the value of the python path for the in-use module.''' 173 174 return os.path.join(self._installdir, 'lib', 175 'python'+platform.python_version_tuple()[0]+ 176 '.'+platform.python_version_tuple()[1], 177 'site-packages') 178 179 def _append_path(self, d, name, value, sep): 180 ''' Append the variable to the system in use configuration. ''' 181 182 if not name in d: 183 d[name] = value 184 else: 185 d[name] = d[name] + sep + value 186 187 def start_source(self, name, dir): 188 ''' Sets the environment to be used by the given source module.''' 189 190 assert self._module_supports_objdir is None 191 self._module_name = name 192 self._module_dir = dir 193 self._logger.set_current_module(name) 194 195 # ensure source directory exists 196 if not os.path.isdir(self._sourcedir): 197 os.makedirs(self._sourcedir) 198 199 def end_source(self): 200 ''' Cleans the environment regarding the informations of the last used 201 source module. 202 ''' 203 204 self._module_name = None 205 self._module_dir = None 206 self._logger.clear_current_module() 207 208 def start_build(self, name, dir, supports_objdir): 209 ''' Sets the environment to be used by the given build module.''' 210 211# assert self._module_supports_objdir is None 212 self._module_name = name 213 self._module_dir = dir 214 self._module_supports_objdir = supports_objdir 215 self._logger.set_current_module(name) 216 217 if not os.path.isdir(self.installdir): 218 os.makedirs(self.installdir) 219 if not os.path.isdir(self.objdir): 220 os.makedirs(self.objdir) 221 222 def end_build(self): 223 ''' Cleans the environment regarding the informations of the last used 224 build module. 225 ''' 226 227 self._module_name = None 228 self._module_dir = None 229 self._module_supports_objdir = None 230 self._logger.clear_current_module() 231 232 def exist_file(self, file): 233 ''' Finds if the file exists in the path.''' 234 235 return os.path.exists(file) 236 237 def path_list(self): 238 ''' Return path that will be searched for executables ''' 239 pythonpath=[] 240 241 if os.environ.get('PYTHONPATH'): 242 pythonpath=os.environ.get('PYTHONPATH').split(os.pathsep) 243 return os.environ.get('PATH').split(os.pathsep) + [self._bin_path()] + pythonpath 244 245 def _program_location(self, program): 246 ''' Finds where the executable is located in the user's path.''' 247 248 # function to verify if the program exists on the given path 249 # and if it is executable 250 def is_exe(path): 251 return os.path.exists(path) and os.access(path, os.X_OK) 252 path, name = os.path.split(program) 253 # if the path for the executable was passed as part of its name 254 if path: 255 if is_exe(program): 256 return program 257 else: 258 # for all the directories in the path search for the executable 259 for path in self.path_list(): 260 exe_file = os.path.join(path, program) 261 if is_exe(exe_file): 262 return exe_file 263 264 toFindIn=None 265 # search for libs with that name on the library path 266 index=program.find(".so") + program.find(".a") 267 if index>0 : 268 toFindIn=['/usr/lib','/usr/lib64','/usr/lib32','/usr/local/lib', 269 '/lib','/opt/local/lib','/opt/local/Library', '/usr/local/opt'] 270 for libpath in self._libpaths: 271 toFindIn.append(libpath) 272 stdLibs = [] 273 try: 274 libPath = os.environ[self._lib_var()] 275 if libPath: 276 stdLibs=libPath.split(os.pathsep) 277 except: 278 pass 279 280 tofindIn=toFindIn+stdLibs+[self._lib_path()] 281 282 elif program.endswith(".h"): 283 toFindIn=['/usr/include', '/usr/local/include', '/usr/lib','/opt/local/include', '/usr/local/opt'] 284 285 if toFindIn : 286 for eachdir in toFindIn: 287 if sys.platform == "darwin": 288 # enable symlink walking for MacOS only (bug 2975) 289 for dirname, dirnames, filenames in os.walk(eachdir, True, None, True): 290 for filename in filenames: 291 if filename==name: 292 return os.path.join(dirname, filename) 293 else: 294 for dirname, dirnames, filenames in os.walk(eachdir): 295 for filename in filenames: 296 if filename==name: 297 return os.path.join(dirname, filename) 298 299 return None 300 301 def _check_version(self, found, required, match_type): 302 ''' Checks the version of the required executable.''' 303 304 smallerSize=min(len(found),len(required)) 305 if match_type == self.HIGHER: 306 for i in range(0,smallerSize): 307 if not found[i]: 308 return False 309 if int(found[i]) < int(required[i]): 310 return False 311 elif int(found[i]) > int(required[i]): 312 return True 313 return True 314 elif match_type == self.LOWER: 315 for i in range(0,smallerSize): 316 if not found[i]: 317 return True 318 if int(found[i]) > int(required[i]): 319 return False 320 elif int(found[i]) < int(required[i]): 321 return True 322 if len(found) >= len(required): 323 return False 324 return True 325 elif match_type == self.EQUAL: 326 if len(found) != len(required): 327 return False 328 for i in range(0,smallerSize): 329 if int(found[i]) != int(required[i]): 330 return False 331 return True 332 else: 333 assert False 334 335 def add_libpaths(self, libpaths): 336 ''' Adds the list of paths to the in-use library path environment 337 variable. 338 ''' 339 340 for element in libpaths : 341 self._libpaths.add(self.replace_variables(element)) 342 343 def add_binpaths(self, libpaths): 344 ''' Adds the list of paths to the in-use binary path environment 345 variable. 346 ''' 347 348 for element in libpaths : 349 self._binpaths.add(self.replace_variables(element)) 350 351 def add_pkgpaths(self, libpaths): 352 ''' Adds the list of paths to the in-use package path environment 353 variable. 354 ''' 355 356 for element in libpaths : 357 self._pkgpaths.add(self.replace_variables(element)) 358 359 def add_variables(self, libpaths): 360 ''' Adds/replace the list of variables to the in-use set of environment 361 variables. 362 ''' 363 364 for element in libpaths : 365 self._variables.add(self.replace_variables(element)) 366 367 def create_environment_file(self, fileName): 368 ''' Creates the set environment file to help users to call the Bake 369 built modules. 370 ''' 371 372 script = "#!/bin/bash \n#### \n# Environment setting script. Automatically generated by Bake\n####\n\n" 373 script = script + "if [ \"${BASH_SOURCE:-}\" == \"${0}\" ]; then \n" + \ 374 " echo \"> Call with . bakeSetEnv.sh or source bakeSetEnv.sh\" \n" + \ 375 " exit 1 \n" + \ 376 "fi \n\n" 377 378 self._binpaths.add(self._bin_path()) 379 if os.path.isdir(self._lib_path()): 380 self._libpaths.add(self._lib_path()) 381 if os.path.isdir(self._lib_path()+'64'): 382 self._libpaths.add(self._lib_path()+'64') 383 384 if len(self._libpaths) > 0: 385 script = script + self.add_onPath("LD_LIBRARY_PATH", self._libpaths) + "\n" 386 387 if len(self._binpaths) > 0: 388 script = script + self.add_onPath("PATH", self._binpaths) + "\n" 389 390 if len(self._pkgpaths) > 0: 391 script = script + self.add_onPath("PKG_CONFIG_PATH", self._pkgpaths) + "\n" 392 393 from distutils.sysconfig import get_python_lib 394 localLibPath='' 395 libDir=get_python_lib() 396 if libDir: 397 begin=libDir.lower().index('python') 398 localLibPath=os.path.join(self._lib_path(),libDir[begin:]) 399 400 401 script = script + self.add_onPath("PYTHONPATH", [sys.path[0],self._lib_path(),localLibPath]) + "\n" 402 403 for element in self._variables: 404 script = script + " export " + element + "\n" 405 406 fout = open(fileName, "w") 407 fout.write(script) 408 fout.close() 409 os.chmod(fileName, 0o755) 410 411 return script 412 413 def add_onPath (self, variableName, vectorPath): 414 ''' Format the variable to be added on the system. 415 ''' 416 417 returnString = " export " + variableName + "=\"${" + variableName + ":+${" + variableName + "}:}" 418 for element in vectorPath: 419 returnString = returnString + element + ":" 420 421 # Strip extra ':' 422 returnString = returnString[:-1] 423 424 returnString = returnString + "\"" 425 return returnString 426 427 def replace_variables(self, string): 428 ''' Replace the variables on the string, if they exist, by their 429 system real values. 430 ''' 431 432 import re 433 tmp = string 434 tmp = re.sub('\$INSTALLDIR', self.installdir, tmp) 435 tmp = re.sub('\$OBJDIR', self.objdir, tmp) 436 tmp = re.sub('\$SRCDIR', self.srcdir, tmp) 437 return tmp 438 439 def check_program(self, program, version_arg = None, 440 version_regexp = None, version_required = None, 441 match_type=HIGHER): 442 '''Checks if the program, with the desired version, exists in the 443 system. 444 ''' 445 446 if self._program_location(program) is None: 447 return False 448 if version_arg is None and version_regexp is None and version_required is None: 449 return True 450 else: 451 # This assert as it was avoided the checking of the version of the 452 # executable assert not (version_arg is None or version_regexp is 453 # None or version_required is None) 454 assert not (version_arg is None and version_regexp is None and version_required is None) 455 popen = subprocess.Popen([self._program_location(program), 456 version_arg], 457 stdout = subprocess.PIPE, 458 stderr = subprocess.STDOUT) 459 (out, err) = popen.communicate('') 460 import re 461 reg = re.compile(version_regexp) 462 for line in out.splitlines(): 463 m = reg.search(line) 464 if not m is None: 465 found = m.groups() 466 return self._check_version(found, version_required, match_type) 467 468 469 def append_to_path(self, env_vars): 470 """Sets the library and binary paths.""" 471 472 for libpath in self._libpaths: 473 self._append_path(env_vars, self._lib_var(), libpath, os.pathsep) 474 if self.debug: 475 print(" -> " + self._lib_var() + " " + libpath + " ") 476 477 self._append_path(env_vars, self._lib_var(), self._lib_path(), os.pathsep) 478 for libpath in self._binpaths: 479 self._append_path(env_vars, self._bin_var(), libpath, os.pathsep) 480 if self.debug: 481 print(" -> " + self._bin_var() + " " + libpath + " ") 482 483 self._append_path(env_vars, self._bin_var(), self._bin_path(), os.pathsep) 484 for libpath in self._pkgpaths: 485 self._append_path(env_vars, self._pkgconfig_var(), libpath, os.pathsep) 486 if self.debug: 487 print(" -> " + self._pkgconfig_var() + " " + libpath + " ") 488 489 self._append_path(env_vars, self._pkgconfig_var(), self._pkgconfig_path(), os.pathsep) 490 self._append_path(env_vars, self._py_var(), self._py_path(), os.pathsep) 491 self._append_path(env_vars, self._py_var(), os.path.join(self._installdir, 'lib'), os.pathsep) 492 493 return env_vars 494 495 def run(self, args, directory = None, env = dict(), interactive = False): 496 '''Executes a system program adding the libraries and over the correct 497 directories. 498 ''' 499 500 if not interactive: 501 env_string = '' 502 if len(env) != 0: 503 env_string = ' '.join([a + '=' + b for a,b in env.items()]) 504 try: 505 args_string = ' '.join(args) 506 except TypeError as e: 507 raise TaskError('Wrong argument type: %s, expected string,' 508 ' error: %s' % (str(args), e)) 509 510 self._logger.commands.write(env_string + ' ' + args_string + 511 ' dir=' + str(directory) + '\n') 512 stdin = None 513 stdout = self._logger.stdout 514 stderr = self._logger.stderr 515 else: 516 stdin = sys.stdin 517 stdout = sys.stdout 518 stderr = sys.stderr 519 520 tmp = dict(list(os.environ.items()) + list(env.items())) 521 522 # sets the library and binary paths 523 tmp = self.append_to_path(tmp) 524 525 # Calls the third party executable with the whole context 526 try: 527 popen = subprocess.Popen(args, 528 stdin = stdin, 529 stdout = stdout, 530 stderr = stderr, 531 cwd = directory, 532 env = tmp) 533 except Exception as e: 534 raise TaskError('could not execute: %s %s. \nUnexpected error: %s' 535 % (str(directory), str(args), str(e))) 536 537 # Waits for the full execution of the third party software 538 retcode = popen.wait() 539 if retcode != 0: 540 raise TaskError('Subprocess failed with error %d: %s' % (retcode, str(args))) 541 542 543