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