12 from stat
import ST_MODE
13 from SCons.Script
import SConscript, File, Dir, Glob, BUILD_TARGETS
14 from distutils.spawn
import find_executable
16 from .
import dependencies
21 DEFAULT_TARGETS = (
"lib",
"python",
"shebang",
"tests",
"examples",
"doc")
24 def _getFileBase(node):
25 name, ext = os.path.splitext(os.path.basename(
str(node)))
57 def __new__(cls, packageName, versionString=None, eupsProduct=None, eupsProductPath=None, cleanExt=None,
58 defaultTargets=DEFAULT_TARGETS,
59 subDirList=None, ignoreRegex=None,
60 versionModuleName="python/lsst/%s/version.py", noCfgFile=False,
61 sconscriptOrder=None, disableCc=False):
62 cls.
initialize(packageName, versionString, eupsProduct, eupsProductPath, cleanExt,
63 versionModuleName, noCfgFile=noCfgFile, sconscriptOrder=sconscriptOrder,
65 cls.
finish(defaultTargets, subDirList, ignoreRegex)
102 def initialize(cls, packageName, versionString=None, eupsProduct=None, eupsProductPath=None,
103 cleanExt=None, versionModuleName="python/lsst/%s/version.py", noCfgFile=False,
104 sconscriptOrder=None, disableCc=False):
106 state._configureCommon()
109 state.log.fail(
"Recursion detected; an SConscript file should not call BasicSConstruct.")
111 dependencies.configure(packageName, versionString, eupsProduct, eupsProductPath, noCfgFile)
112 state.env.BuildETags()
114 cleanExt =
r"*~ core core.[1-9]* *.so *.os *.o *.pyc *.pkgc" 115 state.env.CleanTree(cleanExt,
".cache __pycache__ .pytest_cache")
116 if versionModuleName
is not None:
118 versionModuleName = versionModuleName %
"/".join(packageName.split(
"_"))
121 state.targets[
"version"] = state.env.VersionModule(versionModuleName)
123 for root, dirs, files
in os.walk(
"."):
124 if "SConstruct" in files
and root !=
".":
127 dirs[:] = [d
for d
in dirs
if not d.startswith(
'.')]
129 if "SConscript" in files:
130 scripts.append(os.path.join(root,
"SConscript"))
131 if sconscriptOrder
is None:
132 sconscriptOrder = DEFAULT_TARGETS
135 sconscriptOrder = [t
if t !=
"shebang" else "bin.src" for t
in sconscriptOrder]
138 for i, item
in enumerate(sconscriptOrder):
139 if path.lstrip(
"./").startswith(item):
141 return len(sconscriptOrder)
142 scripts.sort(key=key)
143 for script
in scripts:
144 state.log.info(
"Using SConscript at %s" % script)
167 def finish(defaultTargets=DEFAULT_TARGETS,
168 subDirList=None, ignoreRegex=None):
169 if ignoreRegex
is None:
170 ignoreRegex =
r"(~$|\.pyc$|^\.svn$|\.o|\.os$)" 171 if subDirList
is None:
173 for path
in os.listdir(
"."):
174 if os.path.isdir(path)
and not path.startswith(
"."):
175 subDirList.append(path)
176 install = state.env.InstallLSST(state.env[
"prefix"],
177 [subDir
for subDir
in subDirList],
178 ignoreRegex=ignoreRegex)
179 for name, target
in state.targets.items():
180 state.env.Requires(install, target)
181 state.env.Alias(name, target)
182 state.env.Requires(state.targets[
"python"], state.targets[
"version"])
183 declarer = state.env.Declare()
184 state.env.Requires(declarer, install)
188 state.env.Default([t
for t
in defaultTargets
189 if os.path.exists(t)
or (t ==
"shebang" and os.path.exists(
"bin.src"))])
190 if "version" in state.targets:
191 state.env.Default(state.targets[
"version"])
192 state.env.Requires(state.targets[
"tests"], state.targets[
"version"])
193 state.env.Decider(
"MD5-timestamp")
200 if "tests" in [
str(t)
for t
in BUILD_TARGETS]:
201 testsDir = pipes.quote(os.path.join(os.getcwd(),
"tests",
".tests"))
202 checkTestStatus_command = state.env.Command(
'checkTestStatus', [],
""" 203 @ if [ -d {0} ]; then \ 204 nfail=`find {0} -name "*.failed" | wc -l | sed -e 's/ //g'`; \ 205 if [ $$nfail -gt 0 ]; then \ 206 echo "Failed test output:" >&2; \ 207 for f in `find {0} -name "*.failed"`; do \ 210 echo "Global pytest output is in $$f" >&2; \ 217 echo "The following tests failed:" >&2;\ 218 find {0} -name "*.failed" >&2; \ 219 echo "$$nfail tests failed" >&2; exit 1; \ 224 state.env.Depends(checkTestStatus_command, BUILD_TARGETS)
225 BUILD_TARGETS.extend(checkTestStatus_command)
226 state.env.AlwaysBuild(checkTestStatus_command)
252 def lib(libName=None, src=None, libs="self", noBuildList=None):
254 libName = state.env[
"packageName"]
256 src = Glob(
"#src/*.cc") + Glob(
"#src/*/*.cc") + Glob(
"#src/*/*/*.cc") + Glob(
"#src/*/*/*/*.cc")
257 if noBuildList
is not None:
258 src = [node
for node
in src
if os.path.basename(
str(node))
not in noBuildList]
259 src = state.env.SourcesForSharedLibrary(src)
260 if isinstance(libs, str):
261 libs = state.env.getLibs(libs)
264 result = state.env.SharedLibrary(libName, src, LIBS=libs)
265 state.targets[
"lib"].extend(result)
284 FIRST_LINE_RE = re.compile(
r'^#!.*python[0-9.]*([ \t].*)?$')
285 doRewrite = utils.needShebangRewrite()
287 def rewrite_shebang(target, source, env):
288 """Copy source to target, rewriting the shebang""" 290 usepython = utils.whichPython()
291 for targ, src
in zip(target, source):
292 with open(
str(src),
"r") as srcfd: 293 with open(str(targ), "w")
as outfd:
294 first_line = srcfd.readline()
298 match = FIRST_LINE_RE.match(first_line)
299 if match
and doRewrite:
300 post_interp = match.group(1)
or '' 302 outfd.write(
"#!{}{} # noqa\n".
format(usepython, post_interp))
305 state.log.warn(
"Could not rewrite shebang of {}. Please check" 306 " file or move it to bin directory.".
format(
str(src)))
307 outfd.write(first_line)
308 for line
in srcfd.readlines():
311 oldmode = os.stat(
str(targ))[ST_MODE] & 0o7777
312 newmode = (oldmode | 0o555) & 0o7777
313 if newmode != oldmode:
314 state.log.info(
"changing mode of {} from {} to {}".
format(
315 str(targ), oldmode, newmode))
316 os.chmod(
str(targ), newmode)
319 src = Glob(
"#bin.src/*")
323 if filename !=
"SConscript" and re.match(
"[A-Za-z]", filename):
324 result = state.env.Command(target=os.path.join(Dir(
"#bin").abspath, filename),
325 source=s, action=rewrite_shebang)
326 state.targets[
"shebang"].extend(result)
349 def python(module=None, src=None, extra=(), libs=
"main python"):
351 module =
"_" + state.env[
"packageName"].split(
"_")[-1]
353 src = state.env.Glob(
"*.cc")
355 if isinstance(libs, str):
356 libs = state.env.getLibs(libs)
359 result = state.env.Pybind11LoadableModule(module, src, LIBS=libs)
360 state.targets[
"python"].
append(result)
384 def pybind11(nameList=[], libs="main python", extraSrc=None, addUnderscore=True):
387 srcList = dict([(name, [])
for name
in nameList])
388 for name
in nameList:
389 srcList[name].
append(name +
".cc")
390 if isinstance(libs, str):
391 libs = state.env.getLibs(libs)
395 for name
in nameList:
399 if name.startswith(
"_"):
402 pyLibName =
"_" + name
405 result.extend(state.env.Pybind11LoadableModule(pyLibName, srcList[name], LIBS=libs))
406 state.targets[
"python"].extend(result)
420 def doc(config="doxygen.conf.in", projectName=None, projectNumber=None, **kw):
421 if not find_executable(
"doxygen"):
422 state.log.warn(
"doxygen executable not found; skipping documentation build.")
424 if projectName
is None:
425 projectName =
".".join([
"lsst"] + state.env[
"packageName"].split(
"_"))
426 if projectNumber
is None:
427 projectNumber = state.env[
"version"]
428 result = state.env.Doxygen(
429 config, projectName=projectName, projectNumber=projectNumber,
430 includes=state.env.doxygen[
"includes"],
431 useTags=state.env.doxygen[
"tags"],
432 makeTag=(state.env[
"packageName"] +
".tag"),
435 state.targets[
"doc"].extend(result)
473 def tests(pyList=None, ccList=None, swigNameList=None, swigSrc=None,
474 ignoreList=None, noBuildList=None, pySingles=None,
476 if noBuildList
is None:
478 if pySingles
is None:
480 if swigNameList
is None:
481 swigFileList = Glob(
"*.i")
482 swigNameList = [_getFileBase(node)
for node
in swigFileList]
484 swigFileList = [File(name +
".i")
for name
in swigNameList]
488 for name, node
in zip(swigNameList, swigFileList):
489 src = swigSrc.setdefault(name, [])
490 allSwigSrc.update(
str(element)
for element
in src)
493 pyList = [node
for node
in Glob(
"*.py")
494 if _getFileBase(node)
not in swigNameList
and 495 os.path.basename(
str(node))
not in noBuildList]
501 ccList = [node
for node
in Glob(
"*.cc")
502 if (
not str(node).endswith(
"_wrap.cc"))
and str(node)
not in allSwigSrc
and 503 os.path.basename(
str(node))
not in noBuildList]
504 if ignoreList
is None:
510 return [
str(i)
for i
in l]
512 state.log.info(
"SWIG modules for tests: %s" %
s(swigFileList))
513 state.log.info(
"Python tests: %s" %
s(pyList))
514 state.log.info(
"C++ tests: %s" %
s(ccList))
515 state.log.info(
"Files that will not be built: %s" % noBuildList)
516 state.log.info(
"Ignored tests: %s" % ignoreList)
517 control =
tests.Control(state.env, ignoreList=ignoreList, args=args, verbose=
True)
518 for ccTest
in ccList:
519 state.env.Program(ccTest, LIBS=state.env.getLibs(
"main test"))
521 for name, src
in swigSrc.items():
523 state.env.SwigLoadableModule(
"_" + name, src, LIBS=state.env.getLibs(
"main python"))
529 for node
in pySingles:
530 if str(node).startswith(
"test_"):
531 state.log.warn(
"Warning: {} should be run independently but" 532 " can be automatically discovered".
format(node))
535 if pyList
is not None:
536 pyList = [
str(node)
for node
in pyList
if str(node)
not in pySingles]
538 ccList = [control.run(
str(node))
for node
in ccList]
539 pySingles = [control.run(
str(node))
for node
in pySingles]
543 if pyList
is not None:
544 pyList = [control.runPythonTests(pyList)]
547 pyList.extend(pySingles)
548 for pyTest
in pyList:
549 state.env.Depends(pyTest, ccList)
550 state.env.Depends(pyTest, swigMods)
551 state.env.Depends(pyTest, state.targets[
"python"])
552 state.env.Depends(pyTest, state.targets[
"shebang"])
553 result = ccList + pyList
554 state.targets[
"tests"].extend(result)
569 def examples(ccList=None, swigNameList=None, swigSrc=None):
570 if swigNameList
is None:
571 swigFileList = Glob(
"*.i")
572 swigNameList = [_getFileBase(node)
for node
in swigFileList]
574 swigFileList = [File(name)
for name
in swigNameList]
578 for name, node
in zip(swigNameList, swigFileList):
579 src = swigSrc.setdefault(name, [])
580 allSwigSrc.update(
str(element)
for element
in src)
583 ccList = [node
for node
in Glob(
"*.cc")
584 if (
not str(node).endswith(
"_wrap.cc"))
and str(node)
not in allSwigSrc]
585 state.log.info(
"SWIG modules for examples: %s" % swigFileList)
586 state.log.info(
"C++ examples: %s" % ccList)
589 results.extend(state.env.Program(src, LIBS=state.env.getLibs(
"main")))
590 for name, src
in swigSrc.items():
592 state.env.SwigLoadableModule(
"_" + name, src, LIBS=state.env.getLibs(
"main python"))
594 for result
in results:
595 state.env.Depends(result, state.targets[
"lib"])
596 state.targets[
"examples"].extend(results)
def doc(config="doxygen.conf.in", projectName=None, projectNumber=None, kw)
Convenience function to replace standard doc/SConscript boilerplate.
def examples(ccList=None, swigNameList=None, swigSrc=None)
Convenience function to replace standard examples/SConscript boilerplate.
def lib(libName=None, src=None, libs="self", noBuildList=None)
Convenience function to replace standard lib/SConscript boilerplate.
def shebang(src=None)
Handles shebang rewriting.
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def initialize(cls, packageName, versionString=None, eupsProduct=None, eupsProductPath=None, cleanExt=None, versionModuleName="python/lsst/%s/version.py", noCfgFile=False, sconscriptOrder=None, disableCc=False)
Convenience function to replace standard SConstruct boilerplate (step 1).
daf::base::PropertySet * set
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
def python(module=None, src=None, extra=(), libs="main python")
Convenience function to replace standard python/*/SConscript boilerplate.
def pybind11(nameList=[], libs="main python", extraSrc=None, addUnderscore=True)
Convenience function to replace standard python/*/SConscript boilerplate.
A scope-only class for SConstruct-replacement convenience functions.
A scope-only class for SConscript-replacement convenience functions.
def tests(pyList=None, ccList=None, swigNameList=None, swigSrc=None, ignoreList=None, noBuildList=None, pySingles=None, args=None)
Convenience function to replace standard tests/SConscript boilerplate.
def __new__(cls, packageName, versionString=None, eupsProduct=None, eupsProductPath=None, cleanExt=None, defaultTargets=DEFAULT_TARGETS, subDirList=None, ignoreRegex=None, versionModuleName="python/lsst/%s/version.py", noCfgFile=False, sconscriptOrder=None, disableCc=False)
Convenience function to replace standard SConstruct boilerplate.
A class to control unit tests.
def finish(defaultTargets=DEFAULT_TARGETS, subDirList=None, ignoreRegex=None)
Convenience function to replace standard SConstruct boilerplate (step 2).