| 1 | # ############################################################################# |
|---|
| 2 | # SConstruct: SCons build instructions |
|---|
| 3 | # |
|---|
| 4 | # Copyright 2006 the Ithildin Project. |
|---|
| 5 | # See the COPYING file for more information on licensing and use. |
|---|
| 6 | # |
|---|
| 7 | # $Id$ |
|---|
| 8 | # |
|---|
| 9 | # ############################################################################# |
|---|
| 10 | |
|---|
| 11 | ############################################################################### |
|---|
| 12 | # Initial setup (Python path tweaks etc) |
|---|
| 13 | |
|---|
| 14 | package_name = "ithildin" |
|---|
| 15 | package_version = "1.2.0" |
|---|
| 16 | |
|---|
| 17 | EnsurePythonVersion(2,2) |
|---|
| 18 | EnsureSConsVersion(0,96,92) |
|---|
| 19 | |
|---|
| 20 | import glob, os, sys |
|---|
| 21 | # let's just stick the realpath() for ./build in there at the front. THis |
|---|
| 22 | # allows us to use our custom test code in the 'tests' module there. |
|---|
| 23 | sys.path.insert(0, os.path.realpath('./build')) |
|---|
| 24 | import tests |
|---|
| 25 | |
|---|
| 26 | ############################################################################### |
|---|
| 27 | # Global environment setup. Import the user's $PATH for our own sanity. |
|---|
| 28 | env = Environment(ENV = { |
|---|
| 29 | 'PATH': os.environ['PATH'], |
|---|
| 30 | }, |
|---|
| 31 | CPPPATH = ['#/include'] |
|---|
| 32 | ) |
|---|
| 33 | |
|---|
| 34 | # Perform some magic to get the repository version. If we have svn and we |
|---|
| 35 | # can use xml.dom.minidom let's go for it |
|---|
| 36 | try: |
|---|
| 37 | subversion = WhereIs('svn') |
|---|
| 38 | import xml.dom.minidom |
|---|
| 39 | if subversion: |
|---|
| 40 | dom = xml.dom.minidom.parseString( |
|---|
| 41 | os.popen('%s info --xml .' % subversion).read()) |
|---|
| 42 | e = dom.getElementsByTagName('entry')[0] |
|---|
| 43 | package_repository_version = int(e.getAttribute('revision')) |
|---|
| 44 | dom.unlink() |
|---|
| 45 | except: |
|---|
| 46 | # no luck eh, let's cheat |
|---|
| 47 | svnrev = "$Rev$" |
|---|
| 48 | package_repository_version = int(svnrev.split()[1]) |
|---|
| 49 | |
|---|
| 50 | # Find our availabe modules |
|---|
| 51 | available_modules = [] |
|---|
| 52 | for e in os.listdir('modules'): |
|---|
| 53 | if os.path.exists('modules/%s/SConscript' % e): |
|---|
| 54 | available_modules.append(e) |
|---|
| 55 | available_modules.sort() |
|---|
| 56 | |
|---|
| 57 | ############################################################################### |
|---|
| 58 | # Options |
|---|
| 59 | |
|---|
| 60 | # Collect the list of modules we have shipped with |
|---|
| 61 | |
|---|
| 62 | if not os.path.exists('build/options'): |
|---|
| 63 | env.Help(""" |
|---|
| 64 | You may save your build options in the file build/options with the format: |
|---|
| 65 | optionname = "optionvalue" |
|---|
| 66 | A sample options file is provided in build/options.default. |
|---|
| 67 | """) |
|---|
| 68 | |
|---|
| 69 | opts = Options('build/options', ARGUMENTS) |
|---|
| 70 | opts.AddOptions( |
|---|
| 71 | BoolOption('debug', 'Enable creation of debugging symbols', 0), |
|---|
| 72 | ('fdsetsize', 'Override value of FD_SETSIZE', 0), |
|---|
| 73 | BoolOption('ipv6', 'Enable IPv6 support', 1), |
|---|
| 74 | ListOption('modules', 'Modules to build', 'all', available_modules), |
|---|
| 75 | PackageOption('openssl', 'Enable OpenSSL support', 'yes'), |
|---|
| 76 | ListOption('poller', 'Socket polling method', 'auto', |
|---|
| 77 | ['kqueue', 'poll', 'select', 'auto']), |
|---|
| 78 | BoolOption('warnings', 'Compile with very aggressive warning settings', 0), |
|---|
| 79 | # Other path options are handled below |
|---|
| 80 | PathOption('prefix', 'Installation prefix', '/usr/local', |
|---|
| 81 | tests.PathIsDirCreateAccess), |
|---|
| 82 | ) |
|---|
| 83 | opts.Update(env) |
|---|
| 84 | |
|---|
| 85 | # Now take a look in prefix for the stuff we care about: bin, etc, lib, |
|---|
| 86 | # include, share. If these already exist we make our defaults |
|---|
| 87 | # $prefix/etc/ithildin etc. Otherwise we make them $prefix/etc (assuming |
|---|
| 88 | # this is a lone installation location). This is an automated version of |
|---|
| 89 | # the old --disable-dir-suffix autoconf macro from 1.1 and lower. |
|---|
| 90 | # |
|---|
| 91 | # XXX: this is all dumb in Windows where installations will be totally |
|---|
| 92 | # different. |
|---|
| 93 | |
|---|
| 94 | # Okay, let's make some dumb guesses: If the name already contains |
|---|
| 95 | # 'package' in it let's go ahead and not add that again. If the name is |
|---|
| 96 | # standard (/usr, /usr/local, /opt) let's add it there. Other than that we |
|---|
| 97 | # aren't too smart. :) |
|---|
| 98 | if env['prefix'] == '/usr' or env['prefix'] == '/opt': |
|---|
| 99 | # /usr and /opt need special handling to stuff things in /etc |
|---|
| 100 | sysconfdir = '/etc' |
|---|
| 101 | else: |
|---|
| 102 | sysconfdir = env['prefix'] + '/etc' |
|---|
| 103 | |
|---|
| 104 | dir_suffix = '' |
|---|
| 105 | if not package_name in env['prefix']: |
|---|
| 106 | dir_suffix = '/' + package_name |
|---|
| 107 | # let's add one more, if it's in $HOME/<something> let's guess that they |
|---|
| 108 | # don't want that nasty dir_suffix. This could be so wrong as to be absurd, |
|---|
| 109 | # but I believe it is what most will want. |
|---|
| 110 | if (os.environ.has_key('HOME') and os.environ['HOME'] in env['prefix']): |
|---|
| 111 | dir_suffix = '' |
|---|
| 112 | |
|---|
| 113 | opts.AddOptions( |
|---|
| 114 | PathOption('bindir', "Binary installation directory", |
|---|
| 115 | env['prefix'] + '/bin', tests.PathIsDirCreateAccess), |
|---|
| 116 | PathOption('sysconfdir', "Configuration file installation directory", |
|---|
| 117 | sysconfdir + dir_suffix, tests.PathIsDirCreateAccess), |
|---|
| 118 | PathOption('libdir', "Library installation directory", |
|---|
| 119 | env['prefix'] + '/lib' + dir_suffix, tests.PathIsDirCreateAccess), |
|---|
| 120 | PathOption('includedir', 'C header installation directory', |
|---|
| 121 | env['prefix'] + '/include', tests.PathIsDirCreateAccess), |
|---|
| 122 | PathOption('datadir', "Miscellaneous file installation directory", |
|---|
| 123 | env['prefix'] + '/share' + dir_suffix, tests.PathIsDirCreateAccess), |
|---|
| 124 | ) |
|---|
| 125 | opts.Update(env) |
|---|
| 126 | |
|---|
| 127 | env.Help(opts.GenerateHelpText(env)) |
|---|
| 128 | |
|---|
| 129 | ############################################################################### |
|---|
| 130 | # Feature tests |
|---|
| 131 | |
|---|
| 132 | conf = env.Configure(config_h = 'include/ithildin/config.h', |
|---|
| 133 | custom_tests = tests.tests) |
|---|
| 134 | |
|---|
| 135 | conf.CheckCCompiler() |
|---|
| 136 | conf.CheckCVersion() |
|---|
| 137 | |
|---|
| 138 | # header tests: |
|---|
| 139 | # We have headers we want, headers we need, and we save all this to a final |
|---|
| 140 | # variable listing all the headers we have. |
|---|
| 141 | need_headers = Split(''' |
|---|
| 142 | assert.h errno.h limits.h math.h stdbool.h string.h |
|---|
| 143 | ''') |
|---|
| 144 | want_headers = Split(''' |
|---|
| 145 | dl.h dlfcn.h fcntl.h features.h grp.h netdb.h pwd.h strings.h time.h unistd.h |
|---|
| 146 | arpa/inet.h |
|---|
| 147 | machine/endian.h |
|---|
| 148 | netinet/in.h |
|---|
| 149 | sys/mman.h sys/resource.h sys/socket.h sys/stat.h sys/time.h |
|---|
| 150 | |
|---|
| 151 | windows.h winsock.h |
|---|
| 152 | ''') |
|---|
| 153 | |
|---|
| 154 | have_headers = list() |
|---|
| 155 | for h in want_headers: |
|---|
| 156 | if conf.CheckHeader(h): have_headers.append(h) |
|---|
| 157 | for h in need_headers: |
|---|
| 158 | if conf.CheckHeader(h): have_headers.append(h) |
|---|
| 159 | else: |
|---|
| 160 | print 'Header %s is required for compilation' % h |
|---|
| 161 | env.Exit(1) |
|---|
| 162 | |
|---|
| 163 | # library tests: |
|---|
| 164 | # Somewhat different, we use the want/need lists but they are a list of |
|---|
| 165 | # tuples (library name and function to check for). The have list remains a |
|---|
| 166 | # list of libs we have |
|---|
| 167 | |
|---|
| 168 | want_libs = [ |
|---|
| 169 | ('dl', 'dlopen'), |
|---|
| 170 | ('nsl', 'gethostbyname'), |
|---|
| 171 | ('socket', 'socket'), |
|---|
| 172 | ] |
|---|
| 173 | need_libs = list() |
|---|
| 174 | have_libs = list() |
|---|
| 175 | |
|---|
| 176 | for l in want_libs: |
|---|
| 177 | if conf.CheckLib(library = l[0], symbol = l[1]): have_libs.append(l[0]) |
|---|
| 178 | for l in need_libs: |
|---|
| 179 | if conf.CheckLib(library = l[0], symbol = l[1]): have_libs.append(l[0]) |
|---|
| 180 | else: |
|---|
| 181 | print 'Library %s is required for compilation' % l[0] |
|---|
| 182 | env.Exit(1) |
|---|
| 183 | |
|---|
| 184 | # function tests: |
|---|
| 185 | # Here again we have functions we need, functioncs we want, and a final |
|---|
| 186 | # listing of functions we have |
|---|
| 187 | want_funcs = Split(''' |
|---|
| 188 | getaddrinfo kqueue poll select |
|---|
| 189 | fmtcheck strlcat strlcpy strsep |
|---|
| 190 | getegid getgid getgrnam setegid setgid setgroups |
|---|
| 191 | geteuid getuid getpwnam seteuid setuid |
|---|
| 192 | readlink getenv recv send setsockopt |
|---|
| 193 | getrlimit setrlimit |
|---|
| 194 | ''') |
|---|
| 195 | # XXX: fill in need_funcs with a reasonable subset of what we need, stuff |
|---|
| 196 | # that will prove various libraries exist. Blech! |
|---|
| 197 | need_funcs = list() |
|---|
| 198 | |
|---|
| 199 | have_funcs = list() |
|---|
| 200 | |
|---|
| 201 | for f in want_funcs: |
|---|
| 202 | if conf.CheckFunc(f): have_funcs.append(f) |
|---|
| 203 | for f in need_funcs: |
|---|
| 204 | if conf.CheckFunc(f): have_funcs.append(f) |
|---|
| 205 | else: |
|---|
| 206 | print 'Function %s is required for compilation' % f |
|---|
| 207 | env.Exit(1) |
|---|
| 208 | |
|---|
| 209 | # OpenSSL test: |
|---|
| 210 | # We need libs and headers above first, in some cases, to test this. |
|---|
| 211 | if env['openssl']: |
|---|
| 212 | # if the configured option is not an integer it must be their desired |
|---|
| 213 | # path(s) |
|---|
| 214 | if env['openssl'].__class__ is not int: |
|---|
| 215 | res = conf.CheckOpenSSL(Split(env['openssl'])) |
|---|
| 216 | else: |
|---|
| 217 | res = conf.CheckOpenSSL() |
|---|
| 218 | |
|---|
| 219 | if not res: |
|---|
| 220 | print 'Could not find OpenSSL. If you wish to build without support' |
|---|
| 221 | print 'for SSL please specify openssl=no to the build command' |
|---|
| 222 | Exit(1) |
|---|
| 223 | |
|---|
| 224 | env.Append(CPPDEFINES = ['HAVE_OPENSSL']) |
|---|
| 225 | |
|---|
| 226 | # IPv6 test: |
|---|
| 227 | # We just look to see if we have getaddrinfo and if they want IPv6 support |
|---|
| 228 | if conf.CheckIPv6(have_funcs): |
|---|
| 229 | env.Append(CPPDEFINES = ['INET6']) |
|---|
| 230 | |
|---|
| 231 | # FD_SETSIZE test: |
|---|
| 232 | # Just check the passed value and decide whether or not to use this |
|---|
| 233 | if env['fdsetsize']: |
|---|
| 234 | env.Append(CPPDEFINES = ['FD_SETSIZE=%s'] % env['fdsetsize']) |
|---|
| 235 | |
|---|
| 236 | # Poller selection: |
|---|
| 237 | # If they ask for auto go in the preference order of: kqueue > poll > select |
|---|
| 238 | # except when Linux is provided, in which case we always use select() (weird |
|---|
| 239 | # breakage with poll() on Linux and it has no kqueue). If they ask for |
|---|
| 240 | # something explicit we'll always use that. |
|---|
| 241 | kqueue_supported_on = ['DragonFlyBSD', 'FreeBSD', 'NetBSD', 'OpenBSD'] |
|---|
| 242 | host_os = os.uname()[0] |
|---|
| 243 | if str(env['poller']) == 'auto': |
|---|
| 244 | if host_os == 'Linux': |
|---|
| 245 | env['poller'] = 'select' |
|---|
| 246 | elif 'kqueue' in have_funcs and host_os in kqueue_supported_on: |
|---|
| 247 | env['poller'] = 'kqueue' |
|---|
| 248 | elif 'poll' in have_funcs: |
|---|
| 249 | env['poller'] = 'poll' |
|---|
| 250 | elif 'select' in have_funcs: |
|---|
| 251 | env['poller'] = 'select' |
|---|
| 252 | else: |
|---|
| 253 | print 'Unable to find suitable polling function' |
|---|
| 254 | Exit(1) |
|---|
| 255 | |
|---|
| 256 | # now take the poller value, set for correctness, and double check it |
|---|
| 257 | if not str(env['poller']) in have_funcs: |
|---|
| 258 | print 'We do not have the %s poller' % env['poller'] |
|---|
| 259 | Exit(1) |
|---|
| 260 | |
|---|
| 261 | env.Append(CPPDEFINES = ['POLLER_%s' % str(env['poller']).upper()]) |
|---|
| 262 | |
|---|
| 263 | # And let's set compiler options! |
|---|
| 264 | if env['CC'] == 'gcc': |
|---|
| 265 | if env['debug']: |
|---|
| 266 | env.AppendUnique(CCFLAGS = ['-O', '-g3']) |
|---|
| 267 | if env['warnings']: |
|---|
| 268 | env.AppendUnique(CCFLAGS = Split(''' |
|---|
| 269 | -Wall -Wshadow -Wmissing-declarations -Wpointer-arith |
|---|
| 270 | -Wcast-align -Wstrict-prototypes -Wmissing-prototypes |
|---|
| 271 | -Wmissing-declarations -Wredundant-decls -Winline |
|---|
| 272 | -Wbad-function-cast |
|---|
| 273 | ''')) |
|---|
| 274 | |
|---|
| 275 | conf.Finish() |
|---|
| 276 | |
|---|
| 277 | # leave this here for debugging (just comment it out...) |
|---|
| 278 | #print env.Dictionary() |
|---|
| 279 | |
|---|
| 280 | ############################################################################### |
|---|
| 281 | # Build options and subsidiary code |
|---|
| 282 | |
|---|
| 283 | # Set our final environment parameters. |
|---|
| 284 | env.Append(LIBPATH = '#/lib') |
|---|
| 285 | env.AppendUnique(RPATH = [env['libdir'], env['libdir'] + '/modules']) |
|---|
| 286 | |
|---|
| 287 | # Update target for subversion users. |
|---|
| 288 | if subversion: |
|---|
| 289 | env.AlwaysBuild(env.Alias('update', '#/.svn', '%s update' % subversion)) |
|---|
| 290 | # Installation targets for documentation, config and include files |
|---|
| 291 | tests.AddConfInstall(env, env['sysconfdir'], ['etc/ithildin.conf']) |
|---|
| 292 | env.Install(env['datadir'], ['COPYING', 'DEVELOPERS', 'README']) |
|---|
| 293 | env.Install(env['datadir'] + '/doc', glob.glob('doc/*.txt')) |
|---|
| 294 | env.Install(env['includedir'] + '/ithildin', |
|---|
| 295 | glob.glob('include/ithildin/*.h')) |
|---|
| 296 | |
|---|
| 297 | # dummy install target with our various prefixes. |
|---|
| 298 | env.Alias('install', [env['bindir'], env['sysconfdir'], |
|---|
| 299 | env['libdir'], env['includedir'], env['datadir']]) |
|---|
| 300 | |
|---|
| 301 | # Now export useful variables |
|---|
| 302 | env.Export(['env', 'package_name', 'package_version', |
|---|
| 303 | 'package_repository_version', 'tests']) |
|---|
| 304 | |
|---|
| 305 | # and call our sub-scripts. |
|---|
| 306 | env.SConscript('lib/SConscript') |
|---|
| 307 | env.SConscript('source/SConscript') |
|---|
| 308 | |
|---|
| 309 | # Add the requested modules too! |
|---|
| 310 | if str(env['modules']) == 'all': |
|---|
| 311 | modules = str.join(',', available_modules) |
|---|
| 312 | elif str(env['modules']) == 'none': |
|---|
| 313 | modules = '' |
|---|
| 314 | else: |
|---|
| 315 | modules = str(env['modules']) |
|---|
| 316 | |
|---|
| 317 | for m in modules.split(','): |
|---|
| 318 | if not m: continue |
|---|
| 319 | # create a modified env for each module |
|---|
| 320 | modenv = env.Clone() |
|---|
| 321 | env.SConscript('modules/%s/SConscript' % m, exports = ['modenv']) |
|---|
| 322 | del modenv |
|---|
| 323 | |
|---|
| 324 | # vi:set ts=8 sts=4 sw=4 tw=76 et syntax=python: |
|---|