#! /usr/bin/env python
# encoding: utf-8
# copyright 2009 Johan Boule <bohan@jabber.org>
#
# This test builds various combinations of static and shared "modules".
# The program 'main' uses the library 'wrapper', which in turn uses the library 'impl'.
# We build the following "variants" (not variants in the waf-sense):
# - a dynamically linked 'main' program that dynamically links against a shared 'wrapper' library, which dynamically links against a shared 'impl' library,
# - a dynamically linked 'main' program that contains a static 'wrapper' library, which dynamically links against a shared 'impl' library,
# - a dynamically linked 'main' program that contains a static 'wrapper' library, which depends on a static 'impl' library,
# - a statically linked 'main' program that contains a static 'wrapper' library, which depends on a static 'impl' library,
# The case of a shared 'wrapper' library that contains a static 'impl' library is also tested, and needs pic/non-pic trickery.
#
# Once built and installed, on linux, to retrieve the dependencies, use either
# 	readelf -d ++install/{bin,lib}/xxxx | grep NEEDED, or
# 	objdump -x ++install/{bin,lib}/xxxx | grep NEEDED
# On cygwin, use cygcheck ++install/bin/xxxx
# On plain windows, you can use cygcheck too (cygcheck doesn't need cygwin).
#
# The following dependencies are expected (filenames on linux, libstdc++.so.6, libc.so.6 are not shown):
# 	For variant static main, static wrapper, static impl:
# 		static-shared--main-st--wrapper-st--impl-st -> libgcc_s.so.1
# 	For variant static wrapper, static impl:
# 		static-shared--main-sh--wrapper-st--impl-st -> (nothing)
# 	For variant static wrapper, shared impl:
# 		static-shared--main-sh--wrapper-st--impl-sh -> libstatic-shared--impl.so.1
# 	For variant shared wrapper, shared impl:
# 		static-shared--main-sh--wrapper-sh--impl-sh -> libstatic-shared--wrapper--impl-sh.so.1
#		libstatic-shared--wrapper--impl-sh.so -> libstatic-shared--impl.so.1
#
# Equivalent dependencies are expected on cygwin and plain windows (just the filenames change).
#
# If you get results different from the above, please submit a bug report immediately.

import os, Options, Build

APPNAME = 'waf-static-shared'
VERSION = '1.2.3'

top = '.'
out = '++build'

def set_options(opt):
	opt.tool_options('compiler_cxx')

def configure(conf):
	conf.check_tool('compiler_cxx')
	conf.env.PREFIX = os.path.join(conf.blddir, os.pardir, '++install')
	if False: # bug
		for static_impl in (False, True):
			conf.write_config_header('impl-' + (static_impl and 'st' or 'sh') + '/impl/config.hpp')
		for static_wrapper in (False, True):
			conf.write_config_header('wrapper-' + (static_wrapper and 'st' or 'sh') + '/wrapper/config.hpp')
	
def build(bld):
	test_name = 'static-shared' #bld.srcnode.name
	src_dir = 'src'
	
	for static_impl in (False, True):
		for pic_static in static_impl and (True, False) or (False,):
			impl = bld.lib(
				target=test_name + \
					'--impl' + (pic_static and '-pic' or ''),
				types=(static_impl and 'static' or 'shared',),
				sources=[src_dir + '/impl'],
				includes=[src_dir],
				#client_includes=['impl-' + (static_impl and 'st' or 'sh')],
				defines=['IMPL=' + (static_impl and '-1' or '1')]
			)
			if pic_static:
				if impl.env.CXX_NAME == 'gcc': impl.env.append_value('CXXFLAGS', '-fPIC')
				else: pass # TODO
				pic_static_impl = impl
			else: normal_impl = impl
		for static_wrapper in (False, True):
			wrapper = bld.lib(
				target=test_name + \
					'--wrapper' + \
					'--impl-' + (static_impl and 'st' or 'sh'),
				types=(static_wrapper and 'static' or 'shared',),
				sources=[src_dir + '/wrapper'],
				includes=[src_dir],
				#client_includes=['wrapper-' + (static_wrapper and 'st' or 'sh')],
				defines=['WRAPPER=' + (static_wrapper and '-1' or '1'), 'IMPL=' + (static_impl and '-1' or '0')],
				uselib_local=[((not static_wrapper and static_impl) and pic_static_impl or normal_impl).name]
			)
			for static_prog in (False, True):
				if static_prog and not (static_wrapper and static_impl): continue
				bld.program(
					target=test_name + \
						'--main-' + (static_prog and 'st' or 'sh') + \
						'--wrapper-' + (static_wrapper and 'st' or 'sh') + \
						'--impl-' + (static_impl and 'st' or 'sh'),
					types=(static_prog and 'static' or 'shared',),
					sources=[src_dir + '/main'],
					includes=[src_dir],
					defines=['WRAPPER=' + (static_wrapper and '-1' or '0'), 'IMPL=' + (static_impl and '-1' or '0')],
					uselib_local=[wrapper.name]
				)

