1"""custom 2 3Custom builders and methods. 4 5""" 6 7# 8# Copyright 2008 VMware, Inc. 9# All Rights Reserved. 10# 11# Permission is hereby granted, free of charge, to any person obtaining a 12# copy of this software and associated documentation files (the 13# "Software"), to deal in the Software without restriction, including 14# without limitation the rights to use, copy, modify, merge, publish, 15# distribute, sub license, and/or sell copies of the Software, and to 16# permit persons to whom the Software is furnished to do so, subject to 17# the following conditions: 18# 19# The above copyright notice and this permission notice (including the 20# next paragraph) shall be included in all copies or substantial portions 21# of the Software. 22# 23# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 24# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. 26# IN NO EVENT SHALL VMWARE AND/OR ITS SUPPLIERS BE LIABLE FOR 27# ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 28# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 29# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30# 31 32 33import os.path 34import sys 35import subprocess 36import modulefinder 37 38import SCons.Action 39import SCons.Builder 40import SCons.Scanner 41 42import fixes 43 44import source_list 45 46# the get_implicit_deps() method changed between 2.4 and 2.5: now it expects 47# a callable that takes a scanner as argument and returns a path, rather than 48# a path directly. We want to support both, so we need to detect the SCons version, 49# for which no API is provided by SCons 8-P 50 51# Scons version string has consistently been in this format: 52# MajorVersion.MinorVersion.Patch[.alpha/beta.yyyymmdd] 53# so this formula should cover all versions regardless of type 54# stable, alpha or beta. 55# For simplicity alpha and beta flags are removed. 56scons_version = tuple(map(int, SCons.__version__.split('.')[:3])) 57 58def quietCommandLines(env): 59 # Quiet command lines 60 # See also http://www.scons.org/wiki/HidingCommandLinesInOutput 61 env['ASCOMSTR'] = " Assembling $SOURCE ..." 62 env['ASPPCOMSTR'] = " Assembling $SOURCE ..." 63 env['CCCOMSTR'] = " Compiling $SOURCE ..." 64 env['SHCCCOMSTR'] = " Compiling $SOURCE ..." 65 env['CXXCOMSTR'] = " Compiling $SOURCE ..." 66 env['SHCXXCOMSTR'] = " Compiling $SOURCE ..." 67 env['ARCOMSTR'] = " Archiving $TARGET ..." 68 env['RANLIBCOMSTR'] = " Indexing $TARGET ..." 69 env['LINKCOMSTR'] = " Linking $TARGET ..." 70 env['SHLINKCOMSTR'] = " Linking $TARGET ..." 71 env['LDMODULECOMSTR'] = " Linking $TARGET ..." 72 env['SWIGCOMSTR'] = " Generating $TARGET ..." 73 env['LEXCOMSTR'] = " Generating $TARGET ..." 74 env['YACCCOMSTR'] = " Generating $TARGET ..." 75 env['CODEGENCOMSTR'] = " Generating $TARGET ..." 76 env['INSTALLSTR'] = " Installing $TARGET ..." 77 78 79def createConvenienceLibBuilder(env): 80 """This is a utility function that creates the ConvenienceLibrary 81 Builder in an Environment if it is not there already. 82 83 If it is already there, we return the existing one. 84 85 Based on the stock StaticLibrary and SharedLibrary builders. 86 """ 87 88 try: 89 convenience_lib = env['BUILDERS']['ConvenienceLibrary'] 90 except KeyError: 91 action_list = [ SCons.Action.Action("$ARCOM", "$ARCOMSTR") ] 92 if env.Detect('ranlib'): 93 ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR") 94 action_list.append(ranlib_action) 95 96 convenience_lib = SCons.Builder.Builder(action = action_list, 97 emitter = '$LIBEMITTER', 98 prefix = '$LIBPREFIX', 99 suffix = '$LIBSUFFIX', 100 src_suffix = '$SHOBJSUFFIX', 101 src_builder = 'SharedObject') 102 env['BUILDERS']['ConvenienceLibrary'] = convenience_lib 103 104 return convenience_lib 105 106 107def python_scan(node, env, path): 108 # http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789 109 # https://docs.python.org/2/library/modulefinder.html 110 contents = node.get_contents() 111 112 # Tell ModuleFinder to search dependencies in the script dir, and the glapi 113 # dirs 114 source_dir = node.get_dir().abspath 115 GLAPI = env.Dir('#src/mapi/glapi/gen').abspath 116 path = [source_dir, GLAPI] + sys.path 117 118 finder = modulefinder.ModuleFinder(path=path) 119 finder.run_script(node.abspath) 120 results = [] 121 for name, mod in finder.modules.items(): 122 if mod.__file__ is None: 123 continue 124 assert os.path.exists(mod.__file__) 125 results.append(env.File(mod.__file__)) 126 return results 127 128python_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py']) 129 130 131def code_generate(env, script, target, source, command): 132 """Method to simplify code generation via python scripts. 133 134 http://www.scons.org/wiki/UsingCodeGenerators 135 http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html 136 """ 137 138 # We're generating code using Python scripts, so we have to be 139 # careful with our scons elements. This entry represents 140 # the generator file *in the source directory*. 141 script_src = env.File(script).srcnode() 142 143 # This command creates generated code *in the build directory*. 144 command = command.replace('$SCRIPT', script_src.path) 145 action = SCons.Action.Action(command, "$CODEGENCOMSTR") 146 code = env.Command(target, source, action) 147 148 # Explicitly mark that the generated code depends on the generator, 149 # and on implicitly imported python modules 150 path = (script_src.get_dir(),) if scons_version < (2, 5, 0) else lambda x: script_src 151 deps = [script_src] 152 deps += script_src.get_implicit_deps(env, python_scanner, path) 153 env.Depends(code, deps) 154 155 # Running the Python script causes .pyc files to be generated in the 156 # source directory. When we clean up, they should go too. So add side 157 # effects for .pyc files 158 for dep in deps: 159 pyc = env.File(str(dep) + 'c') 160 env.SideEffect(pyc, code) 161 162 return code 163 164 165def createCodeGenerateMethod(env): 166 env.Append(SCANNERS = python_scanner) 167 env.AddMethod(code_generate, 'CodeGenerate') 168 169 170def _pkg_check_modules(env, name, modules): 171 '''Simple wrapper for pkg-config.''' 172 173 env['HAVE_' + name] = False 174 175 # For backwards compatability 176 env[name.lower()] = False 177 178 if env['platform'] == 'windows': 179 return 180 181 if not env.Detect('pkg-config'): 182 return 183 184 if subprocess.call(["pkg-config", "--exists", ' '.join(modules)]) != 0: 185 return 186 187 # Strip version expressions from modules 188 modules = [module.split(' ', 1)[0] for module in modules] 189 190 # Other flags may affect the compilation of unrelated targets, so store 191 # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc) 192 try: 193 flags = env.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules)) 194 except OSError: 195 return 196 prefix = name + '_' 197 for flag_name, flag_value in flags.items(): 198 assert '_' not in flag_name 199 env[prefix + flag_name] = flag_value 200 201 env['HAVE_' + name] = True 202 203def pkg_check_modules(env, name, modules): 204 205 sys.stdout.write('Checking for %s (%s)...' % (name, ' '.join(modules))) 206 _pkg_check_modules(env, name, modules) 207 result = env['HAVE_' + name] 208 sys.stdout.write(' %s\n' % ['no', 'yes'][int(bool(result))]) 209 210 # XXX: For backwards compatability 211 env[name.lower()] = result 212 213 214def pkg_use_modules(env, names): 215 '''Search for all environment flags that match NAME_FOO and append them to 216 the FOO environment variable.''' 217 218 names = env.Flatten(names) 219 220 for name in names: 221 prefix = name + '_' 222 223 if not 'HAVE_' + name in env: 224 raise Exception('Attempt to use unknown module %s' % name) 225 226 if not env['HAVE_' + name]: 227 raise Exception('Attempt to use unavailable module %s' % name) 228 229 flags = {} 230 for flag_name, flag_value in env.Dictionary().items(): 231 if flag_name.startswith(prefix): 232 flag_name = flag_name[len(prefix):] 233 if '_' not in flag_name: 234 flags[flag_name] = flag_value 235 if flags: 236 env.MergeFlags(flags) 237 238 239def createPkgConfigMethods(env): 240 env.AddMethod(pkg_check_modules, 'PkgCheckModules') 241 env.AddMethod(pkg_use_modules, 'PkgUseModules') 242 243 244def parse_source_list(env, filename, names=None): 245 # parse the source list file 246 parser = source_list.SourceListParser() 247 src = env.File(filename).srcnode() 248 249 cur_srcdir = env.Dir('.').srcnode().abspath 250 top_srcdir = env.Dir('#').abspath 251 top_builddir = os.path.join(top_srcdir, env['build_dir']) 252 253 # Normalize everything to / slashes 254 cur_srcdir = cur_srcdir.replace('\\', '/') 255 top_srcdir = top_srcdir.replace('\\', '/') 256 top_builddir = top_builddir.replace('\\', '/') 257 258 # Populate the symbol table of the Makefile parser. 259 parser.add_symbol('top_srcdir', top_srcdir) 260 parser.add_symbol('top_builddir', top_builddir) 261 262 sym_table = parser.parse(src.abspath) 263 264 if names: 265 if sys.version_info[0] >= 3: 266 if isinstance(names, str): 267 names = [names] 268 else: 269 if isinstance(names, basestring): 270 names = [names] 271 272 symbols = names 273 else: 274 symbols = list(sym_table.keys()) 275 276 # convert the symbol table to source lists 277 src_lists = {} 278 for sym in symbols: 279 val = sym_table[sym] 280 srcs = [] 281 for f in val.split(): 282 if f: 283 # Process source paths 284 if f.startswith(top_builddir + '/src'): 285 # Automake puts build output on a `src` subdirectory, but 286 # SCons does not, so strip it here. 287 f = top_builddir + f[len(top_builddir + '/src'):] 288 if f.startswith(cur_srcdir + '/'): 289 # Prefer relative source paths, as absolute files tend to 290 # cause duplicate actions. 291 f = f[len(cur_srcdir + '/'):] 292 # do not include any headers 293 if f.endswith(tuple(['.h','.hpp','.inl'])): 294 continue 295 srcs.append(f) 296 297 src_lists[sym] = srcs 298 299 # if names are given, concatenate the lists 300 if names: 301 srcs = [] 302 for name in names: 303 srcs.extend(src_lists[name]) 304 305 return srcs 306 else: 307 return src_lists 308 309def createParseSourceListMethod(env): 310 env.AddMethod(parse_source_list, 'ParseSourceList') 311 312 313def generate(env): 314 """Common environment generation code""" 315 316 verbose = env.get('verbose', False) or not env.get('quiet', True) 317 if not verbose: 318 quietCommandLines(env) 319 320 # Custom builders and methods 321 createConvenienceLibBuilder(env) 322 createCodeGenerateMethod(env) 323 createPkgConfigMethods(env) 324 createParseSourceListMethod(env) 325 326 # for debugging 327 #print env.Dump() 328 329 330def exists(env): 331 return 1 332