SConstruct revision 262339
111894Speter# -*- python -*-
211894Speter#
311894Speter# Copyright 2011-2012 Justin Erenkrantz and Greg Stein
411894Speter#
59Sjkh# Licensed under the Apache License, Version 2.0 (the "License");
69Sjkh# you may not use this file except in compliance with the License.
79Sjkh# You may obtain a copy of the License at
89Sjkh#
99Sjkh#     http://www.apache.org/licenses/LICENSE-2.0
109Sjkh#
119Sjkh# Unless required by applicable law or agreed to in writing, software
129Sjkh# distributed under the License is distributed on an "AS IS" BASIS,
139Sjkh# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
149Sjkh# See the License for the specific language governing permissions and
159Sjkh# limitations under the License.
169Sjkh#
179Sjkh
189Sjkhimport sys
199Sjkhimport os
2011894Speterimport re
2111894Speter
2211894SpeterEnsureSConsVersion(2,3,0)
239Sjkh
249SjkhHEADER_FILES = ['serf.h',
259Sjkh                'serf_bucket_types.h',
269Sjkh                'serf_bucket_util.h',
279Sjkh                ]
289Sjkh
299Sjkh# where we save the configuration variables
309SjkhSAVED_CONFIG = '.saved_config'
3111894Speter
3211894Speter# Variable class that does no validation on the input
338858Srgrimesdef _converter(val):
3411894Speter    """
3511894Speter    """
3611894Speter    if val == 'none':
3711894Speter      val = []
3811894Speter    else:
3911894Speter      val = val.split(' ')
4011894Speter    return val
4111894Speter
4211894Speterdef RawListVariable(key, help, default):
4311894Speter    """
4411894Speter    The input parameters describe a 'raw string list' option. This class
4511894Speter    accepts a space-separated string and converts it to a list.
4611894Speter    """
4711894Speter    return (key, '%s' % (help), default, None, lambda val: _converter(val))
4811894Speter
4911894Speter# Custom path validator, creates directory when a specified option is set.
509Sjkh# To be used to ensure a PREFIX directory is only created when installing.
519Sjkhdef createPathIsDirCreateWithTarget(target):
529Sjkh  def my_validator(key, val, env):
539Sjkh    build_targets = (map(str, BUILD_TARGETS))
549Sjkh    if target in build_targets:
559Sjkh      return PathVariable.PathIsDirCreate(key, val, env)
569Sjkh    else:
579Sjkh      return PathVariable.PathAccept(key, val, env)
589Sjkh  return my_validator
599Sjkh
609Sjkh# default directories
619Sjkhif sys.platform == 'win32':
629Sjkh  default_incdir='..'
639Sjkh  default_libdir='..'
649Sjkh  default_prefix='Debug'
659Sjkhelse:
668858Srgrimes  default_incdir='/usr'
679Sjkh  default_libdir='$PREFIX/lib'
689Sjkh  default_prefix='/usr/local'
698858Srgrimes
709Sjkhopts = Variables(files=[SAVED_CONFIG])
719Sjkhopts.AddVariables(
729Sjkh  PathVariable('PREFIX',
738858Srgrimes               'Directory to install under',
749Sjkh               default_prefix,
759Sjkh               createPathIsDirCreateWithTarget('install')),
769Sjkh  PathVariable('LIBDIR',
779Sjkh               'Directory to install architecture dependent libraries under',
788858Srgrimes               default_libdir,
799Sjkh               createPathIsDirCreateWithTarget('install')),
809Sjkh  PathVariable('APR',
818858Srgrimes               "Path to apr-1-config, or to APR's install area",
829Sjkh               default_incdir,
839Sjkh               PathVariable.PathAccept),
849Sjkh  PathVariable('APU',
858858Srgrimes               "Path to apu-1-config, or to APR's install area",
869Sjkh               default_incdir,
879Sjkh               PathVariable.PathAccept),
889Sjkh  PathVariable('OPENSSL',
899Sjkh               "Path to OpenSSL's install area",
909Sjkh               default_incdir,
919Sjkh               PathVariable.PathIsDir),
929Sjkh  PathVariable('ZLIB',
939Sjkh               "Path to zlib's install area",
949Sjkh               default_incdir,
959Sjkh               PathVariable.PathIsDir),
969Sjkh  PathVariable('GSSAPI',
979Sjkh               "Path to GSSAPI's install area",
989Sjkh               None,
999Sjkh               None),
1009Sjkh  BoolVariable('DEBUG',
1019Sjkh               "Enable debugging info and strict compile warnings",
10211894Speter               False),
10311894Speter  BoolVariable('APR_STATIC',
1049Sjkh               "Enable using a static compiled APR",
10550472Speter               False),
1069Sjkh  RawListVariable('CC', "Command name or path of the C compiler", None),
10711894Speter  RawListVariable('CFLAGS', "Extra flags for the C compiler (space-separated)",
1089Sjkh                  None),
1099Sjkh  RawListVariable('LIBS', "Extra libraries passed to the linker, "
1109Sjkh                  "e.g. \"-l<library1> -l<library2>\" (space separated)", None),
1119Sjkh  RawListVariable('LINKFLAGS', "Extra flags for the linker (space-separated)",
11211894Speter                  None),
1139Sjkh  RawListVariable('CPPFLAGS', "Extra flags for the C preprocessor "
11411894Speter                  "(space separated)", None), 
1159Sjkh  )
11611894Speter
11711894Speterif sys.platform == 'win32':
11811894Speter  opts.AddVariables(
11911894Speter    # By default SCons builds for the host platform on Windows, when using
12011894Speter    # a supported compiler (E.g. VS2010/VS2012). Allow overriding
12111894Speter
1229Sjkh    # Note that Scons 1.3 only supports this on Windows and only when
12311894Speter    # constructing Environment(). Later changes to TARGET_ARCH are ignored
12411894Speter    EnumVariable('TARGET_ARCH',
12511894Speter                 "Platform to build for (x86|x64|win32|x86_64)",
12611894Speter                 'x86',
1279Sjkh                 allowed_values=('x86', 'x86_64', 'ia64'),
12811894Speter                 map={'X86'  : 'x86',
12911894Speter                      'win32': 'x86',
13011894Speter                      'Win32': 'x86',
13111894Speter                      'x64'  : 'x86_64',
13211894Speter                      'X64'  : 'x86_64'
13311894Speter                     }),
13411894Speter
13511894Speter    EnumVariable('MSVC_VERSION',
13611894Speter                 "Visual C++ to use for building (E.g. 11.0, 9.0)",
13711894Speter                 None,
13811894Speter                 allowed_values=('12.0', '11.0', '10.0', '9.0', '8.0', '6.0')
13911894Speter                ),
14011894Speter
14111894Speter    # We always documented that we handle an install layout, but in fact we
14211894Speter    # hardcoded source layouts. Allow disabling this behavior.
14311894Speter    # ### Fix default?
14411894Speter    BoolVariable('SOURCE_LAYOUT',
14511894Speter                 "Assume a source layout instead of install layout",
14611894Speter                 True),
14711894Speter    )
14811894Speter
14911894Speterenv = Environment(variables=opts,
15011894Speter                  tools=('default', 'textfile',),
1519Sjkh                  CPPPATH=['.', ],
15211894Speter                  )
1539Sjkh
1549Sjkhenv.Append(BUILDERS = {
1559Sjkh    'GenDef' : 
1569Sjkh      Builder(action = sys.executable + ' build/gen_def.py $SOURCES > $TARGET',
1579Sjkh              suffix='.def', src_suffix='.h')
15811894Speter  })
15911894Speter
1609Sjkhmatch = re.search('SERF_MAJOR_VERSION ([0-9]+).*'
16111894Speter                  'SERF_MINOR_VERSION ([0-9]+).*'
16211894Speter                  'SERF_PATCH_VERSION ([0-9]+)',
16311894Speter                  env.File('serf.h').get_contents(),
16411894Speter                  re.DOTALL)
16511894SpeterMAJOR, MINOR, PATCH = [int(x) for x in match.groups()]
1669Sjkhenv.Append(MAJOR=str(MAJOR))
16711894Speterenv.Append(MINOR=str(MINOR))
16811894Speterenv.Append(PATCH=str(PATCH))
16911894Speter
17011894Speter# Calling external programs is okay if we're not cleaning or printing help.
17111894Speter# (cleaning: no sense in fetching information; help: we may not know where
17211894Speter# they are)
17311894SpeterCALLOUT_OKAY = not (env.GetOption('clean') or env.GetOption('help'))
17411894Speter
17511894Speter
1769Sjkh# HANDLING OF OPTION VARIABLES
17711894Speter
17811894Speterunknown = opts.UnknownVariables()
1799Sjkhif unknown:
1809Sjkh  print 'Unknown variables:', ', '.join(unknown.keys())
1819Sjkh  Exit(1)
1829Sjkh
1839Sjkhapr = str(env['APR'])
18411894Speterapu = str(env['APU'])
1859Sjkhzlib = str(env['ZLIB'])
1869Sjkhgssapi = env.get('GSSAPI', None)
1879Sjkh
1889Sjkhif gssapi and os.path.isdir(gssapi):
18911894Speter  krb5_config = os.path.join(gssapi, 'bin', 'krb5-config')
1909Sjkh  if os.path.isfile(krb5_config):
19111894Speter    gssapi = krb5_config
19211894Speter    env['GSSAPI'] = krb5_config
19311894Speter
19411894Speterdebug = env.get('DEBUG', None)
1959Sjkhaprstatic = env.get('APR_STATIC', None)
19611894Speter
1979SjkhHelp(opts.GenerateHelpText(env))
1989Sjkhopts.Save(SAVED_CONFIG, env)
1999Sjkh
20011894Speter
20111894Speter# PLATFORM-SPECIFIC BUILD TWEAKS
2029Sjkh
2039Sjkhthisdir = os.getcwd()
2049Sjkhlibdir = '$LIBDIR'
2059Sjkhincdir = '$PREFIX/include/serf-$MAJOR'
20611894Speter
20711894Speter# This version string is used in the dynamic library name, and for Mac OS X also
20811894Speter# for the current_version and compatibility_version options in the .dylib
20911894Speter#
21011894Speter# Unfortunately we can't set the .dylib compatibility_version option separately
21111894Speter# from current_version, so don't use the PATCH level to avoid that build and
21211894Speter# runtime patch levels have to be identical.
21311894Speterenv['SHLIBVERSION'] = '%d.%d.%d' % (MAJOR, MINOR, 0)
21411894Speter
21511894SpeterLIBNAME = 'libserf-%d' % (MAJOR,)
2169Sjkhif sys.platform != 'win32':
2179Sjkh  LIBNAMESTATIC = LIBNAME
21811894Speterelse:
2199Sjkh  LIBNAMESTATIC = 'serf-${MAJOR}'
2209Sjkh
2219Sjkhenv.Append(RPATH=libdir,
2229Sjkh           PDB='${TARGET.filebase}.pdb')
2239Sjkh
2249Sjkhif sys.platform == 'darwin':
2259Sjkh#  linkflags.append('-Wl,-install_name,@executable_path/%s.dylib' % (LIBNAME,))
2269Sjkh  env.Append(LINKFLAGS='-Wl,-install_name,%s/%s.dylib' % (thisdir, LIBNAME,))
2279Sjkh
2289Sjkhif sys.platform != 'win32':
2299Sjkh  ### gcc only. figure out appropriate test / better way to check these
2309Sjkh  ### flags, and check for gcc.
2319Sjkh  env.Append(CFLAGS='-std=c89')
2329Sjkh
23311894Speter  ### These warnings are not available on Solaris
2349Sjkh  if sys.platform != 'sunos5': 
2359Sjkh    env.Append(CCFLAGS=['-Wdeclaration-after-statement',
23687625Speter                        '-Wmissing-prototypes',
2379Sjkh                        '-Wall'])
2389Sjkh
2399Sjkh  if debug:
2409Sjkh    env.Append(CCFLAGS='-g')
2419Sjkh    env.Append(CPPDEFINES=['DEBUG', '_DEBUG'])
2429Sjkh  else:
2439Sjkh    env.Append(CCFLAGS='-O2')
2449Sjkh    env.Append(CPPDEFINES='NDEBUG')
2459Sjkh
2469Sjkh  ### works for Mac OS. probably needs to change
2479Sjkh  env.Append(LIBS=['ssl', 'crypto', 'z', ])
2489Sjkh
2499Sjkh  if sys.platform == 'sunos5':
2509Sjkh    env.Append(LIBS='m')
2519Sjkhelse:
25211894Speter  # Warning level 4, no unused argument warnings
2539Sjkh  env.Append(CCFLAGS=['/W4', '/wd4100'])
2549Sjkh
2559Sjkh  # Choose runtime and optimization
2569Sjkh  if debug:
2579Sjkh    # Disable optimizations for debugging, use debug DLL runtime
2589Sjkh    env.Append(CCFLAGS=['/Od', '/MDd'])
2599Sjkh    env.Append(CPPDEFINES=['DEBUG', '_DEBUG'])
2609Sjkh  else:
2619Sjkh    # Optimize for speed, use DLL runtime
2629Sjkh    env.Append(CCFLAGS=['/O2', '/MD'])
2639Sjkh    env.Append(CPPDEFINES='NDEBUG')
2649Sjkh    env.Append(LINKFLAGS='/RELEASE')
2659Sjkh
2669Sjkh# PLAN THE BUILD
2679SjkhSHARED_SOURCES = []
26811894Speterif sys.platform == 'win32':
2699Sjkh  env.GenDef(['serf.h','serf_bucket_types.h', 'serf_bucket_util.h'])
2709Sjkh  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