def program(bld, target, types = ('static', 'shared',), sources=None, includes=None, client_includes=None, defines=None, uselib_local=None, uselib=None):
	return module(bld, target, 'program', types, sources, includes, client_includes, None, defines, uselib_local, uselib)
Build.BuildContext.program = program

def lib(bld, target, types = ('static', 'shared'), sources=None, includes=None, client_includes=None, headers=None, defines=None, uselib_local=None, uselib=None):
	return module(bld, target, 'lib', types, sources, includes, client_includes, headers, defines, uselib_local, uselib)
Build.BuildContext.lib = lib

def module(bld, target, kind, types = ('static', 'shared'), sources=None, includes=None, client_includes=None, headers=None, defines=None, uselib_local=None, uselib=None):
	for type in types:
		if kind == 'program':
			obj = bld(features = 'cxx cprogram')
			obj.target = target
			if len(types) > 1: obj.target += '-' + type
			if type == 'static' and obj.env.CXX_NAME == 'gcc': obj.env.append_value('LINKFLAGS', '-static')
		else:
			tp = {'static': 'cstaticlib', 'shared': 'cshlib'}[type]
			obj = bld(features = 'cxx %s' % tp)
			obj.target = target
			if type == 'shared': obj.vnum = VERSION
		obj.name = target + '-' + type
		obj.source = []
		for s in sources:
			node = bld.path.find_dir(s) # is it a dir?
			if node:
				for f in node.find_iter(in_pat = ['*.cpp']): obj.source.append(f.relpath_gen(bld.path))
			else: obj.source.append(s)
		obj.source.sort()
		if includes: obj.includes = includes
		if client_includes: obj.export_incdirs = client_includes
		if defines: obj.defines = defines
		if type != 'static' and obj.env.DEST_BINFMT == 'elf': obj.rpath = '$ORIGIN:$ORIGIN/../lib'
		if uselib_local: obj.uselib_local = uselib_local
		if uselib: obj.uselib = uselib
	if kind != 'program':
		if headers is None:
			headers = []
			# we search headers in source dirs
			for s in sources:
				node = bld.path.find_dir(s) # is it a dir?
				if not node:
					node = bld.path.find_resource(s) # is it a file?
					if node: node = node.parent
				if not node: print >> std.err, 'not found:', s
				if node:
					node.parent # we add headers from where the source file located
					headers.append(node.relpath_gen(bld.path))
		install_headers(bld, includes, headers)
	if len(types) == 1: return obj
Build.BuildContext.module = module

def install_headers(bld, includes, headers):
	headers_filenames = []
	for h in headers:
		#print 'header search:', h
		node = bld.path.find_dir(h) # is it a dir ?
		if node:
			for pat in '*.hpp', '*.h':
				for f in node.find_iter(in_pat = ['*.hpp']): headers_filenames.append(f.relpath_gen(bld.path))
		else: headers_filenames.append(h)
	#print 'found header files:', headers_filenames
	for i in includes:
		for h in headers_filenames:
			if h.startswith(i):
				bld.install_as('${PREFIX}/include/' + APPNAME + '-' + VERSION + '/' + h[(len(i)):], h)
Build.BuildContext.install_headers = install_headers
