import itertools import os import re from collections import OrderedDict from copy import copy try: string_types = basestring except NameError: string_types = str # search any file, not just executables def WhereIsFile(file, paths): for d in paths: f = os.path.join(d, file) if os.path.isfile(f): try: st = os.stat(f) except OSError: # os.stat() raises OSError, not IOError if the file # doesn't exist, so in this case we let IOError get # raised so as to not mask possibly serious disk or # network issues. continue return os.path.normpath(f) return None def FlattenLibs(libs): if isinstance(libs, string_types): return [libs] else: return [item for sublibs in libs for item in FlattenLibs(sublibs)] # removes all but the *LAST* occurance of a lib in the list def RemoveDuplicateLibs(libs): libs = FlattenLibs(libs) # remove empty strings from list libs = list(filter(lambda x: x != '', libs)) return list(reversed(OrderedDict.fromkeys(reversed(libs)))) Import('env') def WorkaroundFreeBSDLibOrder(libs): # lib(re)ssl includes (weak) arc4random functions # which "on purpose" might conflict with those in libc # => link libc first solves this # (required for FreeBSD11 fullstatic build) import platform if ('c' in libs) and (platform.system() == 'FreeBSD'): return ['c'] + libs return libs def GatherLibs(env, *libs): libs = RemoveDuplicateLibs(env['LIBS'] + list(libs) + [env['APPEND_LIBS']]) return WorkaroundFreeBSDLibOrder(libs) common_src = Split("base64.c buffer.c burl.c log.c \ http_header.c http_kv.c keyvalue.c chunk.c \ http_chunk.c fdevent.c fdevent_fdnode.c gw_backend.c \ stat_cache.c http_etag.c array.c \ algo_md5.c algo_sha1.c algo_splaytree.c \ configfile-glue.c \ http-header-glue.c \ http_cgi.c \ http_date.c \ plugin.c \ reqpool.c \ request.c \ sock_addr.c \ rand.c \ fdlog_maint.c \ fdlog.c \ sys-setjmp.c \ ck.c \ ") src = Split("server.c response.c connections.c h1.c \ sock_addr_cache.c \ fdevent_impl.c \ http_range.c \ network.c \ network_write.c \ data_config.c \ configfile.c configparser.c") builtin_mods = Split(" \ mod_rewrite.c \ mod_redirect.c \ mod_access.c \ mod_alias.c \ mod_indexfile.c \ mod_staticfile.c \ mod_setenv.c \ mod_expire.c \ mod_simple_vhost.c \ mod_evhost.c \ mod_fastcgi.c \ mod_scgi.c \ ") lemon = env.Program('lemon', 'lemon.c', LIBS = GatherLibs(env)) def Lemon(env, input): parser = env.Command([input.replace('.y', '.c'),input.replace('.y', '.h')], input, lemon[0].path + ' -q -dsconsbuild/build -Tsrc/lempar.c $SOURCE') env.Depends(parser, lemon) configparser = Lemon(env, 'configparser.y') ## the modules and how they are built modules = { 'mod_accesslog' : { 'src' : [ 'mod_accesslog.c' ] }, 'mod_ajp13' : { 'src' : [ 'mod_ajp13.c' ] }, 'mod_auth' : { 'src' : [ 'mod_auth.c', 'mod_auth_api.c' ], 'lib' : [ env['LIBCRYPTO'] ] }, 'mod_authn_file' : { 'src' : [ 'mod_authn_file.c' ], 'lib' : [ env['LIBCRYPT'], env['LIBCRYPTO'] ] }, 'mod_cgi' : { 'src' : [ 'mod_cgi.c' ] }, 'mod_deflate' : { 'src' : [ 'mod_deflate.c' ], 'lib' : [ env['LIBZ'], env['LIBZSTD'], env['LIBBZ2'], env['LIBBROTLI'], env['LIBDEFLATE'], 'm' ] }, 'mod_dirlisting' : { 'src' : [ 'mod_dirlisting.c' ] }, 'mod_extforward' : { 'src' : [ 'mod_extforward.c' ] }, 'mod_h2' : { 'src' : [ 'h2.c', 'ls-hpack/lshpack.c', 'algo_xxhash.c' ], 'lib' : [ env['LIBXXHASH'] ] }, 'mod_proxy' : { 'src' : [ 'mod_proxy.c' ] }, 'mod_rrdtool' : { 'src' : [ 'mod_rrdtool.c' ] }, 'mod_sockproxy' : { 'src' : [ 'mod_sockproxy.c' ] }, 'mod_ssi' : { 'src' : [ 'mod_ssi.c' ] }, 'mod_status' : { 'src' : [ 'mod_status.c' ] }, 'mod_userdir' : { 'src' : [ 'mod_userdir.c' ] }, 'mod_vhostdb' : { 'src' : [ 'mod_vhostdb.c', 'mod_vhostdb_api.c' ] }, 'mod_webdav' : { 'src' : [ 'mod_webdav.c' ], 'lib' : [ env['LIBXML2'], env['LIBSQLITE3'], env['LIBUUID'] ] }, 'mod_wstunnel' : { 'src' : [ 'mod_wstunnel.c' ], 'lib' : [ env['LIBCRYPTO'] ] }, } if env['with_maxminddb']: modules['mod_maxminddb'] = { 'src' : [ 'mod_maxminddb.c' ], 'lib' : [ env['LIBMAXMINDDB'] ] } if env['with_krb5']: modules['mod_authn_gssapi'] = { 'src' : [ 'mod_authn_gssapi.c' ], 'lib' : [ env['LIBKRB5'], env['LIBGSSAPI_KRB5'] ] } if env['with_ldap']: modules['mod_authn_ldap'] = { 'src' : [ 'mod_authn_ldap.c' ], 'lib' : [ env['LIBLDAP'], env['LIBLBER'] ] } modules['mod_vhostdb_ldap'] = { 'src' : [ 'mod_vhostdb_ldap.c' ], 'lib' : [ env['LIBLDAP'], env['LIBLBER'] ] } if env['with_lua']: modules['mod_magnet'] = { 'src' : [ 'mod_magnet.c', 'mod_magnet_cache.c', 'algo_hmac.c' ], 'lib' : [ env['LIBLUA'], env['LIBCRYPTO'] ] } if env['with_pam']: modules['mod_authn_pam'] = { 'src' : [ 'mod_authn_pam.c' ], 'lib' : [ env['LIBPAM'] ] } if env['with_mysql']: modules['mod_vhostdb_mysql'] = { 'src' : [ 'mod_vhostdb_mysql.c' ], 'lib' : [ env['LIBMYSQL'] ] } if env['with_pgsql']: modules['mod_vhostdb_pgsql'] = { 'src' : [ 'mod_vhostdb_pgsql.c' ], 'lib' : [ env['LIBPGSQL'] ] } if env['with_dbi']: modules['mod_authn_dbi'] = { 'src' : [ 'mod_authn_dbi.c' ], 'lib' : [ env['LIBCRYPT'], env['LIBDBI'], env['LIBCRYPTO'] ] } modules['mod_vhostdb_dbi'] = { 'src' : [ 'mod_vhostdb_dbi.c' ], 'lib' : [ env['LIBDBI'] ] } if env['with_sasl']: modules['mod_authn_sasl'] = { 'src' : [ 'mod_authn_sasl.c' ], 'lib' : [ env['LIBSASL'] ] } if env['with_openssl']: modules['mod_openssl'] = { 'src' : [ 'mod_openssl.c' ], 'lib' : [ env['LIBSSL'], env['LIBSSLCRYPTO'] ] } if env['with_wolfssl']: modules['mod_wolfssl'] = { 'src' : [ 'mod_wolfssl.c' ], 'lib' : [ env['LIBWOLFSSL'], 'm' ] } if env['with_mbedtls']: modules['mod_mbedtls'] = { 'src' : [ 'mod_mbedtls.c' ], 'lib' : [ env['LIBMBEDTLS'], env['LIBMBEDX509'], env['LIBMBEDCRYPTO'] ] } if env['with_nss']: modules['mod_nss'] = { 'src' : [ 'mod_nss.c' ], 'lib' : [ env['LIBNSS'] ] } if env['with_gnutls']: modules['mod_gnutls'] = { 'src' : [ 'mod_gnutls.c' ], 'lib' : [ env['LIBGNUTLS'] ] } staticenv = env.Clone(CPPFLAGS=[ env['CPPFLAGS'], '-DLIGHTTPD_STATIC' ]) ## all the core-sources + the modules staticsrc = src + common_src staticlib = copy(env['LIBS']) staticinit = '' staticsrc += builtin_mods for module in builtin_mods: staticinit += "PLUGIN_INIT(%s)\n"%module[0:-2] for module in modules.keys(): if module == "mod_h2": continue staticsrc += modules[module]['src'] staticinit += "PLUGIN_INIT(%s)\n"%module if 'lib' in modules[module]: staticlib += modules[module]['lib'] def WriteStaticPluginHeader(target, source, env): do_write = True data = env['STATICINIT'] # only touch the file if content actually changes try: with open(target[0].abspath, 'r') as f: do_write = (data != f.read()) except IOError: pass if do_write: with open(target[0].abspath, 'w+') as f: f.write(env['STATICINIT']) env['STATICINIT'] = staticinit staticheader = env.AlwaysBuild(env.Command('plugin-static.h', [], WriteStaticPluginHeader)) ## turn all src-files into objects staticobj = [] static_plugin_obj = None for cfile in staticsrc: if cfile == 'plugin.c': static_plugin_obj = [ staticenv.Object('static-' + cfile.replace('.c', ''), cfile) ] staticobj += static_plugin_obj else: staticobj += [ staticenv.Object('static-' + cfile.replace('.c', ''), cfile) ] env.Depends(static_plugin_obj, 'plugin-static.h') ## includes all modules, but links dynamically against other libs staticbin = staticenv.Program('../static/build/lighttpd', staticobj, LIBS = GatherLibs(env, env['LIBPCRE'], staticlib) ) ## you might have to adjust the list of libs and the order for your setup ## this is tricky, be warned fullstaticlib = [] ## try to calculate the libs for fullstatic with ldd ## 1. find the lib ## 2. check the deps ## 3. add them to the libs #searchlibs = os.pathsep.join([ '/lib/', '/usr/lib/', '/usr/local/lib/' ]) searchlibs = [] searchpathre = re.compile(r'\bSEARCH_DIR\("=([^"]+)"\)') f = os.popen('ld --verbose | grep SEARCH_DIR', 'r') for aword in searchpathre.findall(f.read()): searchlibs += [ aword] f.close lddre = re.compile(r'^\s+lib([^=-]+)(?:-[\.0-9]+)?\.so\.[0-9]+ =>', re.MULTILINE) for libs in staticlib: if isinstance(libs, string_types): libs = [ libs ] for lib in libs: fullstaticlib += [ lib ] solibpath = WhereIsFile('lib' + lib + '.so', paths = searchlibs) if solibpath is None: continue f = os.popen('ldd ' + solibpath, 'r') for aword in lddre.findall(f.read()): fullstaticlib += [ aword ] f.close ## glibc pthread needs to be linked completely (especially nptl-init.o) ## or not at all, or else pthread structures may not be initialized correctly import platform fullstatic_libs = GatherLibs(env, env['LIBPCRE'], fullstaticlib) fullstatic_linkflags = [staticenv['LINKFLAGS'], '-static'] if (('pthread' in fullstatic_libs) or ('pcre' in fullstatic_libs)) and (platform.system() == 'Linux'): fullstatic_linkflags += ['-Wl,--whole-archive','-lpthread','-Wl,--no-whole-archive'] if 'pthread' in fullstatic_libs: fullstatic_libs.remove('pthread') if 'gcc_s' in fullstatic_libs: fullstatic_linkflags += ['-static-libgcc'] fullstatic_libs.remove('gcc_s') ## includes all modules, linked statically fullstaticbin = staticenv.Program('../fullstatic/build/lighttpd', staticobj, LIBS = fullstatic_libs, LINKFLAGS= fullstatic_linkflags ) Alias('static', staticbin) Alias('fullstatic', fullstaticbin) implib = 'lighttpd.exe.a' bin_targets = ['lighttpd'] bin_linkflags = [ env['LINKFLAGS'] ] if env['COMMON_LIB'] == 'lib': common_lib = env.SharedLibrary('liblighttpd', common_src, LINKFLAGS = [ env['LINKFLAGS'], '-Wl,--export-dynamic' ]) else: src += common_src common_lib = [] if env['COMMON_LIB'] == 'bin': bin_linkflags += [ '-Wl,--export-all-symbols', '-Wl,--out-implib=build/' + implib ] bin_targets += [ implib ] else: bin_linkflags += [ '-Wl,--export-dynamic' ] instbin = env.Program(bin_targets, src + builtin_mods, LINKFLAGS = bin_linkflags, LIBS = GatherLibs( env, common_lib, env['LIBCRYPTO'], env['LIBDL'], env['LIBPCRE'], env['LIBXXHASH'], ) ) env.Depends(instbin, configparser) if env['COMMON_LIB'] == 'bin': common_lib = instbin[1] env['SHLIBPREFIX'] = '' instlib = [] for module in modules.keys(): libs = [ common_lib ] if 'lib' in modules[module]: libs += modules[module]['lib'] instlib += env.SharedLibrary(module, modules[module]['src'], LIBS = GatherLibs(env, libs)) env.Alias('modules', instlib) inst = [] if env['build_dynamic']: Default(instbin[0], instlib) inst += env.Install('${sbindir}', instbin[0]) inst += env.Install('${libdir}', instlib) if env['COMMON_LIB'] == 'lib': Default(common_lib) inst += env.Install('${bindir}', common_lib) if env['build_static']: Default(staticbin) inst += env.Install('${sbindir}', staticbin) if env['build_fullstatic']: Default(fullstaticbin) inst += env.Install('${sbindir}', fullstaticbin) env.Alias('dynamic', instbin) # default all to be installed env.Alias('install', inst) pkgdir = '.' tarname = env['package'] + '-' + env['version']