# $Id: boost.jam 62249 2010-05-26 19:05:19Z steven_watanabe $
# Copyright 2008 Roland Schwarz
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)

# Boost library support module.
# 
# This module allows to use the boost library from boost-build projects.
# The location of a boost source tree or the path to a pre-built
# version of the library can be configured from either site-config.jam
# or user-config.jam. If no location  is configured the module looks for
# a BOOST_ROOT environment variable, which should point to a boost source
# tree. As a last resort it tries to use pre-built libraries from the standard
# search path of the compiler.
#
# If the location to a source tree is known, the module can be configured 
# from the *-config.jam files:
#
# using boost : 1.35 : <root>/path-to-boost-root ;
#
# If the location to a pre-built version is known:
#
# using boost : 1.34 
#   : <include>/usr/local/include/boost_1_34 
#     <library>/usr/local/lib
#   ;
#
# It is legal to configure more than one boost library version in the config
# files. The version identifier is used to disambiguate between them.
# The first configured version becomes the default.
#
# To use a boost library you need to put a 'use' statement into your
# Jamfile:
#
# import boost ;
#
# boost.use-project 1.35 ;
#
# If you don't care about a specific version you just can omit the version
# part, in which case the default is picked up:
#
# boost.use-project ;
#
# The library can be referenced with the project identifier '/boost'. To 
# reference the program_options you would specify:
#
# exe myexe : mysrc.cpp : <library>/boost//program_options ;
#
# Note that the requirements are automatically transformed into suitable
# tags to find the correct pre-built library.
#

import re

import bjam

from b2.build import alias, property, property_set, feature
from b2.manager import get_manager
from b2.tools import builtin, common
from b2.util import bjam_signature, regex


# TODO: This is currently necessary in Python Port, but was not in Jam.
feature.feature('layout', ['system', 'versioned', 'tag'], ['optional'])
feature.feature('root', [], ['optional', 'free'])
feature.feature('build-id', [], ['optional', 'free'])

__initialized = None
__boost_auto_config = property_set.create([property.Property('layout', 'system')])
__boost_configured = {}
__boost_default = None
__build_id = None

__debug = None

def debug():
    global __debug
    if __debug is None:
        __debug = "--debug-configuration" in bjam.variable("ARGV")        
    return __debug


# Configuration of the boost library to use.
#
# This can either be a boost source tree or
# pre-built libraries. The 'version' parameter must be a valid boost
# version number, e.g. 1.35, if specifying a pre-built version with
# versioned layout. It may be a symbolic name, e.g. 'trunk' if specifying
# a source tree. The options are specified as named parameters (like
# properties). The following paramters are available:
#
# <root>/path-to-boost-root: Specify a source tree.
#
# <include>/path-to-include: The include directory to search.
#
# <library>/path-to-library: The library directory to search.
# 
# <layout>system or <layout>versioned.
# 
# <build-id>my_build_id: The custom build id to use.
#
def init(version, options = None):
    assert(isinstance(version,list))
    assert(len(version)==1)
    version = version[0]
    if version in __boost_configured:
        get_manager().errors()("Boost {} already configured.".format(version));
    else:
        global __boost_default
        if debug():
            if not __boost_default:
                print "notice: configuring default boost library {}".format(version)
            print "notice: configuring boost library {}".format(version)

        if not __boost_default:
            __boost_default = version
        properties = []
        for option in options:
            properties.append(property.create_from_string(option))
        __boost_configured[ version ] = property_set.PropertySet(properties)

projects = get_manager().projects()
rules = projects.project_rules()


# Use a certain version of the library.
#
# The use-project rule causes the module to define a boost project of
# searchable pre-built boost libraries, or references a source tree
# of the boost library. If the 'version' parameter is omitted either
# the configured default (first in config files) is used or an auto
# configuration will be attempted.
# 
@bjam_signature(([ "version", "?" ], ))
def use_project(version = None):
    projects.push_current( projects.current() )
    if not version:
        version = __boost_default
    if not version:
        version = "auto_config"

    global __initialized
    if __initialized:
        if __initialized != version:
            get_manager().errors()('Attempt to use {} with different parameters'.format('boost'))
    else:
        if version in __boost_configured:
            opts = __boost_configured[ version ]
            root = opts.get('<root>' )
            inc = opts.get('<include>')
            lib = opts.get('<library>')
            
            if debug():
                print "notice: using boost library {} {}".format( version, opt.raw() )

            global __layout
            global __version_tag
            __layout = opts.get('<layout>')
            if not __layout:
                __layout = 'versioned'
            __build_id = opts.get('<build-id>')
            __version_tag = re.sub("[*\\/:.\"\' ]", "_", version)
            __initialized = version

            if ( root and inc ) or \
                ( root and lib ) or \
                ( lib and not inc ) or \
                ( not lib and inc ):
                get_manager().errors()("Ambiguous parameters, use either <root> or <inlude> with <library>.")
            elif not root and not inc:
                root = bjam.variable("BOOST_ROOT")

            module = projects.current().project_module()
            
            if root:
                bjam.call('call-in-module', module, 'use-project', ['boost', root])
            else:
                projects.initialize(__name__)
                if version == '0.0.1':
                    boost_0_0_1( inc, lib )
                else:
                    boost_std( inc, lib )
        else:
            get_manager().errors()("Reference to unconfigured boost version.")
    projects.pop_current()


rules.add_rule( 'boost.use-project', use_project )

