forked from mapnik/mapnik
-
Notifications
You must be signed in to change notification settings - Fork 0
/
SConstruct
2286 lines (2009 loc) · 96.3 KB
/
SConstruct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# This file is part of Mapnik (c++ mapping toolkit)
#
# Copyright (C) 2017 Artem Pavlenko
#
# 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
from __future__ import print_function # support python2
import os
import sys
import re
import platform
from glob import glob
from copy import copy
from subprocess import Popen, PIPE
from SCons.SConf import SetCacheMode
import pickle
try:
import distutils.sysconfig
HAS_DISTUTILS = True
except:
HAS_DISTUTILS = False
try:
# Python 3.3+
from shlex import quote as shquote
except:
# Python 2.7
from pipes import quote as shquote
try:
# Python 3.3+
from subprocess import DEVNULL
except:
# Python 2.7
DEVNULL = open(os.devnull, 'w')
LIBDIR_SCHEMA_DEFAULT='lib'
severities = ['debug', 'warn', 'error', 'none']
ICU_INCLUDES_DEFAULT='/usr/include'
ICU_LIBS_DEFAULT='/usr/'
DEFAULT_CC = "cc"
DEFAULT_CXX = "c++"
DEFAULT_CXX14_CXXFLAGS = " -std=c++14 -DU_USING_ICU_NAMESPACE=0"
DEFAULT_CXX14_LINKFLAGS = ""
if sys.platform == 'darwin':
# homebrew default
ICU_INCLUDES_DEFAULT='/usr/local/opt/icu4c/include/'
ICU_LIBS_DEFAULT='/usr/local/opt/icu4c/'
py3 = None
# local file to hold custom user configuration variables
# Todo check timestamp, reload if changed?
SCONS_LOCAL_CONFIG = 'config.py'
# build log
SCONS_LOCAL_LOG = 'config.log'
# 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',]
BOOST_MIN_VERSION = '1.61'
#CAIRO_MIN_VERSION = '1.8.0'
HARFBUZZ_MIN_VERSION = (0, 9, 34)
HARFBUZZ_MIN_VERSION_STRING = "%s.%s.%s" % HARFBUZZ_MIN_VERSION
DEFAULT_LINK_PRIORITY = ['internal','other','frameworks','user','system']
pretty_dep_names = {
'clntsh':'Oracle database library | configure with OCCI_LIBS & OCCI_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/OCCI',
'gdal':'GDAL C++ library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: https://github.com/mapnik/mapnik/wiki/GDAL',
'ogr':'OGR-enabled GDAL C++ Library | configured using gdal-config program | try setting GDAL_CONFIG SCons option | more info: https://github.com/mapnik/mapnik/wiki/OGR',
'cairo':'Cairo C 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 required for PostGIS plugin | configure with pg_config program or configure with PG_LIBS & PG_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/PostGIS',
'sqlite3':'SQLite3 C Library | configure with SQLITE_LIBS & SQLITE_INCLUDES | more info: https://github.com/mapnik/mapnik/wiki/SQLite',
'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',
'webp':'WEBP C library | configure with WEBP_LIBS & WEBP_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/',
'harfbuzz':'HarfBuzz text shaping library | configure with HB_LIBS & HB_INCLUDES',
'harfbuzz-min-version':'HarfBuzz >= %s (required for font-feature-settings support)' % HARFBUZZ_MIN_VERSION_STRING,
'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',
'pq':'libpq library (postgres client) | try setting PG_CONFIG SCons option or configure with PG_LIBS & PG_INCLUDES',
'xml2-config':'xml2-config program | try setting XML2_CONFIG SCons option or avoid the need for xml2-config command by configuring with XML2_LIBS & XML2_INCLUDES',
'libxml2':'libxml2 library | try setting XML2_CONFIG SCons option to point to location of xml2-config program or configure with XML2_LIBS & XML2_INCLUDES',
'gdal-config':'gdal-config program | try setting GDAL_CONFIG SCons option',
'freetype-config':'freetype-config program | try setting FREETYPE_CONFIG SCons option or configure with FREETYPE_LIBS & FREETYPE_INCLUDES',
'freetype':'libfreetype library | try setting FREETYPE_CONFIG SCons option or configure with FREETYPE_LIBS & FREETYPE_INCLUDES',
'osm':'more info: https://github.com/mapnik/mapnik/wiki/OsmPlugin',
'boost_regex_icu':'libboost_regex built with optional ICU unicode support is needed for unicode regex support in mapnik.',
'sqlite_rtree':'The SQLite plugin requires libsqlite3 built with RTREE support (-DSQLITE_ENABLE_RTREE=1)',
'pgsql2sqlite_rtree':'The pgsql2sqlite program requires libsqlite3 built with RTREE support (-DSQLITE_ENABLE_RTREE=1)',
'PROJ_LIB':'The directory where proj4 stores its data files. Must exist for proj4 to work correctly',
'GDAL_DATA':'The directory where GDAL stores its data files. Must exist for GDAL to work correctly',
'ICU_DATA':'The directory where icu stores its data files. If ICU reports a path, it must exist. ICU can also be built without .dat files and in that case this path is empty'
}
# Core plugin build configuration
# opts.AddVariables still hardcoded however...
PLUGINS = { # plugins with external dependencies
# configured by calling project, hence 'path':None
'postgis': {'default':True,'path':None,'inc':'libpq-fe.h','lib':'pq','lang':'C'},
'pgraster': {'default':True,'path':None,'inc':'libpq-fe.h','lib':'pq','lang':'C'},
'gdal': {'default':True,'path':None,'inc':'gdal_priv.h','lib':'gdal','lang':'C++'},
'ogr': {'default':True,'path':None,'inc':'ogrsf_frmts.h','lib':'gdal','lang':'C++'},
'sqlite': {'default':True,'path':'SQLITE','inc':'sqlite3.h','lib':'sqlite3','lang':'C'},
# plugins without external dependencies requiring CheckLibWithHeader...
'shape': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'csv': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'raster': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'geojson': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'geobuf': {'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'},
'topojson':{'default':True,'path':None,'inc':None,'lib':None,'lang':'C++'}
}
def init_environment(env):
env.Decider('MD5-timestamp')
env.SourceCode(".", None)
env['ORIGIN'] = Literal('$ORIGIN')
env['ENV']['ORIGIN'] = '$ORIGIN'
if os.environ.get('RANLIB'):
env['RANLIB'] = os.environ['RANLIB']
if os.environ.get('AR'):
env['AR'] = os.environ['AR']
#### SCons build options and initial setup ####
env = Environment(ENV=os.environ)
init_environment(env)
def fix_path(path):
return str(os.path.abspath(path))
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, end='')
else:
print (text)
def regular_print(color,text,newline=True):
if not newline:
print (text, end = '')
else:
print (text)
def shell_command(cmd, *args, **kwargs):
""" Run command through shell.
`cmd` should be a valid, properly shell-quoted command.
Additional positional arguments, if provided, will each
be individually quoted as necessary and appended to `cmd`,
separated by spaces.
`logstream` optional keyword argument should be either:
- a file-like object, into which the command-line
and the command's STDERR output will be written; or
- None, in which case STDERR will go to DEVNULL.
Additional keyword arguments will be passed to `Popen`.
Returns a tuple `(result, output)` where:
`result` = True if the command completed successfully,
False otherwise
`output` = captured STDOUT with trailing whitespace removed
"""
# `cmd` itself is intentionally not wrapped in `shquote` here
# in order to support passing user-provided commands that may
# include arguments. For example:
#
# ret, out = shell_command(env['CXX'], '--version')
#
# needs to work even if `env['CXX'] == 'ccache c++'`
#
if args:
cmdstr = ' '.join([cmd] + [shquote(a) for a in args])
else:
cmdstr = cmd
# redirect STDERR to `logstream` if provided
try:
logstream = kwargs.pop('logstream')
except KeyError:
logstream = None
else:
if logstream is not None:
logstream.write(cmdstr + '\n')
kwargs['stderr'] = logstream
else:
kwargs['stderr'] = DEVNULL
# execute command and capture output
proc = Popen(cmdstr, shell=True, stdout=PIPE, **kwargs)
out, err = proc.communicate()
try:
outtext = out.decode(sys.stdout.encoding or 'UTF-8').rstrip()
except UnicodeDecodeError:
outtext = out.decode('UTF-8', errors='replace').rstrip()
if logstream is not None and outtext:
logstream.write('->\t' + outtext.replace('\n', '\n->\t') + '\n')
return proc.returncode == 0, outtext
def silent_command(cmd, *args):
return shell_command(cmd, *args, stderr=DEVNULL)
def config_command(cmd, *args):
return shell_command(cmd, *args, logstream=conf.logstream)
def strip_first(string,find,replace=''):
if string.startswith(find):
return string.replace(find,replace,1)
return string
# http://www.scons.org/wiki/InstallTargets
def create_uninstall_target(env, path, is_glob=False):
if 'uninstall' in COMMAND_LINE_TARGETS:
if is_glob:
all_files = Glob(path,strings=True)
for filei in all_files:
env.Command( "uninstall-"+filei, filei,
[
Delete("$SOURCE"),
])
env.Alias("uninstall", "uninstall-"+filei)
else:
if os.path.exists(path):
env.Command( "uninstall-"+path, path,
[
Delete("$SOURCE"),
])
env.Alias("uninstall", "uninstall-"+path)
def rm_path(item,set,_env):
for i in _env[set]:
if i.startswith(item):
_env[set].remove(i)
def sort_paths(items,priority):
"""Sort paths such that compiling and linking will globally prefer custom or local libs
over system libraries by fixing up the order libs are passed to the compiler and the linker.
Ideally preference could be by-target instead of global, but our SCons implementation
is not currently utilizing different SCons build env()'s as we should.
Overally the current approach within these scripts is to prepend paths of preference
and append all others, but this does not give enough control (particularly due to the
approach of assuming /usr/LIBSCHEMA and letting paths be parsed and added by pkg-config).
In effect /usr/lib is likely to come before /usr/local/lib which makes linking against
custom built icu or boost impossible when those libraries are available in both places.
Sorting using a priority list allows this to be controlled, and fine tuned.
"""
new = []
path_types = {'internal':[],'other':[],'frameworks':[],'user':[],'system':[]}
# parse types of paths into logical/meaningful groups
# based on commonly encountered lib directories on linux and osx
for i in items:
# internal paths for code kept inside
# the mapnik sources
if i.startswith('#'):
path_types['internal'].append(i)
# Mac OS X user installed frameworks
elif '/Library/Frameworks' in i:
path_types['frameworks'].append(i)
# various 'local' installs like /usr/local or /opt/local
elif 'local' in i or '/sw' in i:
if '/usr/local' in i:
path_types['user'].insert(0,i)
else:
path_types['user'].append(i)
# key system libs (likely others will fall into 'other')
elif '/usr/' in i or '/System' in i or i.startswith('/lib'):
path_types['system'].append(i)
# anything not yet matched...
# likely a combo of rare system lib paths and
# very custom user paths that should ideally be
# in 'user'
else:
path_types['other'].append(i)
# build up new list based on priority list
for path in priority:
if path in path_types:
dirs = path_types[path]
new.extend(dirs)
path_types.pop(path)
else:
color_print(1,'\nSorry, "%s" is NOT a valid value for option "LINK_PRIORITY": values include: %s' % (path,','.join(path_types.keys())))
color_print(1,'\tinternal: the local directory of the Mapnik sources (prefix #) (eg. used to link internal agg)')
color_print(1,'\tframeworks: on osx the /Library/Frameworks directory')
color_print(1,'\tuser: any path with "local" or "/sw" inside it')
color_print(1,'\tsystem: any path not yet matched with "/usr/","/lib", or "/System" (osx) inside it')
color_print(1,'\tother: any paths you specified not matched by criteria used to parse the others')
color_print(1,'\tother: any paths you specified not matched by criteria used to parse the others')
color_print(1,'The Default priority is: %s' % ','.join(DEFAULT_LINK_PRIORITY))
color_print(1,'Any priority groups not listed will be appended to the list at the end')
Exit(1)
# append remaining paths potentially not requested
# by any custom priority list defined by user
for k,v in path_types.items():
new.extend(v)
return new
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: https://github.com/mapnik/mapnik/wiki/Mapnik-Installation & http://www.boost.org')
return dep
def pretty_deps(indent, deps):
return indent + indent.join(pretty_dep(dep) for dep in deps)
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 to compile mapnik', DEFAULT_CXX),
('CC', 'The C compiler used for configure checks of C libs.', DEFAULT_CC),
('CUSTOM_CXXFLAGS', 'Custom C++ flags, e.g. -I<include dir> if you have headers in a nonstandard directory <include dir>', ''),
('CUSTOM_DEFINES', 'Custom Compiler DEFINES, e.g. -DENABLE_THIS', ''),
('CUSTOM_CFLAGS', 'Custom C flags, e.g. -I<include dir> if you have headers in a nonstandard directory <include dir> (only used for configure checks)', ''),
('CUSTOM_LDFLAGS', 'Custom linker flags, e.g. -L<lib dir> if you have libraries in a nonstandard directory <lib dir>', ''),
EnumVariable('LINKING', "Set library format for libmapnik",'shared', ['shared','static']),
EnumVariable('RUNTIME_LINK', "Set preference for linking dependencies",'shared', ['shared','static']),
EnumVariable('OPTIMIZATION','Set compiler optimization level','3', ['0','1','2','3','4','s']),
# Note: setting DEBUG=True will override any custom OPTIMIZATION level
BoolVariable('DEBUG', 'Compile a debug version of Mapnik', 'False'),
BoolVariable('COVERAGE', 'Compile a libmapnik and plugins with --coverage', 'False'),
ListVariable('INPUT_PLUGINS','Input drivers to include',DEFAULT_PLUGINS,PLUGINS.keys()),
('WARNING_CXXFLAGS', 'Compiler flags you can set to reduce warning levels which are placed after -Wall.', ''),
# SCons build behavior options
('HOST', 'Set the target host for cross compiling', ''),
('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('NO_ATEXIT', 'Will prevent Singletons from being deleted atexit of main thread', 'False'),
BoolVariable('NO_DLCLOSE', 'Will prevent plugins from being unloaded', 'False'),
BoolVariable('ENABLE_GLIBC_WORKAROUND', "Workaround known GLIBC symbol exports to allow building against libstdc++-4.8 without binaries needing throw_out_of_range_fmt", 'False'),
# http://www.scons.org/wiki/GoFastButton
# http://stackoverflow.com/questions/1318863/how-to-optimize-an-scons-script
BoolVariable('FAST', "Make SCons faster at the cost of less precise dependency tracking", 'False'),
BoolVariable('PRIORITIZE_LINKING', 'Sort list of lib and inc directories to ensure preferential compiling and linking (useful when duplicate libs)', 'True'),
('LINK_PRIORITY','Priority list in which to sort library and include paths (default order is internal, other, frameworks, user, then system - see source of `sort_paths` function for more detail)',','.join(DEFAULT_LINK_PRIORITY)),
# Install Variables
('PREFIX', 'The install path "prefix"', '/usr/local'),
('LIBDIR_SCHEMA', 'The library sub-directory appended to the "prefix", sometimes lib64 on 64bit linux systems', LIBDIR_SCHEMA_DEFAULT),
('DESTDIR', 'The root directory to install into. Useful mainly for binary package building', '/'),
('PATH', 'A custom path (or multiple paths divided by ":") to append to the $PATH env to prioritize usage of command line programs (if multiple are present on the system)', ''),
('PATH_REMOVE', 'A path prefix to exclude from all known command and compile paths (create multiple excludes separated by :)', ''),
('PATH_REPLACE', 'Two path prefixes (divided with a :) to search/replace from all known command and compile paths', ''),
('MAPNIK_NAME', 'Name of library', 'mapnik'),
# 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),
# 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', ICU_INCLUDES_DEFAULT, PathVariable.PathAccept),
PathVariable('ICU_LIBS','Search path for ICU include files',ICU_LIBS_DEFAULT + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
('ICU_LIB_NAME', 'The library name for icu (such as icuuc, sicuuc, or icucore)', 'icuuc', PathVariable.PathAccept),
PathVariable('HB_INCLUDES', 'Search path for HarfBuzz include files', '/usr/include', PathVariable.PathAccept),
PathVariable('HB_LIBS','Search path for HarfBuzz include files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
BoolVariable('PNG', 'Build Mapnik with PNG read and write support', 'True'),
PathVariable('PNG_INCLUDES', 'Search path for libpng include files', '/usr/include', PathVariable.PathAccept),
PathVariable('PNG_LIBS','Search path for libpng library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
BoolVariable('JPEG', 'Build Mapnik with JPEG read and write support', 'True'),
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_DEFAULT, PathVariable.PathAccept),
BoolVariable('TIFF', 'Build Mapnik with TIFF read and write support', 'True'),
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_DEFAULT, PathVariable.PathAccept),
BoolVariable('WEBP', 'Build Mapnik with WEBP read', 'True'),
PathVariable('WEBP_INCLUDES', 'Search path for libwebp include files', '/usr/include', PathVariable.PathAccept),
PathVariable('WEBP_LIBS','Search path for libwebp library files','/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
BoolVariable('PROJ', 'Build Mapnik with proj4 support to enable transformations between many different projections', 'True'),
PathVariable('PROJ_INCLUDES', 'Search path for PROJ.4 include files', '/usr/include', PathVariable.PathAccept),
PathVariable('PROJ_LIBS', 'Search path for PROJ.4 library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
('PG_INCLUDES', 'Search path for libpq (postgres client) include files', ''),
('PG_LIBS', 'Search path for libpq (postgres client) library files', ''),
('FREETYPE_INCLUDES', 'Search path for Freetype include files', ''),
('FREETYPE_LIBS', 'Search path for Freetype library files', ''),
('XML2_INCLUDES', 'Search path for libxml2 include files', ''),
('XML2_LIBS', 'Search path for libxml2 library files', ''),
('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('GRID_RENDERER', 'build support for native grid renderer', 'True'),
BoolVariable('SVG_RENDERER', 'build support for native svg renderer', 'False'),
BoolVariable('CPP_TESTS', 'Compile the C++ tests', 'True'),
BoolVariable('BENCHMARK', 'Compile the C++ benchmark scripts', 'False'),
# Variables for optional dependencies
# Note: cairo and and pycairo are 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'),
PathVariable('CAIRO_INCLUDES', 'Search path for cairo include files', '',PathVariable.PathAccept),
PathVariable('CAIRO_LIBS', 'Search path for cairo library files','',PathVariable.PathAccept),
('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_DEFAULT, 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_DEFAULT, PathVariable.PathAccept),
PathVariable('RASTERLITE_INCLUDES', 'Search path for RASTERLITE include files', '/usr/include/', PathVariable.PathAccept),
PathVariable('RASTERLITE_LIBS', 'Search path for RASTERLITE library files', '/usr/' + LIBDIR_SCHEMA_DEFAULT, PathVariable.PathAccept),
# Variables for logging and statistics
BoolVariable('ENABLE_LOG', 'Enable logging, which is enabled by default when building in *debug*', 'False'),
BoolVariable('ENABLE_STATS', 'Enable global statistics during map processing', 'False'),
('DEFAULT_LOG_SEVERITY', 'The default severity of the logger (eg. ' + ', '.join(severities) + ')', 'error'),
# Plugin linking
EnumVariable('PLUGIN_LINKING', "Set plugin linking with libmapnik", 'shared', ['shared','static']),
# Other variables
BoolVariable('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 provided then the bundled DejaVu fonts are not installed)',''),
('LIB_DIR_NAME','Name to use for the subfolder beside libmapnik where fonts and plugins are installed','mapnik'),
PathVariable('PYTHON','Full path to Python executable used to build bindings', sys.executable),
BoolVariable('FULL_LIB_PATH', 'Embed the full and absolute path to libmapnik when linking ("install_name" on OS X/rpath on Linux)', 'True'),
BoolVariable('ENABLE_SONAME', 'Embed a soname in libmapnik on Linux', 'True'),
EnumVariable('THREADING','Set threading support','multi', ['multi','single']),
EnumVariable('XMLPARSER','Set xml parser','ptree', ['libxml2','ptree']),
BoolVariable('DEMO', 'Compile demo c++ application', 'True'),
BoolVariable('PGSQL2SQLITE', 'Compile and install a utility to convert postgres tables to sqlite', 'False'),
BoolVariable('SHAPEINDEX', 'Compile and install a utility to generate shapefile indexes in the custom format (.index) Mapnik supports', 'True'),
BoolVariable('MAPNIK_INDEX', 'Compile and install a utility to generate spatial indexes for CSV and GeoJSON in the custom format (.index) Mapnik supports', 'True'),
BoolVariable('SVG2PNG', 'Compile and install a utility to generate render an svg file to a png on the command line', 'False'),
BoolVariable('MAPNIK_RENDER', 'Compile and install a utility to render a map to an image', 'True'),
BoolVariable('COLOR_PRINT', 'Print build status information in color', 'True'),
BoolVariable('BIGINT', 'Compile support for 64-bit integers in mapnik::value', 'True'),
BoolVariable('QUIET', 'Reduce build verbosity', 'False'),
)
# 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', # compiler user to check if c deps compile during configure
'CXX', # C++ compiler to compile mapnik
'CFLAGS',
'CPPDEFINES',
'CPPFLAGS', # c preprocessor flags
'CPPPATH',
'CXXFLAGS', # C++ flags built up during configure
'LIBPATH',
'LIBS',
'LINKFLAGS',
'RPATH',
'CUSTOM_LDFLAGS', # user submitted
'CUSTOM_DEFINES', # user submitted
'CUSTOM_CXXFLAGS', # user submitted
'CUSTOM_CFLAGS', # user submitted
'MAPNIK_LIB_NAME',
'LINK',
'RUNTIME_LINK',
# Mapnik's SConstruct build variables
'PLUGINS',
'ABI_VERSION',
'MAPNIK_VERSION_STRING',
'MAPNIK_VERSION',
'PLATFORM',
'BOOST_ABI',
'BOOST_APPEND',
'LIBDIR_SCHEMA',
'REQUESTED_PLUGINS',
'COLOR_PRINT',
'HAS_CAIRO',
'MAPNIK_HAS_DLFCN',
'HAS_PYCAIRO',
'PYCAIRO_PATHS',
'HAS_LIBXML2',
'PKG_CONFIG_PATH',
'PATH',
'PATH_REMOVE',
'PATH_REPLACE',
'MAPNIK_LIB_DIR',
'MAPNIK_LIB_DIR_DEST',
'INSTALL_PREFIX',
'MAPNIK_INPUT_PLUGINS',
'MAPNIK_INPUT_PLUGINS_DEST',
'MAPNIK_FONTS',
'MAPNIK_FONTS_DEST',
'MAPNIK_BUNDLED_SHARE_DIRECTORY',
'MAPNIK_LIB_BASE',
'MAPNIK_LIB_BASE_DEST',
'EXTRA_FREETYPE_LIBS',
'LIBMAPNIK_CPPATHS',
'LIBMAPNIK_DEFINES',
'LIBMAPNIK_CXXFLAGS',
'CAIRO_LIBPATHS',
'CAIRO_ALL_LIBS',
'CAIRO_CPPPATHS',
'GRID_RENDERER',
'SVG_RENDERER',
'SQLITE_LINKFLAGS',
'BOOST_LIB_VERSION_FROM_HEADER',
'BIGINT',
'HOST',
'QUERIED_GDAL_DATA',
'QUERIED_ICU_DATA',
'QUERIED_PROJ_LIB',
'QUIET'
]
# 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)
def rollback_option(env, variable):
global opts
for item in opts.options:
if item.key == variable:
env[variable] = item.default
# 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 and not HELP_REQUESTED:
force_configure = True
elif HELP_REQUESTED:
# to ensure config gets skipped when help is requested
preconfigured = True
# need no-op for clean on fresh checkout
# https://github.com/mapnik/mapnik/issues/2112
if not os.path.exists(SCONS_LOCAL_LOG) and not os.path.exists(SCONS_CONFIGURE_CACHE) \
and ('-c' in command_line_args or '--clean' in command_line_args):
print ('all good: nothing to clean, but you might want to run "make distclean"')
Exit(0)
# initially populate environment with defaults and any possible custom arguments
opts.Update(env)
# 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, 'rb')
pickled_values = pickle.load(pickled_environment)
for key, value in pickled_values.items():
env[key] = value
preconfigured = True
except:
preconfigured = False
else:
preconfigured = False
# check for missing keys in pickled settings
# which can occur when keys are added or changed between
# rebuilds, e.g. for folks following trunk
for opt in pickle_store:
if not opt in env:
#print 'missing opt', opt
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".')
if 'COLOR_PRINT' in env and env['COLOR_PRINT'] == False:
color_print = regular_print
if sys.platform == "win32":
color_print = regular_print
color_print(4,'\nWelcome to Mapnik...\n')
#### Custom Configure Checks ###
def prioritize_paths(context, silent=True):
env = context.env
prefs = env['LINK_PRIORITY'].split(',')
if not silent:
context.Message( 'Sorting lib and inc compiler paths...')
env['LIBPATH'] = sort_paths(env['LIBPATH'], prefs)
env['CPPPATH'] = sort_paths(env['CPPPATH'], prefs)
if silent:
context.did_show_result=1
ret = context.Result( True )
return ret
def CheckPKGConfig(context, version):
context.Message( 'Checking for pkg-config... ' )
context.sconf.cached = False
ret, _ = config_command('pkg-config --atleast-pkgconfig-version', version)
context.Result( ret )
return ret
def CheckPKG(context, name):
context.Message( 'Checking for %s... ' % name )
context.sconf.cached = False
ret, _ = config_command('pkg-config --exists', name)
context.Result( ret )
return ret
def CheckPKGVersion(context, name, version):
context.Message( 'Checking for at least version %s for %s... ' % (version,name) )
context.sconf.cached = False
ret, _ = config_command('pkg-config --atleast-version', version, name)
context.Result( ret )
return ret
def parse_config(context, config, checks='--libs --cflags'):
env = context.env
tool = config.lower().replace('_','-')
toolname = tool
if config in ('GDAL_CONFIG'):
toolname += ' %s' % checks
context.Message( 'Checking for %s... ' % toolname)
context.sconf.cached = False
cmd = '%s %s' % (env[config], checks)
ret, value = config_command(cmd)
parsed = False
if ret:
try:
if 'gdal-config' in cmd:
env.ParseConfig(cmd)
# hack for potential -framework GDAL syntax
# which will not end up being added to env['LIBS']
# and thus breaks knowledge below that gdal worked
# TODO - upgrade our scons logic to support Framework linking
if env['PLATFORM'] == 'Darwin':
if value and b'-framework GDAL' in value:
env['LIBS'].append('gdal')
if os.path.exists('/Library/Frameworks/GDAL.framework/unix/lib'):
env['LIBPATH'].insert(0,'/Library/Frameworks/GDAL.framework/unix/lib')
if 'GDAL' in env.get('FRAMEWORKS',[]):
env["FRAMEWORKS"].remove("GDAL")
else:
env.ParseConfig(cmd)
parsed = True
except OSError as e:
ret = False
print (' (xml2-config not found!)')
if not parsed:
if config in ('GDAL_CONFIG'):
# optional deps...
if tool not in env['SKIPPED_DEPS']:
env['SKIPPED_DEPS'].append(tool)
rollback_option(env, config)
else: # freetype and libxml2, not optional
if tool not in env['MISSING_DEPS']:
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)
context.sconf.cached = False
ret, value = config_command(env[config], '--libs')
parsed = False
if ret:
try:
if ' ' in value:
parts = value.split(' ')
if len(parts) > 1:
value = parts[1]
libnames = re.findall(libpattern, value)
if libnames:
libname = libnames[0]
else:
# osx 1.8 install gives '-framework GDAL'
libname = 'gdal'
except Exception as e:
ret = False
print (' unable to determine library name:# {0!s}'.format(e))
return None
context.Result( libname )
return libname
def parse_pg_config(context, config):
# TODO - leverage `LDFLAGS_SL` if RUNTIME_LINK==static
env = context.env
tool = config.lower()
context.Message( 'Checking for %s... ' % tool)
context.sconf.cached = False
ret, lib_path = config_command(env[config], '--libdir')
ret, inc_path = config_command(env[config], '--includedir')
if ret:
env.AppendUnique(CPPPATH = fix_path(inc_path))
env.AppendUnique(LIBPATH = fix_path(lib_path))
lpq = env['PLUGINS']['postgis']['lib']
env.Append(LIBS = lpq)
else:
env['SKIPPED_DEPS'].append(tool)
rollback_option(env, config)
context.Result( ret )
return ret
def ogr_enabled(context):
env = context.env
context.Message( 'Checking if gdal is ogr enabled... ')
context.sconf.cached = False
ret, out = config_command(env['GDAL_CONFIG'], '--ogr-enabled')
if ret:
ret = (out == 'yes')
if not ret:
if 'ogr' not in env['SKIPPED_DEPS']:
env['SKIPPED_DEPS'].append('ogr')
context.Result( ret )
return ret
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()
search_lib = 'libboost_filesystem'
# note: must call normpath to strip trailing slash otherwise dirname
# does not remove 'lib' and 'include'
prefixes.insert(0,os.path.dirname(os.path.normpath(env['BOOST_INCLUDES'])))
prefixes.insert(0,os.path.dirname(os.path.normpath(env['BOOST_LIBS'])))
for searchDir in prefixes:
libItems = glob(os.path.join(searchDir, env['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 = min(libItems, key=len)
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 += '\nFound boost libs: %s' % BOOST_LIB_DIR
env['BOOST_LIBS'] = BOOST_LIB_DIR
elif not env['BOOST_LIBS']:
env['BOOST_LIBS'] = '/usr/' + env['LIBDIR_SCHEMA']
msg += '\nUsing default boost lib dir: %s' % env['BOOST_LIBS']
else:
msg += '\nUsing boost lib dir: %s' % env['BOOST_LIBS']
if BOOST_INCLUDE_DIR:
msg += '\nFound boost headers: %s' % BOOST_INCLUDE_DIR
env['BOOST_INCLUDES'] = BOOST_INCLUDE_DIR
elif not env['BOOST_INCLUDES']:
env['BOOST_INCLUDES'] = '/usr/include'
msg += '\nUsing default boost include dir: %s' % env['BOOST_INCLUDES']
else:
msg += '\nUsing 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 += '\nFound boost lib name extension: %s' % BOOST_APPEND
env['BOOST_APPEND'] = BOOST_APPEND
else:
# Creating BOOST_APPEND according to the Boost library naming order,
# which goes <toolset>-<threading>-<abi>-<version>. 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 += '\nFound boost lib name extension: %s' % env['BOOST_APPEND']
env.AppendUnique(CPPPATH = fix_path(env['BOOST_INCLUDES']))
env.AppendUnique(LIBPATH = fix_path(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 <boost/version.hpp>
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 CheckIcuData(context, silent=False):
if not silent:
context.Message('Checking for ICU data directory...')
ret, out = context.TryRun("""
#include <unicode/putil.h>
#include <iostream>
int main() {
std::string result = u_getDataDirectory();
std::cout << result;
if (result.empty()) {
return -1;
}
return 0;
}
""", '.cpp')
if silent:
context.did_show_result=1
if ret:
value = out.strip()
context.Result('u_getDataDirectory returned %s' % value)
return value
else:
ret, value = config_command('icu-config --icudatadir')
if ret:
context.Result('icu-config returned %s' % value)
return value
else:
context.Result('Failed to detect (mapnik-config will have null value)')
return ''
def CheckGdalData(context, silent=False):
env = context.env
if not silent:
context.Message('Checking for GDAL data directory... ')
context.sconf.cached = False
ret, out = config_command(env['GDAL_CONFIG'], '--datadir')
value = out.strip()
if silent:
context.did_show_result=1
if ret:
context.Result('%s returned %s' % (env['GDAL_CONFIG'], value))
else:
context.Result('Failed to detect (mapnik-config will have null value)')
return value
def CheckProjData(context, silent=False):
if not silent:
context.Message('Checking for PROJ_LIB directory...')
ret, out = context.TryRun("""
// This is narly, could eventually be replaced using https://github.com/OSGeo/proj.4/pull/551]
#include <proj_api.h>
#include <iostream>
#include <cstring>
static void my_proj4_logger(void * user_data, int /*level*/, const char * msg)
{
std::string* posMsg = static_cast<std::string*>(user_data);
*posMsg += msg;
}
// https://github.com/OSGeo/gdal/blob/ddbf6d39aa4b005a77ca4f27c2d61a3214f336f8/gdal/alg/gdalapplyverticalshiftgrid.cpp#L616-L633
std::string find_proj_path(const char * pszFilename) {
std::string osMsg;
std::string osFilename;
projCtx ctx = pj_ctx_alloc();
pj_ctx_set_app_data(ctx, &osMsg);
pj_ctx_set_debug(ctx, PJ_LOG_DEBUG_MAJOR);
pj_ctx_set_logger(ctx, my_proj4_logger);
PAFile f = pj_open_lib(ctx, pszFilename, "rb");
if( f )
{
pj_ctx_fclose(ctx, f);
}
size_t nPos = osMsg.find("fopen(");
if( nPos != std::string::npos )
{
osFilename = osMsg.substr(nPos + strlen("fopen("));
nPos = osFilename.find(")");
if( nPos != std::string::npos )
osFilename = osFilename.substr(0, nPos);
}
pj_ctx_free(ctx);
return osFilename;
}
int main() {
std::string result = find_proj_path(" ");
std::cout << result;
if (result.empty()) {
return -1;
}
return 0;
}
""", '.cpp')
value = out.strip()
if silent:
context.did_show_result=1
if ret:
context.Result('pj_open_lib returned %s' % value)
else:
context.Result('Failed to detect (mapnik-config will have null value)')
return value
def CheckCairoHasFreetype(context, silent=False):
if not silent:
context.Message('Checking for cairo freetype font support ... ')
context.env.AppendUnique(CPPPATH=copy(env['CAIRO_CPPPATHS']))