ccroot.py (26377B)
1 #!/usr/bin/env python 2 # encoding: utf-8 3 # Thomas Nagy, 2005-2018 (ita) 4 5 """ 6 Classes and methods shared by tools providing support for C-like language such 7 as C/C++/D/Assembly/Go (this support module is almost never used alone). 8 """ 9 10 import os, re 11 from waflib import Task, Utils, Node, Errors, Logs 12 from waflib.TaskGen import after_method, before_method, feature, taskgen_method, extension 13 from waflib.Tools import c_aliases, c_preproc, c_config, c_osx, c_tests 14 from waflib.Configure import conf 15 16 SYSTEM_LIB_PATHS = ['/usr/lib64', '/usr/lib', '/usr/local/lib64', '/usr/local/lib'] 17 18 USELIB_VARS = Utils.defaultdict(set) 19 """ 20 Mapping for features to :py:class:`waflib.ConfigSet.ConfigSet` variables. See :py:func:`waflib.Tools.ccroot.propagate_uselib_vars`. 21 """ 22 23 USELIB_VARS['c'] = set(['INCLUDES', 'FRAMEWORKPATH', 'DEFINES', 'CPPFLAGS', 'CCDEPS', 'CFLAGS', 'ARCH']) 24 USELIB_VARS['cxx'] = set(['INCLUDES', 'FRAMEWORKPATH', 'DEFINES', 'CPPFLAGS', 'CXXDEPS', 'CXXFLAGS', 'ARCH']) 25 USELIB_VARS['d'] = set(['INCLUDES', 'DFLAGS']) 26 USELIB_VARS['includes'] = set(['INCLUDES', 'FRAMEWORKPATH', 'ARCH']) 27 28 USELIB_VARS['cprogram'] = USELIB_VARS['cxxprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS', 'FRAMEWORK', 'FRAMEWORKPATH', 'ARCH', 'LDFLAGS']) 29 USELIB_VARS['cshlib'] = USELIB_VARS['cxxshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS', 'FRAMEWORK', 'FRAMEWORKPATH', 'ARCH', 'LDFLAGS']) 30 USELIB_VARS['cstlib'] = USELIB_VARS['cxxstlib'] = set(['ARFLAGS', 'LINKDEPS']) 31 32 USELIB_VARS['dprogram'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS']) 33 USELIB_VARS['dshlib'] = set(['LIB', 'STLIB', 'LIBPATH', 'STLIBPATH', 'LINKFLAGS', 'RPATH', 'LINKDEPS']) 34 USELIB_VARS['dstlib'] = set(['ARFLAGS', 'LINKDEPS']) 35 36 USELIB_VARS['asm'] = set(['ASFLAGS']) 37 38 # ================================================================================================= 39 40 @taskgen_method 41 def create_compiled_task(self, name, node): 42 """ 43 Create the compilation task: c, cxx, asm, etc. The output node is created automatically (object file with a typical **.o** extension). 44 The task is appended to the list *compiled_tasks* which is then used by :py:func:`waflib.Tools.ccroot.apply_link` 45 46 :param name: name of the task class 47 :type name: string 48 :param node: the file to compile 49 :type node: :py:class:`waflib.Node.Node` 50 :return: The task created 51 :rtype: :py:class:`waflib.Task.Task` 52 """ 53 out = '%s.%d.o' % (node.name, self.idx) 54 task = self.create_task(name, node, node.parent.find_or_declare(out)) 55 try: 56 self.compiled_tasks.append(task) 57 except AttributeError: 58 self.compiled_tasks = [task] 59 return task 60 61 @taskgen_method 62 def to_incnodes(self, inlst): 63 """ 64 Task generator method provided to convert a list of string/nodes into a list of includes folders. 65 66 The paths are assumed to be relative to the task generator path, except if they begin by **#** 67 in which case they are searched from the top-level directory (``bld.srcnode``). 68 The folders are simply assumed to be existing. 69 70 The node objects in the list are returned in the output list. The strings are converted 71 into node objects if possible. The node is searched from the source directory, and if a match is found, 72 the equivalent build directory is created and added to the returned list too. When a folder cannot be found, it is ignored. 73 74 :param inlst: list of folders 75 :type inlst: space-delimited string or a list of string/nodes 76 :rtype: list of :py:class:`waflib.Node.Node` 77 :return: list of include folders as nodes 78 """ 79 lst = [] 80 seen = set() 81 for x in self.to_list(inlst): 82 if x in seen or not x: 83 continue 84 seen.add(x) 85 86 # with a real lot of targets, it is sometimes interesting to cache the results below 87 if isinstance(x, Node.Node): 88 lst.append(x) 89 else: 90 if os.path.isabs(x): 91 lst.append(self.bld.root.make_node(x) or x) 92 else: 93 if x[0] == '#': 94 p = self.bld.bldnode.make_node(x[1:]) 95 v = self.bld.srcnode.make_node(x[1:]) 96 else: 97 p = self.path.get_bld().make_node(x) 98 v = self.path.make_node(x) 99 if p.is_child_of(self.bld.bldnode): 100 p.mkdir() 101 lst.append(p) 102 lst.append(v) 103 return lst 104 105 @feature('c', 'cxx', 'd', 'asm', 'fc', 'includes') 106 @after_method('propagate_uselib_vars', 'process_source') 107 def apply_incpaths(self): 108 """ 109 Task generator method that processes the attribute *includes*:: 110 111 tg = bld(features='includes', includes='.') 112 113 The folders only need to be relative to the current directory, the equivalent build directory is 114 added automatically (for headers created in the build directory). This enables using a build directory 115 or not (``top == out``). 116 117 This method will add a list of nodes read by :py:func:`waflib.Tools.ccroot.to_incnodes` in ``tg.env.INCPATHS``, 118 and the list of include paths in ``tg.env.INCLUDES``. 119 """ 120 121 lst = self.to_incnodes(self.to_list(getattr(self, 'includes', [])) + self.env.INCLUDES) 122 self.includes_nodes = lst 123 cwd = self.get_cwd() 124 self.env.INCPATHS = [x.path_from(cwd) for x in lst] 125 126 class link_task(Task.Task): 127 """ 128 Base class for all link tasks. A task generator is supposed to have at most one link task bound in the attribute *link_task*. See :py:func:`waflib.Tools.ccroot.apply_link`. 129 130 .. inheritance-diagram:: waflib.Tools.ccroot.stlink_task waflib.Tools.c.cprogram waflib.Tools.c.cshlib waflib.Tools.cxx.cxxstlib waflib.Tools.cxx.cxxprogram waflib.Tools.cxx.cxxshlib waflib.Tools.d.dprogram waflib.Tools.d.dshlib waflib.Tools.d.dstlib waflib.Tools.ccroot.fake_shlib waflib.Tools.ccroot.fake_stlib waflib.Tools.asm.asmprogram waflib.Tools.asm.asmshlib waflib.Tools.asm.asmstlib 131 :top-classes: waflib.Tools.ccroot.link_task 132 """ 133 color = 'YELLOW' 134 135 weight = 3 136 """Try to process link tasks as early as possible""" 137 138 inst_to = None 139 """Default installation path for the link task outputs, or None to disable""" 140 141 chmod = Utils.O755 142 """Default installation mode for the link task outputs""" 143 144 def add_target(self, target): 145 """ 146 Process the *target* attribute to add the platform-specific prefix/suffix such as *.so* or *.exe*. 147 The settings are retrieved from ``env.clsname_PATTERN`` 148 """ 149 if isinstance(target, str): 150 base = self.generator.path 151 if target.startswith('#'): 152 # for those who like flat structures 153 target = target[1:] 154 base = self.generator.bld.bldnode 155 156 pattern = self.env[self.__class__.__name__ + '_PATTERN'] 157 if not pattern: 158 pattern = '%s' 159 folder, name = os.path.split(target) 160 161 if self.__class__.__name__.find('shlib') > 0 and getattr(self.generator, 'vnum', None): 162 nums = self.generator.vnum.split('.') 163 if self.env.DEST_BINFMT == 'pe': 164 # include the version in the dll file name, 165 # the import lib file name stays unversioned. 166 name = name + '-' + nums[0] 167 elif self.env.DEST_OS == 'openbsd': 168 pattern = '%s.%s' % (pattern, nums[0]) 169 if len(nums) >= 2: 170 pattern += '.%s' % nums[1] 171 172 if folder: 173 tmp = folder + os.sep + pattern % name 174 else: 175 tmp = pattern % name 176 target = base.find_or_declare(tmp) 177 self.set_outputs(target) 178 179 def exec_command(self, *k, **kw): 180 ret = super(link_task, self).exec_command(*k, **kw) 181 if not ret and self.env.DO_MANIFEST: 182 ret = self.exec_mf() 183 return ret 184 185 def exec_mf(self): 186 """ 187 Create manifest files for VS-like compilers (msvc, ifort, ...) 188 """ 189 if not self.env.MT: 190 return 0 191 192 manifest = None 193 for out_node in self.outputs: 194 if out_node.name.endswith('.manifest'): 195 manifest = out_node.abspath() 196 break 197 else: 198 # Should never get here. If we do, it means the manifest file was 199 # never added to the outputs list, thus we don't have a manifest file 200 # to embed, so we just return. 201 return 0 202 203 # embedding mode. Different for EXE's and DLL's. 204 # see: http://msdn2.microsoft.com/en-us/library/ms235591(VS.80).aspx 205 mode = '' 206 for x in Utils.to_list(self.generator.features): 207 if x in ('cprogram', 'cxxprogram', 'fcprogram', 'fcprogram_test'): 208 mode = 1 209 elif x in ('cshlib', 'cxxshlib', 'fcshlib'): 210 mode = 2 211 212 Logs.debug('msvc: embedding manifest in mode %r', mode) 213 214 lst = [] + self.env.MT 215 lst.extend(Utils.to_list(self.env.MTFLAGS)) 216 lst.extend(['-manifest', manifest]) 217 lst.append('-outputresource:%s;%s' % (self.outputs[0].abspath(), mode)) 218 219 return super(link_task, self).exec_command(lst) 220 221 class stlink_task(link_task): 222 """ 223 Base for static link tasks, which use *ar* most of the time. 224 The target is always removed before being written. 225 """ 226 run_str = '${AR} ${ARFLAGS} ${AR_TGT_F}${TGT} ${AR_SRC_F}${SRC}' 227 228 chmod = Utils.O644 229 """Default installation mode for the static libraries""" 230 231 def rm_tgt(cls): 232 old = cls.run 233 def wrap(self): 234 try: 235 os.remove(self.outputs[0].abspath()) 236 except OSError: 237 pass 238 return old(self) 239 setattr(cls, 'run', wrap) 240 rm_tgt(stlink_task) 241 242 @feature('skip_stlib_link_deps') 243 @before_method('process_use') 244 def apply_skip_stlib_link_deps(self): 245 """ 246 This enables an optimization in the :py:func:wafilb.Tools.ccroot.processes_use: method that skips dependency and 247 link flag optimizations for targets that generate static libraries (via the :py:class:Tools.ccroot.stlink_task task). 248 The actual behavior is implemented in :py:func:wafilb.Tools.ccroot.processes_use: method so this feature only tells waf 249 to enable the new behavior. 250 """ 251 self.env.SKIP_STLIB_LINK_DEPS = True 252 253 @feature('c', 'cxx', 'd', 'fc', 'asm') 254 @after_method('process_source') 255 def apply_link(self): 256 """ 257 Collect the tasks stored in ``compiled_tasks`` (created by :py:func:`waflib.Tools.ccroot.create_compiled_task`), and 258 use the outputs for a new instance of :py:class:`waflib.Tools.ccroot.link_task`. The class to use is the first link task 259 matching a name from the attribute *features*, for example:: 260 261 def build(bld): 262 tg = bld(features='cxx cxxprogram cprogram', source='main.c', target='app') 263 264 will create the task ``tg.link_task`` as a new instance of :py:class:`waflib.Tools.cxx.cxxprogram` 265 """ 266 267 for x in self.features: 268 if x == 'cprogram' and 'cxx' in self.features: # limited compat 269 x = 'cxxprogram' 270 elif x == 'cshlib' and 'cxx' in self.features: 271 x = 'cxxshlib' 272 273 if x in Task.classes: 274 if issubclass(Task.classes[x], link_task): 275 link = x 276 break 277 else: 278 return 279 280 objs = [t.outputs[0] for t in getattr(self, 'compiled_tasks', [])] 281 self.link_task = self.create_task(link, objs) 282 self.link_task.add_target(self.target) 283 284 # remember that the install paths are given by the task generators 285 try: 286 inst_to = self.install_path 287 except AttributeError: 288 inst_to = self.link_task.inst_to 289 if inst_to: 290 # install a copy of the node list we have at this moment (implib not added) 291 self.install_task = self.add_install_files( 292 install_to=inst_to, install_from=self.link_task.outputs[:], 293 chmod=self.link_task.chmod, task=self.link_task) 294 295 @taskgen_method 296 def use_rec(self, name, **kw): 297 """ 298 Processes the ``use`` keyword recursively. This method is kind of private and only meant to be used from ``process_use`` 299 """ 300 301 if name in self.tmp_use_not or name in self.tmp_use_seen: 302 return 303 304 try: 305 y = self.bld.get_tgen_by_name(name) 306 except Errors.WafError: 307 self.uselib.append(name) 308 self.tmp_use_not.add(name) 309 return 310 311 self.tmp_use_seen.append(name) 312 y.post() 313 314 # bind temporary attributes on the task generator 315 y.tmp_use_objects = objects = kw.get('objects', True) 316 y.tmp_use_stlib = stlib = kw.get('stlib', True) 317 try: 318 link_task = y.link_task 319 except AttributeError: 320 y.tmp_use_var = '' 321 else: 322 objects = False 323 if not isinstance(link_task, stlink_task): 324 stlib = False 325 y.tmp_use_var = 'LIB' 326 else: 327 y.tmp_use_var = 'STLIB' 328 329 p = self.tmp_use_prec 330 for x in self.to_list(getattr(y, 'use', [])): 331 if self.env["STLIB_" + x]: 332 continue 333 try: 334 p[x].append(name) 335 except KeyError: 336 p[x] = [name] 337 self.use_rec(x, objects=objects, stlib=stlib) 338 339 @feature('c', 'cxx', 'd', 'use', 'fc') 340 @before_method('apply_incpaths', 'propagate_uselib_vars') 341 @after_method('apply_link', 'process_source') 342 def process_use(self): 343 """ 344 Process the ``use`` attribute which contains a list of task generator names:: 345 346 def build(bld): 347 bld.shlib(source='a.c', target='lib1') 348 bld.program(source='main.c', target='app', use='lib1') 349 350 See :py:func:`waflib.Tools.ccroot.use_rec`. 351 """ 352 353 use_not = self.tmp_use_not = set() 354 self.tmp_use_seen = [] # we would like an ordered set 355 use_prec = self.tmp_use_prec = {} 356 self.uselib = self.to_list(getattr(self, 'uselib', [])) 357 self.includes = self.to_list(getattr(self, 'includes', [])) 358 names = self.to_list(getattr(self, 'use', [])) 359 360 for x in names: 361 self.use_rec(x) 362 363 for x in use_not: 364 if x in use_prec: 365 del use_prec[x] 366 367 # topological sort 368 out = self.tmp_use_sorted = [] 369 tmp = [] 370 for x in self.tmp_use_seen: 371 for k in use_prec.values(): 372 if x in k: 373 break 374 else: 375 tmp.append(x) 376 377 while tmp: 378 e = tmp.pop() 379 out.append(e) 380 try: 381 nlst = use_prec[e] 382 except KeyError: 383 pass 384 else: 385 del use_prec[e] 386 for x in nlst: 387 for y in use_prec: 388 if x in use_prec[y]: 389 break 390 else: 391 tmp.append(x) 392 if use_prec: 393 raise Errors.WafError('Cycle detected in the use processing %r' % use_prec) 394 out.reverse() 395 396 link_task = getattr(self, 'link_task', None) 397 for x in out: 398 y = self.bld.get_tgen_by_name(x) 399 var = y.tmp_use_var 400 if var and link_task: 401 if self.env.SKIP_STLIB_LINK_DEPS and isinstance(link_task, stlink_task): 402 # If the skip_stlib_link_deps feature is enabled then we should 403 # avoid adding lib deps to the stlink_task instance. 404 pass 405 elif var == 'LIB' or y.tmp_use_stlib or x in names: 406 self.env.append_value(var, [y.target[y.target.rfind(os.sep) + 1:]]) 407 self.link_task.dep_nodes.extend(y.link_task.outputs) 408 tmp_path = y.link_task.outputs[0].parent.path_from(self.get_cwd()) 409 self.env.append_unique(var + 'PATH', [tmp_path]) 410 else: 411 if y.tmp_use_objects: 412 self.add_objects_from_tgen(y) 413 414 if getattr(y, 'export_includes', None): 415 # self.includes may come from a global variable #2035 416 self.includes = self.includes + y.to_incnodes(y.export_includes) 417 418 if getattr(y, 'export_defines', None): 419 self.env.append_value('DEFINES', self.to_list(y.export_defines)) 420 421 422 # and finally, add the use variables (no recursion needed) 423 for x in names: 424 try: 425 y = self.bld.get_tgen_by_name(x) 426 except Errors.WafError: 427 if not self.env['STLIB_' + x] and not x in self.uselib: 428 self.uselib.append(x) 429 else: 430 for k in self.to_list(getattr(y, 'use', [])): 431 if not self.env['STLIB_' + k] and not k in self.uselib: 432 self.uselib.append(k) 433 434 @taskgen_method 435 def accept_node_to_link(self, node): 436 """ 437 PRIVATE INTERNAL USE ONLY 438 """ 439 return not node.name.endswith('.pdb') 440 441 @taskgen_method 442 def add_objects_from_tgen(self, tg): 443 """ 444 Add the objects from the depending compiled tasks as link task inputs. 445 446 Some objects are filtered: for instance, .pdb files are added 447 to the compiled tasks but not to the link tasks (to avoid errors) 448 PRIVATE INTERNAL USE ONLY 449 """ 450 try: 451 link_task = self.link_task 452 except AttributeError: 453 pass 454 else: 455 for tsk in getattr(tg, 'compiled_tasks', []): 456 for x in tsk.outputs: 457 if self.accept_node_to_link(x): 458 link_task.inputs.append(x) 459 460 @taskgen_method 461 def get_uselib_vars(self): 462 """ 463 :return: the *uselib* variables associated to the *features* attribute (see :py:attr:`waflib.Tools.ccroot.USELIB_VARS`) 464 :rtype: list of string 465 """ 466 _vars = set() 467 for x in self.features: 468 if x in USELIB_VARS: 469 _vars |= USELIB_VARS[x] 470 return _vars 471 472 @feature('c', 'cxx', 'd', 'fc', 'javac', 'cs', 'uselib', 'asm') 473 @after_method('process_use') 474 def propagate_uselib_vars(self): 475 """ 476 Process uselib variables for adding flags. For example, the following target:: 477 478 def build(bld): 479 bld.env.AFLAGS_aaa = ['bar'] 480 from waflib.Tools.ccroot import USELIB_VARS 481 USELIB_VARS['aaa'] = ['AFLAGS'] 482 483 tg = bld(features='aaa', aflags='test') 484 485 The *aflags* attribute will be processed and this method will set:: 486 487 tg.env.AFLAGS = ['bar', 'test'] 488 """ 489 _vars = self.get_uselib_vars() 490 env = self.env 491 app = env.append_value 492 feature_uselib = self.features + self.to_list(getattr(self, 'uselib', [])) 493 for var in _vars: 494 y = var.lower() 495 val = getattr(self, y, []) 496 if val: 497 app(var, self.to_list(val)) 498 499 for x in feature_uselib: 500 val = env['%s_%s' % (var, x)] 501 if val: 502 app(var, val) 503 504 # ============ the code above must not know anything about import libs ========== 505 506 @feature('cshlib', 'cxxshlib', 'fcshlib') 507 @after_method('apply_link') 508 def apply_implib(self): 509 """ 510 Handle dlls and their import libs on Windows-like systems. 511 512 A ``.dll.a`` file called *import library* is generated. 513 It must be installed as it is required for linking the library. 514 """ 515 if not self.env.DEST_BINFMT == 'pe': 516 return 517 518 dll = self.link_task.outputs[0] 519 if isinstance(self.target, Node.Node): 520 name = self.target.name 521 else: 522 target = self.target 523 if target.startswith('#'): 524 target = target[1:] 525 name = os.path.split(target)[1] 526 implib = self.env.implib_PATTERN % name 527 implib = dll.parent.find_or_declare(implib) 528 self.env.append_value('LINKFLAGS', self.env.IMPLIB_ST % implib.bldpath()) 529 self.link_task.outputs.append(implib) 530 531 if getattr(self, 'defs', None) and self.env.DEST_BINFMT == 'pe': 532 node = self.path.find_resource(self.defs) 533 if not node: 534 raise Errors.WafError('invalid def file %r' % self.defs) 535 if self.env.def_PATTERN: 536 self.env.append_value('LINKFLAGS', self.env.def_PATTERN % node.path_from(self.get_cwd())) 537 self.link_task.dep_nodes.append(node) 538 else: 539 # gcc for windows takes *.def file as input without any special flag 540 self.link_task.inputs.append(node) 541 542 # where to put the import library 543 if getattr(self, 'install_task', None): 544 try: 545 # user has given a specific installation path for the import library 546 inst_to = self.install_path_implib 547 except AttributeError: 548 try: 549 # user has given an installation path for the main library, put the import library in it 550 inst_to = self.install_path 551 except AttributeError: 552 # else, put the library in BINDIR and the import library in LIBDIR 553 inst_to = '${IMPLIBDIR}' 554 self.install_task.install_to = '${BINDIR}' 555 if not self.env.IMPLIBDIR: 556 self.env.IMPLIBDIR = self.env.LIBDIR 557 self.implib_install_task = self.add_install_files(install_to=inst_to, install_from=implib, 558 chmod=self.link_task.chmod, task=self.link_task) 559 560 # ============ the code above must not know anything about vnum processing on unix platforms ========= 561 562 re_vnum = re.compile('^([1-9]\\d*|0)([.]([1-9]\\d*|0)){0,2}?$') 563 @feature('cshlib', 'cxxshlib', 'dshlib', 'fcshlib', 'vnum') 564 @after_method('apply_link', 'propagate_uselib_vars') 565 def apply_vnum(self): 566 """ 567 Enforce version numbering on shared libraries. The valid version numbers must have either zero or two dots:: 568 569 def build(bld): 570 bld.shlib(source='a.c', target='foo', vnum='14.15.16') 571 572 In this example on Linux platform, ``libfoo.so`` is installed as ``libfoo.so.14.15.16``, and the following symbolic links are created: 573 574 * ``libfoo.so → libfoo.so.14.15.16`` 575 * ``libfoo.so.14 → libfoo.so.14.15.16`` 576 577 By default, the library will be assigned SONAME ``libfoo.so.14``, effectively declaring ABI compatibility between all minor and patch releases for the major version of the library. When necessary, the compatibility can be explicitly defined using `cnum` parameter: 578 579 def build(bld): 580 bld.shlib(source='a.c', target='foo', vnum='14.15.16', cnum='14.15') 581 582 In this case, the assigned SONAME will be ``libfoo.so.14.15`` with ABI compatibility only between path releases for a specific major and minor version of the library. 583 584 On OS X platform, install-name parameter will follow the above logic for SONAME with exception that it also specifies an absolute path (based on install_path) of the library. 585 """ 586 if not getattr(self, 'vnum', '') or os.name != 'posix' or self.env.DEST_BINFMT not in ('elf', 'mac-o'): 587 return 588 589 link = self.link_task 590 if not re_vnum.match(self.vnum): 591 raise Errors.WafError('Invalid vnum %r for target %r' % (self.vnum, getattr(self, 'name', self))) 592 nums = self.vnum.split('.') 593 node = link.outputs[0] 594 595 cnum = getattr(self, 'cnum', str(nums[0])) 596 cnums = cnum.split('.') 597 if len(cnums)>len(nums) or nums[0:len(cnums)] != cnums: 598 raise Errors.WafError('invalid compatibility version %s' % cnum) 599 600 libname = node.name 601 if libname.endswith('.dylib'): 602 name3 = libname.replace('.dylib', '.%s.dylib' % self.vnum) 603 name2 = libname.replace('.dylib', '.%s.dylib' % cnum) 604 else: 605 name3 = libname + '.' + self.vnum 606 name2 = libname + '.' + cnum 607 608 # add the so name for the ld linker - to disable, just unset env.SONAME_ST 609 if self.env.SONAME_ST: 610 v = self.env.SONAME_ST % name2 611 self.env.append_value('LINKFLAGS', v.split()) 612 613 # the following task is just to enable execution from the build dir :-/ 614 if self.env.DEST_OS != 'openbsd': 615 outs = [node.parent.make_node(name3)] 616 if name2 != name3: 617 outs.append(node.parent.make_node(name2)) 618 self.create_task('vnum', node, outs) 619 620 if getattr(self, 'install_task', None): 621 self.install_task.hasrun = Task.SKIPPED 622 self.install_task.no_errcheck_out = True 623 path = self.install_task.install_to 624 if self.env.DEST_OS == 'openbsd': 625 libname = self.link_task.outputs[0].name 626 t1 = self.add_install_as(install_to='%s/%s' % (path, libname), install_from=node, chmod=self.link_task.chmod) 627 self.vnum_install_task = (t1,) 628 else: 629 t1 = self.add_install_as(install_to=path + os.sep + name3, install_from=node, chmod=self.link_task.chmod) 630 t3 = self.add_symlink_as(install_to=path + os.sep + libname, install_from=name3) 631 if name2 != name3: 632 t2 = self.add_symlink_as(install_to=path + os.sep + name2, install_from=name3) 633 self.vnum_install_task = (t1, t2, t3) 634 else: 635 self.vnum_install_task = (t1, t3) 636 637 if '-dynamiclib' in self.env.LINKFLAGS: 638 # this requires after(propagate_uselib_vars) 639 try: 640 inst_to = self.install_path 641 except AttributeError: 642 inst_to = self.link_task.inst_to 643 if inst_to: 644 p = Utils.subst_vars(inst_to, self.env) 645 path = os.path.join(p, name2) 646 self.env.append_value('LINKFLAGS', ['-install_name', path]) 647 self.env.append_value('LINKFLAGS', '-Wl,-compatibility_version,%s' % cnum) 648 self.env.append_value('LINKFLAGS', '-Wl,-current_version,%s' % self.vnum) 649 650 class vnum(Task.Task): 651 """ 652 Create the symbolic links for a versioned shared library. Instances are created by :py:func:`waflib.Tools.ccroot.apply_vnum` 653 """ 654 color = 'CYAN' 655 ext_in = ['.bin'] 656 def keyword(self): 657 return 'Symlinking' 658 def run(self): 659 for x in self.outputs: 660 path = x.abspath() 661 try: 662 os.remove(path) 663 except OSError: 664 pass 665 666 try: 667 os.symlink(self.inputs[0].name, path) 668 except OSError: 669 return 1 670 671 class fake_shlib(link_task): 672 """ 673 Task used for reading a system library and adding the dependency on it 674 """ 675 def runnable_status(self): 676 for t in self.run_after: 677 if not t.hasrun: 678 return Task.ASK_LATER 679 return Task.SKIP_ME 680 681 class fake_stlib(stlink_task): 682 """ 683 Task used for reading a system library and adding the dependency on it 684 """ 685 def runnable_status(self): 686 for t in self.run_after: 687 if not t.hasrun: 688 return Task.ASK_LATER 689 return Task.SKIP_ME 690 691 @conf 692 def read_shlib(self, name, paths=[], export_includes=[], export_defines=[]): 693 """ 694 Read a system shared library, enabling its use as a local library. Will trigger a rebuild if the file changes:: 695 696 def build(bld): 697 bld.read_shlib('m') 698 bld.program(source='main.c', use='m') 699 """ 700 return self(name=name, features='fake_lib', lib_paths=paths, lib_type='shlib', export_includes=export_includes, export_defines=export_defines) 701 702 @conf 703 def read_stlib(self, name, paths=[], export_includes=[], export_defines=[]): 704 """ 705 Read a system static library, enabling a use as a local library. Will trigger a rebuild if the file changes. 706 """ 707 return self(name=name, features='fake_lib', lib_paths=paths, lib_type='stlib', export_includes=export_includes, export_defines=export_defines) 708 709 lib_patterns = { 710 'shlib' : ['lib%s.so', '%s.so', 'lib%s.dylib', 'lib%s.dll', '%s.dll'], 711 'stlib' : ['lib%s.a', '%s.a', 'lib%s.dll', '%s.dll', 'lib%s.lib', '%s.lib'], 712 } 713 714 @feature('fake_lib') 715 def process_lib(self): 716 """ 717 Find the location of a foreign library. Used by :py:class:`waflib.Tools.ccroot.read_shlib` and :py:class:`waflib.Tools.ccroot.read_stlib`. 718 """ 719 node = None 720 721 names = [x % self.name for x in lib_patterns[self.lib_type]] 722 for x in self.lib_paths + [self.path] + SYSTEM_LIB_PATHS: 723 if not isinstance(x, Node.Node): 724 x = self.bld.root.find_node(x) or self.path.find_node(x) 725 if not x: 726 continue 727 728 for y in names: 729 node = x.find_node(y) 730 if node: 731 try: 732 Utils.h_file(node.abspath()) 733 except EnvironmentError: 734 raise ValueError('Could not read %r' % y) 735 break 736 else: 737 continue 738 break 739 else: 740 raise Errors.WafError('could not find library %r' % self.name) 741 self.link_task = self.create_task('fake_%s' % self.lib_type, [], [node]) 742 self.target = self.name 743 744 745 class fake_o(Task.Task): 746 def runnable_status(self): 747 return Task.SKIP_ME 748 749 @extension('.o', '.obj') 750 def add_those_o_files(self, node): 751 tsk = self.create_task('fake_o', [], node) 752 try: 753 self.compiled_tasks.append(tsk) 754 except AttributeError: 755 self.compiled_tasks = [tsk] 756 757 @feature('fake_obj') 758 @before_method('process_source') 759 def process_objs(self): 760 """ 761 Puts object files in the task generator outputs 762 """ 763 for node in self.to_nodes(self.source): 764 self.add_those_o_files(node) 765 self.source = [] 766 767 @conf 768 def read_object(self, obj): 769 """ 770 Read an object file, enabling injection in libs/programs. Will trigger a rebuild if the file changes. 771 772 :param obj: object file path, as string or Node 773 """ 774 if not isinstance(obj, self.path.__class__): 775 obj = self.path.find_resource(obj) 776 return self(features='fake_obj', source=obj, name=obj.name) 777 778 @feature('cxxprogram', 'cprogram') 779 @after_method('apply_link', 'process_use') 780 def set_full_paths_hpux(self): 781 """ 782 On hp-ux, extend the libpaths and static library paths to absolute paths 783 """ 784 if self.env.DEST_OS != 'hp-ux': 785 return 786 base = self.bld.bldnode.abspath() 787 for var in ['LIBPATH', 'STLIBPATH']: 788 lst = [] 789 for x in self.env[var]: 790 if x.startswith('/'): 791 lst.append(x) 792 else: 793 lst.append(os.path.normpath(os.path.join(base, x))) 794 self.env[var] = lst 795