def boost_std(inc = None, lib = None):
    # The default definitions for pre-built libraries.
    rules.project(
        ['boost'],
        ['usage-requirements'] + ['<include>{}'.format(i) for i in inc] + ['<define>BOOST_ALL_NO_LIB'],
        ['requirements'] + ['<search>{}'.format(l) for l in lib])

    # TODO: There should be a better way to add a Python function into a
    # project requirements property set.
    tag_prop_set = property_set.create([property.Property('<tag>', tag_std)])
    attributes = projects.attributes(projects.current().project_module())
    attributes.requirements = attributes.requirements.refine(tag_prop_set)
    
    alias('headers')
    
    def boost_lib(lib_name, dyn_link_macro):
        if (isinstance(lib_name,str)):
            lib_name = [lib_name]
        builtin.lib(lib_name, usage_requirements=['<link>shared:<define>{}'.format(dyn_link_macro)])
    
    boost_lib('date_time'           , 'BOOST_DATE_TIME_DYN_LINK'      )
    boost_lib('filesystem'          , 'BOOST_FILE_SYSTEM_DYN_LINK'    )
    boost_lib('graph'               , 'BOOST_GRAPH_DYN_LINK'          )
    boost_lib('graph_parallel'      , 'BOOST_GRAPH_DYN_LINK'          )
    boost_lib('iostreams'           , 'BOOST_IOSTREAMS_DYN_LINK'      )
    boost_lib('locale'              , 'BOOST_LOG_DYN_LINK'            )
    boost_lib('log'                 , 'BOOST_LOG_DYN_LINK'            )
    boost_lib('log_setup'           , 'BOOST_LOG_DYN_LINK'            )
    boost_lib('math_tr1'            , 'BOOST_MATH_TR1_DYN_LINK'       )
    boost_lib('math_tr1f'           , 'BOOST_MATH_TR1_DYN_LINK'       )
    boost_lib('math_tr1l'           , 'BOOST_MATH_TR1_DYN_LINK'       )
    boost_lib('math_c99'            , 'BOOST_MATH_TR1_DYN_LINK'       )
    boost_lib('math_c99f'           , 'BOOST_MATH_TR1_DYN_LINK'       )
    boost_lib('math_c99l'           , 'BOOST_MATH_TR1_DYN_LINK'       )
    boost_lib('mpi'                 , 'BOOST_MPI_DYN_LINK'            )
    boost_lib('program_options'     , 'BOOST_PROGRAM_OPTIONS_DYN_LINK')
    boost_lib('python'              , 'BOOST_PYTHON_DYN_LINK'         )
    boost_lib('python3'             , 'BOOST_PYTHON_DYN_LINK'         )
    boost_lib('random'              , 'BOOST_RANDOM_DYN_LINK'         )
    boost_lib('regex'               , 'BOOST_REGEX_DYN_LINK'          )
    boost_lib('serialization'       , 'BOOST_SERIALIZATION_DYN_LINK'  )
    boost_lib('wserialization'      , 'BOOST_SERIALIZATION_DYN_LINK'  )
    boost_lib('signals'             , 'BOOST_SIGNALS_DYN_LINK'        )
    boost_lib('system'              , 'BOOST_SYSTEM_DYN_LINK'         )
    boost_lib('unit_test_framework' , 'BOOST_TEST_DYN_LINK'           )
    boost_lib('prg_exec_monitor'    , 'BOOST_TEST_DYN_LINK'           )
    boost_lib('test_exec_monitor'   , 'BOOST_TEST_DYN_LINK'           )
    boost_lib('thread'              , 'BOOST_THREAD_DYN_DLL'          )
    boost_lib('wave'                , 'BOOST_WAVE_DYN_LINK'           )

def boost_0_0_1( inc, lib ):
    print "You are trying to use an example placeholder for boost libs." ;
    # Copy this template to another place (in the file boost.jam)
    # and define a project and libraries modelled after the
    # boost_std rule. Please note that it is also possible to have
    # a per version taging rule in case they are different between
    # versions.

def tag_std(name, type, prop_set):
    name = 'boost_' + name
    if 'static' in prop_set.get('<link>') and 'windows' in prop_set.get('<target-os>'):
        name = 'lib' + name
    result = None

    if __layout == 'system':
        versionRe = re.search('^([0-9]+)_([0-9]+)', __version_tag)
        if versionRe and versionRe.group(1) == '1' and int(versionRe.group(2)) < 39:
            result = tag_tagged(name, type, prop_set)
        else:
            result = tag_system(name, type, prop_set)
    elif __layout == 'tagged':
        result = tag_tagged(name, type, prop_set)
    elif __layout == 'versioned':
        result = tag_versioned(name, type, prop_set)
    else:
        get_manager().errors()("Missing layout")
    return result

def tag_maybe(param):
    return ['-{}'.format(param)] if param else []
    
def tag_system(name, type, prop_set):
    return common.format_name(['<base>'] + tag_maybe(__build_id), name, type, prop_set)

def tag_tagged(name, type, prop_set):
    return common.format_name(['<base>', '<threading>', '<runtime>'] + tag_maybe(__build_id), name, type, prop_set)

def tag_versioned(name, type, prop_set):
    return common.format_name(['<base>', '<toolset>', '<threading>', '<runtime>'] + tag_maybe(__version_tag) + tag_maybe(__build_id),
        name, type, prop_set)
