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
dependencies.py
Go to the documentation of this file.
1 ##
2 # @file dependencies.py
3 #
4 # Dependency configuration and definition.
5 #
6 # @defgroup sconsUtilsDependencies Dependencies and Configuration
7 # @{
8 ##
9 
10 from __future__ import absolute_import
11 import os.path
12 import collections
13 import imp
14 import SCons.Script
15 from . import eupsForScons
16 from SCons.Script.SConscript import SConsEnvironment
17 
18 from . import installation
19 from . import state
20 
21 
22 ##
23 # @brief Recursively configure a package using ups/.cfg files.
24 #
25 # Aliased as lsst.sconsUtils.configure().
26 #
27 # Usually, LSST packages will call this function through scripts.BasicSConstruct.
28 #
29 # @param packageName Name of the package being built; must correspond to a .cfg file in ups/.
30 # @param versionString Version-control system string to be parsed for version information
31 # ($HeadURL$ for SVN).
32 # @param eupsProduct Name of the EUPS product being built. Defaults to and is almost always
33 # the name of the package.
34 # @param eupsProductPath An alternate directory where the package should be installed.
35 # @param noCfgFile If True, this package has no .cfg file
36 #
37 # @return an SCons Environment object (which is also available as lsst.sconsUtils.env).
38 ##
39 def configure(packageName, versionString=None, eupsProduct=None, eupsProductPath=None, noCfgFile=False):
40  if not state.env.GetOption("no_progress"):
41  state.log.info("Setting up environment to build package '%s'." % packageName)
42  if eupsProduct is None:
43  eupsProduct = packageName
44  if versionString is None:
45  versionString = "git"
46  state.env['eupsProduct'] = eupsProduct
47  state.env['packageName'] = packageName
48  #
49  # Setup installation directories and variables
50  #
51  SCons.Script.Help(state.opts.GenerateHelpText(state.env))
52  state.env.installing = [t for t in SCons.Script.BUILD_TARGETS if t == "install"]
53  state.env.declaring = [t for t in SCons.Script.BUILD_TARGETS if t == "declare" or t == "current"]
54  state.env.linkFarmDir = state.env.GetOption("linkFarmDir")
55  if state.env.linkFarmDir:
56  state.env.linkFarmDir = os.path.abspath(os.path.expanduser(state.env.linkFarmDir))
57  prefix = installation.setPrefix(state.env, versionString, eupsProductPath)
58  state.env['prefix'] = prefix
59  state.env["libDir"] = "%s/lib" % prefix
60  state.env["pythonDir"] = "%s/python" % prefix
61  #
62  # Process dependencies
63  #
64  state.log.traceback = state.env.GetOption("traceback")
65  state.log.verbose = state.env.GetOption("verbose")
66  packages = PackageTree(packageName, noCfgFile=noCfgFile)
67  state.log.flush() # if we've already hit a fatal error, die now.
68  state.env.libs = {"main": [], "python": [], "test": []}
69  state.env.doxygen = {"tags": [], "includes": []}
70  state.env['CPPPATH'] = []
71  state.env['LIBPATH'] = []
72 
73  # XCPPPATH is a new variable defined by sconsUtils - it's like CPPPATH, but the headers
74  # found there aren't treated as dependencies. This can make scons a lot faster.
75  state.env['XCPPPATH'] = []
76 
77  # XCPPPPREFIX is a replacement for SCons' built-in INCPREFIX. It is used
78  # when compiling headers in XCPPPATH directories. Here, we set it to
79  # `-isystem`, so that those are regarded as "system headers" and warnings
80  # are suppressed.
81  state.env['XCPPPREFIX'] = "-isystem "
82 
83  state.env['_CPPINCFLAGS'] = \
84  "$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)}"\
85  " ${_concat(XCPPPREFIX, XCPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)"
86  state.env['_SWIGINCFLAGS'] = state.env['_CPPINCFLAGS'] \
87  .replace("CPPPATH", "SWIGPATH") \
88  .replace("XCPPPREFIX", "SWIGINCPREFIX")
89 
90  if state.env.linkFarmDir:
91  for d in [state.env.linkFarmDir, "#"]:
92  state.env.Append(CPPPATH=os.path.join(d, "include"))
93  state.env.Append(LIBPATH=os.path.join(d, "lib"))
94  state.env['SWIGPATH'] = state.env['CPPPATH']
95 
96  if not state.env.GetOption("clean") and not state.env.GetOption("help"):
97  packages.configure(state.env, check=state.env.GetOption("checkDependencies"))
98  for target in state.env.libs:
99  state.log.info("Libraries in target '%s': %s" % (target, state.env.libs[target]))
100  state.env.dependencies = packages
101  state.log.flush()
102 
103 
104 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
105 
106 ##
107 # @brief Base class for defining how to configure an LSST sconsUtils package.
108 #
109 # Aliased as lsst.sconsUtils.Configuration.
110 #
111 # An ups/*.cfg file should contain an instance of this class called
112 # "config". Most LSST packages will be able to use this class directly
113 # instead of subclassing it.
114 #
115 # The only important method is configure(), which modifies an SCons
116 # environment to use the package. If a subclass overrides configure,
117 # it may not need to call the base class __init__(), whose only
118 # purpose is to define a number of instance variables used by configure().
119 ##
120 class Configuration(object):
121 
122  # @brief Parse the name of a .cfg file, returning the package name and root directory.
123  @staticmethod
124  def parseFilename(cfgFile):
125  dir, file = os.path.split(cfgFile)
126  name, ext = os.path.splitext(file)
127  return name, os.path.abspath(os.path.join(dir, ".."))
128 
129  @staticmethod
130  def getEupsData(eupsProduct):
131  version, eupsPathDir, productDir, table, flavor = eupsForScons.getEups().findSetupVersion(eupsProduct)
132  if productDir is None:
133  productDir = eupsForScons.productDir(eupsProduct)
134  return version, productDir
135 
136  ##
137  # @brief Initialize the configuration object.
138  #
139  # @param cfgFile The name of the calling .cfg file, usually just passed in with the special
140  # variable __file__. This will be parsed to extract the package name and
141  # root.
142  # @param headers A list of headers provided by the package, to be used in autoconf-style
143  # tests.
144  # @param libs A list or dictionary of libraries provided by the package. If a dictionary
145  # is provided, libs["main"] should contain a list of regular libraries
146  # provided
147  # by the library. Other keys are "python" and "test", which refer to
148  # libraries that are only linked against compiled Python modules and unit
149  # tests, respectively. If a list is provided, the list is used as "main".
150  # These are used both for autoconf-style tests and to support
151  # env.getLibs(...), which recursively computes the libraries a package
152  # must be linked with.
153  # @param hasSwigFiles If True, the package provides SWIG interface files in "<root>/python".
154  # @param hasDoxygenInclude If True, the package provides a Doxygen include file with the
155  # name "<root>/doc/<name>.inc".
156  # @param hasDoxygenTag If True, the package generates a Doxygen TAG file.
157  # @param includeFileDirs List of directories that should be searched for include files
158  # @param libFileDirs List of directories that should be searched for libraries
159  # @param eupsProduct Name of the EUPS product for the package, if different from the name of the
160  # .cfg file.
161  ##
162  def __init__(self, cfgFile, headers=(), libs=None, hasSwigFiles=True,
163  includeFileDirs=["include"], libFileDirs=["lib"],
164  hasDoxygenInclude=False, hasDoxygenTag=True, eupsProduct=None):
165  self.name, self.root = self.parseFilename(cfgFile)
166  if eupsProduct is None:
167  eupsProduct = self.name
168  self.eupsProduct = eupsProduct
169  version, productDir = self.getEupsData(self.eupsProduct)
170  if version is not None:
171  self.version = version
172  if productDir is None:
173  state.log.warn("Could not find EUPS product dir for '%s'; using %s."
174  % (self.eupsProduct, self.root))
175  else:
176  self.root = os.path.realpath(productDir)
177  self.doxygen = {
178  # Doxygen tag files generated by this package
179  "tags": ([os.path.join(self.root, "doc", "%s.tag" % self.name)]
180  if hasDoxygenTag else []),
181  # Doxygen include files to include in the configuration of dependent products
182  "includes": ([os.path.join(self.root, "doc", "%s.inc" % self.name)]
183  if hasDoxygenInclude else [])
184  }
185  if libs is None:
186  self.libs = {
187  # Normal libraries provided by this package
188  "main": [self.name],
189  # Libraries provided that should only be linked with Python modules
190  "python": [],
191  # Libraries provided that should only be linked with unit test code
192  "test": [],
193  }
194  elif "main" in libs:
195  self.libs = libs
196  else:
197  self.libs = {"main": libs, "python": [], "test": []}
198  self.paths = {}
199  if hasSwigFiles:
200  self.paths["SWIGPATH"] = [os.path.join(self.root, "python")]
201  else:
202  self.paths["SWIGPATH"] = []
203 
204  for pathName, subDirs in [("CPPPATH", includeFileDirs),
205  ("LIBPATH", libFileDirs)]:
206  self.paths[pathName] = []
207 
208  if state.env.linkFarmDir:
209  continue
210 
211  for subDir in subDirs:
212  pathDir = os.path.join(self.root, subDir)
213  if os.path.isdir(pathDir):
214  self.paths[pathName].append(pathDir)
215 
216  self.provides = {
217  "headers": tuple(headers),
218  "libs": tuple(self.libs["main"])
219  }
220 
221  ##
222  # @brief Add custom SCons configuration tests to the Configure Context passed to the
223  # configure() method.
224  #
225  # This needs to be done up-front so we can pass in a dictionary of custom tests when
226  # calling env.Configure(), and use the same configure context for all packages.
227  #
228  # @param tests A dictionary to add custom tests to. This will be passed as the
229  # custom_tests argument to env.Configure().
230  ##
231  def addCustomTests(self, tests):
232  pass
233 
234  ##
235  # @brief Update an SCons environment to make use of the package.
236  #
237  # @param conf An SCons Configure context. The SCons Environment conf.env should be updated
238  # by the configure function.
239  # @param packages A dictionary containing the configuration modules of all dependencies (or None if
240  # the dependency was optional and was not found). The <module>.config.configure(...)
241  # method will have already been called on all dependencies.
242  # @param check If True, perform autoconf-style tests to verify that key components are in
243  # fact in place.
244  # @param build If True, this is the package currently being built, and packages in
245  # "buildRequired" and "buildOptional" dependencies will also be present in
246  # the packages dict.
247  ##
248  def configure(self, conf, packages, check=False, build=True):
249  assert(not (check and build))
250  conf.env.PrependUnique(**self.paths)
251  state.log.info("Configuring package '%s'." % self.name)
252  conf.env.doxygen["includes"].extend(self.doxygen["includes"])
253  if not build:
254  conf.env.doxygen["tags"].extend(self.doxygen["tags"])
255  for target in self.libs:
256  if target not in conf.env.libs:
257  conf.env.libs[target] = self.libs[target].copy()
258  state.log.info("Adding '%s' libraries to target '%s'." % (self.libs[target], target))
259  else:
260  for lib in self.libs[target]:
261  if lib not in conf.env.libs[target]:
262  conf.env.libs[target].append(lib)
263  state.log.info("Adding '%s' library to target '%s'." % (lib, target))
264  if check:
265  for header in self.provides["headers"]:
266  if not conf.CheckCXXHeader(header):
267  return False
268  for lib in self.libs["main"]:
269  if not conf.CheckLib(lib, autoadd=False, language="C++"):
270  return False
271  return True
272 
273 
274 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
275 
276 ##
277 # @brief A Configuration subclass for external (third-party) packages.
278 #
279 # Aliased as lsst.sconsUtils.ExternalConfiguration.
280 #
281 # ExternalConfiguration doesn't assume the package uses SWIG or Doxygen,
282 # and tells SCons not to consider header files this package provides as dependencies
283 # (by setting XCPPPATH instead of CPPPATH). This means things SCons won't waste time
284 # looking for changes in it every time you build. Header files in external
285 # packages are treated as "system headers": that is, most warnings generated when
286 # they are being compiled are suppressed.
287 ##
289 
290  ##
291  # @brief Initialize the configuration object.
292  #
293  # @param cfgFile The name of the calling .cfg file, usually just passed in with the special
294  # variable __file__. This will be parsed to extract the package name and root.
295  # @param headers A list of headers provided by the package, to be used in autoconf-style tests.
296  # @param libs A list or dictionary of libraries provided by the package. If a dictionary
297  # is provided, libs["main"] should contain a list of regular libraries provided
298  # by the library. Other keys are "python" and "test", which refer to libraries
299  # that are only linked against compiled Python modules and unit tests, respectively.
300  # If a list is provided, the list is used as "main". These are used both for
301  # autoconf-style tests and to support env.getLibs(...), which recursively computes
302  # the libraries a package must be linked with.
303  ##
304  def __init__(self, cfgFile, headers=(), libs=None, eupsProduct=None):
305  Configuration.__init__(self, cfgFile, headers, libs, eupsProduct=eupsProduct, hasSwigFiles=False,
306  hasDoxygenTag=False, hasDoxygenInclude=False)
307  self.paths["XCPPPATH"] = self.paths["CPPPATH"]
308  del self.paths["CPPPATH"]
309 
310 
311 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
312 
313 ##
314 # @brief A configuration test that checks whether a C compiler supports
315 # a particular flag.
316 #
317 # @param context Configuration context.
318 # @param flag Flag to test, e.g. "-fvisibility-inlines-hidden".
319 # @param append Automatically append the flag to context.env["CCFLAGS"]
320 # if the compiler supports it?
321 ##
322 def CustomCFlagCheck(context, flag, append=True):
323  context.Message("Checking if C compiler supports " + flag + " flag ")
324  ccflags = context.env["CCFLAGS"]
325  context.env.Append(CCFLAGS=flag)
326  result = context.TryCompile("int main(int argc, char **argv) { return 0; }", ".c")
327  context.Result(result)
328  if not append or not result:
329  context.env.Replace(CCFLAGS=ccflags)
330  return result
331 
332 
333 ##
334 # @brief A configuration test that checks whether a C++ compiler supports
335 # a particular flag.
336 #
337 # @param context Configuration context.
338 # @param flag Flag to test, e.g. "-fvisibility-inlines-hidden".
339 # @param append Automatically append the flag to context.env["CXXFLAGS"]
340 # if the compiler supports it?
341 ##
342 def CustomCppFlagCheck(context, flag, append=True):
343  context.Message("Checking if C++ compiler supports " + flag + " flag ")
344  cxxflags = context.env["CXXFLAGS"]
345  context.env.Append(CXXFLAGS=flag)
346  result = context.TryCompile("int main(int argc, char **argv) { return 0; }", ".cc")
347  context.Result(result)
348  if not append or not result:
349  context.env.Replace(CXXFLAGS=cxxflags)
350  return result
351 
352 
353 ##
354 # @brief A configuration test that checks whether the given source code
355 # compiles.
356 # @param context Configuration context.
357 # @param message Message disaplyed on console prior to running the test.
358 # @param source Source code to compile.
359 # param extension Identifies the language of the source code. Use ".c" for C, and ".cc"
360 # for C++ (the default).
361 ##
362 def CustomCompileCheck(context, message, source, extension=".cc"):
363  context.Message(message)
364 
365  env = context.env
366  if (env.GetOption("clean") or env.GetOption("help") or env.GetOption("no_exec")):
367  result = True
368  else:
369  result = context.TryCompile(source, extension)
370 
371  context.Result(result)
372 
373  return result
374 
375 
376 ##
377 # @brief A configuration test that checks whether the given source code
378 # compiles and links.
379 # @param context Configuration context.
380 # @param message Message disaplyed on console prior to running the test.
381 # @param source Source code to compile.
382 # param extension Identifies the language of the source code. Use ".c" for C, and ".cc"
383 # for C++ (the default).
384 ##
385 def CustomLinkCheck(context, message, source, extension=".cc"):
386  context.Message(message)
387  result = context.TryLink(source, extension)
388  context.Result(result)
389  return result
390 
391 
392 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
393 
394 ##
395 # @brief A class for loading and managing the dependency tree of a package, as defined by its
396 # configuration module (.cfg) file.
397 #
398 # This tree isn't actually stored as a tree; it's flattened into an ordered dictionary
399 # as it is recursively loaded.
400 #
401 # The main SCons produced by configure() and available as sconsUtils.env will contain
402 # an instance of this class as env.dependencies.
403 #
404 # Its can be used like a read-only dictionary to check whether an optional package has been
405 # configured; a package that was not found will have a value of None, while a configured
406 # package's value will be its imported .cfg module.
407 ##
408 class PackageTree(object):
409 
410  ##
411  # @brief Recursively load *.cfg files for packageName and all its dependencies.
412  #
413  # @param primaryName The name of the primary package being built.
414  # @param noCfgFile If True, this package has no .cfg file
415  #
416  # After __init__, self.primary will be set to the configuration module for the primary package,
417  # and self.packages will be an OrderedDict of dependencies (excluding self.primary), ordered
418  # such that configuration can proceed in iteration order.
419  ##
420  def __init__(self, primaryName, noCfgFile=False):
421  self.cfgPath = state.env.cfgPath
422  self.packages = collections.OrderedDict()
423  self.customTests = {
424  "CustomCFlagCheck": CustomCFlagCheck,
425  "CustomCppFlagCheck": CustomCppFlagCheck,
426  "CustomCompileCheck": CustomCompileCheck,
427  "CustomLinkCheck": CustomLinkCheck,
428  }
429  self._current = set([primaryName])
430  if noCfgFile:
431  self.primary = None
432  return
433 
434  self.primary = self._tryImport(primaryName)
435  if self.primary is None:
436  state.log.fail("Failed to load primary package configuration for %s." % primaryName)
437 
438  missingDeps = []
439  for dependency in self.primary.dependencies.get("required", ()):
440  if not self._recurse(dependency):
441  missingDeps.append(dependency)
442  if missingDeps:
443  state.log.fail("Failed to load required dependencies: \"%s\"" % '", "'.join(missingDeps))
444 
445  missingDeps = []
446  for dependency in self.primary.dependencies.get("buildRequired", ()):
447  if not self._recurse(dependency):
448  missingDeps.append(dependency)
449  if missingDeps:
450  state.log.fail("Failed to load required build dependencies: \"%s\"" % '", "'.join(missingDeps))
451 
452  for dependency in self.primary.dependencies.get("optional", ()):
453  self._recurse(dependency)
454 
455  for dependency in self.primary.dependencies.get("buildOptional", ()):
456  self._recurse(dependency)
457 
458  name = property(lambda self: self.primary.config.name)
459 
460  # @brief Configure the entire dependency tree in order. and return an updated environment."""
461  def configure(self, env, check=False):
462  conf = env.Configure(custom_tests=self.customTests)
463  for name, module in self.packages.items():
464  if module is None:
465  state.log.info("Skipping missing optional package %s." % name)
466  continue
467  if not module.config.configure(conf, packages=self.packages, check=check, build=False):
468  state.log.fail("%s was found but did not pass configuration checks." % name)
469  if self.primary:
470  self.primary.config.configure(conf, packages=self.packages, check=False, build=True)
471  env.AppendUnique(SWIGPATH=env["CPPPATH"])
472  env.AppendUnique(XSWIGPATH=env["XCPPPATH"])
473  # reverse the order of libraries in env.libs, so libraries that fulfill a dependency
474  # of another appear after it. required by the linker to successfully resolve symbols
475  # in static libraries.
476  for target in env.libs:
477  env.libs[target].reverse()
478  env = conf.Finish()
479  return env
480 
481  def __contains__(self, name):
482  return name == self.name or name in self.packages
483 
484  has_key = __contains__
485 
486  def __getitem__(self, name):
487  if name == self.name:
488  return self.primary
489  else:
490  return self.packages[name]
491 
492  def get(self, name, default=None):
493  if name == self.name:
494  return self.primary
495  else:
496  return self.packages.get(name)
497 
498  def keys(self):
499  k = self.packages.keys()
500  k.append(self.name)
501  return k
502 
503  def _tryImport(self, name):
504  """Search for and import an individual configuration module from file."""
505  for path in self.cfgPath:
506  filename = os.path.join(path, name + ".cfg")
507  if os.path.exists(filename):
508  try:
509  module = imp.load_source(name + "_cfg", filename)
510  except Exception as e:
511  state.log.warn("Error loading configuration %s (%s)" % (filename, e))
512  continue
513  state.log.info("Using configuration for package '%s' at '%s'." % (name, filename))
514  if not hasattr(module, "dependencies") or not isinstance(module.dependencies, dict):
515  state.log.warn("Configuration module for package '%s' lacks a dependencies dict." % name)
516  return None
517  if not hasattr(module, "config") or not isinstance(module.config, Configuration):
518  state.log.warn("Configuration module for package '%s' lacks a config object." % name)
519  return None
520  else:
521  module.config.addCustomTests(self.customTests)
522  return module
523  state.log.info("Failed to import configuration for optional package '%s'." % name)
524 
525  def _recurse(self, name):
526  """Recursively load a dependency."""
527  if name in self._current:
528  state.log.fail("Detected recursive dependency involving package '%s'" % name)
529  else:
530  self._current.add(name)
531  if name in self.packages:
532  self._current.remove(name)
533  return self.packages[name] is not None
534  module = self._tryImport(name)
535  if module is None:
536  self.packages[name] = None
537  self._current.remove(name)
538  return False
539  for dependency in module.dependencies.get("required", ()):
540  if not self._recurse(dependency):
541  # We can't configure this package because a required dependency wasn't found.
542  # But this package might itself be optional, so we don't die yet.
543  self.packages[name] = None
544  self._current.remove(name)
545  state.log.warn("Could not load all dependencies for package '%s' (missing %s)." %
546  (name, dependency))
547  return False
548  for dependency in module.dependencies.get("optional", ()):
549  self._recurse(dependency)
550  # This comes last to ensure the ordering puts all dependencies first.
551  self.packages[name] = module
552  self._current.remove(name)
553  return True
554 
555 
556 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
557 
558 ##
559 # @brief Get the libraries the package should be linked with.
560 #
561 # @param categories A string containing whitespace-delimited categories. Standard
562 # categories are "main", "python", and "test". Default is "main".
563 # A special virtual category "self" can be provided, returning
564 # the results of targets="main" with the env["packageName"] removed.
565 #
566 # Typically, main libraries will be linked with LIBS=getLibs("self"),
567 # Python modules will be linked with LIBS=getLibs("main python") and
568 # C++-coded test programs will be linked with LIBS=getLibs("main test").
569 # """
570 def getLibs(env, categories="main"):
571  libs = []
572  removeSelf = False
573  for category in categories.split():
574  if category == "self":
575  category = "main"
576  removeSelf = True
577  for lib in env.libs[category]:
578  if lib not in libs:
579  libs.append(lib)
580  if removeSelf:
581  try:
582  libs.remove(env["packageName"])
583  except ValueError:
584  pass
585  return libs
586 
587 SConsEnvironment.getLibs = getLibs
588 
589 ## @}
Base class for defining how to configure an LSST sconsUtils package.
def CustomCppFlagCheck
A configuration test that checks whether a C++ compiler supports a particular flag.
def CustomLinkCheck
A configuration test that checks whether the given source code compiles and links.
def configure
Recursively configure a package using ups/.cfg files.
Definition: dependencies.py:39
def getLibs
Get the libraries the package should be linked with.
def configure
Update an SCons environment to make use of the package.
def __init__
Recursively load *.cfg files for packageName and all its dependencies.
def CustomCompileCheck
A configuration test that checks whether the given source code compiles.
def __init__
Initialize the configuration object.
def CustomCFlagCheck
A configuration test that checks whether a C compiler supports a particular flag. ...
def __init__
Initialize the configuration object.
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.
def addCustomTests
Add custom SCons configuration tests to the Configure Context passed to the configure() method...