LSSTApplications  16.0-11-g09ed895+2,16.0-11-g12e47bd,16.0-11-g9bb73b2+6,16.0-12-g5c924a4+6,16.0-14-g9a974b3+1,16.0-15-g1417920+1,16.0-15-gdd5ca33+1,16.0-16-gf0259e2,16.0-17-g31abd91+7,16.0-17-g7d7456e+7,16.0-17-ga3d2e9f+13,16.0-18-ga4d4bcb+1,16.0-18-gd06566c+1,16.0-2-g0febb12+21,16.0-2-g9d5294e+69,16.0-2-ga8830df+6,16.0-20-g21842373+7,16.0-24-g3eae5ec,16.0-28-gfc9ea6c+4,16.0-29-ge8801f9,16.0-3-ge00e371+34,16.0-4-g18f3627+13,16.0-4-g5f3a788+20,16.0-4-ga3eb747+10,16.0-4-gabf74b7+29,16.0-4-gb13d127+6,16.0-49-g42e581f7+6,16.0-5-g27fb78a+7,16.0-5-g6a53317+34,16.0-5-gb3f8a4b+87,16.0-6-g9321be7+4,16.0-6-gcbc7b31+42,16.0-6-gf49912c+29,16.0-7-gd2eeba5+51,16.0-71-ge89f8615e,16.0-8-g21fd5fe+29,16.0-8-g3a9f023+20,16.0-8-g4734f7a+1,16.0-8-g5858431+3,16.0-9-gf5c1f43+8,master-gd73dc1d098+1,w.2019.01
LSSTDataManagementBasePackage
builders.py
Go to the documentation of this file.
1 #
2 # @file builders.py
3 #
4 # Extra builders and methods to be injected into the SConsEnvironment class.
5 
6 
7 import os
8 import re
9 import fnmatch
10 import pipes
11 
12 import SCons.Script
13 from SCons.Script.SConscript import SConsEnvironment
14 
15 from .utils import memberOf
16 from .installation import determineVersion, getFingerprint
17 from . import state
18 
19 
20 # @brief Like SharedLibrary, but don't insist that all symbols are resolved
21 @memberOf(SConsEnvironment)
22 def SharedLibraryIncomplete(self, target, source, **keywords):
23  myenv = self.Clone()
24  if myenv['PLATFORM'] == 'darwin':
25  myenv['SHLINKFLAGS'] += ["-undefined", "suppress", "-flat_namespace", "-headerpad_max_install_names"]
26  return myenv.SharedLibrary(target, source, **keywords)
27 
28 
29 # @brief Like LoadableModule, but don't insist that all symbols are resolved, and set
30 # some pybind11-specific flags.
31 @memberOf(SConsEnvironment)
32 def Pybind11LoadableModule(self, target, source, **keywords):
33  myenv = self.Clone()
34  myenv.Append(CCFLAGS=["-fvisibility=hidden"])
35  if myenv['PLATFORM'] == 'darwin':
36  myenv.Append(LDMODULEFLAGS=["-undefined", "suppress",
37  "-flat_namespace", "-headerpad_max_install_names"])
38  return myenv.LoadableModule(target, source, **keywords)
39 
40 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
41 
42 
43 
56 @memberOf(SConsEnvironment)
57 def SourcesForSharedLibrary(self, files):
58 
59  files = [SCons.Script.File(file) for file in files]
60 
61  if not (self.get("optFiles") or self.get("noOptFiles")):
62  objs = [self.SharedObject(ccFile) for ccFile in sorted(state.env.Flatten(files), key=str)]
63  return objs
64 
65  if self.get("optFiles"):
66  optFiles = self["optFiles"].replace(".", r"\.") # it'll be used in an RE
67  optFiles = SCons.Script.Split(optFiles.replace(",", " "))
68  optFilesRe = "/(%s)$" % "|".join(optFiles)
69  else:
70  optFilesRe = None
71 
72  if self.get("noOptFiles"):
73  noOptFiles = self["noOptFiles"].replace(".", r"\.") # it'll be used in an RE
74  noOptFiles = SCons.Script.Split(noOptFiles.replace(",", " "))
75  noOptFilesRe = "/(%s)$" % "|".join(noOptFiles)
76  else:
77  noOptFilesRe = None
78 
79  if self.get("opt"):
80  opt = int(self["opt"])
81  else:
82  opt = 0
83 
84  if opt == 0:
85  opt = 3
86 
87  CCFLAGS_OPT = re.sub(r"-O(\d|s)\s*", "-O%d " % opt, " ".join(self["CCFLAGS"]))
88  CCFLAGS_NOOPT = re.sub(r"-O(\d|s)\s*", "-O0 ", " ".join(self["CCFLAGS"])) # remove -O flags from CCFLAGS
89 
90  objs = []
91  for ccFile in files:
92  if optFilesRe and re.search(optFilesRe, ccFile.abspath):
93  obj = self.SharedObject(ccFile, CCFLAGS=CCFLAGS_OPT)
94  elif noOptFilesRe and re.search(noOptFilesRe, ccFile.abspath):
95  obj = self.SharedObject(ccFile, CCFLAGS=CCFLAGS_NOOPT)
96  else:
97  obj = self.SharedObject(ccFile)
98  objs.append(obj)
99 
100  objs = sorted(state.env.Flatten(objs), key=str)
101  return objs
102 
103 
104 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
105 
106 #
107 # @brief Return a list of files that need to be scanned for tags, starting at directory root
108 #
109 # These tags are for advanced Emacs users, and should not be confused with SVN tags or Doxygen tags.
110 #
111 # Files are chosen if they match fileRegex; toplevel directories in list ignoreDirs are ignored
112 # This routine won't do anything unless you specified a "TAGS" target
113 #
114 def filesToTag(root=None, fileRegex=None, ignoreDirs=None):
115  if root is None:
116  root = "."
117  if fileRegex is None:
118  fileRegex = r"^[a-zA-Z0-9_].*\.(cc|h(pp)?|py)$"
119  if ignoreDirs is None:
120  ignoreDirs = ["examples", "tests"]
121 
122  if "TAGS" not in SCons.Script.COMMAND_LINE_TARGETS:
123  return []
124 
125  files = []
126  for dirpath, dirnames, filenames in os.walk(root):
127  if dirpath == ".":
128  dirnames[:] = [d for d in dirnames if not re.search(r"^(%s)$" % "|".join(ignoreDirs), d)]
129 
130  dirnames[:] = [d for d in dirnames if not re.search(r"^(\.svn)$", d)] # ignore .svn tree
131  #
132  # List of possible files to tag, but there's some cleanup required for machine-generated files
133  #
134  candidates = [f for f in filenames if re.search(fileRegex, f)]
135  #
136  # Remove files generated by swig
137  #
138  for swigFile in [f for f in filenames if f.endswith(".i")]:
139  name = os.path.splitext(swigFile)[0]
140  candidates = [f for f in candidates if not re.search(r"%s(_wrap\.cc?|\.py)$" % name, f)]
141 
142  files += [os.path.join(dirpath, f) for f in candidates]
143 
144  return files
145 
146 
147 
153 @memberOf(SConsEnvironment)
154 def BuildETags(env, root=None, fileRegex=None, ignoreDirs=None):
155  toTag = filesToTag(root, fileRegex, ignoreDirs)
156  if toTag:
157  return env.Command("TAGS", toTag, "etags -o $TARGET $SOURCES")
158 
159 
160 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
161 
162 
171 @memberOf(SConsEnvironment)
172 def CleanTree(self, filePatterns, dirPatterns="", directory=".", verbose=False):
173 
174  def genFindCommand(patterns, directory, verbose, filesOnly):
175  # Generate find command to clean up (find-glob) patterns, either files
176  # or directories.
177  expr = ""
178  for pattern in SCons.Script.Split(patterns):
179  if expr != "":
180  expr += " -o "
181  # Quote unquoted * and [
182  expr += "-name %s" % re.sub(r"(^|[^\\])([\[*])", r"\1\\\2", pattern)
183  if filesOnly:
184  expr += " -type f"
185  else:
186  expr += " -type d -prune"
187 
188  command = "find " + directory
189  # Don't look into .svn or .git directories to save time.
190  command += r" \( -name .svn -prune -o -name .git -prune -o -name \* \) "
191  command += r" \( " + expr + r" \)"
192  if filesOnly:
193  command += r" -exec rm -f {} \;"
194  else:
195  command += r" -exec rm -rf {} \;"
196  if verbose:
197  command += " -print"
198  return command
199 
200  action = genFindCommand(filePatterns, directory, verbose, filesOnly=True)
201 
202  # Clean up scons files --- users want to be able to say scons -c and get a
203  # clean copy.
204  # We can't delete .sconsign.dblite if we use "scons clean" instead of
205  # "scons --clean", so the former is no longer supported.
206  action += " ; rm -rf .sconf_temp .sconsign.dblite .sconsign.tmp config.log"
207 
208  if dirPatterns != "":
209  action += " ; "
210  action += genFindCommand(dirPatterns, directory, verbose, filesOnly=False)
211  # Do we actually want to clean up? We don't if the command is e.g.
212  # "scons -c install"
213  if "clean" in SCons.Script.COMMAND_LINE_TARGETS:
214  state.log.fail("'scons clean' is no longer supported; please use 'scons --clean'.")
215  elif not SCons.Script.COMMAND_LINE_TARGETS and self.GetOption("clean"):
216  self.Execute(self.Action([action]))
217 
218 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
219 
220 
221 # @brief Return a product's PRODUCT_DIR, or None
222 @memberOf(SConsEnvironment)
223 def ProductDir(env, product):
224  from . import eupsForScons
225  global _productDirs
226  try:
227  _productDirs
228  except Exception:
229  try:
230  _productDirs = eupsForScons.productDir(eupsenv=eupsForScons.getEups())
231  except TypeError: # old version of eups (pre r18588)
232  _productDirs = None
233  if _productDirs:
234  pdir = _productDirs.get(product)
235  else:
236  pdir = eupsForScons.productDir(product)
237  if pdir == "none":
238  pdir = None
239  return pdir
240 
241 
242 # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
243 
244 
250 
251  def __init__(self, **kw):
252  self.__dict__.update(kw)
253  self.results = []
254  self.sources = []
255  self.targets = []
256  self.useTags = list(SCons.Script.File(item).abspath for item in self.useTags)
257  self.inputs = list(SCons.Script.Entry(item).abspath for item in self.inputs)
258  self.excludes = list(SCons.Script.Entry(item).abspath for item in self.excludes)
259  self.outputPaths = list(SCons.Script.Dir(item) for item in self.outputs)
260 
261  def __call__(self, env, config):
262  self.findSources()
263  self.findTargets()
264  inConfigNode = SCons.Script.File(config)
265  outConfigName, ext = os.path.splitext(inConfigNode.abspath)
266  outConfigNode = SCons.Script.File(outConfigName)
267  if self.makeTag:
268  tagNode = SCons.Script.File(self.makeTag)
269  self.makeTag = tagNode.abspath
270  self.targets.append(tagNode)
271  config = env.Command(target=outConfigNode, source=inConfigNode if os.path.exists(config) else None,
272  action=self.buildConfig)
273  env.AlwaysBuild(config)
274  doc = env.Command(target=self.targets, source=self.sources,
275  action="doxygen %s" % pipes.quote(outConfigNode.abspath))
276  for path in self.outputPaths:
277  env.Clean(doc, path)
278  env.Depends(doc, config)
279  self.results.extend(config)
280  self.results.extend(doc)
281  return self.results
282 
283  def findSources(self):
284  for path in self.inputs:
285  if os.path.isdir(path):
286  for root, dirs, files in os.walk(path):
287  if os.path.abspath(root) in self.excludes:
288  dirs[:] = []
289  continue
290  if not self.recursive:
291  dirs[:] = []
292  else:
293  toKeep = []
294  for relDir in dirs:
295  if relDir.startswith("."):
296  continue
297  absDir = os.path.abspath(os.path.join(root, relDir))
298  if absDir not in self.excludes:
299  toKeep.append(relDir)
300  dirs[:] = toKeep
301  if self.excludeSwig:
302  for relFile in files:
303  base, ext = os.path.splitext(relFile)
304  if ext == ".i":
305  self.excludes.append(os.path.join(root, base + ".py"))
306  self.excludes.append(os.path.join(root, base + "_wrap.cc"))
307  for relFile in files:
308  absFile = os.path.abspath(os.path.join(root, relFile))
309  if absFile in self.excludes:
310  continue
311  for pattern in self.patterns:
312  if fnmatch.fnmatch(relFile, pattern):
313  self.sources.append(SCons.Script.File(absFile))
314  break
315  elif os.path.isfile(path):
316  self.sources.append(SCons.Script.File(path))
317 
318  def findTargets(self):
319  for item in self.outputs:
320  self.targets.append(SCons.Script.Dir(item))
321 
322  def buildConfig(self, target, source, env):
323  outConfigFile = open(target[0].abspath, "w")
324 
325  # Need a routine to quote paths that contain spaces
326  # but can not use pipes.quote because it has to be
327  # a double quote for doxygen.conf
328  # Do not quote a string if it is already quoted
329  # Also have a version that quotes each item in a sequence and generates the
330  # final quoted entry.
331  def _quote_path(path):
332  if " " in path and not path.startswith('"') and not path.endswith('"'):
333  return '"{}"'.format(path)
334  return path
335 
336  def _quote_paths(pathList):
337  return " ".join(_quote_path(p) for p in pathList)
338 
339  docPaths = []
340  incFiles = []
341  for incPath in self.includes:
342  docDir, incFile = os.path.split(incPath)
343  docPaths.append('"%s"' % docDir)
344  incFiles.append('"%s"' % incFile)
345  self.sources.append(SCons.Script.File(incPath))
346  if docPaths:
347  outConfigFile.write('@INCLUDE_PATH = %s\n' % _quote_paths(docPaths))
348  for incFile in incFiles:
349  outConfigFile.write('@INCLUDE = %s\n' % _quote_path(incFile))
350 
351  for tagPath in self.useTags:
352  docDir, tagFile = os.path.split(tagPath)
353  htmlDir = os.path.join(docDir, "html")
354  outConfigFile.write('TAGFILES += "%s=%s"\n' % (tagPath, htmlDir))
355  self.sources.append(SCons.Script.Dir(docDir))
356  if self.projectName is not None:
357  outConfigFile.write("PROJECT_NAME = %s\n" % self.projectName)
358  if self.projectNumber is not None:
359  outConfigFile.write("PROJECT_NUMBER = %s\n" % self.projectNumber)
360  outConfigFile.write("INPUT = %s\n" % _quote_paths(self.inputs))
361  outConfigFile.write("EXCLUDE = %s\n" % _quote_paths(self.excludes))
362  outConfigFile.write("FILE_PATTERNS = %s\n" % " ".join(self.patterns))
363  outConfigFile.write("RECURSIVE = YES\n" if self.recursive else "RECURSIVE = NO\n")
364  allOutputs = set(("html", "latex", "man", "rtf", "xml"))
365  for output, path in zip(self.outputs, self.outputPaths):
366  try:
367  allOutputs.remove(output.lower())
368  except Exception:
369  state.log.fail("Unknown Doxygen output format '%s'." % output)
370  state.log.finish()
371  outConfigFile.write("GENERATE_%s = YES\n" % output.upper())
372  outConfigFile.write("%s_OUTPUT = %s\n" % (output.upper(), _quote_path(path.abspath)))
373  for output in allOutputs:
374  outConfigFile.write("GENERATE_%s = NO\n" % output.upper())
375  if self.makeTag is not None:
376  outConfigFile.write("GENERATE_TAGFILE = %s\n" % _quote_path(self.makeTag))
377  #
378  # Append the local overrides (usually doxygen.conf.in)
379  #
380  if len(source) > 0:
381  with open(source[0].abspath, "r") as inConfigFile:
382  outConfigFile.write(inConfigFile.read())
383 
384  outConfigFile.close()
385 
386 
387 
452 @memberOf(SConsEnvironment)
453 def Doxygen(self, config, **kw):
454  inputs = [d for d in ["#doc", "#include", "#python", "#src"]
455  if os.path.exists(SCons.Script.Entry(d).abspath)]
456  defaults = {
457  "inputs": inputs,
458  "recursive": True,
459  "patterns": ["*.h", "*.cc", "*.py", "*.dox"],
460  "outputs": ["html", "xml"],
461  "excludes": [],
462  "includes": [],
463  "useTags": [],
464  "makeTag": None,
465  "projectName": None,
466  "projectNumber": None,
467  "excludeSwig": True
468  }
469  for k in defaults:
470  if kw.get(k) is None:
471  kw[k] = defaults[k]
472  builder = DoxygenBuilder(**kw)
473  return builder(self, config)
474 
475 
476 @memberOf(SConsEnvironment)
477 def VersionModule(self, filename, versionString=None):
478  if versionString is None:
479  for n in ("git", "hg", "svn",):
480  if os.path.isdir(".%s" % n):
481  versionString = n
482 
483  if not versionString:
484  versionString = "git"
485 
486  def calcMd5(filename):
487  try:
488  import hashlib
489  md5 = hashlib.md5(open(filename, "rb").read()).hexdigest()
490  except IOError:
491  md5 = None
492 
493  return md5
494 
495  oldMd5 = calcMd5(filename)
496 
497  def makeVersionModule(target, source, env):
498  try:
499  version = determineVersion(state.env, versionString)
500  except RuntimeError:
501  version = "unknown"
502  parts = version.split("+")
503 
504  names = []
505  with open(target[0].abspath, "w") as outFile:
506  outFile.write("# -------- This file is automatically generated by LSST's sconsUtils -------- #\n")
507 
508  what = "__version__"
509  outFile.write("%s = '%s'\n" % (what, version))
510  names.append(what)
511 
512  what = "__repo_version__"
513  outFile.write("%s = '%s'\n" % (what, parts[0]))
514  names.append(what)
515 
516  what = "__fingerprint__"
517  outFile.write("%s = '%s'\n" % (what, getFingerprint(versionString)))
518  names.append(what)
519 
520  try:
521  info = tuple(int(v) for v in parts[0].split("."))
522  what = "__version_info__"
523  names.append(what)
524  outFile.write("%s = %r\n" % (what, info))
525  except ValueError:
526  pass
527 
528  if len(parts) > 1:
529  try:
530  what = "__rebuild_version__"
531  outFile.write("%s = %s\n" % (what, int(parts[1])))
532  names.append(what)
533  except ValueError:
534  pass
535 
536  what = "__dependency_versions__"
537  names.append(what)
538  outFile.write("%s = {\n" % (what))
539  for name, mod in env.dependencies.packages.items():
540  if mod is None:
541  outFile.write(" '%s': None,\n" % name)
542  elif hasattr(mod.config, "version"):
543  outFile.write(" '%s': '%s',\n" % (name, mod.config.version))
544  else:
545  outFile.write(" '%s': 'unknown',\n" % name)
546  outFile.write("}\n")
547 
548  # Write out an entry per line as there can be many names
549  outFile.write("__all__ = (\n")
550  for n in names:
551  outFile.write(" {!r},\n".format(n))
552  outFile.write(")\n")
553 
554  if calcMd5(target[0].abspath) != oldMd5: # only print if something's changed
555  state.log.info("makeVersionModule([\"%s\"], [])" % str(target[0]))
556 
557  result = self.Command(filename, [], self.Action(makeVersionModule, strfunction=lambda *args: None))
558 
559  self.AlwaysBuild(result)
560  return result
def Doxygen(self, config, kw)
Generate a Doxygen config file and run Doxygen on it.
Definition: builders.py:453
def SourcesForSharedLibrary(self, files)
Prepare the list of files to be passed to a SharedLibrary constructor.
Definition: builders.py:57
def CleanTree(self, filePatterns, dirPatterns="", directory=".", verbose=False)
Remove files matching the argument list starting at directory when scons is invoked with -c/–clean a...
Definition: builders.py:172
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
daf::base::PropertySet * set
Definition: fits.cc:832
def buildConfig(self, target, source, env)
Definition: builders.py:322
def BuildETags(env, root=None, fileRegex=None, ignoreDirs=None)
Build Emacs tags (see man etags for more information).
Definition: builders.py:154
def Pybind11LoadableModule(self, target, source, keywords)
Definition: builders.py:32
def __call__(self, env, config)
Definition: builders.py:261
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:129
def determineVersion(env, versionString)
Definition: installation.py:47
def SharedLibraryIncomplete(self, target, source, keywords)
Definition: builders.py:22
def VersionModule(self, filename, versionString=None)
Definition: builders.py:477
A callable to be used as an SCons Action to run Doxygen.
Definition: builders.py:249
def ProductDir(env, product)
Definition: builders.py:223
def filesToTag(root=None, fileRegex=None, ignoreDirs=None)
Definition: builders.py:114
daf::base::PropertyList * list
Definition: fits.cc:833
def memberOf(cls, name=None)
A Python decorator that injects functions into a class.
Definition: utils.py:185
def getFingerprint(versionString)
Definition: installation.py:75