1# -*- python -*- 2# 3# ==================================================================== 4# Licensed to the Apache Software Foundation (ASF) under one 5# or more contributor license agreements. See the NOTICE file 6# distributed with this work for additional information 7# regarding copyright ownership. The ASF licenses this file 8# to you under the Apache License, Version 2.0 (the 9# "License"); you may not use this file except in compliance 10# with the License. You may obtain a copy of the License at 11# 12# http://www.apache.org/licenses/LICENSE-2.0 13# 14# Unless required by applicable law or agreed to in writing, 15# software distributed under the License is distributed on an 16# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 17# KIND, either express or implied. See the License for the 18# specific language governing permissions and limitations 19# under the License. 20# ==================================================================== 21# 22 23import sys 24import os 25import re 26 27EnsureSConsVersion(2,3,0) 28 29HEADER_FILES = ['serf.h', 30 'serf_bucket_types.h', 31 'serf_bucket_util.h', 32 ] 33 34# where we save the configuration variables 35SAVED_CONFIG = '.saved_config' 36 37# Variable class that does no validation on the input 38def _converter(val): 39 """ 40 """ 41 if val == 'none': 42 val = [] 43 else: 44 val = val.split(' ') 45 return val 46 47def RawListVariable(key, help, default): 48 """ 49 The input parameters describe a 'raw string list' option. This class 50 accepts a space-separated string and converts it to a list. 51 """ 52 return (key, '%s' % (help), default, None, lambda val: _converter(val)) 53 54# Custom path validator, creates directory when a specified option is set. 55# To be used to ensure a PREFIX directory is only created when installing. 56def createPathIsDirCreateWithTarget(target): 57 def my_validator(key, val, env): 58 build_targets = (map(str, BUILD_TARGETS)) 59 if target in build_targets: 60 return PathVariable.PathIsDirCreate(key, val, env) 61 else: 62 return PathVariable.PathAccept(key, val, env) 63 return my_validator 64 65# default directories 66if sys.platform == 'win32': 67 default_incdir='..' 68 default_libdir='..' 69 default_prefix='Debug' 70else: 71 default_incdir='/usr' 72 default_libdir='$PREFIX/lib' 73 default_prefix='/usr/local' 74 75opts = Variables(files=[SAVED_CONFIG]) 76opts.AddVariables( 77 PathVariable('PREFIX', 78 'Directory to install under', 79 default_prefix, 80 createPathIsDirCreateWithTarget('install')), 81 PathVariable('LIBDIR', 82 'Directory to install architecture dependent libraries under', 83 default_libdir, 84 createPathIsDirCreateWithTarget('install')), 85 PathVariable('APR', 86 "Path to apr-1-config, or to APR's install area", 87 default_incdir, 88 PathVariable.PathAccept), 89 PathVariable('APU', 90 "Path to apu-1-config, or to APR's install area", 91 default_incdir, 92 PathVariable.PathAccept), 93 PathVariable('OPENSSL', 94 "Path to OpenSSL's install area", 95 default_incdir, 96 PathVariable.PathIsDir), 97 PathVariable('ZLIB', 98 "Path to zlib's install area", 99 default_incdir, 100 PathVariable.PathIsDir), 101 PathVariable('GSSAPI', 102 "Path to GSSAPI's install area", 103 None, 104 None), 105 BoolVariable('DEBUG', 106 "Enable debugging info and strict compile warnings", 107 False), 108 BoolVariable('APR_STATIC', 109 "Enable using a static compiled APR", 110 False), 111 RawListVariable('CC', "Command name or path of the C compiler", None), 112 RawListVariable('CFLAGS', "Extra flags for the C compiler (space-separated)", 113 None), 114 RawListVariable('LIBS', "Extra libraries passed to the linker, " 115 "e.g. \"-l<library1> -l<library2>\" (space separated)", None), 116 RawListVariable('LINKFLAGS', "Extra flags for the linker (space-separated)", 117 None), 118 RawListVariable('CPPFLAGS', "Extra flags for the C preprocessor " 119 "(space separated)", None), 120 ) 121 122if sys.platform == 'win32': 123 opts.AddVariables( 124 # By default SCons builds for the host platform on Windows, when using 125 # a supported compiler (E.g. VS2010/VS2012). Allow overriding 126 127 # Note that Scons 1.3 only supports this on Windows and only when 128 # constructing Environment(). Later changes to TARGET_ARCH are ignored 129 EnumVariable('TARGET_ARCH', 130 "Platform to build for (x86|x64|win32|x86_64)", 131 'x86', 132 allowed_values=('x86', 'x86_64', 'ia64'), 133 map={'X86' : 'x86', 134 'win32': 'x86', 135 'Win32': 'x86', 136 'x64' : 'x86_64', 137 'X64' : 'x86_64' 138 }), 139 140 EnumVariable('MSVC_VERSION', 141 "Visual C++ to use for building (E.g. 11.0, 9.0)", 142 None, 143 allowed_values=('14.0', '12.0', 144 '11.0', '10.0', '9.0', '8.0', '6.0') 145 ), 146 147 # We always documented that we handle an install layout, but in fact we 148 # hardcoded source layouts. Allow disabling this behavior. 149 # ### Fix default? 150 BoolVariable('SOURCE_LAYOUT', 151 "Assume a source layout instead of install layout", 152 True), 153 ) 154 155env = Environment(variables=opts, 156 tools=('default', 'textfile',), 157 CPPPATH=['.', ], 158 ) 159 160env.Append(BUILDERS = { 161 'GenDef' : 162 Builder(action = sys.executable + ' build/gen_def.py $SOURCES > $TARGET', 163 suffix='.def', src_suffix='.h') 164 }) 165 166match = re.search('SERF_MAJOR_VERSION ([0-9]+).*' 167 'SERF_MINOR_VERSION ([0-9]+).*' 168 'SERF_PATCH_VERSION ([0-9]+)', 169 env.File('serf.h').get_contents(), 170 re.DOTALL) 171MAJOR, MINOR, PATCH = [int(x) for x in match.groups()] 172env.Append(MAJOR=str(MAJOR)) 173env.Append(MINOR=str(MINOR)) 174env.Append(PATCH=str(PATCH)) 175 176# Calling external programs is okay if we're not cleaning or printing help. 177# (cleaning: no sense in fetching information; help: we may not know where 178# they are) 179CALLOUT_OKAY = not (env.GetOption('clean') or env.GetOption('help')) 180 181 182# HANDLING OF OPTION VARIABLES 183 184unknown = opts.UnknownVariables() 185if unknown: 186 print 'Warning: Used unknown variables:', ', '.join(unknown.keys()) 187 188apr = str(env['APR']) 189apu = str(env['APU']) 190zlib = str(env['ZLIB']) 191gssapi = env.get('GSSAPI', None) 192 193if gssapi and os.path.isdir(gssapi): 194 krb5_config = os.path.join(gssapi, 'bin', 'krb5-config') 195 if os.path.isfile(krb5_config): 196 gssapi = krb5_config 197 env['GSSAPI'] = krb5_config 198 199debug = env.get('DEBUG', None) 200aprstatic = env.get('APR_STATIC', None) 201 202Help(opts.GenerateHelpText(env)) 203opts.Save(SAVED_CONFIG, env) 204 205 206# PLATFORM-SPECIFIC BUILD TWEAKS 207 208thisdir = os.getcwd() 209libdir = '$LIBDIR' 210incdir = '$PREFIX/include/serf-$MAJOR' 211 212# This version string is used in the dynamic library name, and for Mac OS X also 213# for the current_version and compatibility_version options in the .dylib 214# 215# Unfortunately we can't set the .dylib compatibility_version option separately 216# from current_version, so don't use the PATCH level to avoid that build and 217# runtime patch levels have to be identical. 218if sys.platform != 'sunos5': 219 env['SHLIBVERSION'] = '%d.%d.%d' % (MAJOR, MINOR, 0) 220 221LIBNAME = 'libserf-%d' % (MAJOR,) 222if sys.platform != 'win32': 223 LIBNAMESTATIC = LIBNAME 224else: 225 LIBNAMESTATIC = 'serf-${MAJOR}' 226 227env.Append(RPATH=libdir, 228 PDB='${TARGET.filebase}.pdb') 229 230if sys.platform == 'darwin': 231# linkflags.append('-Wl,-install_name,@executable_path/%s.dylib' % (LIBNAME,)) 232 env.Append(LINKFLAGS=['-Wl,-install_name,%s/%s.dylib' % (thisdir, LIBNAME,)]) 233 234if sys.platform != 'win32': 235 def CheckGnuCC(context): 236 src = ''' 237 #ifndef __GNUC__ 238 oh noes! 239 #endif 240 ''' 241 context.Message('Checking for GNU-compatible C compiler...') 242 result = context.TryCompile(src, '.c') 243 context.Result(result) 244 return result 245 246 conf = Configure(env, custom_tests = dict(CheckGnuCC=CheckGnuCC)) 247 have_gcc = conf.CheckGnuCC() 248 env = conf.Finish() 249 250 if have_gcc: 251 env.Append(CFLAGS=['-std=c89']) 252 env.Append(CCFLAGS=['-Wdeclaration-after-statement', 253 '-Wmissing-prototypes', 254 '-Wall']) 255 256 if debug: 257 env.Append(CCFLAGS=['-g']) 258 env.Append(CPPDEFINES=['DEBUG', '_DEBUG']) 259 else: 260 env.Append(CCFLAGS=['-O2']) 261 env.Append(CPPDEFINES=['NDEBUG']) 262 263 ### works for Mac OS. probably needs to change 264 env.Append(LIBS=['ssl', 'crypto', 'z', ]) 265 266 if sys.platform == 'sunos5': 267 env.Append(LIBS=['m']) 268 env.Append(PLATFORM='posix') 269else: 270 # Warning level 4, no unused argument warnings 271 env.Append(CCFLAGS=['/W4', '/wd4100']) 272 273 # Choose runtime and optimization 274 if debug: 275 # Disable optimizations for debugging, use debug DLL runtime 276 env.Append(CCFLAGS=['/Od', '/MDd']) 277 env.Append(CPPDEFINES=['DEBUG', '_DEBUG']) 278 else: 279 # Optimize for speed, use DLL runtime 280 env.Append(CCFLAGS=['/O2', '/MD']) 281 env.Append(CPPDEFINES=['NDEBUG']) 282 env.Append(LINKFLAGS=['/RELEASE']) 283 284# PLAN THE BUILD 285SHARED_SOURCES = [] 286if sys.platform == 'win32': 287 env.GenDef(['serf.h','serf_bucket_types.h', 'serf_bucket_util.h']) 288 SHARED_SOURCES.append(['serf.def']) 289 290SOURCES = Glob('*.c') + Glob('buckets/*.c') + Glob('auth/*.c') 291 292lib_static = env.StaticLibrary(LIBNAMESTATIC, SOURCES) 293lib_shared = env.SharedLibrary(LIBNAME, SOURCES + SHARED_SOURCES) 294 295if aprstatic: 296 env.Append(CPPDEFINES=['APR_DECLARE_STATIC', 'APU_DECLARE_STATIC']) 297 298if sys.platform == 'win32': 299 env.Append(LIBS=['user32.lib', 'advapi32.lib', 'gdi32.lib', 'ws2_32.lib', 300 'crypt32.lib', 'mswsock.lib', 'rpcrt4.lib', 'secur32.lib']) 301 302 # Get apr/apu information into our build 303 env.Append(CPPDEFINES=['WIN32','WIN32_LEAN_AND_MEAN','NOUSER', 304 'NOGDI', 'NONLS','NOCRYPT']) 305 306 if env.get('TARGET_ARCH', None) == 'x86_64': 307 env.Append(CPPDEFINES=['WIN64']) 308 309 if aprstatic: 310 apr_libs='apr-1.lib' 311 apu_libs='aprutil-1.lib' 312 env.Append(LIBS=['shell32.lib', 'xml.lib']) 313 else: 314 apr_libs='libapr-1.lib' 315 apu_libs='libaprutil-1.lib' 316 317 env.Append(LIBS=[apr_libs, apu_libs]) 318 if not env.get('SOURCE_LAYOUT', None): 319 env.Append(LIBPATH=['$APR/lib', '$APU/lib'], 320 CPPPATH=['$APR/include/apr-1', '$APU/include/apr-1']) 321 elif aprstatic: 322 env.Append(LIBPATH=['$APR/LibR','$APU/LibR'], 323 CPPPATH=['$APR/include', '$APU/include']) 324 else: 325 env.Append(LIBPATH=['$APR/Release','$APU/Release'], 326 CPPPATH=['$APR/include', '$APU/include']) 327 328 # zlib 329 env.Append(LIBS=['zlib.lib']) 330 if not env.get('SOURCE_LAYOUT', None): 331 env.Append(CPPPATH=['$ZLIB/include'], 332 LIBPATH=['$ZLIB/lib']) 333 else: 334 env.Append(CPPPATH=['$ZLIB'], 335 LIBPATH=['$ZLIB']) 336 337 # openssl 338 env.Append(LIBS=['libeay32.lib', 'ssleay32.lib']) 339 if not env.get('SOURCE_LAYOUT', None): 340 env.Append(CPPPATH=['$OPENSSL/include/openssl'], 341 LIBPATH=['$OPENSSL/lib']) 342 elif 0: # opensslstatic: 343 env.Append(CPPPATH=['$OPENSSL/inc32'], 344 LIBPATH=['$OPENSSL/out32']) 345 else: 346 env.Append(CPPPATH=['$OPENSSL/inc32'], 347 LIBPATH=['$OPENSSL/out32dll']) 348else: 349 if os.path.isdir(apr): 350 apr = os.path.join(apr, 'bin', 'apr-1-config') 351 env['APR'] = apr 352 if os.path.isdir(apu): 353 apu = os.path.join(apu, 'bin', 'apu-1-config') 354 env['APU'] = apu 355 356 ### we should use --cc, but that is giving some scons error about an implict 357 ### dependency upon gcc. probably ParseConfig doesn't know what to do with 358 ### the apr-1-config output 359 if CALLOUT_OKAY: 360 env.ParseConfig('$APR --cflags --cppflags --ldflags --includes' 361 ' --link-ld --libs') 362 env.ParseConfig('$APU --ldflags --includes --link-ld --libs') 363 364 ### there is probably a better way to run/capture output. 365 ### env.ParseConfig() may be handy for getting this stuff into the build 366 if CALLOUT_OKAY: 367 apr_libs = os.popen(env.subst('$APR --link-libtool --libs')).read().strip() 368 apu_libs = os.popen(env.subst('$APU --link-libtool --libs')).read().strip() 369 else: 370 apr_libs = '' 371 apu_libs = '' 372 373 env.Append(CPPPATH=['$OPENSSL/include']) 374 env.Append(LIBPATH=['$OPENSSL/lib']) 375 376 377# If build with gssapi, get its information and define SERF_HAVE_GSSAPI 378if gssapi and CALLOUT_OKAY: 379 env.ParseConfig('$GSSAPI --cflags gssapi') 380 def parse_libs(env, cmd, unique=1): 381 env['GSSAPI_LIBS'] = cmd.strip() 382 return env.MergeFlags(cmd, unique) 383 env.ParseConfig('$GSSAPI --libs gssapi', parse_libs) 384 env.Append(CPPDEFINES=['SERF_HAVE_GSSAPI']) 385if sys.platform == 'win32': 386 env.Append(CPPDEFINES=['SERF_HAVE_SSPI']) 387 388# On some systems, the -R values that APR describes never make it into actual 389# RPATH flags. We'll manually map all directories in LIBPATH into new 390# flags to set RPATH values. 391for d in env['LIBPATH']: 392 env.Append(RPATH=':'+d) 393 394# Set up the construction of serf-*.pc 395pkgconfig = env.Textfile('serf-%d.pc' % (MAJOR,), 396 env.File('build/serf.pc.in'), 397 SUBST_DICT = { 398 '@MAJOR@': str(MAJOR), 399 '@PREFIX@': '$PREFIX', 400 '@LIBDIR@': '$LIBDIR', 401 '@INCLUDE_SUBDIR@': 'serf-%d' % (MAJOR,), 402 '@VERSION@': '%d.%d.%d' % (MAJOR, MINOR, PATCH), 403 '@LIBS@': '%s %s %s -lz' % (apu_libs, apr_libs, 404 env.get('GSSAPI_LIBS', '')), 405 }) 406 407env.Default(lib_static, lib_shared, pkgconfig) 408 409if CALLOUT_OKAY: 410 conf = Configure(env) 411 412 ### some configuration stuffs 413 414 env = conf.Finish() 415 416 417# INSTALLATION STUFF 418 419install_static = env.Install(libdir, lib_static) 420install_shared = env.InstallVersionedLib(libdir, lib_shared) 421 422if sys.platform == 'darwin': 423 # Change the shared library install name (id) to its final name and location. 424 # Notes: 425 # If --install-sandbox=<path> is specified, install_shared_path will point 426 # to a path in the sandbox. We can't use that path because the sandbox is 427 # only a temporary location. The id should be the final target path. 428 # Also, we shouldn't use the complete version number for id, as that'll 429 # make applications depend on the exact major.minor.patch version of serf. 430 431 install_shared_path = install_shared[0].abspath 432 target_install_shared_path = os.path.join(libdir, '%s.dylib' % LIBNAME) 433 env.AddPostAction(install_shared, ('install_name_tool -id %s %s' 434 % (target_install_shared_path, 435 install_shared_path))) 436 437env.Alias('install-lib', [install_static, install_shared, 438 ]) 439env.Alias('install-inc', env.Install(incdir, HEADER_FILES)) 440env.Alias('install-pc', env.Install(os.path.join(libdir, 'pkgconfig'), 441 pkgconfig)) 442env.Alias('install', ['install-lib', 'install-inc', 'install-pc', ]) 443 444 445# TESTS 446### make move to a separate scons file in the test/ subdir? 447 448tenv = env.Clone() 449 450# MockHTTP requires C99 standard, so use it for the test suite. 451cflags = tenv['CFLAGS'] 452tenv.Replace(CFLAGS = [f.replace('-std=c89', '-std=c99') for f in cflags]) 453 454tenv.Append(CPPDEFINES=['MOCKHTTP_OPENSSL']) 455 456TEST_PROGRAMS = [ 'serf_get', 'serf_response', 'serf_request', 'serf_spider', 457 'test_all', 'serf_bwtp' ] 458if sys.platform == 'win32': 459 TEST_EXES = [ os.path.join('test', '%s.exe' % (prog)) for prog in TEST_PROGRAMS ] 460else: 461 TEST_EXES = [ os.path.join('test', '%s' % (prog)) for prog in TEST_PROGRAMS ] 462 463# Find the (dynamic) library in this directory 464tenv.Replace(RPATH=thisdir) 465tenv.Prepend(LIBS=[LIBNAMESTATIC, ], 466 LIBPATH=[thisdir, ]) 467 468check_script = env.File('build/check.py').rstr() 469test_dir = env.File('test/test_all.c').rfile().get_dir() 470src_dir = env.File('serf.h').rfile().get_dir() 471test_app = ("%s %s %s %s") % (sys.executable, check_script, test_dir, 'test') 472 473# Set the library search path for the test programs 474test_env = {'PATH' : os.environ['PATH'], 475 'srcdir' : src_dir} 476if sys.platform != 'win32': 477 test_env['LD_LIBRARY_PATH'] = ':'.join(tenv.get('LIBPATH', [])) 478env.AlwaysBuild(env.Alias('check', TEST_EXES, test_app, ENV=test_env)) 479 480testall_files = [ 481 'test/test_all.c', 482 'test/CuTest.c', 483 'test/test_util.c', 484 'test/test_context.c', 485 'test/test_buckets.c', 486 'test/test_auth.c', 487 'test/mock_buckets.c', 488 'test/test_ssl.c', 489 'test/server/test_server.c', 490 'test/server/test_sslserver.c', 491 ] 492 493for proggie in TEST_EXES: 494 if 'test_all' in proggie: 495 tenv.Program(proggie, testall_files ) 496 else: 497 tenv.Program(target = proggie, source = [proggie.replace('.exe','') + '.c']) 498 499 500# HANDLE CLEANING 501 502if env.GetOption('clean'): 503 # When we're cleaning, we want the dependency tree to include "everything" 504 # that could be built. Thus, include all of the tests. 505 env.Default('check') 506