1 """custom 2 3 Custom 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 33 import os.path 34 import sys 35 import subprocess 36 import modulefinder 37 38 import SCons.Action 39 import SCons.Builder 40 import SCons.Scanner 41 42 import fixes 43 44 import 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 = tuple(map(int, SCons.__version__.split('.'))) 52 53 def quietCommandLines(env): 54 # Quiet command lines 55 # See also http://www.scons.org/wiki/HidingCommandLinesInOutput 56 env['ASCOMSTR'] = " Assembling $SOURCE ..." 57 env['ASPPCOMSTR'] = " Assembling $SOURCE ..." 58 env['CCCOMSTR'] = " Compiling $SOURCE ..." 59 env['SHCCCOMSTR'] = " Compiling $SOURCE ..." 60 env['CXXCOMSTR'] = " Compiling $SOURCE ..." 61 env['SHCXXCOMSTR'] = " Compiling $SOURCE ..." 62 env['ARCOMSTR'] = " Archiving $TARGET ..." 63 env['RANLIBCOMSTR'] = " Indexing $TARGET ..." 64 env['LINKCOMSTR'] = " Linking $TARGET ..." 65 env['SHLINKCOMSTR'] = " Linking $TARGET ..." 66 env['LDMODULECOMSTR'] = " Linking $TARGET ..." 67 env['SWIGCOMSTR'] = " Generating $TARGET ..." 68 env['LEXCOMSTR'] = " Generating $TARGET ..." 69 env['YACCCOMSTR'] = " Generating $TARGET ..." 70 env['CODEGENCOMSTR'] = " Generating $TARGET ..." 71 env['INSTALLSTR'] = " Installing $TARGET ..." 72 73 74 def createConvenienceLibBuilder(env): 75 """This is a utility function that creates the ConvenienceLibrary 76 Builder in an Environment if it is not there already. 77 78 If it is already there, we return the existing one. 79 80 Based on the stock StaticLibrary and SharedLibrary builders. 81 """ 82 83 try: 84 convenience_lib = env['BUILDERS']['ConvenienceLibrary'] 85 except KeyError: 86 action_list = [ SCons.Action.Action("$ARCOM", "$ARCOMSTR") ] 87 if env.Detect('ranlib'): 88 ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR") 89 action_list.append(ranlib_action) 90 91 convenience_lib = SCons.Builder.Builder(action = action_list, 92 emitter = '$LIBEMITTER', 93 prefix = '$LIBPREFIX', 94 suffix = '$LIBSUFFIX', 95 src_suffix = '$SHOBJSUFFIX', 96 src_builder = 'SharedObject') 97 env['BUILDERS']['ConvenienceLibrary'] = convenience_lib 98 99 return convenience_lib 100 101 102 def python_scan(node, env, path): 103 # http://www.scons.org/doc/0.98.5/HTML/scons-user/c2781.html#AEN2789 104 # https://docs.python.org/2/library/modulefinder.html 105 contents = node.get_contents() 106 107 # Tell ModuleFinder to search dependencies in the script dir, and the glapi 108 # dirs 109 source_dir = node.get_dir().abspath 110 GLAPI = env.Dir('#src/mapi/glapi/gen').abspath 111 path = [source_dir, GLAPI] + sys.path 112 113 finder = modulefinder.ModuleFinder(path=path) 114 finder.run_script(node.abspath) 115 results = [] 116 for name, mod in finder.modules.items(): 117 if mod.__file__ is None: 118 continue 119 assert os.path.exists(mod.__file__) 120 results.append(env.File(mod.__file__)) 121 return results 122 123 python_scanner = SCons.Scanner.Scanner(function = python_scan, skeys = ['.py']) 124 125 126 def code_generate(env, script, target, source, command): 127 """Method to simplify code generation via python scripts. 128 129 http://www.scons.org/wiki/UsingCodeGenerators 130 http://www.scons.org/doc/0.98.5/HTML/scons-user/c2768.html 131 """ 132 133 # We're generating code using Python scripts, so we have to be 134 # careful with our scons elements. This entry represents 135 # the generator file *in the source directory*. 136 script_src = env.File(script).srcnode() 137 138 # This command creates generated code *in the build directory*. 139 command = command.replace('$SCRIPT', script_src.path) 140 action = SCons.Action.Action(command, "$CODEGENCOMSTR") 141 code = env.Command(target, source, action) 142 143 # Explicitly mark that the generated code depends on the generator, 144 # and on implicitly imported python modules 145 path = (script_src.get_dir(),) if scons_version < (2, 5, 0) else lambda x: script_src 146 deps = [script_src] 147 deps += script_src.get_implicit_deps(env, python_scanner, path) 148 env.Depends(code, deps) 149 150 # Running the Python script causes .pyc files to be generated in the 151 # source directory. When we clean up, they should go too. So add side 152 # effects for .pyc files 153 for dep in deps: 154 pyc = env.File(str(dep) + 'c') 155 env.SideEffect(pyc, code) 156 157 return code 158 159 160 def createCodeGenerateMethod(env): 161 env.Append(SCANNERS = python_scanner) 162 env.AddMethod(code_generate, 'CodeGenerate') 163 164 165 def _pkg_check_modules(env, name, modules): 166 '''Simple wrapper for pkg-config.''' 167 168 env['HAVE_' + name] = False 169 170 # For backwards compatability 171 env[name.lower()] = False 172 173 if env['platform'] == 'windows': 174 return 175 176 if not env.Detect('pkg-config'): 177 return 178 179 if subprocess.call(["pkg-config", "--exists", ' '.join(modules)]) != 0: 180 return 181 182 # Strip version expressions from modules 183 modules = [module.split(' ', 1)[0] for module in modules] 184 185 # Other flags may affect the compilation of unrelated targets, so store 186 # them with a prefix, (e.g., XXX_CFLAGS, XXX_LIBS, etc) 187 try: 188 flags = env.ParseFlags('!pkg-config --cflags --libs ' + ' '.join(modules)) 189 except OSError: 190 return 191 prefix = name + '_' 192 for flag_name, flag_value in flags.items(): 193 assert '_' not in flag_name 194 env[prefix + flag_name] = flag_value 195 196 env['HAVE_' + name] = True 197 198 def pkg_check_modules(env, name, modules): 199 200 sys.stdout.write('Checking for %s (%s)...' % (name, ' '.join(modules))) 201 _pkg_check_modules(env, name, modules) 202 result = env['HAVE_' + name] 203 sys.stdout.write(' %s\n' % ['no', 'yes'][int(bool(result))]) 204 205 # XXX: For backwards compatability 206 env[name.lower()] = result 207 208 209 def pkg_use_modules(env, names): 210 '''Search for all environment flags that match NAME_FOO and append them to 211 the FOO environment variable.''' 212 213 names = env.Flatten(names) 214 215 for name in names: 216 prefix = name + '_' 217 218 if not 'HAVE_' + name in env: 219 raise Exception('Attempt to use unknown module %s' % name) 220 221 if not env['HAVE_' + name]: 222 raise Exception('Attempt to use unavailable module %s' % name) 223 224 flags = {} 225 for flag_name, flag_value in env.Dictionary().items(): 226 if flag_name.startswith(prefix): 227 flag_name = flag_name[len(prefix):] 228 if '_' not in flag_name: 229 flags[flag_name] = flag_value 230 if flags: 231 env.MergeFlags(flags) 232 233 234 def createPkgConfigMethods(env): 235 env.AddMethod(pkg_check_modules, 'PkgCheckModules') 236 env.AddMethod(pkg_use_modules, 'PkgUseModules') 237 238 239 def parse_source_list(env, filename, names=None): 240 # parse the source list file 241 parser = source_list.SourceListParser() 242 src = env.File(filename).srcnode() 243 244 cur_srcdir = env.Dir('.').srcnode().abspath 245 top_srcdir = env.Dir('#').abspath 246 top_builddir = os.path.join(top_srcdir, env['build_dir']) 247 248 # Normalize everything to / slashes 249 cur_srcdir = cur_srcdir.replace('\\', '/') 250 top_srcdir = top_srcdir.replace('\\', '/') 251 top_builddir = top_builddir.replace('\\', '/') 252 253 # Populate the symbol table of the Makefile parser. 254 parser.add_symbol('top_srcdir', top_srcdir) 255 parser.add_symbol('top_builddir', top_builddir) 256 257 sym_table = parser.parse(src.abspath) 258 259 if names: 260 if isinstance(names, basestring): 261 names = [names] 262 263 symbols = names 264 else: 265 symbols = list(sym_table.keys()) 266 267 # convert the symbol table to source lists 268 src_lists = {} 269 for sym in symbols: 270 val = sym_table[sym] 271 srcs = [] 272 for f in val.split(): 273 if f: 274 # Process source paths 275 if f.startswith(top_builddir + '/src'): 276 # Automake puts build output on a `src` subdirectory, but 277 # SCons does not, so strip it here. 278 f = top_builddir + f[len(top_builddir + '/src'):] 279 if f.startswith(cur_srcdir + '/'): 280 # Prefer relative source paths, as absolute files tend to 281 # cause duplicate actions. 282 f = f[len(cur_srcdir + '/'):] 283 # do not include any headers 284 if f.endswith(tuple(['.h','.hpp','.inl'])): 285 continue 286 srcs.append(f) 287 288 src_lists[sym] = srcs 289 290 # if names are given, concatenate the lists 291 if names: 292 srcs = [] 293 for name in names: 294 srcs.extend(src_lists[name]) 295 296 return srcs 297 else: 298 return src_lists 299 300 def createParseSourceListMethod(env): 301 env.AddMethod(parse_source_list, 'ParseSourceList') 302 303 304 def generate(env): 305 """Common environment generation code""" 306 307 verbose = env.get('verbose', False) or not env.get('quiet', True) 308 if not verbose: 309 quietCommandLines(env) 310 311 # Custom builders and methods 312 createConvenienceLibBuilder(env) 313 createCodeGenerateMethod(env) 314 createPkgConfigMethods(env) 315 createParseSourceListMethod(env) 316 317 # for debugging 318 #print env.Dump() 319 320 321 def exists(env): 322 return 1 323