LSSTApplications  11.0-13-gbb96280,12.1+18,12.1+7,12.1-1-g14f38d3+72,12.1-1-g16c0db7+5,12.1-1-g5961e7a+84,12.1-1-ge22e12b+23,12.1-11-g06625e2+4,12.1-11-g0d7f63b+4,12.1-19-gd507bfc,12.1-2-g7dda0ab+38,12.1-2-gc0bc6ab+81,12.1-21-g6ffe579+2,12.1-21-gbdb6c2a+4,12.1-24-g941c398+5,12.1-3-g57f6835+7,12.1-3-gf0736f3,12.1-37-g3ddd237,12.1-4-gf46015e+5,12.1-5-g06c326c+20,12.1-5-g648ee80+3,12.1-5-gc2189d7+4,12.1-6-ga608fc0+1,12.1-7-g3349e2a+5,12.1-7-gfd75620+9,12.1-9-g577b946+5,12.1-9-gc4df26a+10
LSSTDataManagementBasePackage
state.py
Go to the documentation of this file.
1 ##
2 # @file state.py
3 #
4 # This module acts like a singleton, holding all global state for sconsUtils.
5 # This includes the primary Environment object (state.env), the message log (state.log),
6 # the command-line variables object (state.opts), and a dictionary of command-line targets
7 # used to setup aliases, default targets, and dependencies (state.targets). All four of
8 # these variables are aliased to the main lsst.sconsUtils scope, so there should be no
9 # need for users to deal with the state module directly.
10 #
11 # These are all initialized when the module is imported, but may be modified by other code
12 # (particularly dependencies.configure()).
13 ##
14 
15 from __future__ import absolute_import, division, print_function
16 import os
17 import re
18 
19 import SCons.Script
20 import SCons.Conftest
21 from . import eupsForScons
22 
23 SCons.Script.EnsureSConsVersion(2, 1, 0)
24 
25 ##
26 # @brief A dictionary of SCons aliases and targets.
27 #
28 # These are used to setup aliases, default targets, and dependencies by BasicSConstruct.finish().
29 # While one can still use env.Alias to setup aliases (and should for "install"), putting targets
30 # here will generally provide better build-time dependency handling (like ensuring everything
31 # is built before we try to install, and making sure SCons doesn't rebuild the world before
32 # installing).
33 #
34 # Users can add additional keys to the dictionary if desired.
35 #
36 # Targets should be added by calling extend() or using += on the dict values, to keep the lists of
37 # targets from turning into lists-of-lists.
38 ##
39 targets = {"doc": [], "tests": [], "lib": [], "python": [], "examples": [], "include": [], "version": [],
40  "shebang": []}
41 
42 # @cond INTERNAL
43 
44 env = None
45 log = None
46 opts = None
47 
48 
49 def _initOptions():
50  SCons.Script.AddOption('--checkDependencies', dest='checkDependencies',
51  action='store_true', default=False,
52  help="Verify dependencies with autoconf-style tests.")
53  SCons.Script.AddOption('--filterWarn', dest='filterWarn', action='store_true', default=False,
54  help="Filter out a class of warnings deemed irrelevant"),
55  SCons.Script.AddOption('--force', dest='force', action='store_true', default=False,
56  help="Set to force possibly dangerous behaviours")
57  SCons.Script.AddOption('--linkFarmDir', dest='linkFarmDir', action='store', default=None,
58  help="The directory of symbolic links needed to build and use the package")
59  SCons.Script.AddOption('--prefix', dest='prefix', action='store', default=False,
60  help="Specify the install destination")
61  SCons.Script.AddOption('--setenv', dest='setenv', action='store_true', default=False,
62  help="Treat arguments such as Foo=bar as defining construction variables")
63  SCons.Script.AddOption('--tag', dest='tag', action='store', default=None,
64  help="Declare product with this eups tag")
65  SCons.Script.AddOption('--verbose', dest='verbose', action='store_true', default=False,
66  help="Print additional messages for debugging.")
67  SCons.Script.AddOption('--traceback', dest='traceback', action='store_true', default=False,
68  help="Print full exception tracebacks when errors occur.")
69  SCons.Script.AddOption('--no-eups', dest='no_eups', action='store_true', default=False,
70  help="Do not use EUPS for configuration")
71 
72 
73 def _initLog():
74  from . import utils
75  global log
76  log = utils.Log()
77 
78 
79 def _initVariables():
80  files = []
81  if "optfile" in SCons.Script.ARGUMENTS:
82  configfile = SCons.Script.ARGUMENTS["optfile"]
83  if configfile not in files:
84  files.append(configfile)
85  for file in files:
86  if not os.path.isfile(file):
87  log.warn("Warning: Will ignore non-existent options file, %s" % file)
88  if "optfile" not in SCons.Script.ARGUMENTS:
89  files.append("buildOpts.py")
90  global opts
91  opts = SCons.Script.Variables(files)
92  opts.AddVariables(
93  ('archflags', 'Extra architecture specification to add to CC/LINK flags (e.g. -m32)', ''),
94  ('cc', 'Choose the compiler to use', ''),
95  SCons.Script.BoolVariable('debug', 'Set to enable debugging flags (use --debug)', True),
96  ('eupsdb', 'Specify which element of EUPS_PATH should be used', None),
97  ('flavor', 'Set the build flavor', None),
98  SCons.Script.BoolVariable('force', 'Set to force possibly dangerous behaviours', False),
99  ('optfile', 'Specify a file to read default options from', None),
100  ('prefix', 'Specify the install destination', None),
101  SCons.Script.EnumVariable('opt', 'Set the optimisation level', 3,
102  allowed_values=('g', '0', '1', '2', '3')),
103  SCons.Script.EnumVariable('profile', 'Compile/link for profiler', 0,
104  allowed_values=('0', '1', 'pg', 'gcov')),
105  ('version', 'Specify the version to declare', None),
106  ('baseversion', 'Specify the current base version', None),
107  ('optFiles', "Specify a list of files that SHOULD be optimized", None),
108  ('noOptFiles', "Specify a list of files that should NOT be optimized", None),
109  ('macosx_deployment_target', 'Deployment target for Mac OS X', '10.9'),
110  )
111 
112 
113 def _initEnvironment():
114  """Construction and basic setup of the state.env variable."""
115 
116  ourEnv = {}
117  for key in ('EUPS_DIR', 'EUPS_PATH', 'EUPS_SHELL', 'PATH', 'DYLD_LIBRARY_PATH', 'LD_LIBRARY_PATH',
118  'SHELL', 'TMPDIR', 'TEMP', 'TMP', 'EUPS_LOCK_PID', 'XPA_PORT'):
119  if key in os.environ:
120  ourEnv[key] = os.environ[key]
121 
122  # Find and propagate EUPS environment variables.
123  cfgPath = []
124  for k in os.environ:
125  m = re.search(r"^(?P<name>\w+)_DIR(?P<extra>_EXTRA)?$", k)
126  if not m:
127  continue
128  cfgPath.append(os.path.join(os.environ[k], "ups"))
129  if m.group("extra"):
130  cfgPath.append(os.environ[k])
131  else:
132  cfgPath.append(os.path.join(os.environ[k], "ups"))
133  p = m.group("name")
134  varname = eupsForScons.utils.setupEnvNameFor(p)
135  if varname in os.environ:
136  ourEnv[varname] = os.environ[varname]
137  ourEnv[k] = os.environ[k]
138 
139  # add <build root>/ups directory to the configuration search path
140  # this allows the .cfg file for the package being built to be found without
141  # requiring <product name>_DIR to be in the env
142  cfgPath.append(os.path.join(SCons.Script.Dir('#').abspath, 'ups'))
143 
144  # Recursively walk LSST_CFG_PATH
145  for root in os.environ.get("LSST_CFG_PATH", "").split(":"):
146  for base, dirs, files in os.walk(root):
147  dirs = [d for d in dirs if not d.startswith(".")]
148  cfgPath.insert(0, base)
149  #
150  # Add any values marked as export=FOO=XXX[,GOO=YYY] to ourEnv
151  #
152  exportVal = SCons.Script.ARGUMENTS.pop("export", None)
153  if exportVal:
154  for kv in exportVal.split(','):
155  k, v = kv.split('=')
156  ourEnv[k] = v
157  global env
158  sconsUtilsPath, thisFile = os.path.split(__file__)
159  toolPath = os.path.join(sconsUtilsPath, "tools")
160  env = SCons.Script.Environment(
161  ENV=ourEnv,
162  variables=opts,
163  toolpath=[toolPath],
164  tools=["default", "cuda"]
165  )
166  env.cfgPath = cfgPath
167  #
168  # We don't want "lib" inserted at the beginning of loadable module names;
169  # we'll import them under their given names.
170  #
171  env['LDMODULEPREFIX'] = ""
172  if env['PLATFORM'] == 'darwin':
173  env['LDMODULESUFFIX'] = ".so"
174  if not re.search(r"-install_name", str(env['SHLINKFLAGS'])):
175  env.Append(SHLINKFLAGS=["-install_name", "${TARGET.file}"])
176  if not re.search(r"-headerpad_max_install_names", str(env['SHLINKFLAGS'])):
177  env.Append(SHLINKFLAGS=["-Wl,-headerpad_max_install_names"])
178  #
179  # We want to be explicit about the OS X version we're targeting
180  #
181  env['ENV']['MACOSX_DEPLOYMENT_TARGET'] = env['macosx_deployment_target']
182  log.info("Setting OS X binary compatibility level: %s" % env['ENV']['MACOSX_DEPLOYMENT_TARGET'])
183  #
184  # For XCode 7.3 we need to explicitly add a trailing slash to library paths.
185  # This does not hurt things on older XCodes. We can remove this once XCode
186  # is fixed. See Apple radar rdr://25313838
187  #
188  env['LIBDIRSUFFIX'] = '/'
189 
190  #
191  # Remove valid options from the arguments
192  #
193  # SCons Variables do not behave like dicts
194  for opt in opts.keys():
195  try:
196  del SCons.Script.ARGUMENTS[opt]
197  except KeyError:
198  pass
199  #
200  # Process those arguments
201  #
202  for k in ("force", "prefix"): # these may now be set as options instead of variables
203  if SCons.Script.GetOption(k):
204  env[k] = SCons.Script.GetOption(k)
205 
206  if env['debug']:
207  env.Append(CCFLAGS=['-g'])
208 
209  #
210  # determine if EUPS is present
211  #
212 
213  # --no-eups overides probing
214  # XXX is it possible to test python snippets as a scons action?
215  if SCons.Script.GetOption("no_eups"):
216  env['no_eups'] = True
217  else:
218  env['no_eups'] = not eupsForScons.haveEups()
219 
220  if not env.GetOption("no_progress"):
221  if env['no_eups']:
222  log.info('EUPS integration: disabled')
223  else:
224  log.info('EUPS integration: enabled')
225 
226  #
227  # Find the eups path, replace 'flavor' in favor of 'PLATFORM' if needed.
228  #
229  eupsPath = None
230  try:
231  db = env['eupsdb']
232  if 'EUPS_PATH' not in os.environ:
233  raise RuntimeError("You can't use eupsdb=XXX without an EUPS_PATH set")
234  eupsPath = None
235  for d in os.environ['EUPS_PATH'].split(':'):
236  if re.search(r"/%s$|^%s/|/%s/" % (db, db, db), d):
237  eupsPath = d
238  break
239  if not eupsPath:
240  raise RuntimeError("I cannot find DB \"%s\" in $EUPS_PATH" % db)
241  except KeyError:
242  if 'EUPS_PATH' in os.environ:
243  eupsPath = os.environ['EUPS_PATH'].split(':')[0]
244  env['eupsPath'] = eupsPath
245  try:
246  env['PLATFORM'] = env['flavor']
247  del env['flavor']
248  except KeyError:
249  pass
250  #
251  # Check arguments
252  #
253  errorStr = ""
254  #
255  # Process otherwise unknown arguments. If setenv is true,
256  # set construction variables; otherwise generate an error
257  #
258  if SCons.Script.GetOption("setenv"):
259  for key in SCons.Script.ARGUMENTS:
260  env[key] = SCons.Script.Split(SCons.Script.ARGUMENTS[key])
261  else:
262  for key in SCons.Script.ARGUMENTS:
263  errorStr += " %s=%s" % (key, SCons.Script.ARGUMENTS[key])
264  if errorStr:
265  log.fail("Unprocessed arguments:%s" % errorStr)
266  #
267  # We need a binary name, not just "Posix"
268  #
269  env['eupsFlavor'] = eupsForScons.flavor()
270 
271 
272 def _configureCommon():
273  """Configuration checks for the compiler, platform, and standard libraries."""
274  #
275  # Is the C compiler really gcc/g++?
276  #
277  def ClassifyCc(context):
278  """Return a pair of string identifying the compiler in use
279 
280  @return (compiler, version) as a pair of strings, or ("unknown", "unknown") if unknown
281  """
282  versionNameList = (
283  (r"gcc(?:\-.+)? +\(.+\) +([0-9.a-zA-Z]+)", "gcc"),
284  (r"\(GCC\) +([0-9.a-zA-Z]+) ", "gcc"),
285  (r"LLVM +version +([0-9.a-zA-Z]+) ", "clang"), # clang on Mac
286  (r"clang +version +([0-9.a-zA-Z]+) ", "clang"), # clang on linux
287  (r"\(ICC\) +([0-9.a-zA-Z]+) ", "icc"),
288  )
289 
290  context.Message("Checking who built the CC compiler...")
291  result = context.TryAction(SCons.Script.Action(r"$CC --version > $TARGET"))
292  ccVersDumpOK, ccVersDump = result[0:2]
293  if ccVersDumpOK:
294  for reStr, compilerName in versionNameList:
295  match = re.search(reStr, ccVersDump)
296  if match:
297  compilerVersion = match.groups()[0]
298  context.Result("%s=%s" % (compilerName, compilerVersion))
299  return (compilerName, compilerVersion)
300  context.Result("unknown")
301  return ("unknown", "unknown")
302 
303  if env.GetOption("clean") or env.GetOption("no_exec") or env.GetOption("help"):
304  env.whichCc = "unknown" # who cares? We're cleaning/not execing, not building
305  else:
306  if env['cc'] != '':
307  CC = CXX = None
308  if re.search(r"^gcc(-\d+(\.\d+)*)?( |$)", env['cc']):
309  CC = env['cc']
310  CXX = re.sub(r"^gcc", "g++", CC)
311  elif re.search(r"^icc( |$)", env['cc']):
312  CC = env['cc']
313  CXX = re.sub(r"^icc", "icpc", CC)
314  elif re.search(r"^clang( |$)", env['cc']):
315  CC = env['cc']
316  CXX = re.sub(r"^clang", "clang++", CC)
317  elif re.search(r"^cc( |$)", env['cc']):
318  CC = env['cc']
319  CXX = re.sub(r"^cc", "c++", CC)
320  else:
321  log.fail("Unrecognised compiler:%s" % env['cc'])
322  env0 = SCons.Script.Environment()
323  if CC and env['CC'] == env0['CC']:
324  env['CC'] = CC
325  if CC and env['CXX'] == env0['CXX']:
326  env['CXX'] = CXX
327  conf = env.Configure(custom_tests={'ClassifyCc': ClassifyCc})
328  env.whichCc, env.ccVersion = conf.ClassifyCc()
329  if not env.GetOption("no_progress"):
330  log.info("CC is %s version %s" % (env.whichCc, env.ccVersion))
331  conf.Finish()
332  #
333  # Compiler flags, including CCFLAGS for C and C++ and CXXFLAGS for C++ only
334  #
335  ARCHFLAGS = os.environ.get("ARCHFLAGS", env.get('archflags'))
336  if ARCHFLAGS:
337  env.Append(CCFLAGS=ARCHFLAGS.split())
338  env.Append(LINKFLAGS=ARCHFLAGS.split())
339  # We'll add warning and optimisation options last
340  if env['profile'] == '1' or env['profile'] == "pg":
341  env.Append(CCFLAGS=['-pg'])
342  env.Append(LINKFLAGS=['-pg'])
343  elif env['profile'] == 'gcov':
344  env.Append(CCFLAGS='--coverage')
345  env.Append(LINKFLAGS='--coverage')
346 
347  #
348  # Enable C++11 support (and C99 support for gcc)
349  #
350  if not (env.GetOption("clean") or env.GetOption("help") or env.GetOption("no_exec")):
351  if not env.GetOption("no_progress"):
352  log.info("Checking for C++11 support")
353  conf = env.Configure()
354  for cpp11Arg in ("-std=%s" % (val,) for val in ("c++11",)):
355  conf.env = env.Clone()
356  conf.env.Append(CXXFLAGS=cpp11Arg)
357  if conf.CheckCXX():
358  env.Append(CXXFLAGS=cpp11Arg)
359  if not env.GetOption("no_progress"):
360  log.info("C++11 supported with %r" % (cpp11Arg,))
361  break
362  else:
363  log.fail("C++11 extensions could not be enabled for compiler %r" % env.whichCc)
364  conf.Finish()
365 
366  #
367  # Byte order
368  #
369  import socket
370  if socket.htons(1) != 1:
371  env.Append(CCFLAGS=['-DLSST_LITTLE_ENDIAN=1'])
372  #
373  # If we're linking to libraries that themselves linked to
374  # shareable libraries we need to do something special.
375  #
376  if (re.search(r"^(Linux|Linux64)$", env["eupsFlavor"]) and "LD_LIBRARY_PATH" in os.environ):
377  env.Append(LINKFLAGS=["-Wl,-rpath-link"])
378  env.Append(LINKFLAGS=["-Wl,%s" % os.environ["LD_LIBRARY_PATH"]])
379  #
380  # Set the optimization level.
381  #
382  if env['opt']:
383  env["CCFLAGS"] = [o for o in env["CCFLAGS"] if not re.search(r"^-O(\d|s|g|fast)$", o)]
384  env.MergeFlags('-O%s' % env['opt'])
385  #
386  # Set compiler-specific warning flags.
387  #
388  if env.whichCc == "clang":
389  env.Append(CCFLAGS=['-Wall'])
390  env["CCFLAGS"] = [o for o in env["CCFLAGS"] if not re.search(r"^-mno-fused-madd$", o)]
391 
392  ignoreWarnings = {
393  "unused-function": 'boost::regex has functions in anon namespaces in headers',
394  }
395  filterWarnings = {
396  "attributes": "clang pretends to be g++, but complains about g++ attributes such as flatten",
397  "char-subscripts": 'seems innocous enough, and is used by boost',
398  "constant-logical-operand": "Used by eigen 2.0.15. Should get this fixed",
399  "format-security": "format string is not a string literal",
400  "mismatched-tags": "mixed class and struct. Used by gcc 4.2 RTL and eigen 2.0.15",
401  "parentheses": "equality comparison with extraneous parentheses",
402  "shorten-64-to-32": "implicit conversion loses integer precision",
403  "self-assign": "x = x",
404  "unused-local-typedefs": "unused typedef", # lots from boost
405  "unknown-pragmas": "unknown pragma ignored",
406  "deprecated-register": "register is deprecated",
407  }
408  for k in ignoreWarnings:
409  env.Append(CCFLAGS=["-Wno-%s" % k])
410  if env.GetOption('filterWarn'):
411  for k in filterWarnings:
412  env.Append(CCFLAGS=["-Wno-%s" % k])
413  elif env.whichCc == "gcc":
414  env.Append(CCFLAGS=['-Wall'])
415  env.Append(CCFLAGS=["-Wno-unknown-pragmas"]) # we don't want complaints about icc/clang pragmas
416  env.Append(CCFLAGS=["-Wno-unused-local-typedefs"]) # boost generates a lot of these
417  elif env.whichCc == "icc":
418  env.Append(CCFLAGS=['-Wall'])
419  filterWarnings = {
420  21: 'type qualifiers are meaningless in this declaration',
421  68: 'integer conversion resulted in a change of sign',
422  111: 'statement is unreachable',
423  191: 'type qualifier is meaningless on cast type',
424  193: 'zero used for undefined preprocessing identifier "SYMB"',
425  279: 'controlling expression is constant',
426  304: 'access control not specified ("public" by default)', # comes from boost
427  383: 'value copied to temporary, reference to temporary used',
428  # 424: 'Extra ";" ignored',
429  444: 'destructor for base class "CLASS" is not virtual',
430  981: 'operands are evaluated in unspecified order',
431  1418: 'external function definition with no prior declaration',
432  1419: 'external declaration in primary source file',
433  1572: 'floating-point equality and inequality comparisons are unreliable',
434  1720: 'function "FUNC" has no corresponding member operator delete'
435  '(to be called if an exception is thrown during initialization of an allocated object)',
436  2259: 'non-pointer conversion from "int" to "float" may lose significant bits',
437  }
438  if env.GetOption('filterWarn'):
439  env.Append(CCFLAGS=["-wd%s" % (",".join([str(k) for k in filterWarnings]))])
440  # Workaround intel bug; cf. RHL's intel bug report 580167
441  env.Append(LINKFLAGS=["-Wl,-no_compact_unwind", "-wd,11015"])
442 
443 
444 def _saveState():
445  """Save state such as optimization level used. The scons mailing lists were unable to tell
446  RHL how to get this back from .sconsign.dblite
447  """
448 
449  if env.GetOption("clean"):
450  return
451 
452  import ConfigParser
453 
454  config = ConfigParser.ConfigParser()
455  config.add_section('Build')
456  config.set('Build', 'cc', env.whichCc)
457  if env['opt']:
458  config.set('Build', 'opt', env['opt'])
459 
460  try:
461  confFile = os.path.join(env.Dir(env["CONFIGUREDIR"]).abspath, "build.cfg")
462  with open(confFile, 'wb') as configfile:
463  config.write(configfile)
464  except Exception as e:
465  log.warn("Unexpected exception in _saveState: %s" % e)
466 
467 _initOptions()
468 _initLog()
469 _initVariables()
470 _initEnvironment()
471 _configureCommon()
472 _saveState()
473 
474 # @endcond
A dead-simple logger for all messages.
Definition: utils.py:23