LSSTApplications  16.0-10-g0ee56ad+4,16.0-11-ga33d1f2+4,16.0-12-g3ef5c14+2,16.0-12-g71e5ef5+17,16.0-12-gbdf3636+2,16.0-13-g118c103+2,16.0-13-g8f68b0a+2,16.0-15-gbf5c1cb+3,16.0-16-gfd17674+2,16.0-17-g7c01f5c+2,16.0-18-g0a50484,16.0-20-ga20f992+7,16.0-21-g0e05fd4+5,16.0-21-g15e2d33+3,16.0-22-g62d8060+3,16.0-22-g847a80f+3,16.0-25-gf00d9b8,16.0-28-g3990c221+3,16.0-3-gf928089+2,16.0-32-g88a4f23+4,16.0-34-gd7987ad+2,16.0-37-gc7333cb+1,16.0-4-g10fc685+1,16.0-4-g18f3627+25,16.0-4-g5f3a788+25,16.0-5-gaf5c3d7+3,16.0-5-gcc1f4bb,16.0-6-g3b92700+3,16.0-6-g4412fcd+2,16.0-6-g7235603+3,16.0-69-g2562ce1b+1,16.0-7-g0913a87,16.0-8-g14ebd58+3,16.0-8-g2df868b,16.0-8-g4cec79c+5,16.0-8-gadf6c7a,16.0-82-g59ec2a54a,16.0-9-g5400cdc+1,16.0-9-ge6233d7+4,master-g2880f2d8cf+2,v17.0.rc1
LSSTDataManagementBasePackage
dependencies.py
Go to the documentation of this file.
1 
9 
10 import os.path
11 import collections
12 import imp
13 import SCons.Script
14 from . import eupsForScons
15 from SCons.Script.SConscript import SConsEnvironment
16 
17 from . import installation
18 from . import state
19 
20 
21 
38 def configure(packageName, versionString=None, eupsProduct=None, eupsProductPath=None, noCfgFile=False):
39  if not state.env.GetOption("no_progress"):
40  state.log.info("Setting up environment to build package '%s'." % packageName)
41  if eupsProduct is None:
42  eupsProduct = packageName
43  if versionString is None:
44  versionString = "git"
45  state.env['eupsProduct'] = eupsProduct
46  state.env['packageName'] = packageName
47  #
48  # Setup installation directories and variables
49  #
50  SCons.Script.Help(state.opts.GenerateHelpText(state.env))
51  state.env.installing = [t for t in SCons.Script.BUILD_TARGETS if t == "install"]
52  state.env.declaring = [t for t in SCons.Script.BUILD_TARGETS if t == "declare" or t == "current"]
53  state.env.linkFarmDir = state.env.GetOption("linkFarmDir")
54  if state.env.linkFarmDir:
55  state.env.linkFarmDir = os.path.abspath(os.path.expanduser(state.env.linkFarmDir))
56  prefix = installation.setPrefix(state.env, versionString, eupsProductPath)
57  state.env['prefix'] = prefix
58  state.env["libDir"] = "%s/lib" % prefix
59  state.env["pythonDir"] = "%s/python" % prefix
60  #
61  # Process dependencies
62  #
63  state.log.traceback = state.env.GetOption("traceback")
64  state.log.verbose = state.env.GetOption("verbose")
65  packages = PackageTree(packageName, noCfgFile=noCfgFile)
66  state.log.flush() # if we've already hit a fatal error, die now.
67  state.env.libs = {"main": [], "python": [], "test": []}
68  state.env.doxygen = {"tags": [], "includes": []}
69  state.env['CPPPATH'] = []
70  state.env['LIBPATH'] = []
71 
72  # XCPPPATH is a new variable defined by sconsUtils - it's like CPPPATH, but the headers
73  # found there aren't treated as dependencies. This can make scons a lot faster.
74  state.env['XCPPPATH'] = []
75 
76  # XCPPPPREFIX is a replacement for SCons' built-in INCPREFIX. It is used
77  # when compiling headers in XCPPPATH directories. Here, we set it to
78  # `-isystem`, so that those are regarded as "system headers" and warnings
79  # are suppressed.
80  state.env['XCPPPREFIX'] = "-isystem "
81 
82  state.env['_CPPINCFLAGS'] = \
83  "$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)}"\
84  " ${_concat(XCPPPREFIX, XCPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)"
85  state.env['_SWIGINCFLAGS'] = state.env['_CPPINCFLAGS'] \
86  .replace("CPPPATH", "SWIGPATH") \
87  .replace("XCPPPREFIX", "SWIGINCPREFIX")
88 
89  if state.env.linkFarmDir:
90  for d in [state.env.linkFarmDir, "#"]:
91  state.env.Append(CPPPATH=os.path.join(d, "include"))
92  state.env.Append(LIBPATH=os.path.join(d, "lib"))
93  state.env['SWIGPATH'] = state.env['CPPPATH']
94 
95  if not state.env.GetOption("clean") and not state.env.GetOption("help"):
96  packages.configure(state.env, check=state.env.GetOption("checkDependencies"))
97  for target in state.env.libs:
98  state.log.info("Libraries in target '%s': %s" % (target, state.env.libs[target]))
99  state.env.dependencies = packages
100  state.log.flush()
101 
102 
103 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
104 
105 
120 
121  # @brief Parse the name of a .cfg file, returning the package name and root directory.
122  @staticmethod
123  def parseFilename(cfgFile):
124  dir, file = os.path.split(cfgFile)
125  name, ext = os.path.splitext(file)
126  return name, os.path.abspath(os.path.join(dir, ".."))
127 
128  @staticmethod
129  def getEupsData(eupsProduct):
130  version, eupsPathDir, productDir, table, flavor = eupsForScons.getEups().findSetupVersion(eupsProduct)
131  if productDir is None:
132  productDir = eupsForScons.productDir(eupsProduct)
133  return version, productDir
134 
135 
161  def __init__(self, cfgFile, headers=(), libs=None, hasSwigFiles=True,
162  includeFileDirs=["include"], libFileDirs=["lib"],
163  hasDoxygenInclude=False, hasDoxygenTag=True, eupsProduct=None):
164  self.name, self.root = self.parseFilename(cfgFile)
165  if eupsProduct is None:
166  eupsProduct = self.name
167  self.eupsProduct = eupsProduct
168  version, productDir = self.getEupsData(self.eupsProduct)
169  if version is not None:
170  self.version = version
171  if productDir is None:
172  state.log.warn("Could not find EUPS product dir for '%s'; using %s."
173  % (self.eupsProduct, self.root))
174  else:
175  self.root = os.path.realpath(productDir)
176  self.doxygen = {
177  # Doxygen tag files generated by this package
178  "tags": ([os.path.join(self.root, "doc", "%s.tag" % self.name)]
179  if hasDoxygenTag else []),
180  # Doxygen include files to include in the configuration of dependent products
181  "includes": ([os.path.join(self.root, "doc", "%s.inc" % self.name)]
182  if hasDoxygenInclude else [])
183  }
184  if libs is None:
185  self.libs = {
186  # Normal libraries provided by this package
187  "main": [self.name],
188  # Libraries provided that should only be linked with Python modules
189  "python": [],
190  # Libraries provided that should only be linked with unit test code
191  "test": [],
192  }
193  elif "main" in libs:
194  self.libs = libs
195  else:
196  self.libs = {"main": libs, "python": [], "test": []}
197  self.paths = {}
198  if hasSwigFiles:
199  self.paths["SWIGPATH"] = [os.path.join(self.root, "python")]
200  else:
201  self.paths["SWIGPATH"] = []
202 
203  for pathName, subDirs in [("CPPPATH", includeFileDirs),
204  ("LIBPATH", libFileDirs)]:
205  self.paths[pathName] = []
206 
207  if state.env.linkFarmDir:
208  continue
209 
210  for subDir in subDirs:
211  pathDir = os.path.join(self.root, subDir)
212  if os.path.isdir(pathDir):
213  self.paths[pathName].append(pathDir)
214 
215  self.provides = {
216  "headers": tuple(headers),
217  "libs": tuple(self.libs["main"])
218  }
219 
220 
230  def addCustomTests(self, tests):
231  pass
232 
233 
247  def configure(self, conf, packages, check=False, build=True):
248  assert(not (check and build))
249  conf.env.PrependUnique(**self.paths)
250  state.log.info("Configuring package '%s'." % self.name)
251  conf.env.doxygen["includes"].extend(self.doxygen["includes"])
252  if not build:
253  conf.env.doxygen["tags"].extend(self.doxygen["tags"])
254  for target in self.libs:
255  if target not in conf.env.libs:
256  conf.env.libs[target] = self.libs[target].copy()
257  state.log.info("Adding '%s' libraries to target '%s'." % (self.libs[target], target))
258  else:
259  for lib in self.libs[target]:
260  if lib not in conf.env.libs[target]:
261  conf.env.libs[target].append(lib)
262  state.log.info("Adding '%s' library to target '%s'." % (lib, target))
263  if check:
264  for header in self.provides["headers"]:
265  if not conf.CheckCXXHeader(header):
266  return False
267  for lib in self.libs["main"]:
268  if not conf.CheckLib(lib, autoadd=False, language="C++"):
269  return False
270  return True
271 
272 
273 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
274 
275 
288 
289 
303  def __init__(self, cfgFile, headers=(), libs=None, eupsProduct=None):
304  Configuration.__init__(self, cfgFile, headers, libs, eupsProduct=eupsProduct, hasSwigFiles=False,
305  hasDoxygenTag=False, hasDoxygenInclude=False)
306  self.paths["XCPPPATH"] = self.paths["CPPPATH"]
307  del self.paths["CPPPATH"]
308 
309 
310 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
311 
312 
321 def CustomCFlagCheck(context, flag, append=True):
322  context.Message("Checking if C compiler supports " + flag + " flag ")
323  ccflags = context.env["CCFLAGS"]
324  context.env.Append(CCFLAGS=flag)
325  result = context.TryCompile("int main(int argc, char **argv) { return 0; }", ".c")
326  context.Result(result)
327  if not append or not result:
328  context.env.Replace(CCFLAGS=ccflags)
329  return result
330 
331 
332 
341 def CustomCppFlagCheck(context, flag, append=True):
342  context.Message("Checking if C++ compiler supports " + flag + " flag ")
343  cxxflags = context.env["CXXFLAGS"]
344  context.env.Append(CXXFLAGS=flag)
345  result = context.TryCompile("int main(int argc, char **argv) { return 0; }", ".cc")
346  context.Result(result)
347  if not append or not result:
348  context.env.Replace(CXXFLAGS=cxxflags)
349  return result
350 
351 
352 
361 def CustomCompileCheck(context, message, source, extension=".cc"):
362  context.Message(message)
363 
364  env = context.env
365  if (env.GetOption("clean") or env.GetOption("help") or env.GetOption("no_exec")):
366  result = True
367  else:
368  result = context.TryCompile(source, extension)
369 
370  context.Result(result)
371 
372  return result
373 
374 
375 
384 def CustomLinkCheck(context, message, source, extension=".cc"):
385  context.Message(message)
386  result = context.TryLink(source, extension)
387  context.Result(result)
388  return result
389 
390 
391 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
392 
393 
408 
409 
419  def __init__(self, primaryName, noCfgFile=False):
420  self.cfgPath = state.env.cfgPath
421  self.packages = collections.OrderedDict()
422  self.customTests = {
423  "CustomCFlagCheck": CustomCFlagCheck,
424  "CustomCppFlagCheck": CustomCppFlagCheck,
425  "CustomCompileCheck": CustomCompileCheck,
426  "CustomLinkCheck": CustomLinkCheck,
427  }
428  self._current = set([primaryName])
429  if noCfgFile:
430  self.primary = None
431  return
432 
433  self.primary = self._tryImport(primaryName)
434  if self.primary is None:
435  state.log.fail("Failed to load primary package configuration for %s." % primaryName)
436 
437  missingDeps = []
438  for dependency in self.primary.dependencies.get("required", ()):
439  if not self._recurse(dependency):
440  missingDeps.append(dependency)
441  if missingDeps:
442  state.log.fail("Failed to load required dependencies: \"%s\"" % '", "'.join(missingDeps))
443 
444  missingDeps = []
445  for dependency in self.primary.dependencies.get("buildRequired", ()):
446  if not self._recurse(dependency):
447  missingDeps.append(dependency)
448  if missingDeps:
449  state.log.fail("Failed to load required build dependencies: \"%s\"" % '", "'.join(missingDeps))
450 
451  for dependency in self.primary.dependencies.get("optional", ()):
452  self._recurse(dependency)
453 
454  for dependency in self.primary.dependencies.get("buildOptional", ()):
455  self._recurse(dependency)
456 
457  name = property(lambda self: self.primary.config.name)
458 
459  # @brief Configure the entire dependency tree in order. and return an updated environment."""
460  def configure(self, env, check=False):
461  conf = env.Configure(custom_tests=self.customTests)
462  for name, module in self.packages.items():
463  if module is None:
464  state.log.info("Skipping missing optional package %s." % name)
465  continue
466  if not module.config.configure(conf, packages=self.packages, check=check, build=False):
467  state.log.fail("%s was found but did not pass configuration checks." % name)
468  if self.primary:
469  self.primary.config.configure(conf, packages=self.packages, check=False, build=True)
470  env.AppendUnique(SWIGPATH=env["CPPPATH"])
471  env.AppendUnique(XSWIGPATH=env["XCPPPATH"])
472  # reverse the order of libraries in env.libs, so libraries that fulfill a dependency
473  # of another appear after it. required by the linker to successfully resolve symbols
474  # in static libraries.
475  for target in env.libs:
476  env.libs[target].reverse()
477  env = conf.Finish()
478  return env
479 
480  def __contains__(self, name):
481  return name == self.name or name in self.packages
482 
483  has_key = __contains__
484 
485  def __getitem__(self, name):
486  if name == self.name:
487  return self.primary
488  else:
489  return self.packages[name]
490 
491  def get(self, name, default=None):
492  if name == self.name:
493  return self.primary
494  else:
495  return self.packages.get(name)
496 
497  def keys(self):
498  k = list(self.packages.keys())
499  k.append(self.name)
500  return k
501 
502  def _tryImport(self, name):
503  """Search for and import an individual configuration module from file."""
504  for path in self.cfgPath:
505  filename = os.path.join(path, name + ".cfg")
506  if os.path.exists(filename):
507  try:
508  module = imp.load_source(name + "_cfg", filename)
509  except Exception as e:
510  state.log.warn("Error loading configuration %s (%s)" % (filename, e))
511  continue
512  state.log.info("Using configuration for package '%s' at '%s'." % (name, filename))
513  if not hasattr(module, "dependencies") or not isinstance(module.dependencies, dict):
514  state.log.warn("Configuration module for package '%s' lacks a dependencies dict." % name)
515  return None
516  if not hasattr(module, "config") or not isinstance(module.config, Configuration):
517  state.log.warn("Configuration module for package '%s' lacks a config object." % name)
518  return None
519  else:
520  module.config.addCustomTests(self.customTests)
521  return module
522  state.log.info("Failed to import configuration for optional package '%s'." % name)
523 
524  def _recurse(self, name):
525  """Recursively load a dependency."""
526  if name in self._current:
527  state.log.fail("Detected recursive dependency involving package '%s'" % name)
528  else:
529  self._current.add(name)
530  if name in self.packages:
531  self._current.remove(name)
532  return self.packages[name] is not None
533  module = self._tryImport(name)
534  if module is None:
535  self.packages[name] = None
536  self._current.remove(name)
537  return False
538  for dependency in module.dependencies.get("required", ()):
539  if not self._recurse(dependency):
540  # We can't configure this package because a required dependency wasn't found.
541  # But this package might itself be optional, so we don't die yet.
542  self.packages[name] = None
543  self._current.remove(name)
544  state.log.warn("Could not load all dependencies for package '%s' (missing %s)." %
545  (name, dependency))
546  return False
547  for dependency in module.dependencies.get("optional", ()):
548  self._recurse(dependency)
549  # This comes last to ensure the ordering puts all dependencies first.
550  self.packages[name] = module
551  self._current.remove(name)
552  return True
553 
554 
555 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
556 
557 
569 def getLibs(env, categories="main"):
570  libs = []
571  removeSelf = False
572  for category in categories.split():
573  if category == "self":
574  category = "main"
575  removeSelf = True
576  for lib in env.libs[category]:
577  if lib not in libs:
578  libs.append(lib)
579  if removeSelf:
580  try:
581  libs.remove(env["packageName"])
582  except ValueError:
583  pass
584  return libs
585 
586 
587 SConsEnvironment.getLibs = getLibs
588 
589 
Base class for defining how to configure an LSST sconsUtils package.
def CustomCFlagCheck(context, flag, append=True)
A configuration test that checks whether a C compiler supports a particular flag. ...
def configure(self, env, check=False)
def CustomCppFlagCheck(context, flag, append=True)
A configuration test that checks whether a C++ compiler supports a particular flag.
def CustomLinkCheck(context, message, source, extension=".cc")
A configuration test that checks whether the given source code compiles and links.
def addCustomTests(self, tests)
Add custom SCons configuration tests to the Configure Context passed to the configure() method...
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
def configure(packageName, versionString=None, eupsProduct=None, eupsProductPath=None, noCfgFile=False)
Recursively configure a package using ups/.cfg files.
Definition: dependencies.py:38
daf::base::PropertySet * set
Definition: fits.cc:832
def getLibs(env, categories="main")
Get the libraries the package should be linked with.
def __init__(self, cfgFile, headers=(), libs=None, hasSwigFiles=True, includeFileDirs=["include"], libFileDirs=["lib"], hasDoxygenInclude=False, hasDoxygenTag=True, eupsProduct=None)
Initialize the configuration object.
def __init__(self, cfgFile, headers=(), libs=None, eupsProduct=None)
Initialize the configuration object.
def __init__(self, primaryName, noCfgFile=False)
Recursively load *.cfg files for packageName and all its dependencies.
def get(self, name, default=None)
def CustomCompileCheck(context, message, source, extension=".cc")
A configuration test that checks whether the given source code compiles.
A class for loading and managing the dependency tree of a package, as defined by its configuration mo...
A Configuration subclass for external (third-party) packages.
std::vector< SchemaItem< Flag > > * items
daf::base::PropertyList * list
Definition: fits.cc:833
def configure(self, conf, packages, check=False, build=True)
Update an SCons environment to make use of the package.