# This file is part of Mapnik (c++ mapping toolkit) # # Copyright (C) 2009 Artem Pavlenko, Jean-Francois Doyon, Dane Springmeyer # # Mapnik is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # # $Id$ import os import sys import re import platform from glob import glob from subprocess import Popen, PIPE from SCons.SConf import SetCacheMode import pickle try: import distutils.sysconfig HAS_DISTUTILS = True except: HAS_DISTUTILS = False #### SCons build options and initial setup #### env = Environment(ENV=os.environ) def color_print(color,text,newline=True): # 1 - red # 2 - green # 3 - yellow # 4 - blue text = "\033[9%sm%s\033[0m" % (color,text) if not newline: print text, else: print text def regular_print(color,text,newline=True): if not newline: print text, else: print text def call(cmd, silent=False): stdin, stderr = Popen(cmd,shell=True,stdout=PIPE,stderr=PIPE).communicate() if not stderr: return stdin.strip() elif not silent: color_print(1,'Problem encounted with SCons scripts, please post bug report to: http://trac.mapnik.org\nError was: %s' % stderr) def shortest_name(libs): name = '-'*200 for lib in libs: if len(name) > len(lib): name = lib return name if platform.uname()[4] == 'x86_64' and platform.system() == 'Linux': LIBDIR_SCHEMA='lib64' elif platform.uname()[4] == 'ppc64': LIBDIR_SCHEMA='lib64' else: LIBDIR_SCHEMA='lib' pretty_dep_names = { 'ociei':'Oracle database library | configure with OCCI_LIBS & OCCI_INCLUDES | more info: http://trac.mapnik.org/wiki/OCCI', 'gdal':'GDAL C++ library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: http://trac.mapnik.org/wiki/GDAL', 'ogr':'OGR-enabled GDAL C++ Library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: http://trac.mapnik.org/wiki/OGR', 'cairo':'Cairo C library | configured using pkg-config | try setting PKG_CONFIG_PATH SCons option', 'cairomm':'Cairomm C++ bindings to Cairo library | configured using pkg-config | try setting PKG_CONFIG_PATH SCons option', 'pycairo':'Python bindings to Cairo library | configured using pkg-config | try setting PKG_CONFIG_PATH SCons option', 'proj':'Proj.4 C Projections library | configure with PROJ_LIBS & PROJ_INCLUDES | more info: http://trac.osgeo.org/proj/', 'pg':'Postgres C Library requiered for PostGIS plugin | configure with pg_config program | more info: http://trac.mapnik.org/wiki/PostGIS', 'sqlite3':'SQLite3 C Library | configure with SQLITE_LIBS & SQLITE_INCLUDES | more info: http://trac.mapnik.org/wiki/SQLite', 'osm':'more info: http://trac.mapnik.org/wiki/OsmPlugin', 'jpeg':'JPEG C library | configure with JPEG_LIBS & JPEG_INCLUDES', 'tiff':'TIFF C library | configure with TIFF_LIBS & TIFF_INCLUDES', 'png':'PNG C library | configure with PNG_LIBS & PNG_INCLUDES', 'icuuc':'ICU C++ library | configure with ICU_LIBS & ICU_INCLUDES or use ICU_LIB_NAME to specify custom lib name | more info: http://site.icu-project.org/', 'ltdl':'GNU Libtool | more info: http://www.gnu.org/software/libtool', 'z':'Z compression library | more info: http://www.zlib.net/', 'm':'Basic math library, part of C++ stlib', 'pkg-config':'pkg-config tool | more info: http://pkg-config.freedesktop.org', 'pg_config':'pg_config program | try setting PG_CONFIG SCons option', 'xml2-config':'xml2-config program | try setting XML2_CONFIG SCons option', 'gdal-config':'gdal-config program | try setting GDAL_CONFIG SCons option', 'freetype-config':'freetype-config program | try setting FREETYPE_CONFIG SCons option', } def pretty_dep(dep): pretty = pretty_dep_names.get(dep) if pretty: return '%s (%s)' % (dep,pretty) elif 'boost' in dep: return '%s (%s)' % (dep,'more info see: http://trac.mapnik.org/wiki/MapnikInstallation & http://www.boost.org') return dep # local file to hold custom user configuration variables SCONS_LOCAL_CONFIG = 'config.py' # local pickled file to cache configured environment SCONS_CONFIGURE_CACHE = 'config.cache' # directory SCons uses to stash build tests SCONF_TEMP_DIR = '.sconf_temp' # auto-search directories for boost libs/headers BOOST_SEARCH_PREFIXES = ['/usr/local','/opt/local','/sw','/usr',] # Core plugin build configuration # opts.AddVariables still hardcoded however... PLUGINS = { # plugins with external dependencies 'postgis': {'default':True,'path':'PGSQL','inc':'libpq-fe.h','lib':'pq','lang':'C'}, 'gdal': {'default':False,'path':None,'inc':'gdal_priv.h','lib':'gdal','lang':'C++'}, 'ogr': {'default':False,'path':None,'inc':'ogrsf_frmts.h','lib':'gdal','lang':'C++'}, 'occi': {'default':False,'path':'OCCI','inc':'occi.h','lib':'ociei','lang':'C++'}, 'sqlite': {'default':False,'path':'SQLITE','inc':'sqlite3.h','lib':'sqlite3','lang':'C'}, # todo: osm plugin does depend on libxml2 and libcurl 'osm': {'default':False,'path':None,'inc':None,'lib':None,'lang':'C++'}, # plugins without external dependencies requiring CheckLibWithHeader... 'shape': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'raster': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}, 'kismet': {'default':False,'path':None,'inc':None,'lib':None,'lang':'C++'}, } DEFAULT_PLUGINS = [] for k,v in PLUGINS.items(): if v['default']: DEFAULT_PLUGINS.append(k) # All of the following options may be modified at the command-line, for example: # `python scons/scons.py PREFIX=/opt` opts = Variables() opts.AddVariables( # Compiler options ('CXX', 'The C++ compiler to use (defaults to g++).', 'g++'), EnumVariable('OPTIMIZATION','Set g++ optimization level','2', ['0','1','2','3','4']), # Note: setting DEBUG=True will override any custom OPTIMIZATION level BoolVariable('DEBUG', 'Compile a debug version of Mapnik', 'False'), BoolVariable('XML_DEBUG', 'Compile a XML verbose debug version of mapnik', 'False'), ListVariable('INPUT_PLUGINS','Input drivers to include',DEFAULT_PLUGINS,PLUGINS.keys()), # SCons build behavior options ('CONFIG', "The path to the python file in which to save user configuration options. Currently : '%s'" % SCONS_LOCAL_CONFIG,SCONS_LOCAL_CONFIG), BoolVariable('USE_CONFIG', "Use SCons user '%s' file (will also write variables after successful configuration)", 'True'), BoolVariable('FAST', "Make scons faster at the cost of less precise dependency tracking", 'False'), # Install Variables ('PREFIX', 'The install path "prefix"', '/usr/local'), ('PYTHON_PREFIX','Custom install path "prefix" for python bindings (default of no prefix)',''), ('DESTDIR', 'The root directory to install into. Useful mainly for binary package building', '/'), # Boost variables # default is '/usr/include', see FindBoost method below ('BOOST_INCLUDES', 'Search path for boost include files', '',False), # default is '/usr/' + LIBDIR_SCHEMA, see FindBoost method below ('BOOST_LIBS', 'Search path for boost library files', '',False), ('BOOST_TOOLKIT','Specify boost toolkit, e.g., gcc41.','',False), ('BOOST_ABI', 'Specify boost ABI, e.g., d.','',False), ('BOOST_VERSION','Specify boost version, e.g., 1_35.','',False), ('BOOST_PYTHON_LIB','Specify library name or full path to boost_python lib (e.g. "boost_python-py26" or "/usr/lib/libboost_python.dylib")',''), # Variables for required dependencies ('FREETYPE_CONFIG', 'The path to the freetype-config executable.', 'freetype-config'), ('XML2_CONFIG', 'The path to the xml2-config executable.', 'xml2-config'), PathVariable('ICU_INCLUDES', 'Search path for ICU include files', '/usr/include', PathVariable.PathAccept), PathVariable('ICU_LIBS','Search path for ICU include files','/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), ('ICU_LIB_NAME', 'The library name for icu (such as icuuc, sicuuc, or icucore)', 'icuuc'), PathVariable('PNG_INCLUDES', 'Search path for libpng include files', '/usr/include', PathVariable.PathAccept), PathVariable('PNG_LIBS','Search path for libpng include files','/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), PathVariable('JPEG_INCLUDES', 'Search path for libjpeg include files', '/usr/include', PathVariable.PathAccept), PathVariable('JPEG_LIBS', 'Search path for libjpeg library files', '/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), PathVariable('TIFF_INCLUDES', 'Search path for libtiff include files', '/usr/include', PathVariable.PathAccept), PathVariable('TIFF_LIBS', 'Search path for libtiff library files', '/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), PathVariable('PROJ_INCLUDES', 'Search path for PROJ.4 include files', '/usr/local/include', PathVariable.PathAccept), PathVariable('PROJ_LIBS', 'Search path for PROJ.4 library files', '/usr/local/' + LIBDIR_SCHEMA, PathVariable.PathAccept), ('PKG_CONFIG_PATH', 'Use this path to point pkg-config to .pc files instead of the PKG_CONFIG_PATH environment setting',''), # Variables affecting rendering back-ends BoolVariable('INTERNAL_LIBAGG', 'Use provided libagg', 'True'), # Variables for optional dependencies # Note: cairo, cairomm, and pycairo all optional but configured automatically through pkg-config # Therefore, we use a single boolean for whether to attempt to build cairo support. BoolVariable('CAIRO', 'Attempt to build with Cairo rendering support', 'True'), ('GDAL_CONFIG', 'The path to the gdal-config executable for finding gdal and ogr details.', 'gdal-config'), ('PG_CONFIG', 'The path to the pg_config executable.', 'pg_config'), PathVariable('OCCI_INCLUDES', 'Search path for OCCI include files', '/usr/lib/oracle/10.2.0.3/client/include', PathVariable.PathAccept), PathVariable('OCCI_LIBS', 'Search path for OCCI library files', '/usr/lib/oracle/10.2.0.3/client/'+ LIBDIR_SCHEMA, PathVariable.PathAccept), PathVariable('SQLITE_INCLUDES', 'Search path for SQLITE include files', '/usr/include/', PathVariable.PathAccept), PathVariable('SQLITE_LIBS', 'Search path for SQLITE library files', '/usr/' + LIBDIR_SCHEMA, PathVariable.PathAccept), # Other variables BoolVariable('SHAPE_MEMORY_MAPPED_FILE', 'Utilize memory-mapped files in Shapefile Plugin (higher memory usage, better performance)', 'True'), ('SYSTEM_FONTS','Provide location for python bindings to register fonts (if given aborts installation of bundled DejaVu fonts)',''), ('LIB_DIR_NAME','Name to use for the "lib" folder where fonts and plugins are installed','/mapnik/'), PathVariable('PYTHON','Full path to Python executable used to build bindings', sys.executable), BoolVariable('FRAMEWORK_PYTHON', 'Link against Framework Python on Mac OS X', 'True'), ('FRAMEWORK_SEARCH_PATH','Custom framework search path on Mac OS X', ''), BoolVariable('FULL_LIB_PATH', 'Use the full path for the libmapnik.dylib "install_name" when linking on Mac OS X', 'True'), ListVariable('BINDINGS','Language bindings to build','all',['python']), EnumVariable('THREADING','Set threading support','multi', ['multi','single']), EnumVariable('XMLPARSER','Set xml parser ','libxml2', ['tinyxml','spirit','libxml2']), ('JOBS', 'Set the number of parallel compilations', "1", lambda key, value, env: int(value), int), BoolVariable('DEMO', 'Compile demo c++ application', 'False'), BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', 'False'), BoolVariable('COLOR_PRINT', 'Print build status information in color', 'True'), ) # variables to pickle after successful configure step # these include all scons core variables as well as custom # env variables needed in Sconscript files pickle_store = [# Scons internal variables 'CC', 'CXX', 'CCFLAGS', 'CPPDEFINES', 'CPPFLAGS', 'CPPPATH', 'CXXFLAGS', 'LIBPATH', 'LIBS', 'LINKFLAGS', # Mapnik's SConstruct build variables 'PLUGINS', 'ABI_VERSION', 'PLATFORM', 'BOOST_ABI', 'BOOST_APPEND', 'LIBDIR_SCHEMA', 'REQUESTED_PLUGINS', 'SUNCC', 'PYTHON_VERSION', 'PYTHON_INCLUDES', 'PYTHON_INSTALL_LOCATION', 'PYTHON_SYS_PREFIX', 'COLOR_PRINT', 'BOOST_SYSTEM_REQUIRED', ] # Add all other user configurable options to pickle pickle_store # We add here more options than are needed for the build stage # but helpful so that scons -h shows the exact cached options for opt in opts.options: if opt.key not in pickle_store: pickle_store.append(opt.key) # Method of adding configure behavior to Scons adapted from: # http://freeorion.svn.sourceforge.net/svnroot/freeorion/trunk/FreeOrion/SConstruct preconfigured = False force_configure = False command_line_args = sys.argv[1:] HELP_REQUESTED = False if ('-h' in command_line_args) or ('--help' in command_line_args): HELP_REQUESTED = True if 'configure' in command_line_args: force_configure = True elif HELP_REQUESTED: preconfigured = True # this is just to ensure config gets skipped when help is requested # initially populate environment with defaults and any possible custom arguments opts.Update(env) if env.has_key('COLOR_PRINT') and env['COLOR_PRINT'] == False: color_print = regular_print if sys.platform == "win32": color_print = regular_print color_print(4,'\nWelcome to Mapnik...\n') # if we are not configuring overwrite environment with pickled settings if not force_configure: if os.path.exists(SCONS_CONFIGURE_CACHE): try: pickled_environment = open(SCONS_CONFIGURE_CACHE, 'r') pickled_values = pickle.load(pickled_environment) for key, value in pickled_values.items(): env[key] = value preconfigured = True except: # unpickling failed, so reconfigure as fallback preconfigured = False else: preconfigured = False # if custom arguments are supplied make sure to accept them if opts.args: # since we have custom arguments update environment with all opts to # make sure to absorb the custom ones opts.Update(env) # now since we've got custom arguments we'll disregard any # pickled environment and force another configuration preconfigured = False elif preconfigured: if not HELP_REQUESTED: color_print(4,'Using previous successful configuration...') color_print(4,'Re-configure by running "python scons/scons.py configure".') #### Custom Configure Checks ### def CheckPKGConfig(context, version): context.Message( 'Checking for pkg-config... ' ) ret = context.TryAction('pkg-config --atleast-pkgconfig-version=%s' % version)[0] context.Result( ret ) return ret def CheckPKG(context, name): context.Message( 'Checking for %s... ' % name ) ret = context.TryAction('pkg-config --exists \'%s\'' % name)[0] context.Result( ret ) return ret def parse_config(context, config, checks='--libs --cflags'): env = context.env tool = config.lower().replace('_','-') toolname = tool if config == 'GDAL_CONFIG': toolname += ' %s' % checks context.Message( 'Checking for %s... ' % toolname) cmd = '%s %s' % (env[config],checks) ret = context.TryAction(cmd)[0] parsed = False if ret: try: env.ParseConfig(cmd) parsed = True except OSError, e: ret = False print ' (xml2-config not found!)' if not parsed: if config == 'GDAL_CONFIG': env['SKIPPED_DEPS'].append(tool) conf.rollback_option('GDAL_CONFIG') else: env['MISSING_DEPS'].append(tool) context.Result( ret ) return ret def get_pkg_lib(context, config, lib): libpattern = r'-l([^\s]*)' libname = None env = context.env context.Message( 'Checking for name of %s library... ' % lib) cmd = '%s --libs' % env[config] ret = context.TryAction(cmd)[0] parsed = False if ret: try: libnames = re.findall(libpattern,call(cmd,silent=True)) if libnames: libname = libnames[0] except Exception, e: ret = False print ' unable to determine library name:'# %s' % str(e) return None context.Result( libname ) return libname def parse_pg_config(context, config): env = context.env tool = config.lower() context.Message( 'Checking for %s... ' % tool) ret = context.TryAction(env[config])[0] if ret: lib_path = call('%s --libdir' % env[config]) inc_path = call('%s --includedir' % env[config]) env.AppendUnique(CPPPATH = inc_path) env.AppendUnique(LIBPATH = lib_path) lpq = env['PLUGINS']['postgis']['lib'] env.Append(LIBS = lpq) else: env['SKIPPED_DEPS'].append(tool) conf.rollback_option(config) context.Result( ret ) return ret def ogr_enabled(context): env = context.env context.Message( 'Checking if gdal is ogr enabled... ') ret = context.TryAction('%s --ogr-enabled' % env['GDAL_CONFIG'])[0] if not ret: env['SKIPPED_DEPS'].append('ogr') context.Result( ret ) return ret def rollback_option(context,variable): global opts env = context.env for item in opts.options: if item.key == variable: env[variable] = item.default def FindBoost(context, prefixes, thread_flag): """Routine to auto-find boost header dir, lib dir, and library naming structure. """ context.Message( 'Searching for boost libs and headers... ' ) env = context.env BOOST_LIB_DIR = None BOOST_INCLUDE_DIR = None BOOST_APPEND = None env['BOOST_APPEND'] = str() if env['THREADING'] == 'multi': search_lib = 'libboost_thread' else: search_lib = 'libboost_filesystem' prefixes.insert(0,os.path.dirname(env['BOOST_INCLUDES'])) prefixes.insert(0,os.path.dirname(env['BOOST_LIBS'])) for searchDir in prefixes: libItems = glob(os.path.join(searchDir, LIBDIR_SCHEMA, '%s*.*' % search_lib)) if not libItems: libItems = glob(os.path.join(searchDir, 'lib/%s*.*' % search_lib)) incItems = glob(os.path.join(searchDir, 'include/boost*/')) if len(libItems) >= 1 and len(incItems) >= 1: BOOST_LIB_DIR = os.path.dirname(libItems[0]) BOOST_INCLUDE_DIR = incItems[0].rstrip('boost/') shortest_lib_name = shortest_name(libItems) match = re.search(r'%s(.*)\..*' % search_lib, shortest_lib_name) if hasattr(match,'groups'): BOOST_APPEND = match.groups()[0] break msg = str() if BOOST_LIB_DIR: msg += '\n *libs found: %s' % BOOST_LIB_DIR env['BOOST_LIBS'] = BOOST_LIB_DIR else: env['BOOST_LIBS'] = '/usr/' + LIBDIR_SCHEMA msg += '\n *using default boost lib dir: %s' % env['BOOST_LIBS'] if BOOST_INCLUDE_DIR: msg += '\n *headers found: %s' % BOOST_INCLUDE_DIR env['BOOST_INCLUDES'] = BOOST_INCLUDE_DIR else: env['BOOST_INCLUDES'] = '/usr/include' msg += '\n *using default boost include dir: %s' % env['BOOST_INCLUDES'] if not env['BOOST_TOOLKIT'] and not env['BOOST_ABI'] and not env['BOOST_VERSION']: if BOOST_APPEND: msg += '\n *lib naming extension found: %s' % BOOST_APPEND env['BOOST_APPEND'] = BOOST_APPEND else: msg += '\n *no lib naming extension found' else: # Creating BOOST_APPEND according to the Boost library naming order, # which goes ---. See: # http://www.boost.org/doc/libs/1_35_0/more/getting_started/unix-variants.html#library-naming append_params = [''] if env['BOOST_TOOLKIT']: append_params.append(env['BOOST_TOOLKIT']) if thread_flag: append_params.append(thread_flag) if env['BOOST_ABI']: append_params.append(env['BOOST_ABI']) if env['BOOST_VERSION']: append_params.append(env['BOOST_VERSION']) # Constructing the BOOST_APPEND setting that will be used to find the # Boost libraries. if len(append_params) > 1: env['BOOST_APPEND'] = '-'.join(append_params) msg += '\n *using boost lib naming: %s' % env['BOOST_APPEND'] env.AppendUnique(CPPPATH = env['BOOST_INCLUDES']) env.AppendUnique(LIBPATH = env['BOOST_LIBS']) if env['COLOR_PRINT']: msg = "\033[94m%s\033[0m" % (msg) ret = context.Result(msg) return ret def CheckBoost(context, version, silent=False): # Boost versions are in format major.minor.subminor v_arr = version.split(".") version_n = 0 if len(v_arr) > 0: version_n += int(v_arr[0])*100000 if len(v_arr) > 1: version_n += int(v_arr[1])*100 if len(v_arr) > 2: version_n += int(v_arr[2]) if not silent: context.Message('Checking for Boost version >= %s... ' % (version)) ret = context.TryRun(""" #include int main() { return BOOST_VERSION >= %d ? 0 : 1; } """ % version_n, '.cpp')[0] if silent: context.did_show_result=1 context.Result(ret) return ret def GetBoostLibVersion(context): ret = context.TryRun(""" #include #include int main() { std::cout << BOOST_LIB_VERSION << std::endl; return 0; } """, '.cpp') # hack to avoid printed output context.did_show_result=1 context.Result(ret[0]) return ret[1].strip() def GetMapnikLibVersion(context): ret = context.TryRun(""" #include #include int main() { std::cout << MAPNIK_VERSION << std::endl; return 0; } """, '.cpp') # hack to avoid printed output context.did_show_result=1 context.Result(ret[0]) if not ret[1]: return [] version = int(ret[1].strip()) patch_level = version % 100 minor_version = version / 100 % 1000 major_version = version / 100000 return [major_version,minor_version,patch_level] conf_tests = { 'CheckPKGConfig' : CheckPKGConfig, 'CheckPKG' : CheckPKG, 'FindBoost' : FindBoost, 'CheckBoost' : CheckBoost, 'GetBoostLibVersion' : GetBoostLibVersion, 'GetMapnikLibVersion' : GetMapnikLibVersion, 'parse_config' : parse_config, 'parse_pg_config' : parse_pg_config, 'ogr_enabled': ogr_enabled, 'get_pkg_lib': get_pkg_lib, 'rollback_option': rollback_option } if not preconfigured: color_print(4,'Configuring build environment...') if not env['FAST']: SetCacheMode('force') if env['USE_CONFIG']: if not env['CONFIG'].endswith('.py'): color_print(1,'SCons CONFIG file specified is not a python file, will not be read...') else: # Accept more than one file as comma-delimited list user_confs = env['CONFIG'].split(',') # If they exist add the files to the existing `opts` for conf in user_confs: if os.path.exists(conf): opts.files.append(conf) color_print(4,"SCons CONFIG found: '%s', variables will be inherited..." % conf) optfile = file(conf) print optfile.read().replace("\n", " ").replace("'","").replace(" = ","=") optfile.close() elif not conf == SCONS_LOCAL_CONFIG: # if default missing, no worries # but if the default is overridden and the file is not found, give warning color_print(1,"SCons CONFIG not found: '%s'" % conf) # Recreate the base environment using modified `opts` env = Environment(ENV=os.environ,options=opts) env['USE_CONFIG'] = True else: color_print(4,'SCons USE_CONFIG specified as false, will not inherit variables python config file...') conf = Configure(env, custom_tests = conf_tests) if env['DEBUG']: mode = 'debug mode' else: mode = 'release mode' if env['XML_DEBUG']: mode += ' (with XML debug on)' env['PLATFORM'] = platform.uname()[0] color_print(4,"Configuring on %s in *%s*..." % (env['PLATFORM'],mode)) env['MISSING_DEPS'] = [] env['SKIPPED_DEPS'] = [] env['LIBDIR_SCHEMA'] = LIBDIR_SCHEMA env['PLUGINS'] = PLUGINS if env['PKG_CONFIG_PATH']: env['ENV']['PKG_CONFIG_PATH'] = env['PKG_CONFIG_PATH'] # otherwise this variable == os.environ["PKG_CONFIG_PATH"] thread_suffix = 'mt' if env['PLATFORM'] == 'FreeBSD': thread_suffix = '' env.Append(LIBS = 'pthread') if env['SYSTEM_FONTS']: if not os.path.isdir(env['SYSTEM_FONTS']): color_print(1,'Warning: Directory specified for SYSTEM_FONTS does not exist!') #### Libraries and headers dependency checks #### # Set up for libraries and headers dependency checks env['CPPPATH'] = ['#include', '#'] env['LIBPATH'] = ['#src'] # Solaris & Sun Studio settings (the `SUNCC` flag will only be # set if the `CXX` option begins with `CC`) SOLARIS = env['PLATFORM'] == 'SunOS' env['SUNCC'] = SOLARIS and env['CXX'].startswith('CC') # For Solaris include paths (e.g., for freetype2, ltdl, etc.). if SOLARIS: blastwave_dir = '/opt/csw/%s' env.AppendUnique(CPPPATH = blastwave_dir % 'include') env.AppendUnique(LIBPATH = blastwave_dir % LIBDIR_SCHEMA) # If the Sun Studio C++ compiler (`CC`) is used instead of GCC. if env['SUNCC']: env['CC'] = 'cc' # To be compatible w/Boost everything needs to be compiled # with the `-library=stlport4` flag (which needs to come # before the `-o` flag). env['CXX'] = 'CC -library=stlport4' if env['THREADING'] == 'multi': env['CXXFLAGS'] = ['-mt'] # Adding the required prerequisite library directories to the include path for # compiling and the library path for linking, respectively. for required in ('PNG', 'JPEG', 'TIFF','PROJ','ICU'): inc_path = env['%s_INCLUDES' % required] lib_path = env['%s_LIBS' % required] env.AppendUnique(CPPPATH = inc_path) env.AppendUnique(LIBPATH = lib_path) conf.parse_config('FREETYPE_CONFIG') if env['XMLPARSER'] == 'tinyxml': env['CPPPATH'].append('#tinyxml') env.Append(CXXFLAGS = '-DBOOST_PROPERTY_TREE_XML_PARSER_TINYXML -DTIXML_USE_STL') elif env['XMLPARSER'] == 'libxml2': if conf.parse_config('XML2_CONFIG'): env.Append(CXXFLAGS = '-DHAVE_LIBXML2') if env['CAIRO'] and conf.CheckPKGConfig('0.15.0') and conf.CheckPKG('cairomm-1.0'): env.ParseConfig('pkg-config --libs --cflags cairomm-1.0') env.Append(CXXFLAGS = '-DHAVE_CAIRO') else: env['SKIPPED_DEPS'].extend(['cairo','cairomm']) LIBSHEADERS = [ ['m', 'math.h', True,'C'], ['ltdl', 'ltdl.h', True,'C'], ['png', 'png.h', True,'C'], ['tiff', 'tiff.h', True,'C'], ['z', 'zlib.h', True,'C'], ['jpeg', ['stdio.h', 'jpeglib.h'], True,'C'], ['proj', 'proj_api.h', True,'C'], [env['ICU_LIB_NAME'],'unicode/unistr.h',True,'C++'], ] # allow for mac osx /usr/lib/libicucore.dylib compatibility # requires custom supplied headers since Apple does not include them # details: http://lists.apple.com/archives/xcode-users/2005/Jun/msg00633.html # To use system lib download and make && make install one of these: # http://www.opensource.apple.com/tarballs/ICU/ # then copy the headers to a location that mapnik will find if env['ICU_LIB_NAME'] == 'icucore': env.Append(CXXFLAGS = '-DU_HIDE_DRAFT_API') env.Append(CXXFLAGS = '-DUDISABLE_RENAMING') for libinfo in LIBSHEADERS: if not conf.CheckLibWithHeader(libinfo[0], libinfo[1], libinfo[3]): if libinfo[2]: color_print(1,'Could not find required header or shared library for %s' % libinfo[0]) env['MISSING_DEPS'].append(libinfo[0]) else: color_print(4,'Could not find optional header or shared library for %s' % libinfo[0]) env['SKIPPED_DEPS'].append(libinfo[0]) if env['THREADING'] == 'multi': thread_flag = thread_suffix else: thread_flag = '' conf.FindBoost(BOOST_SEARCH_PREFIXES,thread_flag) # get boost version from boost headers rather than previous approach # of fetching from the user provided INCLUDE path boost_system_required = False boost_lib_version_from_header = conf.GetBoostLibVersion() if boost_lib_version_from_header: boost_version_from_header = int(boost_lib_version_from_header.split('_')[1]) if boost_version_from_header >= 35 and env['PLATFORM'] == 'Darwin': boost_system_required = True env['BOOST_SYSTEM_REQUIRED'] = True else: boost_system_required = False env['BOOST_SYSTEM_REQUIRED'] = False # The other required boost headers. BOOST_LIBSHEADERS = [ ['system', 'boost/system/system_error.hpp', boost_system_required], ['filesystem', 'boost/filesystem/operations.hpp', True], ['regex', 'boost/regex.hpp', True], ['iostreams','boost/iostreams/device/mapped_file.hpp',True], ['program_options', 'boost/program_options.hpp', False] ] if env['THREADING'] == 'multi': BOOST_LIBSHEADERS.append(['thread', 'boost/thread/mutex.hpp', True]) # if the user is not setting custom boost configuration # enforce boost version greater than or equal to 1.34 if not conf.CheckBoost('1.34'): color_print (1,'Boost version 1.34 or greater is requred') if not env['BOOST_VERSION']: env['MISSING_DEPS'].append('boost version >=1.34') else: color_print (4,'Found boost lib version... %s' % boost_lib_version_from_header ) for count, libinfo in enumerate(BOOST_LIBSHEADERS): if not conf.CheckLibWithHeader('boost_%s%s' % (libinfo[0],env['BOOST_APPEND']), libinfo[1], 'C++'): if libinfo[2]: color_print(1,'Could not find required header or shared library for boost %s' % libinfo[0]) env['MISSING_DEPS'].append('boost ' + libinfo[0]) else: color_print(4,'Could not find optional header or shared library for boost %s' % libinfo[0]) env['SKIPPED_DEPS'].append('boost ' + libinfo[0]) env['REQUESTED_PLUGINS'] = [ driver.strip() for driver in Split(env['INPUT_PLUGINS'])] color_print(4,'Checking for requested plugins dependencies...') for plugin in env['REQUESTED_PLUGINS']: details = env['PLUGINS'][plugin] if plugin == 'gdal': if conf.parse_config('GDAL_CONFIG',checks='--libs'): conf.parse_config('GDAL_CONFIG',checks='--cflags') libname = conf.get_pkg_lib('GDAL_CONFIG','gdal') if libname: details['lib'] = libname elif plugin == 'postgis': conf.parse_pg_config('PG_CONFIG') elif plugin == 'ogr': if conf.ogr_enabled(): if not 'gdal' in env['REQUESTED_PLUGINS']: conf.parse_config('GDAL_CONFIG',checks='--libs') conf.parse_config('GDAL_CONFIG',checks='--cflags') libname = conf.get_pkg_lib('GDAL_CONFIG','ogr') if libname: details['lib'] = libname elif details['path'] and details['lib'] and details['inc']: backup = env.Clone().Dictionary() # Note, the 'delete_existing' keyword makes sure that these paths are prepended # to the beginning of the path list even if they already exist incpath = env['%s_INCLUDES' % details['path']] env.PrependUnique(CPPPATH = incpath,delete_existing=True) env.PrependUnique(LIBPATH = env['%s_LIBS' % details['path']],delete_existing=True) if not conf.CheckLibWithHeader(details['lib'], details['inc'], details['lang']): env.Replace(**backup) env['SKIPPED_DEPS'].append(details['lib']) # re-append the local paths for mapnik sources to the beginning of the list # to make sure they come before any plugins that were 'prepended' env.PrependUnique(CPPPATH = ['#include', '#'], delete_existing=True) env.PrependUnique(LIBPATH = '#src', delete_existing=True) # Decide which libagg to use # if we are using internal agg, then prepend to make sure # we link locally if env['INTERNAL_LIBAGG']: env.Prepend(CPPPATH = '#agg/include') env.Prepend(LIBPATH = '#agg') else: env.ParseConfig('pkg-config --libs --cflags libagg') if 'python' in env['BINDINGS']: # checklibwithheader does not work for boost_python since we can't feed it # multiple header files, so we fall back on a simple check for boost_python headers if not conf.CheckHeader(header='boost/python/detail/config.hpp',language='C++'): color_print(1,'Could not find required header files for boost python') env['MISSING_DEPS'].append('boost python') if env['CAIRO'] and conf.CheckPKGConfig('0.15.0') and conf.CheckPKG('pycairo'): env.ParseConfig('pkg-config --cflags pycairo') env.Append(CXXFLAGS = '-DHAVE_PYCAIRO') else: env['SKIPPED_DEPS'].extend(['pycairo']) #### End Config Stage for Required Dependencies #### if env['MISSING_DEPS']: # if required dependencies are missing, print warnings and then let SCons finish without building or saving local config color_print(1,'\nExiting... the following required dependencies were not found:\n - %s' % '\n - '.join([pretty_dep(dep) for dep in env['MISSING_DEPS']])) color_print(1,"\nSee the 'config.log' for details on possible problems.") if env['SKIPPED_DEPS']: color_print(4,'\nAlso, these OPTIONAL dependencies were not found:\n - %s' % '\n - '.join([pretty_dep(dep) for dep in env['SKIPPED_DEPS']])) color_print(4,"\nSet custom paths to these libraries and header files on the command-line or in a file called '%s'" % SCONS_LOCAL_CONFIG) color_print(4," ie. $ python scons/scons.py BOOST_INCLUDES=/usr/local/include/ BOOST_LIBS=/usr/local/lib") color_print(4, "\nOnce all required dependencies are found a local '%s' will be saved and then install:" % SCONS_LOCAL_CONFIG) color_print(4," $ sudo python scons/scons.py install") color_print(4,"\nTo view available path variables:\n $ python scons/scons.py --help or -h") color_print(4,'\nTo view overall SCons help options:\n $ python scons/scons.py --help-options or -H\n') color_print(4,'More info: http://trac.mapnik.org/wiki/MapnikInstallation') if not HELP_REQUESTED: Exit(1) else: # Save the custom variables in a SCONS_LOCAL_CONFIG # that will be reloaded to allow for `install` without re-specifying custom variables color_print(4,"\nAll Required dependencies found!\n") if env['USE_CONFIG']: if os.path.exists(SCONS_LOCAL_CONFIG): action = 'Overwriting and re-saving' os.unlink(SCONS_LOCAL_CONFIG) else: action = 'Saving new' color_print(4,"%s file '%s'..." % (action,SCONS_LOCAL_CONFIG)) color_print(4,"Will hold custom path variables from commandline and python config file(s)...") opts.Save(SCONS_LOCAL_CONFIG,env) else: color_print(4,"Did not use user config file, no custom path variables will be saved...") if env['SKIPPED_DEPS']: color_print(3,'\nNote: will build without these OPTIONAL dependencies:\n - %s' % '\n - '.join([pretty_dep(dep) for dep in env['SKIPPED_DEPS']])) print # fetch the mapnik version header in order to set the # ABI version used to build libmapnik.so on linux in src/SConscript abi = conf.GetMapnikLibVersion() abi_fallback = [0,6,1] if not abi: color_print(1,'Problem encountered parsing mapnik version, falling back to %s' % abi_fallback) env['ABI_VERSION'] = abi_fallback else: env['ABI_VERSION'] = abi # Common C++ flags. if env['THREADING'] == 'multi' : common_cxx_flags = '-D%s -DBOOST_SPIRIT_THREADSAFE -DMAPNIK_THREADSAFE ' % env['PLATFORM'].upper() else : common_cxx_flags = '-D%s ' % env['PLATFORM'].upper() svn_version = call('svnversion') if not svn_version == 'exported': pattern = r'(\d+)(.*)' try: rev = re.match(pattern,svn_version).groups()[0] common_cxx_flags += '-DSVN_REVISION=%s ' % rev except: pass # Mac OSX (Darwin) special settings if env['PLATFORM'] == 'Darwin': pthread = '' # Getting the macintosh version number, sticking as a compiler macro # for Leopard -- needed because different workarounds are needed than # for Tiger. # this was used for fribidi - not longer needed # but will retain logic for future use #if platform.mac_ver()[0].startswith('10.5'): # common_cxx_flags += '-DOSX_LEOPARD ' else: pthread = '-pthread' # Common debugging flags. debug_flags = '-g -DDEBUG -DMAPNIK_DEBUG' ndebug_flags = '-DNDEBUG' if env['XML_DEBUG']: common_cxx_flags += '-DMAPNIK_XML_DEBUG ' # Customizing the C++ compiler flags depending on: # (1) the C++ compiler used; and # (2) whether debug binaries are requested. if env['SUNCC']: if env['DEBUG']: env.Append(CXXFLAGS = common_cxx_flags + debug_flags) else: env.Append(CXXFLAGS = common_cxx_flags + '-O %s' % ndebug_flags) else: # Common flags for GCC. gcc_cxx_flags = '-ansi -Wall %s -ftemplate-depth-100 %s' % (pthread, common_cxx_flags) if env['DEBUG']: env.Append(CXXFLAGS = gcc_cxx_flags + '-O0 -fno-inline %s' % debug_flags) else: env.Append(CXXFLAGS = gcc_cxx_flags + '-O%s -finline-functions -Wno-inline %s' % (env['OPTIMIZATION'],ndebug_flags)) if 'python' in env['BINDINGS']: if not os.access(env['PYTHON'], os.X_OK): color_print(1,"Cannot run python interpreter at '%s', make sure that you have the permissions to execute it." % env['PYTHON']) Exit(1) res = os.popen('''%s -c "print 1"''' % env['PYTHON']) if res.read() == '1': py3 = False else: py3 = True #sys.version_info.major == 3 if py3: sys_prefix = '''%s -c "import sys; print(sys.prefix)"''' % env['PYTHON'] else: sys_prefix = '''%s -c "import sys; print sys.prefix"''' % env['PYTHON'] env['PYTHON_SYS_PREFIX'] = call(sys_prefix) if HAS_DISTUTILS: if py3: sys_version = '''%s -c "from distutils.sysconfig import get_python_version; print(get_python_version())"''' % env['PYTHON'] else: sys_version = '''%s -c "from distutils.sysconfig import get_python_version; print get_python_version()"''' % env['PYTHON'] env['PYTHON_VERSION'] = call(sys_version) if py3: py_includes = '''%s -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())"''' % env['PYTHON'] else: py_includes = '''%s -c "from distutils.sysconfig import get_python_inc; print get_python_inc()"''' % env['PYTHON'] env['PYTHON_INCLUDES'] = call(py_includes) # Note: we use the plat_specific argument here to make sure to respect the arch-specific site-packages location if py3: site_packages = '''%s -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(plat_specific=True))"''' % env['PYTHON'] else: site_packages = '''%s -c "from distutils.sysconfig import get_python_lib; print get_python_lib(plat_specific=True)"''' % env['PYTHON'] env['PYTHON_SITE_PACKAGES'] = call(site_packages) else: env['PYTHON_SYS_PREFIX'] = os.popen('''%s -c "import sys; print sys.prefix"''' % env['PYTHON']).read().strip() env['PYTHON_VERSION'] = os.popen('''%s -c "import sys; print sys.version"''' % env['PYTHON']).read()[0:3] env['PYTHON_INCLUDES'] = env['PYTHON_SYS_PREFIX'] + '/include/python' + env['PYTHON_VERSION'] env['PYTHON_SITE_PACKAGES'] = env['DESTDIR'] + '/' + env['PYTHON_SYS_PREFIX'] + '/' + env['LIBDIR_SCHEMA'] + '/python' + env['PYTHON_VERSION'] + '/site-packages/' # if user-requested custom prefix fall back to manual concatenation for building subdirectories if env['PYTHON_PREFIX']: py_relative_install = env['LIBDIR_SCHEMA'] + '/python' + env['PYTHON_VERSION'] + '/site-packages/' env['PYTHON_INSTALL_LOCATION'] = env['DESTDIR'] + '/' + env['PYTHON_PREFIX'] + '/' + py_relative_install else: env['PYTHON_INSTALL_LOCATION'] = env['DESTDIR'] + '/' + env['PYTHON_SITE_PACKAGES'] majver, minver = env['PYTHON_VERSION'].split('.') if (int(majver), int(minver)) < (2, 2): color_print(1,"Python version 2.2 or greater required") Exit(1) color_print(4,'Bindings Python version... %s' % env['PYTHON_VERSION']) color_print(4,'Python %s prefix... %s' % (env['PYTHON_VERSION'], env['PYTHON_SYS_PREFIX'])) color_print(4,'Python bindings will install in... %s' % os.path.normpath(env['PYTHON_INSTALL_LOCATION'])) # finish config stage and pickle results env = conf.Finish() env_cache = open(SCONS_CONFIGURE_CACHE, 'w') pickle_dict = {} for i in pickle_store: pickle_dict[i] = env.get(i) pickle.dump(pickle_dict,env_cache) env_cache.close() # fix up permissions on configure outputs # this is hackish but avoids potential problems # with a non-root configure following a root install # that also triggered a re-configure try: os.chmod(SCONS_CONFIGURE_CACHE,0666) except: pass try: os.chmod(SCONS_LOCAL_CONFIG,0666) except: pass try: os.chmod('.sconsign.dblite',0666) except: pass try: for item in glob('%s/*' % SCONF_TEMP_DIR): os.chmod(item,0666) except: pass if 'configure' in command_line_args: color_print(4,'\n*Configure complete*\nNow run "python scons/scons.py" to build or "python scons/scons.py install" to install') if not HELP_REQUESTED: Exit(0) # autogenerate help on default/current SCons options Help(opts.GenerateHelpText(env)) #### Builds #### if not HELP_REQUESTED: # export env so it is available in Sconscript files Export('env') if env['FAST']: # caching is 'auto' by default in SCons # But let's also cache implicit deps... EnsureSConsVersion(0,98) SetOption('implicit_cache', 1) env.Decider('MD5-timestamp') SetOption('max_drift', 1) else: # Set the cache mode to 'force' unless requested, avoiding hidden caching of Scons 'opts' in '.sconsign.dblite' # This allows for a SCONS_LOCAL_CONFIG, if present, to be used as the primary means of storing paths to successful build dependencies SetCacheMode('force') if env['JOBS'] > 1: SetOption("num_jobs", env['JOBS']) # Build agg first, doesn't need anything special if env['INTERNAL_LIBAGG']: SConscript('agg/SConscript') # Build the core library SConscript('src/SConscript') # Build the requested and able-to-be-compiled input plug-ins GDAL_BUILT = False OGR_BUILT = False for plugin in env['REQUESTED_PLUGINS']: details = env['PLUGINS'][plugin] if details['lib'] in env['LIBS']: SConscript('plugins/input/%s/SConscript' % plugin) if plugin == 'ogr': OGR_BUILT = True if plugin == 'gdal': GDAL_BUILT = True if plugin == 'ogr' or plugin == 'gdal': if GDAL_BUILT and OGR_BUILT: env['LIBS'].remove(details['lib']) else: env['LIBS'].remove(details['lib']) elif not details['lib']: # build internal shape and raster plugins SConscript('plugins/input/%s/SConscript' % plugin) # Build the c++ rundemo app if requested if env['DEMO']: SConscript('demo/c++/SConscript') # Build the pgsql2psqlite app if requested if env['PGSQL2SQLITE']: SConscript('utils/pgsql2sqlite/SConscript') # Build shapeindex and remove its dependency from the LIBS if 'boost_program_options%s' % env['BOOST_APPEND'] in env['LIBS']: SConscript('utils/shapeindex/SConscript') env['LIBS'].remove('boost_program_options%s' % env['BOOST_APPEND']) else : color_print(1,"WARNING: Cannot find boost_program_options. 'shapeindex' won't be available") # Build the Python bindings if 'python' in env['BINDINGS']: SConscript('bindings/python/SConscript') # Configure fonts and if requested install the bundled DejaVu fonts SConscript('fonts/SConscript')