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