waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

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