From 72c578fd4b0b4a5a43e18594339ac4ff26c376dc Mon Sep 17 00:00:00 2001 From: Luca Falavigna Date: Sat, 2 Jan 2010 20:56:27 +0100 Subject: Imported Upstream version 1.2.0.d20091224 --- LICENSE | 20 + SConstruct | 1389 +++ bin/Command.py | 143 + bin/SConsDoc.py | 336 + bin/ae-cvs-ci | 204 + bin/ae-svn-ci | 240 + bin/ae2cvs | 579 ++ bin/calibrate.py | 81 + bin/caller-tree.py | 96 + bin/docdiff | 16 + bin/docrun | 16 + bin/docupdate | 16 + bin/files | 106 + bin/import-test.py | 104 + bin/install_python.py | 141 + bin/install_scons.py | 216 + bin/linecount.py | 123 + bin/makedocs | 18 + bin/memlogs.py | 49 + bin/memoicmp.py | 78 + bin/objcounts.py | 109 + bin/restore.sh | 90 + bin/rsync-sourceforge | 32 + bin/scons-cdist | 272 + bin/scons-diff.py | 195 + bin/scons-proc.py | 283 + bin/scons-review.sh | 24 + bin/scons-test.py | 247 + bin/scons-unzip.py | 76 + bin/scons_dev_master.py | 215 + bin/sconsexamples.py | 512 + bin/sconsoutput.py | 827 ++ bin/sfsum | 148 + bin/svn-bisect.py | 72 + bin/time-scons.py | 355 + bin/timebuild | 65 + bin/xml_export | 225 + bin/xml_export-LICENSE | 52 + bin/xml_export-README | 56 + bin/xmlagenda.py | 93 + config | 299 + doc/MANIFEST | 1 + doc/SConscript | 530 + doc/design/MANIFEST | 14 + doc/design/acks.xml | 179 + doc/design/bground.xml | 86 + doc/design/copyright.xml | 39 + doc/design/engine.fig | 179 + doc/design/engine.jpg | Bin 0 -> 41228 bytes doc/design/engine.xml | 1964 ++++ doc/design/goals.xml | 216 + doc/design/install.xml | 28 + doc/design/intro.xml | 111 + doc/design/issues.xml | 195 + doc/design/main.xml | 158 + doc/design/native.xml | 364 + doc/design/overview.xml | 498 + doc/design/scons.mod | 429 + doc/developer/MANIFEST | 9 + doc/developer/architecture.xml | 40 + doc/developer/branches.xml | 40 + doc/developer/copyright.xml | 32 + doc/developer/cycle.xml | 40 + doc/developer/main.xml | 110 + doc/developer/packaging.xml | 40 + doc/developer/preface.xml | 175 + doc/developer/sourcetree.xml | 40 + doc/developer/testing.xml | 40 + doc/man/MANIFEST | 2 + doc/man/scons-time.1 | 1017 ++ doc/man/scons.1 | 10149 +++++++++++++++++++ doc/man/sconsign.1 | 208 + doc/python10/MANIFEST | 16 + doc/python10/abstract.xml | 32 + doc/python10/acks.xml | 27 + doc/python10/arch.eps | 134 + doc/python10/arch.fig | 35 + doc/python10/arch.jpg | Bin 0 -> 22004 bytes doc/python10/builder.eps | 325 + doc/python10/builder.fig | 128 + doc/python10/builder.jpg | Bin 0 -> 53107 bytes doc/python10/copyright.xml | 32 + doc/python10/design.xml | 898 ++ doc/python10/future.xml | 170 + doc/python10/install.xml | 179 + doc/python10/intro.xml | 212 + doc/python10/job-task.eps | 238 + doc/python10/job-task.fig | 90 + doc/python10/job-task.jpg | Bin 0 -> 25895 bytes doc/python10/main.xml | 221 + doc/python10/node.eps | 351 + doc/python10/node.fig | 165 + doc/python10/node.jpg | Bin 0 -> 61105 bytes doc/python10/process.xml | 353 + doc/python10/scanner.eps | 168 + doc/python10/scanner.fig | 57 + doc/python10/scanner.jpg | Bin 0 -> 26788 bytes doc/python10/scons.mod | 428 + doc/python10/sig.eps | 147 + doc/python10/sig.fig | 44 + doc/python10/sig.jpg | Bin 0 -> 13913 bytes doc/reference/Alias.xml | 41 + doc/reference/CFile.xml | 41 + doc/reference/CXXFile.xml | 41 + doc/reference/Command.xml | 73 + doc/reference/Install.xml | 41 + doc/reference/InstallAs.xml | 41 + doc/reference/Library.xml | 152 + doc/reference/MANIFEST | 21 + doc/reference/Object.xml | 71 + doc/reference/PCH.xml | 41 + doc/reference/PDF.xml | 41 + doc/reference/PostScript.xml | 41 + doc/reference/Program.xml | 77 + doc/reference/RES.xml | 41 + doc/reference/SharedLibrary.xml | 41 + doc/reference/SharedObject.xml | 41 + doc/reference/StaticLibrary.xml | 41 + doc/reference/StaticObject.xml | 41 + doc/reference/copyright.xml | 32 + doc/reference/errors.xml | 41 + doc/reference/main.xml | 207 + doc/reference/preface.xml | 85 + doc/scons.mod | 521 + doc/user/MANIFEST | 50 + doc/user/README | 21 + doc/user/SCons-win32-install-1.jpg | Bin 0 -> 28857 bytes doc/user/SCons-win32-install-2.jpg | Bin 0 -> 27131 bytes doc/user/SCons-win32-install-3.jpg | Bin 0 -> 25137 bytes doc/user/SCons-win32-install-4.jpg | Bin 0 -> 19549 bytes doc/user/actions.in | 404 + doc/user/actions.xml | 404 + doc/user/add-method.in | 127 + doc/user/add-method.xml | 135 + doc/user/alias.in | 102 + doc/user/alias.xml | 112 + doc/user/ant.in | 52 + doc/user/ant.xml | 52 + doc/user/build-install.in | 719 ++ doc/user/build-install.xml | 719 ++ doc/user/builders-built-in.in | 963 ++ doc/user/builders-built-in.xml | 949 ++ doc/user/builders-commands.in | 156 + doc/user/builders-commands.xml | 146 + doc/user/builders-writing.in | 1087 ++ doc/user/builders-writing.xml | 953 ++ doc/user/builders.in | 57 + doc/user/builders.xml | 57 + doc/user/caching.in | 502 + doc/user/caching.xml | 506 + doc/user/command-line.in | 2327 +++++ doc/user/command-line.xml | 2243 ++++ doc/user/cons.pl | 720 ++ doc/user/copyright.in | 32 + doc/user/copyright.xml | 32 + doc/user/depends.in | 1816 ++++ doc/user/depends.xml | 1776 ++++ doc/user/environments.in | 1678 +++ doc/user/environments.xml | 1684 +++ doc/user/errors.in | 41 + doc/user/errors.xml | 41 + doc/user/example.in | 41 + doc/user/example.xml | 41 + doc/user/factories.in | 507 + doc/user/factories.xml | 466 + doc/user/file-removal.in | 223 + doc/user/file-removal.xml | 202 + doc/user/hierarchy.in | 794 ++ doc/user/hierarchy.xml | 746 ++ doc/user/install.in | 247 + doc/user/install.xml | 237 + doc/user/java.in | 657 ++ doc/user/java.xml | 433 + doc/user/less-simple.in | 623 ++ doc/user/less-simple.xml | 608 ++ doc/user/libraries.in | 445 + doc/user/libraries.xml | 451 + doc/user/main.in | 385 + doc/user/main.xml | 385 + doc/user/make.in | 121 + doc/user/make.xml | 121 + doc/user/mergeflags.in | 137 + doc/user/mergeflags.xml | 138 + doc/user/misc.in | 606 ++ doc/user/misc.xml | 565 ++ doc/user/nodes.in | 386 + doc/user/nodes.xml | 401 + doc/user/output.in | 681 ++ doc/user/output.xml | 703 ++ doc/user/parseconfig.in | 114 + doc/user/parseconfig.xml | 132 + doc/user/parseflags.in | 184 + doc/user/parseflags.xml | 199 + doc/user/preface.in | 426 + doc/user/preface.xml | 426 + doc/user/python.in | 154 + doc/user/python.xml | 154 + doc/user/repositories.in | 641 ++ doc/user/repositories.xml | 595 ++ doc/user/run.in | 375 + doc/user/run.xml | 375 + doc/user/scanners.in | 331 + doc/user/scanners.xml | 317 + doc/user/sconf.in | 486 + doc/user/sconf.xml | 486 + doc/user/separate.in | 540 + doc/user/separate.xml | 520 + doc/user/sideeffect.in | 216 + doc/user/sideeffect.xml | 211 + doc/user/simple.in | 517 + doc/user/simple.xml | 587 ++ doc/user/sourcecode.in | 162 + doc/user/sourcecode.xml | 157 + doc/user/tasks.in | 108 + doc/user/tasks.xml | 108 + doc/user/tools.in | 38 + doc/user/tools.xml | 38 + doc/user/troubleshoot.in | 875 ++ doc/user/troubleshoot.xml | 1313 +++ doc/user/variables.in | 56 + doc/user/variables.xml | 56 + doc/user/variants.in | 151 + doc/user/variants.xml | 146 + src/CHANGES.txt | 5176 ++++++++++ src/LICENSE.txt | 20 + src/MANIFEST.in | 0 src/README.txt | 273 + src/RELEASE.txt | 1016 ++ src/engine/MANIFEST-xml.in | 99 + src/engine/MANIFEST.in | 187 + src/engine/README.txt | 167 + src/engine/SCons/Action.py | 1240 +++ src/engine/SCons/Action.xml | 107 + src/engine/SCons/ActionTests.py | 2013 ++++ src/engine/SCons/Builder.py | 868 ++ src/engine/SCons/BuilderTests.py | 1650 +++ src/engine/SCons/CacheDir.py | 217 + src/engine/SCons/CacheDirTests.py | 297 + src/engine/SCons/Conftest.py | 794 ++ src/engine/SCons/Debug.py | 237 + src/engine/SCons/Defaults.py | 485 + src/engine/SCons/Defaults.xml | 472 + src/engine/SCons/DefaultsTests.py | 94 + src/engine/SCons/Environment.py | 2327 +++++ src/engine/SCons/Environment.xml | 187 + src/engine/SCons/EnvironmentTests.py | 3987 ++++++++ src/engine/SCons/Errors.py | 207 + src/engine/SCons/ErrorsTests.py | 109 + src/engine/SCons/Executor.py | 636 ++ src/engine/SCons/ExecutorTests.py | 466 + src/engine/SCons/Job.py | 435 + src/engine/SCons/JobTests.py | 539 + src/engine/SCons/Memoize.py | 292 + src/engine/SCons/MemoizeTests.py | 198 + src/engine/SCons/Node/Alias.py | 153 + src/engine/SCons/Node/AliasTests.py | 130 + src/engine/SCons/Node/FS.py | 3220 ++++++ src/engine/SCons/Node/FSTests.py | 3520 +++++++ src/engine/SCons/Node/NodeTests.py | 1317 +++ src/engine/SCons/Node/Python.py | 128 + src/engine/SCons/Node/PythonTests.py | 130 + src/engine/SCons/Node/__init__.py | 1341 +++ src/engine/SCons/Options/BoolOption.py | 50 + src/engine/SCons/Options/EnumOption.py | 50 + src/engine/SCons/Options/ListOption.py | 50 + src/engine/SCons/Options/PackageOption.py | 50 + src/engine/SCons/Options/PathOption.py | 76 + src/engine/SCons/Options/__init__.py | 74 + src/engine/SCons/PathList.py | 232 + src/engine/SCons/PathListTests.py | 170 + src/engine/SCons/Platform/PlatformTests.py | 126 + src/engine/SCons/Platform/__init__.py | 236 + src/engine/SCons/Platform/__init__.xml | 183 + src/engine/SCons/Platform/aix.py | 70 + src/engine/SCons/Platform/cygwin.py | 55 + src/engine/SCons/Platform/darwin.py | 46 + src/engine/SCons/Platform/hpux.py | 46 + src/engine/SCons/Platform/irix.py | 44 + src/engine/SCons/Platform/os2.py | 58 + src/engine/SCons/Platform/posix.py | 264 + src/engine/SCons/Platform/posix.xml | 51 + src/engine/SCons/Platform/sunos.py | 50 + src/engine/SCons/Platform/sunos.xml | 30 + src/engine/SCons/Platform/win32.py | 386 + src/engine/SCons/Platform/win32.xml | 14 + src/engine/SCons/SConf.py | 1038 ++ src/engine/SCons/SConfTests.py | 759 ++ src/engine/SCons/SConsign.py | 381 + src/engine/SCons/SConsignTests.py | 397 + src/engine/SCons/Scanner/C.py | 132 + src/engine/SCons/Scanner/CTests.py | 468 + src/engine/SCons/Scanner/D.py | 74 + src/engine/SCons/Scanner/Dir.py | 111 + src/engine/SCons/Scanner/DirTests.py | 140 + src/engine/SCons/Scanner/Fortran.py | 320 + src/engine/SCons/Scanner/FortranTests.py | 543 + src/engine/SCons/Scanner/IDL.py | 48 + src/engine/SCons/Scanner/IDLTests.py | 453 + src/engine/SCons/Scanner/LaTeX.py | 345 + src/engine/SCons/Scanner/LaTeXTests.py | 162 + src/engine/SCons/Scanner/Prog.py | 103 + src/engine/SCons/Scanner/ProgTests.py | 262 + src/engine/SCons/Scanner/RC.py | 55 + src/engine/SCons/Scanner/RCTests.py | 168 + src/engine/SCons/Scanner/ScannerTests.py | 611 ++ src/engine/SCons/Scanner/__init__.py | 415 + src/engine/SCons/Script/Interactive.py | 386 + src/engine/SCons/Script/Main.py | 1360 +++ src/engine/SCons/Script/MainTests.py | 53 + src/engine/SCons/Script/SConsOptions.py | 944 ++ src/engine/SCons/Script/SConscript.py | 642 ++ src/engine/SCons/Script/SConscriptTests.py | 34 + src/engine/SCons/Script/__init__.py | 414 + src/engine/SCons/Sig.py | 63 + src/engine/SCons/Subst.py | 911 ++ src/engine/SCons/SubstTests.py | 1242 +++ src/engine/SCons/Taskmaster.py | 1030 ++ src/engine/SCons/TaskmasterTests.py | 1138 +++ src/engine/SCons/Tool/386asm.py | 61 + src/engine/SCons/Tool/386asm.xml | 25 + src/engine/SCons/Tool/BitKeeper.py | 65 + src/engine/SCons/Tool/BitKeeper.xml | 58 + src/engine/SCons/Tool/CVS.py | 73 + src/engine/SCons/Tool/CVS.xml | 66 + src/engine/SCons/Tool/FortranCommon.py | 247 + src/engine/SCons/Tool/JavaCommon.py | 324 + src/engine/SCons/Tool/JavaCommonTests.py | 580 ++ src/engine/SCons/Tool/MSCommon/__init__.py | 56 + src/engine/SCons/Tool/MSCommon/arch.py | 61 + src/engine/SCons/Tool/MSCommon/common.py | 195 + src/engine/SCons/Tool/MSCommon/netframework.py | 84 + src/engine/SCons/Tool/MSCommon/sdk.py | 321 + src/engine/SCons/Tool/MSCommon/vc.py | 367 + src/engine/SCons/Tool/MSCommon/vc.py.bak | 394 + src/engine/SCons/Tool/MSCommon/vs.py | 497 + src/engine/SCons/Tool/Perforce.py | 104 + src/engine/SCons/Tool/Perforce.xml | 47 + src/engine/SCons/Tool/PharLapCommon.py | 138 + src/engine/SCons/Tool/PharLapCommonTests.py | 68 + src/engine/SCons/Tool/RCS.py | 64 + src/engine/SCons/Tool/RCS.xml | 61 + src/engine/SCons/Tool/SCCS.py | 64 + src/engine/SCons/Tool/SCCS.xml | 58 + src/engine/SCons/Tool/Subversion.py | 71 + src/engine/SCons/Tool/Subversion.xml | 47 + src/engine/SCons/Tool/ToolTests.py | 84 + src/engine/SCons/Tool/__init__.py | 675 ++ src/engine/SCons/Tool/__init__.xml | 367 + src/engine/SCons/Tool/aixc++.py | 82 + src/engine/SCons/Tool/aixc++.xml | 19 + src/engine/SCons/Tool/aixcc.py | 74 + src/engine/SCons/Tool/aixcc.xml | 18 + src/engine/SCons/Tool/aixf77.py | 80 + src/engine/SCons/Tool/aixf77.xml | 17 + src/engine/SCons/Tool/aixlink.py | 76 + src/engine/SCons/Tool/aixlink.xml | 19 + src/engine/SCons/Tool/applelink.py | 71 + src/engine/SCons/Tool/applelink.xml | 114 + src/engine/SCons/Tool/ar.py | 63 + src/engine/SCons/Tool/ar.xml | 82 + src/engine/SCons/Tool/as.py | 78 + src/engine/SCons/Tool/as.xml | 88 + src/engine/SCons/Tool/bcc32.py | 82 + src/engine/SCons/Tool/bcc32.xml | 32 + src/engine/SCons/Tool/c++.py | 99 + src/engine/SCons/Tool/c++.xml | 102 + src/engine/SCons/Tool/cc.py | 114 + src/engine/SCons/Tool/cc.xml | 161 + src/engine/SCons/Tool/cvf.py | 58 + src/engine/SCons/Tool/cvf.xml | 30 + src/engine/SCons/Tool/default.py | 50 + src/engine/SCons/Tool/default.xml | 12 + src/engine/SCons/Tool/dmd.py | 224 + src/engine/SCons/Tool/dmd.xml | 53 + src/engine/SCons/Tool/dvi.py | 64 + src/engine/SCons/Tool/dvi.xml | 67 + src/engine/SCons/Tool/dvipdf.py | 125 + src/engine/SCons/Tool/dvipdf.xml | 51 + src/engine/SCons/Tool/dvips.py | 94 + src/engine/SCons/Tool/dvips.xml | 81 + src/engine/SCons/Tool/f77.py | 62 + src/engine/SCons/Tool/f77.xml | 265 + src/engine/SCons/Tool/f90.py | 62 + src/engine/SCons/Tool/f90.xml | 251 + src/engine/SCons/Tool/f95.py | 63 + src/engine/SCons/Tool/f95.xml | 252 + src/engine/SCons/Tool/filesystem.py | 98 + src/engine/SCons/Tool/fortran.py | 63 + src/engine/SCons/Tool/fortran.xml | 302 + src/engine/SCons/Tool/g++.py | 90 + src/engine/SCons/Tool/g++.xml | 18 + src/engine/SCons/Tool/g77.py | 73 + src/engine/SCons/Tool/g77.xml | 13 + src/engine/SCons/Tool/gas.py | 53 + src/engine/SCons/Tool/gas.xml | 15 + src/engine/SCons/Tool/gcc.py | 80 + src/engine/SCons/Tool/gcc.xml | 16 + src/engine/SCons/Tool/gfortran.py | 64 + src/engine/SCons/Tool/gfortran.xml | 25 + src/engine/SCons/Tool/gnulink.py | 63 + src/engine/SCons/Tool/gnulink.xml | 16 + src/engine/SCons/Tool/gs.py | 81 + src/engine/SCons/Tool/gs.xml | 47 + src/engine/SCons/Tool/hpc++.py | 85 + src/engine/SCons/Tool/hpc++.xml | 11 + src/engine/SCons/Tool/hpcc.py | 53 + src/engine/SCons/Tool/hpcc.xml | 18 + src/engine/SCons/Tool/hplink.py | 77 + src/engine/SCons/Tool/hplink.xml | 16 + src/engine/SCons/Tool/icc.py | 59 + src/engine/SCons/Tool/icc.xml | 30 + src/engine/SCons/Tool/icl.py | 52 + src/engine/SCons/Tool/icl.xml | 12 + src/engine/SCons/Tool/ifl.py | 72 + src/engine/SCons/Tool/ifl.xml | 24 + src/engine/SCons/Tool/ifort.py | 90 + src/engine/SCons/Tool/ifort.xml | 26 + src/engine/SCons/Tool/ilink.py | 59 + src/engine/SCons/Tool/ilink.xml | 23 + src/engine/SCons/Tool/ilink32.py | 60 + src/engine/SCons/Tool/ilink32.xml | 23 + src/engine/SCons/Tool/install.py | 229 + src/engine/SCons/Tool/install.xml | 52 + src/engine/SCons/Tool/intelc.py | 490 + src/engine/SCons/Tool/intelc.xml | 33 + src/engine/SCons/Tool/ipkg.py | 71 + src/engine/SCons/Tool/jar.py | 110 + src/engine/SCons/Tool/jar.xml | 110 + src/engine/SCons/Tool/javac.py | 234 + src/engine/SCons/Tool/javac.xml | 224 + src/engine/SCons/Tool/javah.py | 138 + src/engine/SCons/Tool/javah.xml | 100 + src/engine/SCons/Tool/latex.py | 79 + src/engine/SCons/Tool/latex.xml | 71 + src/engine/SCons/Tool/lex.py | 99 + src/engine/SCons/Tool/lex.xml | 50 + src/engine/SCons/Tool/link.py | 121 + src/engine/SCons/Tool/link.xml | 175 + src/engine/SCons/Tool/linkloc.py | 112 + src/engine/SCons/Tool/linkloc.xml | 31 + src/engine/SCons/Tool/m4.py | 63 + src/engine/SCons/Tool/m4.xml | 61 + src/engine/SCons/Tool/masm.py | 77 + src/engine/SCons/Tool/masm.xml | 26 + src/engine/SCons/Tool/midl.py | 90 + src/engine/SCons/Tool/midl.xml | 68 + src/engine/SCons/Tool/mingw.py | 159 + src/engine/SCons/Tool/mingw.xml | 38 + src/engine/SCons/Tool/mslib.py | 64 + src/engine/SCons/Tool/mslib.xml | 23 + src/engine/SCons/Tool/mslink.py | 266 + src/engine/SCons/Tool/mslink.xml | 237 + src/engine/SCons/Tool/mssdk.py | 50 + src/engine/SCons/Tool/mssdk.xml | 50 + src/engine/SCons/Tool/msvc.py | 257 + src/engine/SCons/Tool/msvc.xml | 316 + src/engine/SCons/Tool/msvs.py | 1439 +++ src/engine/SCons/Tool/msvs.xml | 640 ++ src/engine/SCons/Tool/msvsTests.py | 757 ++ src/engine/SCons/Tool/mwcc.py | 208 + src/engine/SCons/Tool/mwcc.xml | 53 + src/engine/SCons/Tool/mwld.py | 107 + src/engine/SCons/Tool/mwld.xml | 27 + src/engine/SCons/Tool/nasm.py | 72 + src/engine/SCons/Tool/nasm.xml | 23 + src/engine/SCons/Tool/packaging.xml | 73 + src/engine/SCons/Tool/packaging/__init__.py | 314 + src/engine/SCons/Tool/packaging/__init__.xml | 659 ++ src/engine/SCons/Tool/packaging/ipk.py | 185 + src/engine/SCons/Tool/packaging/msi.py | 526 + src/engine/SCons/Tool/packaging/rpm.py | 367 + src/engine/SCons/Tool/packaging/src_tarbz2.py | 43 + src/engine/SCons/Tool/packaging/src_targz.py | 43 + src/engine/SCons/Tool/packaging/src_zip.py | 43 + src/engine/SCons/Tool/packaging/tarbz2.py | 44 + src/engine/SCons/Tool/packaging/targz.py | 44 + src/engine/SCons/Tool/packaging/zip.py | 44 + src/engine/SCons/Tool/pdf.py | 78 + src/engine/SCons/Tool/pdf.xml | 49 + src/engine/SCons/Tool/pdflatex.py | 83 + src/engine/SCons/Tool/pdflatex.xml | 49 + src/engine/SCons/Tool/pdftex.py | 108 + src/engine/SCons/Tool/pdftex.xml | 53 + src/engine/SCons/Tool/qt.py | 336 + src/engine/SCons/Tool/qt.xml | 314 + src/engine/SCons/Tool/rmic.py | 121 + src/engine/SCons/Tool/rmic.xml | 93 + src/engine/SCons/Tool/rpcgen.py | 70 + src/engine/SCons/Tool/rpcgen.xml | 137 + src/engine/SCons/Tool/rpm.py | 132 + src/engine/SCons/Tool/sgiar.py | 68 + src/engine/SCons/Tool/sgiar.xml | 24 + src/engine/SCons/Tool/sgic++.py | 58 + src/engine/SCons/Tool/sgic++.xml | 18 + src/engine/SCons/Tool/sgicc.py | 53 + src/engine/SCons/Tool/sgicc.xml | 18 + src/engine/SCons/Tool/sgilink.py | 63 + src/engine/SCons/Tool/sgilink.xml | 17 + src/engine/SCons/Tool/sunar.py | 67 + src/engine/SCons/Tool/sunar.xml | 25 + src/engine/SCons/Tool/sunc++.py | 142 + src/engine/SCons/Tool/sunc++.xml | 19 + src/engine/SCons/Tool/suncc.py | 58 + src/engine/SCons/Tool/suncc.xml | 17 + src/engine/SCons/Tool/sunf77.py | 63 + src/engine/SCons/Tool/sunf77.xml | 19 + src/engine/SCons/Tool/sunf90.py | 64 + src/engine/SCons/Tool/sunf90.xml | 19 + src/engine/SCons/Tool/sunf95.py | 64 + src/engine/SCons/Tool/sunf95.xml | 19 + src/engine/SCons/Tool/sunlink.py | 77 + src/engine/SCons/Tool/sunlink.xml | 16 + src/engine/SCons/Tool/swig.py | 186 + src/engine/SCons/Tool/swig.xml | 212 + src/engine/SCons/Tool/tar.py | 73 + src/engine/SCons/Tool/tar.xml | 95 + src/engine/SCons/Tool/tex.py | 792 ++ src/engine/SCons/Tool/tex.xml | 126 + src/engine/SCons/Tool/textfile.py | 175 + src/engine/SCons/Tool/textfile.xml | 205 + src/engine/SCons/Tool/tlib.py | 53 + src/engine/SCons/Tool/tlib.xml | 22 + src/engine/SCons/Tool/wix.py | 100 + src/engine/SCons/Tool/yacc.py | 131 + src/engine/SCons/Tool/yacc.xml | 115 + src/engine/SCons/Tool/zip.py | 100 + src/engine/SCons/Tool/zip.xml | 110 + src/engine/SCons/Util.py | 1645 +++ src/engine/SCons/UtilTests.py | 802 ++ src/engine/SCons/Variables/BoolVariable.py | 91 + src/engine/SCons/Variables/BoolVariableTests.py | 127 + src/engine/SCons/Variables/EnumVariable.py | 107 + src/engine/SCons/Variables/EnumVariableTests.py | 204 + src/engine/SCons/Variables/ListVariable.py | 139 + src/engine/SCons/Variables/ListVariableTests.py | 134 + src/engine/SCons/Variables/PackageVariable.py | 109 + src/engine/SCons/Variables/PackageVariableTests.py | 124 + src/engine/SCons/Variables/PathVariable.py | 147 + src/engine/SCons/Variables/PathVariableTests.py | 237 + src/engine/SCons/Variables/VariablesTests.py | 663 ++ src/engine/SCons/Variables/__init__.py | 317 + src/engine/SCons/Warnings.py | 228 + src/engine/SCons/WarningsTests.py | 135 + src/engine/SCons/__init__.py | 49 + src/engine/SCons/compat/__init__.py | 302 + src/engine/SCons/compat/_scons_UserString.py | 98 + src/engine/SCons/compat/_scons_hashlib.py | 91 + src/engine/SCons/compat/_scons_itertools.py | 124 + src/engine/SCons/compat/_scons_optparse.py | 1725 ++++ src/engine/SCons/compat/_scons_platform.py | 237 + src/engine/SCons/compat/_scons_sets.py | 583 ++ src/engine/SCons/compat/_scons_sets15.py | 176 + src/engine/SCons/compat/_scons_shlex.py | 325 + src/engine/SCons/compat/_scons_subprocess.py | 1296 +++ src/engine/SCons/compat/_scons_textwrap.py | 382 + src/engine/SCons/compat/builtins.py | 187 + src/engine/SCons/cpp.py | 598 ++ src/engine/SCons/cppTests.py | 715 ++ src/engine/SCons/dblite.py | 248 + src/engine/SCons/exitfuncs.py | 77 + src/engine/setup.cfg | 2 + src/engine/setup.py | 74 + src/os_spawnv_fix.diff | 83 + src/script/MANIFEST.in | 3 + src/script/README.txt | 169 + src/script/scons-post-install.py | 82 + src/script/scons-time.py | 1529 +++ src/script/scons.bat | 31 + src/script/scons.py | 184 + src/script/sconsign.py | 508 + src/script/setup.cfg | 2 + src/script/setup.py | 55 + src/setup.cfg | 6 + src/setup.py | 427 + src/test_aegistests.py | 83 + src/test_files.py | 106 + src/test_interrupts.py | 146 + src/test_pychecker.py | 161 + src/test_setup.py | 335 + src/test_strings.py | 280 + 580 files changed, 169411 insertions(+) create mode 100644 LICENSE create mode 100644 SConstruct create mode 100644 bin/Command.py create mode 100644 bin/SConsDoc.py create mode 100755 bin/ae-cvs-ci create mode 100755 bin/ae-svn-ci create mode 100644 bin/ae2cvs create mode 100644 bin/calibrate.py create mode 100644 bin/caller-tree.py create mode 100644 bin/docdiff create mode 100644 bin/docrun create mode 100644 bin/docupdate create mode 100644 bin/files create mode 100644 bin/import-test.py create mode 100644 bin/install_python.py create mode 100644 bin/install_scons.py create mode 100644 bin/linecount.py create mode 100644 bin/makedocs create mode 100644 bin/memlogs.py create mode 100644 bin/memoicmp.py create mode 100644 bin/objcounts.py create mode 100644 bin/restore.sh create mode 100644 bin/rsync-sourceforge create mode 100644 bin/scons-cdist create mode 100644 bin/scons-diff.py create mode 100644 bin/scons-proc.py create mode 100755 bin/scons-review.sh create mode 100644 bin/scons-test.py create mode 100644 bin/scons-unzip.py create mode 100644 bin/scons_dev_master.py create mode 100644 bin/sconsexamples.py create mode 100644 bin/sconsoutput.py create mode 100644 bin/sfsum create mode 100755 bin/svn-bisect.py create mode 100644 bin/time-scons.py create mode 100644 bin/timebuild create mode 100644 bin/xml_export create mode 100644 bin/xml_export-LICENSE create mode 100644 bin/xml_export-README create mode 100755 bin/xmlagenda.py create mode 100644 config create mode 100644 doc/MANIFEST create mode 100644 doc/SConscript create mode 100644 doc/design/MANIFEST create mode 100644 doc/design/acks.xml create mode 100644 doc/design/bground.xml create mode 100644 doc/design/copyright.xml create mode 100644 doc/design/engine.fig create mode 100644 doc/design/engine.jpg create mode 100644 doc/design/engine.xml create mode 100644 doc/design/goals.xml create mode 100644 doc/design/install.xml create mode 100644 doc/design/intro.xml create mode 100644 doc/design/issues.xml create mode 100644 doc/design/main.xml create mode 100644 doc/design/native.xml create mode 100644 doc/design/overview.xml create mode 100644 doc/design/scons.mod create mode 100644 doc/developer/MANIFEST create mode 100644 doc/developer/architecture.xml create mode 100644 doc/developer/branches.xml create mode 100644 doc/developer/copyright.xml create mode 100644 doc/developer/cycle.xml create mode 100644 doc/developer/main.xml create mode 100644 doc/developer/packaging.xml create mode 100644 doc/developer/preface.xml create mode 100644 doc/developer/sourcetree.xml create mode 100644 doc/developer/testing.xml create mode 100644 doc/man/MANIFEST create mode 100644 doc/man/scons-time.1 create mode 100644 doc/man/scons.1 create mode 100644 doc/man/sconsign.1 create mode 100644 doc/python10/MANIFEST create mode 100644 doc/python10/abstract.xml create mode 100644 doc/python10/acks.xml create mode 100644 doc/python10/arch.eps create mode 100644 doc/python10/arch.fig create mode 100644 doc/python10/arch.jpg create mode 100644 doc/python10/builder.eps create mode 100644 doc/python10/builder.fig create mode 100644 doc/python10/builder.jpg create mode 100644 doc/python10/copyright.xml create mode 100644 doc/python10/design.xml create mode 100644 doc/python10/future.xml create mode 100644 doc/python10/install.xml create mode 100644 doc/python10/intro.xml create mode 100644 doc/python10/job-task.eps create mode 100644 doc/python10/job-task.fig create mode 100644 doc/python10/job-task.jpg create mode 100644 doc/python10/main.xml create mode 100644 doc/python10/node.eps create mode 100644 doc/python10/node.fig create mode 100644 doc/python10/node.jpg create mode 100644 doc/python10/process.xml create mode 100644 doc/python10/scanner.eps create mode 100644 doc/python10/scanner.fig create mode 100644 doc/python10/scanner.jpg create mode 100644 doc/python10/scons.mod create mode 100644 doc/python10/sig.eps create mode 100644 doc/python10/sig.fig create mode 100644 doc/python10/sig.jpg create mode 100644 doc/reference/Alias.xml create mode 100644 doc/reference/CFile.xml create mode 100644 doc/reference/CXXFile.xml create mode 100644 doc/reference/Command.xml create mode 100644 doc/reference/Install.xml create mode 100644 doc/reference/InstallAs.xml create mode 100644 doc/reference/Library.xml create mode 100644 doc/reference/MANIFEST create mode 100644 doc/reference/Object.xml create mode 100644 doc/reference/PCH.xml create mode 100644 doc/reference/PDF.xml create mode 100644 doc/reference/PostScript.xml create mode 100644 doc/reference/Program.xml create mode 100644 doc/reference/RES.xml create mode 100644 doc/reference/SharedLibrary.xml create mode 100644 doc/reference/SharedObject.xml create mode 100644 doc/reference/StaticLibrary.xml create mode 100644 doc/reference/StaticObject.xml create mode 100644 doc/reference/copyright.xml create mode 100644 doc/reference/errors.xml create mode 100644 doc/reference/main.xml create mode 100644 doc/reference/preface.xml create mode 100644 doc/scons.mod create mode 100644 doc/user/MANIFEST create mode 100644 doc/user/README create mode 100644 doc/user/SCons-win32-install-1.jpg create mode 100644 doc/user/SCons-win32-install-2.jpg create mode 100644 doc/user/SCons-win32-install-3.jpg create mode 100644 doc/user/SCons-win32-install-4.jpg create mode 100644 doc/user/actions.in create mode 100644 doc/user/actions.xml create mode 100644 doc/user/add-method.in create mode 100644 doc/user/add-method.xml create mode 100644 doc/user/alias.in create mode 100644 doc/user/alias.xml create mode 100644 doc/user/ant.in create mode 100644 doc/user/ant.xml create mode 100644 doc/user/build-install.in create mode 100644 doc/user/build-install.xml create mode 100644 doc/user/builders-built-in.in create mode 100644 doc/user/builders-built-in.xml create mode 100644 doc/user/builders-commands.in create mode 100644 doc/user/builders-commands.xml create mode 100644 doc/user/builders-writing.in create mode 100644 doc/user/builders-writing.xml create mode 100644 doc/user/builders.in create mode 100644 doc/user/builders.xml create mode 100644 doc/user/caching.in create mode 100644 doc/user/caching.xml create mode 100644 doc/user/command-line.in create mode 100644 doc/user/command-line.xml create mode 100644 doc/user/cons.pl create mode 100644 doc/user/copyright.in create mode 100644 doc/user/copyright.xml create mode 100644 doc/user/depends.in create mode 100644 doc/user/depends.xml create mode 100644 doc/user/environments.in create mode 100644 doc/user/environments.xml create mode 100644 doc/user/errors.in create mode 100644 doc/user/errors.xml create mode 100644 doc/user/example.in create mode 100644 doc/user/example.xml create mode 100644 doc/user/factories.in create mode 100644 doc/user/factories.xml create mode 100644 doc/user/file-removal.in create mode 100644 doc/user/file-removal.xml create mode 100644 doc/user/hierarchy.in create mode 100644 doc/user/hierarchy.xml create mode 100644 doc/user/install.in create mode 100644 doc/user/install.xml create mode 100644 doc/user/java.in create mode 100644 doc/user/java.xml create mode 100644 doc/user/less-simple.in create mode 100644 doc/user/less-simple.xml create mode 100644 doc/user/libraries.in create mode 100644 doc/user/libraries.xml create mode 100644 doc/user/main.in create mode 100644 doc/user/main.xml create mode 100644 doc/user/make.in create mode 100644 doc/user/make.xml create mode 100644 doc/user/mergeflags.in create mode 100644 doc/user/mergeflags.xml create mode 100644 doc/user/misc.in create mode 100644 doc/user/misc.xml create mode 100644 doc/user/nodes.in create mode 100644 doc/user/nodes.xml create mode 100644 doc/user/output.in create mode 100644 doc/user/output.xml create mode 100644 doc/user/parseconfig.in create mode 100644 doc/user/parseconfig.xml create mode 100644 doc/user/parseflags.in create mode 100644 doc/user/parseflags.xml create mode 100644 doc/user/preface.in create mode 100644 doc/user/preface.xml create mode 100644 doc/user/python.in create mode 100644 doc/user/python.xml create mode 100644 doc/user/repositories.in create mode 100644 doc/user/repositories.xml create mode 100644 doc/user/run.in create mode 100644 doc/user/run.xml create mode 100644 doc/user/scanners.in create mode 100644 doc/user/scanners.xml create mode 100644 doc/user/sconf.in create mode 100644 doc/user/sconf.xml create mode 100644 doc/user/separate.in create mode 100644 doc/user/separate.xml create mode 100644 doc/user/sideeffect.in create mode 100644 doc/user/sideeffect.xml create mode 100644 doc/user/simple.in create mode 100644 doc/user/simple.xml create mode 100644 doc/user/sourcecode.in create mode 100644 doc/user/sourcecode.xml create mode 100644 doc/user/tasks.in create mode 100644 doc/user/tasks.xml create mode 100644 doc/user/tools.in create mode 100644 doc/user/tools.xml create mode 100644 doc/user/troubleshoot.in create mode 100644 doc/user/troubleshoot.xml create mode 100644 doc/user/variables.in create mode 100644 doc/user/variables.xml create mode 100644 doc/user/variants.in create mode 100644 doc/user/variants.xml create mode 100644 src/CHANGES.txt create mode 100644 src/LICENSE.txt create mode 100644 src/MANIFEST.in create mode 100644 src/README.txt create mode 100644 src/RELEASE.txt create mode 100644 src/engine/MANIFEST-xml.in create mode 100644 src/engine/MANIFEST.in create mode 100644 src/engine/README.txt create mode 100644 src/engine/SCons/Action.py create mode 100644 src/engine/SCons/Action.xml create mode 100644 src/engine/SCons/ActionTests.py create mode 100644 src/engine/SCons/Builder.py create mode 100644 src/engine/SCons/BuilderTests.py create mode 100644 src/engine/SCons/CacheDir.py create mode 100644 src/engine/SCons/CacheDirTests.py create mode 100644 src/engine/SCons/Conftest.py create mode 100644 src/engine/SCons/Debug.py create mode 100644 src/engine/SCons/Defaults.py create mode 100644 src/engine/SCons/Defaults.xml create mode 100644 src/engine/SCons/DefaultsTests.py create mode 100644 src/engine/SCons/Environment.py create mode 100644 src/engine/SCons/Environment.xml create mode 100644 src/engine/SCons/EnvironmentTests.py create mode 100644 src/engine/SCons/Errors.py create mode 100644 src/engine/SCons/ErrorsTests.py create mode 100644 src/engine/SCons/Executor.py create mode 100644 src/engine/SCons/ExecutorTests.py create mode 100644 src/engine/SCons/Job.py create mode 100644 src/engine/SCons/JobTests.py create mode 100644 src/engine/SCons/Memoize.py create mode 100644 src/engine/SCons/MemoizeTests.py create mode 100644 src/engine/SCons/Node/Alias.py create mode 100644 src/engine/SCons/Node/AliasTests.py create mode 100644 src/engine/SCons/Node/FS.py create mode 100644 src/engine/SCons/Node/FSTests.py create mode 100644 src/engine/SCons/Node/NodeTests.py create mode 100644 src/engine/SCons/Node/Python.py create mode 100644 src/engine/SCons/Node/PythonTests.py create mode 100644 src/engine/SCons/Node/__init__.py create mode 100644 src/engine/SCons/Options/BoolOption.py create mode 100644 src/engine/SCons/Options/EnumOption.py create mode 100644 src/engine/SCons/Options/ListOption.py create mode 100644 src/engine/SCons/Options/PackageOption.py create mode 100644 src/engine/SCons/Options/PathOption.py create mode 100644 src/engine/SCons/Options/__init__.py create mode 100644 src/engine/SCons/PathList.py create mode 100644 src/engine/SCons/PathListTests.py create mode 100644 src/engine/SCons/Platform/PlatformTests.py create mode 100644 src/engine/SCons/Platform/__init__.py create mode 100644 src/engine/SCons/Platform/__init__.xml create mode 100644 src/engine/SCons/Platform/aix.py create mode 100644 src/engine/SCons/Platform/cygwin.py create mode 100644 src/engine/SCons/Platform/darwin.py create mode 100644 src/engine/SCons/Platform/hpux.py create mode 100644 src/engine/SCons/Platform/irix.py create mode 100644 src/engine/SCons/Platform/os2.py create mode 100644 src/engine/SCons/Platform/posix.py create mode 100644 src/engine/SCons/Platform/posix.xml create mode 100644 src/engine/SCons/Platform/sunos.py create mode 100644 src/engine/SCons/Platform/sunos.xml create mode 100644 src/engine/SCons/Platform/win32.py create mode 100644 src/engine/SCons/Platform/win32.xml create mode 100644 src/engine/SCons/SConf.py create mode 100644 src/engine/SCons/SConfTests.py create mode 100644 src/engine/SCons/SConsign.py create mode 100644 src/engine/SCons/SConsignTests.py create mode 100644 src/engine/SCons/Scanner/C.py create mode 100644 src/engine/SCons/Scanner/CTests.py create mode 100644 src/engine/SCons/Scanner/D.py create mode 100644 src/engine/SCons/Scanner/Dir.py create mode 100644 src/engine/SCons/Scanner/DirTests.py create mode 100644 src/engine/SCons/Scanner/Fortran.py create mode 100644 src/engine/SCons/Scanner/FortranTests.py create mode 100644 src/engine/SCons/Scanner/IDL.py create mode 100644 src/engine/SCons/Scanner/IDLTests.py create mode 100644 src/engine/SCons/Scanner/LaTeX.py create mode 100644 src/engine/SCons/Scanner/LaTeXTests.py create mode 100644 src/engine/SCons/Scanner/Prog.py create mode 100644 src/engine/SCons/Scanner/ProgTests.py create mode 100644 src/engine/SCons/Scanner/RC.py create mode 100644 src/engine/SCons/Scanner/RCTests.py create mode 100644 src/engine/SCons/Scanner/ScannerTests.py create mode 100644 src/engine/SCons/Scanner/__init__.py create mode 100644 src/engine/SCons/Script/Interactive.py create mode 100644 src/engine/SCons/Script/Main.py create mode 100644 src/engine/SCons/Script/MainTests.py create mode 100644 src/engine/SCons/Script/SConsOptions.py create mode 100644 src/engine/SCons/Script/SConscript.py create mode 100644 src/engine/SCons/Script/SConscriptTests.py create mode 100644 src/engine/SCons/Script/__init__.py create mode 100644 src/engine/SCons/Sig.py create mode 100644 src/engine/SCons/Subst.py create mode 100644 src/engine/SCons/SubstTests.py create mode 100644 src/engine/SCons/Taskmaster.py create mode 100644 src/engine/SCons/TaskmasterTests.py create mode 100644 src/engine/SCons/Tool/386asm.py create mode 100644 src/engine/SCons/Tool/386asm.xml create mode 100644 src/engine/SCons/Tool/BitKeeper.py create mode 100644 src/engine/SCons/Tool/BitKeeper.xml create mode 100644 src/engine/SCons/Tool/CVS.py create mode 100644 src/engine/SCons/Tool/CVS.xml create mode 100644 src/engine/SCons/Tool/FortranCommon.py create mode 100644 src/engine/SCons/Tool/JavaCommon.py create mode 100644 src/engine/SCons/Tool/JavaCommonTests.py create mode 100644 src/engine/SCons/Tool/MSCommon/__init__.py create mode 100644 src/engine/SCons/Tool/MSCommon/arch.py create mode 100644 src/engine/SCons/Tool/MSCommon/common.py create mode 100644 src/engine/SCons/Tool/MSCommon/netframework.py create mode 100644 src/engine/SCons/Tool/MSCommon/sdk.py create mode 100644 src/engine/SCons/Tool/MSCommon/vc.py create mode 100644 src/engine/SCons/Tool/MSCommon/vc.py.bak create mode 100644 src/engine/SCons/Tool/MSCommon/vs.py create mode 100644 src/engine/SCons/Tool/Perforce.py create mode 100644 src/engine/SCons/Tool/Perforce.xml create mode 100644 src/engine/SCons/Tool/PharLapCommon.py create mode 100644 src/engine/SCons/Tool/PharLapCommonTests.py create mode 100644 src/engine/SCons/Tool/RCS.py create mode 100644 src/engine/SCons/Tool/RCS.xml create mode 100644 src/engine/SCons/Tool/SCCS.py create mode 100644 src/engine/SCons/Tool/SCCS.xml create mode 100644 src/engine/SCons/Tool/Subversion.py create mode 100644 src/engine/SCons/Tool/Subversion.xml create mode 100644 src/engine/SCons/Tool/ToolTests.py create mode 100644 src/engine/SCons/Tool/__init__.py create mode 100644 src/engine/SCons/Tool/__init__.xml create mode 100644 src/engine/SCons/Tool/aixc++.py create mode 100644 src/engine/SCons/Tool/aixc++.xml create mode 100644 src/engine/SCons/Tool/aixcc.py create mode 100644 src/engine/SCons/Tool/aixcc.xml create mode 100644 src/engine/SCons/Tool/aixf77.py create mode 100644 src/engine/SCons/Tool/aixf77.xml create mode 100644 src/engine/SCons/Tool/aixlink.py create mode 100644 src/engine/SCons/Tool/aixlink.xml create mode 100644 src/engine/SCons/Tool/applelink.py create mode 100644 src/engine/SCons/Tool/applelink.xml create mode 100644 src/engine/SCons/Tool/ar.py create mode 100644 src/engine/SCons/Tool/ar.xml create mode 100644 src/engine/SCons/Tool/as.py create mode 100644 src/engine/SCons/Tool/as.xml create mode 100644 src/engine/SCons/Tool/bcc32.py create mode 100644 src/engine/SCons/Tool/bcc32.xml create mode 100644 src/engine/SCons/Tool/c++.py create mode 100644 src/engine/SCons/Tool/c++.xml create mode 100644 src/engine/SCons/Tool/cc.py create mode 100644 src/engine/SCons/Tool/cc.xml create mode 100644 src/engine/SCons/Tool/cvf.py create mode 100644 src/engine/SCons/Tool/cvf.xml create mode 100644 src/engine/SCons/Tool/default.py create mode 100644 src/engine/SCons/Tool/default.xml create mode 100644 src/engine/SCons/Tool/dmd.py create mode 100644 src/engine/SCons/Tool/dmd.xml create mode 100644 src/engine/SCons/Tool/dvi.py create mode 100644 src/engine/SCons/Tool/dvi.xml create mode 100644 src/engine/SCons/Tool/dvipdf.py create mode 100644 src/engine/SCons/Tool/dvipdf.xml create mode 100644 src/engine/SCons/Tool/dvips.py create mode 100644 src/engine/SCons/Tool/dvips.xml create mode 100644 src/engine/SCons/Tool/f77.py create mode 100644 src/engine/SCons/Tool/f77.xml create mode 100644 src/engine/SCons/Tool/f90.py create mode 100644 src/engine/SCons/Tool/f90.xml create mode 100644 src/engine/SCons/Tool/f95.py create mode 100644 src/engine/SCons/Tool/f95.xml create mode 100644 src/engine/SCons/Tool/filesystem.py create mode 100644 src/engine/SCons/Tool/fortran.py create mode 100644 src/engine/SCons/Tool/fortran.xml create mode 100644 src/engine/SCons/Tool/g++.py create mode 100644 src/engine/SCons/Tool/g++.xml create mode 100644 src/engine/SCons/Tool/g77.py create mode 100644 src/engine/SCons/Tool/g77.xml create mode 100644 src/engine/SCons/Tool/gas.py create mode 100644 src/engine/SCons/Tool/gas.xml create mode 100644 src/engine/SCons/Tool/gcc.py create mode 100644 src/engine/SCons/Tool/gcc.xml create mode 100644 src/engine/SCons/Tool/gfortran.py create mode 100644 src/engine/SCons/Tool/gfortran.xml create mode 100644 src/engine/SCons/Tool/gnulink.py create mode 100644 src/engine/SCons/Tool/gnulink.xml create mode 100644 src/engine/SCons/Tool/gs.py create mode 100644 src/engine/SCons/Tool/gs.xml create mode 100644 src/engine/SCons/Tool/hpc++.py create mode 100644 src/engine/SCons/Tool/hpc++.xml create mode 100644 src/engine/SCons/Tool/hpcc.py create mode 100644 src/engine/SCons/Tool/hpcc.xml create mode 100644 src/engine/SCons/Tool/hplink.py create mode 100644 src/engine/SCons/Tool/hplink.xml create mode 100644 src/engine/SCons/Tool/icc.py create mode 100644 src/engine/SCons/Tool/icc.xml create mode 100644 src/engine/SCons/Tool/icl.py create mode 100644 src/engine/SCons/Tool/icl.xml create mode 100644 src/engine/SCons/Tool/ifl.py create mode 100644 src/engine/SCons/Tool/ifl.xml create mode 100644 src/engine/SCons/Tool/ifort.py create mode 100644 src/engine/SCons/Tool/ifort.xml create mode 100644 src/engine/SCons/Tool/ilink.py create mode 100644 src/engine/SCons/Tool/ilink.xml create mode 100644 src/engine/SCons/Tool/ilink32.py create mode 100644 src/engine/SCons/Tool/ilink32.xml create mode 100644 src/engine/SCons/Tool/install.py create mode 100644 src/engine/SCons/Tool/install.xml create mode 100644 src/engine/SCons/Tool/intelc.py create mode 100644 src/engine/SCons/Tool/intelc.xml create mode 100644 src/engine/SCons/Tool/ipkg.py create mode 100644 src/engine/SCons/Tool/jar.py create mode 100644 src/engine/SCons/Tool/jar.xml create mode 100644 src/engine/SCons/Tool/javac.py create mode 100644 src/engine/SCons/Tool/javac.xml create mode 100644 src/engine/SCons/Tool/javah.py create mode 100644 src/engine/SCons/Tool/javah.xml create mode 100644 src/engine/SCons/Tool/latex.py create mode 100644 src/engine/SCons/Tool/latex.xml create mode 100644 src/engine/SCons/Tool/lex.py create mode 100644 src/engine/SCons/Tool/lex.xml create mode 100644 src/engine/SCons/Tool/link.py create mode 100644 src/engine/SCons/Tool/link.xml create mode 100644 src/engine/SCons/Tool/linkloc.py create mode 100644 src/engine/SCons/Tool/linkloc.xml create mode 100644 src/engine/SCons/Tool/m4.py create mode 100644 src/engine/SCons/Tool/m4.xml create mode 100644 src/engine/SCons/Tool/masm.py create mode 100644 src/engine/SCons/Tool/masm.xml create mode 100644 src/engine/SCons/Tool/midl.py create mode 100644 src/engine/SCons/Tool/midl.xml create mode 100644 src/engine/SCons/Tool/mingw.py create mode 100644 src/engine/SCons/Tool/mingw.xml create mode 100644 src/engine/SCons/Tool/mslib.py create mode 100644 src/engine/SCons/Tool/mslib.xml create mode 100644 src/engine/SCons/Tool/mslink.py create mode 100644 src/engine/SCons/Tool/mslink.xml create mode 100644 src/engine/SCons/Tool/mssdk.py create mode 100644 src/engine/SCons/Tool/mssdk.xml create mode 100644 src/engine/SCons/Tool/msvc.py create mode 100644 src/engine/SCons/Tool/msvc.xml create mode 100644 src/engine/SCons/Tool/msvs.py create mode 100644 src/engine/SCons/Tool/msvs.xml create mode 100644 src/engine/SCons/Tool/msvsTests.py create mode 100644 src/engine/SCons/Tool/mwcc.py create mode 100644 src/engine/SCons/Tool/mwcc.xml create mode 100644 src/engine/SCons/Tool/mwld.py create mode 100644 src/engine/SCons/Tool/mwld.xml create mode 100644 src/engine/SCons/Tool/nasm.py create mode 100644 src/engine/SCons/Tool/nasm.xml create mode 100644 src/engine/SCons/Tool/packaging.xml create mode 100644 src/engine/SCons/Tool/packaging/__init__.py create mode 100644 src/engine/SCons/Tool/packaging/__init__.xml create mode 100644 src/engine/SCons/Tool/packaging/ipk.py create mode 100644 src/engine/SCons/Tool/packaging/msi.py create mode 100644 src/engine/SCons/Tool/packaging/rpm.py create mode 100644 src/engine/SCons/Tool/packaging/src_tarbz2.py create mode 100644 src/engine/SCons/Tool/packaging/src_targz.py create mode 100644 src/engine/SCons/Tool/packaging/src_zip.py create mode 100644 src/engine/SCons/Tool/packaging/tarbz2.py create mode 100644 src/engine/SCons/Tool/packaging/targz.py create mode 100644 src/engine/SCons/Tool/packaging/zip.py create mode 100644 src/engine/SCons/Tool/pdf.py create mode 100644 src/engine/SCons/Tool/pdf.xml create mode 100644 src/engine/SCons/Tool/pdflatex.py create mode 100644 src/engine/SCons/Tool/pdflatex.xml create mode 100644 src/engine/SCons/Tool/pdftex.py create mode 100644 src/engine/SCons/Tool/pdftex.xml create mode 100644 src/engine/SCons/Tool/qt.py create mode 100644 src/engine/SCons/Tool/qt.xml create mode 100644 src/engine/SCons/Tool/rmic.py create mode 100644 src/engine/SCons/Tool/rmic.xml create mode 100644 src/engine/SCons/Tool/rpcgen.py create mode 100644 src/engine/SCons/Tool/rpcgen.xml create mode 100644 src/engine/SCons/Tool/rpm.py create mode 100644 src/engine/SCons/Tool/sgiar.py create mode 100644 src/engine/SCons/Tool/sgiar.xml create mode 100644 src/engine/SCons/Tool/sgic++.py create mode 100644 src/engine/SCons/Tool/sgic++.xml create mode 100644 src/engine/SCons/Tool/sgicc.py create mode 100644 src/engine/SCons/Tool/sgicc.xml create mode 100644 src/engine/SCons/Tool/sgilink.py create mode 100644 src/engine/SCons/Tool/sgilink.xml create mode 100644 src/engine/SCons/Tool/sunar.py create mode 100644 src/engine/SCons/Tool/sunar.xml create mode 100644 src/engine/SCons/Tool/sunc++.py create mode 100644 src/engine/SCons/Tool/sunc++.xml create mode 100644 src/engine/SCons/Tool/suncc.py create mode 100644 src/engine/SCons/Tool/suncc.xml create mode 100644 src/engine/SCons/Tool/sunf77.py create mode 100644 src/engine/SCons/Tool/sunf77.xml create mode 100644 src/engine/SCons/Tool/sunf90.py create mode 100644 src/engine/SCons/Tool/sunf90.xml create mode 100644 src/engine/SCons/Tool/sunf95.py create mode 100644 src/engine/SCons/Tool/sunf95.xml create mode 100644 src/engine/SCons/Tool/sunlink.py create mode 100644 src/engine/SCons/Tool/sunlink.xml create mode 100644 src/engine/SCons/Tool/swig.py create mode 100644 src/engine/SCons/Tool/swig.xml create mode 100644 src/engine/SCons/Tool/tar.py create mode 100644 src/engine/SCons/Tool/tar.xml create mode 100644 src/engine/SCons/Tool/tex.py create mode 100644 src/engine/SCons/Tool/tex.xml create mode 100644 src/engine/SCons/Tool/textfile.py create mode 100644 src/engine/SCons/Tool/textfile.xml create mode 100644 src/engine/SCons/Tool/tlib.py create mode 100644 src/engine/SCons/Tool/tlib.xml create mode 100644 src/engine/SCons/Tool/wix.py create mode 100644 src/engine/SCons/Tool/yacc.py create mode 100644 src/engine/SCons/Tool/yacc.xml create mode 100644 src/engine/SCons/Tool/zip.py create mode 100644 src/engine/SCons/Tool/zip.xml create mode 100644 src/engine/SCons/Util.py create mode 100644 src/engine/SCons/UtilTests.py create mode 100644 src/engine/SCons/Variables/BoolVariable.py create mode 100644 src/engine/SCons/Variables/BoolVariableTests.py create mode 100644 src/engine/SCons/Variables/EnumVariable.py create mode 100644 src/engine/SCons/Variables/EnumVariableTests.py create mode 100644 src/engine/SCons/Variables/ListVariable.py create mode 100644 src/engine/SCons/Variables/ListVariableTests.py create mode 100644 src/engine/SCons/Variables/PackageVariable.py create mode 100644 src/engine/SCons/Variables/PackageVariableTests.py create mode 100644 src/engine/SCons/Variables/PathVariable.py create mode 100644 src/engine/SCons/Variables/PathVariableTests.py create mode 100644 src/engine/SCons/Variables/VariablesTests.py create mode 100644 src/engine/SCons/Variables/__init__.py create mode 100644 src/engine/SCons/Warnings.py create mode 100644 src/engine/SCons/WarningsTests.py create mode 100644 src/engine/SCons/__init__.py create mode 100644 src/engine/SCons/compat/__init__.py create mode 100644 src/engine/SCons/compat/_scons_UserString.py create mode 100644 src/engine/SCons/compat/_scons_hashlib.py create mode 100644 src/engine/SCons/compat/_scons_itertools.py create mode 100644 src/engine/SCons/compat/_scons_optparse.py create mode 100644 src/engine/SCons/compat/_scons_platform.py create mode 100644 src/engine/SCons/compat/_scons_sets.py create mode 100644 src/engine/SCons/compat/_scons_sets15.py create mode 100644 src/engine/SCons/compat/_scons_shlex.py create mode 100644 src/engine/SCons/compat/_scons_subprocess.py create mode 100644 src/engine/SCons/compat/_scons_textwrap.py create mode 100644 src/engine/SCons/compat/builtins.py create mode 100644 src/engine/SCons/cpp.py create mode 100644 src/engine/SCons/cppTests.py create mode 100644 src/engine/SCons/dblite.py create mode 100644 src/engine/SCons/exitfuncs.py create mode 100644 src/engine/setup.cfg create mode 100644 src/engine/setup.py create mode 100644 src/os_spawnv_fix.diff create mode 100644 src/script/MANIFEST.in create mode 100644 src/script/README.txt create mode 100644 src/script/scons-post-install.py create mode 100644 src/script/scons-time.py create mode 100644 src/script/scons.bat create mode 100644 src/script/scons.py create mode 100644 src/script/sconsign.py create mode 100644 src/script/setup.cfg create mode 100644 src/script/setup.py create mode 100644 src/setup.cfg create mode 100644 src/setup.py create mode 100644 src/test_aegistests.py create mode 100644 src/test_files.py create mode 100644 src/test_interrupts.py create mode 100644 src/test_pychecker.py create mode 100644 src/test_setup.py create mode 100644 src/test_strings.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..67f1906 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/SConstruct b/SConstruct new file mode 100644 index 0000000..1a7f0ea --- /dev/null +++ b/SConstruct @@ -0,0 +1,1389 @@ +# +# SConstruct file to build scons packages during development. +# +# See the README file for an overview of how SCons is built and tested. +# + +# When this gets changed, you must also change the copyright_years string +# in QMTest/TestSCons.py so the test scripts look for the right string. +copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009' + +# This gets inserted into the man pages to reflect the month of release. +month_year = 'December 2009' + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +import distutils.util +import fnmatch +import os +import os.path +import re +import stat +import string +import sys +import tempfile + +project = 'scons' +default_version = '1.2.0' +copyright = "Copyright (c) %s The SCons Foundation" % copyright_years + +platform = distutils.util.get_platform() + +SConsignFile() + +# +# An internal "whereis" routine to figure out if a given program +# is available on this system. +# +def whereis(file): + exts = [''] + if platform == "win32": + exts += ['.exe'] + for dir in string.split(os.environ['PATH'], os.pathsep): + f = os.path.join(dir, file) + for ext in exts: + f_ext = f + ext + if os.path.isfile(f_ext): + try: + st = os.stat(f_ext) + except: + continue + if stat.S_IMODE(st[stat.ST_MODE]) & 0111: + return f_ext + return None + +# +# We let the presence or absence of various utilities determine whether +# or not we bother to build certain pieces of things. This should allow +# people to still do SCons packaging work even if they don't have all +# of the utilities installed (e.g. RPM). +# +dh_builddeb = whereis('dh_builddeb') +fakeroot = whereis('fakeroot') +gzip = whereis('gzip') +rpmbuild = whereis('rpmbuild') or whereis('rpm') +hg = os.path.exists('.hg') and whereis('hg') +svn = os.path.exists('.svn') and whereis('svn') +unzip = whereis('unzip') +zip = whereis('zip') + +# +# Now grab the information that we "build" into the files. +# +date = ARGUMENTS.get('DATE') +if not date: + import time + date = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(time.time())) + +developer = ARGUMENTS.get('DEVELOPER') +if not developer: + for variable in ['USERNAME', 'LOGNAME', 'USER']: + developer = os.environ.get(variable) + if developer: + break + +build_system = ARGUMENTS.get('BUILD_SYSTEM') +if not build_system: + import socket + build_system = string.split(socket.gethostname(), '.')[0] + +version = ARGUMENTS.get('VERSION', '') +if not version: + version = default_version + +hg_status_lines = [] +svn_status_lines = [] + +if hg: + cmd = "%s status --all 2> /dev/null" % hg + hg_status_lines = os.popen(cmd, "r").readlines() + +if svn: + cmd = "%s status --verbose 2> /dev/null" % svn + svn_status_lines = os.popen(cmd, "r").readlines() + +revision = ARGUMENTS.get('REVISION', '') +def generate_build_id(revision): + return revision + +if not revision and hg: + hg_heads = os.popen("%s heads 2> /dev/null" % hg, "r").read() + cs = re.search('changeset:\s+(\S+)', hg_heads) + if cs: + revision = cs.group(1) + b = re.search('branch:\s+(\S+)', hg_heads) + if b: + revision = b.group(1) + ':' + revision + def generate_build_id(revision): + result = revision + if filter(lambda l: l[0] in 'AMR!', hg_status_lines): + result = result + '[MODIFIED]' + return result + +if not revision and svn: + svn_info = os.popen("%s info 2> /dev/null" % svn, "r").read() + m = re.search('Revision: (\d+)', svn_info) + if m: + revision = m.group(1) + def generate_build_id(revision): + result = 'r' + revision + if filter(lambda l: l[0] in 'ACDMR', svn_status_lines): + result = result + '[MODIFIED]' + return result + + +checkpoint = ARGUMENTS.get('CHECKPOINT', '') +if checkpoint: + if checkpoint == 'd': + import time + checkpoint = time.strftime('d%Y%m%d', time.localtime(time.time())) + elif checkpoint == 'r': + checkpoint = 'r' + revision + version = version + '.' + checkpoint + + +build_id = ARGUMENTS.get('BUILD_ID') +if build_id is None: + if revision: + build_id = generate_build_id(revision) + else: + build_id = '' + +python_ver = sys.version[0:3] + +# Re-exporting LD_LIBRARY_PATH is necessary if the Python version was +# built with the --enable-shared option. + +ENV = { 'PATH' : os.environ['PATH'] } +for key in ['LOGNAME', 'PYTHONPATH', 'LD_LIBRARY_PATH']: + if os.environ.has_key(key): + ENV[key] = os.environ[key] + +build_dir = ARGUMENTS.get('BUILDDIR', 'build') +if not os.path.isabs(build_dir): + build_dir = os.path.normpath(os.path.join(os.getcwd(), build_dir)) + +command_line_variables = [ + ("BUILDDIR=", "The directory in which to build the packages. " + + "The default is the './build' subdirectory."), + + ("BUILD_ID=", "An identifier for the specific build." + + "The default is the Subversion revision number."), + + ("BUILD_SYSTEM=", "The system on which the packages were built. " + + "The default is whatever hostname is returned " + + "by socket.gethostname()."), + + ("CHECKPOINT=", "The specific checkpoint release being packaged, " + + "which will be appended to the VERSION string. " + + "A value of CHECKPOINT=d will generate a string " + + "of 'd' plus today's date in the format YYYMMDD. " + + "A value of CHECKPOINT=r will generate a " + + "string of 'r' plus the Subversion revision " + + "number. Any other CHECKPOINT= string will be " + + "used as is. There is no default value."), + + ("DATE=", "The date string representing when the packaging " + + "build occurred. The default is the day and time " + + "the SConstruct file was invoked, in the format " + + "YYYY/MM/DD HH:MM:SS."), + + ("DEVELOPER=", "The developer who created the packages. " + + "The default is the first set environment " + + "variable from the list $USERNAME, $LOGNAME, $USER."), + + ("REVISION=", "The revision number of the source being built. " + + "The default is the Subversion revision returned " + + "'svn info', with an appended string of " + + "'[MODIFIED]' if there are any changes in the " + + "working copy."), + + ("VERSION=", "The SCons version being packaged. The default " + + "is the hard-coded value '%s' " % default_version + + "from this SConstruct file."), +] + +Default('.', build_dir) + +packaging_flavors = [ + ('deb', "A .deb package. (This is currently not supported.)"), + + ('rpm', "A RedHat Package Manager file."), + + ('tar-gz', "The normal .tar.gz file for end-user installation."), + + ('src-tar-gz', "A .tar.gz file containing all the source " + + "(including tests and documentation)."), + + ('local-tar-gz', "A .tar.gz file for dropping into other software " + + "for local use."), + + ('zip', "The normal .zip file for end-user installation."), + + ('src-zip', "A .zip file containing all the source " + + "(including tests and documentation)."), + + ('local-zip', "A .zip file for dropping into other software " + + "for local use."), +] + +test_deb_dir = os.path.join(build_dir, "test-deb") +test_rpm_dir = os.path.join(build_dir, "test-rpm") +test_tar_gz_dir = os.path.join(build_dir, "test-tar-gz") +test_src_tar_gz_dir = os.path.join(build_dir, "test-src-tar-gz") +test_local_tar_gz_dir = os.path.join(build_dir, "test-local-tar-gz") +test_zip_dir = os.path.join(build_dir, "test-zip") +test_src_zip_dir = os.path.join(build_dir, "test-src-zip") +test_local_zip_dir = os.path.join(build_dir, "test-local-zip") + +unpack_tar_gz_dir = os.path.join(build_dir, "unpack-tar-gz") +unpack_zip_dir = os.path.join(build_dir, "unpack-zip") + +if platform == "win32": + tar_hflag = '' + python_project_subinst_dir = os.path.join("Lib", "site-packages", project) + project_script_subinst_dir = 'Scripts' +else: + tar_hflag = 'h' + python_project_subinst_dir = os.path.join("lib", project) + project_script_subinst_dir = 'bin' + + + +import textwrap + +indent_fmt = ' %-26s ' + +Help("""\ +The following aliases build packages of various types, and unpack the +contents into build/test-$PACKAGE subdirectories, which can be used by the +runtest.py -p option to run tests against what's been actually packaged: + +""") + +aliases = packaging_flavors + [('doc', 'The SCons documentation.')] +aliases.sort() + +for alias, help_text in aliases: + tw = textwrap.TextWrapper( + width = 78, + initial_indent = indent_fmt % alias, + subsequent_indent = indent_fmt % '' + ' ', + ) + Help(tw.fill(help_text) + '\n') + +Help(""" +The following command-line variables can be set: + +""") + +for variable, help_text in command_line_variables: + tw = textwrap.TextWrapper( + width = 78, + initial_indent = indent_fmt % variable, + subsequent_indent = indent_fmt % '' + ' ', + ) + Help(tw.fill(help_text) + '\n') + + + +zcat = 'gzip -d -c' + +# +# Figure out if we can handle .zip files. +# +zipit = None +unzipit = None +try: + import zipfile + + def zipit(env, target, source): + print "Zipping %s:" % str(target[0]) + def visit(arg, dirname, names): + for name in names: + path = os.path.join(dirname, name) + if os.path.isfile(path): + arg.write(path) + zf = zipfile.ZipFile(str(target[0]), 'w') + olddir = os.getcwd() + os.chdir(env['CD']) + try: os.path.walk(env['PSV'], visit, zf) + finally: os.chdir(olddir) + zf.close() + + def unzipit(env, target, source): + print "Unzipping %s:" % str(source[0]) + zf = zipfile.ZipFile(str(source[0]), 'r') + for name in zf.namelist(): + dest = os.path.join(env['UNPACK_ZIP_DIR'], name) + dir = os.path.dirname(dest) + try: + os.makedirs(dir) + except: + pass + print dest,name + # if the file exists, then delete it before writing + # to it so that we don't end up trying to write to a symlink: + if os.path.isfile(dest) or os.path.islink(dest): + os.unlink(dest) + if not os.path.isdir(dest): + open(dest, 'wb').write(zf.read(name)) + +except: + if unzip and zip: + zipit = "cd $CD && $ZIP $ZIPFLAGS $( ${TARGET.abspath} $) $PSV" + unzipit = "$UNZIP $UNZIPFLAGS $SOURCES" + +def SCons_revision(target, source, env): + """Interpolate specific values from the environment into a file. + + This is used to copy files into a tree that gets packaged up + into the source file package. + """ + t = str(target[0]) + s = source[0].rstr() + contents = open(s, 'rb').read() + # Note: We construct the __*__ substitution strings here + # so that they don't get replaced when this file gets + # copied into the tree for packaging. + contents = string.replace(contents, '__BUILD' + '__', env['BUILD']) + contents = string.replace(contents, '__BUILDSYS' + '__', env['BUILDSYS']) + contents = string.replace(contents, '__COPYRIGHT' + '__', env['COPYRIGHT']) + contents = string.replace(contents, '__DATE' + '__', env['DATE']) + contents = string.replace(contents, '__DEVELOPER' + '__', env['DEVELOPER']) + contents = string.replace(contents, '__FILE' + '__', str(source[0])) + contents = string.replace(contents, '__MONTH_YEAR'+ '__', env['MONTH_YEAR']) + contents = string.replace(contents, '__REVISION' + '__', env['REVISION']) + contents = string.replace(contents, '__VERSION' + '__', env['VERSION']) + contents = string.replace(contents, '__NULL' + '__', '') + open(t, 'wb').write(contents) + os.chmod(t, os.stat(s)[0]) + +revbuilder = Builder(action = Action(SCons_revision, + varlist=['COPYRIGHT', 'VERSION'])) + +def soelim(target, source, env): + """ + Interpolate files included in [gnt]roff source files using the + .so directive. + + This behaves somewhat like the soelim(1) wrapper around groff, but + makes us independent of whether the actual underlying implementation + includes an soelim() command or the corresponding command-line option + to groff(1). The key behavioral difference is that this doesn't + recursively include .so files from the include file. Not yet, anyway. + """ + t = str(target[0]) + s = str(source[0]) + dir, f = os.path.split(s) + tfp = open(t, 'w') + sfp = open(s, 'r') + for line in sfp.readlines(): + if line[:4] in ['.so ', "'so "]: + sofile = os.path.join(dir, line[4:-1]) + tfp.write(open(sofile, 'r').read()) + else: + tfp.write(line) + sfp.close() + tfp.close() + +def soscan(node, env, path): + c = node.get_text_contents() + return re.compile(r"^[\.']so\s+(\S+)", re.M).findall(c) + +soelimbuilder = Builder(action = Action(soelim), + source_scanner = Scanner(soscan)) + +# When copying local files from a Repository (Aegis), +# just make copies, don't symlink them. +SetOption('duplicate', 'copy') + +env = Environment( + ENV = ENV, + + BUILD = build_id, + BUILDDIR = build_dir, + BUILDSYS = build_system, + COPYRIGHT = copyright, + DATE = date, + DEVELOPER = developer, + DISTDIR = os.path.join(build_dir, 'dist'), + MONTH_YEAR = month_year, + REVISION = revision, + VERSION = version, + DH_COMPAT = 2, + + TAR_HFLAG = tar_hflag, + + ZIP = zip, + ZIPFLAGS = '-r', + UNZIP = unzip, + UNZIPFLAGS = '-o -d $UNPACK_ZIP_DIR', + + ZCAT = zcat, + + RPMBUILD = rpmbuild, + RPM2CPIO = 'rpm2cpio', + + TEST_DEB_DIR = test_deb_dir, + TEST_RPM_DIR = test_rpm_dir, + TEST_SRC_TAR_GZ_DIR = test_src_tar_gz_dir, + TEST_SRC_ZIP_DIR = test_src_zip_dir, + TEST_TAR_GZ_DIR = test_tar_gz_dir, + TEST_ZIP_DIR = test_zip_dir, + + UNPACK_TAR_GZ_DIR = unpack_tar_gz_dir, + UNPACK_ZIP_DIR = unpack_zip_dir, + + BUILDERS = { 'SCons_revision' : revbuilder, + 'SOElim' : soelimbuilder }, + + PYTHON = '"%s"' % sys.executable, + PYTHONFLAGS = '-tt', + ) + +Version_values = [Value(version), Value(build_id)] + +# +# Define SCons packages. +# +# In the original, more complicated packaging scheme, we were going +# to have separate packages for: +# +# python-scons only the build engine +# scons-script only the script +# scons the script plus the build engine +# +# We're now only delivering a single "scons" package, but this is still +# "built" as two sub-packages (the build engine and the script), so +# the definitions remain here, even though we're not using them for +# separate packages. +# + +python_scons = { + 'pkg' : 'python-' + project, + 'src_subdir' : 'engine', + 'inst_subdir' : os.path.join('lib', 'python1.5', 'site-packages'), + 'rpm_dir' : '/usr/lib/scons', + + 'debian_deps' : [ + 'debian/changelog', + 'debian/control', + 'debian/copyright', + 'debian/dirs', + 'debian/docs', + 'debian/postinst', + 'debian/prerm', + 'debian/rules', + ], + + 'files' : [ 'LICENSE.txt', + 'README.txt', + 'setup.cfg', + 'setup.py', + ], + + 'filemap' : { + 'LICENSE.txt' : '../LICENSE.txt' + }, + + 'buildermap' : {}, + + 'extra_rpm_files' : [], + + 'explicit_deps' : { + 'SCons/__init__.py' : Version_values, + }, +} + +# Figure out the name of a .egg-info file that might be generated +# as part of the RPM package. There are two complicating factors. +# +# First, the RPM spec file we generate will just execute "python", not +# necessarily the one in sys.executable. If *that* version of python has +# a distutils that knows about Python eggs, then setup.py will generate a +# .egg-info file, so we have to execute any distutils logic in a subshell. +# +# Second, we can't just have the subshell check for the existence of the +# distutils.command.install_egg_info module and generate the expected +# file name by hand, the way we used to, because different systems can +# have slightly different .egg-info naming conventions. (Specifically, +# Ubuntu overrides the default behavior to remove the Python version +# string from the .egg-info file name.) The right way to do this is to +# actually call into the install_egg_info() class to have it generate +# the expected name for us. +# +# This is all complicated enough that we do it by writing an in-line +# script to a temporary file and then feeding it to a separate invocation +# of "python" to tell us the actual name of the generated .egg-info file. + +print_egg_info_name = """ +try: + from distutils.dist import Distribution + from distutils.command.install_egg_info import install_egg_info +except ImportError: + pass +else: + dist = Distribution({'name' : "scons", 'version' : '%s'}) + i = install_egg_info(dist) + i.finalize_options() + import os.path + print os.path.split(i.outputs[0])[1] +""" % version + +try: + fd, tfname = tempfile.mkstemp() + tfp = os.fdopen(fd, "w") + tfp.write(print_egg_info_name) + tfp.close() + egg_info_file = os.popen("python %s" % tfname).read()[:-1] + if egg_info_file: + python_scons['extra_rpm_files'].append(egg_info_file) +finally: + try: + os.unlink(tfname) + except EnvironmentError: + pass + +# +# The original packaging scheme would have have required us to push +# the Python version number into the package name (python1.5-scons, +# python2.0-scons, etc.), which would have required a definition +# like the following. Leave this here in case we ever decide to do +# this in the future, but note that this would require some modification +# to src/engine/setup.py before it would really work. +# +#python2_scons = { +# 'pkg' : 'python2-' + project, +# 'src_subdir' : 'engine', +# 'inst_subdir' : os.path.join('lib', 'python2.2', 'site-packages'), +# +# 'debian_deps' : [ +# 'debian/changelog', +# 'debian/control', +# 'debian/copyright', +# 'debian/dirs', +# 'debian/docs', +# 'debian/postinst', +# 'debian/prerm', +# 'debian/rules', +# ], +# +# 'files' : [ +# 'LICENSE.txt', +# 'README.txt', +# 'setup.cfg', +# 'setup.py', +# ], +# 'filemap' : { +# 'LICENSE.txt' : '../LICENSE.txt', +# }, +# 'buildermap' : {}, +#} +# + +scons_script = { + 'pkg' : project + '-script', + 'src_subdir' : 'script', + 'inst_subdir' : 'bin', + 'rpm_dir' : '/usr/bin', + + 'debian_deps' : [ + 'debian/changelog', + 'debian/control', + 'debian/copyright', + 'debian/dirs', + 'debian/docs', + 'debian/postinst', + 'debian/prerm', + 'debian/rules', + ], + + 'files' : [ + 'LICENSE.txt', + 'README.txt', + 'setup.cfg', + 'setup.py', + ], + + 'filemap' : { + 'LICENSE.txt' : '../LICENSE.txt', + 'scons' : 'scons.py', + 'sconsign' : 'sconsign.py', + 'scons-time' : 'scons-time.py', + }, + + 'buildermap' : {}, + + 'extra_rpm_files' : [ + 'scons-' + version, + 'sconsign-' + version, + 'scons-time-' + version, + ], + + 'explicit_deps' : { + 'scons' : Version_values, + 'sconsign' : Version_values, + }, +} + +scons = { + 'pkg' : project, + + 'debian_deps' : [ + 'debian/changelog', + 'debian/control', + 'debian/copyright', + 'debian/dirs', + 'debian/docs', + 'debian/postinst', + 'debian/prerm', + 'debian/rules', + ], + + 'files' : [ + 'CHANGES.txt', + 'LICENSE.txt', + 'README.txt', + 'RELEASE.txt', + 'os_spawnv_fix.diff', + 'scons.1', + 'sconsign.1', + 'scons-time.1', + 'script/scons.bat', + #'script/scons-post-install.py', + 'setup.cfg', + 'setup.py', + ], + + 'filemap' : { + 'scons.1' : '$BUILDDIR/doc/man/scons.1', + 'sconsign.1' : '$BUILDDIR/doc/man/sconsign.1', + 'scons-time.1' : '$BUILDDIR/doc/man/scons-time.1', + }, + + 'buildermap' : { + 'scons.1' : env.SOElim, + 'sconsign.1' : env.SOElim, + 'scons-time.1' : env.SOElim, + }, + + 'subpkgs' : [ python_scons, scons_script ], + + 'subinst_dirs' : { + 'python-' + project : python_project_subinst_dir, + project + '-script' : project_script_subinst_dir, + }, +} + +scripts = ['scons', 'sconsign', 'scons-time'] + +src_deps = [] +src_files = [] + +for p in [ scons ]: + # + # Initialize variables with the right directories for this package. + # + pkg = p['pkg'] + pkg_version = "%s-%s" % (pkg, version) + + + src = 'src' + if p.has_key('src_subdir'): + src = os.path.join(src, p['src_subdir']) + + build = os.path.join(build_dir, pkg) + + tar_gz = os.path.join(build, 'dist', "%s.tar.gz" % pkg_version) + platform_tar_gz = os.path.join(build, + 'dist', + "%s.%s.tar.gz" % (pkg_version, platform)) + zip = os.path.join(build, 'dist', "%s.zip" % pkg_version) + platform_zip = os.path.join(build, + 'dist', + "%s.%s.zip" % (pkg_version, platform)) + if platform == "win-amd64": + win32_exe = os.path.join(build, 'dist', "%s.win-amd64.exe" % pkg_version) + else: + win32_exe = os.path.join(build, 'dist', "%s.win32.exe" % pkg_version) + + # + # Update the environment with the relevant information + # for this package. + # + # We can get away with calling setup.py using a directory path + # like this because we put a preamble in it that will chdir() + # to the directory in which setup.py exists. + # + setup_py = os.path.join(build, 'setup.py') + env.Replace(PKG = pkg, + PKG_VERSION = pkg_version, + SETUP_PY = '"%s"' % setup_py) + Local(setup_py) + + # + # Read up the list of source files from our MANIFEST.in. + # This list should *not* include LICENSE.txt, MANIFEST, + # README.txt, or setup.py. Make a copy of the list for the + # destination files. + # + manifest_in = File(os.path.join(src, 'MANIFEST.in')).rstr() + src_files = map(lambda x: x[:-1], + open(manifest_in).readlines()) + raw_files = src_files[:] + dst_files = src_files[:] + rpm_files = [] + + MANIFEST_in_list = [] + + if p.has_key('subpkgs'): + # + # This package includes some sub-packages. Read up their + # MANIFEST.in files, and add them to our source and destination + # file lists, modifying them as appropriate to add the + # specified subdirs. + # + for sp in p['subpkgs']: + ssubdir = sp['src_subdir'] + isubdir = p['subinst_dirs'][sp['pkg']] + MANIFEST_in = File(os.path.join(src, ssubdir, 'MANIFEST.in')).rstr() + MANIFEST_in_list.append(MANIFEST_in) + files = map(lambda x: x[:-1], open(MANIFEST_in).readlines()) + raw_files.extend(files) + src_files.extend(map(lambda x, s=ssubdir: os.path.join(s, x), files)) + for f in files: + r = os.path.join(sp['rpm_dir'], f) + rpm_files.append(r) + if f[-3:] == ".py": + rpm_files.append(r + 'c') + for f in sp.get('extra_rpm_files', []): + r = os.path.join(sp['rpm_dir'], f) + rpm_files.append(r) + files = map(lambda x, i=isubdir: os.path.join(i, x), files) + dst_files.extend(files) + for k, f in sp['filemap'].items(): + if f: + k = os.path.join(ssubdir, k) + p['filemap'][k] = os.path.join(ssubdir, f) + for f, deps in sp['explicit_deps'].items(): + f = os.path.join(build, ssubdir, f) + env.Depends(f, deps) + + # + # Now that we have the "normal" source files, add those files + # that are standard for each distribution. Note that we don't + # add these to dst_files, because they don't get installed. + # And we still have the MANIFEST to add. + # + src_files.extend(p['files']) + + # + # Now run everything in src_file through the sed command we + # concocted to expand SConstruct, 1.2.0.d20091224, etc. + # + for b in src_files: + s = p['filemap'].get(b, b) + if not s[0] == '$' and not os.path.isabs(s): + s = os.path.join(src, s) + builder = p['buildermap'].get(b, env.SCons_revision) + x = builder(os.path.join(build, b), s) + Local(x) + + # + # NOW, finally, we can create the MANIFEST, which we do + # by having Python spit out the contents of the src_files + # array we've carefully created. After we've added + # MANIFEST itself to the array, of course. + # + src_files.append("MANIFEST") + MANIFEST_in_list.append(os.path.join(src, 'MANIFEST.in')) + + def write_src_files(target, source, **kw): + global src_files + src_files.sort() + f = open(str(target[0]), 'wb') + for file in src_files: + f.write(file + "\n") + f.close() + return 0 + env.Command(os.path.join(build, 'MANIFEST'), + MANIFEST_in_list, + write_src_files) + + # + # Now go through and arrange to create whatever packages we can. + # + build_src_files = map(lambda x, b=build: os.path.join(b, x), src_files) + apply(Local, build_src_files, {}) + + distutils_formats = [] + + distutils_targets = [ win32_exe ] + + dist_distutils_targets = env.Install('$DISTDIR', distutils_targets) + Local(dist_distutils_targets) + AddPostAction(dist_distutils_targets, Chmod(dist_distutils_targets, 0644)) + + if not gzip: + print "gzip not found in %s; skipping .tar.gz package for %s." % (os.environ['PATH'], pkg) + else: + + distutils_formats.append('gztar') + + src_deps.append(tar_gz) + + distutils_targets.extend([ tar_gz, platform_tar_gz ]) + + dist_tar_gz = env.Install('$DISTDIR', tar_gz) + dist_platform_tar_gz = env.Install('$DISTDIR', platform_tar_gz) + Local(dist_tar_gz, dist_platform_tar_gz) + AddPostAction(dist_tar_gz, Chmod(dist_tar_gz, 0644)) + AddPostAction(dist_platform_tar_gz, Chmod(dist_platform_tar_gz, 0644)) + + # + # Unpack the tar.gz archive created by the distutils into + # build/unpack-tar-gz/scons-{version}. + # + # We'd like to replace the last three lines with the following: + # + # tar zxf $SOURCES -C $UNPACK_TAR_GZ_DIR + # + # but that gives heartburn to Cygwin's tar, so work around it + # with separate zcat-tar-rm commands. + # + unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, pv=pkg_version: + os.path.join(u, pv, x), + src_files) + env.Command(unpack_tar_gz_files, dist_tar_gz, [ + Delete(os.path.join(unpack_tar_gz_dir, pkg_version)), + "$ZCAT $SOURCES > .temp", + "tar xf .temp -C $UNPACK_TAR_GZ_DIR", + Delete(".temp"), + ]) + + # + # Run setup.py in the unpacked subdirectory to "install" everything + # into our build/test subdirectory. The runtest.py script will set + # PYTHONPATH so that the tests only look under build/test-{package}, + # and under etc (for the testing modules TestCmd.py, TestSCons.py, + # and unittest.py). This makes sure that our tests pass with what + # we really packaged, not because of something hanging around in + # the development directory. + # + # We can get away with calling setup.py using a directory path + # like this because we put a preamble in it that will chdir() + # to the directory in which setup.py exists. + # + dfiles = map(lambda x, d=test_tar_gz_dir: os.path.join(d, x), dst_files) + env.Command(dfiles, unpack_tar_gz_files, [ + Delete(os.path.join(unpack_tar_gz_dir, pkg_version, 'build')), + Delete("$TEST_TAR_GZ_DIR"), + '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_TAR_GZ_DIR" --standalone-lib' % \ + os.path.join(unpack_tar_gz_dir, pkg_version, 'setup.py'), + ]) + + # + # Generate portage files for submission to Gentoo Linux. + # + gentoo = os.path.join(build, 'gentoo') + ebuild = os.path.join(gentoo, 'scons-%s.ebuild' % version) + digest = os.path.join(gentoo, 'files', 'digest-scons-%s' % version) + env.Command(ebuild, os.path.join('gentoo', 'scons.ebuild.in'), SCons_revision) + def Digestify(target, source, env): + import md5 + def hexdigest(s): + """Return a signature as a string of hex characters. + """ + # NOTE: This routine is a method in the Python 2.0 interface + # of the native md5 module, but we want SCons to operate all + # the way back to at least Python 1.5.2, which doesn't have it. + h = string.hexdigits + r = '' + for c in s: + i = ord(c) + r = r + h[(i >> 4) & 0xF] + h[i & 0xF] + return r + src = source[0].rfile() + contents = open(str(src)).read() + sig = hexdigest(md5.new(contents).digest()) + bytes = os.stat(str(src))[6] + open(str(target[0]), 'w').write("MD5 %s %s %d\n" % (sig, + src.name, + bytes)) + env.Command(digest, tar_gz, Digestify) + + if not zipit: + print "zip not found; skipping .zip package for %s." % pkg + else: + + distutils_formats.append('zip') + + src_deps.append(zip) + + distutils_targets.extend([ zip, platform_zip ]) + + dist_zip = env.Install('$DISTDIR', zip) + dist_platform_zip = env.Install('$DISTDIR', platform_zip) + Local(dist_zip, dist_platform_zip) + AddPostAction(dist_zip, Chmod(dist_zip, 0644)) + AddPostAction(dist_platform_zip, Chmod(dist_platform_zip, 0644)) + + # + # Unpack the zip archive created by the distutils into + # build/unpack-zip/scons-{version}. + # + unpack_zip_files = map(lambda x, u=unpack_zip_dir, pv=pkg_version: + os.path.join(u, pv, x), + src_files) + + env.Command(unpack_zip_files, dist_zip, [ + Delete(os.path.join(unpack_zip_dir, pkg_version)), + unzipit, + ]) + + # + # Run setup.py in the unpacked subdirectory to "install" everything + # into our build/test subdirectory. The runtest.py script will set + # PYTHONPATH so that the tests only look under build/test-{package}, + # and under etc (for the testing modules TestCmd.py, TestSCons.py, + # and unittest.py). This makes sure that our tests pass with what + # we really packaged, not because of something hanging around in + # the development directory. + # + # We can get away with calling setup.py using a directory path + # like this because we put a preamble in it that will chdir() + # to the directory in which setup.py exists. + # + dfiles = map(lambda x, d=test_zip_dir: os.path.join(d, x), dst_files) + env.Command(dfiles, unpack_zip_files, [ + Delete(os.path.join(unpack_zip_dir, pkg_version, 'build')), + Delete("$TEST_ZIP_DIR"), + '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_ZIP_DIR" --standalone-lib' % \ + os.path.join(unpack_zip_dir, pkg_version, 'setup.py'), + ]) + + if not rpmbuild: + msg = "@echo \"Warning: Can not build 'rpm': no rpmbuild utility found\"" + AlwaysBuild(Alias('rpm', [], msg)) + else: + topdir = os.path.join(build, 'build', + 'bdist.' + platform, 'rpm') + + buildroot = os.path.join(build_dir, 'rpm-buildroot') + + BUILDdir = os.path.join(topdir, 'BUILD', pkg + '-' + version) + RPMSdir = os.path.join(topdir, 'RPMS', 'noarch') + SOURCESdir = os.path.join(topdir, 'SOURCES') + SPECSdir = os.path.join(topdir, 'SPECS') + SRPMSdir = os.path.join(topdir, 'SRPMS') + + specfile_in = os.path.join('rpm', "%s.spec.in" % pkg) + specfile = os.path.join(SPECSdir, "%s-1.spec" % pkg_version) + sourcefile = os.path.join(SOURCESdir, "%s.tar.gz" % pkg_version); + noarch_rpm = os.path.join(RPMSdir, "%s-1.noarch.rpm" % pkg_version) + src_rpm = os.path.join(SRPMSdir, "%s-1.src.rpm" % pkg_version) + + def spec_function(target, source, env): + """Generate the RPM .spec file from the template file. + + This fills in the %files portion of the .spec file with a + list generated from our MANIFEST(s), so we don't have to + maintain multiple lists. + """ + c = open(str(source[0]), 'rb').read() + c = string.replace(c, '__VERSION' + '__', env['VERSION']) + c = string.replace(c, '__RPM_FILES' + '__', env['RPM_FILES']) + open(str(target[0]), 'wb').write(c) + + rpm_files.sort() + rpm_files_str = string.join(rpm_files, "\n") + "\n" + rpm_spec_env = env.Clone(RPM_FILES = rpm_files_str) + rpm_spec_action = Action(spec_function, varlist=['RPM_FILES']) + rpm_spec_env.Command(specfile, specfile_in, rpm_spec_action) + + env.InstallAs(sourcefile, tar_gz) + Local(sourcefile) + + targets = [ noarch_rpm, src_rpm ] + cmd = "$RPMBUILD --define '_topdir $(%s$)' --buildroot %s -ba $SOURCES" % (topdir, buildroot) + if not os.path.isdir(BUILDdir): + cmd = ("$( mkdir -p %s; $)" % BUILDdir) + cmd + t = env.Command(targets, specfile, cmd) + env.Depends(t, sourcefile) + + dist_noarch_rpm = env.Install('$DISTDIR', noarch_rpm) + dist_src_rpm = env.Install('$DISTDIR', src_rpm) + Local(dist_noarch_rpm, dist_src_rpm) + AddPostAction(dist_noarch_rpm, Chmod(dist_noarch_rpm, 0644)) + AddPostAction(dist_src_rpm, Chmod(dist_src_rpm, 0644)) + + dfiles = map(lambda x, d=test_rpm_dir: os.path.join(d, 'usr', x), + dst_files) + env.Command(dfiles, + dist_noarch_rpm, + "$RPM2CPIO $SOURCES | (cd $TEST_RPM_DIR && cpio -id)") + + if dh_builddeb and fakeroot: + # Our Debian packaging builds directly into build/dist, + # so we don't need to Install() the .debs. + deb = os.path.join(build_dir, 'dist', "%s_%s-1_all.deb" % (pkg, version)) + for d in p['debian_deps']: + b = env.SCons_revision(os.path.join(build, d), d) + env.Depends(deb, b) + Local(b) + env.Command(deb, build_src_files, [ + "cd %s && fakeroot make -f debian/rules PYTHON=$PYTHON BUILDDEB_OPTIONS=--destdir=../../build/dist binary" % build, + ]) + + old = os.path.join('lib', 'scons', '') + new = os.path.join('lib', 'python' + python_ver, 'site-packages', '') + def xxx(s, old=old, new=new): + if s[:len(old)] == old: + s = new + s[len(old):] + return os.path.join('usr', s) + dfiles = map(lambda x, t=test_deb_dir: os.path.join(t, x), + map(xxx, dst_files)) + env.Command(dfiles, + deb, + "dpkg --fsys-tarfile $SOURCES | (cd $TEST_DEB_DIR && tar -xf -)") + + + # + # Use the Python distutils to generate the appropriate packages. + # + commands = [ + Delete(os.path.join(build, 'build', 'lib')), + Delete(os.path.join(build, 'build', 'scripts')), + ] + + if distutils_formats: + commands.append(Delete(os.path.join(build, + 'build', + 'bdist.' + platform, + 'dumb'))) + for format in distutils_formats: + commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_dumb -f %s" % format) + + commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY sdist --formats=%s" % \ + string.join(distutils_formats, ',')) + + commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_wininst") + + env.Command(distutils_targets, build_src_files, commands) + + # + # Now create local packages for people who want to let people + # build their SCons-buildable packages without having to + # install SCons. + # + s_l_v = '%s-local-%s' % (pkg, version) + + local = pkg + '-local' + build_dir_local = os.path.join(build_dir, local) + build_dir_local_slv = os.path.join(build_dir, local, s_l_v) + + dist_local_tar_gz = os.path.join("$DISTDIR/%s.tar.gz" % s_l_v) + dist_local_zip = os.path.join("$DISTDIR/%s.zip" % s_l_v) + AddPostAction(dist_local_tar_gz, Chmod(dist_local_tar_gz, 0644)) + AddPostAction(dist_local_zip, Chmod(dist_local_zip, 0644)) + + commands = [ + Delete(build_dir_local), + '$PYTHON $PYTHONFLAGS $SETUP_PY install "--install-script=%s" "--install-lib=%s" --no-install-man --no-compile --standalone-lib --no-version-script' % \ + (build_dir_local, build_dir_local_slv), + ] + + for script in scripts: + #commands.append("mv %s/%s %s/%s.py" % (local, script, local, script)) + local_script = os.path.join(build_dir_local, script) + commands.append(Move(local_script + '.py', local_script)) + + rf = filter(lambda x: not x in scripts, raw_files) + rf = map(lambda x, slv=s_l_v: os.path.join(slv, x), rf) + for script in scripts: + rf.append("%s.py" % script) + local_targets = map(lambda x, s=build_dir_local: os.path.join(s, x), rf) + + env.Command(local_targets, build_src_files, commands) + + scons_LICENSE = os.path.join(build_dir_local, 'scons-LICENSE') + l = env.SCons_revision(scons_LICENSE, 'LICENSE-local') + local_targets.append(l) + Local(l) + + scons_README = os.path.join(build_dir_local, 'scons-README') + l = env.SCons_revision(scons_README, 'README-local') + local_targets.append(l) + Local(l) + + if gzip: + env.Command(dist_local_tar_gz, + local_targets, + "cd %s && tar czf $( ${TARGET.abspath} $) *" % build_dir_local) + + unpack_targets = map(lambda x, d=test_local_tar_gz_dir: + os.path.join(d, x), + rf) + commands = [Delete(test_local_tar_gz_dir), + Mkdir(test_local_tar_gz_dir), + "cd %s && tar xzf $( ${SOURCE.abspath} $)" % test_local_tar_gz_dir] + + env.Command(unpack_targets, dist_local_tar_gz, commands) + + if zipit: + env.Command(dist_local_zip, local_targets, zipit, + CD = build_dir_local, PSV = '.') + + unpack_targets = map(lambda x, d=test_local_zip_dir: + os.path.join(d, x), + rf) + commands = [Delete(test_local_zip_dir), + Mkdir(test_local_zip_dir), + unzipit] + + env.Command(unpack_targets, dist_local_zip, unzipit, + UNPACK_ZIP_DIR = test_local_zip_dir) + +# +# +# +Export('build_dir', 'env') + +SConscript('QMTest/SConscript') + +# +# +# +files = [ + 'runtest.py', +] + +def copy(target, source, env): + t = str(target[0]) + s = str(source[0]) + open(t, 'wb').write(open(s, 'rb').read()) + +for file in files: + # Guarantee that real copies of these files always exist in + # build/. If there's a symlink there, then this is an Aegis + # build and we blow them away now so that they'll get "built" later. + p = os.path.join(build_dir, file) + if os.path.islink(p): + os.unlink(p) + if not os.path.isabs(p): + p = '#' + p + sp = env.Command(p, file, copy) + Local(sp) + +# +# Documentation. +# +Export('build_dir', 'env', 'whereis') + +SConscript('doc/SConscript') + +# +# If we're running in a Subversion working directory, pack up a complete +# source archive from the project files and files in the change. +# + +sfiles = None +if hg_status_lines: + slines = filter(lambda l: l[0] in 'ACM', hg_status_lines) + sfiles = map(lambda l: l.split()[-1], slines) +elif svn_status_lines: + slines = filter(lambda l: l[0] in ' MA', svn_status_lines) + sentries = map(lambda l: l.split()[-1], slines) + sfiles = filter(os.path.isfile, sentries) +else: + "Not building in a Mercurial or Subversion tree; skipping building src package." + +if sfiles: + remove_patterns = [ + '.hgt/*', + '.svnt/*', + '*.aeignore', + '*.cvsignore', + '*.hgignore', + 'www/*', + ] + + for p in remove_patterns: + sfiles = filter(lambda s, p=p: not fnmatch.fnmatch(s, p), sfiles) + + if sfiles: + ps = "%s-src" % project + psv = "%s-%s" % (ps, version) + b_ps = os.path.join(build_dir, ps) + b_psv = os.path.join(build_dir, psv) + b_psv_stamp = b_psv + '-stamp' + + src_tar_gz = os.path.join(build_dir, 'dist', '%s.tar.gz' % psv) + src_zip = os.path.join(build_dir, 'dist', '%s.zip' % psv) + + Local(src_tar_gz, src_zip) + + for file in sfiles: + env.SCons_revision(os.path.join(b_ps, file), file) + + b_ps_files = map(lambda x, d=b_ps: os.path.join(d, x), sfiles) + cmds = [ + Delete(b_psv), + Copy(b_psv, b_ps), + Touch("$TARGET"), + ] + + env.Command(b_psv_stamp, src_deps + b_ps_files, cmds) + + apply(Local, b_ps_files, {}) + + if gzip: + + env.Command(src_tar_gz, b_psv_stamp, + "tar cz${TAR_HFLAG} -f $TARGET -C build %s" % psv) + + # + # Unpack the archive into build/unpack/scons-{version}. + # + unpack_tar_gz_files = map(lambda x, u=unpack_tar_gz_dir, psv=psv: + os.path.join(u, psv, x), + sfiles) + + # + # We'd like to replace the last three lines with the following: + # + # tar zxf $SOURCES -C $UNPACK_TAR_GZ_DIR + # + # but that gives heartburn to Cygwin's tar, so work around it + # with separate zcat-tar-rm commands. + env.Command(unpack_tar_gz_files, src_tar_gz, [ + Delete(os.path.join(unpack_tar_gz_dir, psv)), + "$ZCAT $SOURCES > .temp", + "tar xf .temp -C $UNPACK_TAR_GZ_DIR", + Delete(".temp"), + ]) + + # + # Run setup.py in the unpacked subdirectory to "install" everything + # into our build/test subdirectory. The runtest.py script will set + # PYTHONPATH so that the tests only look under build/test-{package}, + # and under etc (for the testing modules TestCmd.py, TestSCons.py, + # and unittest.py). This makes sure that our tests pass with what + # we really packaged, not because of something hanging around in + # the development directory. + # + # We can get away with calling setup.py using a directory path + # like this because we put a preamble in it that will chdir() + # to the directory in which setup.py exists. + # + dfiles = map(lambda x, d=test_src_tar_gz_dir: os.path.join(d, x), + dst_files) + scons_lib_dir = os.path.join(unpack_tar_gz_dir, psv, 'src', 'engine') + ENV = env.Dictionary('ENV').copy() + ENV['SCONS_LIB_DIR'] = scons_lib_dir + ENV['USERNAME'] = developer + env.Command(dfiles, unpack_tar_gz_files, + [ + Delete(os.path.join(unpack_tar_gz_dir, + psv, + 'build', + 'scons', + 'build')), + Delete("$TEST_SRC_TAR_GZ_DIR"), + 'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s" VERSION="$VERSION"' % \ + (os.path.join(unpack_tar_gz_dir, psv), + os.path.join('src', 'script', 'scons.py'), + os.path.join('build', 'scons')), + '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_SRC_TAR_GZ_DIR" --standalone-lib' % \ + os.path.join(unpack_tar_gz_dir, + psv, + 'build', + 'scons', + 'setup.py'), + ], + ENV = ENV) + + if zipit: + + env.Command(src_zip, b_psv_stamp, zipit, CD = 'build', PSV = psv) + + # + # Unpack the archive into build/unpack/scons-{version}. + # + unpack_zip_files = map(lambda x, u=unpack_zip_dir, psv=psv: + os.path.join(u, psv, x), + sfiles) + + env.Command(unpack_zip_files, src_zip, [ + Delete(os.path.join(unpack_zip_dir, psv)), + unzipit + ]) + + # + # Run setup.py in the unpacked subdirectory to "install" everything + # into our build/test subdirectory. The runtest.py script will set + # PYTHONPATH so that the tests only look under build/test-{package}, + # and under etc (for the testing modules TestCmd.py, TestSCons.py, + # and unittest.py). This makes sure that our tests pass with what + # we really packaged, not because of something hanging around in + # the development directory. + # + # We can get away with calling setup.py using a directory path + # like this because we put a preamble in it that will chdir() + # to the directory in which setup.py exists. + # + dfiles = map(lambda x, d=test_src_zip_dir: os.path.join(d, x), + dst_files) + scons_lib_dir = os.path.join(unpack_zip_dir, psv, 'src', 'engine') + ENV = env.Dictionary('ENV').copy() + ENV['SCONS_LIB_DIR'] = scons_lib_dir + ENV['USERNAME'] = developer + env.Command(dfiles, unpack_zip_files, + [ + Delete(os.path.join(unpack_zip_dir, + psv, + 'build', + 'scons', + 'build')), + Delete("$TEST_SRC_ZIP_DIR"), + 'cd "%s" && $PYTHON $PYTHONFLAGS "%s" "%s" VERSION="$VERSION"' % \ + (os.path.join(unpack_zip_dir, psv), + os.path.join('src', 'script', 'scons.py'), + os.path.join('build', 'scons')), + '$PYTHON $PYTHONFLAGS "%s" install "--prefix=$TEST_SRC_ZIP_DIR" --standalone-lib' % \ + os.path.join(unpack_zip_dir, + psv, + 'build', + 'scons', + 'setup.py'), + ], + ENV = ENV) + +for pf, help_text in packaging_flavors: + Alias(pf, [ + os.path.join(build_dir, 'test-'+pf), + os.path.join(build_dir, 'QMTest'), + os.path.join(build_dir, 'runtest.py'), + ]) diff --git a/bin/Command.py b/bin/Command.py new file mode 100644 index 0000000..efaa356 --- /dev/null +++ b/bin/Command.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python +# +# XXX Python script template +# +# XXX Describe what the script does here. +# + +import getopt +import os +import shlex +import sys + +class Usage(Exception): + def __init__(self, msg): + self.msg = msg + +class CommandRunner: + """ + Representation of a command to be executed. + """ + + def __init__(self, dictionary={}): + self.subst_dictionary(dictionary) + + def subst_dictionary(self, dictionary): + self._subst_dictionary = dictionary + + def subst(self, string, dictionary=None): + """ + Substitutes (via the format operator) the values in the specified + dictionary into the specified command. + + The command can be an (action, string) tuple. In all cases, we + perform substitution on strings and don't worry if something isn't + a string. (It's probably a Python function to be executed.) + """ + if dictionary is None: + dictionary = self._subst_dictionary + if dictionary: + try: + string = string % dictionary + except TypeError: + pass + return string + + def do_display(self, string): + if type(string) == type(()): + func = string[0] + args = string[1:] + s = '%s(%s)' % (func.__name__, ', '.join(map(repr, args))) + else: + s = self.subst(string) + if not s.endswith('\n'): + s += '\n' + sys.stdout.write(s) + sys.stdout.flush() + + def do_not_display(self, string): + pass + + def do_execute(self, command): + if type(command) == type(''): + command = self.subst(command) + cmdargs = shlex.split(command) + if cmdargs[0] == 'cd': + command = (os.chdir,) + tuple(cmdargs[1:]) + elif cmdargs[0] == 'mkdir': + command = (os.mkdir,) + tuple(cmdargs[1:]) + if type(command) == type(()): + func = command[0] + args = command[1:] + return func(*args) + else: + return os.system(command) + + def do_not_execute(self, command): + pass + + display = do_display + execute = do_execute + + def run(self, command, display=None): + """ + Runs this command, displaying it first. + + The actual display() and execute() methods we call may be + overridden if we're printing but not executing, or vice versa. + """ + if display is None: + display = command + self.display(display) + return self.execute(command) + +def main(argv=None): + if argv is None: + argv = sys.argv + + short_options = 'hnq' + long_options = ['help', 'no-exec', 'quiet'] + + helpstr = """\ +Usage: script-template.py [-hnq] + + -h, --help Print this help and exit + -n, --no-exec No execute, just print command lines + -q, --quiet Quiet, don't print command lines +""" + + try: + try: + opts, args = getopt.getopt(argv[1:], short_options, long_options) + except getopt.error, msg: + raise Usage(msg) + + for o, a in opts: + if o in ('-h', '--help'): + print helpstr + sys.exit(0) + elif o in ('-n', '--no-exec'): + Command.execute = Command.do_not_execute + elif o in ('-q', '--quiet'): + Command.display = Command.do_not_display + except Usage, err: + sys.stderr.write(err.msg) + sys.stderr.write('use -h to get help') + return 2 + + commands = [ + ] + + for command in [ Command(c) for c in commands ]: + status = command.run(command) + if status: + sys.exit(status) + +if __name__ == "__main__": + sys.exit(main()) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/SConsDoc.py b/bin/SConsDoc.py new file mode 100644 index 0000000..d3a043b --- /dev/null +++ b/bin/SConsDoc.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# +# Module for handling SCons documentation processing. +# + +__doc__ = """ +This module parses home-brew XML files that document various things +in SCons. Right now, it handles Builders, construction variables, +and Tools, but we expect it to get extended in the future. + +In general, you can use any DocBook tag in the input, and this module +just adds processing various home-brew tags to try to make life a +little easier. + +Builder example: + + + + This is the summary description of an SCons Tool. + It will get placed in the man page, + and in the appropriate User's Guide appendix. + The name of any builder may be interpolated + anywhere in the document by specifying the + &b-VARIABLE; + element. It need not be on a line by itself. + + Unlike normal XML, blank lines are significant in these + descriptions and serve to separate paragraphs. + They'll get replaced in DocBook output with appropriate tags + to indicate a new paragraph. + + + print "this is example code, it will be offset and indented" + + + + +Construction variable example: + + + + This is the summary description of a construction variable. + It will get placed in the man page, + and in the appropriate User's Guide appendix. + The name of any construction variable may be interpolated + anywhere in the document by specifying the + &t-VARIABLE; + element. It need not be on a line by itself. + + Unlike normal XML, blank lines are significant in these + descriptions and serve to separate paragraphs. + They'll get replaced in DocBook output with appropriate tags + to indicate a new paragraph. + + + print "this is example code, it will be offset and indented" + + + + +Tool example: + + + + This is the summary description of an SCons Tool. + It will get placed in the man page, + and in the appropriate User's Guide appendix. + The name of any tool may be interpolated + anywhere in the document by specifying the + &t-VARIABLE; + element. It need not be on a line by itself. + + Unlike normal XML, blank lines are significant in these + descriptions and serve to separate paragraphs. + They'll get replaced in DocBook output with appropriate tags + to indicate a new paragraph. + + + print "this is example code, it will be offset and indented" + + + +""" + +import os.path +import imp +import sys +import xml.sax.handler + +class Item: + def __init__(self, name): + self.name = name + self.sort_name = name.lower() + if self.sort_name[0] == '_': + self.sort_name = self.sort_name[1:] + self.summary = [] + self.sets = None + self.uses = None + def cmp_name(self, name): + if name[0] == '_': + name = name[1:] + return name.lower() + def __cmp__(self, other): + return cmp(self.sort_name, other.sort_name) + +class Builder(Item): + pass + +class Tool(Item): + def __init__(self, name): + Item.__init__(self, name) + self.entity = self.name.replace('+', 'X') + +class ConstructionVariable(Item): + pass + +class Chunk: + def __init__(self, tag, body=None): + self.tag = tag + if not body: + body = [] + self.body = body + def __str__(self): + body = ''.join(self.body) + return "<%s>%s\n" % (self.tag, body, self.tag) + def append(self, data): + self.body.append(data) + +class Summary: + def __init__(self): + self.body = [] + self.collect = [] + def append(self, data): + self.collect.append(data) + def end_para(self): + text = ''.join(self.collect) + paras = text.split('\n\n') + if paras == ['\n']: + return + if paras[0] == '': + self.body.append('\n') + paras = paras[1:] + paras[0] = '\n' + paras[0] + if paras[-1] == '': + paras = paras[:-1] + paras[-1] = paras[-1] + '\n' + last = '\n' + else: + last = None + sep = None + for p in paras: + c = Chunk("para", p) + if sep: + self.body.append(sep) + self.body.append(c) + sep = '\n' + if last: + self.body.append(last) + def begin_chunk(self, chunk): + self.end_para() + self.collect = chunk + def end_chunk(self): + self.body.append(self.collect) + self.collect = [] + +class SConsDocHandler(xml.sax.handler.ContentHandler, + xml.sax.handler.ErrorHandler): + def __init__(self): + self._start_dispatch = {} + self._end_dispatch = {} + keys = self.__class__.__dict__.keys() + start_tag_method_names = filter(lambda k: k[:6] == 'start_', keys) + end_tag_method_names = filter(lambda k: k[:4] == 'end_', keys) + for method_name in start_tag_method_names: + tag = method_name[6:] + self._start_dispatch[tag] = getattr(self, method_name) + for method_name in end_tag_method_names: + tag = method_name[4:] + self._end_dispatch[tag] = getattr(self, method_name) + self.stack = [] + self.collect = [] + self.current_object = [] + self.builders = {} + self.tools = {} + self.cvars = {} + + def startElement(self, name, attrs): + try: + start_element_method = self._start_dispatch[name] + except KeyError: + self.characters('<%s>' % name) + else: + start_element_method(attrs) + + def endElement(self, name): + try: + end_element_method = self._end_dispatch[name] + except KeyError: + self.characters('' % name) + else: + end_element_method() + + # + # + def characters(self, chars): + self.collect.append(chars) + + def begin_collecting(self, chunk): + self.collect = chunk + def end_collecting(self): + self.collect = [] + + def begin_chunk(self): + pass + def end_chunk(self): + pass + + # + # + # + + def begin_xxx(self, obj): + self.stack.append(self.current_object) + self.current_object = obj + def end_xxx(self): + self.current_object = self.stack.pop() + + # + # + # + def start_scons_doc(self, attrs): + pass + def end_scons_doc(self): + pass + + def start_builder(self, attrs): + name = attrs.get('name') + try: + builder = self.builders[name] + except KeyError: + builder = Builder(name) + self.builders[name] = builder + self.begin_xxx(builder) + def end_builder(self): + self.end_xxx() + + def start_tool(self, attrs): + name = attrs.get('name') + try: + tool = self.tools[name] + except KeyError: + tool = Tool(name) + self.tools[name] = tool + self.begin_xxx(tool) + def end_tool(self): + self.end_xxx() + + def start_cvar(self, attrs): + name = attrs.get('name') + try: + cvar = self.cvars[name] + except KeyError: + cvar = ConstructionVariable(name) + self.cvars[name] = cvar + self.begin_xxx(cvar) + def end_cvar(self): + self.end_xxx() + + def start_summary(self, attrs): + summary = Summary() + self.current_object.summary = summary + self.begin_xxx(summary) + self.begin_collecting(summary) + def end_summary(self): + self.current_object.end_para() + self.end_xxx() + + def start_example(self, attrs): + example = Chunk("programlisting") + self.current_object.begin_chunk(example) + def end_example(self): + self.current_object.end_chunk() + + def start_uses(self, attrs): + self.begin_collecting([]) + def end_uses(self): + self.current_object.uses = ''.join(self.collect).split() + self.current_object.uses.sort() + self.end_collecting() + + def start_sets(self, attrs): + self.begin_collecting([]) + def end_sets(self): + self.current_object.sets = ''.join(self.collect).split() + self.current_object.sets.sort() + self.end_collecting() + + # Stuff for the ErrorHandler portion. + def error(self, exception): + linenum = exception._linenum - self.preamble_lines + sys.stderr.write('%s:%d:%d: %s (error)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args))) + + def fatalError(self, exception): + linenum = exception._linenum - self.preamble_lines + sys.stderr.write('%s:%d:%d: %s (fatalError)\n' % (self.filename, linenum, exception._colnum, ''.join(exception.args))) + + def set_file_info(self, filename, preamble_lines): + self.filename = filename + self.preamble_lines = preamble_lines + +# lifted from Ka-Ping Yee's way cool pydoc module. +def importfile(path): + """Import a Python source file or compiled file given its path.""" + magic = imp.get_magic() + file = open(path, 'r') + if file.read(len(magic)) == magic: + kind = imp.PY_COMPILED + else: + kind = imp.PY_SOURCE + file.close() + filename = os.path.basename(path) + name, ext = os.path.splitext(filename) + file = open(path, 'r') + try: + module = imp.load_module(name, file, path, (ext, 'r', kind)) + except ImportError, e: + sys.stderr.write("Could not import %s: %s\n" % (path, e)) + return None + file.close() + return module + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/ae-cvs-ci b/bin/ae-cvs-ci new file mode 100755 index 0000000..47c8073 --- /dev/null +++ b/bin/ae-cvs-ci @@ -0,0 +1,204 @@ +# +# aegis - project change supervisor +# Copyright (C) 2004 Peter Miller; +# All rights reserved. +# +# As a specific exception to the GPL, you are allowed to copy +# this source file into your own project and modify it, without +# releasing your project under the GPL, unless there is some other +# file or condition which would require it. +# +# MANIFEST: shell script to commit changes to CVS +# +# It is assumed that your CVSROOT and CVS_RSH environment variables have +# already been set appropriately. +# +# This script is expected to be run as by integrate_pass_notify_command +# and as such the baseline has already assumed the shape asked for by +# the change. +# +# integrate_pass_notify_command = +# "$bin/ae-cvs-ci $project $change"; +# +# Alternatively, you may wish to tailor this script to the individual +# needs of your project. Make it a source file, e.g. "etc/ae-cvs-ci.sh" +# and then use the following: +# +# integrate_pass_notify_command = +# "$sh ${s etc/ae-cvs-ci} $project $change"; +# + +USAGE="Usage: $0 " + +PRINT="echo" +EXECUTE="eval" + +while getopts "hnq" FLAG +do + case ${FLAG} in + h ) + echo "${USAGE}" + exit 0 + ;; + n ) + EXECUTE=":" + ;; + q ) + PRINT=":" + ;; + * ) + echo "$0: unknown option ${FLAG}" >&2 + exit 1 + ;; + esac +done + +shift `expr ${OPTIND} - 1` + +case $# in +2) + project=$1 + change=$2 + ;; +*) + echo "${USAGE}" 1>&2 + exit 1 + ;; +esac + +here=`pwd` + +AEGIS_PROJECT=$project +export AEGIS_PROJECT +AEGIS_CHANGE=$change +export AEGIS_CHANGE + +module=`echo $project | sed 's|[.].*||'` + +baseline=`aegis -cd -bl` + +if test X${TMPDIR} = X; then TMPDIR=/var/tmp; fi + +TMP=${TMPDIR}/ae-cvs-ci.$$ +mkdir ${TMP} +cd ${TMP} + +PWD=`pwd` +if test X${PWD} != X${TMP}; then + echo "$0: ended up in ${PWD}, not ${TMP}" >&2 + exit 1 +fi + +fail() +{ + set +x + cd $here + rm -rf ${TMP} + echo "FAILED" 1>&2 + exit 1 +} +trap "fail" 1 2 3 15 + +Command() +{ + ${PRINT} "$*" + ${EXECUTE} "$*" +} + +# +# Create a new CVS work area. +# +# Note: this assumes the module is checked-out into a directory of the +# same name. Is there a way to ask CVS where is is going to put a +# modules, so we can always get the "cd" right? +# +${PRINT} cvs co $module +${EXECUTE} cvs co $module > LOG 2>&1 +if test $? -ne 0; then cat LOG; fail; fi +${EXECUTE} cd $module + +# +# Now we need to extract the sources from Aegis and drop them into the +# CVS work area. There are two ways to do this. +# +# The first way is to use the generated tarball. +# This has the advantage that it has the Makefile.in file in it, and +# will work immediately. +# +# The second way is to use aetar, which will give exact sources, and +# omit all derived files. This will *not* include the Makefile.in, +# and so will not be readily compilable. +# +# gunzip < $baseline/export/${project}.tar.gz | tardy -rp ${project} | tar xf - +aetar -send -comp-alg=gzip -o - | tar xzf - + +# +# If any new directories have been created we will need to add them +# to CVS before we can add the new files which we know are in them, +# or they would not have been created. Do this only if the -n option +# isn't used, because if it is, we won't have actually checked out the +# source and we'd erroneously report that all of them need to be added. +# +if test "X${EXECUTE}" != "X:" +then + find . \( -name CVS -o -name Attic \) -prune -o -type d -print | + xargs --max-args=1 | + while read dir + do + if [ ! -d "$dir/CVS" ] + then + Command cvs add "$dir" + fi + done +fi + +# +# Use the Aegis meta-data to perform some CVS commands that CVS can't +# figure out for itself. +# +aegis -l cf -unf | sed 's| -> [0-9][0-9.]*||' | +while read usage action rev filename +do + if test "x$filename" = "x" + then + filename="$rev" + fi + case $action in + create) + Command cvs add $filename + ;; + remove) + Command rm -f $filename + Command cvs remove $filename + ;; + *) + ;; + esac +done + +# +# Extract the brief description. We'd like to do this using aesub +# or something, like so: +# +# message=`aesub '${version} - ${change description}'` +# +# but the expansion of ${change description} has a lame hard-coded max of +# 80 characters, so we have to do this by hand. (This has the slight +# benefit of preserving backslashes in front of any double-quotes in +# the text; that will have to be handled if we go back to using aesub.) +# +description=`aegis -ca -l | sed -n 's/brief_description = "\(.*\)";$/\1/p'` +version=`aesub '${version}'` +message="$version - $description" + +# +# Now commit all the changes. +# +Command cvs -q commit -m \"$message\" + +# +# All done. Clean up and go home. +# +cd $here +rm -rf ${TMP} +exit 0 diff --git a/bin/ae-svn-ci b/bin/ae-svn-ci new file mode 100755 index 0000000..301d890 --- /dev/null +++ b/bin/ae-svn-ci @@ -0,0 +1,240 @@ +# +# aegis - project change supervisor +# Copyright (C) 2004 Peter Miller; +# All rights reserved. +# +# As a specific exception to the GPL, you are allowed to copy +# this source file into your own project and modify it, without +# releasing your project under the GPL, unless there is some other +# file or condition which would require it. +# +# MANIFEST: shell script to commit changes to Subversion +# +# This script is expected to be run by the integrate_pass_notify_command +# and as such the baseline has already assumed the shape asked for by +# the change. +# +# integrate_pass_notify_command = +# "$bin/ae-svn-ci $project $change http://svn.site.com/svn/trunk --username svn_user"; +# +# Alternatively, you may wish to tailor this script to the individual +# needs of your project. Make it a source file, e.g. "etc/ae-svn-ci.sh" +# and then use the following: +# +# integrate_pass_notify_command = +# "$sh ${s etc/ae-svn-ci} $project $change http://svn.site.com/svn/trunk --username svn_user"; +# + +USAGE="Usage: $0 [-hnq] []" + +PRINT="echo" +EXECUTE="eval" + +while getopts "hnq" FLAG +do + case ${FLAG} in + h ) + echo "${USAGE}" + exit 0 + ;; + n ) + EXECUTE=":" + ;; + q ) + PRINT=":" + ;; + * ) + echo "$0: unknown option ${FLAG}" >&2 + exit 1 + ;; + esac +done + +shift `expr ${OPTIND} - 1` + +case $# in +[012]) + echo "${USAGE}" 1>&2 + exit 1 + ;; +*) + project=$1 + change=$2 + svn_url=$3 + shift 3 + svn_co_flags=$* + ;; +esac + +here=`pwd` + +AEGIS_PROJECT=$project +export AEGIS_PROJECT +AEGIS_CHANGE=$change +export AEGIS_CHANGE + +module=`echo $project | sed 's|[.].*||'` + +baseline=`aegis -cd -bl` + +if test X${TMPDIR} = X; then TMPDIR=/var/tmp; fi + +TMP=${TMPDIR}/ae-svn-ci.$$ +mkdir ${TMP} +cd ${TMP} + +PWD=`pwd` +if test X${PWD} != X${TMP}; then + echo "$0: ended up in ${PWD}, not ${TMP}" >&2 + exit 1 +fi + +fail() +{ + set +x + cd $here + rm -rf ${TMP} + echo "FAILED" 1>&2 + exit 1 +} +trap "fail" 1 2 3 15 + +Command() +{ + ${PRINT} "$*" + ${EXECUTE} "$*" +} + +# +# Create a new Subversion work area. +# +# Note: this assumes the module is checked-out into a directory of the +# same name. Is there a way to ask Subversion where it is going to put a +# module, so we can always get the "cd" right? +# +${PRINT} svn co $svn_url $module $svn_co_flags +${EXECUTE} svn co $svn_url $module $svn_co_flags > LOG 2>&1 +if test $? -ne 0; then cat LOG; fail; fi +${EXECUTE} cd $module + +# +# Now we need to extract the sources from Aegis and drop them into the +# Subversion work area. There are two ways to do this. +# +# The first way is to use the generated tarball. +# This has the advantage that it has the Makefile.in file in it, and +# will work immediately. +# +# The second way is to use aetar, which will give exact sources, and +# omit all derived files. This will *not* include the Makefile.in, +# and so will not be readily compilable. +# +# gunzip < $baseline/export/${project}.tar.gz | tardy -rp ${project} | tar xf - +aetar -send -comp-alg=gzip -o - | tar xzf - + +# +# If any new directories have been created we will need to add them +# to Subversion before we can add the new files which we know are in them, +# or they would not have been created. Do this only if the -n option +# isn't used, because if it is, we won't have actually checked out the +# source and we'd erroneously report that all of them need to be added. +# +if test "X${EXECUTE}" != "X:" +then + find . -name .svn -prune -o -type d -print | + xargs --max-args=1 | + while read dir + do + if [ ! -d "$dir/.svn" ] + then + Command svn add -N "$dir" + fi + done +fi + +# +# Use the Aegis meta-data to perform some commands that Subversion can't +# figure out for itself. We use an inline "aer" report script to identify +# when a remove-create pair are actually due to a move. +# +aegis -rpt -nph -f - <<_EOF_ | +auto cs; +cs = project[project_name()].state.branch.change[change_number()]; + +columns({width = 1000;}); + +auto file, moved; +for (file in cs.src) +{ + if (file.move != "") + moved[file.move] = 1; +} + +auto action; +for (file in cs.src) +{ + if (file.action == "remove" && file.move != "") + action = "move"; + else + action = file.action; + /* + * Suppress printing of any files created as the result of a move. + * These are printed as the destination when printing the line for + * the file that was *removed* as a result of the move. + */ + if (action != "create" || ! moved[file.file_name]) + print(sprintf("%s %s \\"%s\\" \\"%s\\"", file.usage, action, file.file_name, file.move)); +} +_EOF_ +while read line +do + eval set -- "$line" + usage="$1" + action="$2" + srcfile="$3" + dstfile="$4" + case $action in + create) + Command svn add $srcfile + ;; + remove) + Command rm -f $srcfile + Command svn remove $srcfile + ;; + move) + Command mv $dstfile $dstfile.move + Command svn move $srcfile $dstfile + Command cp $dstfile.move $dstfile + Command rm -f $dstfile.move + ;; + *) + ;; + esac +done + +# +# Extract the brief description. We'd like to do this using aesub +# or something, like so: +# +# message=`aesub '${version} - ${change description}'` +# +# but the expansion of ${change description} has a lame hard-coded max of +# 80 characters, so we have to do this by hand. (This has the slight +# benefit of preserving backslashes in front of any double-quotes in +# the text; that will have to be handled if we go back to using aesub.) +# +description=`aegis -ca -l | sed -n 's/brief_description = "\(.*\)";$/\1/p'` +version=`aesub '${version}'` +message="$version - $description" + +# +# Now commit all the changes. +# +Command svn commit -m \"$message\" + +# +# All done. Clean up and go home. +# +cd $here +rm -rf ${TMP} +exit 0 diff --git a/bin/ae2cvs b/bin/ae2cvs new file mode 100644 index 0000000..e7cb22b --- /dev/null +++ b/bin/ae2cvs @@ -0,0 +1,579 @@ +#! /usr/bin/env perl + +$revision = "src/ae2cvs.pl 0.04.D001 2005/08/14 15:13:36 knight"; + +$copyright = "Copyright 2001, 2002, 2003, 2004, 2005 Steven Knight."; + +# +# All rights reserved. This program is free software; you can +# redistribute and/or modify under the same terms as Perl itself. +# + +use strict; +use File::Find; +use File::Spec; +use Pod::Usage (); + +use vars qw( @add_list @args @cleanup @copy_list @libraries + @mkdir_list @remove_list + %seen_dir + $ae_copy $aedir $aedist + $cnum $comment $commit $common $copyright + $cvs_command $cvsmod $cvsroot + $delta $description $exec $help $indent $infile + $proj $pwd $quiet $revision + $summary $usedir $usepath ); + +$aedist = 1; +$cvsroot = undef; +$exec = undef; +$indent = ""; + +sub version { + print "ae2cvs: $revision\n"; + print "$copyright\n"; + exit 0; +} + +{ + use Getopt::Long; + + Getopt::Long::Configure('no_ignore_case'); + + my $ret = GetOptions ( + "aedist" => sub { $aedist = 1 }, + "aegis" => sub { $aedist = 0 }, + "change=i" => \$cnum, + "d=s" => \$cvsroot, + "file=s" => \$infile, + "help|?" => \$help, + "library=s" => \@libraries, + "module=s" => \$cvsmod, + "noexecute" => sub { $exec = 0 }, + "project=s" => \$proj, + "quiet" => \$quiet, + "usedir=s" => \$usedir, + "v|version" => \&version, + "x|execute" => sub { $exec++ if ! defined $exec || $exec != 0 }, + "X|EXECUTE" => sub { $exec = 2 if ! defined $exec || $exec != 0 }, + ); + + Pod::Usage::pod2usage(-verbose => 0) if $help || ! $ret; + + $exec = 0 if ! defined $exec; +} + +$cvs_command = $cvsroot ? "cvs -d $cvsroot -Q" : "cvs -Q"; + +# +# Wrap up the $quiet logic in one place. +# +sub printit { + return if $quiet; + my $string = join('', @_); + $string =~ s/^/$indent/msg if $indent; + print $string; +} + +# +# Wrappers for executing various builtin Perl functions in +# accordance with the -n, -q and -x options. +# +sub execute { + my $cmd = shift; + printit "$cmd\n"; + if (! $exec) { + return 1; + } + ! system($cmd); +} + +sub _copy { + my ($source, $dest) = @_; + printit "cp $source $dest\n"; + if ($exec) { + use File::Copy; + copy($source, $dest); + } +} + +sub _chdir { + my $dir = shift; + printit "cd $dir\n"; + if ($exec) { + chdir($dir) || die "ae2cvs: could not chdir($dir): $!"; + } +} + +sub _mkdir { + my $dir = shift; + printit "mkdir $dir\n"; + if ($exec) { + mkdir($dir); + } +} + +# +# Put some input data through an external filter and capture the output. +# +sub filter { + my ($cmd, $input) = @_; + + use FileHandle; + use IPC::Open2; + + my $pid = open2(*READ, *WRITE, $cmd) || die "Cannot exec '$cmd': $!\n"; + print WRITE $input; + close(WRITE); + my $output = join('', ); + close(READ); + return $output; +} + +# +# Parse a change description, in both 'aegis -l cd" and "aedist" formats. +# +# Returns an array containing the project name, the change number +# (if any), the delta number (if any), the SUMMARY, the DESCRIPTION +# and the lines describing the files in the change. +# +sub parse_change { + my $output = shift; + + my ($p, $c, $d, $c_or_d, $sum, $desc, $filesection, @flines); + + # The project name line comes after NAME in "aegis -l cd" format, + # and PROJECT in "aedist" format. In both cases, the project name + # and the change/delta name are separated a comma. + ($p = $output) =~ s/(?:NAME|PROJECT)\n([^\n]*)\n.*/$1/ms; + ($p, $c_or_d) = (split(/,/, $p)); + + # In "aegis -l cd" format, the project name actually comes after + # the string "Project" and is itself enclosed in double quotes. + $p =~ s/Project "([^"]*)"/$1/; + + # The change or delta string was the right-hand side of the comma. + # "aegis -l cd" format spells it "Change 123." or "Delta 123." while + # "aedist" format spells it "change 123." + if ($c_or_d =~ /\s*[Cc]hange (\d+).*/) { $c = $1 }; + if ($c_or_d =~ /\s*[Dd]elta (\d+).*/) { $d = $1 }; + + # The SUMMARY line is always followed the DESCRIPTION section. + # It seems to always be a single line, but we grab everything in + # between just in case. + ($sum = $output) =~ s/.*\nSUMMARY\n//ms; + $sum =~ s/\nDESCRIPTION\n.*//ms; + + # The DESCRIPTION section is followed ARCHITECTURE in "aegis -l cd" + # format and by CAUSE in "aedist" format. Explicitly under it if the + # string is only "none," which means they didn't supply a description. + ($desc = $output) =~ s/.*\nDESCRIPTION\n//ms; + $desc =~ s/\n(ARCHITECTURE|CAUSE)\n.*//ms; + chomp($desc); + if ($desc eq "none" || $desc eq "none\n") { $desc = undef } + + # The FILES section is followed by HISTORY in "aegis -l cd" format. + # It seems to be the last section in "aedist" format, but stripping + # a non-existent HISTORY section doesn't hurt. + ($filesection = $output) =~ s/.*\nFILES\n//ms; + $filesection =~ s/\nHISTORY\n.*//ms; + + @flines = split(/\n/, $filesection); + + ($p, $c, $d, $sum, $desc, \@flines) +} + +# +# +# +$pwd = Cwd::cwd(); + +# +# Fetch the file list either from our aedist input +# or directly from the project itself. +# +my @filelines; +if ($aedist) { + local ($/); + undef $/; + my $infile_redir = ""; + my $contents; + if (! $infile || $infile eq "-") { + $contents = join('', ); + } else { + open(FILE, "<$infile") || die "Cannot open '$infile': $!\n"; + binmode(FILE); + $contents = join('', ); + close(FILE); + if (! File::Spec->file_name_is_absolute($infile)) { + $infile = File::Spec->catfile($pwd, $infile); + } + $infile_redir = " < $infile"; + } + + my $output = filter("aedist -l -unf", $contents); + my ($p, $c, $d, $s, $desc, $fl) = parse_change($output); + + $proj = $p if ! defined $proj; + $summary = $s; + $description = $desc; + @filelines = @$fl; + + if (! $exec) { + printit qq(MYTMP="/tmp/ae2cvs-ae.\$\$"\n), + qq(mkdir \$MYTMP\n), + qq(cd \$MYTMP\n); + printit q(perl -MMIME::Base64 -e 'undef $/; ($c = <>) =~ s/.*\n\n//ms; print decode_base64($c)'), + $infile_redir, + qq( | zcat), + qq( | cpio -i -d --quiet\n); + $aedir = '$MYTMP'; + push(@cleanup, $aedir); + } else { + $aedir = File::Spec->catfile(File::Spec->tmpdir, "ae2cvs-ae.$$"); + _mkdir($aedir); + push(@cleanup, $aedir); + _chdir($aedir); + + use MIME::Base64; + + $contents =~ s/.*\n\n//ms; + $contents = filter("zcat", decode_base64($contents)); + + open(CPIO, "|cpio -i -d --quiet"); + print CPIO $contents; + close(CPIO); + } + + $ae_copy = sub { + foreach my $dest (@_) { + my $source = File::Spec->catfile($aedir, "src", $dest); + execute(qq(cp $source $dest)); + } + } +} else { + $cnum = $ENV{AEGIS_CHANGE} if ! defined $cnum; + $proj = $ENV{AEGIS_PROJECT} if ! defined $proj; + + $common = "-lib " . join(" -lib ", @libraries) if @libraries; + $common = "$common -proj $proj" if $proj; + + my $output = `aegis -l cd $cnum -unf $common`; + my ($p, $c, $d, $s, $desc, $fl) = parse_change($output); + + $delta = $d; + $summary = $s; + $description = $desc; + @filelines = @$fl; + + if (! $delta) { + print STDERR "ae2cvs: No delta number, exiting.\n"; + exit 1; + } + + $ae_copy = sub { + execute(qq(aegis -cp -ind -delta $delta $common @_)); + } +} + +if (! $usedir) { + $usedir = File::Spec->catfile(File::Spec->tmpdir, "ae2cvs.$$"); + _mkdir($usedir); + push(@cleanup, $usedir); +} + +_chdir($usedir); + +$usepath = $usedir; +if (! File::Spec->file_name_is_absolute($usepath)) { + $usepath = File::Spec->catfile($pwd, $usepath); +} + +if (! -d File::Spec->catfile($usedir, "CVS")) { + $cvsmod = (split(/\./, $proj))[0] if ! defined $cvsmod; + + execute(qq($cvs_command co $cvsmod)); + + _chdir($cvsmod); + + $usepath = File::Spec->catfile($usepath, $cvsmod); +} + +# +# Figure out what we have to do to accomplish everything. +# +foreach (@filelines) { + my @arr = split(/\s+/, $_); + my $type = shift @arr; # source / test + my $act = shift @arr; # modify / create + my $file = pop @arr; + + if ($act eq "create" or $act eq "modify") { + # XXX Do we really only need to do this for + # ($act eq "create") files? + my (undef, $dirs, undef) = File::Spec->splitpath($file); + my $absdir = $usepath; + my $reldir; + my $d; + foreach $d (File::Spec->splitdir($dirs)) { + next if ! $d; + $absdir = File::Spec->catdir($absdir, $d); + $reldir = $reldir ? File::Spec->catdir($reldir, $d) : $d; + if (! -d $absdir && ! $seen_dir{$reldir}) { + $seen_dir{$reldir} = 1; + push(@mkdir_list, $reldir); + } + } + + push(@copy_list, $file); + + if ($act eq "create") { + push(@add_list, $file); + } + } elsif ($act eq "remove") { + push(@remove_list, $file); + } else { + print STDERR "Unsure how to '$act' the '$file' file.\n"; + } +} + +# Now go through and mkdir() the directories, +# adding them to the CVS tree as we do. +if (@mkdir_list) { + if (! $exec) { + printit qq(# The following "mkdir" and "cvs -Q add" calls are not\n), + qq(# necessary for any directories that already exist in the\n), + qq(# CVS tree but which aren't present locally.\n); + } + foreach (@mkdir_list) { + if (! $exec) { + printit qq(if test ! -d $_; then\n); + $indent = " "; + } + _mkdir($_); + execute(qq($cvs_command add $_)); + if (! $exec) { + $indent = ""; + printit qq(fi\n); + } + } + if (! $exec) { + printit qq(# End of directory creation.\n); + } +} + +# Copy in any files in the change, before we try to "cvs add" them. +$ae_copy->(@copy_list) if @copy_list; + +if (@add_list) { + execute(qq($cvs_command add @add_list)); +} + +if (@remove_list) { + execute(qq(rm -f @remove_list)); + execute(qq($cvs_command remove @remove_list)); +} + +# Last, commit the whole bunch. +$comment = $summary; +$comment .= "\n" . $description if $description; +$commit = qq($cvs_command commit -m '$comment' .); +if ($exec == 1) { + printit qq(# Execute the following to commit the changes:\n), + qq(# $commit\n); +} else { + execute($commit); +} + +_chdir($pwd); + +# +# Directory cleanup. +# +sub END { + my $dir; + foreach $dir (@cleanup) { + printit "rm -rf $dir\n"; + if ($exec) { + finddepth(sub { + # print STDERR "unlink($_)\n" if (!-d $_); + # print STDERR "rmdir($_)\n" if (-d $_ && $_ ne "."); + unlink($_) if (!-d $_); + rmdir($_) if (-d $_ && $_ ne "."); + 1; + }, $dir); + rmdir($dir) || print STDERR "Could not remove $dir: $!\n"; + } + } +} + +__END__; + +=head1 NAME + +ae2cvs - convert an Aegis change set to CVS commands + +=head1 SYNOPSIS + +ae2cvs [-aedist|-aegis] [-c change] [-d cvs_root] [-f file] [-l lib] + [-m module] [-n] [-p proj] [-q] [-u dir] [-v] [-x] [-X] + + -aedist use aedist format from input (default) + -aegis query aegis repository directly + -c change change number + -d cvs_root CVS root directory + -f file read aedist from file ('-' == stdin) + -l lib Aegis library directory + -m module CVS module + -n no execute + -p proj project name + -q quiet, don't print commands + -u dir use dir for CVS checkin + -v print version string and exit + -x execute the commands, but don't commit; + two or more -x options commit changes + -X execute the commands and commit changes + +=head1 DESCRIPTION + +The C utility can convert an Aegis change into a set of CVS (and +other) commands to make the corresponding change(s) to a carbon-copy CVS +repository. This can be used to keep a front-end CVS repository in sync +with changes made to an Aegis project, either manually or automatically +using the C attribute of the Aegis +project. + +By default, C makes no changes to any software, and only prints +out the necessary commands. These commands can be examined first for +safety, and then fed to any Bourne shell variant (sh, ksh, or bash) to +make the actual CVS changes. + +An option exists to have C execute the commands directly. + +=head1 OPTIONS + +The C utility supports the following options: + +=over 4 + +=item -aedist + +Reads an aedist change set. +By default, the change set is read from standard input, +or a file specified with the C<-f> option. + +=item -aegis + +Reads the change directly from the Aegis repository +by executing the proper C commands. + +=item -c change + +Specify the Aegis change number to be used. +The value of the C environment variable +is used by default. + +=item -d cvsroot + +Specify the CVS root directory to be used. +This option is passed explicitly to each executed C command. +The default behavior is to omit any C<-d> options +and let the executed C commands use the +C environment variable as they normally would. + +=item -f file + +Reads the aedist change set from the specified C, +or from standard input if C is C<'-'>. + +=item -l lib + +Specifies an Aegis library directory to be searched for global states +files and user state files. + +=item -m module + +Specifies the name of the CVS module to be brought up-to-date. +The default is to use the Aegis project name, +minus any branch numbers; +for example, given an Aegis project name of C, +the default CVS module name is C. + +=item -n + +No execute. Commands are printed (including a command for a final +commit of changes), but not executed. This is the default. + +=item -p proj + +Specifies the name of the Aegis project from which this change is taken. +The value of the C environment variable +is used by default. + +=item -q + +Quiet. Commands are not printed. + +=item -u dir + +Use the already checked-out CVS tree that exists at C +for the checkins and commits. +The default is to use a separately-created temporary directory. + +=item -v + +Print the version string and exit. + +=item -x + +Execute the commands to bring the CVS repository up to date, +except for the final commit of the changes. Two or more +C<-x> options will cause the change to be committed. + +=item -X + +Execute the commands to bring the CVS repository up to date, +including the final commit of the changes. + +=back + +=head1 ENVIRONMENT VARIABLES + +=over 4 + +=item AE2CVS_FLAGS + +Specifies any options to be used to initialize +the C utility. +Options on the command line override these values. + +=back + +=head1 AUTHOR + +Steven Knight (knight at baldmt dot com) + +=head1 BUGS + +If errors occur during the execution of the Aegis or CVS commands, and +the -X option is used, a partial change (consisting of those files for +which the command(s) succeeded) will be committed. It would be safer to +generate code to detect the error and print a warning. + +When a file has been deleted in Aegis, the standard whiteout file can +cause a regex failure in this script. It doesn't necessarily happen all +the time, though, so this needs more investigation. + +=head1 TODO + +Add an explicit test for using ae2cvs in the Aegis +integrate_pass_notify_command field to support fully keeping a +repository in sync automatically. + +=head1 COPYRIGHT + +Copyright 2001, 2002, 2003, 2004, 2005 Steven Knight. + +=head1 SEE ALSO + +aegis(1), cvs(1) diff --git a/bin/calibrate.py b/bin/calibrate.py new file mode 100644 index 0000000..c5d45ce --- /dev/null +++ b/bin/calibrate.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# Copyright (c) 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import optparse +import os +import re +import subprocess +import sys + +variable_re = re.compile('^VARIABLE: (.*)$', re.M) +elapsed_re = re.compile('^ELAPSED: (.*)$', re.M) + +def main(argv=None): + if argv is None: + argv = sys.argv + + parser = optparse.OptionParser(usage="calibrate.py [-h] [-p PACKAGE], [--min time] [--max time] timings/*/*-run.py") + parser.add_option('--min', type='float', default=9.5, + help="minimum acceptable execution time (default 9.5)") + parser.add_option('--max', type='float', default=10.00, + help="maximum acceptable execution time (default 10.00)") + parser.add_option('-p', '--package', type="string", + help="package type") + opts, args = parser.parse_args(argv[1:]) + + os.environ['TIMESCONS_CALIBRATE'] = '1' + + for arg in args: + if len(args) > 1: + print arg + ':' + + command = [sys.executable, 'runtest.py', '--noqmtest'] + if opts.package: + command.extend(['-p', opts.package]) + command.append(arg) + + run = 1 + good = 0 + while good < 3: + p = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + output = p.communicate()[0] + vm = variable_re.search(output) + em = elapsed_re.search(output) + elapsed = float(em.group(1)) + print "run %3d: %7.3f: %s" % (run, elapsed, ' '.join(vm.groups())) + if opts.min < elapsed and elapsed < opts.max: + good += 1 + else: + good = 0 + for v in vm.groups(): + var, value = v.split('=', 1) + value = int((int(value) * opts.max) / elapsed) + os.environ[var] = str(value) + run += 1 + + return 0 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/caller-tree.py b/bin/caller-tree.py new file mode 100644 index 0000000..85bb599 --- /dev/null +++ b/bin/caller-tree.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# +# Quick script to process the *summary* output from SCons.Debug.caller() +# and print indented calling trees with call counts. +# +# The way to use this is to add something like the following to a function +# for which you want information about who calls it and how many times: +# +# from SCons.Debug import caller +# caller(0, 1, 2, 3, 4, 5) +# +# Each integer represents how many stack frames back SCons will go +# and capture the calling information, so in the above example it will +# capture the calls six levels up the stack in a central dictionary. +# +# At the end of any run where SCons.Debug.caller() is used, SCons will +# print a summary of the calls and counts that looks like the following: +# +# Callers of Node/__init__.py:629(calc_signature): +# 1 Node/__init__.py:683(calc_signature) +# Callers of Node/__init__.py:676(gen_binfo): +# 6 Node/FS.py:2035(current) +# 1 Node/__init__.py:722(get_bsig) +# +# If you cut-and-paste that summary output and feed it to this script +# on standard input, it will figure out how these entries hook up and +# print a calling tree for each one looking something like: +# +# Node/__init__.py:676(gen_binfo) +# Node/FS.py:2035(current) 6 +# Taskmaster.py:253(make_ready_current) 18 +# Script/Main.py:201(make_ready) 18 +# +# Note that you should *not* look at the call-count numbers in the right +# hand column as the actual number of times each line *was called by* +# the function on the next line. Rather, it's the *total* number +# of times each function was found in the call chain for any of the +# calls to SCons.Debug.caller(). If you're looking at more than one +# function at the same time, for example, their counts will intermix. +# So use this to get a *general* idea of who's calling what, not for +# fine-grained performance tuning. + +import sys + +class Entry: + def __init__(self, file_line_func): + self.file_line_func = file_line_func + self.called_by = [] + self.calls = [] + +AllCalls = {} + +def get_call(flf): + try: + e = AllCalls[flf] + except KeyError: + e = AllCalls[flf] = Entry(flf) + return e + +prefix = 'Callers of ' + +c = None +for line in sys.stdin.readlines(): + if line[0] == '#': + pass + elif line[:len(prefix)] == prefix: + c = get_call(line[len(prefix):-2]) + else: + num_calls, flf = line.strip().split() + e = get_call(flf) + c.called_by.append((e, num_calls)) + e.calls.append(c) + +stack = [] + +def print_entry(e, level, calls): + print '%-72s%6s' % ((' '*2*level) + e.file_line_func, calls) + if e in stack: + print (' '*2*(level+1))+'RECURSION' + print + elif e.called_by: + stack.append(e) + for c in e.called_by: + print_entry(c[0], level+1, c[1]) + stack.pop() + else: + print + +for e in [ e for e in AllCalls.values() if not e.calls ]: + print_entry(e, 0, '') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/docdiff b/bin/docdiff new file mode 100644 index 0000000..c565a04 --- /dev/null +++ b/bin/docdiff @@ -0,0 +1,16 @@ +#!/bin/sh + +if test $# -eq 0; then + for f in doc/user/*.in; do + xml=doc/user/`basename $f .in`.xml + echo $f: + python bin/sconsoutput.py $f | diff $DIFFFLAGS $xml - + done +else + for a in $*; do + f=doc/user/$a.in + xml=doc/user/$a.xml + echo $f: + python bin/sconsoutput.py $f | diff $DIFFFLAGS $xml - + done +fi diff --git a/bin/docrun b/bin/docrun new file mode 100644 index 0000000..5ba4215 --- /dev/null +++ b/bin/docrun @@ -0,0 +1,16 @@ +#!/bin/sh + +if test $# -eq 0; then + for f in doc/user/*.in; do + xml=doc/user/`basename $f .in`.xml + echo $f: + python bin/sconsoutput.py $f + done +else + for a in $*; do + f=doc/user/$a.in + xml=doc/user/$a.xml + echo $f: + python bin/sconsoutput.py $f + done +fi diff --git a/bin/docupdate b/bin/docupdate new file mode 100644 index 0000000..0e1631b --- /dev/null +++ b/bin/docupdate @@ -0,0 +1,16 @@ +#!/bin/sh + +if test $# -eq 0; then + for f in doc/user/*.in; do + xml=doc/user/`basename $f .in`.xml + echo $f: + python bin/sconsoutput.py $f > $xml + done +else + for a in $*; do + f=doc/user/$a.in + xml=doc/user/$a.xml + echo $f: + python bin/sconsoutput.py $f > $xml + done +fi diff --git a/bin/files b/bin/files new file mode 100644 index 0000000..08b1caa --- /dev/null +++ b/bin/files @@ -0,0 +1,106 @@ +./SCons/Action.py +./SCons/Builder.py +./SCons/Conftest.py +./SCons/Debug.py +./SCons/Defaults.py +./SCons/Environment.py +./SCons/Errors.py +./SCons/Executor.py +./SCons/Job.py +./SCons/Node/Alias.py +./SCons/Node/FS.py +./SCons/Node/Python.py +./SCons/Node/__init__.py +./SCons/Options/__init__.py +./SCons/Options/BoolOption.py +./SCons/Options/EnumOption.py +./SCons/Options/ListOption.py +./SCons/Options/PackageOption.py +./SCons/Options/PathOption.py +./SCons/Platform/__init__.py +./SCons/Platform/aix.py +./SCons/Platform/cygwin.py +./SCons/Platform/hpux.py +./SCons/Platform/irix.py +./SCons/Platform/os2.py +./SCons/Platform/posix.py +./SCons/Platform/sunos.py +./SCons/Platform/win32.py +./SCons/Scanner/C.py +./SCons/Scanner/D.py +./SCons/Scanner/Fortran.py +./SCons/Scanner/IDL.py +./SCons/Scanner/Prog.py +./SCons/Scanner/__init__.py +./SCons/Script/SConscript.py +./SCons/Script/__init__.py +./SCons/Sig/MD5.py +./SCons/Sig/TimeStamp.py +./SCons/Sig/__init__.py +./SCons/Taskmaster.py +./SCons/Tool/__init__.py +./SCons/Tool/aixc++.py +./SCons/Tool/aixcc.py +./SCons/Tool/aixf77.py +./SCons/Tool/aixlink.py +./SCons/Tool/ar.py +./SCons/Tool/as.py +./SCons/Tool/bcc32.py +./SCons/Tool/c++.py +./SCons/Tool/cc.py +./SCons/Tool/CVS.py +./SCons/Tool/dmd.py +./SCons/Tool/default.py +./SCons/Tool/dvipdf.py +./SCons/Tool/dvips.py +./SCons/Tool/f77.py +./SCons/Tool/g++.py +./SCons/Tool/g77.py +./SCons/Tool/gas.py +./SCons/Tool/gcc.py +./SCons/Tool/gnulink.py +./SCons/Tool/hpc++.py +./SCons/Tool/hpcc.py +./SCons/Tool/hplink.py +./SCons/Tool/icc.py +./SCons/Tool/icl.py +./SCons/Tool/ifl.py +./SCons/Tool/ilink.py +./SCons/Tool/ilink32.py +./SCons/Tool/jar.py +./SCons/Tool/javac.py +./SCons/Tool/JavaCommon.py +./SCons/Tool/javah.py +./SCons/Tool/latex.py +./SCons/Tool/lex.py +./SCons/Tool/link.py +./SCons/Tool/m4.py +./SCons/Tool/masm.py +./SCons/Tool/midl.py +./SCons/Tool/mingw.py +./SCons/Tool/mslib.py +./SCons/Tool/mslink.py +./SCons/Tool/msvc.py +./SCons/Tool/msvs.py +./SCons/Tool/nasm.py +./SCons/Tool/pdflatex.py +./SCons/Tool/pdftex.py +./SCons/Tool/qt.py +./SCons/Tool/rmic.py +./SCons/Tool/sgiar.py +./SCons/Tool/sgic++.py +./SCons/Tool/sgicc.py +./SCons/Tool/sgilink.py +./SCons/Tool/sunar.py +./SCons/Tool/sunc++.py +./SCons/Tool/suncc.py +./SCons/Tool/sunlink.py +./SCons/Tool/swig.py +./SCons/Tool/tar.py +./SCons/Tool/tex.py +./SCons/Tool/tlib.py +./SCons/Tool/yacc.py +./SCons/Util.py +./SCons/Warnings.py +./SCons/__init__.py +./SCons/exitfuncs.py diff --git a/bin/import-test.py b/bin/import-test.py new file mode 100644 index 0000000..473a7ed --- /dev/null +++ b/bin/import-test.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# tree2test.py - turn a directory tree into TestSCons code +# +# A quick script for importing directory hierarchies containing test +# cases that people supply (typically in a .zip or .tar.gz file) into a +# TestSCons.py script. No error checking or options yet, it just walks +# the first command-line argument (assumed to be the directory containing +# the test case) and spits out code looking like the following: +# +# test.subdir(['sub1'], +# ['sub1', 'sub2']) +# +# test.write(['sub1', 'file1'], """\ +# contents of file1 +# """) +# +# test.write(['sub1', 'sub2', 'file2'], """\ +# contents of file2 +# """) +# +# There's no massaging of contents, so any files that themselves contain +# """ triple-quotes will need to have their contents edited by hand. +# + +__revision__ = "bin/import-test.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import sys + +directory = sys.argv[1] + +Top = None +TopPath = None + +class Dir: + def __init__(self, path): + self.path = path + self.entries = {} + def call_for_each_entry(self, func): + entries = self.entries + names = entries.keys() + names.sort() + for name in names: + func(name, entries[name]) + +def lookup(dirname): + global Top, TopPath + if not Top: + Top = Dir([]) + TopPath = dirname + os.sep + return Top + dirname = dirname.replace(TopPath, '') + dirs = dirname.split(os.sep) + t = Top + for d in dirs[:-1]: + t = t.entries[d] + node = t.entries[dirs[-1]] = Dir(dirs) + return node + +def make_nodes(arg, dirname, fnames): + dir = lookup(dirname) + for f in fnames: + dir.entries[f] = None + +def collect_dirs(l, dir): + if dir.path: + l.append(dir.path) + def recurse(n, d): + if d: + collect_dirs(l, d) + dir.call_for_each_entry(recurse) + +def print_files(dir): + def print_a_file(n, d): + if not d: + l = dir.path + [n] + sys.stdout.write('\ntest.write(%s, """\\\n' % l) + p = os.path.join(*([directory] + l)) + sys.stdout.write(open(p, 'r').read()) + sys.stdout.write('""")\n') + dir.call_for_each_entry(print_a_file) + + def recurse(n, d): + if d: + print_files(d) + dir.call_for_each_entry(recurse) + +os.path.walk(directory, make_nodes, None) + +subdir_list = [] +collect_dirs(subdir_list, Top) +subdir_list = [ str(l) for l in subdir_list ] +sys.stdout.write('test.subdir(' + ',\n '.join(subdir_list) + ')\n') + +print_files(Top) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/install_python.py b/bin/install_python.py new file mode 100644 index 0000000..ca1c8b7 --- /dev/null +++ b/bin/install_python.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# +# A script for unpacking and installing different historic versions of +# Python in a consistent manner for side-by-side development testing. +# +# This was written for a Linux system (specifically Ubuntu) but should +# be reasonably generic to any POSIX-style system with a /usr/local +# hierarchy. + +import getopt +import os +import shutil +import sys + +from Command import CommandRunner, Usage + +all_versions = [ + #'1.5.2', # no longer available at python.org + '2.0.1', + '2.1.3', + '2.2', + '2.3.7', + '2.4.5', + #'2.5.2', + '2.6', +] + +def main(argv=None): + if argv is None: + argv = sys.argv + + all = False + downloads_dir = 'Downloads' + downloads_url = 'http://www.python.org/ftp/python' + sudo = 'sudo' + prefix = '/usr/local' + + short_options = 'ad:hnp:q' + long_options = ['all', 'help', 'no-exec', 'prefix=', 'quiet'] + + helpstr = """\ +Usage: install_python.py [-ahnq] [-d DIR] [-p PREFIX] [VERSION ...] + + -a, --all Install all SCons versions. + -d DIR, --downloads=DIR Downloads directory. + -h, --help Print this help and exit + -n, --no-exec No execute, just print command lines + -p PREFIX, --prefix=PREFIX Installation prefix. + -q, --quiet Quiet, don't print command lines +""" + + try: + try: + opts, args = getopt.getopt(argv[1:], short_options, long_options) + except getopt.error, msg: + raise Usage(msg) + + for o, a in opts: + if o in ('-a', '--all'): + all = True + elif o in ('-d', '--downloads'): + downloads_dir = a + elif o in ('-h', '--help'): + print helpstr + sys.exit(0) + elif o in ('-n', '--no-exec'): + CommandRunner.execute = CommandRunner.do_not_execute + elif o in ('-p', '--prefix'): + prefix = a + elif o in ('-q', '--quiet'): + CommandRunner.display = CommandRunner.do_not_display + except Usage, err: + sys.stderr.write(str(err.msg) + '\n') + sys.stderr.write('use -h to get help\n') + return 2 + + if all: + if args: + msg = 'install-scons.py: -a and version arguments both specified' + sys.stderr.write(msg) + sys.exit(1) + + args = all_versions + + cmd = CommandRunner() + + for version in args: + python = 'Python-' + version + tar_gz = os.path.join(downloads_dir, python + '.tgz') + tar_gz_url = os.path.join(downloads_url, version, python + '.tgz') + + if (version.startswith('1.5') or + version.startswith('1.6') or + version.startswith('2.0')): + + configureflags = '--with-threads' + + else: + + configureflags = '' + + cmd.subst_dictionary(locals()) + + if not os.path.exists(tar_gz): + if not os.path.exists(downloads_dir): + cmd.run('mkdir %(downloads_dir)s') + cmd.run('wget -O %(tar_gz)s %(tar_gz_url)s') + + cmd.run('tar zxf %(tar_gz)s') + + cmd.run('cd %(python)s') + + if (version.startswith('1.6') or + version.startswith('2.0')): + + def edit_modules_setup_in(): + content = open('Modules/Setup.in', 'r').read() + content = content.replace('\n#zlib', '\nzlib') + open('Modules/Setup.in', 'w').write(content) + + display = 'ed Modules/Setup.in <&1 | tee configure.out') + cmd.run('make 2>&1 | tee make.out') + cmd.run('%(sudo)s make install') + + cmd.run('%(sudo)s rm -f %(prefix)s/bin/{idle,pydoc,python,python-config,smtpd.py}') + + cmd.run('cd ..') + + cmd.run((shutil.rmtree, python), 'rm -rf %(python)s') + +if __name__ == "__main__": + sys.exit(main()) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/install_scons.py b/bin/install_scons.py new file mode 100644 index 0000000..e4e6aff --- /dev/null +++ b/bin/install_scons.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# +# A script for unpacking and installing different historic versions of +# SCons in a consistent manner for side-by-side development testing. +# +# This abstracts the changes we've made to the SCons setup.py scripts in +# different versions so that, no matter what version is specified, it ends +# up installing the necessary script(s) and library into version-specific +# names that won't interfere with other things. +# +# By default, we expect to extract the .tar.gz files from a Downloads +# subdirectory in the current directory. +# +# Note that this script cleans up after itself, removing the extracted +# directory in which we do the build. +# +# This was written for a Linux system (specifically Ubuntu) but should +# be reasonably generic to any POSIX-style system with a /usr/local +# hierarchy. + +import getopt +import os +import shutil +import sys + +from Command import CommandRunner, Usage + +all_versions = [ + '0.01', + '0.02', + '0.03', + '0.04', + '0.05', + '0.06', + '0.07', + '0.08', + '0.09', + '0.10', + '0.11', + '0.12', + '0.13', + '0.14', + '0.90', + '0.91', + '0.92', + '0.93', + '0.94', + #'0.94.1', + '0.95', + #'0.95.1', + '0.96', + '0.96.1', + '0.96.90', + '0.96.91', + '0.96.92', + '0.96.93', + '0.96.94', + '0.96.95', + '0.96.96', + '0.97', + '0.97.0d20070809', + '0.97.0d20070918', + '0.97.0d20071212', + '0.98.0', + '0.98.1', + '0.98.2', + '0.98.3', + '0.98.4', + '0.98.5', + '1.0.0', + '1.0.0.d20080826', + '1.0.1', + '1.0.1.d20080915', + '1.0.1.d20081001', + '1.1.0', + '1.1.0.d20081104', + '1.1.0.d20081125', + '1.1.0.d20081207', + '1.2.0', + '1.2.0.d20090113', + '1.2.0.d20090223', +] + +def main(argv=None): + if argv is None: + argv = sys.argv + + all = False + downloads_dir = 'Downloads' + downloads_url = 'http://downloads.sourceforge.net/scons' + sudo = 'sudo' + prefix = '/usr/local' + python = sys.executable + + short_options = 'ad:hnp:q' + long_options = ['all', 'help', 'no-exec', 'prefix=', 'quiet'] + + helpstr = """\ +Usage: install_scons.py [-ahnq] [-d DIR] [-p PREFIX] [VERSION ...] + + -a, --all Install all SCons versions. + -d DIR, --downloads=DIR Downloads directory. + -h, --help Print this help and exit + -n, --no-exec No execute, just print command lines + -p PREFIX, --prefix=PREFIX Installation prefix. + -q, --quiet Quiet, don't print command lines +""" + + try: + try: + opts, args = getopt.getopt(argv[1:], short_options, long_options) + except getopt.error, msg: + raise Usage(msg) + + for o, a in opts: + if o in ('-a', '--all'): + all = True + elif o in ('-d', '--downloads'): + downloads_dir = a + elif o in ('-h', '--help'): + print helpstr + sys.exit(0) + elif o in ('-n', '--no-exec'): + CommandRunner.execute = CommandRunner.do_not_execute + elif o in ('-p', '--prefix'): + prefix = a + elif o in ('-q', '--quiet'): + CommandRunner.display = CommandRunner.do_not_display + except Usage, err: + sys.stderr.write(str(err.msg) + '\n') + sys.stderr.write('use -h to get help\n') + return 2 + + if all: + if args: + msg = 'install-scons.py: -a and version arguments both specified' + sys.stderr.write(msg) + sys.exit(1) + + args = all_versions + + cmd = CommandRunner() + + for version in args: + scons = 'scons-' + version + tar_gz = os.path.join(downloads_dir, scons + '.tar.gz') + tar_gz_url = os.path.join(downloads_url, scons + '.tar.gz') + + cmd.subst_dictionary(locals()) + + if not os.path.exists(tar_gz): + if not os.path.exists(downloads_dir): + cmd.run('mkdir %(downloads_dir)s') + cmd.run('wget -O %(tar_gz)s %(tar_gz_url)s') + + cmd.run('tar zxf %(tar_gz)s') + + cmd.run('cd %(scons)s') + + if version in ('0.01', '0.02', '0.03', '0.04', '0.05', + '0.06', '0.07', '0.08', '0.09', '0.10'): + + # 0.01 through 0.10 install /usr/local/bin/scons and + # /usr/local/lib/scons. The "scons" script knows how to + # look up the library in a version-specific directory, but + # we have to move both it and the library directory into + # the right version-specific name by hand. + cmd.run('%(python)s setup.py build') + cmd.run('%(sudo)s %(python)s setup.py install --prefix=%(prefix)s') + cmd.run('%(sudo)s mv %(prefix)s/bin/scons %(prefix)s/bin/scons-%(version)s') + cmd.run('%(sudo)s mv %(prefix)s/lib/scons %(prefix)s/lib/scons-%(version)s') + + elif version in ('0.11', '0.12', '0.13', '0.14', '0.90'): + + # 0.11 through 0.90 install /usr/local/bin/scons and + # /usr/local/lib/scons-%(version)s. We just need to move + # the script to a version-specific name. + cmd.run('%(python)s setup.py build') + cmd.run('%(sudo)s %(python)s setup.py install --prefix=%(prefix)s') + cmd.run('%(sudo)s mv %(prefix)s/bin/scons %(prefix)s/bin/scons-%(version)s') + + elif version in ('0.91', '0.92', '0.93', + '0.94', '0.94.1', + '0.95', '0.95.1', + '0.96', '0.96.1', '0.96.90'): + + # 0.91 through 0.96.90 install /usr/local/bin/scons, + # /usr/local/bin/sconsign and /usr/local/lib/scons-%(version)s. + # We need to move both scripts to version-specific names. + cmd.run('%(python)s setup.py build') + cmd.run('%(sudo)s %(python)s setup.py install --prefix=%(prefix)s') + cmd.run('%(sudo)s mv %(prefix)s/bin/scons %(prefix)s/bin/scons-%(version)s') + cmd.run('%(sudo)s mv %(prefix)s/bin/sconsign %(prefix)s/bin/sconsign-%(version)s') + lib_scons = os.path.join(prefix, 'lib', 'scons') + if os.path.isdir(lib_scons): + cmd.run('%(sudo)s mv %(prefix)s/lib/scons %(prefix)s/lib/scons-%(version)s') + + else: + + # Versions from 0.96.91 and later support what we want + # with a --no-scons-script option. + cmd.run('%(python)s setup.py build') + cmd.run('%(sudo)s %(python)s setup.py install --prefix=%(prefix)s --no-scons-script') + + cmd.run('cd ..') + + cmd.run((shutil.rmtree, scons), 'rm -rf %(scons)s') + +if __name__ == "__main__": + sys.exit(main()) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/linecount.py b/bin/linecount.py new file mode 100644 index 0000000..359f642 --- /dev/null +++ b/bin/linecount.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Count statistics about SCons test and source files. This must be run +# against a fully-populated tree (for example, one that's been freshly +# checked out). +# +# A test file is anything under the src/ directory that begins with +# 'test_' or ends in 'Tests.py', or anything under the test/ directory +# that ends in '.py'. Note that runtest.py script does *not*, by default, +# consider the files that begin with 'test_' to be tests, because they're +# tests of SCons packaging and installation, not functional tests of +# SCons code. +# +# A source file is anything under the src/engine/ or src/script/ +# directories that ends in '.py' but does NOT begin with 'test_' +# or end in 'Tests.py'. +# +# We report the number of tests and sources, the total number of lines +# in each category, the number of non-blank lines, and the number of +# non-comment lines. The last figure (non-comment) lines is the most +# interesting one for most purposes. +# + +__revision__ = "bin/linecount.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string + +fmt = "%-16s %5s %7s %9s %11s %11s" + +class Collection(object): + def __init__(self, name, files=None, pred=None): + self._name = name + if files is None: + files = [] + self.files = files + if pred is None: + pred = lambda x: True + self.pred = pred + def __call__(self, fname): + return self.pred(fname) + def __len__(self): + return len(self.files) + def extend(self, files): + self.files.extend(files) + def lines(self): + try: + return self._lines + except AttributeError: + self._lines = lines = [] + for file in self.files: + file_lines = open(file).readlines() + lines.extend([s.lstrip() for s in file_lines]) + return lines + def non_blank(self): + return [s for s in self.lines() if s != ''] + def non_comment(self): + return [s for s in self.lines() if s == '' or s[0] != '#'] + def non_blank_non_comment(self): + return [s for s in self.lines() if s != '' and s[0] != '#'] + def printables(self): + return (self._name + ':', + len(self.files), + len(self.lines()), + len(self.non_blank()), + len(self.non_comment()), + len(self.non_blank_non_comment())) + +def is_Tests_py(x): + return x[-8:] == 'Tests.py' +def is_test_(x): + return x[:5] == 'test_' +def is_python(x): + return x[-3:] == '.py' +def is_source(x): + return is_python(x) and not is_Tests_py(x) and not is_test_(x) + +src_Tests_py_tests = Collection('src/ *Tests.py', pred=is_Tests_py) +src_test_tests = Collection('src/ test_*.py', pred=is_test_) +test_tests = Collection('test/ tests', pred=is_python) +sources = Collection('sources', pred=is_source) + +def t(arg, dirname, names): + try: names.remove('.svn') + except ValueError: pass + names = filter(arg, names) + arg.extend(map(lambda n, d=dirname: os.path.join(d, n), names)) + +os.path.walk('src', t, src_Tests_py_tests) +os.path.walk('src', t, src_test_tests) +os.path.walk('test', t, test_tests) +os.path.walk('src/engine', t, sources) +os.path.walk('src/script', t, sources) + +src_tests = Collection('src/ tests', src_Tests_py_tests.files + + src_test_tests.files) +all_tests = Collection('all tests', src_tests.files + test_tests.files) + +def ratio(over, under): + return "%.2f" % (float(len(over)) / float(len(under))) + +print fmt % ('', '', '', '', '', 'non-blank') +print fmt % ('', 'files', 'lines', 'non-blank', 'non-comment', 'non-comment') +print +print fmt % src_Tests_py_tests.printables() +print fmt % src_test_tests.printables() +print +print fmt % src_tests.printables() +print fmt % test_tests.printables() +print +print fmt % all_tests.printables() +print fmt % sources.printables() +print +print fmt % ('ratio:', + ratio(all_tests, sources), + ratio(all_tests.lines(), sources.lines()), + ratio(all_tests.non_blank(), sources.non_blank()), + ratio(all_tests.non_comment(), sources.non_comment()), + ratio(all_tests.non_blank_non_comment(), + sources.non_blank_non_comment()) + ) diff --git a/bin/makedocs b/bin/makedocs new file mode 100644 index 0000000..2278a97 --- /dev/null +++ b/bin/makedocs @@ -0,0 +1,18 @@ +#! /bin/sh + +# This script uses HappyDoc to create the HTML class documentation for +# SCons. It must be run from the src/engine directory. + +base=`basename $PWD` +if [ "$base" != "engine" ]; then + echo "You must run this script from the engine directory." + exit +fi + +DEVDIR=../../doc/developer +if [ ! -d $DEVDIR ]; then + mkdir $DEVDIR +fi + +SRCFILE=../../bin/files +happydoc -d $DEVDIR `cat $SRCFILE` diff --git a/bin/memlogs.py b/bin/memlogs.py new file mode 100644 index 0000000..9d957c9 --- /dev/null +++ b/bin/memlogs.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# +# Copyright (c) 2005 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import getopt +import sys + +filenames = sys.argv[1:] + +if not filenames: + print """Usage: memlogs.py file [...] + +Summarizes the --debug=memory numbers from one or more build logs. +""" + sys.exit(0) + +fmt = "%12s %12s %12s %12s %s" + +print fmt % ("pre-read", "post-read", "pre-build", "post-build", "") + +for fname in sys.argv[1:]: + lines = [l for l in open(fname).readlines() if l[:7] == 'Memory '] + t = tuple([l.split()[-1] for l in lines]) + (fname,) + print fmt % t + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/memoicmp.py b/bin/memoicmp.py new file mode 100644 index 0000000..f45ecb0 --- /dev/null +++ b/bin/memoicmp.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +# +# A script to compare the --debug=memoizer output found int +# two different files. + +import sys,string + +def memoize_output(fname): + mout = {} + lines=filter(lambda words: + len(words) == 5 and + words[1] == 'hits' and words[3] == 'misses', + map(string.split, open(fname,'r').readlines())) + for line in lines: + mout[line[-1]] = ( int(line[0]), int(line[2]) ) + return mout + + +def memoize_cmp(filea, fileb): + ma = memoize_output(filea) + mb = memoize_output(fileb) + + print 'All output: %s / %s [delta]'%(filea, fileb) + print '----------HITS---------- ---------MISSES---------' + cfmt='%7d/%-7d [%d]' + ma_o = [] + mb_o = [] + mab = [] + for k in ma.keys(): + if k in mb.keys(): + if k not in mab: + mab.append(k) + else: + ma_o.append(k) + for k in mb.keys(): + if k in ma.keys(): + if k not in mab: + mab.append(k) + else: + mb_o.append(k) + + mab.sort() + ma_o.sort() + mb_o.sort() + + for k in mab: + hits = cfmt%(ma[k][0], mb[k][0], mb[k][0]-ma[k][0]) + miss = cfmt%(ma[k][1], mb[k][1], mb[k][1]-ma[k][1]) + print '%-24s %-24s %s'%(hits, miss, k) + + for k in ma_o: + hits = '%7d/ --'%(ma[k][0]) + miss = '%7d/ --'%(ma[k][1]) + print '%-24s %-24s %s'%(hits, miss, k) + + for k in mb_o: + hits = ' -- /%-7d'%(mb[k][0]) + miss = ' -- /%-7d'%(mb[k][1]) + print '%-24s %-24s %s'%(hits, miss, k) + + print '-'*(24+24+1+20) + + +if __name__ == "__main__": + if len(sys.argv) != 3: + print """Usage: %s file1 file2 + +Compares --debug=memomize output from file1 against file2."""%sys.argv[0] + sys.exit(1) + + memoize_cmp(sys.argv[1], sys.argv[2]) + sys.exit(0) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/objcounts.py b/bin/objcounts.py new file mode 100644 index 0000000..ca814b4 --- /dev/null +++ b/bin/objcounts.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# +# Copyright (c) 2005 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import re +import sys + +filenames = sys.argv[1:] + +if len(sys.argv) != 3: + print """Usage: objcounts.py file1 file2 + +Compare the --debug=object counts from two build logs. +""" + sys.exit(0) + +def fetch_counts(fname): + contents = open(fname).read() + m = re.search('\nObject counts:(\n\s[^\n]*)*', contents, re.S) + lines = m.group().split('\n') + list = [l.split() for l in lines if re.match('\s+\d', l)] + d = {} + for l in list: + d[l[-1]] = map(int, l[:-1]) + return d + +c1 = fetch_counts(sys.argv[1]) +c2 = fetch_counts(sys.argv[2]) + +common = {} +for k in c1.keys(): + try: + common[k] = (c1[k], c2[k]) + except KeyError: + # Transition: we added the module to the names of a bunch of + # the logged objects. Assume that c1 might be from an older log + # without the modules in the names, and look for an equivalent + # in c2. + if not '.' in k: + s = '.'+k + l = len(s) + for k2 in c2.keys(): + if k2[-l:] == s: + common[k2] = (c1[k], c2[k2]) + del c1[k] + del c2[k2] + break + else: + del c1[k] + del c2[k] + +def diffstr(c1, c2): + try: + d = c2 - c1 + except TypeError: + d = '' + else: + if d: + d = '[%+d]' % d + else: + d = '' + return " %5s/%-5s %-8s" % (c1, c2, d) + +def printline(c1, c2, classname): + print \ + diffstr(c1[2], c2[2]) + \ + diffstr(c1[3], c2[3]) + \ + ' ' + classname + +keys = common.keys() +keys.sort() +for k in keys: + c = common[k] + printline(c[0], c[1], k) + +keys = c1.keys() +keys.sort() +for k in keys: + printline(c1[k], ['--']*4, k) + +keys = c2.keys() +keys.sort() +for k in keys: + printline(['--']*4, c2[k], k) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/restore.sh b/bin/restore.sh new file mode 100644 index 0000000..a5da9e8 --- /dev/null +++ b/bin/restore.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env sh +# +# Simple hack script to restore __revision__, __COPYRIGHT_, 1.2.0.d20091224 +# and other similar variables to what gets checked in to source. This +# comes in handy when people send in diffs based on the released source. +# + +if test "X$*" = "X"; then + DIRS="src test" +else + DIRS="$*" +fi + +SEPARATOR="================================================================================" + +header() { + arg_space="$1 " + dots=`echo "$arg_space" | sed 's/./\./g'` + echo "$SEPARATOR" | sed "s;$dots;$arg_space;" +} + +for i in `find $DIRS -name '*.py'`; do + header $i + ed $i <&2 + echo "${USAGE}" >&2 + exit 1 + ;; + esac +done + +shift `expr ${OPTIND} - 1` + +if test "X$1" = "X"; then + echo "${USAGE}" >&2 + exit 1 +fi + +if test "X${AEGIS_PROJECT}" = "X"; then + echo "$PROG: No AEGIS_PROJECT set." >&2 + echo "${USAGE}" >&2 + exit 1 +fi + +if test "X$DO" = "X"; then + DO="alrstz" +fi + +cmd() +{ + $PRINT "$*" + $EXECUTE "$*" +} + +CHANGE=$1 + +if test "X${SANITY_CHECK}" = "Xyes"; then + SCM="cvs" + SCMROOT="/home/scons/CVSROOT/scons" + DELTA=`aegis -l -ter cd ${CHANGE} | sed -n 's/.*, Delta \([0-9]*\)\./\1/p'` + if test "x${DELTA}" = "x"; then + echo "${PROG}: Could not find delta for change ${CHANGE}." >&2 + echo "Has this finished integrating? Change ${CHANGE} not distributed." >&2 + exit 1 + fi + PREV_DELTA=`expr ${DELTA} - 1` + COMMAND="scons-scmcheck -D ${PREV_DELTA} -d q -p ${AEGIS_PROJECT} -s ${SCM} ${SCMROOT}" + $PRINT "${COMMAND}" + OUTPUT=`${COMMAND}` + if test "X${OUTPUT}" != "X"; then + echo "${PROG}: ${SCMROOT} is not up to date:" >&2 + echo "${OUTPUT}" >& 2 + echo "Did you skip any changes? Change ${CHANGE} not distributed." >&2 + exit 1 + fi +fi + +if test X$EXECUTE != "X:" -a "X$SSH_AGENT_PID" = "X"; then + eval `ssh-agent` + ssh-add + trap 'eval `ssh-agent -k`; exit' 0 1 2 3 15 +fi + +cd + +BASELINE=`aesub -p ${AEGIS_PROJECT} -c ${CHANGE} '${Project trunk_name}'` + +TMPBLAE="/tmp/${BASELINE}.ae" +TMPCAE="/tmp/${AEGIS_PROJECT}.C${CHANGE}.ae" + +# Original values for SourceForge. +#SFLOGIN="stevenknight" +#SFHOST="scons.sourceforge.net" +#SFDEST="/home/groups/s/sc/scons/htdocs" + +SCONSLOGIN="scons" +SCONSHOST="manam.pair.com" +#SCONSDEST="public_html/production" +SCONSDEST="public_ftp" + +# +# Copy the baseline .ae to the constant location on SourceForge. +# +case "${DO}" in +*a* ) + cmd "aedist -s -bl -p ${AEGIS_PROJECT} > ${TMPBLAE}" + cmd "scp ${TMPBLAE} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/${BASELINE}.ae" + cmd "rm ${TMPBLAE}" + ;; +esac + +# +# Copy the latest .tar.gz and .zip files to the constant location on +# SourceForge. +# +case "${DO}" in +*z* ) + BUILD_DIST=`aegis -p ${AEGIS_PROJECT} -cd -bl`/build/dist + SCONS_SRC_TAR_GZ=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.tar.gz + SCONS_SRC_ZIP=`echo ${AEGIS_PROJECT} | sed 's/scons./scons-src-/'`*.zip + cmd "scp ${BUILD_DIST}/${SCONS_SRC_TAR_GZ} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.tar.gz" + cmd "scp ${BUILD_DIST}/${SCONS_SRC_ZIP} ${SCONSLOGIN}@${SCONSHOST}:${SCONSDEST}/scons-src-latest.zip" +esac + +# +# Sync Aegis tree with SourceForge. +# +# Cribbed and modified from Peter Miller's same-named script in +# /home/groups/a/ae/aegis/aegis at SourceForge. +# +# Guide to what this does with rsync: +# +# --rsh=ssh use ssh for the transfer +# -l copy symlinks as symlinks +# -p preserve permissions +# -r recursive +# -t preserve times +# -z compress data +# --stats file transfer statistics +# --exclude exclude files matching the pattern +# --delete delete files that don't exist locally +# --delete-excluded delete files that match the --exclude patterns +# --progress show progress during the transfer +# -v verbose +# +# We no longer use the --stats option. +# +case "${DO}" in +*r* ) + LOCAL=/home/scons/scons + REMOTE=/home/groups/s/sc/scons/scons + cmd "/usr/bin/rsync --rsh='ssh -l stevenknight' \ + -l -p -r -t -z \ + --exclude build \ + --exclude '*,D' \ + --exclude '*.pyc' \ + --exclude aegis.log \ + --exclude '.sconsign*' \ + --delete --delete-excluded \ + --progress -v \ + ${LOCAL}/. scons.sourceforge.net:${REMOTE}/." + ;; +esac + +# +# Sync the CVS tree with the local repository. +# +case "${DO}" in +*l* ) + ( + export CVSROOT=/home/scons/CVSROOT/scons + #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/baldmt.com/scons" + cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}" + ) + ;; +esac + +# +# Sync the Subversion tree with Tigris.org. +# +case "${DO}" in +*t* ) + ( + SVN=http://scons.tigris.org/svn/scons + case ${AEGIS_PROJECT} in + scons.0.96 ) + SVN_URL=${SVN}/branches/core + ;; + scons.0.96.513 ) + SVN_URL=${SVN}/branches/sigrefactor + ;; + * ) + echo "$PROG: Don't know SVN branch for '${AEGIS_PROJECT}'" >&2 + exit 1 + ;; + esac + SVN_CO_FLAGS="--username stevenknight" + #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/tigris.org/scons" + cmd "ae-svn-ci ${AEGIS_PROJECT} ${CHANGE} ${SVN_URL} ${SVN_CO_FLAGS}" + ) + ;; +esac + +# +# Sync the CVS tree with SourceForge. +# +case "${DO}" in +*s* ) + ( + export CVS_RSH=ssh + export CVSROOT=:ext:stevenknight@scons.cvs.sourceforge.net:/cvsroot/scons + #cmd "ae2cvs -X -aegis -p ${AEGIS_PROJECT} -c ${CHANGE} -u $HOME/SCons/sourceforge.net/scons" + cmd "ae-cvs-ci ${AEGIS_PROJECT} ${CHANGE}" + ) + ;; +esac + +# +# Send the change .ae to the scons-aedist mailing list +# +# The subject requires editing by hand... +# +#aedist -s -p ${AEGIS_PROJECT} ${CHANGE} > ${TMPCAE} +#aegis -l -p ${AEGIS_PROJECT} -c ${CHANGE} cd | +# pine -attach_and_delete ${TMPCAE} scons-aedist@lists.sourceforge.net diff --git a/bin/scons-diff.py b/bin/scons-diff.py new file mode 100644 index 0000000..11d1bda --- /dev/null +++ b/bin/scons-diff.py @@ -0,0 +1,195 @@ +#!/usr/bin/env python +# +# scons-diff.py - diff-like utility for comparing SCons trees +# +# This supports most common diff options (with some quirks, like you can't +# just say -c and have it use a default value), but canonicalizes the +# various version strings within the file like __revision__, __build__, +# etc. so that you can diff trees without having to ignore changes in +# version lines. +# + +import difflib +import getopt +import os.path +import re +import sys + +Usage = """\ +Usage: scons-diff.py [OPTIONS] dir1 dir2 +Options: + -c NUM, --context=NUM Print NUM lines of copied context. + -h, --help Print this message and exit. + -n Don't canonicalize SCons lines. + -q, --quiet Print only whether files differ. + -r, --recursive Recursively compare found subdirectories. + -s Report when two files are the same. + -u NUM, --unified=NUM Print NUM lines of unified context. +""" + +opts, args = getopt.getopt(sys.argv[1:], + 'c:dhnqrsu:', + ['context=', 'help', 'recursive', 'unified=']) + +diff_type = None +edit_type = None +context = 2 +recursive = False +report_same = False +diff_options = [] + +def diff_line(left, right): + if diff_options: + opts = ' ' + ' '.join(diff_options) + else: + opts = '' + print 'diff%s %s %s' % (opts, left, right) + +for o, a in opts: + if o in ('-c', '-u'): + diff_type = o + context = int(a) + diff_options.append(o) + elif o in ('-h', '--help'): + print Usage + sys.exit(0) + elif o in ('-n'): + diff_options.append(o) + edit_type = o + elif o in ('-q'): + diff_type = o + diff_line = lambda l, r: None + elif o in ('-r', '--recursive'): + recursive = True + diff_options.append(o) + elif o in ('-s'): + report_same = True + +try: + left, right = args +except ValueError: + sys.stderr.write(Usage) + sys.exit(1) + +def quiet_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A function with the same calling signature as difflib.context_diff + (diff -c) and difflib.unified_diff (diff -u) but which prints + output like the simple, unadorned 'diff" command. + """ + if a == b: + return [] + else: + return ['Files %s and %s differ\n' % (fromfile, tofile)] + +def simple_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + """ + A function with the same calling signature as difflib.context_diff + (diff -c) and difflib.unified_diff (diff -u) but which prints + output like the simple, unadorned 'diff" command. + """ + sm = difflib.SequenceMatcher(None, a, b) + def comma(x1, x2): + return x1+1 == x2 and str(x2) or '%s,%s' % (x1+1, x2) + result = [] + for op, a1, a2, b1, b2 in sm.get_opcodes(): + if op == 'delete': + result.append("%sd%d\n" % (comma(a1, a2), b1)) + result.extend(map(lambda l: '< ' + l, a[a1:a2])) + elif op == 'insert': + result.append("%da%s\n" % (a1, comma(b1, b2))) + result.extend(map(lambda l: '> ' + l, b[b1:b2])) + elif op == 'replace': + result.append("%sc%s\n" % (comma(a1, a2), comma(b1, b2))) + result.extend(map(lambda l: '< ' + l, a[a1:a2])) + result.append('---\n') + result.extend(map(lambda l: '> ' + l, b[b1:b2])) + return result + +diff_map = { + '-c' : difflib.context_diff, + '-q' : quiet_diff, + '-u' : difflib.unified_diff, +} + +diff_function = diff_map.get(diff_type, simple_diff) + +baseline_re = re.compile('(# |@REM )/home/\S+/baseline/') +comment_rev_re = re.compile('(# |@REM )(\S+) 0.96.[CD]\d+ \S+ \S+( knight)') +revision_re = re.compile('__revision__ = "[^"]*"') +build_re = re.compile('__build__ = "[^"]*"') +date_re = re.compile('__date__ = "[^"]*"') + +def lines_read(file): + return open(file).readlines() + +def lines_massage(file): + text = open(file).read() + text = baseline_re.sub('\\1', text) + text = comment_rev_re.sub('\\1\\2\\3', text) + text = revision_re.sub('__revision__ = "bin/scons-diff.py"', text) + text = build_re.sub('__build__ = "0.96.92.DXXX"', text) + text = date_re.sub('__date__ = "2006/08/25 02:59:00"', text) + return text.splitlines(1) + +lines_map = { + '-n' : lines_read, +} + +lines_function = lines_map.get(edit_type, lines_massage) + +def do_diff(left, right, diff_subdirs): + if os.path.isfile(left) and os.path.isfile(right): + diff_file(left, right) + elif not os.path.isdir(left): + diff_file(left, os.path.join(right, os.path.split(left)[1])) + elif not os.path.isdir(right): + diff_file(os.path.join(left, os.path.split(right)[1]), right) + elif diff_subdirs: + diff_dir(left, right) + +def diff_file(left, right): + l = lines_function(left) + r = lines_function(right) + d = diff_function(l, r, left, right, context) + try: + text = ''.join(d) + except IndexError: + sys.stderr.write('IndexError diffing %s and %s\n' % (left, right)) + else: + if text: + diff_line(left, right) + print text, + elif report_same: + print 'Files %s and %s are identical' % (left, right) + +def diff_dir(left, right): + llist = os.listdir(left) + rlist = os.listdir(right) + u = {} + for l in llist: + u[l] = 1 + for r in rlist: + u[r] = 1 + clist = [ x for x in u.keys() if x[-4:] != '.pyc' ] + clist.sort() + for x in clist: + if x in llist: + if x in rlist: + do_diff(os.path.join(left, x), + os.path.join(right, x), + recursive) + else: + print 'Only in %s: %s' % (left, x) + else: + print 'Only in %s: %s' % (right, x) + +do_diff(left, right, True) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/scons-proc.py b/bin/scons-proc.py new file mode 100644 index 0000000..cc3b085 --- /dev/null +++ b/bin/scons-proc.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python +# +# Process a list of Python and/or XML files containing SCons documentation. +# +# This script creates formatted lists of the Builders, Tools or +# construction variables documented in the specified XML files. +# +# Dependening on the options, the lists are output in either +# DocBook-formatted generated XML files containing the summary text +# and/or .mod files contining the ENTITY definitions for each item, +# or in man-page-formatted output. +# +import getopt +import os.path +import re +import string +import StringIO +import sys +import xml.sax + +import SConsDoc + +base_sys_path = [os.getcwd() + '/build/test-tar-gz/lib/scons'] + sys.path + +helpstr = """\ +Usage: scons-proc.py [--man|--xml] + [-b file(s)] [-t file(s)] [-v file(s)] [infile ...] +Options: + -b file(s) dump builder information to the specified file(s) + -t file(s) dump tool information to the specified file(s) + -v file(s) dump variable information to the specified file(s) + --man print info in man page format, each -[btv] argument + is a single file name + --xml (default) print info in SML format, each -[btv] argument + is a pair of comma-separated .gen,.mod file names +""" + +opts, args = getopt.getopt(sys.argv[1:], + "b:ht:v:", + ['builders=', 'help', + 'man', 'xml', 'tools=', 'variables=']) + +buildersfiles = None +output_type = '--xml' +toolsfiles = None +variablesfiles = None + +for o, a in opts: + if o in ['-b', '--builders']: + buildersfiles = a + elif o in ['-h', '--help']: + sys.stdout.write(helpstr) + sys.exit(0) + elif o in ['--man', '--xml']: + output_type = o + elif o in ['-t', '--tools']: + toolsfiles = a + elif o in ['-v', '--variables']: + variablesfiles = a + +h = SConsDoc.SConsDocHandler() +saxparser = xml.sax.make_parser() +saxparser.setContentHandler(h) +saxparser.setErrorHandler(h) + +xml_preamble = """\ + + +""" + +xml_postamble = """\ + +""" + +for f in args: + _, ext = os.path.splitext(f) + if ext == '.py': + dir, _ = os.path.split(f) + if dir: + sys.path = [dir] + base_sys_path + module = SConsDoc.importfile(f) + h.set_file_info(f, len(xml_preamble.split('\n'))) + try: + content = module.__scons_doc__ + except AttributeError: + content = None + else: + del module.__scons_doc__ + else: + h.set_file_info(f, len(xml_preamble.split('\n'))) + content = open(f).read() + if content: + content = content.replace('&', '&') + input = xml_preamble + content + xml_postamble + try: + saxparser.parse(StringIO.StringIO(input)) + except: + sys.stderr.write("error in %s\n" % f) + raise + +Warning = """\ + +""" + +Regular_Entities_Header = """\ + +""" + +Link_Entities_Header = """\ + +""" + +class SCons_XML: + def __init__(self, entries, **kw): + values = entries.values() + values.sort() + self.values = values + for k, v in kw.items(): + setattr(self, k, v) + def fopen(self, name): + if name == '-': + return sys.stdout + return open(name, 'w') + +class SCons_XML_to_XML(SCons_XML): + def write(self, files): + gen, mod = string.split(files, ',') + g.write_gen(gen) + g.write_mod(mod) + def write_gen(self, filename): + if not filename: + return + f = self.fopen(filename) + for v in self.values: + f.write('\n\n' % + (self.prefix, self.idfunc(v.name))) + for term in self.termfunc(v.name): + f.write('<%s>%s\n' % + (self.tag, term, self.tag)) + f.write('\n') + for chunk in v.summary.body: + f.write(str(chunk)) + if v.sets: + s = map(lambda x: '&cv-link-%s;' % x, v.sets) + f.write('\n') + f.write('Sets: ' + ', '.join(s) + '.\n') + f.write('\n') + if v.uses: + u = map(lambda x: '&cv-link-%s;' % x, v.uses) + f.write('\n') + f.write('Uses: ' + ', '.join(u) + '.\n') + f.write('\n') + f.write('\n') + f.write('\n') + def write_mod(self, filename): + if not filename: + return + f = self.fopen(filename) + f.write(Warning) + f.write('\n') + f.write(Regular_Entities_Header % self.description) + f.write('\n') + for v in self.values: + f.write('%s">\n' % + (self.prefix, self.idfunc(v.name), + self.tag, self.entityfunc(v.name), self.tag)) + f.write('\n') + f.write(Warning) + f.write('\n') + f.write(Link_Entities_Header % self.description) + f.write('\n') + for v in self.values: + f.write('<%s>%s\'>\n' % + (self.prefix, self.idfunc(v.name), + self.prefix, self.idfunc(v.name), + self.tag, self.entityfunc(v.name), self.tag)) + f.write('\n') + f.write(Warning) + +class SCons_XML_to_man(SCons_XML): + def mansep(self): + return ['\n'] + def initial_chunks(self, name): + return [name] + def write(self, filename): + if not filename: + return + f = self.fopen(filename) + chunks = [] + for v in self.values: + chunks.extend(self.mansep()) + for n in self.initial_chunks(v.name): + chunks.append('.IP %s\n' % n) + chunks.extend(map(str, v.summary.body)) + + body = ''.join(chunks) + body = string.replace(body, '', '.ES') + body = string.replace(body, '', '.EE') + body = string.replace(body, '\n\n\n', '\n\n') + body = string.replace(body, '\n', '') + body = string.replace(body, '', '\n') + body = string.replace(body, '\n', '') + body = re.sub('\.EE\n\n+(?!\.IP)', '.EE\n.IP\n', body) + body = re.sub('&(scons|SConstruct|SConscript|jar);', r'\\fB\1\\fP', body) + body = string.replace(body, '&Dir;', r'\fBDir\fP') + body = string.replace(body, '⌖', r'\fItarget\fP') + body = string.replace(body, '&source;', r'\fIsource\fP') + body = re.sub('&b(-link)?-([^;]*);', r'\\fB\2\\fP()', body) + body = re.sub('&cv(-link)?-([^;]*);', r'$\2', body) + body = re.sub(r'<(command|envar|filename|literal|option)>([^<]*)', + r'\\fB\2\\fP', body) + body = re.sub(r'<(classname|emphasis|varname)>([^<]*)', + r'\\fI\2\\fP', body) + body = re.compile(r'^\\f([BI])(.*)\\fP\s*$', re.M).sub(r'.\1 \2', body) + body = re.compile(r'^\\f([BI])(.*)\\fP(\S+)', re.M).sub(r'.\1R \2 \3', body) + body = string.replace(body, '<', '<') + body = string.replace(body, '>', '>') + body = re.sub(r'\\([^f])', r'\\\\\1', body) + body = re.compile("^'\\\\\\\\", re.M).sub("'\\\\", body) + body = re.compile(r'^\.([BI]R?) -', re.M).sub(r'.\1 \-', body) + body = re.compile(r'^\.([BI]R?) (\S+)\\\\(\S+)', re.M).sub(r'.\1 "\2\\\\\\\\\2"', body) + body = re.compile(r'\\f([BI])-', re.M).sub(r'\\f\1\-', body) + f.write(body) + +if output_type == '--man': + processor_class = SCons_XML_to_man +elif output_type == '--xml': + processor_class = SCons_XML_to_XML +else: + sys.stderr.write("Unknown output type '%s'\n" % output_type) + sys.exit(1) + +if buildersfiles: + g = processor_class(h.builders, + description = 'builder', + prefix = 'b-', + tag = 'function', + idfunc = lambda x: x, + termfunc = lambda x: [x+'()', 'env.'+x+'()'], + entityfunc = lambda x: x) + + g.mansep = lambda: ['\n', "'\\" + '"'*69 + '\n'] + g.initial_chunks = lambda n: [n+'()', 'env.'+n+'()'] + + g.write(buildersfiles) + +if toolsfiles: + g = processor_class(h.tools, + description = 'tool', + prefix = 't-', + tag = 'literal', + idfunc = lambda x: string.replace(x, '+', 'X'), + termfunc = lambda x: [x], + entityfunc = lambda x: x) + + g.write(toolsfiles) + +if variablesfiles: + g = processor_class(h.cvars, + description = 'construction variable', + prefix = 'cv-', + tag = 'envar', + idfunc = lambda x: x, + termfunc = lambda x: [x], + entityfunc = lambda x: '$'+x) + + g.write(variablesfiles) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/scons-review.sh b/bin/scons-review.sh new file mode 100755 index 0000000..f126333 --- /dev/null +++ b/bin/scons-review.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +case "$1" in +'') exec svn diff --diff-cmd diff -x -c $* ;; +-m) svn diff --diff-cmd diff -x -c $* | alpine scons-dev ;; +*) echo "Error: unknown option '$1"; exit 1 ;; +esac + +# OLD CODE FOR USE WITH AEGIS +# +#if test $# -ne 1; then +# echo "Usage: scons-review change#" >&2 +# exit 1 +#fi +#if test "X$AEGIS_PROJECT" = "X"; then +# echo "scons-review: AEGIS_PROJECT is not set" >&2 +# exit 1 +#fi +#DIR=`aegis -cd -dd $*` +#if test "X${DIR}" = "X"; then +# echo "scons-review: No Aegis directory for '$*'" >&2 +# exit 1 +#fi +#(cd ${DIR} && find * -name '*,D' | sort | xargs cat) | pine scons-dev diff --git a/bin/scons-test.py b/bin/scons-test.py new file mode 100644 index 0000000..aa03d72 --- /dev/null +++ b/bin/scons-test.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# +# A script that takes an scons-src-{version}.zip file, unwraps it in +# a temporary location, and calls runtest.py to execute one or more of +# its tests. +# +# The default is to download the latest scons-src archive from the SCons +# web site, and to execute all of the tests. +# +# With a little more work, this will become the basis of an automated +# testing and reporting system that anyone will be able to use to +# participate in testing SCons on their system and regularly reporting +# back the results. A --xml option is a stab at gathering a lot of +# relevant information about the system, the Python version, etc., +# so that problems on different platforms can be identified sooner. +# + +import getopt +import imp +import os +import os.path +import string +import sys +import tempfile +import time +import urllib +import zipfile + +helpstr = """\ +Usage: scons-test.py [-f zipfile] [-o outdir] [-v] [--xml] [runtest arguments] +Options: + -f FILE Specify input .zip FILE name + -o DIR, --out DIR Change output directory name to DIR + -v, --verbose Print file names when extracting + --xml XML output +""" + +opts, args = getopt.getopt(sys.argv[1:], + "f:o:v", + ['file=', 'out=', 'verbose', 'xml']) + +format = None +outdir = None +printname = lambda x: x +inputfile = 'http://scons.sourceforge.net/scons-src-latest.zip' + +for o, a in opts: + if o == '-f' or o == '--file': + inputfile = a + elif o == '-o' or o == '--out': + outdir = a + elif o == '-v' or o == '--verbose': + def printname(x): + print x + elif o == '--xml': + format = o + +startdir = os.getcwd() + +tempfile.template = 'scons-test.' +tempdir = tempfile.mktemp() + +if not os.path.exists(tempdir): + os.mkdir(tempdir) + def cleanup(tempdir=tempdir): + import shutil + os.chdir(startdir) + shutil.rmtree(tempdir) + sys.exitfunc = cleanup + +# Fetch the input file if it happens to be across a network somewhere. +# Ohmigod, does Python make this simple... +inputfile, headers = urllib.urlretrieve(inputfile) + +# Unzip the header file in the output directory. We use our own code +# (lifted from scons-unzip.py) to make the output subdirectory name +# match the basename of the .zip file. +zf = zipfile.ZipFile(inputfile, 'r') + +if outdir is None: + name, _ = os.path.splitext(os.path.basename(inputfile)) + outdir = os.path.join(tempdir, name) + +def outname(n, outdir=outdir): + l = [] + while 1: + n, tail = os.path.split(n) + if not n: + break + l.append(tail) + l.append(outdir) + l.reverse() + return apply(os.path.join, l) + +for name in zf.namelist(): + dest = outname(name) + dir = os.path.dirname(dest) + try: + os.makedirs(dir) + except: + pass + printname(dest) + # if the file exists, then delete it before writing + # to it so that we don't end up trying to write to a symlink: + if os.path.isfile(dest) or os.path.islink(dest): + os.unlink(dest) + if not os.path.isdir(dest): + open(dest, 'w').write(zf.read(name)) + +os.chdir(outdir) + +# Load (by hand) the SCons modules we just unwrapped so we can +# extract their version information. Note that we have to override +# SCons.Script.main() with a do_nothing() function, because loading up +# the 'scons' script will actually try to execute SCons... +src_script = os.path.join(outdir, 'src', 'script') +src_engine = os.path.join(outdir, 'src', 'engine') +src_engine_SCons = os.path.join(src_engine, 'SCons') + +fp, pname, desc = imp.find_module('SCons', [src_engine]) +SCons = imp.load_module('SCons', fp, pname, desc) + +fp, pname, desc = imp.find_module('Script', [src_engine_SCons]) +SCons.Script = imp.load_module('Script', fp, pname, desc) + +def do_nothing(): + pass +SCons.Script.main = do_nothing + +fp, pname, desc = imp.find_module('scons', [src_script]) +scons = imp.load_module('scons', fp, pname, desc) +fp.close() + +# Default is to run all the tests by passing the -a flags to runtest.py. +if not args: + runtest_args = '-a' +else: + runtest_args = string.join(args) + +if format == '--xml': + + print "" + print " " + sys_keys = ['byteorder', 'exec_prefix', 'executable', 'maxint', 'maxunicode', 'platform', 'prefix', 'version', 'version_info'] + for k in sys_keys: + print " <%s>%s" % (k, sys.__dict__[k], k) + print " " + + fmt = '%a %b %d %H:%M:%S %Y' + print " " + + print " %s" % tempdir + + def print_version_info(tag, module): + print " <%s>" % tag + print " %s" % module.__version__ + print " %s" % module.__build__ + print " %s" % module.__buildsys__ + print " %s" % module.__date__ + print " %s" % module.__developer__ + print " " % tag + + print " " + print_version_info("script", scons) + print_version_info("engine", SCons) + print " " + + environ_keys = [ + 'PATH', + 'SCONSFLAGS', + 'SCONS_LIB_DIR', + 'PYTHON_ROOT', + 'QTDIR', + + 'COMSPEC', + 'INTEL_LICENSE_FILE', + 'INCLUDE', + 'LIB', + 'MSDEVDIR', + 'OS', + 'PATHEXT', + 'SystemRoot', + 'TEMP', + 'TMP', + 'USERNAME', + 'VXDOMNTOOLS', + 'WINDIR', + 'XYZZY' + + 'ENV', + 'HOME', + 'LANG', + 'LANGUAGE', + 'LOGNAME', + 'MACHINE', + 'OLDPWD', + 'PWD', + 'OPSYS', + 'SHELL', + 'TMPDIR', + 'USER', + ] + + print " " + #keys = os.environ.keys() + keys = environ_keys + keys.sort() + for key in keys: + value = os.environ.get(key) + if value: + print " " + print " %s" % key + print " %s" % value + print " " + print " " + + command = '"%s" runtest.py -q -o - --xml %s' % (sys.executable, runtest_args) + #print command + os.system(command) + print "" + +else: + + def print_version_info(tag, module): + print "\t%s: v%s.%s, %s, by %s on %s" % (tag, + module.__version__, + module.__build__, + module.__date__, + module.__developer__, + module.__buildsys__) + + print "SCons by Steven Knight et al.:" + print_version_info("script", scons) + print_version_info("engine", SCons) + + command = '"%s" runtest.py %s' % (sys.executable, runtest_args) + #print command + os.system(command) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/scons-unzip.py b/bin/scons-unzip.py new file mode 100644 index 0000000..28c73f8 --- /dev/null +++ b/bin/scons-unzip.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# +# A quick script to unzip a .zip archive and put the files in a +# subdirectory that matches the basename of the .zip file. +# +# This is actually generic functionality, it's not SCons-specific, but +# I'm using this to make it more convenient to manage working on multiple +# changes on Windows, where I don't have access to my Aegis tools. +# + +import getopt +import os.path +import sys +import zipfile + +helpstr = """\ +Usage: scons-unzip.py [-o outdir] zipfile +Options: + -o DIR, --out DIR Change output directory name to DIR + -v, --verbose Print file names when extracting +""" + +opts, args = getopt.getopt(sys.argv[1:], + "o:v", + ['out=', 'verbose']) + +outdir = None +printname = lambda x: x + +for o, a in opts: + if o == '-o' or o == '--out': + outdir = a + elif o == '-v' or o == '--verbose': + def printname(x): + print x + +if len(args) != 1: + sys.stderr.write("scons-unzip.py: \n") + sys.exit(1) + +zf = zipfile.ZipFile(str(args[0]), 'r') + +if outdir is None: + outdir, _ = os.path.splitext(os.path.basename(args[0])) + +def outname(n, outdir=outdir): + l = [] + while 1: + n, tail = os.path.split(n) + if not n: + break + l.append(tail) + l.append(outdir) + l.reverse() + return apply(os.path.join, l) + +for name in zf.namelist(): + dest = outname(name) + dir = os.path.dirname(dest) + try: + os.makedirs(dir) + except: + pass + printname(dest) + # if the file exists, then delete it before writing + # to it so that we don't end up trying to write to a symlink: + if os.path.isfile(dest) or os.path.islink(dest): + os.unlink(dest) + if not os.path.isdir(dest): + open(dest, 'w').write(zf.read(name)) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/scons_dev_master.py b/bin/scons_dev_master.py new file mode 100644 index 0000000..5e8df41 --- /dev/null +++ b/bin/scons_dev_master.py @@ -0,0 +1,215 @@ +#!/bin/sh +# + +# A script for turning a generic Ubuntu system into a master for +# SCons development. + +import getopt +import sys + +from Command import CommandRunner, Usage + +INITIAL_PACKAGES = [ + 'subversion', +] + +INSTALL_PACKAGES = [ + 'wget', +] + +PYTHON_PACKAGES = [ + 'g++', + 'gcc', + 'make', + 'zlib1g-dev', +] + +BUILDING_PACKAGES = [ + 'docbook', + 'docbook-dsssl', + 'docbook-utils', + 'docbook-xml', + 'groff-base', + 'jade', + 'jadetex', + 'man2html', + 'python-epydoc', + 'rpm', + 'sp', + 'tar', + + # additional packages that Bill Deegan's web page suggests + #'docbook-to-man', + #'docbook-xsl', + #'docbook2x', + #'tetex-bin', + #'tetex-latex', +] + +DOCUMENTATION_PACKAGES = [ + 'docbook-doc', + 'epydoc-doc', + 'gcc-doc', + 'pkg-config', + 'python-doc', + 'sun-java5-doc', + 'sun-java6-doc', + 'swig-doc', + 'texlive-doc', +] + +TESTING_PACKAGES = [ + 'bison', + 'cssc', + 'cvs', + 'flex', + 'g++', + 'gcc', + 'gcj', + 'ghostscript', +# 'libgcj7-dev', + 'm4', + 'openssh-client', + 'openssh-server', + 'python-profiler', + 'python-all-dev', + 'rcs', + 'rpm', + 'sun-java5-jdk', + 'sun-java6-jdk', + 'swig', + 'texlive-base-bin', + 'texlive-latex-base', + 'texlive-latex-extra', + 'zip', +] + +BUILDBOT_PACKAGES = [ + 'buildbot', + 'cron', +] + +default_args = [ + 'upgrade', + 'checkout', + 'building', + 'testing', + 'python-versions', + 'scons-versions', +] + +def main(argv=None): + if argv is None: + argv = sys.argv + + short_options = 'hnqy' + long_options = ['help', 'no-exec', 'password=', 'quiet', 'username=', + 'yes', 'assume-yes'] + + helpstr = """\ +Usage: scons_dev_master.py [-hnqy] [--password PASSWORD] [--username USER] + [ACTIONS ...] + + ACTIONS (in default order): + upgrade Upgrade the system + checkout Check out SCons + building Install packages for building SCons + testing Install packages for testing SCons + scons-versions Install versions of SCons + python-versions Install versions of Python + + ACTIONS (optional): + buildbot Install packages for running BuildBot +""" + + scons_url = 'http://scons.tigris.org/svn/scons/trunk' + sudo = 'sudo' + password = '""' + username = 'guest' + yesflag = '' + + try: + try: + opts, args = getopt.getopt(argv[1:], short_options, long_options) + except getopt.error, msg: + raise Usage(msg) + + for o, a in opts: + if o in ('-h', '--help'): + print helpstr + sys.exit(0) + elif o in ('-n', '--no-exec'): + CommandRunner.execute = CommandRunner.do_not_execute + elif o in ('--password'): + password = a + elif o in ('-q', '--quiet'): + CommandRunner.display = CommandRunner.do_not_display + elif o in ('--username'): + username = a + elif o in ('-y', '--yes', '--assume-yes'): + yesflag = o + except Usage, err: + sys.stderr.write(str(err.msg) + '\n') + sys.stderr.write('use -h to get help\n') + return 2 + + if not args: + args = default_args + + initial_packages = ' '.join(INITIAL_PACKAGES) + install_packages = ' '.join(INSTALL_PACKAGES) + building_packages = ' '.join(BUILDING_PACKAGES) + testing_packages = ' '.join(TESTING_PACKAGES) + buildbot_packages = ' '.join(BUILDBOT_PACKAGES) + python_packages = ' '.join(PYTHON_PACKAGES) + + cmd = CommandRunner(locals()) + + for arg in args: + if arg == 'upgrade': + cmd.run('%(sudo)s apt-get %(yesflag)s upgrade') + elif arg == 'checkout': + cmd.run('%(sudo)s apt-get %(yesflag)s install %(initial_packages)s') + cmd.run('svn co --username guest --password "" %(scons_url)s') + elif arg == 'building': + cmd.run('%(sudo)s apt-get %(yesflag)s install %(building_packages)s') + elif arg == 'testing': + cmd.run('%(sudo)s apt-get %(yesflag)s install %(testing_packages)s') + elif arg == 'buildbot': + cmd.run('%(sudo)s apt-get %(yesflag)s install %(buildbot_packages)s') + elif arg == 'python-versions': + if install_packages: + cmd.run('%(sudo)s apt-get %(yesflag)s install %(install_packages)s') + install_packages = None + cmd.run('%(sudo)s apt-get %(yesflag)s install %(python_packages)s') + try: + import install_python + except ImportError: + msg = 'Could not import install_python; skipping python-versions.\n' + sys.stderr.write(msg) + else: + install_python.main(['install_python.py', '-a']) + elif arg == 'scons-versions': + if install_packages: + cmd.run('%(sudo)s apt-get %(yesflag)s install %(install_packages)s') + install_packages = None + try: + import install_scons + except ImportError: + msg = 'Could not import install_scons; skipping scons-versions.\n' + sys.stderr.write(msg) + else: + install_scons.main(['install_scons.py', '-a']) + else: + msg = '%s: unknown argument %s\n' + sys.stderr.write(msg % (argv[0], repr(arg))) + sys.exit(1) + +if __name__ == "__main__": + sys.exit(main()) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/sconsexamples.py b/bin/sconsexamples.py new file mode 100644 index 0000000..0a409bc --- /dev/null +++ b/bin/sconsexamples.py @@ -0,0 +1,512 @@ +#!/usr/bin/env python2 +# +# scons_examples.py - an SGML preprocessor for capturing SCons output +# and inserting into examples in our DocBook +# documentation +# + +# This script looks for some SGML tags that describe SCons example +# configurations and commands to execute in those configurations, and +# uses TestCmd.py to execute the commands and insert the output into +# the output SGML. This way, we can run a script and update all of +# our example output without having to do a lot of laborious by-hand +# checking. +# +# An "SCons example" looks like this, and essentially describes a set of +# input files (program source files as well as SConscript files): +# +# +# +# env = Environment() +# env.Program('foo') +# +# +# int main() { printf("foo.c\n"); } +# +# +# +# The contents within the tag will get written +# into a temporary directory whenever example output needs to be +# generated. By default, the contents are not inserted into text +# directly, unless you set the "printme" attribute on one or more files, +# in which case they will get inserted within a tag. +# This makes it easy to define the example at the appropriate +# point in the text where you intend to show the SConstruct file. +# +# Note that you should usually give the a "name" +# attribute so that you can refer to the example configuration later to +# run SCons and generate output. +# +# If you just want to show a file's contents without worry about running +# SCons, there's a shorter tag: +# +# +# env = Environment() +# env.Program('foo') +# +# +# This is essentially equivalent to , +# but it's more straightforward. +# +# SCons output is generated from the following sort of tag: +# +# +# scons -Q foo +# scons -Q foo +# +# +# You tell it which example to use with the "example" attribute, and +# then give it a list of tags to execute. You can also supply +# an "os" tag, which specifies the type of operating system this example +# is intended to show; if you omit this, default value is "posix". +# +# The generated SGML will show the command line (with the appropriate +# command-line prompt for the operating system), execute the command in +# a temporary directory with the example files, capture the standard +# output from SCons, and insert it into the text as appropriate. +# Error output gets passed through to your error output so you +# can see if there are any problems executing the command. +# + +import os +import os.path +import re +import sgmllib +import string +import sys + +sys.path.append(os.path.join(os.getcwd(), 'etc')) +sys.path.append(os.path.join(os.getcwd(), 'build', 'etc')) + +scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py') +if not os.path.exists(scons_py): + scons_py = os.path.join('src', 'script', 'scons.py') + +scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine') +if not os.path.exists(scons_lib_dir): + scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine') + +import TestCmd + +# The regular expression that identifies entity references in the +# standard sgmllib omits the underscore from the legal characters. +# Override it with our own regular expression that adds underscore. +sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]') + +class DataCollector: + """Generic class for collecting data between a start tag and end + tag. We subclass for various types of tags we care about.""" + def __init__(self): + self.data = "" + def afunc(self, data): + self.data = self.data + data + +class Example(DataCollector): + """An SCons example. This is essentially a list of files that + will get written to a temporary directory to collect output + from one or more SCons runs.""" + def __init__(self): + DataCollector.__init__(self) + self.files = [] + self.dirs = [] + +class File(DataCollector): + """A file, that will get written out to a temporary directory + for one or more SCons runs.""" + def __init__(self, name): + DataCollector.__init__(self) + self.name = name + +class Directory(DataCollector): + """A directory, that will get created in a temporary directory + for one or more SCons runs.""" + def __init__(self, name): + DataCollector.__init__(self) + self.name = name + +class Output(DataCollector): + """Where the command output goes. This is essentially + a list of commands that will get executed.""" + def __init__(self): + DataCollector.__init__(self) + self.commandlist = [] + +class Command(DataCollector): + """A tag for where the command output goes. This is essentially + a list of commands that will get executed.""" + pass + +Prompt = { + 'posix' : '% ', + 'win32' : 'C:\\>' +} + +# Magick SCons hackery. +# +# So that our examples can still use the default SConstruct file, we +# actually feed the following into SCons via stdin and then have it +# SConscript() the SConstruct file. This stdin wrapper creates a set +# of ToolSurrogates for the tools for the appropriate platform. These +# Surrogates print output like the real tools and behave like them +# without actually having to be on the right platform or have the right +# tool installed. +# +# The upshot: We transparently change the world out from under the +# top-level SConstruct file in an example just so we can get the +# command output. + +Stdin = """\ +import string +import SCons.Defaults + +platform = '%s' + +class Curry: + def __init__(self, fun, *args, **kwargs): + self.fun = fun + self.pending = args[:] + self.kwargs = kwargs.copy() + + def __call__(self, *args, **kwargs): + if kwargs and self.kwargs: + kw = self.kwargs.copy() + kw.update(kwargs) + else: + kw = kwargs or self.kwargs + + return apply(self.fun, self.pending + args, kw) + +def Str(target, source, env, cmd=""): + result = [] + for cmd in env.subst_list(cmd, target=target, source=source): + result.append(string.join(map(str, cmd))) + return string.join(result, '\\n') + +class ToolSurrogate: + def __init__(self, tool, variable, func): + self.tool = tool + self.variable = variable + self.func = func + def __call__(self, env): + t = Tool(self.tool) + t.generate(env) + orig = env[self.variable] + env[self.variable] = Action(self.func, strfunction=Curry(Str, cmd=orig)) + +def Null(target, source, env): + pass + +def Cat(target, source, env): + target = str(target[0]) + f = open(target, "wb") + for src in map(str, source): + f.write(open(src, "rb").read()) + f.close() + +ToolList = { + 'posix' : [('cc', 'CCCOM', Cat), + ('link', 'LINKCOM', Cat), + ('tar', 'TARCOM', Null), + ('zip', 'ZIPCOM', Null)], + 'win32' : [('msvc', 'CCCOM', Cat), + ('mslink', 'LINKCOM', Cat)] +} + +tools = map(lambda t: apply(ToolSurrogate, t), ToolList[platform]) + +SCons.Defaults.ConstructionEnvironment.update({ + 'PLATFORM' : platform, + 'TOOLS' : tools, +}) + +SConscript('SConstruct') +""" + +class MySGML(sgmllib.SGMLParser): + """A subclass of the standard Python 2.2 sgmllib SGML parser. + + Note that this doesn't work with the 1.5.2 sgmllib module, because + that didn't have the ability to work with ENTITY declarations. + """ + def __init__(self): + sgmllib.SGMLParser.__init__(self) + self.examples = {} + self.afunclist = [] + + def handle_data(self, data): + try: + f = self.afunclist[-1] + except IndexError: + sys.stdout.write(data) + else: + f(data) + + def handle_comment(self, data): + sys.stdout.write('') + + def handle_decl(self, data): + sys.stdout.write('') + + def unknown_starttag(self, tag, attrs): + try: + f = self.example.afunc + except AttributeError: + f = sys.stdout.write + if not attrs: + f('<' + tag + '>') + else: + f('<' + tag) + for name, value in attrs: + f(' ' + name + '=' + '"' + value + '"') + f('>') + + def unknown_endtag(self, tag): + sys.stdout.write('') + + def unknown_entityref(self, ref): + sys.stdout.write('&' + ref + ';') + + def unknown_charref(self, ref): + sys.stdout.write('&#' + ref + ';') + + def start_scons_example(self, attrs): + t = filter(lambda t: t[0] == 'name', attrs) + if t: + name = t[0][1] + try: + e = self.examples[name] + except KeyError: + e = self.examples[name] = Example() + else: + e = Example() + for name, value in attrs: + setattr(e, name, value) + self.e = e + self.afunclist.append(e.afunc) + + def end_scons_example(self): + e = self.e + files = filter(lambda f: f.printme, e.files) + if files: + sys.stdout.write('') + for f in files: + if f.printme: + i = len(f.data) - 1 + while f.data[i] == ' ': + i = i - 1 + output = string.replace(f.data[:i+1], '__ROOT__', '') + sys.stdout.write(output) + if e.data and e.data[0] == '\n': + e.data = e.data[1:] + sys.stdout.write(e.data + '') + delattr(self, 'e') + self.afunclist = self.afunclist[:-1] + + def start_file(self, attrs): + try: + e = self.e + except AttributeError: + self.error(" tag outside of ") + t = filter(lambda t: t[0] == 'name', attrs) + if not t: + self.error("no name attribute found") + try: + e.prefix + except AttributeError: + e.prefix = e.data + e.data = "" + f = File(t[0][1]) + f.printme = None + for name, value in attrs: + setattr(f, name, value) + e.files.append(f) + self.afunclist.append(f.afunc) + + def end_file(self): + self.e.data = "" + self.afunclist = self.afunclist[:-1] + + def start_directory(self, attrs): + try: + e = self.e + except AttributeError: + self.error(" tag outside of ") + t = filter(lambda t: t[0] == 'name', attrs) + if not t: + self.error("no name attribute found") + try: + e.prefix + except AttributeError: + e.prefix = e.data + e.data = "" + d = Directory(t[0][1]) + for name, value in attrs: + setattr(d, name, value) + e.dirs.append(d) + self.afunclist.append(d.afunc) + + def end_directory(self): + self.e.data = "" + self.afunclist = self.afunclist[:-1] + + def start_scons_example_file(self, attrs): + t = filter(lambda t: t[0] == 'example', attrs) + if not t: + self.error("no example attribute found") + exname = t[0][1] + try: + e = self.examples[exname] + except KeyError: + self.error("unknown example name '%s'" % exname) + fattrs = filter(lambda t: t[0] == 'name', attrs) + if not fattrs: + self.error("no name attribute found") + fname = fattrs[0][1] + f = filter(lambda f, fname=fname: f.name == fname, e.files) + if not f: + self.error("example '%s' does not have a file named '%s'" % (exname, fname)) + self.f = f[0] + + def end_scons_example_file(self): + f = self.f + sys.stdout.write('') + i = len(f.data) - 1 + while f.data[i] == ' ': + i = i - 1 + sys.stdout.write(f.data[:i+1] + '') + delattr(self, 'f') + + def start_scons_output(self, attrs): + t = filter(lambda t: t[0] == 'example', attrs) + if not t: + self.error("no example attribute found") + exname = t[0][1] + try: + e = self.examples[exname] + except KeyError: + self.error("unknown example name '%s'" % exname) + # Default values for an example. + o = Output() + o.os = 'posix' + o.e = e + # Locally-set. + for name, value in attrs: + setattr(o, name, value) + self.o = o + self.afunclist.append(o.afunc) + + def end_scons_output(self): + o = self.o + e = o.e + t = TestCmd.TestCmd(workdir='', combine=1) + t.subdir('ROOT', 'WORK') + for d in e.dirs: + dir = t.workpath('WORK', d.name) + if not os.path.exists(dir): + os.makedirs(dir) + for f in e.files: + i = 0 + while f.data[i] == '\n': + i = i + 1 + lines = string.split(f.data[i:], '\n') + i = 0 + while lines[0][i] == ' ': + i = i + 1 + lines = map(lambda l, i=i: l[i:], lines) + path = string.replace(f.name, '__ROOT__', t.workpath('ROOT')) + dir, name = os.path.split(f.name) + if dir: + dir = t.workpath('WORK', dir) + if not os.path.exists(dir): + os.makedirs(dir) + content = string.join(lines, '\n') + content = string.replace(content, + '__ROOT__', + t.workpath('ROOT')) + t.write(t.workpath('WORK', f.name), content) + i = len(o.prefix) + while o.prefix[i-1] != '\n': + i = i - 1 + sys.stdout.write('' + o.prefix[:i]) + p = o.prefix[i:] + for c in o.commandlist: + sys.stdout.write(p + Prompt[o.os]) + d = string.replace(c.data, '__ROOT__', '') + sys.stdout.write('' + d + '\n') + e = string.replace(c.data, '__ROOT__', t.workpath('ROOT')) + args = string.split(e)[1:] + os.environ['SCONS_LIB_DIR'] = scons_lib_dir + t.run(interpreter = sys.executable, + program = scons_py, + arguments = '-f - ' + string.join(args), + chdir = t.workpath('WORK'), + stdin = Stdin % o.os) + out = string.replace(t.stdout(), t.workpath('ROOT'), '') + if out: + lines = string.split(out, '\n') + if lines: + while lines[-1] == '': + lines = lines[:-1] + for l in lines: + sys.stdout.write(p + l + '\n') + #err = t.stderr() + #if err: + # sys.stderr.write(err) + if o.data[0] == '\n': + o.data = o.data[1:] + sys.stdout.write(o.data + '') + delattr(self, 'o') + self.afunclist = self.afunclist[:-1] + + def start_command(self, attrs): + try: + o = self.o + except AttributeError: + self.error(" tag outside of ") + try: + o.prefix + except AttributeError: + o.prefix = o.data + o.data = "" + c = Command() + o.commandlist.append(c) + self.afunclist.append(c.afunc) + + def end_command(self): + self.o.data = "" + self.afunclist = self.afunclist[:-1] + + def start_sconstruct(self, attrs): + sys.stdout.write('') + + def end_sconstruct(self): + sys.stdout.write('') + +try: + file = sys.argv[1] +except IndexError: + file = '-' + +if file == '-': + f = sys.stdin +else: + try: + f = open(file, 'r') + except IOError, msg: + print file, ":", msg + sys.exit(1) + +data = f.read() +if f is not sys.stdin: + f.close() + +x = MySGML() +for c in data: + x.feed(c) +x.close() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/sconsoutput.py b/bin/sconsoutput.py new file mode 100644 index 0000000..f99ec5a --- /dev/null +++ b/bin/sconsoutput.py @@ -0,0 +1,827 @@ +#!/usr/bin/env python2 + +# +# Copyright (c) 2003 Steven Knight +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "/home/scons/sconsoutput/branch.0/baseline/src/sconsoutput.py 0.4.D001 2004/11/27 18:44:37 knight" + +# +# sconsoutput.py - an SGML preprocessor for capturing SCons output +# and inserting it into examples in our DocBook +# documentation +# +# This script looks for some SGML tags that describe SCons example +# configurations and commands to execute in those configurations, and +# uses TestCmd.py to execute the commands and insert the output from +# those commands into the SGML that we output. This way, we can run a +# script and update all of our example documentation output without +# a lot of laborious by-hand checking. +# +# An "SCons example" looks like this, and essentially describes a set of +# input files (program source files as well as SConscript files): +# +# +# +# env = Environment() +# env.Program('foo') +# +# +# int main() { printf("foo.c\n"); } +# +# +# +# The contents within the tag will get written +# into a temporary directory whenever example output needs to be +# generated. By default, the contents are not inserted into text +# directly, unless you set the "printme" attribute on one or more files, +# in which case they will get inserted within a tag. +# This makes it easy to define the example at the appropriate +# point in the text where you intend to show the SConstruct file. +# +# Note that you should usually give the a "name" +# attribute so that you can refer to the example configuration later to +# run SCons and generate output. +# +# If you just want to show a file's contents without worry about running +# SCons, there's a shorter tag: +# +# +# env = Environment() +# env.Program('foo') +# +# +# This is essentially equivalent to , +# but it's more straightforward. +# +# SCons output is generated from the following sort of tag: +# +# +# scons -Q foo +# scons -Q foo +# +# +# You tell it which example to use with the "example" attribute, and then +# give it a list of tags to execute. You can also +# supply an "os" tag, which specifies the type of operating system this +# example is intended to show; if you omit this, default value is "posix". +# +# The generated SGML will show the command line (with the appropriate +# command-line prompt for the operating system), execute the command in +# a temporary directory with the example files, capture the standard +# output from SCons, and insert it into the text as appropriate. +# Error output gets passed through to your error output so you +# can see if there are any problems executing the command. +# + +import os +import os.path +import re +import sgmllib +import string +import sys +import time + +sys.path.append(os.path.join(os.getcwd(), 'QMTest')) +sys.path.append(os.path.join(os.getcwd(), 'build', 'QMTest')) + +scons_py = os.path.join('bootstrap', 'src', 'script', 'scons.py') +if not os.path.exists(scons_py): + scons_py = os.path.join('src', 'script', 'scons.py') + +scons_lib_dir = os.path.join(os.getcwd(), 'bootstrap', 'src', 'engine') +if not os.path.exists(scons_lib_dir): + scons_lib_dir = os.path.join(os.getcwd(), 'src', 'engine') + +os.environ['SCONS_LIB_DIR'] = scons_lib_dir + +import TestCmd + +# The regular expression that identifies entity references in the +# standard sgmllib omits the underscore from the legal characters. +# Override it with our own regular expression that adds underscore. +sgmllib.entityref = re.compile('&([a-zA-Z][-_.a-zA-Z0-9]*)[^-_a-zA-Z0-9]') + +# Classes for collecting different types of data we're interested in. +class DataCollector: + """Generic class for collecting data between a start tag and end + tag. We subclass for various types of tags we care about.""" + def __init__(self): + self.data = "" + def afunc(self, data): + self.data = self.data + data + +class Example(DataCollector): + """An SCons example. This is essentially a list of files that + will get written to a temporary directory to collect output + from one or more SCons runs.""" + def __init__(self): + DataCollector.__init__(self) + self.files = [] + self.dirs = [] + +class File(DataCollector): + """A file, that will get written out to a temporary directory + for one or more SCons runs.""" + def __init__(self, name): + DataCollector.__init__(self) + self.name = name + +class Directory(DataCollector): + """A directory, that will get created in a temporary directory + for one or more SCons runs.""" + def __init__(self, name): + DataCollector.__init__(self) + self.name = name + +class Output(DataCollector): + """Where the command output goes. This is essentially + a list of commands that will get executed.""" + def __init__(self): + DataCollector.__init__(self) + self.commandlist = [] + +class Command(DataCollector): + """A tag for where the command output goes. This is essentially + a list of commands that will get executed.""" + def __init__(self): + DataCollector.__init__(self) + self.output = None + +Prompt = { + 'posix' : '% ', + 'win32' : 'C:\\>' +} + +# The magick SCons hackery that makes this work. +# +# So that our examples can still use the default SConstruct file, we +# actually feed the following into SCons via stdin and then have it +# SConscript() the SConstruct file. This stdin wrapper creates a set +# of ToolSurrogates for the tools for the appropriate platform. These +# Surrogates print output like the real tools and behave like them +# without actually having to be on the right platform or have the right +# tool installed. +# +# The upshot: The wrapper transparently changes the world out from +# under the top-level SConstruct file in an example just so we can get +# the command output. + +Stdin = """\ +import os +import re +import string +import SCons.Action +import SCons.Defaults +import SCons.Node.FS + +platform = '%(osname)s' + +Sep = { + 'posix' : '/', + 'win32' : '\\\\', +}[platform] + + +# Slip our own __str__() method into the EntryProxy class used to expand +# $TARGET{S} and $SOURCE{S} to translate the path-name separators from +# what's appropriate for the system we're running on to what's appropriate +# for the example system. +orig = SCons.Node.FS.EntryProxy +class MyEntryProxy(orig): + def __str__(self): + return string.replace(str(self._Proxy__subject), os.sep, Sep) +SCons.Node.FS.EntryProxy = MyEntryProxy + +# Slip our own RDirs() method into the Node.FS.File class so that the +# expansions of $_{CPPINC,F77INC,LIBDIR}FLAGS will have the path-name +# separators translated from what's appropriate for the system we're +# running on to what's appropriate for the example system. +orig_RDirs = SCons.Node.FS.File.RDirs +def my_RDirs(self, pathlist, orig_RDirs=orig_RDirs): + return map(lambda x: string.replace(str(x), os.sep, Sep), + orig_RDirs(self, pathlist)) +SCons.Node.FS.File.RDirs = my_RDirs + +class Curry: + def __init__(self, fun, *args, **kwargs): + self.fun = fun + self.pending = args[:] + self.kwargs = kwargs.copy() + + def __call__(self, *args, **kwargs): + if kwargs and self.kwargs: + kw = self.kwargs.copy() + kw.update(kwargs) + else: + kw = kwargs or self.kwargs + + return apply(self.fun, self.pending + args, kw) + +def Str(target, source, env, cmd=""): + result = [] + for cmd in env.subst_list(cmd, target=target, source=source): + result.append(string.join(map(str, cmd))) + return string.join(result, '\\n') + +class ToolSurrogate: + def __init__(self, tool, variable, func, varlist): + self.tool = tool + if not type(variable) is type([]): + variable = [variable] + self.variable = variable + self.func = func + self.varlist = varlist + def __call__(self, env): + t = Tool(self.tool) + t.generate(env) + for v in self.variable: + orig = env[v] + try: + strfunction = orig.strfunction + except AttributeError: + strfunction = Curry(Str, cmd=orig) + # Don't call Action() through its global function name, because + # that leads to infinite recursion in trying to initialize the + # Default Environment. + env[v] = SCons.Action.Action(self.func, + strfunction=strfunction, + varlist=self.varlist) + def __repr__(self): + # This is for the benefit of printing the 'TOOLS' + # variable through env.Dump(). + return repr(self.tool) + +def Null(target, source, env): + pass + +def Cat(target, source, env): + target = str(target[0]) + f = open(target, "wb") + for src in map(str, source): + f.write(open(src, "rb").read()) + f.close() + +def CCCom(target, source, env): + target = str(target[0]) + fp = open(target, "wb") + def process(source_file, fp=fp): + for line in open(source_file, "rb").readlines(): + m = re.match(r'#include\s[<"]([^<"]+)[>"]', line) + if m: + include = m.group(1) + for d in [str(env.Dir('$CPPPATH')), '.']: + f = os.path.join(d, include) + if os.path.exists(f): + process(f) + break + elif line[:11] != "STRIP CCCOM": + fp.write(line) + for src in map(str, source): + process(src) + fp.write('debug = ' + ARGUMENTS.get('debug', '0') + '\\n') + fp.close() + +public_class_re = re.compile('^public class (\S+)', re.MULTILINE) + +def JavaCCom(target, source, env): + # This is a fake Java compiler that just looks for + # public class FooBar + # lines in the source file(s) and spits those out + # to .class files named after the class. + tlist = map(str, target) + not_copied = {} + for t in tlist: + not_copied[t] = 1 + for src in map(str, source): + contents = open(src, "rb").read() + classes = public_class_re.findall(contents) + for c in classes: + for t in filter(lambda x: string.find(x, c) != -1, tlist): + open(t, "wb").write(contents) + del not_copied[t] + for t in not_copied.keys(): + open(t, "wb").write("\\n") + +def JavaHCom(target, source, env): + tlist = map(str, target) + slist = map(str, source) + for t, s in zip(tlist, slist): + open(t, "wb").write(open(s, "rb").read()) + +def find_class_files(arg, dirname, names): + class_files = filter(lambda n: n[-6:] == '.class', names) + paths = map(lambda n, d=dirname: os.path.join(d, n), class_files) + arg.extend(paths) + +def JarCom(target, source, env): + target = str(target[0]) + class_files = [] + for src in map(str, source): + os.path.walk(src, find_class_files, class_files) + f = open(target, "wb") + for cf in class_files: + f.write(open(cf, "rb").read()) + f.close() + +# XXX Adding COLOR, COLORS and PACKAGE to the 'cc' varlist(s) by hand +# here is bogus. It's for the benefit of doc/user/command-line.in, which +# uses examples that want to rebuild based on changes to these variables. +# It would be better to figure out a way to do it based on the content of +# the generated command-line, or else find a way to let the example markup +# language in doc/user/command-line.in tell this script what variables to +# add, but that's more difficult than I want to figure out how to do right +# now, so let's just use the simple brute force approach for the moment. + +ToolList = { + 'posix' : [('cc', ['CCCOM', 'SHCCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']), + ('link', ['LINKCOM', 'SHLINKCOM'], Cat, []), + ('ar', ['ARCOM', 'RANLIBCOM'], Cat, []), + ('tar', 'TARCOM', Null, []), + ('zip', 'ZIPCOM', Null, []), + ('BitKeeper', 'BITKEEPERCOM', Cat, []), + ('CVS', 'CVSCOM', Cat, []), + ('RCS', 'RCS_COCOM', Cat, []), + ('SCCS', 'SCCSCOM', Cat, []), + ('javac', 'JAVACCOM', JavaCCom, []), + ('javah', 'JAVAHCOM', JavaHCom, []), + ('jar', 'JARCOM', JarCom, []), + ('rmic', 'RMICCOM', Cat, []), + ], + 'win32' : [('msvc', ['CCCOM', 'SHCCCOM', 'RCCOM'], CCCom, ['CCFLAGS', 'CPPDEFINES', 'COLOR', 'COLORS', 'PACKAGE']), + ('mslink', ['LINKCOM', 'SHLINKCOM'], Cat, []), + ('mslib', 'ARCOM', Cat, []), + ('tar', 'TARCOM', Null, []), + ('zip', 'ZIPCOM', Null, []), + ('BitKeeper', 'BITKEEPERCOM', Cat, []), + ('CVS', 'CVSCOM', Cat, []), + ('RCS', 'RCS_COCOM', Cat, []), + ('SCCS', 'SCCSCOM', Cat, []), + ('javac', 'JAVACCOM', JavaCCom, []), + ('javah', 'JAVAHCOM', JavaHCom, []), + ('jar', 'JARCOM', JarCom, []), + ('rmic', 'RMICCOM', Cat, []), + ], +} + +toollist = ToolList[platform] +filter_tools = string.split('%(tools)s') +if filter_tools: + toollist = filter(lambda x, ft=filter_tools: x[0] in ft, toollist) + +toollist = map(lambda t: apply(ToolSurrogate, t), toollist) + +toollist.append('install') + +def surrogate_spawn(sh, escape, cmd, args, env): + pass + +def surrogate_pspawn(sh, escape, cmd, args, env, stdout, stderr): + pass + +SCons.Defaults.ConstructionEnvironment.update({ + 'PLATFORM' : platform, + 'TOOLS' : toollist, + 'SPAWN' : surrogate_spawn, + 'PSPAWN' : surrogate_pspawn, +}) + +SConscript('SConstruct') +""" + +# "Commands" that we will execute in our examples. +def command_scons(args, c, test, dict): + save_vals = {} + delete_keys = [] + try: + ce = c.environment + except AttributeError: + pass + else: + for arg in string.split(c.environment): + key, val = string.split(arg, '=') + try: + save_vals[key] = os.environ[key] + except KeyError: + delete_keys.append(key) + os.environ[key] = val + test.run(interpreter = sys.executable, + program = scons_py, + arguments = '-f - ' + string.join(args), + chdir = test.workpath('WORK'), + stdin = Stdin % dict) + os.environ.update(save_vals) + for key in delete_keys: + del(os.environ[key]) + out = test.stdout() + out = string.replace(out, test.workpath('ROOT'), '') + out = string.replace(out, test.workpath('WORK/SConstruct'), + '/home/my/project/SConstruct') + lines = string.split(out, '\n') + if lines: + while lines[-1] == '': + lines = lines[:-1] + #err = test.stderr() + #if err: + # sys.stderr.write(err) + return lines + +def command_touch(args, c, test, dict): + if args[0] == '-t': + t = int(time.mktime(time.strptime(args[1], '%Y%m%d%H%M'))) + times = (t, t) + args = args[2:] + else: + time.sleep(1) + times = None + for file in args: + if not os.path.isabs(file): + file = os.path.join(test.workpath('WORK'), file) + if not os.path.exists(file): + open(file, 'wb') + os.utime(file, times) + return [] + +def command_edit(args, c, test, dict): + try: + add_string = c.edit[:] + except AttributeError: + add_string = 'void edit(void) { ; }\n' + if add_string[-1] != '\n': + add_string = add_string + '\n' + for file in args: + if not os.path.isabs(file): + file = os.path.join(test.workpath('WORK'), file) + contents = open(file, 'rb').read() + open(file, 'wb').write(contents + add_string) + return [] + +def command_ls(args, c, test, dict): + def ls(a): + files = os.listdir(a) + files = filter(lambda x: x[0] != '.', files) + files.sort() + return [string.join(files, ' ')] + if args: + l = [] + for a in args: + l.extend(ls(test.workpath('WORK', a))) + return l + else: + return ls(test.workpath('WORK')) + +CommandDict = { + 'scons' : command_scons, + 'touch' : command_touch, + 'edit' : command_edit, + 'ls' : command_ls, +} + +def ExecuteCommand(args, c, t, dict): + try: + func = CommandDict[args[0]] + except KeyError: + func = lambda args, c, t, dict: [] + return func(args[1:], c, t, dict) + +class MySGML(sgmllib.SGMLParser): + """A subclass of the standard Python 2.2 sgmllib SGML parser. + + This extends the standard sgmllib parser to recognize, and do cool + stuff with, the added tags that describe our SCons examples, + commands, and other stuff. + + Note that this doesn't work with the 1.5.2 sgmllib module, because + that didn't have the ability to work with ENTITY declarations. + """ + def __init__(self): + sgmllib.SGMLParser.__init__(self) + self.examples = {} + self.afunclist = [] + + # The first set of methods here essentially implement pass-through + # handling of most of the stuff in an SGML file. We're really + # only concerned with the tags specific to SCons example processing, + # the methods for which get defined below. + + def handle_data(self, data): + try: + f = self.afunclist[-1] + except IndexError: + sys.stdout.write(data) + else: + f(data) + + def handle_comment(self, data): + sys.stdout.write('') + + def handle_decl(self, data): + sys.stdout.write('') + + def unknown_starttag(self, tag, attrs): + try: + f = self.example.afunc + except AttributeError: + f = sys.stdout.write + if not attrs: + f('<' + tag + '>') + else: + f('<' + tag) + for name, value in attrs: + f(' ' + name + '=' + '"' + value + '"') + f('>') + + def unknown_endtag(self, tag): + sys.stdout.write('') + + def unknown_entityref(self, ref): + sys.stdout.write('&' + ref + ';') + + def unknown_charref(self, ref): + sys.stdout.write('&#' + ref + ';') + + # Here is where the heavy lifting begins. The following methods + # handle the begin-end tags of our SCons examples. + + def start_scons_example(self, attrs): + t = filter(lambda t: t[0] == 'name', attrs) + if t: + name = t[0][1] + try: + e = self.examples[name] + except KeyError: + e = self.examples[name] = Example() + else: + e = Example() + for name, value in attrs: + setattr(e, name, value) + self.e = e + self.afunclist.append(e.afunc) + + def end_scons_example(self): + e = self.e + files = filter(lambda f: f.printme, e.files) + if files: + sys.stdout.write('') + for f in files: + if f.printme: + i = len(f.data) - 1 + while f.data[i] == ' ': + i = i - 1 + output = string.replace(f.data[:i+1], '__ROOT__', '') + output = string.replace(output, '<', '<') + output = string.replace(output, '>', '>') + sys.stdout.write(output) + if e.data and e.data[0] == '\n': + e.data = e.data[1:] + sys.stdout.write(e.data + '') + delattr(self, 'e') + self.afunclist = self.afunclist[:-1] + + def start_file(self, attrs): + try: + e = self.e + except AttributeError: + self.error(" tag outside of ") + t = filter(lambda t: t[0] == 'name', attrs) + if not t: + self.error("no name attribute found") + try: + e.prefix + except AttributeError: + e.prefix = e.data + e.data = "" + f = File(t[0][1]) + f.printme = None + for name, value in attrs: + setattr(f, name, value) + e.files.append(f) + self.afunclist.append(f.afunc) + + def end_file(self): + self.e.data = "" + self.afunclist = self.afunclist[:-1] + + def start_directory(self, attrs): + try: + e = self.e + except AttributeError: + self.error(" tag outside of ") + t = filter(lambda t: t[0] == 'name', attrs) + if not t: + self.error("no name attribute found") + try: + e.prefix + except AttributeError: + e.prefix = e.data + e.data = "" + d = Directory(t[0][1]) + for name, value in attrs: + setattr(d, name, value) + e.dirs.append(d) + self.afunclist.append(d.afunc) + + def end_directory(self): + self.e.data = "" + self.afunclist = self.afunclist[:-1] + + def start_scons_example_file(self, attrs): + t = filter(lambda t: t[0] == 'example', attrs) + if not t: + self.error("no example attribute found") + exname = t[0][1] + try: + e = self.examples[exname] + except KeyError: + self.error("unknown example name '%s'" % exname) + fattrs = filter(lambda t: t[0] == 'name', attrs) + if not fattrs: + self.error("no name attribute found") + fname = fattrs[0][1] + f = filter(lambda f, fname=fname: f.name == fname, e.files) + if not f: + self.error("example '%s' does not have a file named '%s'" % (exname, fname)) + self.f = f[0] + + def end_scons_example_file(self): + f = self.f + sys.stdout.write('') + sys.stdout.write(f.data + '') + delattr(self, 'f') + + def start_scons_output(self, attrs): + t = filter(lambda t: t[0] == 'example', attrs) + if not t: + self.error("no example attribute found") + exname = t[0][1] + try: + e = self.examples[exname] + except KeyError: + self.error("unknown example name '%s'" % exname) + # Default values for an example. + o = Output() + o.preserve = None + o.os = 'posix' + o.tools = '' + o.e = e + # Locally-set. + for name, value in attrs: + setattr(o, name, value) + self.o = o + self.afunclist.append(o.afunc) + + def end_scons_output(self): + # The real raison d'etre for this script, this is where we + # actually execute SCons to fetch the output. + o = self.o + e = o.e + t = TestCmd.TestCmd(workdir='', combine=1) + if o.preserve: + t.preserve() + t.subdir('ROOT', 'WORK') + t.rootpath = string.replace(t.workpath('ROOT'), '\\', '\\\\') + + for d in e.dirs: + dir = t.workpath('WORK', d.name) + if not os.path.exists(dir): + os.makedirs(dir) + + for f in e.files: + i = 0 + while f.data[i] == '\n': + i = i + 1 + lines = string.split(f.data[i:], '\n') + i = 0 + while lines[0][i] == ' ': + i = i + 1 + lines = map(lambda l, i=i: l[i:], lines) + path = string.replace(f.name, '__ROOT__', t.rootpath) + if not os.path.isabs(path): + path = t.workpath('WORK', path) + dir, name = os.path.split(path) + if dir and not os.path.exists(dir): + os.makedirs(dir) + content = string.join(lines, '\n') + content = string.replace(content, '__ROOT__', t.rootpath) + path = t.workpath('WORK', path) + t.write(path, content) + if hasattr(f, 'chmod'): + os.chmod(path, int(f.chmod, 0)) + + i = len(o.prefix) + while o.prefix[i-1] != '\n': + i = i - 1 + + sys.stdout.write('' + o.prefix[:i]) + p = o.prefix[i:] + + for c in o.commandlist: + sys.stdout.write(p + Prompt[o.os]) + d = string.replace(c.data, '__ROOT__', '') + sys.stdout.write('' + d + '\n') + + e = string.replace(c.data, '__ROOT__', t.workpath('ROOT')) + args = string.split(e) + lines = ExecuteCommand(args, c, t, {'osname':o.os, 'tools':o.tools}) + content = None + if c.output: + content = c.output + elif lines: + content = string.join(lines, '\n' + p) + if content: + content = re.sub(' at 0x[0-9a-fA-F]*\>', ' at 0x700000>', content) + content = string.replace(content, '<', '<') + content = string.replace(content, '>', '>') + sys.stdout.write(p + content + '\n') + + if o.data[0] == '\n': + o.data = o.data[1:] + sys.stdout.write(o.data + '') + delattr(self, 'o') + self.afunclist = self.afunclist[:-1] + + def start_scons_output_command(self, attrs): + try: + o = self.o + except AttributeError: + self.error(" tag outside of ") + try: + o.prefix + except AttributeError: + o.prefix = o.data + o.data = "" + c = Command() + for name, value in attrs: + setattr(c, name, value) + o.commandlist.append(c) + self.afunclist.append(c.afunc) + + def end_scons_output_command(self): + self.o.data = "" + self.afunclist = self.afunclist[:-1] + + def start_sconstruct(self, attrs): + f = File('') + self.f = f + self.afunclist.append(f.afunc) + + def end_sconstruct(self): + f = self.f + sys.stdout.write('') + output = string.replace(f.data, '__ROOT__', '') + sys.stdout.write(output + '') + delattr(self, 'f') + self.afunclist = self.afunclist[:-1] + +# The main portion of the program itself, short and simple. +try: + file = sys.argv[1] +except IndexError: + file = '-' + +if file == '-': + f = sys.stdin +else: + try: + f = open(file, 'r') + except IOError, msg: + print file, ":", msg + sys.exit(1) + +data = f.read() +if f is not sys.stdin: + f.close() + +if data.startswith(' tag in the output, which contains a list of items + that have tags in them. Right now, these just overwrite + each other in the Arifact object we create. + + We also don't pay attention to any attributes of a tag other + than the "name" attribute. We'll need to extend this class if we + ever want to pay attention to those attributes. + """ + def __init__(self): + self.artifact = None + + def startElement(self, name, attrs): + self.text = "" + if name == 'artifact': + self.artifact = Artifact() + elif not self.artifact is None and name == 'field': + self.fname = attrs.get('name', None) + + def characters(self, ch): + if not self.artifact is None: + self.text = self.text + ch + + def endElement(self, name): + global Artifacts + if name == 'artifact': + type = self.artifact.artifact_type + try: + list = Artifacts[type] + except KeyError: + Artifacts[type] = list = [] + list.append(self.artifact) + self.artifact = None + elif not self.artifact is None and name == 'field': + setattr(self.artifact, self.fname, self.text) + +if __name__ == '__main__': + # Create a parser. + parser = xml.sax.make_parser() + # Tell the parser we are not interested in XML namespaces. + parser.setFeature(xml.sax.handler.feature_namespaces, 0) + + # Instantiate our handler and tell the parser to use it. + parser.setContentHandler(ClassifyArtifacts()) + + # Parse the input. + parser.parse(sys.argv[1]) + + # Hard-coded search for 'Open' bugs. This should be easily + # generalized once we figure out other things for this script to do. + bugs = filter(lambda x: x.status == 'Open', Artifacts['Bugs']) + + print Artifacts.keys() + + print "%d open bugs" % len(bugs) + + # Sort them into a separate list for each assignee. + Assigned = {} + for bug in bugs: + a = bug.assigned_to + try: + list = Assigned[a] + except KeyError: + Assigned[a] = list = [] + list.append(bug) + + for a in SFName.keys(): + try: + b = Assigned[SFName[a]] + except KeyError: + pass + else: + print " %s" % a + b.sort(lambda x, y: cmp(x.artifact_id, y.artifact_id)) + for bug in b: + print " %-6s %s" % (bug.artifact_id, bug.summary) diff --git a/bin/svn-bisect.py b/bin/svn-bisect.py new file mode 100755 index 0000000..e9ebcf8 --- /dev/null +++ b/bin/svn-bisect.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# -*- Python -*- + +import sys +from math import log, ceil +from optparse import OptionParser +import subprocess + +# crack the command line +parser = OptionParser( + usage="%prog lower-revision upper-revision test_script [arg1 ...]", + description="Binary search for a bug in a SVN checkout") +(options,script_args) = parser.parse_args() + +# make sure we have sufficient parameters +if len(script_args) < 1: + parser.error("Need a lower revision") +elif len(script_args) < 2: + parser.error("Need an upper revision") +elif len(script_args) < 3: + parser.error("Need a script to run") + +# extract our starting values +lower = int(script_args[0]) +upper = int(script_args[1]) +script = script_args[2:] + +# print an error message and quit +def error(s): + print >>sys.stderr, "******", s, "******" + sys.exit(1) + +# update to the specified version and run test +def testfail(revision): + "Return true if test fails" + print "Updating to revision", revision + if subprocess.call(["svn","up","-qr",str(revision)]) != 0: + m = "SVN did not update properly to revision %d" + raise RuntimeError(m % revision) + return subprocess.call(script,shell=False) != 0 + +# confirm that the endpoints are different +print "****** Checking upper bracket", upper +upperfails = testfail(upper) +print "****** Checking lower bracket", lower +lowerfails = testfail(lower) +if upperfails == lowerfails: + error("Upper and lower revisions must bracket the failure") + +# binary search for transition +msg = "****** max %d revisions to test (bug bracketed by [%d,%d])" +while upper-lower > 1: + print msg % (ceil(log(upper-lower,2)), lower, upper) + + mid = int((lower + upper)/2) + midfails = testfail(mid) + if midfails == lowerfails: + lower = mid + lowerfails = midfails + else: + upper = mid + upperfails = midfails + +# show which revision was first to fail +if upperfails != lowerfails: lower = upper +print "The error was caused by revision", lower + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/bin/time-scons.py b/bin/time-scons.py new file mode 100644 index 0000000..78d26e5 --- /dev/null +++ b/bin/time-scons.py @@ -0,0 +1,355 @@ +#!/usr/bin/env python +# +# time-scons.py: a wrapper script for running SCons timings +# +# This script exists to: +# +# 1) Wrap the invocation of runtest.py to run the actual TimeSCons +# timings consistently. It does this specifically by building +# SCons first, so .pyc compilation is not part of the timing. +# +# 2) Provide an interface for running TimeSCons timings against +# earlier revisions, before the whole TimeSCons infrastructure +# was "frozen" to provide consistent timings. This is done +# by updating the specific pieces containing the TimeSCons +# infrastructure to the earliest revision at which those pieces +# were "stable enough." +# +# By encapsulating all the logic in this script, our Buildbot +# infrastructure only needs to call this script, and we should be able +# to change what we need to in this script and have it affect the build +# automatically when the source code is updated, without having to +# restart either master or slave. + +import optparse +import os +import shutil +import subprocess +import sys +import tempfile +import xml.sax.handler + + +SubversionURL = 'http://scons.tigris.org/svn/scons' + + +# This is the baseline revision when the TimeSCons scripts first +# stabilized and collected "real," consistent timings. If we're timing +# a revision prior to this, we'll forcibly update the TimeSCons pieces +# of the tree to this revision to collect consistent timings for earlier +# revisions. +TimeSCons_revision = 4569 + +# The pieces of the TimeSCons infrastructure that are necessary to +# produce consistent timings, even when the rest of the tree is from +# an earlier revision that doesn't have these pieces. +TimeSCons_pieces = ['QMTest', 'timings', 'runtest.py'] + + +class CommandRunner: + """ + Executor class for commands, including "commands" implemented by + Python functions. + """ + verbose = True + active = True + + def __init__(self, dictionary={}): + self.subst_dictionary(dictionary) + + def subst_dictionary(self, dictionary): + self._subst_dictionary = dictionary + + def subst(self, string, dictionary=None): + """ + Substitutes (via the format operator) the values in the specified + dictionary into the specified command. + + The command can be an (action, string) tuple. In all cases, we + perform substitution on strings and don't worry if something isn't + a string. (It's probably a Python function to be executed.) + """ + if dictionary is None: + dictionary = self._subst_dictionary + if dictionary: + try: + string = string % dictionary + except TypeError: + pass + return string + + def display(self, command, stdout=None, stderr=None): + if not self.verbose: + return + if type(command) == type(()): + func = command[0] + args = command[1:] + s = '%s(%s)' % (func.__name__, ', '.join(map(repr, args))) + if type(command) == type([]): + # TODO: quote arguments containing spaces + # TODO: handle meta characters? + s = ' '.join(command) + else: + s = self.subst(command) + if not s.endswith('\n'): + s += '\n' + sys.stdout.write(s) + sys.stdout.flush() + + def execute(self, command, stdout=None, stderr=None): + """ + Executes a single command. + """ + if not self.active: + return 0 + if type(command) == type(''): + command = self.subst(command) + cmdargs = shlex.split(command) + if cmdargs[0] == 'cd': + command = (os.chdir,) + tuple(cmdargs[1:]) + if type(command) == type(()): + func = command[0] + args = command[1:] + return func(*args) + else: + if stdout is sys.stdout: + # Same as passing sys.stdout, except works with python2.4. + subout = None + elif stdout is None: + # Open pipe for anything else so Popen works on python2.4. + subout = subprocess.PIPE + else: + subout = stdout + if stderr is sys.stderr: + # Same as passing sys.stdout, except works with python2.4. + suberr = None + elif stderr is None: + # Merge with stdout if stderr isn't specified. + suberr = subprocess.STDOUT + else: + suberr = stderr + p = subprocess.Popen(command, + shell=(sys.platform == 'win32'), + stdout=subout, + stderr=suberr) + p.wait() + return p.returncode + + def run(self, command, display=None, stdout=None, stderr=None): + """ + Runs a single command, displaying it first. + """ + if display is None: + display = command + self.display(display) + return self.execute(command, stdout, stderr) + + def run_list(self, command_list, **kw): + """ + Runs a list of commands, stopping with the first error. + + Returns the exit status of the first failed command, or 0 on success. + """ + status = 0 + for command in command_list: + s = self.run(command, **kw) + if s and status == 0: + status = s + return 0 + + +def get_svn_revisions(branch, revisions=None): + """ + Fetch the actual SVN revisions for the given branch querying + "svn log." A string specifying a range of revisions can be + supplied to restrict the output to a subset of the entire log. + """ + command = ['svn', 'log', '--xml'] + if revisions: + command.extend(['-r', revisions]) + command.append(branch) + p = subprocess.Popen(command, stdout=subprocess.PIPE) + + class SVNLogHandler(xml.sax.handler.ContentHandler): + def __init__(self): + self.revisions = [] + def startElement(self, name, attributes): + if name == 'logentry': + self.revisions.append(int(attributes['revision'])) + + parser = xml.sax.make_parser() + handler = SVNLogHandler() + parser.setContentHandler(handler) + parser.parse(p.stdout) + return sorted(handler.revisions) + + +def prepare_commands(): + """ + Returns a list of the commands to be executed to prepare the tree + for testing. This involves building SCons, specifically the + build/scons subdirectory where our packaging build is staged, + and then running setup.py to create a local installed copy + with compiled *.pyc files. The build directory gets removed + first. + """ + commands = [] + if os.path.exists('build'): + commands.extend([ + ['mv', 'build', 'build.OLD'], + ['rm', '-rf', 'build.OLD'], + ]) + commands.append([sys.executable, 'bootstrap.py', 'build/scons']) + commands.append([sys.executable, + 'build/scons/setup.py', + 'install', + '--prefix=' + os.path.abspath('build/usr')]) + return commands + +def script_command(script): + """Returns the command to actually invoke the specified timing + script using our "built" scons.""" + return [sys.executable, 'runtest.py', '-x', 'build/usr/bin/scons', script] + +def do_revisions(cr, opts, branch, revisions, scripts): + """ + Time the SCons branch specified scripts through a list of revisions. + + We assume we're in a (temporary) directory in which we can check + out the source for the specified revisions. + """ + stdout = sys.stdout + stderr = sys.stderr + + status = 0 + + if opts.logsdir and not opts.no_exec and len(scripts) > 1: + for script in scripts: + subdir = os.path.basename(os.path.dirname(script)) + logsubdir = os.path.join(opts.origin, opts.logsdir, subdir) + if not os.path.exists(logsubdir): + os.makedirs(logsubdir) + + for this_revision in revisions: + + if opts.logsdir and not opts.no_exec: + log_name = '%s.log' % this_revision + log_file = os.path.join(opts.origin, opts.logsdir, log_name) + stdout = open(log_file, 'w') + stderr = None + + commands = [ + ['svn', 'co', '-q', '-r', str(this_revision), branch, '.'], + ] + + if int(this_revision) < int(TimeSCons_revision): + commands.append(['svn', 'up', '-q', '-r', str(TimeSCons_revision)] + + TimeSCons_pieces) + + commands.extend(prepare_commands()) + + s = cr.run_list(commands, stdout=stdout, stderr=stderr) + if s: + if status == 0: + status = s + continue + + for script in scripts: + if opts.logsdir and not opts.no_exec and len(scripts) > 1: + subdir = os.path.basename(os.path.dirname(script)) + lf = os.path.join(opts.origin, opts.logsdir, subdir, log_name) + out = open(lf, 'w') + err = None + else: + out = stdout + err = stderr + s = cr.run(script_command(script), stdout=out, stderr=err) + if s and status == 0: + status = s + if out not in (sys.stdout, None): + out.close() + out = None + + if int(this_revision) < int(TimeSCons_revision): + # "Revert" the pieces that we previously updated to the + # TimeSCons_revision, so the update to the next revision + # works cleanly. + command = (['svn', 'up', '-q', '-r', str(this_revision)] + + TimeSCons_pieces) + s = cr.run(command, stdout=stdout, stderr=stderr) + if s: + if status == 0: + status = s + continue + + if stdout not in (sys.stdout, None): + stdout.close() + stdout = None + + return status + +Usage = """\ +time-scons.py [-hnq] [-r REVISION ...] [--branch BRANCH] + [--logsdir DIR] [--svn] SCRIPT ...""" + +def main(argv=None): + if argv is None: + argv = sys.argv + + parser = optparse.OptionParser(usage=Usage) + parser.add_option("--branch", metavar="BRANCH", default="trunk", + help="time revision on BRANCH") + parser.add_option("--logsdir", metavar="DIR", default='.', + help="generate separate log files for each revision") + parser.add_option("-n", "--no-exec", action="store_true", + help="no execute, just print the command line") + parser.add_option("-q", "--quiet", action="store_true", + help="quiet, don't print the command line") + parser.add_option("-r", "--revision", metavar="REVISION", + help="time specified revisions") + parser.add_option("--svn", action="store_true", + help="fetch actual revisions for BRANCH") + opts, scripts = parser.parse_args(argv[1:]) + + if not scripts: + sys.stderr.write('No scripts specified.\n') + sys.exit(1) + + CommandRunner.verbose = not opts.quiet + CommandRunner.active = not opts.no_exec + cr = CommandRunner() + + os.environ['TESTSCONS_SCONSFLAGS'] = '' + + branch = SubversionURL + '/' + opts.branch + + if opts.svn: + revisions = get_svn_revisions(branch, opts.revision) + elif opts.revision: + # TODO(sgk): parse this for SVN-style revision strings + revisions = [opts.revision] + else: + revisions = None + + if opts.logsdir and not os.path.exists(opts.logsdir): + os.makedirs(opts.logsdir) + + if revisions: + opts.origin = os.getcwd() + tempdir = tempfile.mkdtemp(prefix='time-scons-') + try: + os.chdir(tempdir) + status = do_revisions(cr, opts, branch, revisions, scripts) + finally: + os.chdir(opts.origin) + shutil.rmtree(tempdir) + else: + commands = prepare_commands() + commands.extend([ script_command(script) for script in scripts ]) + status = cr.run_list(commands, stdout=sys.stdout, stderr=sys.stderr) + + return status + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/bin/timebuild b/bin/timebuild new file mode 100644 index 0000000..d5af983 --- /dev/null +++ b/bin/timebuild @@ -0,0 +1,65 @@ +#!/bin/sh +# +# Profile running SCons to build itself from the current package. +# +# This runs "aegis -build" to build a current scons-src-*.tar.gz +# package, unpacks it in the supplied directory name, and then +# starts a profiled run of an SCons build, followed by another. +# This results in two profiles: +# +# NAME/NAME-0.prof +# profile of a build-everything run +# +# NAME/NAME-1.prof +# profile of an all-up-to-date run +# +# This also copies the build scons-src-*.tar.gz file to the NAME +# subdirectory, and tars up everything under src/ as NAME/src.tar.gz, +# so that repeated runs with different in-progress changes can serve +# as their own crude version control, so you don't lose that exact +# combination of features which performed best. + +if test X$1 = X; then + echo "Must supply name!" >&2 + exit 1 +fi + +VERSION=0.90 + +DIR=$1 + +SRC="scons-src-$VERSION" +SRC_TAR_GZ="${SRC}.tar.gz" +B_D_SRC_TAR_GZ="build/dist/${SRC_TAR_GZ}" + +echo "Building ${B_D_SRC_TAR_GZ}: " `date` +aegis -build ${B_D_SRC_TAR_GZ} + +echo "mkdir ${DIR}: " `date` +mkdir ${DIR} + +echo "cp ${B_D_SRC_TAR_GZ} ${DIR}: " `date` +cp ${B_D_SRC_TAR_GZ} ${DIR} + +echo "tar cf ${DIR}/src.tar.gz: " `date` +tar cf ${DIR}/src.tar.gz src + +cd ${DIR} + +echo "tar zxf ${SRC_TAR_GZ}: " `date` +tar zxf ${SRC_TAR_GZ} + +cd ${SRC} + +SCRIPT="src/script/scons.py" +ARGS="version=$VERSION" + +export SCONS_LIB_DIR=`pwd`/src/engine + +echo "Build run starting: " `date` +python $SCRIPT --profile=../$DIR-0.prof $ARGS > ../$DIR-0.log 2>&1 + +echo "Up-to-date run starting: " `date` +python $SCRIPT --profile=../$DIR-1.prof $ARGS > ../$DIR-1.log 2>&1 + +echo "Finished $DIR at: " `date` diff --git a/bin/xml_export b/bin/xml_export new file mode 100644 index 0000000..bc9ccbd --- /dev/null +++ b/bin/xml_export @@ -0,0 +1,225 @@ +#!/usr/bin/perl -w +# +# xml_export - Retrieve data from the SF.net XML export for project data +# +# Copyright (C) 2002 Open Source Development Network, Inc. ("OSDN") +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the license details found +# below in the section marked "$LICENSE_TEXT". +# +# SCons: modified the following RCS Id line so it won't expand during +# our checkins. +# +# $_Id: adocman,v 1.51 2002/06/07 18:56:35 moorman Exp _$ +# +# Written by Nate Oostendorp +# and Jacob Moorman +########################################################################### + +use strict; +use Alexandria::Client; +use HTTP::Request::Common; +my $client = new Alexandria::Client; + +util_verifyvariables("groupid"); + +my $res = $ua->simple_request(GET "$config{hosturl}/export/xml_export.php?group_id=$config{groupid}"); + +if (not $res->is_success()) { + die "Failed to connect: ".$res->as_string(); +} +print $res->content; + +########################################################################### + +__END__ +=head1 NAME + +xml_export - Retrieve data for a project via the SF.net XML export facility + +=head1 DESCRIPTION + +B provides a simple mechanism to download data from the +XML data export facility on SourceForge.net. This utility is needed +(in place of a downloader like wget or curl) since authentication by +a project administrator is required to access the XML export facility. + +=head1 SYNOPSIS + +xml_export [options] > output_file + + OPTIONS + --login Login to the SourceForge.net site + --logout Logout of the SourceForge.net site + --groupid=GROUPID Group ID of the project whose data you wish to export + +=head1 ERROR LEVELS + +The following error levels are returned upon exit of this program: + + 0 success + + 1 failure: general (requested DocManager operation failed) + + 2 failure: authentication failure + + 3 failure: must --login before performing this operation + + 4 failure: bad command-line option specified or variable setting problem + + 5 failure: error in accessing/creating a file or directory + + 6 failure: failed to enter requested input before timeout expired + +=head1 AUTHORITATIVE SOURCE + +The original version of B may be found in the materials +provided from the SourceForge.net Site Documentation project (sitedocs) +on the SourceForge.net site. The latest version of this program +may be found in the CVS repository for the sitedocs project on +SourceForge.net. The sitedocs project pages may be accessed at: +http://sourceforge.net/projects/sitedocs + +=head1 SECURITY + +For security-related information for this application, please review +the documentation provided for the adocman utility. + +=head1 EXAMPLES + +The following are examples for using this program to export project +data via the XML data export facility on SourceForge.net. It is presumed +that you have a valid SourceForge.net user account, which is listed as +a project administrator on the project in question. This tool will +only work for project administrators. The group ID for the project +may be derived from the URL for the Admin page for the project, or by +viewing the Project Admin page for the project (look for the text +"Your Group ID is: xxxxxx"). + +To login to the SourceForge.net site via the command-line: + + adocman --username=myusername --password=mypassword --login \ + --groupid=8675309 + +To login to the SourceForge.net site, and be prompted to enter your +password interactively: + + adocman --username=myusername --interactive --login --groupid=8675309 + +To perform an export (after logging-in): + + xml_export --groupid=8675309 > output.xml + +To logout of SourceForge.net: + + adocman --logout + +Additional capabilities (including the use of configuration files to +specify information that would otherwise be provided interactively +or on the command-line) are detailed in the documentation provided for +the adocman utility. + +To obtain output for debugging a problem, perform the same command +as originally tested, but first add the --verbose flag, and determine +whether you are able to solve the issue on your own. If the problem +persists, see the "SUPPORT AND BUGS" section, below. + +=head1 SUPPORT AND BUGS + +This program was written by a member of the SourceForge.net staff +team. This software has been released under an Open Source license, +for the greater benefit of the SourceForge.net developer community. + +The SourceForge.net Site Documentation project is the caretaker of +this software. Issues related to the use of this program, or bugs +found in using this program, may be reported to the SourceForge.net +Site Documentation project using their Support Request Tracker at: +https://sourceforge.net/tracker/?func=add&group_id=52614&atid=467457 + +Any support that is provided for this program is provided as to +further enhance the stability and functionality of this program +for SourceForge.net users. The SourceForge.net Site Documentation +project makes use of this software for its own internal purposes, +in managing the Site Documentation collection for the SourceForge.net +site. + +=head1 AUTHOR + +Nathan Oostendorp and +Jacob Moorman + +=head1 PREREQUISITES + +C, C, C, C, +C + +These prerequisites may be installed in an interactive, but automated +fashion through the use of perl's CPAN module, invoked as: + + perl -MCPAN -e shell; + +=head1 LICENSE + +Copyright (c) 2002 Open Source Development Network, Inc. ("OSDN") + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +2. Neither the names of VA Software Corporation, OSDN, SourceForge.net, +the SourceForge.net Site Documentation project, nor the names of its +contributors may be used to endorse or promote products derived from +the Software without specific prior written permission of OSDN. + +3. The name and trademarks of copyright holders may NOT be used in +advertising or publicity pertaining to the Software without specific, +written prior permission. Title to copyright in the Software and +any associated documentation will at all times remain with copyright +holders. + +4. If any files are modified, you must cause the modified files to carry +prominent notices stating that you changed the files and the date of +any change. We recommend that you provide URLs to the location from which +the code is derived. + +5. Altered versions of the Software must be plainly marked as such, and +must not be misrepresented as being the original Software. + +6. The origin of the Software must not be misrepresented; you must not +claim that you wrote the original Software. If you use the Software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +7. The data files supplied as input to, or produced as output from, +the programs of the Software do not automatically fall under the +copyright of the Software, but belong to whomever generated them, and may +be sold commercially, and may be aggregated with the Software. + +8. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE OR DOCUMENTATION. + +This Software consists of contributions made by OSDN and many individuals +on behalf of OSDN. Specific attributions are listed in the accompanying +credits file. + +=head1 HISTORY + +B<2002-12-03> Completed version 0.10 - move to classes, added POD + +=cut diff --git a/bin/xml_export-LICENSE b/bin/xml_export-LICENSE new file mode 100644 index 0000000..3f06fb7 --- /dev/null +++ b/bin/xml_export-LICENSE @@ -0,0 +1,52 @@ +Copyright (c) 2002 Open Source Development Network, Inc. ("OSDN") + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +1. The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +2. Neither the names of VA Software Corporation, OSDN, SourceForge.net, +the SourceForge.net Site Documentation project, nor the names of its +contributors may be used to endorse or promote products derived from +the Software without specific prior written permission of OSDN. + +3. The name and trademarks of copyright holders may NOT be used in +advertising or publicity pertaining to the Software without specific, +written prior permission. Title to copyright in the Software and +any associated documentation will at all times remain with copyright +holders. + +4. If any files are modified, you must cause the modified files to carry +prominent notices stating that you changed the files and the date of +any change. We recommend that you provide URLs to the location from which +the code is derived. + +5. Altered versions of the Software must be plainly marked as such, and +must not be misrepresented as being the original Software. + +6. The origin of the Software must not be misrepresented; you must not +claim that you wrote the original Software. If you use the Software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +7. The data files supplied as input to, or produced as output from, +the programs of the Software do not automatically fall under the +copyright of the Software, but belong to whomever generated them, and may +be sold commercially, and may be aggregated with the Software. + +8. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE OR DOCUMENTATION. + +This Software consists of contributions made by OSDN and many individuals +on behalf of OSDN. Specific attributions are listed in the accompanying +credits file. diff --git a/bin/xml_export-README b/bin/xml_export-README new file mode 100644 index 0000000..82e3524 --- /dev/null +++ b/bin/xml_export-README @@ -0,0 +1,56 @@ +This copy of xml_export was snarfed from adocman-0.10 from SourceForge. +We're checking in a copy as a convenience for any future SCons project +administrator who may need to download exported XML data. The original, +unmodified contents of the README file for that release of adocman are +as follows: + + +adocman - Automation tool for SourceForge.net DocManager handling +Copyright (C) 2002 Open Source Development Network, Inc. ("OSDN") + + +File manifest: + +Alexandria perl-based API for performing operations against the + SourceForge.net site, currently including basic Client + operations (i.e. login/logout) and DocManager operations + +adocman The adocman program, providing the means to perform + DocManager operations from the command-line or scripts + (by project developers or admins listed as DocManager Editors) + +xml_export The xml_export program, providing the means to automate + downloads of data from the XML data export facility + on SourceForge.net (by project administrators) + +adocman.html Manual for adocman, including background information, + command-line options detail, etc. + +xml_export.html Manual for xml_export, including basic info about + command-line options. See adocman.html for additional + information. + +LICENSE License terms for adocman + +README This file + +TODO List of ongoing work in improving adocman. NOTE: + Please contact the maintainer before starting any effort + to improve this code. We have significantly modified + the structure and design of this program for the next + release; structure and command-line interface are subject + to change without notice. + +A list of the prerequisites required to execute 'adocman' may be found +at in the PREREQUISITES section of the adocman manual (adocman.html). +Though not listed, a recent installation of 'perl' is also a prerequisite. + +Support for this program may be obtained as per the SUPPORT AND BUGS +section of the adocman.html manual. Any questions or concerns regarding +this software should be escalated as per the SUPPORT AND BUGS section +of the provided manual. + +The authoritative source of this software is: + https://sourceforge.net/projects/sitedocs + + diff --git a/bin/xmlagenda.py b/bin/xmlagenda.py new file mode 100755 index 0000000..3009e4c --- /dev/null +++ b/bin/xmlagenda.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python + +# Download the issues from Issuzilla as XML; this creates a file named +# 'issues.xml'. Run this script to translate 'issues.xml' into a CSV +# file named 'editlist.csv'. Upload the CSV into a Google spreadsheet. + +# In the spreadsheet, select the last column and pick "delete-->column" (it +# was added by the upload to allow for expansion and we don't need it). +# Select all the columns and pick "align-->top" +# Select the ID and votes columns and pick "align-->right" +# Select the priority column and pick "align-->center" +# Select the first row and click on the "bold" button +# Grab the lines between the column headers to adjust the column widths +# Grab the sort bar on the far left (just above the "1" for row one) +# and move it down one row. (Row one becomes a floating header) +# Voila! + +# The team members +# FIXME: These names really should be external to this script +team = 'Bill Greg Steven Gary Ken Brandon Sohail Jim David'.split() +team.sort() + +# The elements to be picked out of the issue +PickList = [ + # sort key -- these are used to sort the entry + 'target_milestone', 'priority', 'votes_desc', 'creation_ts', + # payload -- these are displayed + 'issue_id', 'votes', 'issue_type', 'target_milestone', + 'priority', 'assigned_to', 'short_desc', + ] + +# Conbert a leaf element into its value as a text string +# We assume it's "short enough" that there's only one substring +def Value(element): + v = element.firstChild + if v is None: return '' + return v.nodeValue + +# Parse the XML issues file and produce a DOM for it +import sys +if len(sys.argv) > 1: xml = sys.argv[1] +else: xml = 'issues.xml' +from xml.dom.minidom import parse +xml = parse(xml) + +# Go through the issues in the DOM, pick out the elements we want, +# and put them in our list of issues. +issues = [] +for issuezilla in xml.childNodes: + # The Issuezilla element contains the issues + if issuezilla.nodeType != issuezilla.ELEMENT_NODE: continue + for issue in issuezilla.childNodes: + # The issue elements contain the info for an issue + if issue.nodeType != issue.ELEMENT_NODE: continue + # Accumulate the pieces we want to include + d = {} + for element in issue.childNodes: + if element.nodeName in PickList: + d[element.nodeName] = Value(element) + # convert 'votes' to numeric, ascending and descending + try: + v = int('0' + d['votes']) + except KeyError: + pass + else: + d['votes_desc'] = -v + d['votes'] = v + # Marshal the elements and add them to the list + issues.append([ d[ix] for ix in PickList ]) +issues.sort() + +# Transcribe the issues into comma-separated values. +# FIXME: parameterize the output file name +import csv +writer = csv.writer(open('editlist.csv', 'w')) +# header +writer.writerow(['ID', 'Votes', 'Type/Member', 'Milestone', + 'Pri', 'Owner', 'Summary/Comments']) +for issue in issues: + row = issue[4:] # strip off sort key + #row[0] = """=hyperlink("http://scons.tigris.org/issues/show_bug.cgi?id=%s","%s")""" % (row[0],row[0]) + if row[3] == '-unspecified-': row[3] = 'triage' + writer.writerow(['','','','','','','']) + writer.writerow(row) + writer.writerow(['','','consensus','','','','']) + writer.writerow(['','','','','','','']) + for member in team: writer.writerow(['','',member,'','','','']) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/config b/config new file mode 100644 index 0000000..b055bdb --- /dev/null +++ b/config @@ -0,0 +1,299 @@ +/* + * MANIFEST: use of SCons in project config file to build itself + * + * SCons has a Repository feature, introduced in SCons 0.09, that was + * designed to work well with Aegis. + */ + +/* + * The build_command field of the project config file is used to invoke + * the relevant build command. This command tells SCons where to find + * the rules. + * + * Our chicken-and-egg dilemma is this: we want to use the version of + * SCons under development in an Aegis change to build itself. But the + * pieces of SCons are likely only partly in this change, and partly in + * baselines. + * + * Python only imports things on a module-by-module basis--which is to + * say, once it finds __init__.py in a given directory, it assumes that + * all other files in that module are in the same directory. But that's + * not the way Aegis works, because if a file hasn't changed on the + * branch, it will only be in its parent's baseline directory. + * + * Aegis' mechanism for working around this sort of problem is to make + * symlinks to the proper baseline versions of each file, which makes + * it look like everything is in the local tree. That's unattractive, + * though, because we really want to eat our own dog food and use the + * SCons -Y options to pull things from the baseline repositories. + * + * So our solution (suggested by Anthony Roach) is a bootstrap.py script + * that does some Aegis-like searching through the baseline directories + * and makes a bootstrap copy of the version of SCons under development + * that we can use for building. After it makes this copy of SCons, it + * executes it with the same command-line arguments we supplied (and + * setting $SCONS_LIB_DIR to the right directory) so we can use it + * here with command-line options as if it were SCons itself. (Note, + * however, that bootstrap.py only understands the specific command-line + * options already in use here, so if you change the call below to add + * some other SCons options, you may have to modify bootstrap.py to + * recognize them. + * + * The ${Source bootstrap.py} substitution finds bootstrap.py wherever + * it may be in the Aegis baselines. + * + * The long -Y${SUBSTitute...} substitution takes the Aegis baseline + * search path and turns it into the right -Y command-line options for + * SCons. + * + * The rest of the substitutions (${DEVeloper}, etc.) should be obvious. + * + * Look in aesub(5) for more information about command substitutions. + */ +build_command = "python2.1 ${Source bootstrap.py} -Y${SUBSTitute : \\ -Y $Search_Path} date='${DAte %Y/%m/%d %H:%M:%S}' developer=${DEVeloper} version=${VERsion} change=${Change}"; + +/* + * SCons removes its targets before constructing them, which qualifies it + * for the following entry in the config file. The files must be removed + * first, otherwise the baseline would cease to be self-consistent. + */ + +link_integration_directory = true; + +/* + * This is set temporarily to allow us to build using the SCons + * currently checked in to the src directory. +create_symlinks_before_build = true; + */ + +/* + * aegis - project change supervisor + * This file is in the Public Domain, 1995, 1998 Peter Miller. + * + * MANIFEST: example of using rcs in the project config file + * + * The entries for the commands are listed below. RCS uses a slightly + * different model than aegis wants, so some maneuvering is required. + * The command strings in this section assume that the RCS commands ci and co + * and rcs and rlog are in the command search PATH, but you may like to + * hard-wire the paths, or set PATH at the start of each. You should also note + * that the strings are always handed to the Bourne shell to be executed, and + * are set to exit with an error immediately a sub-command fails. + * + * In these commands, the RCS file is kept unlocked, since only the owner will + * be checking changes in. The RCS functionality for coordinating shared + * access is not required. + * + * One advantage of using RCS version 5.6 or later is that binary files are + * supported, should you want to have binary files in the baseline. + * + * The ${quote ...} construct is used to quote filenames which contain + * shell special characters. A minimum of quoting is performed, so if + * the filenames do not contail shell special characters, no quotes will + * be used. + */ + +/* + * This command is used to create a new file history. + * This command is always executed as the project owner. + * The following substitutions are available: + * + * ${Input} + * absolute path of the source file + * ${History} + * absolute path of the history file + * + * The "ci -f" option is used to specify that a copy is to be checked-in even + * if there are no changes. + * The "ci -u" option is used to specify that an unlocked copy will remain in + * the baseline. + * The "ci -d" option is used to specify that the file time rather than the + * current time is to be used for the new revision. + * The "ci -M" option is used to specify that the mode date on the original + * file is not to be altered. + * The "ci -t" option is used to specify that there is to be no description + * text for the new RCS file. + * The "ci -m" option is used to specify that the change number is to be stored + * in the file log if this is actually an update (typically from aenf + * after aerm on the same file name). + * The "rcs -U" option is used to specify that the new RCS file is to have + * unstrict locking. + * The "rcs -kk" option is used to specify that keyword substitution is + * disabled (only keyword names, not values, are substituted). + */ +history_create_command = + "ci -f -u -d -M -m$c -t/dev/null ${quote $input} ${quote $history,v}; \ +rcs -kk -U ${quote $history,v}"; + + +/* + * This command is used to get a specific edit back from history. + * This command is always executed as the project owner. + * The following substitutions are available: + * + * ${History} + * absolute path of the history file + * ${Edit} + * edit number, as given by history_\%query_\%command + * ${Output} + * absolute path of the destination file + * + * The "co -r" option is used to specify the edit to be retrieved. + * The "co -p" option is used to specify that the results be printed on the + * standard output; this is because the destination filename will never + * look anything like the history source filename. + * The "rcs -kk" option is used to specify that keyword substitution is + * disabled (only keyword names, not values, are substituted). + */ +history_get_command = + "co -kk -r${quote $edit} -p ${quote $history,v} > ${quote $output}"; + +/* + * This command is used to add a new "top-most" entry to the history file. + * This command is always executed as the project owner. + * The following substitutions are available: + * + * ${Input} + * absolute path of source file + * ${History} + * absolute path of history file + * + * The "ci -f" option is used to specify that a copy is to be checked-in even + * if there are no changes. + * The "ci -u" option is used to specify that an unlocked copy will remain in + * the baseline. + * The "ci -d" option is used to specify that the file time rather than the + * current time is to be used for the new revision. + * The "ci -M" option is used to specify that the mode date on the original + * file is not to be altered. + * The "ci -m" option is used to specify that the change number is to be stored + * in the file log, which allows rlog to be used to find the change + * numbers to which each revision of the file corresponds. + * + * It is possible for a a very cautious approach has been taken, in which case + * the history_put_command may be set to the same string specified above for + * the history_create_command. + */ +history_put_command = + "ci -f -u -d -M -m$c ${quote $input} ${quote $history,v}"; + +/* + * This command is used to query what the history mechanism calls the top-most + * edit of a history file. The result may be any arbitrary string, it need not + * be anything like a number, just so long as it uniquely identifies the edit + * for use by the history_get_command at a later date. The edit number is to + * be printed on the standard output. This command is always executed as the + * project owner. + * + * The following substitutions are available: + * + * ${History} + * absolute path of the history file + */ +history_query_command = + "rlog -r ${quote $history,v} | awk '/^head:/ {print $$2}'"; + +/* + * RCS also provides a merge program, which can be used to provide a three-way + * merge. It has an ouput format some sites prefer to the fmerge output. + * + * This command is used by aed(1) to produce a difference listing when a file + * in the development directory is out of date compared to the current version + * in the baseline. + * + * All of the command substitutions described in aesub(5) are available. + * In addition, the following substitutions are also available: + * + * ${ORiginal} + * The absolute path name of a file containing the common ancestor + * version of ${MostRecent} and {$Input}. Usually the version originally + * copied into the change. Usually in a temporary file. + * ${Most_Recent} + * The absolute path name of a file containing the most recent version. + * Usually in the baseline. + * ${Input} + * The absolute path name of the edited version of the file. Usually in + * the development directory. + * ${Output} + * The absolute path name of the file in which to write the difference + * listing. Usually in the development directory. + * + * An exit status of 0 means successful, even of the files differ (and they + * usually do). An exit status which is non-zero means something is wrong. + * + * The "merge -L" options are used to specify labels for the baseline and the + * development directory, respecticvely, when conflict lines are inserted + * into the result. + * The "merge -p" options is used to specify that the results are to be printed + * on the standard output. + */ + +diff3_command = + "set +e; \ +merge -p -L baseline -L C$c ${quote $mostrecent} ${quote $original} \ +${quote $input} > ${quote $output}; \ +test $? -le 1"; + +/* + * The diff command in Red Hat 8.0 changed the exit status so it *fails* + * when *it* thinks it's trying to diff a binary (non-ASCII-text) file. + * The -a option disables this behavior and makes diff's exit status + * behave like it used to, even on any binary files we have checked in. + */ + +diff_command = + "set +e; \ + diff -a -c ${quote $original} ${quote $input} > ${quote $output}; \ + test $? -le 1"; + +/* + * We use a runtest.py script to execute tests. This takes care of + * massaging environment variables and the like to test against the + * unpacked package in the current directory. + * + * Note that we must include $spe in the batch_test_command line (so + * that Aegis thinks we're smart about testing ourselves against the + * baseline) but we don't actually need it. Our tests always run + * relative to the package built under the current directory, which + * is set appropriately during a baseline test. So we just use the + * proper aesub variable to comment out the expanded $spe. + */ +test_command = "python1.5 ${Source runtest.py Absolute} --noqmtest -p tar-gz -t -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -q --sp ${Search_Path} --spe ${Search_Path_Executable} ${File_Name}"; + +batch_test_command = "python1.5 ${Source runtest.py Absolute} --noqmtest -p tar-gz -t -v ${SUBSTitute '\\.[CD][0-9]+$' '' ${VERsion}} -o ${Output} --aegis --sp ${Search_Path} --spe ${Search_Path_Executable} ${File_Names}"; + +new_test_filename = "test/CHANGETHIS.py"; + +/* + * + */ +file_template = +[ + { + pattern = [ "src/engine/*__init__.py" ]; + body = "${read_file ${source template/__init__.py abs}}"; + }, + { + pattern = [ "src/engine/*Tests.py" ]; + body = "${read_file ${source template/Tests.py abs}}"; + }, + { + pattern = [ "src/engine/*.py" ]; + body = "${read_file ${source template/file.py abs}}"; + }, + { + pattern = [ "test/*.py" ]; + body = "${read_file ${source template/test.py abs}}"; + }, +]; + +/* + * Command for distributing changes from Aegis to all of the repositories + * we want to mirror the information. + * + * XXX Uncomment after upgrading to an Aegis version that supports this. + +integrate_pass_notify_command = + "$sh ${s bin/scons-cdist} -p $project $change"; + * + */ diff --git a/doc/MANIFEST b/doc/MANIFEST new file mode 100644 index 0000000..008afab --- /dev/null +++ b/doc/MANIFEST @@ -0,0 +1 @@ +scons.mod diff --git a/doc/SConscript b/doc/SConscript new file mode 100644 index 0000000..fd18ef4 --- /dev/null +++ b/doc/SConscript @@ -0,0 +1,530 @@ +# +# SConscript file for building SCons documentation. +# + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +import os.path +import re +import string + +Import('build_dir', 'env', 'whereis') + +env = env.Clone() + +build = os.path.join(build_dir, 'doc') + +# +# +# +dist_doc_tar_gz = '$DISTDIR/scons-doc-${VERSION}.tar.gz' + +# +# We'll only try to build text files (for some documents) +# if lynx is available to do the dump. +# +fig2dev = whereis('fig2dev') +epydoc = whereis('epydoc') +groff = whereis('groff') +lynx = whereis('lynx') +man2html = whereis('man2html') +jade_original = whereis('jade') +jade = whereis('openjade') or jade_original +jadetex = whereis('jadetex') +pdfjadetex = whereis('pdfjadetex') +jw = whereis('jw') +tidy = whereis('tidy') + +tar_deps = [] +tar_list = [] + +entity_re = re.compile(r'', re.I) +format_re = re.compile(r'<(?:graphic|imagedata)\s+fileref="([^"]*)"(?:\s+format="([^"]*)")?') + +# +# Find internal dependencies in .xml files: +# +# +# +# +# +# This only finds one per line, and assumes that anything +# defined as a SYSTEM entity is, in fact, a file included +# somewhere in the document. +# +def scanxml(node, env, target): + includes = [] + + contents = node.get_text_contents() + + includes.extend(entity_re.findall(contents)) + + matches = format_re.findall(contents) + for m in matches: + file, format = m + if format and file[-len(format):] != format: + file = file + '.' + format + if not os.path.isabs(file): + a = [] + f = file + while f: + f, tail = os.path.split(f) + if tail == 'doc': + break + a = [tail] + a + file = apply(os.path.join, a, {}) + includes.append(file) + + return includes + +s = Scanner(name = 'xml', function = scanxml, skeys = ['.xml', '.mod']) + +orig_env = env +env = orig_env.Clone(SCANNERS = [s], + SCONS_PROC_PY = File('#bin/scons-proc.py').rfile(), + SCONSOUTPUT_PY = File('#bin/sconsoutput.py').rfile()) + +# Fetch the list of files in the build engine that contain +# SCons documentation XML for processing. +def chop(s): return s[:-1] + +# If we ever read doc from __scons_doc__ strings in *.py files again, +# here's how it's done: +#manifest_in = File('#src/engine/MANIFEST.in').rstr() +#manifest_xml_in = File('#src/engine/MANIFEST-xml.in').rstr() +#scons_doc_files = map(chop, open(manifest_in).readlines() +\ +# open(manifest_xml_in).readlines()) +#scons_doc_files = map(lambda x: '#src/engine/'+x, scons_doc_files) +#manifest_in = File('#src/engine/MANIFEST.in').rstr() + +manifest_xml_in = File('#src/engine/MANIFEST-xml.in').rstr() +scons_doc_files = map(chop, open(manifest_xml_in).readlines()) +scons_doc_files = map(lambda x: File('#src/engine/'+x).rstr(), scons_doc_files) + +if not jw: + print "jw not found, skipping building User Guide." +else: + # + # Always create a version.xml file containing the version information + # for this run. Ignore it for dependency purposes so we don't + # rebuild all the docs every time just because the date changes. + # + date, ver, rev = env.Dictionary('DATE', 'VERSION', 'REVISION') + version_xml = File(os.path.join(build, "version.xml")) + #version_xml = File("version.xml") + verfile = str(version_xml) + try: + os.unlink(verfile) + except OSError: + pass # okay if the file didn't exist + dir, f = os.path.split(verfile) + try: + os.makedirs(dir) + except OSError: + pass # okay if the directory already exists + open(verfile, "w").write(""" + + + +""" % (date, ver, rev)) + + builders_gen = os.path.join(build, 'user', 'builders.gen') + builders_mod = os.path.join(build, 'user', 'builders.mod') + tools_gen = os.path.join(build, 'user', 'tools.gen') + tools_mod = os.path.join(build, 'user', 'tools.mod') + variables_gen = os.path.join(build, 'user', 'variables.gen') + variables_mod = os.path.join(build, 'user', 'variables.mod') + + # We put $( - $) around $SOURCES in the command line below because + # the path names will change when a given input file is found in + # a repository one run and locally the next, and we don't want + # to rebuild documentation just because it's found in one location + # vs. the other. The *.gen and *.mod targets will still be dependent + # on the list of the files themselves. + doc_output_files = [builders_gen, builders_mod, + tools_gen, tools_mod, + variables_gen, variables_mod] + b = env.Command(doc_output_files, + scons_doc_files, + "$PYTHON $SCONS_PROC_PY --xml -b ${TARGETS[0]},${TARGETS[1]} -t ${TARGETS[2]},${TARGETS[3]} -v ${TARGETS[4]},${TARGETS[5]} $( $SOURCES $)") + env.Depends(b, "$SCONS_PROC_PY") + + env.Local(b) + + # + # Each document will live in its own subdirectory. List them here + # as hash keys, with a hash of the info to control its build. + # + docs = { + 'design' : { + 'htmlindex' : 'book1.html', + 'ps' : 1, + 'pdf' : 1, + 'text' : 0, + }, + # This doesn't build on all systems, and the document is old + # enough that there's reallyno need to build it every time any + # more, so just comment it out for now. + #'python10' : { + # 'htmlindex' : 't1.html', + # 'html' : 1, + # 'ps' : 1, + # 'pdf' : 0, + # 'text' : 0, + # 'graphics' : [ + # 'arch.fig', + # 'builder.fig', + # 'job-task.fig', + # 'node.fig', + # 'scanner.fig', + # 'sig.fig' + # ], + #}, + 'reference' : { + 'htmlindex' : 'book1.html', + 'html' : 1, + 'ps' : 1, + 'pdf' : 1, + 'text' : 0, + }, + # For whenever (if ever?) we start putting developer guide + # information in a printable document instead of the wiki. + #'developer' : { + # 'htmlindex' : 'book1.html', + # 'html' : 1, + # 'ps' : 1, + # 'pdf' : 1, + # 'text' : 0, + #}, + 'user' : { + 'htmlindex' : 'book1.html', + 'html' : 1, + 'ps' : 1, + 'pdf' : 1, + 'text' : 1, + 'graphics' : [ + 'SCons-win32-install-1.jpg', + 'SCons-win32-install-2.jpg', + 'SCons-win32-install-3.jpg', + 'SCons-win32-install-4.jpg', + ], + 'sconsoutput' : 1, + }, + } + + # + # We have to tell SCons to scan the top-level XML files which + # get included by the document XML files in the subdirectories. + # + manifest = File('MANIFEST').rstr() + src_files = map(lambda x: x[:-1], open(manifest).readlines()) + for s in src_files: + base, ext = os.path.splitext(s) + if ext in ['.fig', '.jpg']: + orig_env.Install(build, s) + else: + orig_env.SCons_revision(os.path.join(build, s), s) + Local(os.path.join(build, s)) + + # + # For each document, build the document itself in HTML, Postscript, + # and PDF formats. + # + for doc in docs.keys(): + manifest = File(os.path.join(doc, 'MANIFEST')).rstr() + src_files = map(lambda x: x[:-1], + open(manifest).readlines()) + build_doc = docs[doc].get('sconsoutput') and int(ARGUMENTS.get('BUILDDOC', 0)) + for s in src_files: + doc_s = os.path.join(doc, s) + build_s = os.path.join(build, doc, s) + base, ext = os.path.splitext(doc_s) + if ext in ['.fig', '.jpg']: + orig_env.InstallAs(build_s, doc_s) + else: + if build_doc and ext == '.xml': + env.Command(doc_s, + base + '.in', + "$PYTHON $SCONSOUTPUT_PY $SOURCE > $TARGET") + orig_env.SCons_revision(build_s, doc_s) + Local(build_s) + + main = os.path.join(build, doc, 'main.xml') + out = 'main.out' + + # Hard-coding the scons-src path is a bit of a hack. This can + # be reworked when a better solution presents itself. + scons_src_main = os.path.join(build_dir, 'scons-src', 'doc', main) + env.Ignore(scons_src_main, version_xml) + + htmldir = os.path.join(build, 'HTML', 'scons-%s' % doc) + htmlindex = os.path.join(htmldir, docs[doc]['htmlindex']) + html = os.path.join(build, 'HTML', 'scons-%s.html' % doc) + ps = os.path.join(build, 'PS', 'scons-%s.ps' % doc) + pdf = os.path.join(build, 'PDF', 'scons-%s.pdf' % doc) + text = os.path.join(build, 'TEXT', 'scons-%s.txt' % doc) + + if docs[doc].get('html') and jade: + def copy_index_html(target, source, env): + # Older versions of DocBook|jw|jade|whatever would + # create a book1.html file, while newer versions create + # an index.html file (logically enough). The scons.org + # web site links expect book1.html, so we're going to + # leave the target as is, and run this post-processing + # action function to check that the target really did + # get created, and if it didn't, copy it from index.html. + t = str(target[0]) + if not os.path.exists(t): + i = os.path.join(os.path.split(t)[0], 'index.html') + open(t, 'w').write(open(i, 'r').read()) + return None + + cmds = [ + Delete("${TARGET.dir}/*.html"), + "jw -b html -o ${TARGET.dir} $SOURCES", + ] + if tidy: + cmds.append("tidy -m -q $TARGET || true") + cmds.append(Action(copy_index_html)) + env.Command(htmlindex, File(main), cmds) + Local(htmlindex) + + cmds = [ + Delete("${TARGET.dir}/main.html"), + "jw -u -b html -o ${TARGET.dir} $SOURCES", + Move("$TARGET", "${TARGET.dir}/main.html"), + ] + if tidy: + cmds.append("tidy -m -q $TARGET || true") + env.Command(html, File(main), cmds) + Local(html) + + env.Ignore([html, htmlindex], version_xml) + + tar_deps.extend([html, htmlindex]) + tar_list.extend([html, htmldir]) + + for g in docs[doc].get('graphics', []): + base, ext = os.path.splitext(g) + if ext == '.fig': + jpg = base + '.jpg' + htmldir_jpg = os.path.join(htmldir, jpg) + if fig2dev: + fig = os.path.join(build, doc, g) + env.Command(htmldir_jpg, fig, + "%s -L jpeg -q 100 $SOURCES $TARGET" % fig2dev) + else: + env.InstallAs(htmldir_jpg, jpg) + env.Depends(html, htmldir_jpg) + Local(htmldir_jpg) + else: + src = os.path.join(build, doc, g) + Local(env.Install(htmldir, src)) + + if docs[doc].get('ps') and jadetex and jade_original: + env.Command(ps, main, [ + Delete("${TARGET.dir}/%s" % out), + "jw -b ps -o ${TARGET.dir} -p %s $SOURCES" % jade_original, + "mv ${TARGET.dir}/main.ps $TARGET", + Delete("${TARGET.dir}/%s" % out), + ]) + Local(ps) + + env.Ignore(ps, version_xml) + + tar_deps.append(ps) + tar_list.append(ps) + + for g in docs[doc].get('graphics', []): + base, ext = os.path.splitext(g) + if ext == '.fig': + eps = base + '.eps' + build_eps = os.path.join(build, 'PS', eps) + if fig2dev: + fig = os.path.join(build, doc, g) + env.Command(build_eps, fig, "%s -L eps $SOURCES $TARGET" % fig2dev) + else: + env.InstallAs(build_eps, eps) + env.Depends(ps, build_eps) + Local(build_eps) + else: + src = os.path.join(build, doc, g) + Local(env.Install(htmldir, src)) + + if docs[doc].get('pdf') and pdfjadetex and jade_original: + env.Command(pdf, main, [ + Delete("${TARGET.dir}/%s" % out), + "jw -b pdf -o ${TARGET.dir} -p %s $SOURCES" % jade_original, + "mv ${TARGET.dir}/main.pdf $TARGET", + Delete("${TARGET.dir}/out"), + ]) + Local(pdf) + + env.Ignore(pdf, version_xml) + + tar_deps.append(pdf) + tar_list.append(pdf) + + if docs[doc].get('text') and jade and lynx: + env.Command(text, html, "lynx -dump ${SOURCE.abspath} > $TARGET") + Local(text) + + env.Ignore(text, version_xml) + + tar_deps.append(text) + tar_list.append(text) + +# +# Man page(s), in good ol' troff format. +# +man_page_list = ['scons.1', 'sconsign.1', 'scons-time.1'] + +for m in man_page_list: + x = orig_env.SCons_revision(os.path.join(build, 'man', m), + os.path.join('man', m)) + +man_i_files = ['builders.man', 'tools.man', 'variables.man'] + +man_intermediate_files = map(lambda x: os.path.join(build, 'man', x), + man_i_files) + +cmd = "$PYTHON $SCONS_PROC_PY --man -b ${TARGETS[0]} -t ${TARGETS[1]} -v ${TARGETS[2]} $( $SOURCES $)" +man_intermediate_files = env.Command(man_intermediate_files, + scons_doc_files, + cmd) +env.Depends(man_intermediate_files, "$SCONS_PROC_PY") +Local(man_intermediate_files) + +for man_1 in man_page_list: + man, _1 = os.path.splitext(man_1) + + man_1 = os.path.join(build, 'man', man_1) + + if groff: + ps = os.path.join(build, 'PS', '%s-man.ps' % man) + text = os.path.join(build, 'TEXT', '%s-man.txt' % man) + + b = env.Command(ps, man_1, "( cd ${SOURCES.dir} && groff -man -Tps ${SOURCES.file} ) > $TARGET") + Local(ps) + env.Depends(b, man_intermediate_files) + + b = env.Command(text, man_1, "( cd ${SOURCES.dir} && groff -man -Tascii ${SOURCES.file} ) > $TARGET") + Local(text) + env.Depends(b, man_intermediate_files) + + tar_deps.extend([ps, text]) + tar_list.extend([ps, text]) + + if man2html: + html = os.path.join(build, 'HTML' , '%s-man.html' % man) + + def strip_to_first_html_tag(target, source, env): + t = str(target[0]) + contents = open(t).read() + contents = contents[string.find(contents, ''):] + open(t, 'w').write(contents) + return 0 + + cmds = [ + "( cd %s/man && cp %s .. )" % (build, string.join(man_i_files)), + "( cd ${SOURCE.dir} && man2html ${SOURCE.file} ) > $TARGET", + Action(strip_to_first_html_tag), + ] + if tidy: + cmds.append("tidy -m -q $TARGET || true") + b = env.Command(html, man_1, cmds) + Local(html) + env.Depends(b, man_intermediate_files) + + tar_deps.append(html) + tar_list.append(html) + +if not epydoc: + print "epydoc not found, skipping building API documentation." +else: + # XXX Should be in common with reading the same thing in + # the SConstruct file. + e = os.path.join('#src', 'engine') + manifest_in = File(os.path.join(e, 'MANIFEST.in')).rstr() + sources = map(lambda x: x[:-1], open(manifest_in).readlines()) + sources = filter(lambda x: string.find(x, 'Optik') == -1, sources) + sources = filter(lambda x: string.find(x, 'Platform') == -1, sources) + sources = filter(lambda x: string.find(x, 'Tool') == -1, sources) + # XXX + sources = filter(lambda x: string.find(x, 'Options') == -1, sources) + + e = os.path.join(build, '..', 'scons', 'engine') + sources = map(lambda x, e=e: os.path.join(e, x), sources) + + epydoc_commands = [ + Delete('$OUTDIR'), + '$EPYDOC $EPYDOCFLAGS --debug --output $OUTDIR --docformat=restructuredText --name SCons --url http://www.scons.org/ $SOURCES', + Touch('$TARGET'), + ] + + htmldir = os.path.join(build, 'HTML', 'scons-api') + env.Command('${OUTDIR}/index.html', sources, epydoc_commands, + EPYDOC=epydoc, EPYDOCFLAGS='--html', OUTDIR=htmldir) + tar_deps.append(htmldir) + tar_list.append(htmldir) + + # PDF and PostScript and TeX are built from the + # same invocation. + api_dir = os.path.join(build, 'scons-api') + api_pdf = os.path.join(api_dir, 'api.pdf') + api_ps = os.path.join(api_dir, 'api.ps') + api_tex = os.path.join(api_dir, 'api.tex') + api_targets = [api_pdf, api_ps, api_tex] + env.Command(api_targets, sources, epydoc_commands, + EPYDOC=epydoc, EPYDOCFLAGS='--pdf', OUTDIR=api_dir) + Local(api_targets) + + pdf_install = os.path.join(build, 'PDF', 'scons-api.pdf') + env.InstallAs(pdf_install, api_pdf) + tar_deps.append(pdf_install) + tar_list.append(pdf_install) + Local(pdf_install) + + ps_install = os.path.join(build, 'PS', 'scons-api.ps') + env.InstallAs(ps_install, api_ps) + tar_deps.append(ps_install) + tar_list.append(ps_install) + Local(ps_install) + +# +# Now actually create the tar file of the documentation, +# for easy distribution to the web site. +# +if tar_deps: + tar_list = string.join(map(lambda x, b=build+'/': string.replace(x, b, ''), + tar_list)) + t = env.Command(dist_doc_tar_gz, tar_deps, + "tar cf${TAR_HFLAG} - -C %s %s | gzip > $TARGET" % (build, tar_list)) + AddPostAction(dist_doc_tar_gz, Chmod(dist_doc_tar_gz, 0644)) + Local(t) + Alias('doc', t) +else: + Alias('doc', os.path.join(build_dir, 'doc')) diff --git a/doc/design/MANIFEST b/doc/design/MANIFEST new file mode 100644 index 0000000..33ab8f0 --- /dev/null +++ b/doc/design/MANIFEST @@ -0,0 +1,14 @@ +acks.xml +bground.xml +copyright.xml +engine.fig +engine.jpg +engine.xml +goals.xml +install.xml +intro.xml +issues.xml +main.xml +native.xml +overview.xml +scons.mod diff --git a/doc/design/acks.xml b/doc/design/acks.xml new file mode 100644 index 0000000..b1a8a58 --- /dev/null +++ b/doc/design/acks.xml @@ -0,0 +1,179 @@ + + + + + I'm grateful to the following people + for their influence, knowing or not, + on the design of &SCons;: + + + + + + Bob Sidebotham + + + + First, as the original author of &Cons;, Bob did the real heavy + lifting of creating the underlying model for dependency management + and software construction, as well as implementing it in Perl. + During the first years of &Cons;' existence, Bob did a skillful + job of integrating input and code from the first users, and + consequently is a source of practical wisdom and insight into the + problems of real-world software construction. His continuing + advice has been invaluable. + + + + + + + The &SCons; Development Team + + + + A big round of thanks go to those brave souls who have + gotten in on the ground floor: + David Abrahams, + Charles Crain, + Steven Leblanc. + Anthony Roach, + and + Steven Shaw. + Their contributions, + through their general knowledge of software build issues in general + Python in particular, + have made &SCons; what it is today. + + + + + + + The &Cons; Community + + + + The real-world build problems that the users of &Cons; + share on the cons-discuss mailing list + have informed much of the thinking that + has gone into the &SCons; design. + In particular, + Rajesh Vaidheeswarran, + the current maintainer of &Cons;, + has been a very steady influence. + I've also picked up valuable insight from + mailing-list participants + Johan Holmberg, + Damien Neil, + Gary Oberbrunner, + Wayne Scott, + and Greg Spencer. + + + + + + + Peter Miller + + + + + Peter has indirectly + influenced two aspects of the &SCons; design: + + + + + + Miller's influential paper + Recursive Make Considered Harmful + was what led me, indirectly, to my involvement with &Cons; + in the first place. + Experimenting with the single-Makefile approach he describes in + RMCH led me to conclude that while it worked + as advertised, it was not an extensible scheme. This solidified + my frustration with Make and led me to try &Cons;, which at its + core shares the single-process, universal-DAG model of the "RMCH" + single-Makefile technique. + + + + + + The testing framework that Miller created for his + Aegis change management system + changed the way I approach software development + by providing a framework for rigorous, repeatable + testing during development. + It was my success at using Aegis for personal projects + that led me to begin my involvement with &Cons; + by creating the cons-test regression suite. + + + + + + + Stuart Stanley + + + + An experienced Python programmer, + Stuart provided valuable advice and insight + into some of the more useful Python idioms at my disposal + during the original ScCons; design + for the Software Carpentry contest. + + + + + + + Gary Holt + + + + I don't know which came first, + the first-round Software Carpentry contest entry + or the tool itself, + but Gary's design for &Makepp; + showed me that it is possible to marry + the strengths of &Cons;-like dependency management + with backwards compatibility for &Makefile;s. + Striving to support both + &Makefile; compatibility and + a native Python interface + cleaned up the &SCons; design immeasurably + by factoring out the common elements + into the Build Engine. + + + + + + diff --git a/doc/design/bground.xml b/doc/design/bground.xml new file mode 100644 index 0000000..c404e86 --- /dev/null +++ b/doc/design/bground.xml @@ -0,0 +1,86 @@ + + + + + Most of the ideas in &SCons; originate with &Cons;, a Perl-based + software construction utility that has been in use by a small but + growing community since its development by Bob Sidebotham at FORE + Systems in 1996. The &Cons; copyright was transferred in 2000 from + Marconi (who purchased FORE Systems) to the Free Software Foundation. + I've been a principal implementer and maintainer of &Cons; for several + years. + + + + + + &Cons; was originally designed to handle complicated software build + problems (multiple directories, variant builds) while keeping the + input files simple and maintainable. The general philosophy is that + the build tool should ``do the right thing'' with minimal input + from an unsophisticated user, while still providing a rich set of + underlying functionality for more complicated software construction + tasks needed by experts. + + + + + + In 2000, the Software Carpentry sought entries in a contest for a + new, Python-based build tool that would provide an improvement + over Make for physical scientists and other non-programmers + struggling to use their computers more effectively. Prior to that, + the idea of combining the superior build architecture of &Cons; + with the easier syntax of Python had come up several times on + the cons-discuss mailing list. The Software + Carpentry contest provided the right motivation to spend some + actual time working on a design document. + + + + + + After two rounds of competition, the submitted design, named + ScCons, won the competition. Software + Carpentry, however, did not immediately fund implementation of the + build tool, instead contracting for additional, more detailed draft(s) + of the design document. This proved to be not as strong motivation as + actual coding, and after several months of inactivity, I essentially + resigned from the Software Carpentry effort in early 2001 to start + working on the tool independently. + + + + + + After half a year of prototyping some of the important infrastructure, + I accumulated enough code to take the project public at SourceForge, + renaming it &SCons; to distinguish it slightly from the version of the + design that won the Software Carpentry contest while still honoring + its roots there and in the original &Cons; utility. And also because + it would be a teensy bit easier to type. + + diff --git a/doc/design/copyright.xml b/doc/design/copyright.xml new file mode 100644 index 0000000..d73906e --- /dev/null +++ b/doc/design/copyright.xml @@ -0,0 +1,39 @@ + + +
+ + + Copyright (c) 2001 Steven Knight + + Portions of this document, by the same author, were previously + published Copyright 2000 by CodeSourcery LLC, under the Software Carpentry + Open Publication License, the terms of which are available at + + http://www.software-carpentry.com/openpub-license.html + . + + +
diff --git a/doc/design/engine.fig b/doc/design/engine.fig new file mode 100644 index 0000000..90f3d4f --- /dev/null +++ b/doc/design/engine.fig @@ -0,0 +1,179 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 2100 8700 3600 9300 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 2100 8700 3600 8700 3600 9300 2100 9300 2100 8700 +4 0 0 100 0 18 14 0.0000 4 165 900 2400 9075 Node.FS\001 +-6 +6 7050 6900 9000 7500 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 7050 6900 9000 6900 9000 7500 7050 7500 7050 6900 +4 0 0 100 0 18 14 0.0000 4 165 1530 7200 7275 Intercessor.FS\001 +-6 +6 9450 6900 11400 7500 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 9450 6900 11400 6900 11400 7500 9450 7500 9450 6900 +4 0 0 100 0 18 14 0.0000 4 165 1560 9600 7275 Intercessor.DB\001 +-6 +6 1200 4200 2400 4800 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 1200 4200 2400 4200 2400 4800 1200 4800 1200 4200 +4 0 0 100 0 18 14 0.0000 4 165 870 1350 4575 Scanner\001 +-6 +6 2400 3300 3600 3900 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 2400 3300 3600 3300 3600 3900 2400 3900 2400 3300 +4 0 0 100 0 18 14 0.0000 4 165 750 2625 3675 Builder\001 +-6 +6 8700 1650 10500 2250 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 8700 1650 10500 1650 10500 2250 8700 2250 8700 1650 +4 0 0 100 0 18 14 0.0000 4 165 1185 9000 2025 Intercessor\001 +-6 +6 1500 1650 3300 2250 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 1500 1650 3300 1650 3300 2250 1500 2250 1500 1650 +4 0 0 100 0 18 14 0.0000 4 165 1320 1725 2025 Environment\001 +-6 +6 7800 8700 9300 9300 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 7800 8700 9300 8700 9300 9300 7800 9300 7800 8700 +4 0 0 100 0 18 14 0.0000 4 165 930 8100 9075 Node.DB\001 +-6 +6 1500 10200 2400 10800 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 1500 10200 2400 10200 2400 10800 1500 10800 1500 10200 +4 0 0 100 0 18 14 0.0000 4 165 315 1800 10575 Dir\001 +-6 +6 3300 10200 4200 10800 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 3300 10200 4200 10200 4200 10800 3300 10800 3300 10200 +4 0 0 100 0 18 14 0.0000 4 165 375 3600 10575 File\001 +-6 +6 6000 10200 7200 10800 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 6000 10200 7200 10200 7200 10800 6000 10800 6000 10200 +4 0 0 100 0 18 14 0.0000 4 165 555 6300 10575 Table\001 +-6 +6 7800 10200 9300 10800 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 7800 10200 9300 10200 9300 10800 7800 10800 7800 10200 +4 0 0 100 0 18 14 0.0000 4 165 765 8100 10575 Record\001 +-6 +6 9900 10200 11100 10800 +2 2 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 9900 10200 11100 10200 11100 10800 9900 10800 9900 10200 +4 0 0 100 0 18 14 0.0000 4 165 510 10200 10575 Field\001 +-6 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 6900 5250 6825 5175 6900 5100 6975 5175 6900 5250 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 6300 5250 6225 5175 6300 5100 6375 5175 6300 5250 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 5700 5250 5625 5175 5700 5100 5775 5175 5700 5250 +2 2 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 4800 2700 7200 2700 7200 5100 4800 5100 4800 2700 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 4 + 5100 5100 5025 5250 5175 5250 5100 5100 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 5 + 0 0 1.00 60.00 120.00 + 6300 5250 6300 5700 8400 5700 8400 4200 7200 4200 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 5 + 0 0 1.00 60.00 120.00 + 5700 5250 5700 6000 9000 6000 9000 3600 7200 3600 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2 + 5100 5250 5100 8100 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 4725 3675 4650 3600 4725 3525 4800 3600 4725 3675 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 4725 4575 4650 4500 4725 4425 4800 4500 4725 4575 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 4650 3600 3600 3600 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 4650 4500 2400 4500 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 1800 2400 1800 4200 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 3000 2400 3000 3300 +2 1 1 1 0 7 100 0 -1 4.000 0 0 7 0 0 2 + 5850 1950 5850 2700 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 3000 2400 2925 2325 3000 2250 3075 2325 3000 2400 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 5 + 1800 2400 1725 2325 1800 2250 1875 2325 1800 2400 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2 + 3300 1950 8700 1950 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 2 + 9600 2400 9600 6600 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 4 + 7950 6900 7950 6600 10350 6600 10350 6900 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 4 + 9600 2250 9525 2400 9675 2400 9600 2250 +2 1 0 1 0 7 100 0 -1 4.000 0 0 7 0 0 2 + 4800 3000 7200 3000 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 2 + 4800 3300 7200 3300 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 4 + 2850 9300 2775 9450 2925 9450 2850 9300 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 4 + 2100 10200 2100 9900 3750 9900 3750 10200 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 5 + 6600 10200 6600 9900 10500 9900 10500 10200 10500 10125 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 2 + 2850 9450 2850 9900 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 2 + 8475 9450 8475 10200 +2 3 0 1 0 7 100 0 -1 0.000 0 0 -1 0 0 4 + 8475 9300 8400 9450 8550 9450 8475 9300 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 0 0 1 + 2775 6825 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 1 0 3 + 0 0 1.00 60.00 120.00 + 1800 10200 1800 9000 2100 9000 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 9900 10500 9300 10500 +2 1 0 1 0 7 100 0 -1 4.000 0 0 -1 1 0 2 + 0 0 1.00 60.00 120.00 + 7800 10500 7200 10500 +2 1 0 1 0 7 100 0 -1 4.000 0 0 7 0 0 4 + 2850 8700 2850 8100 8550 8100 8550 8700 +2 1 1 1 0 7 100 0 -1 4.000 0 0 -1 1 0 3 + 0 0 1.00 60.00 120.00 + 10350 7500 10350 9000 9300 9000 +2 1 1 1 0 7 100 0 -1 4.000 0 0 -1 1 0 3 + 0 0 1.00 60.00 120.00 + 7050 7200 2400 7200 2400 8700 +2 1 0 1 0 7 100 0 -1 0.000 0 0 -1 1 0 5 + 0 0 1.00 60.00 120.00 + 6900 5250 6900 5400 7800 5400 7800 4800 7200 4800 +4 0 0 100 0 18 14 0.0000 4 165 555 4950 2925 Node\001 +4 0 0 100 0 16 10 0.0000 4 150 825 7350 3525 dependency\001 +4 0 0 100 0 16 10 0.0000 4 45 60 7425 3825 *\001 +4 0 0 100 0 16 10 0.0000 4 120 555 7350 4125 srcnode\001 +4 0 0 100 0 16 10 0.0000 4 120 90 7425 4425 1\001 +4 0 0 100 0 16 10 0.0000 4 150 570 7350 4725 repnode\001 +4 0 0 100 0 16 10 0.0000 4 120 90 7425 5025 1\001 +4 0 0 100 0 16 10 0.0000 4 120 270 2550 4725 0..1\001 +4 0 0 100 0 16 10 0.0000 4 120 270 3750 3825 0..1\001 +4 0 0 100 0 0 12 0.0000 4 75 90 1875 4050 *\001 +4 0 0 100 0 0 12 0.0000 4 75 90 3075 3150 *\001 +4 0 0 100 0 16 14 0.0000 4 210 600 5100 3750 build()\001 +4 0 0 100 0 16 14 0.0000 4 210 630 5100 4260 scan()\001 +4 0 0 100 0 0 12 0.0000 4 135 90 9750 10725 1\001 +4 0 0 100 0 16 10 0.0000 4 120 90 1650 10125 1\001 +4 0 0 100 0 16 10 0.0000 4 45 60 1875 9225 *\001 +4 0 0 100 0 16 10 0.0000 4 120 90 7650 10725 1\001 +4 0 0 100 0 16 10 0.0000 4 45 60 7275 10725 *\001 +4 0 0 100 0 16 10 0.0000 4 45 60 9375 10725 *\001 diff --git a/doc/design/engine.jpg b/doc/design/engine.jpg new file mode 100644 index 0000000..2e82232 Binary files /dev/null and b/doc/design/engine.jpg differ diff --git a/doc/design/engine.xml b/doc/design/engine.xml new file mode 100644 index 0000000..1a1e335 --- /dev/null +++ b/doc/design/engine.xml @@ -0,0 +1,1964 @@ + + +
+ General Principles + +
+ Keyword arguments + + + + All methods and functions in this API will support the use of keyword + arguments in calls, for the sake of explicitness and readability. + For brevity in the hands of experts, most methods and functions + will also support positional arguments for their most-commonly-used + arguments. As an explicit example, the following two lines will each + arrange for an executable program named foo (or + foo.exe on a Win32 system) to be compiled from + the foo.c source file: + + + + + env.Program(target = 'foo', source = 'foo.c') + + env.Program('foo', 'foo.c') + + +
+ +
+ Internal object representation + + + + All methods and functions use internal (Python) objects that + represent the external objects (files, for example) for which they + perform dependency analysis. + + + + + + All methods and functions in this API that accept an external object + as an argument will accept either a string + description or an object reference. For example, the two following + two-line examples are equivalent: + + + + + env.Object(target = 'foo.o', source = 'foo.c') + env.Program(target = 'foo', 'foo.o') # builds foo from foo.o + + foo_obj = env.Object(target = 'foo.o', source = 'foo.c') + env.Program(target = 'foo', foo_obj) # builds foo from foo.o + + +
+ +
+ + + +
+ &ConsEnvs + + + + A &consenv; is the basic means by which a software system interacts + with the &SCons; Python API to control a build process. + + + + + + A &consenv; is an object with associated methods for generating target + files of various types (&Builder; objects), other associated object + methods for automatically determining dependencies from the contents + of various types of source files (&Scanner; objects), and a dictionary + of values used by these methods. + + + + + + Passing no arguments to the &Environment; instantiation creates a + &consenv; with default values for the current platform: + + + + + env = Environment() + + +
+ &Consvars; + + + + A &consenv; has an associated dictionary of &consvars; that control how + the build is performed. By default, the &Environment; method creates + a &consenv; with values that make most software build "out of the box" + on the host system. These default values will be generated at the + time &SCons; is installed using functionality similar to that provided + by GNU &Autoconf;. + + + It would be nice if we could avoid re-inventing the wheel here by + using some other Python-based tool &Autoconf replacement--like what + was supposed to come out of the Software Carpentry configuration + tool contest. It will probably be most efficient to roll our own + logic initially and convert if something better does come along. + + + At a minimum, there will be pre-configured sets of default values + that will provide reasonable defaults for UNIX and Windows NT. + + + + + + The default &consenv; values may be overridden when a new &consenv; is + created by specifying keyword arguments: + + + + + env = Environment(CC = 'gcc', + CCFLAGS = '-g', + CPPPATH = ['.', 'src', '/usr/include'], + LIBPATH = ['/usr/lib', '.']) + + +
+ +
+ Fetching &consvars; + + + + A copy of the dictionary of &consvars; can be returned using + the &Dictionary; method: + + + + + env = Environment() + dict = env.Dictionary() + + + + + If any arguments are supplied, then just the corresponding value(s) + are returned: + + + + + ccflags = env.Dictionary('CCFLAGS') + cc, ld = env.Dictionary('CC', 'LD') + + +
+ +
+ Copying a &consenv; + + + + A method exists to return a copy of an existing environment, with + any overridden values specified as keyword arguments to the method: + + + + + env = Environment() + debug = env.Copy(CCFLAGS = '-g') + + +
+ +
+ Multiple &consenvs; + + + + Different external objects often require different build + characteristics. Multiple &consenvs; may be defined, each with + different values: + + + + + env = Environment(CCFLAGS = '') + debug = Environment(CCFLAGS = '-g') + env.Make(target = 'hello', source = 'hello.c') + debug.Make(target = 'hello-debug', source = 'hello.c') + + + + + Dictionaries of values from multiple &consenvs; may be passed to the + &Environment; instantiation or the &Copy; method, in which case the + last-specified dictionary value wins: + + + + + env1 = Environment(CCFLAGS = '-O', LDFLAGS = '-d') + env2 = Environment(CCFLAGS = '-g') + new = Environment(env1.Dictionary(), env2.Dictionary()) + + + + + The new environment in the above example retains + LDFLAGS = '-d' from the env1 + environment, and CCFLAGS = '-g' from the + env2 environment. + + + + + +
+ +
+ Variable substitution + + + + Within a construction command, any variable from the &consenv; may + be interpolated by prefixing the name of the construction with + $: + + + + + MyBuilder = Builder(command = "$XX $XXFLAGS -c $_INPUTS -o $target") + + env.Command(targets = 'bar.out', sources = 'bar.in', + command = "sed '1d' < $source > $target") + + + + + Variable substitution is recursive: the command line is expanded + until no more substitutions can be made. + + + + + + Variable names following the $ may be enclosed in + braces. This can be used to concatenate an interpolated value with an + alphanumeric character: + + + + + VerboseBuilder = Builder(command = "$XX -${XXFLAGS}v > $target") + + + + + The variable within braces may contain a pair of parentheses + after a Python function name to be evaluated (for example, + ${map()}). &SCons; will interpolate the return + value from the function (presumably a string): + + + + + env = Environment(FUNC = myfunc) + env.Command(target = 'foo.out', source = 'foo.in', + command = "${FUNC($<)}") + + + + + If a referenced variable is not defined in the &consenv;, + the null string is interpolated. + + + + + + The following special variables can also be used: + + + + + + + $targets + + + + All target file names. If multiple targets are specified in an + array, $targets expands to the entire list of + targets, separated by a single space. + + + + + + Individual targets from a list may be extracted by enclosing + the targets keyword in braces and using the + appropriate Python array index or slice: + + + + + ${targets[0]} # expands to the first target + + ${targets[1:]} # expands to all but the first target + + ${targets[1:-1]} # expands to all but the first and last targets + + + + + + + $target + + + + A synonym for ${targets[0]}, the first target + specified. + + + + + + + $sources + + + + All input file names. Any input file names that + are used anywhere else on the current command + line (via ${sources[0]}, + ${sources{[1]}, etc.) are removed from the + expanded list. + + + + + + + + + + Any of the above special variables may be enclosed in braces and + followed immediately by one of the following attributes to select just + a portion of the expanded path name: + + + + + + + .base + + + + Basename: the directory plus the file name, minus any file suffix. + + + + + + + .dir + + + + The directory in which the file lives. This is a relative path, + where appropriate. + + + + + + + .file + + + + The file name, minus any directory portion. + + + + + + + .suffix + + + + The file name suffix (that is, the right-most dot in the file name, + and all characters to the right of that). + + + + + + + .filebase + + + + The file name (no directory portion), minus any file suffix. + + + + + + + .abspath + + + + The absolute path to the file. + + + + + + + +
+ +
+ + + +
+ &Builder; Objects + + + + By default, &SCons; supplies (and uses) a number of pre-defined + &Builder; objects: + + + + + + + + + &Object; + compile or assemble an object file + + + + &Library; + archive files into a library + + + + &SharedLibrary; + archive files into a shared library + + + + &Program; + link objects and/or libraries into an executable + + + + &MakeBuilder; + build according to file suffixes; see below + + + + + + + + + + + A &consenv; can be explicitly initialized with associated &Builder; + objects that will be bound to the &consenv; object: + + + + + env = Environment(BUILDERS = ['Object', 'Program']) + + + + + &Builder; objects bound to a &consenv; can be called directly as + methods. When invoked, a &Builder; object returns a (list of) objects + that it will build: + + + + + obj = env.Object(target ='hello.o', source = 'hello.c') + lib = env.Library(target ='libfoo.a', + source = ['aaa.c', 'bbb.c']) + slib = env.SharedLibrary(target ='libbar.so', + source = ['xxx.c', 'yyy.c']) + prog = env.Program(target ='hello', + source = ['hello.o', 'libfoo.a', 'libbar.so']) + + +
+ Specifying multiple inputs + + + + Multiple input files that go into creating a target file may be passed + in as a single string, with the individual file names separated by + white space: + + + + + env.Library(target = 'foo.a', source = 'aaa.c bbb.c ccc.c') + env.Object(target = 'yyy.o', source = 'yyy.c') + env.Program(target = 'bar', source = 'xxx.c yyy.o foo.a') + + + + + Alternatively, multiple input files that go into creating a target + file may be passed in as an array. This allows input files to be + specified using their object representation: + + + + + env.Library(target = 'foo.a', source = ['aaa.c', 'bbb.c', 'ccc.c']) + yyy_obj = env.Object(target = 'yyy.o', source = 'yyy.c') + env.Program(target = 'bar', source = ['xxx.c', yyy_obj, 'foo.a']) + + + + + Individual string elements within an array of input files are + not further split into white-space separated + file names. This allows file names that contain white space to + be specified by putting the value into an array: + + + env.Program(target = 'foo', source = ['an input file.c']) + + + + +
+ +
+ Specifying multiple targets + + + + Conversely, the generated target may be a string listing multiple + files separated by white space: + + + + + env.Object(target = 'grammar.o y.tab.h', source = 'grammar.y') + + + + + An array of multiple target files can be used to mix string and object + representations, or to accomodate file names that contain white space: + + + + + env.Program(target = ['my program'], source = 'input.c') + + +
+ +
+ File prefixes and suffixes + + + + For portability, if the target file name does not already have an + appropriate file prefix or suffix, the &Builder; objects will + append one appropriate for the file type on the current system: + + + + + # builds 'hello.o' on UNIX, 'hello.obj' on Windows NT: + obj = env.Object(target ='hello', source = 'hello.c') + + # builds 'libfoo.a' on UNIX, 'foo.lib' on Windows NT: + lib = env.Library(target ='foo', source = ['aaa.c', 'bbb.c']) + + # builds 'libbar.so' on UNIX, 'bar.dll' on Windows NT: + slib = env.SharedLibrary(target ='bar', source = ['xxx.c', 'yyy.c']) + + # builds 'hello' on UNIX, 'hello.exe' on Windows NT: + prog = env.Program(target ='hello', + source = ['hello.o', 'libfoo.a', 'libbar.so']) + + +
+ +
+ &Builder; object exceptions + + + + &Builder; objects raise the following exceptions on error: + + + + +
+ +
+ User-defined &Builder; objects + + + + Users can define additional &Builder; objects for specific external + object types unknown to &SCons;. A &Builder; object may build its + target by executing an external command: + + + + + WebPage = Builder(command = 'htmlgen $HTMLGENFLAGS $sources > $target', + suffix = '.html', + src_suffix = '.in') + + + + + Alternatively, a &Builder; object may also build its target by + executing a Python function: + + + + + def update(dest): + # [code to update the object] + return 1 + + OtherBuilder1 = Builder(function = update, + src_suffix = ['.in', '.input']) + + + + + An optional argument to pass to the function may be specified: + + + + + def update_arg(dest, arg): + # [code to update the object] + return 1 + + OtherBuilder2 = Builder(function = update_arg, + function_arg = 'xyzzy', + src_suffix = ['.in', '.input']) + + + + + Both an external command and an internal function may be specified, + in which case the function will be called to build the object first, + followed by the command line. + + + + + + + + User-defined &Builder; objects can be used like the default &Builder; + objects to initialize &consenvs;. + + + + + WebPage = Builder(command = 'htmlgen $HTMLGENFLAGS $sources > $target', + suffix = '.html', + src_suffix = '.in') + env = Environment(BUILDERS = ['WebPage']) + env.WebPage(target = 'foo.html', source = 'foo.in') + # Builds 'bar.html' on UNIX, 'bar.htm' on Windows NT: + env.WebPage(target = 'bar', source = 'bar.in') + + + + + The command-line specification can interpolate variables from the + &consenv;; see "Variable substitution," above. + + + + + + A &Builder; object may optionally be initialized with a list of: + + + + + + + + the prefix of the target file (e.g., 'lib' for libraries) + + + + + + + + the suffix of the target file (e.g., '.a' for libraries) + + + + + + + + the expected suffixes of the input files + (e.g., '.o' for object files) + + + + + + + + These arguments are used in automatic + dependency analysis and to generate output file names that don't + have suffixes supplied explicitly. + + +
+ +
+ Copying &Builder; Objects + + + + A &Copy; method exists to return a copy of an existing &Builder; + object, with any overridden values specified as keyword arguments to + the method: + + + + + build = Builder(function = my_build) + build_out = build.Copy(suffix = '.out') + + + + + Typically, &Builder; objects will be supplied by a tool-master or + administrator through a shared &consenv;. + + +
+ +
+ Special-purpose build rules + + + + A pre-defined &Command; builder exists to associate a target file with + a specific command or list of commands for building the file: + + + + + env.Command(target = 'foo.out', source = + command = 'foo.in', "foo.process $sources > $target") + + commands = [ "bar.process -o .tmpfile $sources", + "mv .tmpfile $target" ] + env.Command(target = 'bar.out', source = 'bar.in', command = commands) + + + + This is useful when it's too cumbersome to create a &Builder; + object just to build a single file in a special way. + + +
+ +
+ The &MakeBuilder; &Builder; + + + + A pre-defined &Builder; object named &MakeBuilder; exists to make + simple builds as easy as possible for users, at the expense of + sacrificing some build portability. + + + + + + The following minimal example builds the 'hello' program from the + 'hello.c' source file: + + + + + Environment().Make('hello', 'hello.c') + + + + + Users of the &MakeBuilder; &Builder; object are not required to + understand intermediate steps involved in generating a file--for + example, the distinction between compiling source code into an object + file, and then linking object files into an executable. The details + of intermediate steps are handled by the invoked method. Users that + need to, however, can specify intermediate steps explicitly: + + + + + env = Environment() + env.Make(target = 'hello.o', source = 'hello.c') + env.Make(target = 'hello', source = 'hello.o') + + + + + The &MakeBuilder; method understands the file suffixes specified and + "does the right thing" to generate the target object and program + files, respectively. It does this by examining the specified output + suffixes for the &Builder; objects bound to the environment. + + + + + + Because file name suffixes in the target and source file names + must be specified, the &MakeBuilder; method can't be used + portably across operating systems. In other words, for the + example above, the &MakeBuilder; builder will not generate + hello.exe on Windows NT. + + + +
+ +
+ &Builder; maps + + + + + + The env.Make method "does the right thing" to + build different file types because it uses a dictionary from the + &consenv; that maps file suffixes to the appropriate &Builder; object. + This &BUILDERMAP; can be initialized at instantiation: + + + + + env = Environment(BUILDERMAP = { + '.o' : Object, + '.a' : Library, + '.html' : WebPage, + '' : Program, + }) + + + + + With the &BUILDERMAP; properly initialized, the + env.Make method can be used to build additional + file types: + + + + + env.Make(target = 'index.html', source = 'index.input') + + + + + &Builder; objects referenced in the &BUILDERMAP; do not need to be + listed separately in the &BUILDERS; variable. The &consenv; will + bind the union of the &Builder; objects listed in both variables. + + + + + +
+ +
+ + + +
+ Dependencies + +
+ Automatic dependencies + + + + By default, &SCons; assumes that a target file has automatic + dependencies on the: + + + +
+ + + tool used to build the target file + + contents of the input files + + command line used to build the target file + + +
+ + + + If any of these changes, the target file will be rebuilt. + + +
+ +
+ Implicit dependencies + + + + Additionally, &SCons; can scan the contents of files for + implicit dependencies on other files. For + example, &SCons; will scan the contents of a .c + file and determine that any object created from it is + dependent on any .h files specified via + #include. &SCons;, therefore, "does the right + thing" without needing to have these dependencies listed explicitly: + + + + + % cat Construct + env = Environment() + env.Program('hello', 'hello.c') + % cat hello.c + #include "hello_string.h" + main() + { + printf("%s\n", STRING); + } + % cat > hello_string.h + #define STRING "Hello, world!\n" + % scons . + gcc -c hello.c -o hello.o + gcc -o hello hello.c + % ./hello + Hello, world! + % cat > hello_string.h + #define STRING "Hello, world, hello!\n" + % scons . + gcc -c hello.c -o hello.o + gcc -o hello hello.c + % ./hello + Hello, world, hello! + % + + +
+ +
+ Ignoring dependencies + + + + Undesirable automatic dependencies or + implicit dependencies may be ignored: + + + + + env.Program(target = 'bar', source = 'bar.c') + env.Ignore('bar', '/usr/bin/gcc', 'version.h') + + + + + In the above example, the bar program will not + be rebuilt if the /usr/bin/gcc compiler or the + version.h file change. + + +
+ +
+ Explicit dependencies + + + + Dependencies that are unknown to &SCons; may be specified explicitly + in an &SCons; configuration file: + + + + + env.Dependency(target = 'output1', dependency = 'input_1 input_2') + env.Dependency(target = 'output2', dependency = ['input_1', 'input_2']) + env.Dependency(target = 'output3', dependency = ['white space input']) + + env.Dependency(target = 'output_a output_b', dependency = 'input_3') + env.Dependency(target = ['output_c', 'output_d'], dependency = 'input_4') + env.Dependency(target = ['white space output'], dependency = 'input_5') + + + + + Just like the target keyword argument, the + dependency keyword argument may be specified as a + string of white-space separated file names, or as an array. + + + + + + A dependency on an &SCons; configuration file itself may be specified + explicitly to force a rebuild whenever the configuration file changes: + + + + + env.Dependency(target = 'archive.tar.gz', dependency = 'SConstruct') + + +
+ +
+ + + +
+ &Scanner; Objects + + + + Analagous to the previously-described &Builder; objects, &SCons; + supplies (and uses) &Scanner; objects to search the contents of + a file for implicit dependency files: + + + + + + + + + CScan + scan .{c,C,cc,cxx,cpp} files for #include dependencies + + + + + + + + + A &consenv; can be explicitly initialized with + associated &Scanner; objects: + + + + + env = Environment(SCANNERS = ['CScan', 'M4Scan']) + + + + + &Scanner; objects bound to a &consenv; can be + associated directly with specified files: + + + + + env.CScan('foo.c', 'bar.c') + env.M4Scan('input.m4') + + +
+ User-defined &Scanner; objects + + + + A user may define a &Scanner; object to scan a type of file for + implicit dependencies: + + + + + def scanner1(file_contents): + # search for dependencies + return dependency_list + + FirstScan = Scanner(function = scanner1) + + + + + The scanner function must return a list of dependencies that its finds + based on analyzing the file contents it is passed as an argument. + + + + + + The scanner function, when invoked, will be passed the calling + environment. The scanner function can use &consenvs; from the passed + environment to affect how it performs its dependency scan--the + canonical example being to use some sort of search-path construction + variable to look for dependency files in other directories: + + + + + def scanner2(file_contents, env): + path = env.{'SCANNERPATH'} # XXX + # search for dependencies using 'path' + return dependency_list + + SecondScan = Scanner(function = scanner2) + + + + + The user may specify an additional argument when the &Scanner; object + is created. When the scanner is invoked, the additional argument + will be passed to the scanner funciton, which can be used in any way + the scanner function sees fit: + + + + + def scanner3(file_contents, env, arg): + # skip 'arg' lines, then search for dependencies + return dependency_list + + Skip_3_Lines_Scan = Scanner(function = scanner2, argument = 3) + Skip_6_Lines_Scan = Scanner(function = scanner2, argument = 6) + + +
+ +
+ Copying &Scanner; Objects + + + + A method exists to return a copy of an existing &Scanner; object, + with any overridden values specified as keyword arguments to the + method: + + + + + scan = Scanner(function = my_scan) + scan_path = scan.Copy(path = '%SCANNERPATH') + + + + + Typically, &Scanner; objects will be supplied by a tool-master or + administrator through a shared &consenv;. + + +
+ +
+ &Scanner; maps + + + + + + Each &consenv; has a &SCANNERMAP;, a dictionary that associates + different file suffixes with a scanner object that can be used to + generate a list of dependencies from the contents of that file. This + &SCANNERMAP; can be initialized at instantiation: + + + + + env = Environment(SCANNERMAP = { + '.c' : CScan, + '.cc' : CScan, + '.m4' : M4Scan, + }) + + + + + &Scanner; objects referenced in the &SCANNERMAP; do not need to + be listed separately in the &SCANNERS; variable. The &consenv; + will bind the union of the &Scanner; objects listed + in both variables. + + + +
+ +
+ + + +
+ Targets + + + + The methods in the build engine API described so far merely + establish associations that describe file dependencies, how a + file should be scanned, etc. Since the real point is to actually + build files, &SCons; also has methods that + actually direct the build engine to build, or otherwise manipulate, + target files. + + + +
+ Building targets + + + One or more targets may be built as follows: + + + + + env.Build(target = ['foo', 'bar']) + + + + + Note that specifying a directory (or other collective object) will + cause all subsidiary/dependent objects to be built as well: + + + + + env.Build(target = '.') + + env.Build(target = 'builddir') + + + + + By default, &SCons; explicitly removes a target file before + invoking the underlying function or command(s) to build it. + + +
+ +
+ Removing targets + + + + A "cleanup" operation of removing generated (target) files is + performed as follows: + + + + + env.Clean(target = ['foo', 'bar']) + + + + + Like the &Build; method, the &Clean; method may be passed a + directory or other collective object, in which case the subsidiary + target objects under the directory will be removed: + + + + + env.Clean(target = '.') + + env.Clean(target = 'builddir') + + + + + (The directories themselves are not removed.) + + +
+ +
+ Suppressing cleanup removal of build-targets + + + + By default, &SCons; explicitly removes all build-targets + when invoked to perform "cleanup". Files that should not be + removed during "cleanup" can be specified via the + &NoClean; method: + + + + + env.Library(target = 'libfoo.a', source = ['aaa.c', 'bbb.c', 'ccc.c']) + env.NoClean('libfoo.a') + + + + + The NoClean operation has precedence over the Clean operation. + A target that is specified as both Clean and NoClean, will not + be removed during a clean. + + In the following example, target 'foo' will not be removed + during "cleanup": + + + env.Clean(target = 'foo') + env.NoClean('foo') + + + + + +
+ +
+ Suppressing build-target removal + + + + As mentioned, by default, &SCons; explicitly removes a target + file before invoking the underlying function or command(s) to build + it. Files that should not be removed before rebuilding can be + specified via the &Precious; method: + + + + + env.Library(target = 'libfoo.a', source = ['aaa.c', 'bbb.c', 'ccc.c']) + env.Precious('libfoo.a') + + +
+ +
+ Default targets + + + + The user may specify default targets that will be built if there are no + targets supplied on the command line: + + + + + env.Default('install', 'src') + + + + + Multiple calls to the &Default; method (typically one per &SConscript; + file) append their arguments to the list of default targets. + + +
+ +
+ File installation + + + + Files may be installed in a destination directory: + + + + + env.Install('/usr/bin', 'program1', 'program2') + + + + + Files may be renamed on installation: + + + + + env.InstallAs('/usr/bin/xyzzy', 'xyzzy.in') + + + + + Multiple files may be renamed on installation by specifying + equal-length lists of target and source files: + + + + + env.InstallAs(['/usr/bin/foo', '/usr/bin/bar'], + ['foo.in', 'bar.in']) + + +
+ +
+ Target aliases + + + + In order to provide convenient "shortcut" target names that expand to + a specified list of targets, aliases may be established: + + + + + env.Alias(alias = 'install', + targets = ['/sbin', '/usr/lib', '/usr/share/man']) + + + + + In this example, specifying a target of install + will cause all the files in the associated directories to be built + (that is, installed). + + + + + + An &Alias; may include one or more other &Aliases; in its list: + + + + + env.Alias(alias = 'libraries', targets = ['lib']) + env.Alias(alias = 'programs', targets = ['libraries', 'src']) + + +
+ +
+ + + +
+ Customizing output + + + + + + The &SCons; API supports the ability to customize, redirect, or + suppress its printed output through user-defined functions. + &SCons; has several pre-defined points in its build process at + which it calls a function to (potentially) print output. User-defined + functions can be specified for these call-back points when &Build; + or &Clean;is invoked: + + + + + env.Build(target = '.', + on_analysis = dump_dependency, + pre_update = my_print_command, + post_update = my_error_handler) + on_error = my_error_handler) + + + + + The specific call-back points are: + + + + + + + on_analysis + + + + Called for every object, immediately after the object has been + analyzed to see if it's out-of-date. Typically used to print a + trace of considered objects for debugging of unexpected dependencies. + + + + + + + pre_update + + + + Called for every object that has been determined to be out-of-date + before its update function or command is executed. Typically used + to print the command being called to update a target. + + + + + + + post_update + + + + Called for every object after its update function or command has + been executed. Typically used to report that a top-level specified + target is up-to-date or was not remade. + + + + + + + on_error + + + + Called for every error returned by an update function or command. + Typically used to report errors with some string that will be + identifiable to build-analysis tools. + + + + + + + + + + Functions for each of these call-back points all take the same + arguments: + + + + + my_dump_dependency(target, level, status, update, dependencies) + + + + + where the arguments are: + + + + + + + target + + + + The target object being considered. + + + + + + + level + + + + Specifies how many levels the dependency analysis has + recursed in order to consider the target. + A value of 0 specifies a top-level + target (that is, one passed to the + &Build; or &Clean; method). Objects which a top-level + target is directly dependent upon have a + level of <1>, their direct dependencies have a + level of <2>, etc. Typically used to indent + output to reflect the recursive levels. + + + + + + + status + + + + A string specifying the current status of the target + ("unknown", "built", + "error", "analyzed", etc.). A + complete list will be enumerated and described during implementation. + + + + + + + update + + + + The command line or function name that will be (or has been) executed + to update the target. + + + + + + + dependencies + + + + A list of direct dependencies of the target. + + + + + + + +
+ + + +
+ Separate source and build trees + + + + + + + + &SCons; allows target files to be built completely separately from + the source files by "linking" a build directory to an underlying + source directory: + + + + + env.Link('build', 'src') + + SConscript('build/SConscript') + + + + + &SCons; will copy (or hard link) necessary files (including the + &SConscript; file) into the build directory hierarchy. This allows the + source directory to remain uncluttered by derived files. + + + +
+ + + +
+ Variant builds + + + + The &Link; method may be used in conjunction with multiple + &consenvs; to support variant builds. The following + &SConstruct; and &SConscript; files would build separate debug and + production versions of the same program side-by-side: + + + + + % cat SConstruct + env = Environment() + env.Link('build/debug', 'src') + env.Link('build/production', 'src') + flags = '-g' + SConscript('build/debug/SConscript', Export(env)) + flags = '-O' + SConscript('build/production/SConscript', Export(env)) + % cat src/SConscript + env = Environment(CCFLAGS = flags) + env.Program('hello', 'hello.c') + + + + + The following example would build the appropriate program for the current + compilation platform, without having to clean any directories of object + or executable files for other architectures: + + + + + % cat SConstruct + build_platform = os.path.join('build', sys.platform) + Link(build_platform, 'src') + SConscript(os.path.join(build_platform, 'SConscript')) + % cat src/SConscript + env = Environment + env.Program('hello', 'hello.c') + + +
+ + + +
+ Code repositories + + + + + + &SCons; may use files from one or more shared code repositories in order + to build local copies of changed target files. A repository would + typically be a central directory tree, maintained by an integrator, + with known good libraries and executables. + + + + + Repository('/home/source/1.1', '/home/source/1.0') + + + + + Specified repositories will be searched in-order for any file + (configuration file, input file, target file) that does not exist + in the local directory tree. When building a local target file, + &SCons; will rewrite path names in the build command to use the + necessary repository files. This includes modifying lists of + or flags to specify an + appropriate set of include paths for dependency analysis. + + + + + &SCons; will modify the Python sys.path variable to + reflect the addition of repositories to the search path, so that any + imported modules or packages necessary for the build can be found in a + repository, as well. + + + + + If an up-to-date target file is found in a code repository, the file + will not be rebuilt or copied locally. Files that must exist locally + (for example, to run tests) may be specified: + + + + + Local('program', 'libfoo.a') + + + + + in which case &SCons; will copy or link an up-to-date copy of the + file from the appropriate repository. + + + +
+ + + +
+ Derived-file caching + + + + + + &SCons; can maintain a cache directory of target files which may be + shared among multiple builds. This reduces build times by allowing + developers working on a project together to share common target + files: + + + + + Cache('/var/tmp/build.cache/i386') + + + + + When a target file is generated, a copy is added to the cache. + When generating a target file, if &SCons; determines that a file + that has been built with the exact same dependencies already exists + in the specified cache, &SCons; will copy the cached file rather + than re-building the target. + + + + + Command-line options exist to modify the &SCons; caching behavior + for a specific build, including disabling caching, building + dependencies in random order, and displaying commands as if cached + files were built. + + + +
+ + + +
+ Job management + + + + + + A simple API exists to inform the Build Engine how many jobs may + be run simultaneously: + + + + + Jobs(limit = 4) + + +
diff --git a/doc/design/goals.xml b/doc/design/goals.xml new file mode 100644 index 0000000..2a7b69b --- /dev/null +++ b/doc/design/goals.xml @@ -0,0 +1,216 @@ + + + + + As a next-generation build tool, + &SCons should fundamentally + improve on its predecessors. + Rather than simply being driven by trying to + not be like previous tools, + &SCons; aims to satisfy the following goals: + + + + + + + Practicality + + + + The &SCons; design emphasizes + an implementable feature set + that lets users get practical, useful work done. + &SCons; is helped in this regard by its roots in &Cons;, + which has had its feature set honed by + several years of input + from a dedicated band of users. + + + + + + + Portability + + + + &SCons; is intended as a portable build tool, + able to handle software construction tasks + on a variety of operating systems. + It should be possible (although not mandatory) + to use &SCons; so that the same configuration file + builds the same software correctly on, + for example, both Linux and Windows NT. + Consequently, &SCons; should hide from users + operating-system-dependent details + such as filename extensions + (for example, .o + vs. .obj). + + + + + + + + + + + Usability + + + + Novice users should be able to grasp quickly + the rudiments of using &SCons; to build their software. + This extends to installing &SCons;, too. + Installation should be painless, + and the installed &SCons; + should work "out of the box" + to build most software. + + + + + + This goal should be kept in mind during implementation, + when there is always a tendency to try to optimize too early. + Speed is nice, but not as important as clarity + and ease of use. + + + + + + + Utility + + + + &SCons; should also provide a rich enough set of features + to accommodate building more complicated software projects. + However, the features required for + building complicated software projects + should not get in the way of novice users. + (See the previous goal.) + In other words, complexity should be available + when it's needed + but not required to get work done. + Practically, this implies that &SCons; + shouldn't be dumbed down to the point it + excludes complicated software builds. + + + + + + + Sharability + + + + As a key element in balancing the conflicting + needs of Usability and Utility, + &SCons; should provide mechanisms to + allow &SCons; users to share build rules, + dependency scanners, and other objects and recipes + for constructing software. + A good sharing mechanism should support + the model wherein most developers on a project + use rules and templates + that are created + and maintained by a local integrator or build-master, + + + + + + + Extensibility + + + + &SCons; should provide mechanisms for + easily extending its capabilities, + including building new types of files, + adding new types of dependency scanning, + being able to accomodate dependencies + between objects other than files, + etc. + + + + + + + Flexibility + + + + In addition to providing a useful command-line interface, + &SCons; should provide the right architectural + framework for embedding its dependency management + in other interfaces. + &SCons; would help strengthen other GUIs or IDEs + and the additional requirements of the + other interfaces would help broaden and solidify + the core &SCons; dependency management. + + + + + + + +
+ Fixing &Make;'s problems + + + + + + + +
+ +
+ Fixing &Cons;'s problems + + + + + + + +
diff --git a/doc/design/install.xml b/doc/design/install.xml new file mode 100644 index 0000000..e670e83 --- /dev/null +++ b/doc/design/install.xml @@ -0,0 +1,28 @@ + + + diff --git a/doc/design/intro.xml b/doc/design/intro.xml new file mode 100644 index 0000000..561baa4 --- /dev/null +++ b/doc/design/intro.xml @@ -0,0 +1,111 @@ + + + + + The &SCons; tool provides an easy-to-use, feature-rich interface + for constructing software. Architecturally, &SCons; separates + its dependency analysis and external object management into an + interface-independent Build Engine that could be embedded in any + software system that can run Python. + + + + + + At the command line, &SCons; presents an easily-grasped tool + where configuration files are Python scripts, reducing the need + to learn new build-tool syntax. Inexperienced users can use + intelligent methods that ``do the right thing'' to build software + with a minimum of fuss. Sophisticated users can use a rich set + of underlying features for finer control of the build process, + including mechanisms for easily extending the build process to new + file types. + + + + + + Dependencies are tracked using digital signatures, + which provide more robust dependency analysis than file time + stamps. Implicit dependencies are determined automatically by + scanning the contents of source files, avoiding the need for + laborious and fragile maintenance of static lists of dependencies in + configuration files. + + + + + + The &SCons; tool supports use of files from one or more central code + repositories, a mechanism for caching derived files, and parallel + builds. The tool also includes a framework for sharing build + environments, which allows system administrators or integrators to + define appropriate build parameters for use by other users. + + + +
+ About This Document + + + + This document is an ongoing work-in-progress to write down the ideas + and tradeoffs that have gone, and will go into, the &SCons; design. + As such, this is intended primarily for use by developers and others + working on &SCons;, although it is also intended to serve as a + detailed overview of &SCons; for other interested parties. It will + be continually updated and evolve, and will likely overlap with other + documentation produced by the project. Sections of this document + that deal with syntax, for example, may move or be copied into a user + guide or reference manual. + + + + + + So please don't assume that everything mentioned here has been + decided and carved in stone. If you have ideas for improvements, or + questions about things that don't seem to make any sense, please help + improve the design by speaking up about them. + + + + + +
diff --git a/doc/design/issues.xml b/doc/design/issues.xml new file mode 100644 index 0000000..1f9a78c --- /dev/null +++ b/doc/design/issues.xml @@ -0,0 +1,195 @@ + + + + No build tools is perfect. + Here are some &SCons; issues that + do not yet have solutions. + + + +
+ Interaction with SC-config + + + + The SC-config tool will be used in the &SCons; installation + process to generate an appropriate default construction environment + so that building most software works "out of the box" on the + installed platform. The SC-config tool will find reasonable default + compilers (C, C++, Fortran), linkers/loaders, library archive tools, + etc. for specification in the default &SCons; construction + environment. + + + +
+ +
+ Interaction with test infrastructures + + + + &SCons; can be configured to use SC-test (or some other test tool) + to provide controlled, automated testing of software. The &Link; + method could link a test subdirectory to a build + subdirectory: + + + + + Link('test', 'build') + SConscript('test/SConscript') + + + + Any test cases checked in with the source code will be linked + into the test subdirectory and executed. If + &SConscript; files and test cases are written with this in mind, then + invoking: + + + + + % sccons test + + + + Would run all the automated test cases that depend on any changed + software. + + + + + + +
+ +
+ Java dependencies + + + + Java dependencies are difficult for an external dependency-based + construction tool to accomodate. Determining Java class dependencies + is more complicated than the simple pattern-matching of C or C++ + #include files. From the point of view of an + external build tool, the Java compiler behaves "unpredictably" + because it may create or update multiple output class files and + directories as a result of its internal class dependencies. + + + + + + An obvious &SCons; implementation would be to have the &Scanner; + object parse output from Java -depend -verbose to + calculate dependencies, but this has the distinct disadvantage of + requiring two separate compiler invocations, thereby slowing down + builds. + + + +
+ +
+ Limitations of digital signature calculation + + + + In practice, calculating digital signatures of a file's contents is a + more robust mechanism than time stamps for determining what needs + building. However: + + + + + + + + + Developers used to the time stamp model of &Make; can initially + find digital signatures counter-intuitive. The assumption that: + + + % touch file.c + + will cause a rebuild of file is strong... + + + + + + + + Abstracting dependency calculation into a single digital signature + loses a little information: It is no longer possible to tell + (without laborious additional calculation) which input file dependency + caused a rebuild of a given target file. A feature that could + report, "I'm rebuilding file X because it's out-of-date with respect + to file Y," would be good, but an digital-signature implementation of + such a feature is non-obvious. + + + + + + +
+ +
+ Remote execution + + + + The ability to use multiple build systems through remote execution + of tools would be good. This should be implementable through the + &Job; class. Construction environments would need modification + to specify build systems. + + + +
+ +
+ Conditional builds + + + + The ability to check run-time conditions as suggested on the + sc-discuss mailing list ("build X only if: the machine is idle / + the file system has Y megabytes free space") would also be good, + but is not part of the current design. + + + +
diff --git a/doc/design/main.xml b/doc/design/main.xml new file mode 100644 index 0000000..e991b36 --- /dev/null +++ b/doc/design/main.xml @@ -0,0 +1,158 @@ + + + + + + %version; + --> + + + + + + + %scons; + + + + + + + + + + + + +]> + + + + SCons Design version &buildversion; + + + Steven + Knight + + + Revision &buildrevision; (&builddate;) + + 2001 + + + 2001 + Steven Knight + + + + ©right; + + + version &buildversion; + + + + + Introduction + &intro; + + + + Goals + &goals; + + + + Overview + &overview; + + + + Build Engine API + &engine; + + + + Native Python Interface + &native; + + + + + + Other Issues + &issues; + + + + Background + &bground; + + + + Summary + + + &SCons; offers a robust and feature-rich design for an SC-build + tool. With a Build Engine based on the proven design of + the &Cons; utility, it offers increased simplification of the + user interface for unsophisticated users with the addition + of the "do-the-right-thing" env.Make + method, increased flexibility for sophisticated users with the + addition of &Builder; and &Scanner; objects, a mechanism to + allow tool-masters (and users) to share working construction + environments, and embeddability to provide reliable dependency + management in a variety of environments and interfaces. + + + + + + Acknowledgements + &acks; + + + diff --git a/doc/design/native.xml b/doc/design/native.xml new file mode 100644 index 0000000..8cdd867 --- /dev/null +++ b/doc/design/native.xml @@ -0,0 +1,364 @@ + + + + + The "Native Python" interface is the interface + that the actual &SCons; utility will present to users. + Because it exposes the Python Build Engine API, + &SCons; users will have direct access to the complete + functionality of the Build Engine. + In contrast, a different user interface such as a GUI + may choose to only use, and present to the end-user, + a subset of the Build Engine functionality. + + + +
+ Configuration files + + + + &SCons; configuration files are simply Python scripts that invoke + methods to specify target files to be built, rules for building the + target files, and dependencies. Common build rules are available by + default and need not be explicitly specified in the configuration + files. + + + + + + By default, the &SCons; utility searches for a file named + &SConstruct;, &Sconstruct; or &sconstruct (in that order) in the + current directory, and reads its configuration from the first file + found. A command-line option exists to read a + different file name. + + + +
+ + + +
+ Python syntax + + + + Because &SCons; configuration files are Python scripts, normal Python + syntax can be used to generate or manipulate lists of targets or + dependencies: + + + + + sources = ['aaa.c', 'bbb.c', 'ccc.c'] + env.Make('bar', sources) + + + + + Python flow-control can be used to iterate through invocations of + build rules: + + + + + objects = ['aaa.o', 'bbb.o', 'ccc.o'] + for obj in objects: + src = replace(obj, '.o', '.c') + env.Make(obj, src) + + + + + or to handle more complicated conditional invocations: + + + + + # only build 'foo' on Linux systems + if sys.platform == 'linux1': + env.Make('foo', 'foo.c') + + + + + Because &SCons; configuration files are Python scripts, syntax errors + will be caught by the Python parser. Target-building does not begin + until after all configuration files are read, so a syntax error will + not cause a build to fail half-way. + + + +
+ + + +
+ Subsidiary configuration Files + + + + A configuration file can instruct &SCons; to read up subsidiary + configuration files. Subsidiary files are specified explicitly in a + configuration file via the &SConscript; method. As usual, multiple + file names may be specified with white space separation, or in an + array: + + + + + SConscript('other_file') + SConscript('file1 file2') + SConscript(['file3', 'file4']) + SConscript(['file name with white space']) + + + + + An explicit sconscript keyword may be used: + + + + + SConscript(sconscript = 'other_file') + + + + + Including subsidiary configuration files is recursive: a configuration + file included via &SConscript; may in turn &SConscript; other + configuration files. + + + +
+ + + +
+ Variable scoping in subsidiary files + + + + When a subsidiary configuration file is read, it is given its own + namespace; it does not have automatic access to variables from the parent + configuration file. + + + + + + Any variables (not just &SCons; objects) that are to be shared between configuration files must be + explicitly passed in the &SConscript; call + using the &Export method: + + + + + env = Environment() + debug = Environment(CCFLAGS = '-g') + installdir = '/usr/bin' + SConscript('src/SConscript', Export(env=env, debug=debug, installdir=installdir)) + + + + + + + Which may be specified explicitly using a keyword argument: + + + + + env = Environment() + debug = Environment(CCFLAGS = '-g') + installdir = '/usr/bin' + SConscript(sconscript = 'src/SConscript', + export = Export(env=env, debug=debug, installdir=installdir)) + + + + + Explicit variable-passing provides control over exactly what is available + to a subsidiary file, and avoids unintended side effects of changes in + one configuration file affecting other far-removed configuration files + (a very hard-to-debug class of build problem). + + + +
+ + + +
+ Hierarchical builds + + + + The &SConscript; method is so named because, by convention, subsidiary + configuration files in subdirectories are named &SConscript;: + + + + + SConscript('src/SConscript') + SConscript('lib/build_me') + + + + + When a subsidiary configuration file is read from a subdirectory, all + of that configuration file's targets and build rules are interpreted + relative to that directory (as if &SCons; had changed its working + directory to that subdirectory). This allows for easy support of + hierarchical builds of directory trees for large projects. + + + +
+ + + +
+ Sharing &consenvs; + + + + &SCons; will allow users to share &consenvs, as well as other &SCons; + objects and Python variables, by importing them from a central, shared + repository using normal Python syntax: + + + + + from LocalEnvironments import optimized, debug + + optimized.Make('foo', 'foo.c') + debug.Make('foo-d', 'foo.c') + + + + + The expectation is that some local tool-master, integrator or + administrator will be responsible for assembling environments (creating + the &Builder; objects that specify the tools, options, etc.) and make + these available for sharing by all users. + + + + + + The modules containing shared &consenvs; + (LocalEnvironments in the above example) can be + checked in and controlled with the rest of the source files. This + allows a project to track the combinations of tools and command-line + options that work on different platforms, at different times, and with + different tool versions, by using already-familiar revision control + tools. + + + +
+ + + +
+ Help + + + + The &SCons; utility provides a &Help; function to allow the writer + of a &SConstruct; file to provide help text that is specific to + the local build tree: + + + + + Help(""" + Type: + scons . build and test everything + scons test build the software + scons src run the tests + scons web build the web pages + """) + + + + + This help text is displayed in response to the + command-line option. Calling the &Help; function more than once is an + error. + + + +
+ + + +
+ Debug + + + + &SCons; supports several command-line options for printing extra + information with which to debug build problems. + + + + + + + + + + See the -d, -p, -pa, and -pw options + in the , below. + All of these options make use of call-back functions to + + printed by the Build Engine. + + + + + +
diff --git a/doc/design/overview.xml b/doc/design/overview.xml new file mode 100644 index 0000000..38e4258 --- /dev/null +++ b/doc/design/overview.xml @@ -0,0 +1,498 @@ + + +
+ Architecture + + + + The heart of &SCons; is its Build Engine. + The &SCons; Build Engine is a Python module + that manages dependencies between + external objects + such as files or database records. + The Build Engine is designed to + be interface-neutral + and easily embeddable in any + software system that needs dependency + analysis between updatable objects. + + + + + + The key parts of the Build Engine architecture + are captured in the following quasi-UML diagram: + + + + + + + + + + The point of &SCons; is to manage + dependencies between arbitrary external objects. + Consequently, the Build Engine does not restrict or specify + the nature of the external objects it manages, + but instead relies on subclass of the &Node; + class to interact with the external system or systems + (file systems, database management systems) + that maintain the objects being examined or updated. + + + + + + The Build Engine presents to the software system in + which it is embedded + a Python API for specifying source (input) and target (output) objects, + rules for building/updating objects, + rules for scanning objects for dependencies, etc. + Above its Python API, + the Build Engine is completely + interface-independent, + and can be encapsulated by any other software + that supports embedded Python. + + + + + + Software that chooses to use the Build Engine + for dependency management + interacts with it + through Construction Environments. + A Construction Environment consists + of a dictionary of environment variables, + and one or more associated + &Scanner; objects + and &Builder; objects. + The Python API is used to + form these associations. + + + + + + A &Scanner; object specifies + how to examine a type of source object + (C source file, database record) + for dependency information. + A &Scanner; object may use + variables from the associated + Construction Environment + to modify how it scans an object: + specifying a search path for included files, + which field in a database record to consult, + etc. + + + + + + A &Builder; object specifies + how to update a type of target object: + executable program, object file, database field, etc. + Like a &Scanner; object, + a &Builder; object may use + variables from the associated + Construction Environment + to modify how it builds an object: + specifying flags to a compiler, + using a different update function, + etc. + + + + + + &Scanner; and &Builder; objects will return one or more + &Node; objects that represent external objects. + &Node; objects are the means by which the + Build Engine tracks dependencies: + A &Node; may represent a source (input) object that + should already exist, + or a target (output) object which may be built, + or both. + The &Node; class is sub-classed to + represent external objects of specific type: + files, directories, database fields or records, etc. + Because dependency information, however, + is tracked by the top-level &Node; methods and attributes, + dependencies can exist + between nodes representing different external object types. + For example, + building a file could be made + dependent on the value of a given + field in a database record, + or a database table could depend + on the contents of an external file. + + + + + + The Build Engine uses a &Job; class (not displayed) + to manage the actual work of updating external target objects: + spawning commands to build files, + submitting the necessary commands to update a database record, + etc. + The &Job; class has sub-classes + to handle differences between spawning + jobs in parallel and serially. + + + + + + The Build Engine also uses a + &Signature; class (not displayed) + to maintain information about whether + an external object is up-to-date. + Target objects with out-of-date signatures + are updated using the appropriate + &Builder; object. + + + + + + + + + +
+ + + +
+ Build Engine + + + + More detailed discussion of some of the + Build Engine's characteristics: + + + +
+ Python API + + + + The Build Engine can be embedded in any other software + that supports embedding Python: + in a GUI, + in a wrapper script that + interprets classic Makefile syntax, + or in any other software that + can translate its dependency representation + into the appropriate calls to the Build Engine API. + describes in detail + the specification for a "Native Python" interface + that will drive the &SCons; implementation effort. + + + +
+ +
+ Single-image execution + + + + When building/updating the objects, + the Build Engine operates as a single executable + with a complete Directed Acyclic Graph (DAG) + of the dependencies in the entire build tree. + This is in stark contrast to the + commonplace recursive use of Make + to handle hierarchical directory-tree builds. + + + +
+ +
+ Dependency analysis + + + + Dependency analysis is carried out via digital signatures + (a.k.a. "fingerprints"). + Contents of object are examined and reduced + to a number that can be stored and compared to + see if the object has changed. + Additionally, &SCons; uses the same + signature technique on the command-lines that + are executed to update an object. + If the command-line has changed since the last time, + then the object must be rebuilt. + + + +
+ +
+ Customized output + + + + The output of Build Engine is customizable + through user-defined functions. + This could be used to print additional desired + information about what &SCons; is doing, + or tailor output to a specific build analyzer, + GUI, or IDE. + + + +
+ +
+ Build failures + + + + &SCons; detects build failures via the exit status from the tools + used to build the target files. By default, a failed exit status + (non-zero on UNIX systems) terminates the build with an appropriate + error message. An appropriate class from the Python library will + interpret build-tool failures via an OS-independent API. + + + + + + If multiple tasks are executing in a parallel build, and one tool + returns failure, &SCons; will not initiate any further build tasks, + but allow the other build tasks to complete before terminating. + + + + + + A command-line option may be used to ignore + errors and continue building other targets. In no case will a target + that depends on a failed build be rebuilt. + + + +
+ +
+ + + +
+ Interfaces + + + + As previously described, + the &SCons; Build Engine + is interface-independent above its Python API, + and can be embedded in any software system + that can translate its dependency requirements + into the necessary Python calls. + + + + + + The "main" &SCons; interface + for implementation purposes, + uses Python scripts as configuration files. + Because this exposes the Build Engine's Python API to the user, + it is current called the "Native Python" interface. + + + + + + This section will also discuss + how &SCons; will function in the context + of two other interfaces: + the &Makefile; interface of the classic &Make; utility, + and a hypothetical graphical user interface (GUI). + + + +
+ Native Python interface + + + + The Native Python interface is intended to be the primary interface + by which users will know &SCons;--that is, + it is the interface they will use + if they actually type &SCons; at a command-line prompt. + + + + + + In the Native Python interface, &SCons; configuration files are simply + Python scripts that directly invoke methods from the Build Engine's + Python API to specify target files to be built, rules for building + the target files, and dependencies. Additional methods, specific to + this interface, are added to handle functionality that is specific to + the Native Python interface: reading a subsidiary configuration file; + copying target files to an installation directory; etc. + + + + + + Because configuration files are Python scripts, Python flow control + can be used to provide very flexible manipulation of objects and + dependencies. For example, a function could be used to invoke a common + set of methods on a file, and called iteratively over an array of + files. + + + + + + As an additional advantage, syntax errors in &SCons; Native Python + configuration files will be caught by the Python parser. Target-building + does not begin until after all configuration files are read, so a syntax + error will not cause a build to fail half-way. + + + +
+ +
+ Makefile interface + + + + An alternate &SCons; interface would provide backwards + compatibility with the classic &Make utility. + This would be done by embedding the &SCons; Build Engine + in a Python script that can translate existing + &Makefile;s into the underlying calls to the + Build Engine's Python API + for building and tracking dependencies. + Here are approaches to solving some of the issues + that arise from marrying these two pieces: + + + + + + + + &Makefile; suffix rules can be translated + into an appropriate &Builder; object + with suffix maps from the Construction Environment. + + + + + + Long lists of static dependences + appended to a &Makefile; by + various "make depend" schemes + can be preserved + but supplemented by + the more accurate dependency information + provided by &Scanner; objects. + + + + + + Recursive invocations of &Make; + can be avoided by reading up + the subsidiary &Makefile; instead. + + + + + + + + Lest this seem like too outlandish an undertaking, + there is a working example of this approach: + Gary Holt's &Makepp; utility + is a Perl script that provides + admirably complete parsing of complicated &Makefile;s + around an internal build engine inspired, + in part, by the classic Cons utility. + + + +
+ +
+ Graphical interfaces + + + + The &SCons; Build Engine + is designed from the ground up to be embedded + into multiple interfaces. + Consequently, embedding the dependency capabilities + of &SCons; into graphical interface + would be a matter of mapping the + GUI's dependency representation + (either implicit or explicit) + into corresponding calls to the Python API + of the &SCons; Build Engine. + + + + + + Note, however, that this proposal leaves the problem of + designed a good graphical interface + for representing software build dependencies + to people with actual GUI design experience... + + + +
+ +
diff --git a/doc/design/scons.mod b/doc/design/scons.mod new file mode 100644 index 0000000..d805210 --- /dev/null +++ b/doc/design/scons.mod @@ -0,0 +1,429 @@ + + + + + + +Aegis"> +Ant"> +Autoconf"> +Automake"> +cc"> +Cons"> +cp"> +csh"> +gcc"> +Jam"> +jar"> +javac"> +javah"> +Make"> +Make++"> +Python"> +ranlib"> +rmic"> +SCons"> +scons"> +ScCons"> +tar"> +touch"> +zip"> + + + + +Action"> +ActionBase"> +CommandAction"> +FunctionAction"> +ListAction"> +Builder"> +BuilderBase"> +CompositeBuilder"> +MultiStepBuilder"> +Job"> +Jobs"> +Serial"> +Parallel"> +Node"> +Node.FS"> +Scanner"> +Sig"> +Signature"> +Taskmaster"> +TimeStamp"> +Walker"> +Wrapper"> + + + + + +--debug=explain"> +--implicit-cache"> +--implicit-deps-changed"> +--implicit-deps-unchanged"> +-Q"> + + + +implicit_cache"> +implicit_deps_changed"> +implicit_deps_unchanged"> + + + + + +build"> +Makefile"> +Makefiles"> +SConscript"> +SConstruct"> +Sconstruct"> +sconstruct"> +.sconsign"> +src"> + + + + + +Add"> +AddOptions"> +Alias"> +Aliases"> +Append"> +BoolOption"> +Build"> +CacheDir"> +Clean"> +Clone"> +Command"> +Configure"> +Copy"> +Default"> +DefaultRules"> +Depends"> +Dir"> +Entry"> +EnumOption"> +Environment"> +Export"> +File"> +Finish"> +GenerateHelpText"> +Help"> +Ignore"> +Import"> +Install"> +InstallAs"> +Link"> +ListOption"> +Local"> +Module"> +NoClean"> +Objects"> +Options"> +PackageOption"> +PathOption"> +Precious"> +Prepend"> +Replace"> +Repository"> +Return"> +RuleSet"> +Salt"> +SetBuildSignatureType"> +SetContentSignatureType"> +SourceSignature"> +SourceSignatures"> +Split"> +TargetSignatures"> +Task"> + + +subst"> + + +Message"> +Result"> +CheckCHeader"> +CheckCXXHeader"> +CheckFunc"> +CheckHeader"> +CheckLib"> +CheckLibWithHeader"> +CheckType"> +TryAction"> +TryBuild"> +TryCompile"> +TryLink"> +TryRun"> + + +str"> +zipfile"> + + +Cache"> + + + + + +ARGUMENTS"> +BUILD_TARGETS"> +COMMAND_LINE_TARGETS"> +DEFAULT_TARGETS"> + + + + + +BUILDERMAP"> +BUILDERS"> +CC"> +CCFLAGS"> +CCCOM"> +COLOR"> +COLORS"> +CONFIG"> +CPPDEFINES"> +ENV"> +JAVACLASSDIR"> +LIBDIRPREFIX"> +LIBDIRSUFFIX"> +LIBLINKPREFIX"> +LIBLINKSUFFIX"> +LIBPATH"> +LIBS"> +LINK"> +LINKCOM"> +LINKFLAGS"> +RELEASE"> +RELEASE_BUILD"> +SCANNERMAP"> +SCANNERS"> +TARFLAGS"> +TARSUFFIX"> + + + + + +PATH"> +PYTHONPATH"> +SCONSFLAGS"> + + + + + +allowed_values"> +build_dir"> +map"> +ignorecase"> +options"> +exports"> +source"> +target"> + + + + + +all"> +none"> + + + + + +BuildDir"> +CFile"> +CXXFile"> +DVI"> +Jar"> +Java"> +JavaH"> +Library"> +Object"> +PCH"> +PDF"> +PostScript"> +Program"> +RES"> +RMIC"> +SharedLibrary"> +SharedObject"> +StaticLibrary"> +StaticObject"> +Tar"> +Zip"> + + +Make"> + + + + + +builder function"> +builder method"> + +Configure Contexts"> +configure context"> + +Construction Environment"> +Construction Environments"> +Construction environment"> +Construction environments"> +construction environment"> +construction environments"> + +Construction Variable"> +Construction Variables"> +Construction variable"> +Construction variables"> +construction variable"> +construction variables"> + +CPPPATH"> + +Dictionary"> + +Emitter"> +emitter"> +Generator"> +generator"> + +Nodes"> + +signature"> +build signature"> + +true"> +false"> + +typedef"> + + + +bar"> +common1.c"> +common2.c"> +custom.py"> +goodbye"> +goodbye.o"> +goodbye.obj"> +file.dll"> +file.in"> +file.lib"> +file.o"> +file.obj"> +file.out"> +foo"> +foo.o"> +foo.obj"> +hello"> +hello.c"> +hello.exe"> +hello.h"> +hello.o"> +hello.obj"> +libfile_a"> +libfile_so"> +new_hello"> +new_hello.exe"> +prog"> +prog1"> +prog2"> +prog.c"> +prog.exe"> +stdio.h"> + + + ++"> +#"> + + + +announce@scons.tigris.org"> +dev@scons.tigris.org"> +users@scons.tigris.org"> diff --git a/doc/developer/MANIFEST b/doc/developer/MANIFEST new file mode 100644 index 0000000..eece338 --- /dev/null +++ b/doc/developer/MANIFEST @@ -0,0 +1,9 @@ +architecture.xml +branches.xml +copyright.xml +cycle.xml +main.xml +packaging.xml +preface.xml +sourcetree.xml +testing.xml diff --git a/doc/developer/architecture.xml b/doc/developer/architecture.xml new file mode 100644 index 0000000..3a6e18c --- /dev/null +++ b/doc/developer/architecture.xml @@ -0,0 +1,40 @@ + + + + + XXX + + + +
+ Architecture + + + + XXX + + +
diff --git a/doc/developer/branches.xml b/doc/developer/branches.xml new file mode 100644 index 0000000..e46b616 --- /dev/null +++ b/doc/developer/branches.xml @@ -0,0 +1,40 @@ + + + + + XXX + + + +
+ &SCons; Branches + + + + XXX + + +
diff --git a/doc/developer/copyright.xml b/doc/developer/copyright.xml new file mode 100644 index 0000000..7a37cb9 --- /dev/null +++ b/doc/developer/copyright.xml @@ -0,0 +1,32 @@ + + +
+ + + SCons Developer's Guide Copyright (c) 2007 Steven Knight + + +
diff --git a/doc/developer/cycle.xml b/doc/developer/cycle.xml new file mode 100644 index 0000000..b56edd2 --- /dev/null +++ b/doc/developer/cycle.xml @@ -0,0 +1,40 @@ + + + + + XXX + + + +
+ Development Cycle + + + + XXX + + +
diff --git a/doc/developer/main.xml b/doc/developer/main.xml new file mode 100644 index 0000000..0ebed33 --- /dev/null +++ b/doc/developer/main.xml @@ -0,0 +1,110 @@ + + + + + + %version; + + + %scons; + + + + + + + + + + +]> + + + + SCons Developer's Guide &buildversion; + + + Steven + Knight + + + Revision &buildrevision; (&builddate;) + + 2007 + + + 2007 + Steven Knight + + + + ©right; + + + version &buildversion; + + + + + Preface + &preface; + + + + Development Cycle + &cycle; + + + + Source Tree + &sourcetree; + + + + Testing + &testing; + + + + Branches + &branches; + + + + Packaging + &packaging; + + + + Architecture + &architecture; + + + diff --git a/doc/developer/packaging.xml b/doc/developer/packaging.xml new file mode 100644 index 0000000..92b6a31 --- /dev/null +++ b/doc/developer/packaging.xml @@ -0,0 +1,40 @@ + + + + + XXX + + + +
+ Packaging + + + + XXX + + +
diff --git a/doc/developer/preface.xml b/doc/developer/preface.xml new file mode 100644 index 0000000..ece9cdc --- /dev/null +++ b/doc/developer/preface.xml @@ -0,0 +1,175 @@ + + + + + This document assumes that you already know how &SCons; + and that you want to learn how to work on the code. + + + +
+ &SCons; Principles + + + + There are a few overriding principles + we try to live up to in designing and implementing &SCons: + + + + + + + Correctness + + + + + First and foremost, + by default, &SCons; guarantees a correct build + even if it means sacrificing performance a little. + We strive to guarantee the build is correct + regardless of how the software being built is structured, + how it may have been written, + or how unusual the tools are that build it. + + + + + + + Performance + + + + + Given that the build is correct, + we try to make &SCons; build software + as quickly as possible. + In particular, wherever we may have needed to slow + down the default &SCons; behavior to guarantee a correct build, + we also try to make it easy to speed up &SCons; + through optimization options that let you trade off + guaranteed correctness in all end cases for + a speedier build in the usual cases. + + + + + + + Convenience + + + + + &SCons; tries to do as much for you out of the box as reasonable, + including detecting the right tools on your system + and using them correctly to build the software. + + + + + + + + + + In a nutshell, we try hard to make &SCons; just + "do the right thing" and build software correctly, + with a minimum of hassles. + + + +
+ +
+ Acknowledgements + + + + &SCons; would not exist without a lot of help + from a lot of people, + many of whom may not even be aware + that they helped or served as inspiration. + So in no particular order, + and at the risk of leaving out someone: + + + + + + First and foremost, + &SCons; owes a tremendous debt to Bob Sidebotham, + the original author of the classic Perl-based &Cons; tool + which Bob first released to the world back around 1996. + Bob's work on Cons classic provided the underlying architecture + and model of specifying a build configuration + using a real scripting language. + My real-world experience working on Cons + informed many of the design decisions in SCons, + including the improved parallel build support, + making Builder objects easily definable by users, + and separating the build engine from the wrapping interface. + + + + + + Greg Wilson was instrumental in getting + &SCons; started as a real project + when he initiated the Software Carpentry design + competition in February 2000. + Without that nudge, + marrying the advantages of the Cons classic + architecture with the readability of Python + might have just stayed no more than a nice idea. + + + + + + Thanks to Peter Miller + for his splendid change management system, &Aegis;, + which has provided the &SCons; project + with a robust development methodology from day one, + and which showed me how you could + integrate incremental regression tests into + a practical development cycle + (years before eXtreme Programming arrived on the scene). + + + + + + And last, thanks to Guido van Rossum + for his elegant scripting language, + which is the basis not only for the &SCons; implementation, + but for the interface itself. + + + +
diff --git a/doc/developer/sourcetree.xml b/doc/developer/sourcetree.xml new file mode 100644 index 0000000..97bfee0 --- /dev/null +++ b/doc/developer/sourcetree.xml @@ -0,0 +1,40 @@ + + + + + XXX + + + +
+ Source Tree + + + + XXX + + +
diff --git a/doc/developer/testing.xml b/doc/developer/testing.xml new file mode 100644 index 0000000..a41c61b --- /dev/null +++ b/doc/developer/testing.xml @@ -0,0 +1,40 @@ + + + + + XXX + + + +
+ Testing + + + + XXX + + +
diff --git a/doc/man/MANIFEST b/doc/man/MANIFEST new file mode 100644 index 0000000..8e69d1c --- /dev/null +++ b/doc/man/MANIFEST @@ -0,0 +1,2 @@ +scons.1 +sconsign.1 diff --git a/doc/man/scons-time.1 b/doc/man/scons-time.1 new file mode 100644 index 0000000..8231c9d --- /dev/null +++ b/doc/man/scons-time.1 @@ -0,0 +1,1017 @@ +.\" Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining +.\" a copy of this software and associated documentation files (the +.\" "Software"), to deal in the Software without restriction, including +.\" without limitation the rights to use, copy, modify, merge, publish, +.\" distribute, sublicense, and/or sell copies of the Software, and to +.\" permit persons to whom the Software is furnished to do so, subject to +.\" the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included +.\" in all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +.\" KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +.\" WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +.\" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +.\" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +.\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +.\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +.\" +.\" doc/man/scons-time.1 4577 2009/12/27 19:44:43 scons +.\" +.\" ES - Example Start - indents and turns off line fill +.de ES +.RS +.nf +.. +.\" EE - Example End - ends indent and turns line fill back on +.de EE +.RE +.fi +.. +'\"========================================================================== +.de SF +.B scons-time func +[\fB-h\fR] +[\fB--chdir=\fIDIR\fR] +[\fB-f \fIFILE\fR] +[\fB--fmt=\fIFORMAT\fR] +[\fB--func=\fINAME\fR] +[\fB-p \fISTRING\fR] +[\fB-t \fINUMBER\fR] +[\fB--title= TITLE\fR] +[\fIARGUMENTS\fR] +.. +'\"-------------------------------------------------------------------------- +.de SY +.B scons-time mem +[\fB-h\fR] +[\fB--chdir=\fIDIR\fR] +[\fB-f \fIFILE\fR] +[\fB--fmt=\fIFORMAT\fR] +[\fB-p \fISTRING\fR] +[\fB--stage=\fISTAGE\fR] +[\fB-t \fINUMBER\fR] +[\fB--title=\fITITLE\fR] +[\fIARGUMENTS\fR] +.. +'\"-------------------------------------------------------------------------- +.de SO +.B scons-time obj +[\fB-h\fR] +[\fB--chdir=\fIDIR\fR] +[\fB-f \fIFILE\fR] +[\fB--fmt=\fIFORMAT\fR] +[\fB-p \fISTRING\fR] +[\fB--stage=\fISTAGE\fR] +[\fB-t \fINUMBER\fR] +[\fB--title=\fITITLE\fR] +[\fIARGUMENTS\fR] +.. +'\"-------------------------------------------------------------------------- +.de SR +.B scons-time run +[\fB-hnqv\fR] +[\fB--aegis=\fIPROJECT\fR] +[\fB-f \fIFILE\fR] +[\fB--number=\fINUMBER\fR] +[\fB--outdir=\fIOUTDIR\fR] +[\fB-p \fISTRING\fR] +[\fB--python=\fIPYTHON\fR] +[\fB-s \fIDIR\fR] +[\fB--scons=\fISCONS\fR] +[\fB--svn=\fIURL\fR] +[\fIARGUMENTS\fR] +.. +'\"-------------------------------------------------------------------------- +.de ST +.B scons-time time +[\fB-h\fR] +[\fB--chdir=\fIDIR\fR] +[\fB-f \fIFILE\fR] +[\fB--fmt=\fIFORMAT\fR] +[\fB-p \fISTRING\fR] +[\fB-t \fINUMBER\fR] +[\fB--title=\fITITLE\fR] +[\fB--which=\fIWHICH\fR] +[\fIARGUMENTS\fR] +.. +.TH SCONS-TIME 1 "December 2009" +.SH NAME +scons-time \- generate and display SCons timing information +'\"========================================================================== +.SH SYNOPSIS +.B scons-time +.IR subcommand +[ +.IR options ... +] +[ +.IR arguments ... +] +'\"-------------------------------------------------------------------------- +.SS "Generating Timing Information" +.SR +'\"-------------------------------------------------------------------------- +.SS "Extracting Function Timings" +.SF +'\"-------------------------------------------------------------------------- +.SS "Extracting Memory Statistics" +.SY +'\"-------------------------------------------------------------------------- +.SS "Extracting Object Counts" +.SO +'\"-------------------------------------------------------------------------- +.SS "Extracting Execution Times" +.ST +'\"-------------------------------------------------------------------------- +.SS "Help Text" +.B scons-time help +.I SUBCOMMAND +[...] +'\"========================================================================== +.SH DESCRIPTION +The +.B scons-time +command runs an SCons configuration +through a standard set of profiled timings +and can extract and graph information from the +resulting profiles and log files of those timings. +The action to be performed by the +.B scons-time +script is specified +by a subcommand, the first argument on the command line. +See the +.B SUBCOMMANDS +section below for information about the operation +of specific subcommands. +.P +The basic way to use +.B scons-time +is to run the +.B scons-time run +subcommand +(possibly multiple times) +to generate profile and log file output, +and then use one of the other +subcommands to display the results +captured in the profiles and log files +for a particular kind of information: +function timings +(the +.B scons-time func +subcommand), +total memory used +(the +.B scons-time mem +subcommand), +object counts +(the +.B scons-time obj +subcommand) +and overall execution time +(the +.B scons-time time +subcommand). +Options exist to place and find the +profiles and log files in separate directories, +to generate the output in a format suitable +for graphing with the +.BR gnuplot (1) +program, +and so on. +.P +There are two basic ways the +.B scons-time run +subcommand +is intended to be used +to gather timing statistics +for a configuration. +One is to use the +.B --svn= +option to test a configuration against +a list of revisions from the SCons Subversion repository. +This will generate a profile and timing log file +for every revision listed with the +.B --number= +option, +and can be used to look at the +impact of commited changes to the +SCons code base on a particular +configuration over time. +.P +The other way is to profile incremental changes to a +local SCons code base during a development cycle--that is, +to look at the performance impact of changes +you're making in the local tree. +In this mode, +you run the +.B scons-time run +subcommand +.I without +the +.B --svn= +option, +in which case it simply looks in the profile/log file output directory +(the current directory by default) +and automatically figures out the +.I next +run number for the output profile and log file. +Used in this way, +the development cycle goes something like: +make a change to SCons; +run +.B scons-time run +to profile it against a specific configuration; +make another change to SCons; +run +.B scons-time run +again to profile it; +etc. +'\"========================================================================== +.SH OPTIONS +The +.B scons-time +command only supports a few global options: +.TP +-h, --help +Displays the global help text and exits, +identical to the +.B scons-time help +subcommand. +.TP +-V, --version +Displays the +.B scons-time +version and exits. +.P +Most functionality is controlled by options +to the individual subcommands. +See the next section for information +about individual subcommand options. +'\"========================================================================== +.SH SUBCOMMANDS +The +.B scons-time +command supports the following +individual subcommands. +'\"-------------------------------------------------------------------------- +.SS "The func Subcommand" +.SF +.P +The +.B scons-time func +subcommand displays timing information +for a specific Python function within SCons. +By default, it extracts information about the +.BR _main () +function, +which includes the Python profiler timing +for all of SCons. +.P +The +.B scons-time func +subcommand extracts function timing information +from all the specified file arguments, +which should be Python profiler output files. +(Normally, these would be +.B *.prof +files generated by the +.B scons-time run +subcommand, +but they can actually be generated +by any Python profiler invocation.) +All file name arguments will be +globbed for on-disk files. +.P +If no arguments are specified, +then function timing information +will be extracted from all +.B *.prof +files, +or the subset of them +with a prefix specified by the +.B -p +option. +.P +Options include: +.TP +-C DIRECTORY, --chdir=DIRECTORY +Changes to the specified +.I DIRECTORY +before looking for the specified files +(or files that match the specified patterns). +.TP +-f FILE, --file=FILE +Reads configuration information from the specified +.IR FILE . +.TP +-fmt=FORMAT, --format=FORMAT +Reports the output in the specified +.IR FORMAT . +The formats currently supported are +.B ascii +(the default) +and +.BR gnuplot . +.TP +--func=NAME +Extracts timings for the specified function +.IR NAME . +The default is to report cumulative timings for the +.BR _main () +function, +which contains the entire SCons run. +.TP +-h, --help +Displays help text for the +.B scons-time func +subcommand. +.TP +-p STRING, --prefix=STRING +Specifies the prefix string for profiles +from which to extract function timing information. +This will be used to search for profiles +if no arguments are specified on the command line. +.TP +-t NUMBER, --tail=NUMBER +Only extracts function timings from the last +.I NUMBER +files. +'\"-------------------------------------------------------------------------- +.SS "The help Subcommand" +.B scons-time help +.I SUBCOMMAND +[...] +The +.B help +subcommand prints help text for any +other subcommands listed as later arguments on the command line. +'\"-------------------------------------------------------------------------- +.SS "The mem Subcommand" +.SY +.P +The +.B scons-time mem +subcommand displays how much memory SCons uses. +.P +The +.B scons-time mem +subcommand extracts memory use information +from all the specified file arguments, +which should be files containing output from +running SCons with the +.B --debug=memory +option. +(Normally, these would be +.B *.log +files generated by the +.B scons-time run +subcommand.) +All file name arguments will be +globbed for on-disk files. +.P +If no arguments are specified, +then memory information +will be extracted from all +.B *.log +files, +or the subset of them +with a prefix specified by the +.B -p +option. +.P +.TP +-C DIR, --chdir=DIR +Changes to the specified +.I DIRECTORY +before looking for the specified files +(or files that match the specified patterns). +.TP +-f FILE, --file=FILE +Reads configuration information from the specified +.IR FILE . +.TP +-fmt=FORMAT, --format=FORMAT +Reports the output in the specified +.IR FORMAT . +The formats currently supported are +.B ascii +(the default) +and +.BR gnuplot . +.TP +-h, --help +Displays help text for the +.B scons-time mem +subcommand. +.TP +-p STRING, --prefix=STRING +Specifies the prefix string for log files +from which to extract memory usage information. +This will be used to search for log files +if no arguments are specified on the command line. +.TP +--stage=STAGE +Prints the memory used at the end of the specified +.IR STAGE : +.B pre-read +(before the SConscript files are read), +.B post-read , +(after the SConscript files are read), +.B pre-build +(before any targets are built) +or +.B post-build +(after any targets are built). +If no +.B --stage +option is specified, +the default behavior is +.BR post-build , +which reports the final amount of memory +used by SCons during each run. +.TP +-t NUMBER, --tail=NUMBER +Only reports memory statistics from the last +.I NUMBER +files. +'\"-------------------------------------------------------------------------- +.SS "The obj Subcommand" +.SO +.P +The +.B scons-time obj +subcommand displays how many objects of a specific named type +are created by SCons. +.P +The +.B scons-time obj +subcommand extracts object counts +from all the specified file arguments, +which should be files containing output from +running SCons with the +.B --debug=count +option. +(Normally, these would be +.B *.log +files generated by the +.B scons-time run +subcommand.) +All file name arguments will be +globbed for on-disk files. +.P +If no arguments are specified, +then object counts +will be extracted from all +.B *.log +files, +or the subset of them +with a prefix specified by the +.B -p +option. +.TP +-C DIR, --chdir=DIR +Changes to the specified +.I DIRECTORY +before looking for the specified files +(or files that match the specified patterns). +.TP +-f FILE, --file=FILE +Reads configuration information from the specified +.IR FILE . +.TP +-fmt=FORMAT, --format=FORMAT +Reports the output in the specified +.IR FORMAT . +The formats currently supported are +.B ascii +(the default) +and +.BR gnuplot . +.TP +-h, --help +Displays help text for the +.B scons-time obj +subcommand. +.TP +-p STRING, --prefix=STRING +Specifies the prefix string for log files +from which to extract object counts. +This will be used to search for log files +if no arguments are specified on the command line. +.TP +--stage=STAGE +Prints the object count at the end of the specified +.IR STAGE : +.B pre-read +(before the SConscript files are read), +.B post-read , +(after the SConscript files are read), +.B pre-build +(before any targets are built) +or +.B post-build +(after any targets are built). +If no +.B --stage +option is specified, +the default behavior is +.BR post-build , +which reports the final object count during each run. +.TP +-t NUMBER, --tail=NUMBER +Only reports object counts from the last +.I NUMBER +files. +'\"-------------------------------------------------------------------------- +.SS "The run Subcommand" +.SR +The +.B scons-time run +subcommand is the basic subcommand +for profiling a specific configuration +against a version of SCons. +.P +The configuration to be tested +is specified as a list of files +or directories that will be unpacked or copied +into a temporary directory +in which SCons will be invoked. +The +.B scons-time run +subcommand understands file suffixes like +.BR .tar , +.BR .tar.gz , +.BR .tgz +and +.BR .zip +and will unpack their contents into a temporary directory. +If more than one argument is specified, +each one will be unpacked or copied +into the temporary directory "on top of" +the previous archives or directories, +so the expectation is that multiple +specified archives share the same directory layout. +.P +Once the file or directory arguments are unpacked or +copied to the temporary directory, +the +.B scons-time run +subcommand runs the +requested version of SCons +against the configuration +three times: +.TP +Startup +SCons is run with the +.B --help +option so that just the SConscript files are read, +and then the default help text is printed. +This profiles just the perceived "overhead" of starting up SCons +and processing the SConscript files. +.TP +Full build +SCons is run to build everything specified in the configuration. +Specific targets to be passed in on the command l ine +may be specified by the +.B targets +keyword in a configuration file; see below for details. +.TP +Rebuild +SCons is run again on the same just-built directory. +If the dependencies in the SCons configuration are correct, +this should be an up-to-date, "do nothing" rebuild. +.P +Each invocation captures the output log file and a profile. +.P +The +.B scons-time run +subcommand supports the following options: +.TP +--aegis=PROJECT +Specifies the Aegis +.I PROJECT +from which the +version(s) of +.B scons +being timed will be extracted. +When +.B --aegis +is specified, the +.BI --number= NUMBER +option specifies delta numbers +that will be tested. +Output from each invocation run will be placed in file +names that match the Aegis delta numbers. +If the +.B --number= +option is not specified, +then the default behavior is to time the +tip of the specified +.IR PROJECT . +.TP +-f FILE, --file=FILE +Reads configuration information from the specified +.IR FILE . +This often provides a more convenient way to specify and +collect parameters associated with a specific timing configuration +than specifying them on the command line. +See the +.B CONFIGURATION FILE +section below +for information about the configuration file parameters. +.TP +-h, --help +Displays help text for the +.B scons-time run +subcommand. +.TP +-n, --no-exec +Do not execute commands, +just printing the command-line equivalents of what would be executed. +Note that the +.B scons-time +script actually executes its actions in Python, +where possible, +for portability. +The commands displayed are UNIX +.I equivalents +of what it's doing. +.TP +--number=NUMBER +Specifies the run number to be used in the names of +the log files and profile outputs generated by this run. +.IP +When used in conjuction with the +.BI --aegis= PROJECT +option, +.I NUMBER +specifies one or more comma-separated Aegis delta numbers +that will be retrieved automatically from the specified Aegis +.IR PROJECT . +.IP +When used in conjuction with the +.BI --svn= URL +option, +.I NUMBER +specifies one or more comma-separated Subversion revision numbers +that will be retrieved automatically from the Subversion +repository at the specified +.IR URL . +Ranges of delta or revision numbers +may be specified be separating two numbers +with a hyphen +.RB ( \- ). +.P +Example: +.ES +% scons-time run --svn=http://scons.tigris.org/svn/trunk --num=1247,1249-1252 . +.EE +.TP +-p STRING, --prefix=STRING +Specifies the prefix string to be used for all of the log files +and profiles generated by this run. +The default is derived from the first +specified argument: +if the first argument is a directory, +the default prefix is the name of the directory; +if the first argument is an archive +(tar or zip file), +the default prefix is the the base name of the archive, +that is, what remains after stripping the archive suffix +.RB ( .tgz ", " .tar.gz " or " .zip ). +.TP +--python=PYTHON +Specifies a path to the Python executable to be used +for the timing runs. +The default is to use the same Python executable that +is running the +.B scons-time +command itself. +.TP +-q, --quiet +Suppresses display of the command lines being executed. +.TP +-s DIR, --subdir=DIR +Specifies the name of directory or subdirectory +from which the commands should be executed. +The default is XXX +.TP +--scons=SCONS +Specifies a path to the SCons script to be used +for the timing runs. +The default is XXX +.TP +--svn=URL, --subversion=URL +Specifies the +.I URL +of the Subversion repository from which the +version(s) of +.B scons +being timed will be extracted. +When +.B --svn +is specified, the +.BI --number= NUMBER +option specifies revision numbers +that will be tested. +Output from each invocation run will be placed in file +names that match the Subversion revision numbers. +If the +.B --number= +option is not specified, +then the default behavior is to time the +.B HEAD +of the specified +.IR URL . +.TP +-v, --verbose +Displays the output from individual commands to the screen +(in addition to capturing the output in log files). +'\"-------------------------------------------------------------------------- +.SS "The time Subcommand" +.ST +.P +The +.B scons-time time +subcommand displays SCons execution times +as reported by the +.B scons --debug=time +option. +.P +The +.B scons-time time +subcommand extracts SCons timing +from all the specified file arguments, +which should be files containing output from +running SCons with the +.B --debug=time +option. +(Normally, these would be +.B *.log +files generated by the +.B scons-time run +subcommand.) +All file name arguments will be +globbed for on-disk files. +.P +If no arguments are specified, +then execution timings +will be extracted from all +.B *.log +files, +or the subset of them +with a prefix specified by the +.B -p +option. +.TP +-C DIR, --chdir=DIR +Changes to the specified +.I DIRECTORY +before looking for the specified files +(or files that match the specified patterns). +.TP +-f FILE, --file=FILE +Reads configuration information from the specified +.IR FILE . +.TP +-fmt=FORMAT, --format=FORMAT +Reports the output in the specified +.IR FORMAT . +The formats currently supported are +.B ascii +(the default) +and +.BR gnuplot . +.TP +-h, --help +Displays help text for the +.B scons-time time +subcommand. +.TP +-p STRING, --prefix=STRING +Specifies the prefix string for log files +from which to extract execution timings. +This will be used to search for log files +if no arguments are specified on the command line. +.TP +-t NUMBER, --tail=NUMBER +Only reports object counts from the last +.I NUMBER +files. +.TP +--which=WHICH +Prints the execution time for the specified +.IR WHICH +value: +.B total +(the total execution time), +.B SConscripts +(total execution time for the SConscript files themselves), +.B SCons +(exectuion time in SCons code itself) +or +.B commands +(execution time of the commands and other actions +used to build targets). +If no +.B --which +option is specified, +the default behavior is +.BR total , +which reports the total execution time for each run. +'\"========================================================================== +.SH CONFIGURATION FILE +Various +.B scons-time +subcommands can read information from a specified +configuration file when passed the +.B \-f +or +.B \--file +options. +The configuration file is actually executed as a Python script. +Setting Python variables in the configuration file +controls the behavior of the +.B scons-time +script more conveniently than having to specify +command-line options or arguments for every run, +and provides a handy way to "shrink-wrap" +the necessary information for producing (and reporting) +consistent timing runs for a given configuration. +.TP +.B aegis +The Aegis executable for extracting deltas. +The default is simply +.BR aegis . +.TP +.B aegis_project +The Aegis project from which deltas should be extracted. +The default is whatever is specified +with the +.B --aegis= +command-line option. +.TP +.B archive_list +A list of archives (files or directories) +that will be copied to the temporary directory +in which SCons will be invoked. +.BR .tar , +.BR .tar.gz , +.BR .tgz +and +.BR .zip +files will have their contents unpacked in +the temporary directory. +Directory trees and files will be copied as-is. +.TP +.B initial_commands +A list of commands that will be executed +before the actual timed +.B scons +runs. +This can be used for commands that are necessary +to prepare the source tree\-for example, +creating a configuration file +that should not be part of the timed run. +.TP +.B key_location +The location of the key on Gnuplot graphing information +generated with the +.BR --format=gnuplot +option. +The default is +.BR "bottom left" . +.TP +.B prefix +The file name prefix to be used when +running or extracting timing for this configuration. +.TP +.B python +The path name of the Python executable +to be used when running or extracting information +for this configuration. +The default is the same version of Python +used to run the SCons +.TP +.B scons +The path name of the SCons script to be used +when running or extracting information +for this configuration. +The default is simply +.BR scons . +.TP +.B scons_flags +The +.B scons +flags used when running SCons to collect timing information. +The default value is +.BR "--debug=count --debug=memory --debug=time --debug=memoizer" . +.TP +.B scons_lib_dir +.TP +.B scons_wrapper +.TP +.B startup_targets +.TP +.B subdir +The subdirectory of the project into which the +.B scons-time +script should change +before executing the SCons commands to time. +.TP +.B subversion_url +The Subversion URL from +.TP +.B svn +The subversion executable used to +check out revisions of SCons to be timed. +The default is simple +.BR svn . +.TP +.B svn_co_flag +.TP +.B tar +.TP +.B targets +A string containing the targets that should be added to +the command line of every timed +.B scons +run. +This can be used to restrict what's being timed to a +subset of the full build for the configuration. +.TP +.B targets0 +.TP +.B targets1 +.TP +.B targets2 +.TP +.B title +.TP +.B unzip +.TP +.B verbose +.TP +.B vertical_bars +'\"-------------------------------------------------------------------------- +.SS Example +Here is an example +.B scons-time +configuration file +for a hypothetical sample project: +.P +.ES +# The project doesn't use SCons natively (yet), so we're +# timing a separate set of SConscript files that we lay +# on top of the vanilla unpacked project tarball. +arguments = ['project-1.2.tgz', 'project-SConscripts.tar'] + +# The subdirectory name contains the project version number, +# so tell scons-time to chdir there before building. +subdir = 'project-1.2' + +# Set the prefix so output log files and profiles are named: +# project-000-[012].{log,prof} +# project-001-[012].{log,prof} +# etc. +prefix = 'project' + +# The SConscript files being tested don't do any SConf +# configuration, so run their normal ./configure script +# before we invoke SCons. +initial_commands = [ + './configure', +] + +# Only time building the bin/project executable. +targets = 'bin/project' + +# Time against SCons revisions of the branches/core branch +subversion_url = 'http://scons.tigris.org/svn/scons/branches/core' +.EE +'\"========================================================================== +.SH ENVIRONMENT +The +.B scons-time +script uses the following environment variables: +.TP +.B PRESERVE +If this value is set, +the +.B scons-time +script will +.I not +remove the temporary directory or directories +in which it builds the specified configuration +or downloads a specific version of SCons. +'\"========================================================================== +.SH "SEE ALSO" +.BR gnuplot (1), +.BR scons (1) + +.SH AUTHORS +Steven Knight diff --git a/doc/man/scons.1 b/doc/man/scons.1 new file mode 100644 index 0000000..f57169b --- /dev/null +++ b/doc/man/scons.1 @@ -0,0 +1,10149 @@ +.\" Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining +.\" a copy of this software and associated documentation files (the +.\" "Software"), to deal in the Software without restriction, including +.\" without limitation the rights to use, copy, modify, merge, publish, +.\" distribute, sublicense, and/or sell copies of the Software, and to +.\" permit persons to whom the Software is furnished to do so, subject to +.\" the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included +.\" in all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +.\" KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +.\" WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +.\" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +.\" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +.\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +.\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +.\" +.\" doc/man/scons.1 4577 2009/12/27 19:44:43 scons +.\" +.TH SCONS 1 "December 2009" +.\" ES - Example Start - indents and turns off line fill +.rm ES +.de ES +.RS +.nf +.. +.\" EE - Example End - ends indent and turns line fill back on +.rm EE +.de EE +.fi +.RE +.. +.SH NAME +scons \- a software construction tool +.SH SYNOPSIS +.B scons +[ +.IR options ... +] +[ +.IR name = val ... +] +[ +.IR targets ... +] +.SH DESCRIPTION + +The +.B scons +utility builds software (or other files) by determining which +component pieces must be rebuilt and executing the necessary commands to +rebuild them. + +By default, +.B scons +searches for a file named +.IR SConstruct , +.IR Sconstruct , +or +.I sconstruct +(in that order) in the current directory and reads its +configuration from the first file found. +An alternate file name may be +specified via the +.B -f +option. + +The +.I SConstruct +file can specify subsidiary +configuration files using the +.B SConscript() +function. +By convention, +these subsidiary files are named +.IR SConscript , +although any name may be used. +(Because of this naming convention, +the term "SConscript files" +is sometimes used to refer +generically to all +.B scons +configuration files, +regardless of actual file name.) + +The configuration files +specify the target files to be built, and +(optionally) the rules to build those targets. Reasonable default +rules exist for building common software components (executable +programs, object files, libraries), so that for most software +projects, only the target and input files need be specified. + +Before reading the +.I SConstruct +file, +.B scons +looks for a directory named +.I site_scons +in the directory containing the +.I SConstruct +file; if it exists, +.I site_scons +is added to sys.path, +the file +.IR site_scons/site_init.py , +is evaluated if it exists, +and the directory +.I site_scons/site_tools +is added to the default toolpath if it exist. +See the +.I --no-site-dir +and +.I --site-dir +options for more details. + +.B scons +reads and executes the SConscript files as Python scripts, +so you may use normal Python scripting capabilities +(such as flow control, data manipulation, and imported Python libraries) +to handle complicated build situations. +.BR scons , +however, reads and executes all of the SConscript files +.I before +it begins building any targets. +To make this obvious, +.B scons +prints the following messages about what it is doing: + +.ES +$ scons foo.out +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons: Building targets ... +cp foo.in foo.out +scons: done building targets. +$ +.EE + +The status messages +(everything except the line that reads "cp foo.in foo.out") +may be suppressed using the +.B -Q +option. + +.B scons +does not automatically propagate +the external environment used to execute +.B scons +to the commands used to build target files. +This is so that builds will be guaranteed +repeatable regardless of the environment +variables set at the time +.B scons +is invoked. +This also means that if the compiler or other commands +that you want to use to build your target files +are not in standard system locations, +.B scons +will not find them unless +you explicitly set the PATH +to include those locations. +Whenever you create an +.B scons +construction environment, +you can propagate the value of PATH +from your external environment as follows: + +.ES +import os +env = Environment(ENV = {'PATH' : os.environ['PATH']}) +.EE + +Similarly, if the commands use external environment variables +like $PATH, $HOME, $JAVA_HOME, $LANG, $SHELL, $TERM, etc., +these variables can also be explicitly propagated: + +.ES +import os +env = Environment(ENV = {'PATH' : os.environ['PATH'], + 'HOME' : os.environ['HOME']}) +.EE + +Or you may explicitly propagate the invoking user's +complete external environment: + +.ES +import os +env = Environment(ENV = os.environ) +.EE + +This comes at the expense of making your build +dependent on the user's environment being set correctly, +but it may be more convenient for many configurations. + +.B scons +can scan known input files automatically for dependency +information (for example, #include statements +in C or C++ files) and will rebuild dependent files appropriately +whenever any "included" input file changes. +.B scons +supports the +ability to define new scanners for unknown input file types. + +.B scons +knows how to fetch files automatically from +SCCS or RCS subdirectories +using SCCS, RCS or BitKeeper. + +.B scons +is normally executed in a top-level directory containing a +.I SConstruct +file, optionally specifying +as command-line arguments +the target file or files to be built. + +By default, the command + +.ES +scons +.EE + +will build all target files in or below the current directory. +Explicit default targets +(to be built when no targets are specified on the command line) +may be defined the SConscript file(s) +using the +.B Default() +function, described below. + +Even when +.B Default() +targets are specified in the SConscript file(s), +all target files in or below the current directory +may be built by explicitly specifying +the current directory (.) +as a command-line target: + +.ES +scons . +.EE + +Building all target files, +including any files outside of the current directory, +may be specified by supplying a command-line target +of the root directory (on POSIX systems): + +.ES +scons / +.EE + +or the path name(s) of the volume(s) in which all the targets +should be built (on Windows systems): + +.ES +scons C:\\ D:\\ +.EE + +To build only specific targets, +supply them as command-line arguments: + +.ES +scons foo bar +.EE + +in which case only the specified targets will be built +(along with any derived files on which they depend). + +Specifying "cleanup" targets in SConscript files is not usually necessary. +The +.B -c +flag removes all files +necessary to build the specified target: + +.ES +scons -c . +.EE + +to remove all target files, or: + +.ES +scons -c build export +.EE + +to remove target files under build and export. +Additional files or directories to remove can be specified using the +.BR Clean() +function. +Conversely, targets that would normally be removed by the +.B -c +invocation +can be prevented from being removed by using the +.BR NoClean () +function. + +A subset of a hierarchical tree may be built by +remaining at the top-level directory (where the +.I SConstruct +file lives) and specifying the subdirectory as the target to be +built: + +.ES +scons src/subdir +.EE + +or by changing directory and invoking scons with the +.B -u +option, which traverses up the directory +hierarchy until it finds the +.I SConstruct +file, and then builds +targets relatively to the current subdirectory: + +.ES +cd src/subdir +scons -u . +.EE + +.B scons +supports building multiple targets in parallel via a +.B -j +option that takes, as its argument, the number +of simultaneous tasks that may be spawned: + +.ES +scons -j 4 +.EE + +builds four targets in parallel, for example. + +.B scons +can maintain a cache of target (derived) files that can +be shared between multiple builds. When caching is enabled in a +SConscript file, any target files built by +.B scons +will be copied +to the cache. If an up-to-date target file is found in the cache, it +will be retrieved from the cache instead of being rebuilt locally. +Caching behavior may be disabled and controlled in other ways by the +.BR --cache-force , +.BR --cache-disable , +and +.B --cache-show +command-line options. The +.B --random +option is useful to prevent multiple builds +from trying to update the cache simultaneously. + +Values of variables to be passed to the SConscript file(s) +may be specified on the command line: + +.ES +scons debug=1 . +.EE + +These variables are available in SConscript files +through the ARGUMENTS dictionary, +and can be used in the SConscript file(s) to modify +the build in any way: + +.ES +if ARGUMENTS.get('debug', 0): + env = Environment(CCFLAGS = '-g') +else: + env = Environment() +.EE + +The command-line variable arguments are also available +in the ARGLIST list, +indexed by their order on the command line. +This allows you to process them in order rather than by name, +if necessary. +ARGLIST[0] returns a tuple +containing (argname, argvalue). +A Python exception is thrown if you +try to access a list member that +does not exist. + +.B scons +requires Python version 1.5.2 or later. +There should be no other dependencies or requirements to run +.B scons. + +.\" The following paragraph reflects the default tool search orders +.\" currently in SCons/Tool/__init__.py. If any of those search orders +.\" change, this documentation should change, too. +By default, +.B scons +knows how to search for available programming tools +on various systems. +On Windows systems, +.B scons +searches in order for the +Microsoft Visual C++ tools, +the MinGW tool chain, +the Intel compiler tools, +and the PharLap ETS compiler. +On OS/2 systems, +.B scons +searches in order for the +OS/2 compiler, +the GCC tool chain, +and the Microsoft Visual C++ tools, +On SGI IRIX, IBM AIX, Hewlett Packard HP-UX, and Sun Solaris systems, +.B scons +searches for the native compiler tools +(MIPSpro, Visual Age, aCC, and Forte tools respectively) +and the GCC tool chain. +On all other platforms, +including POSIX (Linux and UNIX) platforms, +.B scons +searches in order +for the GCC tool chain, +the Microsoft Visual C++ tools, +and the Intel compiler tools. +You may, of course, override these default values +by appropriate configuration of +Environment construction variables. + +.SH OPTIONS +In general, +.B scons +supports the same command-line options as GNU +.BR make , +and many of those supported by +.BR cons . + +.TP +-b +Ignored for compatibility with non-GNU versions of +.BR make. + +.TP +-c, --clean, --remove +Clean up by removing all target files for which a construction +command is specified. +Also remove any files or directories associated to the construction command +using the +.BR Clean () +function. +Will not remove any targets specified by the +.BR NoClean () +function. + +.TP +.RI --cache-debug= file +Print debug information about the +.BR CacheDir () +derived-file caching +to the specified +.IR file . +If +.I file +is +.B \- +(a hyphen), +the debug information are printed to the standard output. +The printed messages describe what signature file names are +being looked for in, retrieved from, or written to the +.BR CacheDir () +directory tree. + +.TP +--cache-disable, --no-cache +Disable the derived-file caching specified by +.BR CacheDir (). +.B scons +will neither retrieve files from the cache +nor copy files to the cache. + +.TP +--cache-force, --cache-populate +When using +.BR CacheDir (), +populate a cache by copying any already-existing, up-to-date +derived files to the cache, +in addition to files built by this invocation. +This is useful to populate a new cache with +all the current derived files, +or to add to the cache any derived files +recently built with caching disabled via the +.B --cache-disable +option. + +.TP +--cache-show +When using +.BR CacheDir () +and retrieving a derived file from the cache, +show the command +that would have been executed to build the file, +instead of the usual report, +"Retrieved `file' from cache." +This will produce consistent output for build logs, +regardless of whether a target +file was rebuilt or retrieved from the cache. + +.TP +.RI --config= mode +This specifies how the +.B Configure +call should use or generate the +results of configuration tests. +The option should be specified from +among the following choices: + +.TP +--config=auto +scons will use its normal dependency mechanisms +to decide if a test must be rebuilt or not. +This saves time by not running the same configuration tests +every time you invoke scons, +but will overlook changes in system header files +or external commands (such as compilers) +if you don't specify those dependecies explicitly. +This is the default behavior. + +.TP +--config=force +If this option is specified, +all configuration tests will be re-run +regardless of whether the +cached results are out of date. +This can be used to explicitly +force the configuration tests to be updated +in response to an otherwise unconfigured change +in a system header file or compiler. + +.TP +--config=cache +If this option is specified, +no configuration tests will be rerun +and all results will be taken from cache. +Note that scons will still consider it an error +if --config=cache is specified +and a necessary test does not +yet have any results in the cache. + +.TP +.RI "-C" " directory" ", --directory=" directory +Change to the specified +.I directory +before searching for the +.IR SConstruct , +.IR Sconstruct , +or +.I sconstruct +file, or doing anything +else. Multiple +.B -C +options are interpreted +relative to the previous one, and the right-most +.B -C +option wins. (This option is nearly +equivalent to +.BR "-f directory/SConstruct" , +except that it will search for +.IR SConstruct , +.IR Sconstruct , +or +.I sconstruct +in the specified directory.) + +.\" .TP +.\" -d +.\" Display dependencies while building target files. Useful for +.\" figuring out why a specific file is being rebuilt, as well as +.\" general debugging of the build process. + +.TP +-D +Works exactly the same way as the +.B -u +option except for the way default targets are handled. +When this option is used and no targets are specified on the command line, +all default targets are built, whether or not they are below the current +directory. + +.TP +.RI --debug= type +Debug the build process. +.I type +specifies what type of debugging: + +.TP +--debug=count +Print how many objects are created +of the various classes used internally by SCons +before and after reading the SConscript files +and before and after building targets. +This is not supported when run under Python versions earlier than 2.1, +when SCons is executed with the Python +.B -O +(optimized) option, +or when the SCons modules +have been compiled with optimization +(that is, when executing from +.B *.pyo +files). + +.TP +--debug=dtree +A synonym for the newer +.B --tree=derived +option. +This will be deprecated in some future release +and ultimately removed. + +.TP +--debug=explain +Print an explanation of precisely why +.B scons +is deciding to (re-)build any targets. +(Note: this does not print anything +for targets that are +.I not +rebuilt.) + +.TP +--debug=findlibs +Instruct the scanner that searches for libraries +to print a message about each potential library +name it is searching for, +and about the actual libraries it finds. + +.TP +--debug=includes +Print the include tree after each top-level target is built. +This is generally used to find out what files are included by the sources +of a given derived file: + +.ES +$ scons --debug=includes foo.o +.EE + +.TP +--debug=memoizer +Prints a summary of hits and misses using the Memoizer, +an internal subsystem that counts +how often SCons uses cached values in memory +instead of recomputing them each time they're needed. +Only available when using Python 2.2 or later. + +.TP +--debug=memory +Prints how much memory SCons uses +before and after reading the SConscript files +and before and after building targets. + +.TP +--debug=nomemoizer +A deprecated option preserved for backwards compatibility. + +.TP +--debug=objects +Prints a list of the various objects +of the various classes used internally by SCons. +This only works when run under Python 2.1 or later. + +.TP +--debug=pdb +Re-run SCons under the control of the +.RI pdb +Python debugger. + +.TP +--debug=presub +Print the raw command line used to build each target +before the construction environment variables are substituted. +Also shows which targets are being built by this command. +Output looks something like this: +.ES +$ scons --debug=presub +Building myprog.o with action(s): + $SHCC $SHCFLAGS $SHCCFLAGS $CPPFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES +\&... +.EE + +.TP +--debug=stacktrace +Prints an internal Python stack trace +when encountering an otherwise unexplained error. + +.TP +--debug=stree +A synonym for the newer +.B --tree=all,status +option. +This will be deprecated in some future release +and ultimately removed. + +.TP +--debug=time +Prints various time profiling information: +the time spent executing each individual build command; +the total build time (time SCons ran from beginning to end); +the total time spent reading and executing SConscript files; +the total time spent SCons itself spend running +(that is, not counting reading and executing SConscript files); +and both the total time spent executing all build commands +and the elapsed wall-clock time spent executing those build commands. +(When +.B scons +is executed without the +.B -j +option, +the elapsed wall-clock time will typically +be slightly longer than the total time spent +executing all the build commands, +due to the SCons processing that takes place +in between executing each command. +When +.B scons +is executed +.I with +the +.B -j +option, +and your build configuration allows good parallelization, +the elapsed wall-clock time should +be significantly smaller than the +total time spent executing all the build commands, +since multiple build commands and +intervening SCons processing +should take place in parallel.) + +.TP +--debug=tree +A synonym for the newer +.B --tree=all +option. +This will be deprecated in some future release +and ultimately removed. + +.TP +.RI --diskcheck= types +Enable specific checks for +whether or not there is a file on disk +where the SCons configuration expects a directory +(or vice versa), +and whether or not RCS or SCCS sources exist +when searching for source and include files. +The +.I types +argument can be set to: +.BR all , +to enable all checks explicitly +(the default behavior); +.BR none , +to disable all such checks; +.BR match , +to check that files and directories on disk +match SCons' expected configuration; +.BR rcs , +to check for the existence of an RCS source +for any missing source or include files; +.BR sccs , +to check for the existence of an SCCS source +for any missing source or include files. +Multiple checks can be specified separated by commas; +for example, +.B --diskcheck=sccs,rcs +would still check for SCCS and RCS sources, +but disable the check for on-disk matches of files and directories. +Disabling some or all of these checks +can provide a performance boost for large configurations, +or when the configuration will check for files and/or directories +across networked or shared file systems, +at the slight increased risk of an incorrect build +or of not handling errors gracefully +(if include files really should be +found in SCCS or RCS, for example, +or if a file really does exist +where the SCons configuration expects a directory). + +.TP +.RI --duplicate= ORDER +There are three ways to duplicate files in a build tree: hard links, +soft (symbolic) links and copies. The default behaviour of SCons is to +prefer hard links to soft links to copies. You can specify different +behaviours with this option. +.IR ORDER +must be one of +.IR hard-soft-copy +(the default), +.IR soft-hard-copy , +.IR hard-copy , +.IR soft-copy +or +.IR copy . +SCons will attempt to duplicate files using +the mechanisms in the specified order. + +.\" .TP +.\" -e, --environment-overrides +.\" Variables from the execution environment override construction +.\" variables from the SConscript files. + +.TP +.RI -f " file" ", --file=" file ", --makefile=" file ", --sconstruct=" file +Use +.I file +as the initial SConscript file. +Multiple +.B -f +options may be specified, +in which case +.B scons +will read all of the specified files. + +.TP +-h, --help +Print a local help message for this build, if one is defined in +the SConscript file(s), plus a line that describes the +.B -H +option for command-line option help. If no local help message +is defined, prints the standard help message about command-line +options. Exits after displaying the appropriate message. + +.TP +-H, --help-options +Print the standard help message about command-line options and +exit. + +.TP +-i, --ignore-errors +Ignore all errors from commands executed to rebuild files. + +.TP +.RI -I " directory" ", --include-dir=" directory +Specifies a +.I directory +to search for +imported Python modules. If several +.B -I +options +are used, the directories are searched in the order specified. + +.TP +--implicit-cache +Cache implicit dependencies. +This causes +.B scons +to use the implicit (scanned) dependencies +from the last time it was run +instead of scanning the files for implicit dependencies. +This can significantly speed up SCons, +but with the following limitations: +.IP +.B scons +will not detect changes to implicit dependency search paths +(e.g. +.BR CPPPATH ", " LIBPATH ) +that would ordinarily +cause different versions of same-named files to be used. +.IP +.B scons +will miss changes in the implicit dependencies +in cases where a new implicit +dependency is added earlier in the implicit dependency search path +(e.g. +.BR CPPPATH ", " LIBPATH ) +than a current implicit dependency with the same name. + +.TP +--implicit-deps-changed +Forces SCons to ignore the cached implicit dependencies. This causes the +implicit dependencies to be rescanned and recached. This implies +.BR --implicit-cache . + +.TP +--implicit-deps-unchanged +Force SCons to ignore changes in the implicit dependencies. +This causes cached implicit dependencies to always be used. +This implies +.BR --implicit-cache . + +.TP +--interactive +Starts SCons in interactive mode. +The SConscript files are read once and a +.B "scons>>>" +prompt is printed. +Targets may now be rebuilt by typing commands at interactive prompt +without having to re-read the SConscript files +and re-initialize the dependency graph from scratch. + +SCons interactive mode supports the following commands: + +.RS 10 +.TP 6 +.BI build "[OPTIONS] [TARGETS] ..." +Builds the specified +.I TARGETS +(and their dependencies) +with the specified +SCons command-line +.IR OPTIONS . +.B b +and +.B scons +are synonyms. + +The following SCons command-line options affect the +.B build +command: + +.ES +--cache-debug=FILE +--cache-disable, --no-cache +--cache-force, --cache-populate +--cache-show +--debug=TYPE +-i, --ignore-errors +-j N, --jobs=N +-k, --keep-going +-n, --no-exec, --just-print, --dry-run, --recon +-Q +-s, --silent, --quiet +--taskmastertrace=FILE +--tree=OPTIONS +.EE + +.IP "" 6 +Any other SCons command-line options that are specified +do not cause errors +but have no effect on the +.B build +command +(mainly because they affect how the SConscript files are read, +which only happens once at the beginning of interactive mode). + +.TP 6 +.BI clean "[OPTIONS] [TARGETS] ..." +Cleans the specified +.I TARGETS +(and their dependencies) +with the specified options. +.B c +is a synonym. +This command is itself a synonym for +.B "build --clean" + +.TP 6 +.BI exit +Exits SCons interactive mode. +You can also exit by terminating input +(CTRL+D on UNIX or Linux systems, +CTRL+Z on Windows systems). + +.TP 6 +.BI help "[COMMAND]" +Provides a help message about +the commands available in SCons interactive mode. +If +.I COMMAND +is specified, +.B h +and +.B ? +are synonyms. + +.TP 6 +.BI shell "[COMMANDLINE]" +Executes the specified +.I COMMANDLINE +in a subshell. +If no +.I COMMANDLINE +is specified, +executes the interactive command interpreter +specified in the +.B SHELL +environment variable +(on UNIX and Linux systems) +or the +.B COMSPEC +environment variable +(on Windows systems). +.B sh +and +.B ! +are synonyms. + +.TP 6 +.B version +Prints SCons version information. +.RE + +.IP +An empty line repeats the last typed command. +Command-line editing can be used if the +.B readline +module is available. + +.ES +$ scons --interactive +scons: Reading SConscript files ... +scons: done reading SConscript files. +scons>>> build -n prog +scons>>> exit +.EE + +.TP +.RI -j " N" ", --jobs=" N +Specifies the number of jobs (commands) to run simultaneously. +If there is more than one +.B -j +option, the last one is effective. +.\" ??? If the +.\" .B -j +.\" option +.\" is specified without an argument, +.\" .B scons +.\" will not limit the number of +.\" simultaneous jobs. + +.TP +-k, --keep-going +Continue as much as possible after an error. The target that +failed and those that depend on it will not be remade, but other +targets specified on the command line will still be processed. + +.\" .TP +.\" .RI -l " N" ", --load-average=" N ", --max-load=" N +.\" No new jobs (commands) will be started if +.\" there are other jobs running and the system load +.\" average is at least +.\" .I N +.\" (a floating-point number). + +.\" +.\" .TP +.\" --list-derived +.\" List derived files (targets, dependencies) that would be built, +.\" but do not build them. +.\" [XXX This can probably go away with the right +.\" combination of other options. Revisit this issue.] +.\" +.\" .TP +.\" --list-actions +.\" List derived files that would be built, with the actions +.\" (commands) that build them. Does not build the files. +.\" [XXX This can probably go away with the right +.\" combination of other options. Revisit this issue.] +.\" +.\" .TP +.\" --list-where +.\" List derived files that would be built, plus where the file is +.\" defined (file name and line number). Does not build the files. +.\" [XXX This can probably go away with the right +.\" combination of other options. Revisit this issue.] + +.TP +-m +Ignored for compatibility with non-GNU versions of +.BR make . + +.TP +.RI --max-drift= SECONDS +Set the maximum expected drift in the modification time of files to +.IR SECONDS . +This value determines how long a file must be unmodified +before its cached content signature +will be used instead of +calculating a new content signature (MD5 checksum) +of the file's contents. +The default value is 2 days, which means a file must have a +modification time of at least two days ago in order to have its +cached content signature used. +A negative value means to never cache the content +signature and to ignore the cached value if there already is one. A value +of 0 means to always use the cached signature, +no matter how old the file is. + +.TP +.RI --md5-chunksize= KILOBYTES +Set the block size used to compute MD5 signatures to +.IR KILOBYTES . +This value determines the size of the chunks which are read in at once when +computing MD5 signatures. Files below that size are fully stored in memory +before performing the signature computation while bigger files are read in +block-by-block. A huge block-size leads to high memory consumption while a very +small block-size slows down the build considerably. + +The default value is to use a chunk size of 64 kilobytes, which should +be appropriate for most uses. + +.TP +-n, --just-print, --dry-run, --recon +No execute. Print the commands that would be executed to build +any out-of-date target files, but do not execute the commands. + +.TP +.RI --no-site-dir +Prevents the automatic addition of the standard +.I site_scons +dir to +.IR sys.path . +Also prevents loading the +.I site_scons/site_init.py +module if it exists, and prevents adding +.I site_scons/site_tools +to the toolpath. + +.\" .TP +.\" .RI -o " file" ", --old-file=" file ", --assume-old=" file +.\" Do not rebuild +.\" .IR file , +.\" and do +.\" not rebuild anything due to changes in the contents of +.\" .IR file . +.\" .TP +.\" .RI --override " file" +.\" Read values to override specific build environment variables +.\" from the specified +.\" .IR file . +.\" .TP +.\" -p +.\" Print the data base (construction environments, +.\" Builder and Scanner objects) that are defined +.\" after reading the SConscript files. +.\" After printing, a normal build is performed +.\" as usual, as specified by other command-line options. +.\" This also prints version information +.\" printed by the +.\" .B -v +.\" option. +.\" +.\" To print the database without performing a build do: +.\" +.\" .ES +.\" scons -p -q +.\" .EE + +.TP +.RI --profile= file +Run SCons under the Python profiler +and save the results in the specified +.IR file . +The results may be analyzed using the Python +pstats module. + +.TP +-q, --question +Do not run any commands, or print anything. Just return an exit +status that is zero if the specified targets are already up to +date, non-zero otherwise. +.TP +-Q +Quiets SCons status messages about +reading SConscript files, +building targets +and entering directories. +Commands that are executed +to rebuild target files are still printed. + +.\" .TP +.\" -r, -R, --no-builtin-rules, --no-builtin-variables +.\" Clear the default construction variables. Construction +.\" environments that are created will be completely empty. + +.TP +--random +Build dependencies in a random order. This is useful when +building multiple trees simultaneously with caching enabled, +to prevent multiple builds from simultaneously trying to build +or retrieve the same target files. + +.TP +-s, --silent, --quiet +Silent. Do not print commands that are executed to rebuild +target files. +Also suppresses SCons status messages. + +.TP +-S, --no-keep-going, --stop +Ignored for compatibility with GNU +.BR make . + +.TP +.RI --site-dir= dir +Uses the named dir as the site dir rather than the default +.I site_scons +dir. This dir will get prepended to +.IR sys.path , +the module +.IR dir /site_init.py +will get loaded if it exists, and +.IR dir /site_tools +will get added to the default toolpath. + +.TP +.RI --stack-size= KILOBYTES +Set the size stack used to run threads to +.IR KILOBYTES . +This value determines the stack size of the threads used to run jobs. +These are the threads that execute the actions of the builders for the +nodes that are out-of-date. +Note that this option has no effect unless the +.B num_jobs +option, which corresponds to -j and --jobs, is larger than one. Using +a stack size that is too small may cause stack overflow errors. This +usually shows up as segmentation faults that cause scons to abort +before building anything. Using a stack size that is too large will +cause scons to use more memory than required and may slow down the entire +build process. + +The default value is to use a stack size of 256 kilobytes, which should +be appropriate for most uses. You should not need to increase this value +unless you encounter stack overflow errors. + +.TP +-t, --touch +Ignored for compatibility with GNU +.BR make . +(Touching a file to make it +appear up-to-date is unnecessary when using +.BR scons .) + +.TP +.RI --taskmastertrace= file +Prints trace information to the specified +.I file +about how the internal Taskmaster object +evaluates and controls the order in which Nodes are built. +A file name of +.B - +may be used to specify the standard output. + +.TP +.RI -tree= options +Prints a tree of the dependencies +after each top-level target is built. +This prints out some or all of the tree, +in various formats, +depending on the +.I options +specified: + +.TP +--tree=all +Print the entire dependency tree +after each top-level target is built. +This prints out the complete dependency tree, +including implicit dependencies and ignored dependencies. + +.TP +--tree=derived +Restricts the tree output to only derived (target) files, +not source files. + +.TP +--tree=status +Prints status information for each displayed node. + +.TP +--tree=prune +Prunes the tree to avoid repeating dependency information +for nodes that have already been displayed. +Any node that has already been displayed +will have its name printed in +.BR "[square brackets]" , +as an indication that the dependencies +for that node can be found by searching +for the relevant output higher up in the tree. + +.IP +Multiple options may be specified, +separated by commas: + +.ES +# Prints only derived files, with status information: +scons --tree=derived,status + +# Prints all dependencies of target, with status information +# and pruning dependencies of already-visited Nodes: +scons --tree=all,prune,status target +.EE + +.TP +-u, --up, --search-up +Walks up the directory structure until an +.I SConstruct , +.I Sconstruct +or +.I sconstruct +file is found, and uses that +as the top of the directory tree. +If no targets are specified on the command line, +only targets at or below the +current directory will be built. + +.TP +-U +Works exactly the same way as the +.B -u +option except for the way default targets are handled. +When this option is used and no targets are specified on the command line, +all default targets that are defined in the SConscript(s) in the current +directory are built, regardless of what directory the resultant targets end +up in. + +.TP +-v, --version +Print the +.B scons +version, copyright information, +list of authors, and any other relevant information. +Then exit. + +.TP +-w, --print-directory +Print a message containing the working directory before and +after other processing. + +.TP +--no-print-directory +Turn off -w, even if it was turned on implicitly. + +.TP +.RI --warn= type ", --warn=no-" type +Enable or disable warnings. +.I type +specifies the type of warnings to be enabled or disabled: + +.TP +--warn=all, --warn=no-all +Enables or disables all warnings. + +.TP +--warn=cache-write-error, --warn=no-cache-write-error +Enables or disables warnings about errors trying to +write a copy of a built file to a specified +.BR CacheDir (). +These warnings are disabled by default. + +.TP +--warn=corrupt-sconsign, --warn=no-corrupt-sconsign +Enables or disables warnings about unfamiliar signature data in +.B .sconsign +files. +These warnings are enabled by default. + +.TP +--warn=dependency, --warn=no-dependency +Enables or disables warnings about dependencies. +These warnings are disabled by default. + +.TP +--warn=deprecated, --warn=no-deprecated +Enables or disables all warnings about use of +currently deprecated features. +These warnings are enabled by default. +Note that the +.B --warn=no-deprecated +option does not disable warnings about absolutely all deprecated features. +Warnings for some deprecated features that have already been through +several releases with deprecation warnings +may be mandatory for a release or two +before they are officially no longer supported by SCons. +Warnings for some specific deprecated features +may be enabled or disabled individually; +see below. + +.RS +.TP +--warn=deprecated-copy, --warn=no-deprecated-copy +Enables or disables warnings about use of the deprecated +.B env.Copy() +method. + +.TP +--warn=deprecated-source-signatures, --warn=no-deprecated-source-signatures +Enables or disables warnings about use of the deprecated +.B SourceSignatures() +function or +.B env.SourceSignatures() +method. + +.TP +--warn=deprecated-target-signatures, --warn=no-deprecated-target-signatures +Enables or disables warnings about use of the deprecated +.B TargetSignatures() +function or +.B env.TargetSignatures() +method. +.RE + +.TP +--warn=duplicate-environment, --warn=no-duplicate-environment +Enables or disables warnings about attempts to specify a build +of a target with two different construction environments +that use the same action. +These warnings are enabled by default. + +.TP +--warn=fortran-cxx-mix, --warn=no-fortran-cxx-mix +Enables or disables the specific warning about linking +Fortran and C++ object files in a single executable, +which can yield unpredictable behavior with some compilers. + +.TP +--warn=future-deprecated, --warn=no-future-deprecated +Enables or disables warnings about features +that will be deprecated in the future. +These warnings are disabled by default. +Enabling this warning is especially +recommended for projects that redistribute +SCons configurations for other users to build, +so that the project can be warned as soon as possible +about to-be-deprecated features +that may require changes to the configuration. + +.TP +--warn=link, --warn=no-link +Enables or disables warnings about link steps. + +.TP +--warn=misleading-keywords, --warn=no-misleading-keywords +Enables or disables warnings about use of the misspelled keywords +.B targets +and +.B sources +when calling Builders. +(Note the last +.B s +characters, the correct spellings are +.B target +and +.B source.) +These warnings are enabled by default. + +.TP +--warn=missing-sconscript, --warn=no-missing-sconscript +Enables or disables warnings about missing SConscript files. +These warnings are enabled by default. + +.TP +--warn=no-md5-module, --warn=no-no-md5-module +Enables or disables warnings about the version of Python +not having an MD5 checksum module available. +These warnings are enabled by default. + +.TP +--warn=no-metaclass-support, --warn=no-no-metaclass-support +Enables or disables warnings about the version of Python +not supporting metaclasses when the +.B --debug=memoizer +option is used. +These warnings are enabled by default. + +.TP +--warn=no-object-count, --warn=no-no-object-count +Enables or disables warnings about the +.B --debug=object +feature not working when +.B scons +is run with the python +.B \-O +option or from optimized Python (.pyo) modules. + +.TP +--warn=no-parallel-support, --warn=no-no-parallel-support +Enables or disables warnings about the version of Python +not being able to support parallel builds when the +.B -j +option is used. +These warnings are enabled by default. + +.TP +--warn=python-version, --warn=no-python-version +Enables or disables the warning about running +SCons with a deprecated version of Python. +These warnings are enabled by default. + +.TP +--warn=reserved-variable, --warn=no-reserved-variable +Enables or disables warnings about attempts to set the +reserved construction variable names +.BR CHANGED_SOURCES , +.BR CHANGED_TARGETS , +.BR TARGET , +.BR TARGETS , +.BR SOURCE , +.BR SOURCES , +.BR UNCHANGED_SOURCES +or +.BR UNCHANGED_TARGETS . +These warnings are disabled by default. + +.TP +--warn=stack-size, --warn=no-stack-size +Enables or disables warnings about requests to set the stack size +that could not be honored. +These warnings are enabled by default. + +.\" .TP +.\" .RI --write-filenames= file +.\" Write all filenames considered into +.\" .IR file . +.\" +.\" .TP +.\" .RI -W " file" ", --what-if=" file ", --new-file=" file ", --assume-new=" file +.\" Pretend that the target +.\" .I file +.\" has been +.\" modified. When used with the +.\" .B -n +.\" option, this +.\" show you what would be rebuilt if you were to modify that file. +.\" Without +.\" .B -n +.\" ... what? XXX +.\" +.\" .TP +.\" --warn-undefined-variables +.\" Warn when an undefined variable is referenced. + +.TP +.RI -Y " repository" ", --repository=" repository ", --srcdir=" repository +Search the specified repository for any input and target +files not found in the local directory hierarchy. Multiple +.B -Y +options may be specified, in which case the +repositories are searched in the order specified. + +.SH CONFIGURATION FILE REFERENCE +.\" .SS Python Basics +.\" XXX Adding this in the future would be a help. +.SS Construction Environments +A construction environment is the basic means by which the SConscript +files communicate build information to +.BR scons . +A new construction environment is created using the +.B Environment +function: + +.ES +env = Environment() +.EE + +Variables, called +.I construction +.IR variables , +may be set in a construction environment +either by specifying them as keywords when the object is created +or by assigning them a value after the object is created: + +.ES +env = Environment(FOO = 'foo') +env['BAR'] = 'bar' +.EE + +As a convenience, +construction variables may also be set or modified by the +.I parse_flags +keyword argument, which applies the +.B ParseFlags +method (described below) to the argument value +after all other processing is completed. +This is useful either if the exact content of the flags is unknown +(for example, read from a control file) +or if the flags are distributed to a number of construction variables. + +.ES +env = Environment(parse_flags = '-Iinclude -DEBUG -lm') +.EE + +This example adds 'include' to +.BR CPPPATH , +\&'EBUG' to +.BR CPPDEFINES , +and 'm' to +.BR LIBS . + +By default, a new construction environment is +initialized with a set of builder methods +and construction variables that are appropriate +for the current platform. +An optional platform keyword argument may be +used to specify that an environment should +be initialized for a different platform: + +.ES +env = Environment(platform = 'cygwin') +env = Environment(platform = 'os2') +env = Environment(platform = 'posix') +env = Environment(platform = 'win32') +.EE + +Specifying a platform initializes the appropriate +construction variables in the environment +to use and generate file names with prefixes +and suffixes appropriate for the platform. + +Note that the +.B win32 +platform adds the +.B SystemDrive +and +.B SystemRoot +variables from the user's external environment +to the construction environment's +.B ENV +dictionary. +This is so that any executed commands +that use sockets to connect with other systems +(such as fetching source files from +external CVS repository specifications like +.BR :pserver:anonymous@cvs.sourceforge.net:/cvsroot/scons ) +will work on Windows systems. + +The platform argument may be function or callable object, +in which case the Environment() method +will call the specified argument to update +the new construction environment: + +.ES +def my_platform(env): + env['VAR'] = 'xyzzy' + +env = Environment(platform = my_platform) +.EE + +Additionally, a specific set of tools +with which to initialize the environment +may be specified as an optional keyword argument: + +.ES +env = Environment(tools = ['msvc', 'lex']) +.EE + +Non-built-in tools may be specified using the toolpath argument: + +.ES +env = Environment(tools = ['default', 'foo'], toolpath = ['tools']) +.EE + +This looks for a tool specification in tools/foo.py (as well as +using the ordinary default tools for the platform). foo.py should +have two functions: generate(env, **kw) and exists(env). +The +.B generate() +function +modifies the passed-in environment +to set up variables so that the tool +can be executed; +it may use any keyword arguments +that the user supplies (see below) +to vary its initialization. +The +.B exists() +function should return a true +value if the tool is available. +Tools in the toolpath are used before +any of the built-in ones. For example, adding gcc.py to the toolpath +would override the built-in gcc tool. +Also note that the toolpath is +stored in the environment for use +by later calls to +.BR Clone () +and +.BR Tool () +methods: + +.ES +base = Environment(toolpath=['custom_path']) +derived = base.Clone(tools=['custom_tool']) +derived.CustomBuilder() +.EE + +The elements of the tools list may also +be functions or callable objects, +in which case the Environment() method +will call the specified elements +to update the new construction environment: + +.ES +def my_tool(env): + env['XYZZY'] = 'xyzzy' + +env = Environment(tools = [my_tool]) +.EE + +The individual elements of the tools list +may also themselves be two-element lists of the form +.RI ( toolname ", " kw_dict ). +SCons searches for the +.I toolname +specification file as described above, and +passes +.IR kw_dict , +which must be a dictionary, as keyword arguments to the tool's +.B generate +function. +The +.B generate +function can use the arguments to modify the tool's behavior +by setting up the environment in different ways +or otherwise changing its initialization. + +.ES +# in tools/my_tool.py: +def generate(env, **kw): + # Sets MY_TOOL to the value of keyword argument 'arg1' or 1. + env['MY_TOOL'] = kw.get('arg1', '1') +def exists(env): + return 1 + +# in SConstruct: +env = Environment(tools = ['default', ('my_tool', {'arg1': 'abc'})], + toolpath=['tools']) +.EE + +The tool definition (i.e. my_tool()) can use the PLATFORM variable from +the environment it receives to customize the tool for different platforms. + +If no tool list is specified, then SCons will auto-detect the installed +tools using the PATH variable in the ENV construction variable and the +platform name when the Environment is constructed. Changing the PATH +variable after the Environment is constructed will not cause the tools to +be redetected. + +SCons supports the following tool specifications out of the box: + +.ES +386asm +aixc++ +aixcc +aixf77 +aixlink +ar +as +bcc32 +c++ +cc +cvf +dmd +dvipdf +dvips +f77 +f90 +f95 +fortran +g++ +g77 +gas +gcc +gfortran +gnulink +gs +hpc++ +hpcc +hplink +icc +icl +ifl +ifort +ilink +ilink32 +intelc +jar +javac +javah +latex +lex +link +linkloc +m4 +masm +midl +mingw +mslib +mslink +mssdk +msvc +msvs +mwcc +mwld +nasm +pdflatex +pdftex +qt +rmic +rpcgen +sgiar +sgic++ +sgicc +sgilink +sunar +sunc++ +suncc +sunf77 +sunf90 +sunf95 +sunlink +swig +tar +tex +textfile +tlib +yacc +zip +.EE + +Additionally, there is a "tool" named +.B default +which configures the +environment with a default set of tools for the current platform. + +On posix and cygwin platforms +the GNU tools (e.g. gcc) are preferred by SCons, +on Windows the Microsoft tools (e.g. msvc) +followed by MinGW are preferred by SCons, +and in OS/2 the IBM tools (e.g. icc) are preferred by SCons. + +.SS Builder Methods + +Build rules are specified by calling a construction +environment's builder methods. +The arguments to the builder methods are +.B target +(a list of targets to be built, +usually file names) +and +.B source +(a list of sources to be built, +usually file names). + +Because long lists of file names +can lead to a lot of quoting, +.B scons +supplies a +.B Split() +global function +and a same-named environment method +that split a single string +into a list, separated on +strings of white-space characters. +(These are similar to the +string.split() method +from the standard Python library, +but work even if the input isn't a string.) + +Like all Python arguments, +the target and source arguments to a builder method +can be specified either with or without +the "target" and "source" keywords. +When the keywords are omitted, +the target is first, +followed by the source. +The following are equivalent examples of calling the Program builder method: + +.ES +env.Program('bar', ['bar.c', 'foo.c']) +env.Program('bar', Split('bar.c foo.c')) +env.Program('bar', env.Split('bar.c foo.c')) +env.Program(source = ['bar.c', 'foo.c'], target = 'bar') +env.Program(target = 'bar', Split('bar.c foo.c')) +env.Program(target = 'bar', env.Split('bar.c foo.c')) +env.Program('bar', source = string.split('bar.c foo.c')) +.EE + +Target and source file names +that are not absolute path names +(that is, do not begin with +.B / +on POSIX systems +or +.B \\ +on Windows systems, +with or without +an optional drive letter) +are interpreted relative to the directory containing the +.B SConscript +file being read. +An initial +.B # +(hash mark) +on a path name means that the rest of the file name +is interpreted relative to +the directory containing +the top-level +.B SConstruct +file, +even if the +.B # +is followed by a directory separator character +(slash or backslash). + +Examples: + +.ES +# The comments describing the targets that will be built +# assume these calls are in a SConscript file in the +# a subdirectory named "subdir". + +# Builds the program "subdir/foo" from "subdir/foo.c": +env.Program('foo', 'foo.c') + +# Builds the program "/tmp/bar" from "subdir/bar.c": +env.Program('/tmp/bar', 'bar.c') + +# An initial '#' or '#/' are equivalent; the following +# calls build the programs "foo" and "bar" (in the +# top-level SConstruct directory) from "subdir/foo.c" and +# "subdir/bar.c", respectively: +env.Program('#foo', 'foo.c') +env.Program('#/bar', 'bar.c') + +# Builds the program "other/foo" (relative to the top-level +# SConstruct directory) from "subdir/foo.c": +env.Program('#other/foo', 'foo.c') +.EE + +When the target shares the same base name +as the source and only the suffix varies, +and if the builder method has a suffix defined for the target file type, +then the target argument may be omitted completely, +and +.B scons +will deduce the target file name from +the source file name. +The following examples all build the +executable program +.B bar +(on POSIX systems) +or +.B bar.exe +(on Windows systems) +from the bar.c source file: + +.ES +env.Program(target = 'bar', source = 'bar.c') +env.Program('bar', source = 'bar.c') +env.Program(source = 'bar.c') +env.Program('bar.c') +.EE + +As a convenience, a +.B srcdir +keyword argument may be specified +when calling a Builder. +When specified, +all source file strings that are not absolute paths +will be interpreted relative to the specified +.BR srcdir . +The following example will build the +.B build/prog +(or +.B build/prog.exe +on Windows) +program from the files +.B src/f1.c +and +.BR src/f2.c : + +.ES +env.Program('build/prog', ['f1.c', 'f2.c'], srcdir='src') +.EE + +It is possible to override or add construction variables when calling a +builder method by passing additional keyword arguments. +These overridden or added +variables will only be in effect when building the target, so they will not +affect other parts of the build. For example, if you want to add additional +libraries for just one program: + +.ES +env.Program('hello', 'hello.c', LIBS=['gl', 'glut']) +.EE + +or generate a shared library with a non-standard suffix: + +.ES +env.SharedLibrary('word', 'word.cpp', + SHLIBSUFFIX='.ocx', + LIBSUFFIXES=['.ocx']) +.EE + +(Note that both the $SHLIBSUFFIX and $LIBSUFFIXES variables must be set +if you want SCons to search automatically +for dependencies on the non-standard library names; +see the descriptions of these variables, below, for more information.) + +It is also possible to use the +.I parse_flags +keyword argument in an override: + +.ES +env = Program('hello', 'hello.c', parse_flags = '-Iinclude -DEBUG -lm') +.EE + +This example adds 'include' to +.BR CPPPATH , +\&'EBUG' to +.BR CPPDEFINES , +and 'm' to +.BR LIBS . + +Although the builder methods defined by +.B scons +are, in fact, +methods of a construction environment object, +they may also be called without an explicit environment: + +.ES +Program('hello', 'hello.c') +SharedLibrary('word', 'word.cpp') +.EE + +In this case, +the methods are called internally using a default construction +environment that consists of the tools and values that +.B scons +has determined are appropriate for the local system. + +Builder methods that can be called without an explicit +environment may be called from custom Python modules that you +import into an SConscript file by adding the following +to the Python module: + +.ES +from SCons.Script import * +.EE + +All builder methods return a list-like object +containing Nodes that +represent the target or targets that will be built. +A +.I Node +is an internal SCons object +which represents +build targets or sources. + +The returned Node-list object +can be passed to other builder methods as source(s) +or passed to any SCons function or method +where a filename would normally be accepted. +For example, if it were necessary +to add a specific +.B -D +flag when compiling one specific object file: + +.ES +bar_obj_list = env.StaticObject('bar.c', CPPDEFINES='-DBAR') +env.Program(source = ['foo.c', bar_obj_list, 'main.c']) +.EE + +Using a Node in this way +makes for a more portable build +by avoiding having to specify +a platform-specific object suffix +when calling the Program() builder method. + +Note that Builder calls will automatically "flatten" +the source and target file lists, +so it's all right to have the bar_obj list +return by the StaticObject() call +in the middle of the source file list. +If you need to manipulate a list of lists returned by Builders +directly using Python, +you can either build the list by hand: + +.ES +foo = Object('foo.c') +bar = Object('bar.c') +objects = ['begin.o'] + foo + ['middle.o'] + bar + ['end.o'] +for object in objects: + print str(object) +.EE + +Or you can use the +.BR Flatten () +function supplied by scons +to create a list containing just the Nodes, +which may be more convenient: + +.ES +foo = Object('foo.c') +bar = Object('bar.c') +objects = Flatten(['begin.o', foo, 'middle.o', bar, 'end.o']) +for object in objects: + print str(object) +.EE + +Note also that because Builder calls return +a list-like object, not an actual Python list, +you should +.I not +use the Python +.B += +operator to append Builder results to a Python list. +Because the list and the object are different types, +Python will not update the original list in place, +but will instead create a new Node-list object +containing the concatenation of the list +elements and the Builder results. +This will cause problems for any other Python variables +in your SCons configuration +that still hold on to a reference to the original list. +Instead, use the Python +.B .extend() +method to make sure the list is updated in-place. +Example: + +.ES +object_files = [] + +# Do NOT use += as follows: +# +# object_files += Object('bar.c') +# +# It will not update the object_files list in place. +# +# Instead, use the .extend() method: +object_files.extend(Object('bar.c')) + +.EE + +The path name for a Node's file may be used +by passing the Node to the Python-builtin +.B str() +function: + +.ES +bar_obj_list = env.StaticObject('bar.c', CPPDEFINES='-DBAR') +print "The path to bar_obj is:", str(bar_obj_list[0]) +.EE + +Note again that because the Builder call returns a list, +we have to access the first element in the list +.B (bar_obj_list[0]) +to get at the Node that actually represents +the object file. + +Builder calls support a +.B chdir +keyword argument that +specifies that the Builder's action(s) +should be executed +after changing directory. +If the +.B chdir +argument is +a string or a directory Node, +scons will change to the specified directory. +If the +.B chdir +is not a string or Node +and is non-zero, +then scons will change to the +target file's directory. + +.ES +# scons will change to the "sub" subdirectory +# before executing the "cp" command. +env.Command('sub/dir/foo.out', 'sub/dir/foo.in', + "cp dir/foo.in dir/foo.out", + chdir='sub') + +# Because chdir is not a string, scons will change to the +# target's directory ("sub/dir") before executing the +# "cp" command. +env.Command('sub/dir/foo.out', 'sub/dir/foo.in', + "cp foo.in foo.out", + chdir=1) +.EE + +Note that scons will +.I not +automatically modify +its expansion of +construction variables like +.B $TARGET +and +.B $SOURCE +when using the chdir +keyword argument--that is, +the expanded file names +will still be relative to +the top-level SConstruct directory, +and consequently incorrect +relative to the chdir directory. +If you use the chdir keyword argument, +you will typically need to supply a different +command line using +expansions like +.B ${TARGET.file} +and +.B ${SOURCE.file} +to use just the filename portion of the +targets and source. + +.B scons +provides the following builder methods: + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +'\" BEGIN GENERATED BUILDER DESCRIPTIONS +'\" +'\" The descriptions below of the various SCons Builders are generated +'\" from the .xml files that live next to the various Python modules in +'\" the build enginer library. If you're reading this [gnt]roff file +'\" with an eye towards patching this man page, you can still submit +'\" a diff against this text, but it will have to be translated to a +'\" diff against the underlying .xml file before the patch is actually +'\" accepted. If you do that yourself, it will make it easier to +'\" integrate the patch. +'\" +'\" BEGIN GENERATED BUILDER DESCRIPTIONS +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.so builders.man +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +'\" END GENERATED BUILDER DESCRIPTIONS +'\" +'\" The descriptions above of the various SCons Builders are generated +'\" from the .xml files that live next to the various Python modules in +'\" the build enginer library. If you're reading this [gnt]roff file +'\" with an eye towards patching this man page, you can still submit +'\" a diff against this text, but it will have to be translated to a +'\" diff against the underlying .xml file before the patch is actually +'\" accepted. If you do that yourself, it will make it easier to +'\" integrate the patch. +'\" +'\" END GENERATED BUILDER DESCRIPTIONS +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.P +All +targets of builder methods automatically depend on their sources. +An explicit dependency can +be specified using the +.B Depends +method of a construction environment (see below). + +In addition, +.B scons +automatically scans +source files for various programming languages, +so the dependencies do not need to be specified explicitly. +By default, SCons can +C source files, +C++ source files, +Fortran source files with +.B .F +(POSIX systems only), +.B .fpp, +or +.B .FPP +file extensions, +and assembly language files with +.B .S +(POSIX systems only), +.B .spp, +or +.B .SPP +files extensions +for C preprocessor dependencies. +SCons also has default support +for scanning D source files, +You can also write your own Scanners +to add support for additional source file types. +These can be added to the default +Scanner object used by the +.BR Object (), +.BR StaticObject (), +and +.BR SharedObject () +Builders by adding them +to the +.B SourceFileScanner +object as follows: + +See the section "Scanner Objects," +below, for a more information about +defining your own Scanner objects. + +.SS Methods and Functions to Do Things +In addition to Builder methods, +.B scons +provides a number of other construction environment methods +and global functions to +manipulate the build configuration. + +Usually, a construction environment method +and global function with the same name both exist +so that you don't have to remember whether +to a specific bit of functionality +must be called with or without a construction environment. +In the following list, +if you call something as a global function +it looks like: +.ES +.RI Function( arguments ) +.EE +and if you call something through a construction +environment it looks like: +.ES +.RI env.Function( arguments ) +.EE +If you can call the functionality in both ways, +then both forms are listed. + +Global functions may be called from custom Python modules that you +import into an SConscript file by adding the following +to the Python module: + +.ES +from SCons.Script import * +.EE + +Except where otherwise noted, +the same-named +construction environment method +and global function +provide the exact same functionality. +The only difference is that, +where appropriate, +calling the functionality through a construction environment will +substitute construction variables into +any supplied strings. +For example: + +.ES +env = Environment(FOO = 'foo') +Default('$FOO') +env.Default('$FOO') +.EE + +In the above example, +the first call to the global +.B Default() +function will actually add a target named +.B $FOO +to the list of default targets, +while the second call to the +.B env.Default() +construction environment method +will expand the value +and add a target named +.B foo +to the list of default targets. +For more on construction variable expansion, +see the next section on +construction variables. + +Construction environment methods +and global functions supported by +.B scons +include: + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Action( action ", [" cmd/str/fun ", [" var ", ...]] [" option = value ", ...])" +.TP +.IR env .Action( action ", [" cmd/str/fun ", [" var ", ...]] [" option = value ", ...])" +Creates an Action object for +the specified +.IR action . +See the section "Action Objects," +below, for a complete explanation of the arguments and behavior. + +Note that the +.BR env.Action () +form of the invocation will expand +construction variables in any argument strings, +including the +.I action +argument, at the time it is called +using the construction variables in the +.I env +construction environment through which +.BR env.Action () +was called. +The +.BR Action () +form delays all variable expansion +until the Action object is actually used. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI AddMethod( object, function ", [" name ]) +.TP +.RI env.AddMethod( function ", [" name ]) +When called with the +.BR AddMethod () +form, +adds the specified +.I function +to the specified +.I object +as the specified method +.IR name . +When called with the +.BR env.AddMethod () +form, +adds the specified +.I function +to the construction environment +.I env +as the specified method +.IR name . +In both cases, if +.I name +is omitted or +.BR None , +the name of the +specified +.I function +itself is used for the method name. + +Examples: + +.ES +# Note that the first argument to the function to +# be attached as a method must be the object through +# which the method will be called; the Python +# convention is to call it 'self'. +def my_method(self, arg): + print "my_method() got", arg + +# Use the global AddMethod() function to add a method +# to the Environment class. This +AddMethod(Environment, my_method) +env = Environment() +env.my_method('arg') + +# Add the function as a method, using the function +# name for the method call. +env = Environment() +env.AddMethod(my_method, 'other_method_name') +env.other_method_name('another arg') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI AddOption( arguments ) +This function adds a new command-line option to be recognized. +The specified +.I arguments +are the same as supported by the standard Python +.BR optparse.add_option () +method (with a few additional capabilities noted below); +see the documentation for +.B optparse +for a thorough discussion of its option-processing capabities. +(Note that although the +.B optparse +module was not a standard module until Python 2.3, +.B scons +contains a compatible version of the module +that is used to provide identical functionality +when run by earlier Python versions.) + +In addition to the arguments and values supported by the +.B optparse.add_option () +method, +the SCons +.BR AddOption () +function allows you to set the +.B nargs +keyword value to +.B '?' +(a string with just the question mark) +to indicate that the specified long option(s) take(s) an +.I optional +argument. +When +.B "nargs = '?'" +is passed to the +.BR AddOption () +function, the +.B const +keyword argument +may be used to supply the "default" +value that should be used when the +option is specified on the command line +without an explicit argument. + +If no +.B default= +keyword argument is supplied when calling +.BR AddOption (), +the option will have a default value of +.BR None . + +Once a new command-line option has been added with +.BR AddOption (), +the option value may be accessed using +.BR GetOption () +or +.BR env.GetOption (). +\" NOTE: in SCons 1.x or 2.0, user options will be settable, but not yet. +\" Uncomment this when that works. See tigris issue 2105. +\" The value may also be set, using +\" .BR SetOption () +\" or +\" .BR env.SetOption (), +\" if conditions in a +\" .B SConscript +\" require overriding any default value. +\" Note, however, that a +\" value specified on the command line will +\" .I always +\" override a value set by any SConscript file. + +Any specified +.B help= +strings for the new option(s) +will be displayed by the +.B -H +or +.B -h +options +(the latter only if no other help text is +specified in the SConscript files). +The help text for the local options specified by +.BR AddOption () +will appear below the SCons options themselves, +under a separate +.B "Local Options" +heading. +The options will appear in the help text +in the order in which the +.BR AddOption () +calls occur. + +Example: + +.ES +AddOption('--prefix', + dest='prefix', + nargs=1, type='string', + action='store', + metavar='DIR', + help='installation prefix') +env = Environment(PREFIX = GetOption('prefix')) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI AddPostAction( target ", " action ) +.TP +.RI env.AddPostAction( target ", " action ) +Arranges for the specified +.I action +to be performed +after the specified +.I target +has been built. +The specified action(s) may be +an Action object, or anything that +can be converted into an Action object +(see below). + +When multiple targets are supplied, +the action may be called multiple times, +once after each action that generates +one or more targets in the list. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI AddPreAction( target ", " action ) +.TP +.RI env.AddPreAction( target ", " action ) +Arranges for the specified +.I action +to be performed +before the specified +.I target +is built. +The specified action(s) may be +an Action object, or anything that +can be converted into an Action object +(see below). + +When multiple targets are specified, +the action(s) may be called multiple times, +once before each action that generates +one or more targets in the list. + +Note that if any of the targets are built in multiple steps, +the action will be invoked just +before the "final" action that specifically +generates the specified target(s). +For example, when building an executable program +from a specified source +.B .c +file via an intermediate object file: + +.ES +foo = Program('foo.c') +AddPreAction(foo, 'pre_action') +.EE + +The specified +.B pre_action +would be executed before +.B scons +calls the link command that actually +generates the executable program binary +.BR foo , +not before compiling the +.B foo.c +file into an object file. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Alias( alias ", [" targets ", [" action ]]) +.TP +.RI env.Alias( alias ", [" targets ", [" action ]]) +Creates one or more phony targets that +expand to one or more other targets. +An optional +.I action +(command) +or list of actions +can be specified that will be executed +whenever the any of the alias targets are out-of-date. +Returns the Node object representing the alias, +which exists outside of any file system. +This Node object, or the alias name, +may be used as a dependency of any other target, +including another alias. +.B Alias +can be called multiple times for the same +alias to add additional targets to the alias, +or additional actions to the list for this alias. + +Examples: + +.ES +Alias('install') +Alias('install', '/usr/bin') +Alias(['install', 'install-lib'], '/usr/local/lib') + +env.Alias('install', ['/usr/local/bin', '/usr/local/lib']) +env.Alias('install', ['/usr/local/man']) + +env.Alias('update', ['file1', 'file2'], "update_database $SOURCES") +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI AllowSubstExceptions([ exception ", ...])" +Specifies the exceptions that will be allowed +when expanding construction variables. +By default, +any construction variable expansions that generate a +.B NameError +or +.BR IndexError +exception will expand to a +.B '' +(a null string) and not cause scons to fail. +All exceptions not in the specified list +will generate an error message +and terminate processing. + +If +.B AllowSubstExceptions +is called multiple times, +each call completely overwrites the previous list +of allowed exceptions. + +Example: + +.ES +# Requires that all construction variable names exist. +# (You may wish to do this if you want to enforce strictly +# that all construction variables must be defined before use.) +AllowSubstExceptions() + +# Also allow a string containing a zero-division expansion +# like '${1 / 0}' to evalute to ''. +AllowSubstExceptions(IndexError, NameError, ZeroDivisionError) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI AlwaysBuild( target ", ...)" +.TP +.RI env.AlwaysBuild( target ", ...)" +Marks each given +.I target +so that it is always assumed to be out of date, +and will always be rebuilt if needed. +Note, however, that +.BR AlwaysBuild () +does not add its target(s) to the default target list, +so the targets will only be built +if they are specified on the command line, +or are a dependent of a target specified on the command line--but +they will +.I always +be built if so specified. +Multiple targets can be passed in to a single call to +.BR AlwaysBuild (). + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Append( key = val ", [...])" +Appends the specified keyword arguments +to the end of construction variables in the environment. +If the Environment does not have +the specified construction variable, +it is simply added to the environment. +If the values of the construction variable +and the keyword argument are the same type, +then the two values will be simply added together. +Otherwise, the construction variable +and the value of the keyword argument +are both coerced to lists, +and the lists are added together. +(See also the Prepend method, below.) + +Example: + +.ES +env.Append(CCFLAGS = ' -g', FOO = ['foo.yyy']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.AppendENVPath( name ", " newpath ", [" envname ", " sep ", " delete_existing ]) +This appends new path elements to the given path in the +specified external environment +.RB ( ENV +by default). +This will only add +any particular path once (leaving the last one it encounters and +ignoring the rest, to preserve path order), +and to help assure this, +will normalize all paths (using +.B os.path.normpath +and +.BR os.path.normcase ). +This can also handle the +case where the given old path variable is a list instead of a +string, in which case a list will be returned instead of a string. + +If +.I delete_existing +is 0, then adding a path that already exists +will not move it to the end; it will stay where it is in the list. + +Example: + +.ES +print 'before:',env['ENV']['INCLUDE'] +include_path = '/foo/bar:/foo' +env.AppendENVPath('INCLUDE', include_path) +print 'after:',env['ENV']['INCLUDE'] + +yields: +before: /foo:/biz +after: /biz:/foo/bar:/foo +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.AppendUnique( key = val ", [...], delete_existing=0)" +Appends the specified keyword arguments +to the end of construction variables in the environment. +If the Environment does not have +the specified construction variable, +it is simply added to the environment. +If the construction variable being appended to is a list, +then any value(s) that already exist in the +construction variable will +.I not +be added again to the list. +However, if delete_existing is 1, +existing matching values are removed first, so +existing values in the arg list move to the end of the list. + +Example: + +.ES +env.AppendUnique(CCFLAGS = '-g', FOO = ['foo.yyy']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +env.BitKeeper() +A factory function that +returns a Builder object +to be used to fetch source files +using BitKeeper. +The returned Builder +is intended to be passed to the +.B SourceCode +function. + +Example: + +.ES +env.SourceCode('.', env.BitKeeper()) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI BuildDir( build_dir ", " src_dir ", [" duplicate ]) +.TP +.RI env.BuildDir( build_dir ", " src_dir ", [" duplicate ]) +Deprecated synonyms for +.BR VariantDir () +and +.BR env.VariantDir (). +The +.I build_dir +argument becomes the +.I variant_dir +argument of +.BR VariantDir () +or +.BR env.VariantDir (). + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Builder( action ", [" arguments ]) +.TP +.RI env.Builder( action ", [" arguments ]) +Creates a Builder object for +the specified +.IR action . +See the section "Builder Objects," +below, for a complete explanation of the arguments and behavior. + +Note that the +.BR env.Builder () +form of the invocation will expand +construction variables in any arguments strings, +including the +.I action +argument, +at the time it is called +using the construction variables in the +.B env +construction environment through which +.BR env.Builder () +was called. +The +.BR Builder () +form delays all variable expansion +until after the Builder object is actually called. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI CacheDir( cache_dir ) +.TP +.RI env.CacheDir( cache_dir ) +Specifies that +.B scons +will maintain a cache of derived files in +.I cache_dir . +The derived files in the cache will be shared +among all the builds using the same +.BR CacheDir () +call. +Specifying a +.I cache_dir +of +.B None +disables derived file caching. + +Calling +.BR env.CacheDir () +will only affect targets built +through the specified construction environment. +Calling +.BR CacheDir () +sets a global default +that will be used by all targets built +through construction environments +that do +.I not +have an +.BR env.CacheDir () +specified. + +When a +.BR CacheDir () +is being used and +.B scons +finds a derived file that needs to be rebuilt, +it will first look in the cache to see if a +derived file has already been built +from identical input files and an identical build action +(as incorporated into the MD5 build signature). +If so, +.B scons +will retrieve the file from the cache. +If the derived file is not present in the cache, +.B scons +will rebuild it and +then place a copy of the built file in the cache +(identified by its MD5 build signature), +so that it may be retrieved by other +builds that need to build the same derived file +from identical inputs. + +Use of a specified +.BR CacheDir() +may be disabled for any invocation +by using the +.B --cache-disable +option. + +If the +.B --cache-force +option is used, +.B scons +will place a copy of +.I all +derived files in the cache, +even if they already existed +and were not built by this invocation. +This is useful to populate a cache +the first time +.BR CacheDir () +is added to a build, +or after using the +.B --cache-disable +option. + +When using +.BR CacheDir (), +.B scons +will report, +"Retrieved `file' from cache," +unless the +.B --cache-show +option is being used. +When the +.B --cache-show +option is used, +.B scons +will print the action that +.I would +have been used to build the file, +without any indication that +the file was actually retrieved from the cache. +This is useful to generate build logs +that are equivalent regardless of whether +a given derived file has been built in-place +or retrieved from the cache. + +The +.BR NoCache () +method can be used to disable caching of specific files. This can be +useful if inputs and/or outputs of some tool are impossible to +predict or prohibitively large. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Clean( targets ", " files_or_dirs ) +.TP +.RI env.Clean( targets ", " files_or_dirs ) +This specifies a list of files or directories which should be removed +whenever the targets are specified with the +.B -c +command line option. +The specified targets may be a list +or an individual target. +Multiple calls to +.BR Clean () +are legal, +and create new targets or add files and directories to the +clean list for the specified targets. + +Multiple files or directories should be specified +either as separate arguments to the +.BR Clean () +method, or as a list. +.BR Clean () +will also accept the return value of any of the construction environment +Builder methods. +Examples: + +The related +.BR NoClean () +function overrides calling +.BR Clean () +for the same target, +and any targets passed to both functions will +.I not +be removed by the +.B -c +option. + +Examples: + +.ES +Clean('foo', ['bar', 'baz']) +Clean('dist', env.Program('hello', 'hello.c')) +Clean(['foo', 'bar'], 'something_else_to_clean') +.EE + +In this example, +installing the project creates a subdirectory for the documentation. +This statement causes the subdirectory to be removed +if the project is deinstalled. +.ES +Clean(docdir, os.path.join(docdir, projectname)) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Command( target ", " source ", " action ", [" key = val ", ...])" +.TP +.RI env.Command( target ", " source ", " action ", [" key = val ", ...])" +Executes a specific action +(or list of actions) +to build a target file or files. +This is more convenient +than defining a separate Builder object +for a single special-case build. + +As a special case, the +.B source_scanner +keyword argument can +be used to specify +a Scanner object +that will be used to scan the sources. +(The global +.B DirScanner +object can be used +if any of the sources will be directories +that must be scanned on-disk for +changes to files that aren't +already specified in other Builder of function calls.) + +Any other keyword arguments specified override any +same-named existing construction variables. + +An action can be an external command, +specified as a string, +or a callable Python object; +see "Action Objects," below, +for more complete information. +Also note that a string specifying an external command +may be preceded by an +.B @ +(at-sign) +to suppress printing the command in question, +or by a +.B \- +(hyphen) +to ignore the exit status of the external command. + +Examples: + +.ES +env.Command('foo.out', 'foo.in', + "$FOO_BUILD < $SOURCES > $TARGET") + +env.Command('bar.out', 'bar.in', + ["rm -f $TARGET", + "$BAR_BUILD < $SOURCES > $TARGET"], + ENV = {'PATH' : '/usr/local/bin/'}) + +def rename(env, target, source): + import os + os.rename('.tmp', str(target[0])) + +env.Command('baz.out', 'baz.in', + ["$BAZ_BUILD < $SOURCES > .tmp", + rename ]) +.EE + +.IP +Note that the +.BR Command () +function will usually assume, by default, +that the specified targets and/or sources are Files, +if no other part of the configuration +identifies what type of entry it is. +If necessary, you can explicitly specify +that targets or source nodes should +be treated as directoriese +by using the +.BR Dir () +or +.BR env.Dir () +functions. + +Examples: + +.ES +env.Command('ddd.list', Dir('ddd'), 'ls -l $SOURCE > $TARGET') + +env['DISTDIR'] = 'destination/directory' +env.Command(env.Dir('$DISTDIR')), None, make_distdir) +.EE + +.IP +(Also note that SCons will usually +automatically create any directory necessary to hold a target file, +so you normally don't need to create directories by hand.) + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ]) +.TP +.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ]) +Creates a Configure object for integrated +functionality similar to GNU autoconf. +See the section "Configure Contexts," +below, for a complete explanation of the arguments and behavior. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Clone([ key = val ", ...])" +Return a separate copy of a construction environment. +If there are any keyword arguments specified, +they are added to the returned copy, +overwriting any existing values +for the keywords. + +Example: + +.ES +env2 = env.Clone() +env3 = env.Clone(CCFLAGS = '-g') +.EE +.IP +Additionally, a list of tools and a toolpath may be specified, as in +the Environment constructor: + +.ES +def MyTool(env): env['FOO'] = 'bar' +env4 = env.Clone(tools = ['msvc', MyTool]) +.EE + +The +.I parse_flags +keyword argument is also recognized: + +.ES +# create an environment for compiling programs that use wxWidgets +wx_env = env.Clone(parse_flags = '!wx-config --cflags --cxxflags') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Copy([ key = val ", ...])" +A now-deprecated synonym for +.BR env.Clone() . + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.CVS( repository ", " module ) +A factory function that +returns a Builder object +to be used to fetch source files +from the specified +CVS +.IR repository . +The returned Builder +is intended to be passed to the +.B SourceCode +function. + +The optional specified +.I module +will be added to the beginning +of all repository path names; +this can be used, in essence, +to strip initial directory names +from the repository path names, +so that you only have to +replicate part of the repository +directory hierarchy in your +local build directory. + +Examples: + +.ES +# Will fetch foo/bar/src.c +# from /usr/local/CVSROOT/foo/bar/src.c. +env.SourceCode('.', env.CVS('/usr/local/CVSROOT')) + +# Will fetch bar/src.c +# from /usr/local/CVSROOT/foo/bar/src.c. +env.SourceCode('.', env.CVS('/usr/local/CVSROOT', 'foo')) + +# Will fetch src.c +# from /usr/local/CVSROOT/foo/bar/src.c. +env.SourceCode('.', env.CVS('/usr/local/CVSROOT', 'foo/bar')) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Decider( function ) +.TP +.RI env.Decider( function ) +Specifies that all up-to-date decisions for +targets built through this construction environment +will be handled by the specified +.IR function . +The +.I function +can be one of the following strings +that specify the type of decision function +to be performed: + +.RS 10 +.HP 6 +.B timestamp-newer +Specifies that a target shall be considered out of date and rebuilt +if the dependency's timestamp is newer than the target file's timestamp. +This is the behavior of the classic Make utility, +and +.B make +can be used a synonym for +.BR timestamp-newer . + +.HP 6 +.B timestamp-match +Specifies that a target shall be considered out of date and rebuilt +if the dependency's timestamp is different than the +timestamp recorded the last time the target was built. +This provides behavior very similar to the classic Make utility +(in particular, files are not opened up so that their +contents can be checksummed) +except that the target will also be rebuilt if a +dependency file has been restored to a version with an +.I earlier +timestamp, such as can happen when restoring files from backup archives. + +.HP 6 +.B MD5 +Specifies that a target shall be considered out of date and rebuilt +if the dependency's content has changed sine the last time +the target was built, +as determined be performing an MD5 checksum +on the dependency's contents +and comparing it to the checksum recorded the +last time the target was built. +.B content +can be used as a synonym for +.BR MD5 . + +.HP 6 +.B MD5-timestamp +Specifies that a target shall be considered out of date and rebuilt +if the dependency's content has changed sine the last time +the target was built, +except that dependencies with a timestamp that matches +the last time the target was rebuilt will be +assumed to be up-to-date and +.I not +rebuilt. +This provides behavior very similar +to the +.B MD5 +behavior of always checksumming file contents, +with an optimization of not checking +the contents of files whose timestamps haven't changed. +The drawback is that SCons will +.I not +detect if a file's content has changed +but its timestamp is the same, +as might happen in an automated script +that runs a build, +updates a file, +and runs the build again, +all within a single second. +.RE + +.IP +Examples: + +.ES +# Use exact timestamp matches by default. +Decider('timestamp-match') + +# Use MD5 content signatures for any targets built +# with the attached construction environment. +env.Decider('content') +.EE + +.IP +In addition to the above already-available functions, +the +.I function +argument may be an actual Python function +that takes the following three arguments: + +.RS 10 +.IP dependency +The Node (file) which +should cause the +.I target +to be rebuilt +if it has "changed" since the last tme +.I target was built. + +.IP target +The Node (file) being built. +In the normal case, +this is what should get rebuilt +if the +.I dependency +has "changed." + +.IP prev_ni +Stored information about the state of the +.I dependency +the last time the +.I target +was built. +This can be consulted to match various +file characteristics +such as the timestamp, +size, or content signature. +.RE + +.IP +The +.I function +should return a +.B True +(non-zero) +value if the +.I dependency +has "changed" since the last time +the +.I target +was built +(indicating that the target +.I should +be rebuilt), +and +.B False +(zero) +otherwise +(indicating that the target should +.I not +be rebuilt). +Note that the decision can be made +using whatever criteria are appopriate. +Ignoring some or all of the function arguments +is perfectly normal. + +Example: + +.ES +def my_decider(dependency, target, prev_ni): + return not os.path.exists(str(target)) + +env.Decider(my_decider) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Default( targets ) +.TP +.RI env.Default( targets ) +This specifies a list of default targets, +which will be built by +.B scons +if no explicit targets are given on the command line. +Multiple calls to +.BR Default () +are legal, +and add to the list of default targets. + +Multiple targets should be specified as +separate arguments to the +.BR Default () +method, or as a list. +.BR Default () +will also accept the Node returned by any +of a construction environment's +builder methods. + +Examples: + +.ES +Default('foo', 'bar', 'baz') +env.Default(['a', 'b', 'c']) +hello = env.Program('hello', 'hello.c') +env.Default(hello) +.EE +.IP +An argument to +.BR Default () +of +.B None +will clear all default targets. +Later calls to +.BR Default () +will add to the (now empty) default-target list +like normal. + +The current list of targets added using the +.BR Default () +function or method is available in the +.B DEFAULT_TARGETS +list; +see below. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI DefaultEnvironment([ args ]) +Creates and returns a default construction environment object. +This construction environment is used internally by SCons +in order to execute many of the global functions in this list, +and to fetch source files transparently +from source code management systems. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Depends( target ", " dependency ) +.TP +.RI env.Depends( target ", " dependency ) +Specifies an explicit dependency; +the +.I target +will be rebuilt +whenever the +.I dependency +has changed. +Both the specified +.I target +and +.I dependency +can be a string +(usually the path name of a file or directory) +or Node objects, +or a list of strings or Node objects +(such as returned by a Builder call). +This should only be necessary +for cases where the dependency +is not caught by a Scanner +for the file. + +Example: + +.ES +env.Depends('foo', 'other-input-file-for-foo') + +mylib = env.Library('mylib.c') +installed_lib = env.Install('lib', mylib) +bar = env.Program('bar.c') + +# Arrange for the library to be copied into the installation +# directory before trying to build the "bar" program. +# (Note that this is for example only. A "real" library +# dependency would normally be configured through the $LIBS +# and $LIBPATH variables, not using an env.Depends() call.) + +env.Depends(bar, installed_lib) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Dictionary([ vars ]) +Returns a dictionary object +containing copies of all of the +construction variables in the environment. +If there are any variable names specified, +only the specified construction +variables are returned in the dictionary. + +Example: + +.ES +dict = env.Dictionary() +cc_dict = env.Dictionary('CC', 'CCFLAGS', 'CCCOM') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Dir( name ", [" directory ]) +.TP +.RI env.Dir( name ", [" directory ]) +This returns a Directory Node, +an object that represents the specified directory +.IR name . +.I name +can be a relative or absolute path. +.I directory +is an optional directory that will be used as the parent directory. +If no +.I directory +is specified, the current script's directory is used as the parent. + +If +.I name +is a list, SCons returns a list of Dir nodes. +Construction variables are expanded in +.IR name . + +Directory Nodes can be used anywhere you +would supply a string as a directory name +to a Builder method or function. +Directory Nodes have attributes and methods +that are useful in many situations; +see "File and Directory Nodes," below. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Dump([ key ]) +Returns a pretty printable representation of the environment. +.IR key , +if not +.IR None , +should be a string containing the name of the variable of interest. + +This SConstruct: +.ES +env=Environment() +print env.Dump('CCCOM') +.EE +.IP +will print: +.ES +\&'$CC -c -o $TARGET $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $SOURCES' +.EE + +.ES +env=Environment() +print env.Dump() +.EE +.IP +will print: +.ES +{ 'AR': 'ar', + 'ARCOM': '$AR $ARFLAGS $TARGET $SOURCES\n$RANLIB $RANLIBFLAGS $TARGET', + 'ARFLAGS': ['r'], + 'AS': 'as', + 'ASCOM': '$AS $ASFLAGS -o $TARGET $SOURCES', + 'ASFLAGS': [], + ... +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI EnsurePythonVersion( major ", " minor ) +.TP +.RI env.EnsurePythonVersion( major ", " minor ) +Ensure that the Python version is at least +.IR major . minor . +This function will +print out an error message and exit SCons with a non-zero exit code if the +actual Python version is not late enough. + +Example: + +.ES +EnsurePythonVersion(2,2) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI EnsureSConsVersion( major ", " minor ", [" revision ]) +.TP +.RI env.EnsureSConsVersion( major ", " minor ", [" revision ]) +Ensure that the SCons version is at least +.IR major.minor , +or +.IR major.minor.revision . +if +.I revision +is specified. +This function will +print out an error message and exit SCons with a non-zero exit code if the +actual SCons version is not late enough. + +Examples: + +.ES +EnsureSConsVersion(0,14) + +EnsureSConsVersion(0,96,90) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Environment([ key = value ", ...])" +.TP +.RI env.Environment([ key = value ", ...])" +Return a new construction environment +initialized with the specified +.IR key = value +pairs. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Execute( action ", [" strfunction ", " varlist ]) +.TP +.RI env.Execute( action ", [" strfunction ", " varlist ]) +Executes an Action object. +The specified +.IR action +may be an Action object +(see the section "Action Objects," +below, for a complete explanation of the arguments and behavior), +or it may be a command-line string, +list of commands, +or executable Python function, +each of which will be converted +into an Action object +and then executed. +The exit value of the command +or return value of the Python function +will be returned. + +Note that +.B scons +will print an error message if the executed +.I action +fails--that is, +exits with or returns a non-zero value. +.B scons +will +.I not , +however, +automatically terminate the build +if the specified +.I action +fails. +If you want the build to stop in response to a failed +.BR Execute () +call, +you must explicitly check for a non-zero return value: + +.ES +Execute(Copy('file.out', 'file.in')) + +if Execute("mkdir sub/dir/ectory"): + # The mkdir failed, don't try to build. + Exit(1) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Exit([ value ]) +.TP +.RI env.Exit([ value ]) +This tells +.B scons +to exit immediately +with the specified +.IR value . +A default exit value of +.B 0 +(zero) +is used if no value is specified. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Export( vars ) +.TP +.RI env.Export( vars ) +This tells +.B scons +to export a list of variables from the current +SConscript file to all other SConscript files. +The exported variables are kept in a global collection, +so subsequent calls to +.BR Export () +will over-write previous exports that have the same name. +Multiple variable names can be passed to +.BR Export () +as separate arguments or as a list. +Keyword arguments can be used to provide names and their values. +A dictionary can be used to map variables to a different name when exported. +Both local variables and global variables can be exported. + +Examples: + +.ES +env = Environment() +# Make env available for all SConscript files to Import(). +Export("env") + +package = 'my_name' +# Make env and package available for all SConscript files:. +Export("env", "package") + +# Make env and package available for all SConscript files: +Export(["env", "package"]) + +# Make env available using the name debug: +Export(debug = env) + +# Make env available using the name debug: +Export({"debug":env}) +.EE + +.IP +Note that the +.BR SConscript () +function supports an +.I exports +argument that makes it easier to to export a variable or +set of variables to a single SConscript file. +See the description of the +.BR SConscript () +function, below. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI File( name ", [" directory ]) +.TP +.RI env.File( name ", [" directory ]) +This returns a +File Node, +an object that represents the specified file +.IR name . +.I name +can be a relative or absolute path. +.I directory +is an optional directory that will be used as the parent directory. + +If +.I name +is a list, SCons returns a list of File nodes. +Construction variables are expanded in +.IR name . + +File Nodes can be used anywhere you +would supply a string as a file name +to a Builder method or function. +File Nodes have attributes and methods +that are useful in many situations; +see "File and Directory Nodes," below. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI FindFile( file ", " dirs ) +.TP +.RI env.FindFile( file ", " dirs ) +Search for +.I file +in the path specified by +.IR dirs . +.I dirs +may be a list of directory names or a single directory name. +In addition to searching for files that exist in the filesytem, +this function also searches for derived files +that have not yet been built. + +Example: + +.ES +foo = env.FindFile('foo', ['dir1', 'dir2']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI FindInstalledFiles( ) +.TP +.RI env.FindInstalledFiles( ) +Returns the list of targets set up by the +.B Install() +or +.B InstallAs() +builders. + +This function serves as a convenient method to select the contents of +a binary package. + +Example: + +.ES +Install( '/bin', [ 'executable_a', 'executable_b' ] ) + +# will return the file node list +# [ '/bin/executable_a', '/bin/executable_b' ] +FindInstalledFiles() + +Install( '/lib', [ 'some_library' ] ) + +# will return the file node list +# [ '/bin/executable_a', '/bin/executable_b', '/lib/some_library' ] +FindInstalledFiles() +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI FindSourceFiles( node = '"."' ) +.TP +.RI env.FindSourceFiles( node = '"."' ) + +Returns the list of nodes which serve as the source of the built files. +It does so by inspecting the dependency tree starting at the optional +argument +.B node +which defaults to the '"."'-node. It will then return all leaves of +.B node. +These are all children which have no further children. + +This function is a convenient method to select the contents of a Source +Package. + +Example: + +.ES +Program( 'src/main_a.c' ) +Program( 'src/main_b.c' ) +Program( 'main_c.c' ) + +# returns ['main_c.c', 'src/main_a.c', 'SConstruct', 'src/main_b.c'] +FindSourceFiles() + +# returns ['src/main_b.c', 'src/main_a.c' ] +FindSourceFiles( 'src' ) +.EE + +.IP +As you can see build support files (SConstruct in the above example) +will also be returned by this function. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI FindPathDirs( variable ) +Returns a function +(actually a callable Python object) +intended to be used as the +.B path_function +of a Scanner object. +The returned object will look up the specified +.I variable +in a construction environment +and treat the construction variable's value as a list of +directory paths that should be searched +(like +.BR CPPPATH , +.BR LIBPATH , +etc.). + +Note that use of +.BR FindPathDirs () +is generally preferable to +writing your own +.B path_function +for the following reasons: +1) The returned list will contain all appropriate directories +found in source trees +(when +.BR VariantDir () +is used) +or in code repositories +(when +.BR Repository () +or the +.B \-Y +option are used). +2) scons will identify expansions of +.I variable +that evaluate to the same list of directories as, +in fact, the same list, +and avoid re-scanning the directories for files, +when possible. + +Example: + +.ES +def my_scan(node, env, path, arg): + # Code to scan file contents goes here... + return include_files + +scanner = Scanner(name = 'myscanner', + function = my_scan, + path_function = FindPathDirs('MYPATH')) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Flatten( sequence ) +.TP +.RI env.Flatten( sequence ) +Takes a sequence (that is, a Python list or tuple) +that may contain nested sequences +and returns a flattened list containing +all of the individual elements in any sequence. +This can be helpful for collecting +the lists returned by calls to Builders; +other Builders will automatically +flatten lists specified as input, +but direct Python manipulation of +these lists does not. + +Examples: + +.ES +foo = Object('foo.c') +bar = Object('bar.c') + +# Because `foo' and `bar' are lists returned by the Object() Builder, +# `objects' will be a list containing nested lists: +objects = ['f1.o', foo, 'f2.o', bar, 'f3.o'] + +# Passing such a list to another Builder is all right because +# the Builder will flatten the list automatically: +Program(source = objects) + +# If you need to manipulate the list directly using Python, you need to +# call Flatten() yourself, or otherwise handle nested lists: +for object in Flatten(objects): + print str(object) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI GetBuildFailures() +Returns a list of exceptions for the +actions that failed while +attempting to build targets. +Each element in the returned list is a +.B BuildError +object +with the following attributes +that record various aspects +of the build failure: + +.B .node +The node that was being built +when the build failure occurred. + +.B .status +The numeric exit status +returned by the command or Python function +that failed when trying to build the +specified Node. + +.B .errstr +The SCons error string +describing the build failure. +(This is often a generic +message like "Error 2" +to indicate that an executed +command exited with a status of 2.) + +.B .filename +The name of the file or +directory that actually caused the failure. +This may be different from the +.B .node +attribute. +For example, +if an attempt to build a target named +.B sub/dir/target +fails because the +.B sub/dir +directory could not be created, +then the +.B .node +attribute will be +.B sub/dir/target +but the +.B .filename +attribute will be +.BR sub/dir . + +.B .executor +The SCons Executor object +for the target Node +being built. +This can be used to retrieve +the construction environment used +for the failed action. + +.B .action +The actual SCons Action object that failed. +This will be one specific action +out of the possible list of +actions that would have been +executed to build the target. + +.B .command +The actual expanded command that was executed and failed, +after expansion of +.BR $TARGET , +.BR $SOURCE , +and other construction variables. + +Note that the +.BR GetBuildFailures () +function +will always return an empty list +until any build failure has occurred, +which means that +.BR GetBuildFailures () +will always return an empty list +while the +.B SConscript +files are being read. +Its primary intended use is +for functions that will be +executed before SCons exits +by passing them to the +standard Python +.BR atexit.register () +function. +Example: + +.ES +import atexit + +def print_build_failures(): + from SCons.Script import GetBuildFailures + for bf in GetBuildFailures(): + print "%s failed: %s" % (bf.node, bf.errstr) + +atexit.register(print_build_failures) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI GetBuildPath( file ", [" ... ]) +.TP +.RI env.GetBuildPath( file ", [" ... ]) +Returns the +.B scons +path name (or names) for the specified +.I file +(or files). +The specified +.I file +or files +may be +.B scons +Nodes or strings representing path names. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI GetLaunchDir() +.TP +.RI env.GetLaunchDir() +Returns the absolute path name of the directory from which +.B scons +was initially invoked. +This can be useful when using the +.BR \-u , +.BR \-U +or +.BR \-D +options, which internally +change to the directory in which the +.B SConstruct +file is found. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI GetOption( name ) +.TP +.RI env.GetOption( name ) +This function provides a way to query the value of +SCons options set on scons command line +(or set using the +.IR SetOption () +function). +The options supported are: + +.RS 10 +.TP 6 +.B cache_debug +which corresponds to --cache-debug; +.TP 6 +.B cache_disable +which corresponds to --cache-disable; +.TP 6 +.B cache_force +which corresponds to --cache-force; +.TP 6 +.B cache_show +which corresponds to --cache-show; +.TP 6 +.B clean +which corresponds to -c, --clean and --remove; +.TP 6 +.B config +which corresponds to --config; +.TP 6 +.B directory +which corresponds to -C and --directory; +.TP 6 +.B diskcheck +which corresponds to --diskcheck +.TP 6 +.B duplicate +which corresponds to --duplicate; +.TP 6 +.B file +which corresponds to -f, --file, --makefile and --sconstruct; +.TP 6 +.B help +which corresponds to -h and --help; +.TP 6 +.B ignore_errors +which corresponds to --ignore-errors; +.TP 6 +.B implicit_cache +which corresponds to --implicit-cache; +.TP 6 +.B implicit_deps_changed +which corresponds to --implicit-deps-changed; +.TP 6 +.B implicit_deps_unchanged +which corresponds to --implicit-deps-unchanged; +.TP 6 +.B interactive +which corresponds to --interact and --interactive; +.TP 6 +.B keep_going +which corresponds to -k and --keep-going; +.TP 6 +.B max_drift +which corresponds to --max-drift; +.TP 6 +.B no_exec +which corresponds to -n, --no-exec, --just-print, --dry-run and --recon; +.TP 6 +.B no_site_dir +which corresponds to --no-site-dir; +.TP 6 +.B num_jobs +which corresponds to -j and --jobs; +.TP 6 +.B profile_file +which corresponds to --profile; +.TP 6 +.B question +which corresponds to -q and --question; +.TP 6 +.B random +which corresponds to --random; +.TP 6 +.B repository +which corresponds to -Y, --repository and --srcdir; +.TP 6 +.B silent +which corresponds to -s, --silent and --quiet; +.TP 6 +.B site_dir +which corresponds to --site-dir; +.TP 6 +.B stack_size +which corresponds to --stack-size; +.TP 6 +.B taskmastertrace_file +which corresponds to --taskmastertrace; and +.TP 6 +.B warn +which corresponds to --warn and --warning. +.RE + +.IP +See the documentation for the +corresponding command line object for information about each specific +option. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Glob( pattern ", [" ondisk ", " source ", " strings ]) +.TP +.RI env.Glob( pattern ", [" ondisk ", " source ", " strings ]) +Returns Nodes (or strings) that match the specified +.IR pattern , +relative to the directory of the current +.B SConscript +file. +The +.BR env.Glob () +form performs string substition on +.I pattern +and returns whatever matches +the resulting expanded pattern. + +The specified +.I pattern +uses Unix shell style metacharacters for matching: + +.ES + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq +.EE + +.IP +If the first character of a filename is a dot, +it must be matched explicitly. +Character matches do +.I not +span directory separators. + +The +.BR Glob () +knows about +repositories +(see the +.BR Repository () +function) +and source directories +(see the +.BR VariantDir () +function) +and +returns a Node (or string, if so configured) +in the local (SConscript) directory +if matching Node is found +anywhere in a corresponding +repository or source directory. + +The +.B ondisk +argument may be set to +.B False +(or any other non-true value) +to disable the search for matches on disk, +thereby only returning matches among +already-configured File or Dir Nodes. +The default behavior is to +return corresponding Nodes +for any on-disk matches found. + +The +.B source +argument may be set to +.B True +(or any equivalent value) +to specify that, +when the local directory is a +.BR VariantDir (), +the returned Nodes should be from the +corresponding source directory, +not the local directory. + +The +.B strings +argument may be set to +.B True +(or any equivalent value) +to have the +.BR Glob () +function return strings, not Nodes, +that represent the matched files or directories. +The returned strings will be relative to +the local (SConscript) directory. +(Note that This may make it easier to perform +arbitrary manipulation of file names, +but if the returned strings are +passed to a different +.B SConscript +file, +any Node translation will be relative +to the other +.B SConscript +directory, +not the original +.B SConscript +directory.) + +Examples: + +.ES +Program('foo', Glob('*.c')) +Zip('/tmp/everything', Glob('.??*') + Glob('*')) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +'\".TP +'\".RI GlobalBuilders( flag ) +'\"When +'\".B flag +'\"is non-zero, +'\"adds the names of the default builders +'\"(Program, Library, etc.) +'\"to the global name space +'\"so they can be called without an explicit construction environment. +'\"(This is the default.) +'\"When +'\".B +'\"flag is zero, +'\"the names of the default builders are removed +'\"from the global name space +'\"so that an explicit construction environment is required +'\"to call all builders. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Help( text ) +.TP +.RI env.Help( text ) +This specifies help text to be printed if the +.B -h +argument is given to +.BR scons . +If +.BR Help +is called multiple times, the text is appended together in the order +that +.BR Help +is called. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Ignore( target ", " dependency ) +.TP +.RI env.Ignore( target ", " dependency ) +The specified dependency file(s) +will be ignored when deciding if +the target file(s) need to be rebuilt. + +You can also use +.BR Ignore() +to remove a target from the default build. +In order to do this you must specify the directory the target will +be built in as the target, and the file you want to skip building +as the dependency. + +Note that this will only remove the dependencies listed from +the files built by default. It will still be built if that +dependency is needed by another object being built. +See the third and forth examples below. + +Examples: + +.ES +env.Ignore('foo', 'foo.c') +env.Ignore('bar', ['bar1.h', 'bar2.h']) +env.Ignore('.','foobar.obj') +env.Ignore('bar','bar/foobar.obj') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Import( vars ) +.TP +.RI env.Import( vars ) +This tells +.B scons +to import a list of variables into the current SConscript file. This +will import variables that were exported with +.BR Export () +or in the +.I exports +argument to +.BR SConscript (). +Variables exported by +.BR SConscript () +have precedence. +Multiple variable names can be passed to +.BR Import () +as separate arguments or as a list. The variable "*" can be used +to import all variables. + +Examples: + +.ES +Import("env") +Import("env", "variable") +Import(["env", "variable"]) +Import("*") +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Literal( string ) +.TP +.RI env.Literal( string ) +The specified +.I string +will be preserved as-is +and not have construction variables expanded. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Local( targets ) +.TP +.RI env.Local( targets ) +The specified +.I targets +will have copies made in the local tree, +even if an already up-to-date copy +exists in a repository. +Returns a list of the target Node or Nodes. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +\" .TP +\" .RI env.MergeShellPaths( arg ", [" prepend ]) +\" Merges the elements of the specified +\" .IR arg , +\" which must be a dictionary, to the construction +\" environment's copy of the shell environment +\" in env['ENV']. +\" (This is the environment which is passed +\" to subshells spawned by SCons.) +\" Note that +\" .I arg +\" must be a single value, +\" so multiple strings must +\" be passed in as a list, +\" not as separate arguments to +\" .BR env.MergeShellPaths (). + +\" New values are prepended to the environment variable by default, +\" unless prepend=0 is specified. +\" Duplicate values are always eliminated, +\" since this function calls +\" .B AppendENVPath +\" or +\" .B PrependENVPath +\" depending on the +\" .I prepend +\" argument. See those functions for more details. + +\" Examples: + +\" .ES +\" # Prepend a path to the shell PATH. +\" env.MergeShellPaths({'PATH':'/usr/local/bin'} ) +\" # Append two dirs to the shell INCLUDE. +\" env.MergeShellPaths({'INCLUDE':['c:/inc1', 'c:/inc2']}, prepend=0 ) + +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.MergeFlags( arg ", [" unique ]) +Merges the specified +.I arg +values to the construction environment's construction variables. +If the +.I arg +argument is not a dictionary, +it is converted to one by calling +.B env.ParseFlags() +on the argument +before the values are merged. +Note that +.I arg +must be a single value, +so multiple strings must +be passed in as a list, +not as separate arguments to +.BR env.MergeFlags (). + +By default, +duplicate values are eliminated; +you can, however, specify +.B unique=0 +to allow duplicate +values to be added. +When eliminating duplicate values, +any construction variables that end with +the string +.B PATH +keep the left-most unique value. +All other construction variables keep +the right-most unique value. + +Examples: + +.ES +# Add an optimization flag to $CCFLAGS. +env.MergeFlags('-O3') + +# Combine the flags returned from running pkg-config with an optimization +# flag and merge the result into the construction variables. +env.MergeFlags(['!pkg-config gtk+-2.0 --cflags', '-O3']) + +# Combine an optimization flag with the flags returned from running pkg-config +# twice and merge the result into the construction variables. +env.MergeFlags(['-O3', + '!pkg-config gtk+-2.0 --cflags --libs', + '!pkg-config libpng12 --cflags --libs']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI NoCache( target ", ...)" +.TP +.RI env.NoCache( target ", ...)" +Specifies a list of files which should +.I not +be cached whenever the +.BR CacheDir () +method has been activated. +The specified targets may be a list +or an individual target. + +Multiple files should be specified +either as separate arguments to the +.BR NoCache () +method, or as a list. +.BR NoCache () +will also accept the return value of any of the construction environment +Builder methods. + +Calling +.BR NoCache () +on directories and other non-File Node types has no effect because +only File Nodes are cached. + +Examples: + +.ES +NoCache('foo.elf') +NoCache(env.Program('hello', 'hello.c')) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI NoClean( target ", ...)" +.TP +.RI env.NoClean( target ", ...)" +Specifies a list of files or directories which should +.I not +be removed whenever the targets (or their dependencies) +are specified with the +.B -c +command line option. +The specified targets may be a list +or an individual target. +Multiple calls to +.BR NoClean () +are legal, +and prevent each specified target +from being removed by calls to the +.B -c +option. + +Multiple files or directories should be specified +either as separate arguments to the +.BR NoClean () +method, or as a list. +.BR NoClean () +will also accept the return value of any of the construction environment +Builder methods. + +Calling +.BR NoClean () +for a target overrides calling +.BR Clean () +for the same target, +and any targets passed to both functions will +.I not +be removed by the +.B -c +option. + +Examples: + +.ES +NoClean('foo.elf') +NoClean(env.Program('hello', 'hello.c')) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.ParseConfig( command ", [" function ", " unique ]) +Calls the specified +.I function +to modify the environment as specified by the output of +.I command . +The default +.I function +is +.BR env.MergeFlags (), +which expects the output of a typical +.I *-config command +(for example, +.BR gtk-config ) +and adds the options +to the appropriate construction variables. +By default, +duplicate values are not +added to any construction variables; +you can specify +.B unique=0 +to allow duplicate +values to be added. + +Interpreted options +and the construction variables they affect +are as specified for the +.BR env.ParseFlags () +method (which this method calls). +See that method's description, below, +for a table of options and construction variables. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI ParseDepends( filename ", [" must_exist ", " only_one ]) +.TP +.RI env.ParseDepends( filename ", [" must_exist ", " only_one ]) +Parses the contents of the specified +.I filename +as a list of dependencies in the style of +.BR Make +or +.BR mkdep , +and explicitly establishes all of the listed dependencies. + +By default, +it is not an error +if the specified +.I filename +does not exist. +The optional +.I must_exist +argument may be set to a non-zero +value to have +scons +throw an exception and +generate an error if the file does not exist, +or is otherwise inaccessible. + +The optional +.I only_one +argument may be set to a non-zero +value to have +scons +thrown an exception and +generate an error +if the file contains dependency +information for more than one target. +This can provide a small sanity check +for files intended to be generated +by, for example, the +.B gcc -M +flag, +which should typically only +write dependency information for +one output file into a corresponding +.B .d +file. + +The +.I filename +and all of the files listed therein +will be interpreted relative to +the directory of the +.I SConscript +file which calls the +.B ParseDepends +function. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.ParseFlags( flags ", ...)" +Parses one or more strings containing +typical command-line flags for GCC tool chains +and returns a dictionary with the flag values +separated into the appropriate SCons construction variables. +This is intended as a companion to the +.BR env.MergeFlags () +method, but allows for the values in the returned dictionary +to be modified, if necessary, +before merging them into the construction environment. +(Note that +.BR env.MergeFlags () +will call this method if its argument is not a dictionary, +so it is usually not necessary to call +.BR env.ParseFlags () +directly unless you want to manipulate the values.) + +If the first character in any string is +an exclamation mark (!), +the rest of the string is executed as a command, +and the output from the command is +parsed as GCC tool chain command-line flags +and added to the resulting dictionary. + +Flag values are translated accordig to the prefix found, +and added to the following construction variables: + +.ES +-arch CCFLAGS, LINKFLAGS +-D CPPDEFINES +-framework FRAMEWORKS +-frameworkdir= FRAMEWORKPATH +-include CCFLAGS +-isysroot CCFLAGS, LINKFLAGS +-I CPPPATH +-l LIBS +-L LIBPATH +-mno-cygwin CCFLAGS, LINKFLAGS +-mwindows LINKFLAGS +-pthread CCFLAGS, LINKFLAGS +-std= CFLAGS +-Wa, ASFLAGS, CCFLAGS +-Wl,-rpath= RPATH +-Wl,-R, RPATH +-Wl,-R RPATH +-Wl, LINKFLAGS +-Wp, CPPFLAGS +- CCFLAGS ++ CCFLAGS, LINKFLAGS +.EE + +.IP +Any other strings not associated with options +are assumed to be the names of libraries +and added to the +.B LIBS +construction variable. + +Examples (all of which produce the same result): + +.ES +dict = env.ParseFlags('-O2 -Dfoo -Dbar=1') +dict = env.ParseFlags('-O2', '-Dfoo', '-Dbar=1') +dict = env.ParseFlags(['-O2', '-Dfoo -Dbar=1']) +dict = env.ParseFlags('-O2', '!echo -Dfoo -Dbar=1') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +env.Perforce() +A factory function that +returns a Builder object +to be used to fetch source files +from the Perforce source code management system. +The returned Builder +is intended to be passed to the +.B SourceCode +function. + +Example: + +.ES +env.SourceCode('.', env.Perforce()) +.EE +.IP +Perforce uses a number of external +environment variables for its operation. +Consequently, this function adds the +following variables from the user's external environment +to the construction environment's +ENV dictionary: +P4CHARSET, +P4CLIENT, +P4LANGUAGE, +P4PASSWD, +P4PORT, +P4USER, +SystemRoot, +USER, +and +USERNAME. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Platform( string ) +Returns a callable object +that can be used to initialize +a construction environment using the +platform keyword of the Environment() method. + +Example: + +.ES +env = Environment(platform = Platform('win32')) +.EE +.TP +.RI env.Platform( string ) +Applies the callable object for the specified platform +.I string +to the environment through which the method was called. + +.ES +env.Platform('posix') +.EE +.IP +Note that the +.B win32 +platform adds the +.B SystemDrive +and +.B SystemRoot +variables from the user's external environment +to the construction environment's +.B ENV +dictionary. +This is so that any executed commands +that use sockets to connect with other systems +(such as fetching source files from +external CVS repository specifications like +.BR :pserver:anonymous@cvs.sourceforge.net:/cvsroot/scons ) +will work on Windows systems. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Progress( callable ", [" interval ]) +.TP +.RI Progress( string ", [" interval ", " file ", " overwrite ]) +.TP +.RI Progress( list_of_strings ", [" interval ", " file ", " overwrite ]) +Allows SCons to show progress made during the build +by displaying a string or calling a function while +evaluating Nodes (e.g. files). + +If the first specified argument is a Python callable +(a function or an object that has a +.BR __call__ () +method), +the function will be called +once every +.I interval +times a Node is evaluated. +The callable will be passed the evaluated Node +as its only argument. +(For future compatibility, +it's a good idea to also add +.B *args +and +.B **kw +as arguments to your function or method. +This will prevent the code from breaking +if SCons ever changes the interface +to call the function with additional arguments in the future.) + +An example of a simple custom progress function +that prints a string containing the Node name +every 10 Nodes: + +.ES +def my_progress_function(node, *args, **kw): + print 'Evaluating node %s!' % node +Progress(my_progress_function, interval=10) +.EE +.IP +A more complicated example of a custom progress display object +that prints a string containing a count +every 100 evaluated Nodes. +Note the use of +.B \\\\r +(a carriage return) +at the end so that the string +will overwrite itself on a display: + +.ES +import sys +class ProgressCounter: + count = 0 + def __call__(self, node, *args, **kw): + self.count += 100 + sys.stderr.write('Evaluated %s nodes\\r' % self.count) +Progress(ProgressCounter(), interval=100) +.EE +.IP +If the first argument +.BR Progress () +is a string, +the string will be displayed +every +.I interval +evaluated Nodes. +The default is to print the string on standard output; +an alternate output stream +may be specified with the +.B file= +argument. +The following will print a series of dots +on the error output, +one dot for every 100 evaluated Nodes: + +.ES +import sys +Progress('.', interval=100, file=sys.stderr) +.EE +.IP +If the string contains the verbatim substring +.B $TARGET, +it will be replaced with the Node. +Note that, for performance reasons, this is +.I not +a regular SCons variable substition, +so you can not use other variables +or use curly braces. +The following example will print the name of +every evaluated Node, +using a +.B \\\\r +(carriage return) to cause each line to overwritten by the next line, +and the +.B overwrite= +keyword argument to make sure the previously-printed +file name is overwritten with blank spaces: + +.ES +import sys +Progress('$TARGET\\r', overwrite=True) +.EE +.IP +If the first argument to +.BR Progress () +is a list of strings, +then each string in the list will be displayed +in rotating fashion every +.I interval +evaluated Nodes. +This can be used to implement a "spinner" +on the user's screen as follows: + +.ES +Progress(['-\\r', '\\\\\\r', '|\\r', '/\\r'], interval=5) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Precious( target ", ...)" +.TP +.RI env.Precious( target ", ...)" +Marks each given +.I target +as precious so it is not deleted before it is rebuilt. Normally +.B scons +deletes a target before building it. +Multiple targets can be passed in to a single call to +.BR Precious (). + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Prepend( key = val ", [...])" +Appends the specified keyword arguments +to the beginning of construction variables in the environment. +If the Environment does not have +the specified construction variable, +it is simply added to the environment. +If the values of the construction variable +and the keyword argument are the same type, +then the two values will be simply added together. +Otherwise, the construction variable +and the value of the keyword argument +are both coerced to lists, +and the lists are added together. +(See also the Append method, above.) + +Example: + +.ES +env.Prepend(CCFLAGS = '-g ', FOO = ['foo.yyy']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.PrependENVPath( name ", " newpath ", [" envname ", " sep ", " delete_existing ]) +This appends new path elements to the given path in the +specified external environment +.RB ( ENV +by default). +This will only add +any particular path once (leaving the first one it encounters and +ignoring the rest, to preserve path order), +and to help assure this, +will normalize all paths (using +.B os.path.normpath +and +.BR os.path.normcase ). +This can also handle the +case where the given old path variable is a list instead of a +string, in which case a list will be returned instead of a string. + +If +.I delete_existing +is 0, then adding a path that already exists +will not move it to the beginning; +it will stay where it is in the list. + +Example: + +.ES +print 'before:',env['ENV']['INCLUDE'] +include_path = '/foo/bar:/foo' +env.PrependENVPath('INCLUDE', include_path) +print 'after:',env['ENV']['INCLUDE'] +.EE + +The above exmaple will print: + +.ES +before: /biz:/foo +after: /foo/bar:/foo:/biz +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.PrependUnique( key = val ", delete_existing=0, [...])" +Appends the specified keyword arguments +to the beginning of construction variables in the environment. +If the Environment does not have +the specified construction variable, +it is simply added to the environment. +If the construction variable being appended to is a list, +then any value(s) that already exist in the +construction variable will +.I not +be added again to the list. +However, if delete_existing is 1, +existing matching values are removed first, so +existing values in the arg list move to the front of the list. + +Example: + +.ES +env.PrependUnique(CCFLAGS = '-g', FOO = ['foo.yyy']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +env.RCS() +A factory function that +returns a Builder object +to be used to fetch source files +from RCS. +The returned Builder +is intended to be passed to the +.B SourceCode +function: + +Examples: + +.ES +env.SourceCode('.', env.RCS()) +.EE +.IP +Note that +.B scons +will fetch source files +from RCS subdirectories automatically, +so configuring RCS +as demonstrated in the above example +should only be necessary if +you are fetching from +RCS,v +files in the same +directory as the source files, +or if you need to explicitly specify RCS +for a specific subdirectory. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.Replace( key = val ", [...])" +Replaces construction variables in the Environment +with the specified keyword arguments. + +Example: + +.ES +env.Replace(CCFLAGS = '-g', FOO = 'foo.xxx') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Repository( directory ) +.TP +.RI env.Repository( directory ) +Specifies that +.I directory +is a repository to be searched for files. +Multiple calls to +.BR Repository () +are legal, +and each one adds to the list of +repositories that will be searched. + +To +.BR scons , +a repository is a copy of the source tree, +from the top-level directory on down, +which may contain +both source files and derived files +that can be used to build targets in +the local source tree. +The canonical example would be an +official source tree maintained by an integrator. +If the repository contains derived files, +then the derived files should have been built using +.BR scons , +so that the repository contains the necessary +signature information to allow +.B scons +to figure out when it is appropriate to +use the repository copy of a derived file, +instead of building one locally. + +Note that if an up-to-date derived file +already exists in a repository, +.B scons +will +.I not +make a copy in the local directory tree. +In order to guarantee that a local copy +will be made, +use the +.B Local() +method. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Requires( target ", " prerequisite ) +.TP +.RI env.Requires( target ", " prerequisite ) +Specifies an order-only relationship +between the specified target file(s) +and the specified prerequisite file(s). +The prerequisite file(s) +will be (re)built, if necessary, +.I before +the target file(s), +but the target file(s) do not actually +depend on the prerequisites +and will not be rebuilt simply because +the prerequisite file(s) change. + +Example: + +.ES +env.Requires('foo', 'file-that-must-be-built-before-foo') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Return([ vars "... , " stop= ]) +By default, +this stops processing the current SConscript +file and returns to the calling SConscript file +the values of the variables named in the +.I vars +string arguments. +Multiple strings contaning variable names may be passed to +.BR Return (). +Any strings that contain white space + +The optional +.B stop= +keyword argument may be set to a false value +to continue processing the rest of the SConscript +file after the +.BR Return () +call. +This was the default behavior prior to SCons 0.98. +However, the values returned +are still the values of the variables in the named +.I vars +at the point +.BR Return () +is called. + +Examples: + +.ES +# Returns without returning a value. +Return() + +# Returns the value of the 'foo' Python variable. +Return("foo") + +# Returns the values of the Python variables 'foo' and 'bar'. +Return("foo", "bar") + +# Returns the values of Python variables 'val1' and 'val2'. +Return('val1 val2') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Scanner( function ", [" argument ", " keys ", " path_function ", " node_class ", " node_factory ", " scan_check ", " recursive ]) +.TP +.RI env.Scanner( function ", [" argument ", " keys ", " path_function ", " node_class ", " node_factory ", " scan_check ", " recursive ]) +Creates a Scanner object for +the specified +.IR function . +See the section "Scanner Objects," +below, for a complete explanation of the arguments and behavior. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +env.SCCS() +A factory function that +returns a Builder object +to be used to fetch source files +from SCCS. +The returned Builder +is intended to be passed to the +.B SourceCode +function. + +Example: + +.ES +env.SourceCode('.', env.SCCS()) +.EE +.IP +Note that +.B scons +will fetch source files +from SCCS subdirectories automatically, +so configuring SCCS +as demonstrated in the above example +should only be necessary if +you are fetching from +.I s.SCCS +files in the same +directory as the source files, +or if you need to explicitly specify SCCS +for a specific subdirectory. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SConscript( scripts ", [" exports ", " variant_dir ", " duplicate ]) +'\" .RI SConscript( scripts ", [" exports ", " variant_dir ", " src_dir ", " duplicate ]) +.TP +.RI env.SConscript( scripts ", [" exports ", " variant_dir ", " duplicate ]) +'\" .RI env.SConscript( scripts ", [" exports ", " variant_dir ", " src_dir ", " duplicate ]) +.TP +.RI SConscript(dirs= subdirs ", [name=" script ", " exports ", " variant_dir ", " duplicate ]) +'\" .RI SConscript(dirs= subdirs ", [name=" script ", " exports ", " variant_dir ", " src_dir ", " duplicate ]) +.TP +.RI env.SConscript(dirs= subdirs ", [name=" script ", " exports ", " variant_dir ", " duplicate ]) +'\" .RI env.SConscript(dirs= subdirs ", [name=" script ", " exports ", " variant_dir ", " src_dir ", " duplicate ]) +This tells +.B scons +to execute +one or more subsidiary SConscript (configuration) files. +Any variables returned by a called script using +.BR Return () +will be returned by the call to +.BR SConscript (). +There are two ways to call the +.BR SConscript () +function. + +The first way you can call +.BR SConscript () +is to explicitly specify one or more +.I scripts +as the first argument. +A single script may be specified as a string; +multiple scripts must be specified as a list +(either explicitly or as created by +a function like +.BR Split ()). +Examples: +.ES +SConscript('SConscript') # run SConscript in the current directory +SConscript('src/SConscript') # run SConscript in the src directory +SConscript(['src/SConscript', 'doc/SConscript']) +config = SConscript('MyConfig.py') +.EE + +The second way you can call +.BR SConscript () +is to specify a list of (sub)directory names +as a +.RI dirs= subdirs +keyword argument. +In this case, +.B scons +will, by default, +execute a subsidiary configuration file named +.B SConscript +in each of the specified directories. +You may specify a name other than +.B SConscript +by supplying an optional +.RI name= script +keyword argument. +The first three examples below have the same effect +as the first three examples above: +.ES +SConscript(dirs='.') # run SConscript in the current directory +SConscript(dirs='src') # run SConscript in the src directory +SConscript(dirs=['src', 'doc']) +SConscript(dirs=['sub1', 'sub2'], name='MySConscript') +.EE + +The optional +.I exports +argument provides a list of variable names or a dictionary of +named values to export to the +.IR script(s) . +These variables are locally exported only to the specified +.IR script(s) , +and do not affect the global pool of variables used by the +.BR Export () +function. +'\"If multiple dirs are provided, each script gets a fresh export. +The subsidiary +.I script(s) +must use the +.BR Import () +function to import the variables. +Examples: +.ES +foo = SConscript('sub/SConscript', exports='env') +SConscript('dir/SConscript', exports=['env', 'variable']) +SConscript(dirs='subdir', exports='env variable') +SConscript(dirs=['one', 'two', 'three'], exports='shared_info') +.EE + +If the optional +.I variant_dir +argument is present, it causes an effect equivalent to the +.BR VariantDir () +method described below. +(If +.I variant_dir +is not present, the +'\" .IR src_dir and +.I duplicate +'\" arguments are ignored.) +argument is ignored.) +The +.I variant_dir +'\" and +'\" .I src_dir +'\" arguments are interpreted relative to the directory of the calling +argument is interpreted relative to the directory of the calling +.BR SConscript file. +See the description of the +.BR VariantDir () +function below for additional details and restrictions. + +If +'\" .IR variant_dir " is present, but" +'\" .IR src_dir " is not," +.IR variant_dir " is present," +the source directory is relative to the called +.BR SConscript " file." +.ES +SConscript('src/SConscript', variant_dir = 'build') +.EE +is equivalent to +.ES +VariantDir('build', 'src') +SConscript('build/SConscript') +.EE +This later paradigm is often used when the sources are +in the same directory as the +.BR SConstruct file: +.ES +SConscript('SConscript', variant_dir = 'build') +.EE +is equivalent to +.ES +VariantDir('build', '.') +SConscript('build/SConscript') +.EE + +'\" If +'\" .IR variant_dir " and" +'\" .IR src_dir " are both present," +'\" xxxxx everything is in a state of confusion. +'\" .ES +'\" SConscript(dirs = 'src', variant_dir = 'build', src_dir = '.') +'\" runs src/SConscript in build/src, but +'\" SConscript(dirs = 'lib', variant_dir = 'build', src_dir = 'src') +'\" runs lib/SConscript (in lib!). However, +'\" SConscript(dirs = 'src', variant_dir = 'build', src_dir = 'src') +'\" runs src/SConscript in build. Moreover, +'\" SConscript(dirs = 'src/lib', variant_dir = 'build', src_dir = 'src') +'\" runs src/lib/SConscript in build/lib. Moreover, +'\" SConscript(dirs = 'build/src/lib', variant_dir = 'build', src_dir = 'src') +'\" can't find build/src/lib/SConscript, even though it ought to exist. +'\" .EE +'\" is equivalent to +'\" .ES +'\" ???????????????? +'\" .EE +'\" and what about this alternative? +'\"TODO??? SConscript('build/SConscript', src_dir='src') + +Here are some composite examples: + +.ES +# collect the configuration information and use it to build src and doc +shared_info = SConscript('MyConfig.py') +SConscript('src/SConscript', exports='shared_info') +SConscript('doc/SConscript', exports='shared_info') +.EE + +.ES +# build debugging and production versions. SConscript +# can use Dir('.').path to determine variant. +SConscript('SConscript', variant_dir='debug', duplicate=0) +SConscript('SConscript', variant_dir='prod', duplicate=0) +.EE + +.ES +# build debugging and production versions. SConscript +# is passed flags to use. +opts = { 'CPPDEFINES' : ['DEBUG'], 'CCFLAGS' : '-pgdb' } +SConscript('SConscript', variant_dir='debug', duplicate=0, exports=opts) +opts = { 'CPPDEFINES' : ['NODEBUG'], 'CCFLAGS' : '-O' } +SConscript('SConscript', variant_dir='prod', duplicate=0, exports=opts) +.EE + +.ES +# build common documentation and compile for different architectures +SConscript('doc/SConscript', variant_dir='build/doc', duplicate=0) +SConscript('src/SConscript', variant_dir='build/x86', duplicate=0) +SConscript('src/SConscript', variant_dir='build/ppc', duplicate=0) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SConscriptChdir( value ) +.TP +.RI env.SConscriptChdir( value ) +By default, +.B scons +changes its working directory +to the directory in which each +subsidiary SConscript file lives. +This behavior may be disabled +by specifying either: + +.ES +SConscriptChdir(0) +env.SConscriptChdir(0) +.EE +.IP +in which case +.B scons +will stay in the top-level directory +while reading all SConscript files. +(This may be necessary when building from repositories, +when all the directories in which SConscript files may be found +don't necessarily exist locally.) +You may enable and disable +this ability by calling +SConscriptChdir() +multiple times. + +Example: + +.ES +env = Environment() +SConscriptChdir(0) +SConscript('foo/SConscript') # will not chdir to foo +env.SConscriptChdir(1) +SConscript('bar/SConscript') # will chdir to bar +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SConsignFile([ file , dbm_module ]) +.TP +.RI env.SConsignFile([ file , dbm_module ]) +This tells +.B scons +to store all file signatures +in the specified database +.IR file . +If the +.I file +name is omitted, +.B .sconsign +is used by default. +(The actual file name(s) stored on disk +may have an appropriated suffix appended +by the +.IR dbm_module .) +If +.I file +is not an absolute path name, +the file is placed in the same directory as the top-level +.B SConstruct +file. + +If +.I file +is +.BR None , +then +.B scons +will store file signatures +in a separate +.B .sconsign +file in each directory, +not in one global database file. +(This was the default behavior +prior to SCons 0.96.91 and 0.97.) + +The optional +.I dbm_module +argument can be used to specify +which Python database module +The default is to use a custom +.B SCons.dblite +module that uses pickled +Python data structures, +and which works on all Python versions from 1.5.2 on. + +Examples: + +.ES +# Explicitly stores signatures in ".sconsign.dblite" +# in the top-level SConstruct directory (the +# default behavior). +SConsignFile() + +# Stores signatures in the file "etc/scons-signatures" +# relative to the top-level SConstruct directory. +SConsignFile("etc/scons-signatures") + +# Stores signatures in the specified absolute file name. +SConsignFile("/home/me/SCons/signatures") + +# Stores signatures in a separate .sconsign file +# in each directory. +SConsignFile(None) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.SetDefault(key = val ", [...])" +Sets construction variables to default values specified with the keyword +arguments if (and only if) the variables are not already set. +The following statements are equivalent: + +.ES +env.SetDefault(FOO = 'foo') + +if not env.has_key('FOO'): env['FOO'] = 'foo' +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SetOption( name ", " value ) +.TP +.RI env.SetOption( name ", " value ) +This function provides a way to set a select subset of the scons command +line options from a SConscript file. The options supported are: + +.RS 10 +.TP 6 +.B clean +which corresponds to -c, --clean and --remove; +.TP 6 +.B duplicate +which corresponds to --duplicate; +.TP 6 +.B help +which corresponds to -h and --help; +.TP 6 +.B implicit_cache +which corresponds to --implicit-cache; +.TP 6 +.B max_drift +which corresponds to --max-drift; +.TP 6 +.B no_exec +which corresponds to -n, --no-exec, --just-print, --dry-run and --recon; +.TP 6 +.B num_jobs +which corresponds to -j and --jobs; +.TP 6 +.B random +which corresponds to --random; and +.TP 6 +.B stack_size +which corresponds to --stack-size. +.RE + +.IP +See the documentation for the +corresponding command line object for information about each specific +option. + +Example: + +.ES +SetOption('max_drift', 1) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SideEffect( side_effect ", " target ) +.TP +.RI env.SideEffect( side_effect ", " target ) +Declares +.I side_effect +as a side effect of building +.IR target . +Both +.I side_effect +and +.I target +can be a list, a file name, or a node. +A side effect is a target file that is created or updated +as a side effect of building other targets. +For example, a Windows PDB +file is created as a side effect of building the .obj +files for a static library, +and various log files are created updated +as side effects of various TeX commands. +If a target is a side effect of multiple build commands, +.B scons +will ensure that only one set of commands +is executed at a time. +Consequently, you only need to use this method +for side-effect targets that are built as a result of +multiple build commands. + +Because multiple build commands may update +the same side effect file, +by default the +.I side_effect +target is +.I not +automatically removed +when the +.I target +is removed by the +.B -c +option. +(Note, however, that the +.I side_effect +might be removed as part of +cleaning the directory in which it lives.) +If you want to make sure the +.I side_effect +is cleaned whenever a specific +.I target +is cleaned, +you must specify this explicitly +with the +.BR Clean () +or +.BR env.Clean () +function. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SourceCode( entries ", " builder ) +.TP +.RI env.SourceCode( entries ", " builder ) +Arrange for non-existent source files to +be fetched from a source code management system +using the specified +.IR builder . +The specified +.I entries +may be a Node, string or list of both, +and may represent either individual +source files or directories in which +source files can be found. + +For any non-existent source files, +.B scons +will search up the directory tree +and use the first +.B SourceCode +builder it finds. +The specified +.I builder +may be +.BR None , +in which case +.B scons +will not use a builder to fetch +source files for the specified +.IR entries , +even if a +.B SourceCode +builder has been specified +for a directory higher up the tree. + +.B scons +will, by default, +fetch files from SCCS or RCS subdirectories +without explicit configuration. +This takes some extra processing time +to search for the necessary +source code management files on disk. +You can avoid these extra searches +and speed up your build a little +by disabling these searches as follows: + +.ES +env.SourceCode('.', None) +.EE + +.IP +Note that if the specified +.I builder +is one you create by hand, +it must have an associated +construction environment to use +when fetching a source file. + +.B scons +provides a set of canned factory +functions that return appropriate +Builders for various popular +source code management systems. +Canonical examples of invocation include: + +.ES +env.SourceCode('.', env.BitKeeper('/usr/local/BKsources')) +env.SourceCode('src', env.CVS('/usr/local/CVSROOT')) +env.SourceCode('/', env.RCS()) +env.SourceCode(['f1.c', 'f2.c'], env.SCCS()) +env.SourceCode('no_source.c', None) +.EE +'\"env.SourceCode('.', env.Subversion('file:///usr/local/Subversion')) + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI env.subst( input ", [" raw ", " target ", " source ", " conv ]) +Performs construction variable interpolation +on the specified string or sequence argument +.IR input . + +By default, +leading or trailing white space will +be removed from the result. +and all sequences of white space +will be compressed to a single space character. +Additionally, any +.B $( +and +.B $) +character sequences will be stripped from the returned string, +The optional +.I raw +argument may be set to +.B 1 +if you want to preserve white space and +.BR $( - $) +sequences. +The +.I raw +argument may be set to +.B 2 +if you want to strip +all characters between +any +.B $( +and +.B $) +pairs +(as is done for signature calculation). + +If the input is a sequence +(list or tuple), +the individual elements of +the sequence will be expanded, +and the results will be returned as a list. + +The optional +.I target +and +.I source +keyword arguments +must be set to lists of +target and source nodes, respectively, +if you want the +.BR $TARGET , +.BR $TARGETS , +.BR $SOURCE +and +.BR $SOURCES +to be available for expansion. +This is usually necessary if you are +calling +.BR env.subst () +from within a Python function used +as an SCons action. + +Returned string values or sequence elements +are converted to their string representation by default. +The optional +.I conv +argument +may specify a conversion function +that will be used in place of +the default. +For example, if you want Python objects +(including SCons Nodes) +to be returned as Python objects, +you can use the Python +.B lambda +idiom to pass in an unnamed function +that simply returns its unconverted argument. + +Example: + +.ES +print env.subst("The C compiler is: $CC") + +def compile(target, source, env): + sourceDir = env.subst("${SOURCE.srcdir}", + target=target, + source=source) + +source_nodes = env.subst('$EXPAND_TO_NODELIST', + conv=lambda x: x) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +'\".TP +'\".RI Subversion( repository ", " module ) +'\"A factory function that +'\"returns a Builder object +'\"to be used to fetch source files +'\"from the specified Subversion +'\".IR repository . +'\"The returned Builder +'\"is intended to be passed to the +'\".B SourceCode +'\"function. +'\" +'\"The optional specified +'\".I module +'\"will be added to the beginning +'\"of all repository path names; +'\"this can be used, in essence, +'\"to strip initial directory names +'\"from the repository path names, +'\"so that you only have to +'\"replicate part of the repository +'\"directory hierarchy in your +'\"local build directory. +'\" +'\"Example: +'\" +'\".ES +'\"# Will fetch foo/bar/src.c +'\"# from /usr/local/Subversion/foo/bar/src.c. +'\"env.SourceCode('.', env.Subversion('file:///usr/local/Subversion')) +'\" +'\"# Will fetch bar/src.c +'\"# from /usr/local/Subversion/foo/bar/src.c. +'\"env.SourceCode('.', env.Subversion('file:///usr/local/Subversion', 'foo')) +'\" +'\"# Will fetch src.c +'\"# from /usr/local/Subversion/foo/bar/src.c. +'\"env.SourceCode('.', env.Subversion('file:///usr/local/Subversion', 'foo/bar')) +'\".EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI SourceSignatures( type ) +.TP +.RI env.SourceSignatures( type ) +Note: Although it is not yet officially deprecated, +use of this function is discouraged. +See the +.BR Decider () +function for a more flexible and straightforward way +to configure SCons' decision-making. + +The +.BR SourceSignatures () +function tells +.B scons +how to decide if a source file +(a file that is not built from any other files) +has changed since the last time it +was used to build a particular target file. +Legal values are +.B "MD5" +or +.BR "timestamp" . + +If the environment method is used, +the specified type of source signature +is only used when deciding whether targets +built with that environment are up-to-date or must be rebuilt. +If the global function is used, +the specified type of source signature becomes the default +used for all decisions +about whether targets are up-to-date. + +.B "MD5" +means +.B scons +decides that a source file has changed +if the MD5 checksum of its contents has changed since +the last time it was used to rebuild a particular target file. + +.B "timestamp" +means +.B scons +decides that a source file has changed +if its timestamp (modification time) has changed since +the last time it was used to rebuild a particular target file. +(Note that although this is similar to the behavior of Make, +by default it will also rebuild if the dependency is +.I older +than the last time it was used to rebuild the target file.) + +There is no different between the two behaviors +for Python +.BR Value () +node objects. + +.B "MD5" +signatures take longer to compute, +but are more accurate than +.B "timestamp" +signatures. +The default value is +.BR "MD5" . + +Note that the default +.BR TargetSignatures () +setting (see below) +is to use this +.BR SourceSignatures () +setting for any target files that are used +to build other target files. +Consequently, changing the value of +.BR SourceSignatures () +will, by default, +affect the up-to-date decision for all files in the build +(or all files built with a specific construction environment +when +.BR env.SourceSignatures () +is used). + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Split( arg ) +.TP +.RI env.Split( arg ) +Returns a list of file names or other objects. +If arg is a string, +it will be split on strings of white-space characters +within the string, +making it easier to write long lists of file names. +If arg is already a list, +the list will be returned untouched. +If arg is any other type of object, +it will be returned as a list +containing just the object. + +Example: + +.ES +files = Split("f1.c f2.c f3.c") +files = env.Split("f4.c f5.c f6.c") +files = Split(""" + f7.c + f8.c + f9.c +""") +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Tag( node ", " tags ) +Annotates file or directory Nodes with +information about how the +.BR Package () +Builder should package those files or directories. +All tags are optional. + +Examples: + +.ES +# makes sure the built library will be installed with 0644 file +# access mode +Tag( Library( 'lib.c' ), UNIX_ATTR="0644" ) + +# marks file2.txt to be a documentation file +Tag( 'file2.txt', DOC ) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI TargetSignatures( type ) +.TP +.RI env.TargetSignatures( type ) +Note: Although it is not yet officially deprecated, +use of this function is discouraged. +See the +.BR Decider () +function for a more flexible and straightforward way +to configure SCons' decision-making. + +The +.BR TargetSignatures () +function tells +.B scons +how to decide if a target file +(a file that +.I is +built from any other files) +has changed since the last time it +was used to build some other target file. +Legal values are +.BR "build" ; +.BR "content" +(or its synonym +.BR "MD5" ); +.BR "timestamp" ; +or +.BR "source" . + +If the environment method is used, +the specified type of target signature is only used +for targets built with that environment. +If the global function is used, +the specified type of signature becomes the default +used for all target files that +don't have an explicit target signature type +specified for their environments. + +.B "content" +(or its synonym +.BR "MD5" ) +means +.B scons +decides that a target file has changed +if the MD5 checksum of its contents has changed since +the last time it was used to rebuild some other target file. +This means +.B scons +will open up +MD5 sum the contents +of target files after they're built, +and may decide that it does not need to rebuild +"downstream" target files if a file was +rebuilt with exactly the same contents as the last time. + +.B "timestamp" +means +.B scons +decides that a target file has changed +if its timestamp (modification time) has changed since +the last time it was used to rebuild some other target file. +(Note that although this is similar to the behavior of Make, +by default it will also rebuild if the dependency is +.I older +than the last time it was used to rebuild the target file.) + +.B "source" +means +.B scons +decides that a target file has changed +as specified by the corresponding +.BR SourceSignatures () +setting +.BR "" ( "MD5" +or +.BR "timestamp" ). +This means that +.B scons +will treat all input files to a target the same way, +regardless of whether they are source files +or have been built from other files. + +.B "build" +means +.B scons +decides that a target file has changed +if it has been rebuilt in this invocation +or if its content or timestamp have changed +as specified by the corresponding +.BR SourceSignatures () +setting. +This "propagates" the status of a rebuilt file +so that other "downstream" target files +will always be rebuilt, +even if the contents or the timestamp +have not changed. + +.B "build" +signatures are fastest because +.B "content" +(or +.BR "MD5" ) +signatures take longer to compute, +but are more accurate than +.B "timestamp" +signatures, +and can prevent unnecessary "downstream" rebuilds +when a target file is rebuilt to the exact same contents +as the previous build. +The +.B "source" +setting provides the most consistent behavior +when other target files may be rebuilt from +both source and target input files. +The default value is +.BR "source" . + +Because the default setting is +.BR "source" , +using +.BR SourceSignatures () +is generally preferable to +.BR TargetSignatures () , +so that the up-to-date decision +will be consistent for all files +(or all files built with a specific construction environment). +Use of +.BR TargetSignatures () +provides specific control for how built target files +affect their "downstream" dependencies. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Tool( string [, toolpath ", " **kw ]) +Returns a callable object +that can be used to initialize +a construction environment using the +tools keyword of the Environment() method. +The object may be called with a construction +environment as an argument, +in which case the object will +add the necessary variables +to the construction environment +and the name of the tool will be added to the +.B $TOOLS +construction variable. + +Additional keyword arguments are passed to the tool's +.B generate() +method. + +Examples: + +.ES +env = Environment(tools = [ Tool('msvc') ]) + +env = Environment() +t = Tool('msvc') +t(env) # adds 'msvc' to the TOOLS variable +u = Tool('opengl', toolpath = ['tools']) +u(env) # adds 'opengl' to the TOOLS variable +.EE +.TP +.RI env.Tool( string [, toolpath ", " **kw ]) +Applies the callable object for the specified tool +.I string +to the environment through which the method was called. + +Additional keyword arguments are passed to the tool's +.B generate() +method. + +.ES +env.Tool('gcc') +env.Tool('opengl', toolpath = ['build/tools']) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI Value( value ", [" built_value ]) +.TP +.RI env.Value( value ", [" built_value ]) +Returns a Node object representing the specified Python value. Value +Nodes can be used as dependencies of targets. If the result of +calling +.BR str( value ) +changes between SCons runs, any targets depending on +.BR Value( value ) +will be rebuilt. +(This is true even when using timestamps to decide if +files are up-to-date.) +When using timestamp source signatures, Value Nodes' +timestamps are equal to the system time when the Node is created. + +The returned Value Node object has a +.BR write () +method that can be used to "build" a Value Node +by setting a new value. +The optional +.I built_value +argument can be specified +when the Value Node is created +to indicate the Node should already be considered +"built." +There is a corresponding +.BR read () +method that will return the built value of the Node. + +Examples: + +.ES +env = Environment() + +def create(target, source, env): + # A function that will write a 'prefix=$SOURCE' + # string into the file name specified as the + # $TARGET. + f = open(str(target[0]), 'wb') + f.write('prefix=' + source[0].get_contents()) + +# Fetch the prefix= argument, if any, from the command +# line, and use /usr/local as the default. +prefix = ARGUMENTS.get('prefix', '/usr/local') + +# Attach a .Config() builder for the above function action +# to the construction environment. +env['BUILDERS']['Config'] = Builder(action = create) +env.Config(target = 'package-config', source = Value(prefix)) + +def build_value(target, source, env): + # A function that "builds" a Python Value by updating + # the the Python value with the contents of the file + # specified as the source of the Builder call ($SOURCE). + target[0].write(source[0].get_contents()) + +output = env.Value('before') +input = env.Value('after') + +# Attach a .UpdateValue() builder for the above function +# action to the construction environment. +env['BUILDERS']['UpdateValue'] = Builder(action = build_value) +env.UpdateValue(target = Value(output), source = Value(input)) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI VariantDir( variant_dir ", " src_dir ", [" duplicate ]) +.TP +.RI env.VariantDir( variant_dir ", " src_dir ", [" duplicate ]) +Use the +.BR VariantDir () +function to create a copy of your sources in another location: +if a name under +.IR variant_dir +is not found but exists under +.IR src_dir , +the file or directory is copied to +.IR variant_dir . +Target files can be built in a different directory +than the original sources by simply refering to the sources (and targets) +within the variant tree. + +.BR VariantDir () +can be called multiple times with the same +.I src_dir +to set up multiple builds with different options +.RI ( variants ). +The +.I src_dir +location must be in or underneath the SConstruct file's directory, and +.I variant_dir +may not be underneath +.IR src_dir . +'\"TODO: Can the above restrictions be clarified or relaxed? +'\"TODO: The latter restriction is clearly not completely right; +'\"TODO: src_dir = '.' works fine with a build dir under it. + +The default behavior is for +.B scons +to physically duplicate the source files in the variant tree. +Thus, a build performed in the variant tree is guaranteed to be identical +to a build performed in the source tree even if +intermediate source files are generated during the build, +or preprocessors or other scanners search for included files +relative to the source file, +or individual compilers or other invoked tools are hard-coded +to put derived files in the same directory as source files. + +If possible on the platform, +the duplication is performed by linking rather than copying; +see also the +.IR --duplicate +command-line option. +Moreover, only the files needed for the build are duplicated; +files and directories that are not used are not present in +.IR variant_dir . + +Duplicating the source tree may be disabled by setting the +.I duplicate +argument to 0 (zero). +This will cause +.B scons +to invoke Builders using the path names of source files in +.I src_dir +and the path names of derived files within +.IR variant_dir . +This is always more efficient than +.IR duplicate =1, +and is usually safe for most builds +(but see above for cases that may cause problems). + +Note that +.BR VariantDir () +works most naturally with a subsidiary SConscript file. +However, you would then call the subsidiary SConscript file +not in the source directory, but in the +.I variant_dir , +regardless of the value of +.IR duplicate . +This is how you tell +.B scons +which variant of a source tree to build: + +.ES +# run src/SConscript in two variant directories +VariantDir('build/variant1', 'src') +SConscript('build/variant1/SConscript') +VariantDir('build/variant2', 'src') +SConscript('build/variant2/SConscript') +.EE + +.IP +See also the +.BR SConscript () +function, described above, +for another way to specify a variant directory +in conjunction with calling a subsidiary SConscript file. + +Examples: + +.ES +# use names in the build directory, not the source directory +VariantDir('build', 'src', duplicate=0) +Program('build/prog', 'build/source.c') +.EE + +.ES +# this builds both the source and docs in a separate subtree +VariantDir('build', '.', duplicate=0) +SConscript(dirs=['build/src','build/doc']) +.EE + +.ES +# same as previous example, but only uses SConscript +SConscript(dirs='src', variant_dir='build/src', duplicate=0) +SConscript(dirs='doc', variant_dir='build/doc', duplicate=0) +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +.RI WhereIs( program ", [" path ", " pathext ", " reject ]) +.TP +.RI env.WhereIs( program ", [" path ", " pathext ", " reject ]) + +Searches for the specified executable +.I program, +returning the full path name to the program +if it is found, +and returning None if not. +Searches the specified +.I path, +the value of the calling environment's PATH +(env['ENV']['PATH']), +or the user's current external PATH +(os.environ['PATH']) +by default. +On Windows systems, searches for executable +programs with any of the file extensions +listed in the specified +.I pathext, +the calling environment's PATHEXT +(env['ENV']['PATHEXT']) +or the user's current PATHEXT +(os.environ['PATHEXT']) +by default. +Will not select any +path name or names +in the specified +.I reject +list, if any. + +.SS SConscript Variables +In addition to the global functions and methods, +.B scons +supports a number of Python variables +that can be used in SConscript files +to affect how you want the build to be performed. +These variables may be accessed from custom Python modules that you +import into an SConscript file by adding the following +to the Python module: + +.ES +from SCons.Script import * +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +ARGLIST +A list +.IR keyword = value +arguments specified on the command line. +Each element in the list is a tuple +containing the +.RI ( keyword , value ) +of the argument. +The separate +.I keyword +and +.I value +elements of the tuple +can be accessed by +subscripting for element +.B [0] +and +.B [1] +of the tuple, respectively. + +Example: + +.ES +print "first keyword, value =", ARGLIST[0][0], ARGLIST[0][1] +print "second keyword, value =", ARGLIST[1][0], ARGLIST[1][1] +third_tuple = ARGLIST[2] +print "third keyword, value =", third_tuple[0], third_tuple[1] +for key, value in ARGLIST: + # process key and value +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +ARGUMENTS +A dictionary of all the +.IR keyword = value +arguments specified on the command line. +The dictionary is not in order, +and if a given keyword has +more than one value assigned to it +on the command line, +the last (right-most) value is +the one in the +.B ARGUMENTS +dictionary. + +Example: + +.ES +if ARGUMENTS.get('debug', 0): + env = Environment(CCFLAGS = '-g') +else: + env = Environment() +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +BUILD_TARGETS +A list of the targets which +.B scons +will actually try to build, +regardless of whether they were specified on +the command line or via the +.BR Default () +function or method. +The elements of this list may be strings +.I or +nodes, so you should run the list through the Python +.B str +function to make sure any Node path names +are converted to strings. + +Because this list may be taken from the +list of targets specified using the +.BR Default () +function or method, +the contents of the list may change +on each successive call to +.BR Default (). +See the +.B DEFAULT_TARGETS +list, below, +for additional information. + +Example: + +.ES +if 'foo' in BUILD_TARGETS: + print "Don't forget to test the `foo' program!" +if 'special/program' in BUILD_TARGETS: + SConscript('special') +.EE +.IP +Note that the +.B BUILD_TARGETS +list only contains targets expected listed +on the command line or via calls to the +.BR Default () +function or method. +It does +.I not +contain all dependent targets that will be built as +a result of making the sure the explicitly-specified +targets are up to date. + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +COMMAND_LINE_TARGETS +A list of the targets explicitly specified on +the command line. +If there are no targets specified on the command line, +the list is empty. +This can be used, for example, +to take specific actions only +when a certain target or targets +is explicitly being built. + +Example: + +.ES +if 'foo' in COMMAND_LINE_TARGETS: + print "Don't forget to test the `foo' program!" +if 'special/program' in COMMAND_LINE_TARGETS: + SConscript('special') +.EE + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.TP +DEFAULT_TARGETS +A list of the target +.I nodes +that have been specified using the +.BR Default () +function or method. +The elements of the list are nodes, +so you need to run them through the Python +.B str +function to get at the path name for each Node. + +Example: + +.ES +print str(DEFAULT_TARGETS[0]) +if 'foo' in map(str, DEFAULT_TARGETS): + print "Don't forget to test the `foo' program!" +.EE +.IP +The contents of the +.B DEFAULT_TARGETS +list change on on each successive call to the +.BR Default () +function: + +.ES +print map(str, DEFAULT_TARGETS) # originally [] +Default('foo') +print map(str, DEFAULT_TARGETS) # now a node ['foo'] +Default('bar') +print map(str, DEFAULT_TARGETS) # now a node ['foo', 'bar'] +Default(None) +print map(str, DEFAULT_TARGETS) # back to [] +.EE +.IP +Consequently, be sure to use +.B DEFAULT_TARGETS +only after you've made all of your +.BR Default () +calls, +or else simply be careful of the order +of these statements in your SConscript files +so that you don't look for a specific +default target before it's actually been added to the list. + +.SS Construction Variables +.\" XXX From Gary Ruben, 23 April 2002: +.\" I think it would be good to have an example with each construction +.\" variable description in the documentation. +.\" eg. +.\" CC The C compiler +.\" Example: env["CC"] = "c68x" +.\" Default: env["CC"] = "cc" +.\" +.\" CCCOM The command line ... +.\" Example: +.\" To generate the compiler line c68x -ps -qq -mr -o $TARGET $SOURCES +.\" env["CC"] = "c68x" +.\" env["CFLAGS"] = "-ps -qq -mr" +.\" env["CCCOM"] = "$CC $CFLAGS -o $TARGET $SOURCES +.\" Default: +.\" (I dunno what this is ;-) +A construction environment has an associated dictionary of +.I construction variables +that are used by built-in or user-supplied build rules. +Construction variables must follow the same rules for +Python identifiers: +the initial character must be an underscore or letter, +followed by any number of underscores, letters, or digits. + +A number of useful construction variables are automatically defined by +scons for each supported platform, and additional construction variables +can be defined by the user. The following is a list of the automatically +defined construction variables: + +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +'\" BEGIN GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS +'\" +'\" The descriptions below of the various SCons construction variables +'\" are generated from the .xml files that live next to the various +'\" Python modules in the build enginer library. If you're reading +'\" this [gnt]roff file with an eye towards patching this man page, +'\" you can still submit a diff against this text, but it will have to +'\" be translated to a diff against the underlying .xml file before the +'\" patch is actually accepted. If you do that yourself, it will make +'\" it easier to integrate the patch. +'\" +'\" BEGIN GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +.so variables.man +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" +'\" END GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS +'\" +'\" The descriptions above of the various SCons construction variables +'\" are generated from the .xml files that live next to the various +'\" Python modules in the build enginer library. If you're reading +'\" this [gnt]roff file with an eye towards patching this man page, +'\" you can still submit a diff against this text, but it will have to +'\" be translated to a diff against the underlying .xml file before the +'\" patch is actually accepted. If you do that yourself, it will make +'\" it easier to integrate the patch. +'\" +'\" END GENERATED CONSTRUCTION VARIABLE DESCRIPTIONS +'\""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +.LP +Construction variables can be retrieved and set using the +.B Dictionary +method of the construction environment: + +.ES +dict = env.Dictionary() +dict["CC"] = "cc" +.EE + +or using the [] operator: + +.ES +env["CC"] = "cc" +.EE + +Construction variables can also be passed to the construction environment +constructor: + +.ES +env = Environment(CC="cc") +.EE + +or when copying a construction environment using the +.B Clone +method: + +.ES +env2 = env.Clone(CC="cl.exe") +.EE + +.SS Configure Contexts + +.B scons +supports +.I configure contexts, +an integrated mechanism similar to the +various AC_CHECK macros in GNU autoconf +for testing for the existence of C header +files, libraries, etc. +In contrast to autoconf, +.B scons +does not maintain an explicit cache of the tested values, +but uses its normal dependency tracking to keep the checked values +up to date. However, users may override this behaviour with the +.B --config +command line option. + +The following methods can be used to perform checks: + +.TP +.RI Configure( env ", [" custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help]) +.TP +.RI env.Configure([ custom_tests ", " conf_dir ", " log_file ", " config_h ", " clean ", " help]) +This creates a configure context, which can be used to perform checks. +.I env +specifies the environment for building the tests. +This environment may be modified when performing checks. +.I custom_tests +is a dictionary containing custom tests. +See also the section about custom tests below. +By default, no custom tests are added to the configure context. +.I conf_dir +specifies a directory where the test cases are built. +Note that this directory is not used for building +normal targets. +The default value is the directory +#/.sconf_temp. +.I log_file +specifies a file which collects the output from commands +that are executed to check for the existence of header files, libraries, etc. +The default is the file #/config.log. +If you are using the +.BR VariantDir () +method, +you may want to specify a subdirectory under your variant directory. +.I config_h +specifies a C header file where the results of tests +will be written, e.g. #define HAVE_STDIO_H, #define HAVE_LIBM, etc. +The default is to not write a +.B config.h +file. +You can specify the same +.B config.h +file in multiple calls to Configure, +in which case +.B scons +will concatenate all results in the specified file. +Note that SCons +uses its normal dependency checking +to decide if it's necessary to rebuild +the specified +.I config_h +file. +This means that the file is not necessarily re-built each +time scons is run, +but is only rebuilt if its contents will have changed +and some target that depends on the +.I config_h +file is being built. + +The optional +.B clean +and +.B help +arguments can be used to suppress execution of the configuration +tests when the +.B -c/--clean +or +.B -H/-h/--help +options are used, respectively. +The default behavior is always to execute +configure context tests, +since the results of the tests may +affect the list of targets to be cleaned +or the help text. +If the configure tests do not affect these, +then you may add the +.B clean=False +or +.B help=False +arguments +(or both) +to avoid unnecessary test execution. + +.EE +A created +.B Configure +instance has the following associated methods: + +.TP +.RI SConf.Finish( context ) +.TP +.IR sconf .Finish() +This method should be called after configuration is done. +It returns the environment as modified +by the configuration checks performed. +After this method is called, no further checks can be performed +with this configuration context. +However, you can create a new +.RI Configure +context to perform additional checks. +Only one context should be active at a time. + +The following Checks are predefined. +(This list will likely grow larger as time +goes by and developers contribute new useful tests.) + +.TP +.RI SConf.CheckHeader( context ", " header ", [" include_quotes ", " language ]) +.TP +.IR sconf .CheckHeader( header ", [" include_quotes ", " language ]) +Checks if +.I header +is usable in the specified language. +.I header +may be a list, +in which case the last item in the list +is the header file to be checked, +and the previous list items are +header files whose +.B #include +lines should precede the +header line being checked for. +The optional argument +.I include_quotes +must be +a two character string, where the first character denotes the opening +quote and the second character denotes the closing quote. +By default, both characters are " (double quote). +The optional argument +.I language +should be either +.B C +or +.B C++ +and selects the compiler to be used for the check. +Returns 1 on success and 0 on failure. + +.TP +.RI SConf.CheckCHeader( context ", " header ", [" include_quotes ]) +.TP +.IR sconf .CheckCHeader( header ", [" include_quotes ]) +This is a wrapper around +.B SConf.CheckHeader +which checks if +.I header +is usable in the C language. +.I header +may be a list, +in which case the last item in the list +is the header file to be checked, +and the previous list items are +header files whose +.B #include +lines should precede the +header line being checked for. +The optional argument +.I include_quotes +must be +a two character string, where the first character denotes the opening +quote and the second character denotes the closing quote (both default +to \N'34'). +Returns 1 on success and 0 on failure. + +.TP +.RI SConf.CheckCXXHeader( context ", " header ", [" include_quotes ]) +.TP +.IR sconf .CheckCXXHeader( header ", [" include_quotes ]) +This is a wrapper around +.B SConf.CheckHeader +which checks if +.I header +is usable in the C++ language. +.I header +may be a list, +in which case the last item in the list +is the header file to be checked, +and the previous list items are +header files whose +.B #include +lines should precede the +header line being checked for. +The optional argument +.I include_quotes +must be +a two character string, where the first character denotes the opening +quote and the second character denotes the closing quote (both default +to \N'34'). +Returns 1 on success and 0 on failure. + +.TP +.RI SConf.CheckFunc( context, ", " function_name ", [" header ", " language ]) +.TP +.IR sconf .CheckFunc( function_name ", [" header ", " language ]) +Checks if the specified +C or C++ function is available. +.I function_name +is the name of the function to check for. +The optional +.I header +argument is a string +that will be +placed at the top +of the test file +that will be compiled +to check if the function exists; +the default is: +.ES +#ifdef __cplusplus +extern "C" +#endif +char function_name(); +.EE +The optional +.I language +argument should be +.B C +or +.B C++ +and selects the compiler to be used for the check; +the default is "C". + +.TP +.RI SConf.CheckLib( context ", [" library ", " symbol ", " header ", " language ", " autoadd=1 ]) +.TP +.IR sconf .CheckLib([ library ", " symbol ", " header ", " language ", " autoadd=1 ]) +Checks if +.I library +provides +.IR symbol . +If the value of +.I autoadd +is 1 and the library provides the specified +.IR symbol , +appends the library to the LIBS construction environment variable. +.I library +may also be None (the default), +in which case +.I symbol +is checked with the current LIBS variable, +or a list of library names, +in which case each library in the list +will be checked for +.IR symbol . +If +.I symbol +is not set or is +.BR None , +then +.BR SConf.CheckLib () +just checks if +you can link against the specified +.IR library . +The optional +.I language +argument should be +.B C +or +.B C++ +and selects the compiler to be used for the check; +the default is "C". +The default value for +.I autoadd +is 1. +This method returns 1 on success and 0 on error. + +.TP +.RI SConf.CheckLibWithHeader( context ", " library ", " header ", " language ", [" call ", " autoadd ]) +.TP +.IR sconf .CheckLibWithHeader( library ", " header ", " language ", [" call ", " autoadd ]) + +In contrast to the +.RI SConf.CheckLib +call, this call provides a more sophisticated way to check against libraries. +Again, +.I library +specifies the library or a list of libraries to check. +.I header +specifies a header to check for. +.I header +may be a list, +in which case the last item in the list +is the header file to be checked, +and the previous list items are +header files whose +.B #include +lines should precede the +header line being checked for. +.I language +may be one of 'C','c','CXX','cxx','C++' and 'c++'. +.I call +can be any valid expression (with a trailing ';'). +If +.I call +is not set, +the default simply checks that you +can link against the specified +.IR library . +.I autoadd +specifies whether to add the library to the environment (only if the check +succeeds). This method returns 1 on success and 0 on error. + +.TP +.RI SConf.CheckType( context ", " type_name ", [" includes ", " language ]) +.TP +.IR sconf .CheckType( type_name ", [" includes ", " language ]) +Checks for the existence of a type defined by +.BR typedef . +.I type_name +specifies the typedef name to check for. +.I includes +is a string containing one or more +.B #include +lines that will be inserted into the program +that will be run to test for the existence of the type. +The optional +.I language +argument should be +.B C +or +.B C++ +and selects the compiler to be used for the check; +the default is "C". +Example: +.ES +sconf.CheckType('foo_type', '#include "my_types.h"', 'C++') +.EE + +.TP +.RI Configure.CheckCC( self ) +Checks whether the C compiler (as defined by the CC construction variable) works +by trying to compile a small source file. + +By default, SCons only detects if there is a program with the correct name, not +if it is a functioning compiler. + +This uses the exact same command than the one used by the object builder for C +source file, so it can be used to detect if a particular compiler flag works or +not. + +.TP +.RI Configure.CheckCXX( self ) +Checks whether the C++ compiler (as defined by the CXX construction variable) +works by trying to compile a small source file. By default, SCons only detects +if there is a program with the correct name, not if it is a functioning compiler. + +This uses the exact same command than the one used by the object builder for +CXX source files, so it can be used to detect if a particular compiler flag +works or not. + +.TP +.RI Configure.CheckSHCC( self ) +Checks whether the C compiler (as defined by the SHCC construction variable) works +by trying to compile a small source file. By default, SCons only detects if +there is a program with the correct name, not if it is a functioning compiler. + +This uses the exact same command than the one used by the object builder for C +source file, so it can be used to detect if a particular compiler flag works or +not. This does not check whether the object code can be used to build a shared +library, only that the compilation (not link) succeeds. + +.TP +.RI Configure.CheckSHCXX( self ) +Checks whether the C++ compiler (as defined by the SHCXX construction variable) +works by trying to compile a small source file. By default, SCons only detects +if there is a program with the correct name, not if it is a functioning compiler. + +This uses the exact same command than the one used by the object builder for +CXX source files, so it can be used to detect if a particular compiler flag +works or not. This does not check whether the object code can be used to build +a shared library, only that the compilation (not link) succeeds. + +.EE +Example of a typical Configure usage: + +.ES +env = Environment() +conf = Configure( env ) +if not conf.CheckCHeader( 'math.h' ): + print 'We really need math.h!' + Exit(1) +if conf.CheckLibWithHeader( 'qt', 'qapp.h', 'c++', + 'QApplication qapp(0,0);' ): + # do stuff for qt - usage, e.g. + conf.env.Append( CPPFLAGS = '-DWITH_QT' ) +env = conf.Finish() +.EE + +.TP +.RI SConf.CheckTypeSize( context ", " type_name ", [" header ", " language ", " expect ]) +.TP +.IR sconf .CheckTypeSize( type_name ", [" header ", " language ", " expect ]) +Checks for the size of a type defined by +.BR typedef . +.I type_name +specifies the typedef name to check for. +The optional +.I header +argument is a string +that will be +placed at the top +of the test file +that will be compiled +to check if the function exists; +the default is empty. +The optional +.I language +argument should be +.B C +or +.B C++ +and selects the compiler to be used for the check; +the default is "C". +The optional +.I expect +argument should be an integer. +If this argument is used, +the function will only check whether the type +given in type_name has the expected size (in bytes). +For example, +.B "CheckTypeSize('short', expect = 2)" +will return success only if short is two bytes. + +.ES +.EE + +.TP +.RI SConf.CheckDeclaration( context ", " symbol ", [" includes ", " language ]) +.TP +.IR sconf .CheckDeclaration( symbol ", [" includes ", " language ]) +Checks if the specified +.I symbol +is declared. +.I includes +is a string containing one or more +.B #include +lines that will be inserted into the program +that will be run to test for the existence of the type. +The optional +.I language +argument should be +.B C +or +.B C++ +and selects the compiler to be used for the check; +the default is "C". + +.TP +.RI SConf.Define( context ", " symbol ", [" value ", " comment ]) +.TP +.IR sconf .Define( symbol ", [" value ", " comment ]) +This function does not check for anything, but defines a +preprocessor symbol that will be added to the configuration header file. +It is the equivalent of AC_DEFINE, +and defines the symbol +.I name +with the optional +.B value +and the optional comment +.BR comment . + +.IP +Examples: + +.ES +env = Environment() +conf = Configure( env ) + +# Puts the following line in the config header file: +# #define A_SYMBOL +conf.Define('A_SYMBOL') + +# Puts the following line in the config header file: +# #define A_SYMBOL 1 +conf.Define('A_SYMBOL', 1) +.EE + +.IP +Be careful about quoting string values, though: + +.ES +env = Environment() +conf = Configure( env ) + +# Puts the following line in the config header file: +# #define A_SYMBOL YA +conf.Define('A_SYMBOL', "YA") + +# Puts the following line in the config header file: +# #define A_SYMBOL "YA" +conf.Define('A_SYMBOL', '"YA"') +.EE + +.IP +For comment: + +.ES +env = Environment() +conf = Configure( env ) + +# Puts the following lines in the config header file: +# /* Set to 1 if you have a symbol */ +# #define A_SYMBOL 1 +conf.Define('A_SYMBOL', 1, 'Set to 1 if you have a symbol') +.EE + +.EE +You can define your own custom checks. +in addition to the predefined checks. +These are passed in a dictionary to the Configure function. +This dictionary maps the names of the checks +to user defined Python callables +(either Python functions or class instances implementing the +.I __call__ +method). +The first argument of the call is always a +.I CheckContext +instance followed by the arguments, +which must be supplied by the user of the check. +These CheckContext instances define the following methods: + +.TP +.RI CheckContext.Message( self ", " text ) + +Usually called before the check is started. +.I text +will be displayed to the user, e.g. 'Checking for library X...' + +.TP +.RI CheckContext.Result( self, ", " res ) + +Usually called after the check is done. +.I res +can be either an integer or a string. In the former case, 'yes' (res != 0) +or 'no' (res == 0) is displayed to the user, in the latter case the +given string is displayed. + +.TP +.RI CheckContext.TryCompile( self ", " text ", " extension ) +Checks if a file with the specified +.I extension +(e.g. '.c') containing +.I text +can be compiled using the environment's +.B Object +builder. Returns 1 on success and 0 on failure. + +.TP +.RI CheckContext.TryLink( self ", " text ", " extension ) +Checks, if a file with the specified +.I extension +(e.g. '.c') containing +.I text +can be compiled using the environment's +.B Program +builder. Returns 1 on success and 0 on failure. + +.TP +.RI CheckContext.TryRun( self ", " text ", " extension ) +Checks, if a file with the specified +.I extension +(e.g. '.c') containing +.I text +can be compiled using the environment's +.B Program +builder. On success, the program is run. If the program +executes successfully +(that is, its return status is 0), +a tuple +.I (1, outputStr) +is returned, where +.I outputStr +is the standard output of the +program. +If the program fails execution +(its return status is non-zero), +then (0, '') is returned. + +.TP +.RI CheckContext.TryAction( self ", " action ", [" text ", " extension ]) +Checks if the specified +.I action +with an optional source file (contents +.I text +, extension +.I extension += '' +) can be executed. +.I action +may be anything which can be converted to a +.B scons +.RI Action. +On success, +.I (1, outputStr) +is returned, where +.I outputStr +is the content of the target file. +On failure +.I (0, '') +is returned. + +.TP +.RI CheckContext.TryBuild( self ", " builder ", [" text ", " extension ]) +Low level implementation for testing specific builds; +the methods above are based on this method. +Given the Builder instance +.I builder +and the optional +.I text +of a source file with optional +.IR extension , +this method returns 1 on success and 0 on failure. In addition, +.I self.lastTarget +is set to the build target node, if the build was successful. + +.EE +Example for implementing and using custom tests: + +.ES +def CheckQt(context, qtdir): + context.Message( 'Checking for qt ...' ) + lastLIBS = context.env['LIBS'] + lastLIBPATH = context.env['LIBPATH'] + lastCPPPATH= context.env['CPPPATH'] + context.env.Append(LIBS = 'qt', LIBPATH = qtdir + '/lib', CPPPATH = qtdir + '/include' ) + ret = context.TryLink(""" +#include +int main(int argc, char **argv) { + QApplication qapp(argc, argv); + return 0; +} +""") + if not ret: + context.env.Replace(LIBS = lastLIBS, LIBPATH=lastLIBPATH, CPPPATH=lastCPPPATH) + context.Result( ret ) + return ret + +env = Environment() +conf = Configure( env, custom_tests = { 'CheckQt' : CheckQt } ) +if not conf.CheckQt('/usr/lib/qt'): + print 'We really need qt!' + Exit(1) +env = conf.Finish() +.EE + +.SS Command-Line Construction Variables + +Often when building software, +some variables must be specified at build time. +For example, libraries needed for the build may be in non-standard +locations, or site-specific compiler options may need to be passed to the +compiler. +.B scons +provides a +.B Variables +object to support overriding construction variables +on the command line: +.ES +$ scons VARIABLE=foo +.EE +The variable values can also be specified in a text-based SConscript file. +To create a Variables object, call the Variables() function: + +.TP +.RI Variables([ files "], [" args ]) +This creates a Variables object that will read construction variables from +the file or list of filenames specified in +.IR files . +If no files are specified, +or the +.I files +argument is +.BR None , +then no files will be read. +The optional argument +.I args +is a dictionary of +values that will override anything read from the specified files; +it is primarily intended to be passed the +.B ARGUMENTS +dictionary that holds variables +specified on the command line. +Example: + +.ES +vars = Variables('custom.py') +vars = Variables('overrides.py', ARGUMENTS) +vars = Variables(None, {FOO:'expansion', BAR:7}) +.EE + +Variables objects have the following methods: + +.TP +.RI Add( key ", [" help ", " default ", " validator ", " converter ]) +This adds a customizable construction variable to the Variables object. +.I key +is the name of the variable. +.I help +is the help text for the variable. +.I default +is the default value of the variable; +if the default value is +.B None +and there is no explicit value specified, +the construction variable will +.I not +be added to the construction environment. +.I validator +is called to validate the value of the variable, and should take three +arguments: key, value, and environment. +The recommended way to handle an invalid value is +to raise an exception (see example below). +.I converter +is called to convert the value before putting it in the environment, and +should take either a value, or the value and environment, as parameters. +The +.I converter +must return a value, +which will be converted into a string +before being validated by the +.I validator +(if any) +and then added to the environment. + +Examples: + +.ES +vars.Add('CC', 'The C compiler') + +def validate_color(key, val, env): + if not val in ['red', 'blue', 'yellow']: + raise "Invalid color value '%s'" % val +vars.Add('COLOR', validator=valid_color) +.EE + +.TP +.RI AddVariables( list ) +A wrapper script that adds +multiple customizable construction variables +to a Variables object. +.I list +is a list of tuple or list objects +that contain the arguments +for an individual call to the +.B Add +method. + +.ES +opt.AddVariables( + ('debug', '', 0), + ('CC', 'The C compiler'), + ('VALIDATE', 'An option for testing validation', + 'notset', validator, None), + ) +.EE + +.TP +.RI Update( env ", [" args ]) +This updates a construction environment +.I env +with the customized construction variables. +Any specified variables that are +.I not +configured for the Variables object +will be saved and may be +retrieved with the +.BR UnknownVariables () +method, below. + +Normally this method is not called directly, +but is called indirectly by passing the Variables object to +the Environment() function: + +.ES +env = Environment(variables=vars) +.EE + +.IP +The text file(s) that were specified +when the Variables object was created +are executed as Python scripts, +and the values of (global) Python variables set in the file +are added to the construction environment. + +Example: + +.ES +CC = 'my_cc' +.EE + +.TP +.RI UnknownVariables( ) +Returns a dictionary containing any +variables that were specified +either in the files or the dictionary +with which the Variables object was initialized, +but for which the Variables object was +not configured. + +.ES +env = Environment(variables=vars) +for key, value in vars.UnknownVariables(): + print "unknown variable: %s=%s" % (key, value) +.EE + +.TP +.RI Save( filename ", " env ) +This saves the currently set variables into a script file named +.I filename +that can be used on the next invocation to automatically load the current +settings. This method combined with the Variables method can be used to +support caching of variables between runs. + +.ES +env = Environment() +vars = Variables(['variables.cache', 'custom.py']) +vars.Add(...) +vars.Update(env) +vars.Save('variables.cache', env) +.EE + +.TP +.RI GenerateHelpText( env ", [" sort ]) +This generates help text documenting the customizable construction +variables suitable to passing in to the Help() function. +.I env +is the construction environment that will be used to get the actual values +of customizable variables. Calling with +an optional +.I sort +function +will cause the output to be sorted +by the specified argument. +The specific +.I sort +function +should take two arguments +and return +-1, 0 or 1 +(like the standard Python +.I cmp +function). + +.ES +Help(vars.GenerateHelpText(env)) +Help(vars.GenerateHelpText(env, sort=cmp)) +.EE + +.TP +.RI FormatVariableHelpText( env ", " opt ", " help ", " default ", " actual ) +This method returns a formatted string +containing the printable help text +for one option. +It is normally not called directly, +but is called by the +.IR GenerateHelpText () +method to create the returned help text. +It may be overridden with your own +function that takes the arguments specified above +and returns a string of help text formatted to your liking. +Note that the +.IR GenerateHelpText () +will not put any blank lines or extra +characters in between the entries, +so you must add those characters to the returned +string if you want the entries separated. + +.ES +def my_format(env, opt, help, default, actual): + fmt = "\n%s: default=%s actual=%s (%s)\n" + return fmt % (opt, default. actual, help) +vars.FormatVariableHelpText = my_format +.EE + +To make it more convenient to work with customizable Variables, +.B scons +provides a number of functions +that make it easy to set up +various types of Variables: + +.TP +.RI BoolVariable( key ", " help ", " default ) +Return a tuple of arguments +to set up a Boolean option. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will interpret the values +.BR y , +.BR yes , +.BR t , +.BR true , +.BR 1 , +.B on +and +.B all +as true, +and the values +.BR n , +.BR no , +.BR f , +.BR false , +.BR 0 , +.B off +and +.B none +as false. + +.TP +.RI EnumVariable( key ", " help ", " default ", " allowed_values ", [" map ", " ignorecase ]) +Return a tuple of arguments +to set up an option +whose value may be one +of a specified list of legal enumerated values. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will only support those +values in the +.I allowed_values +list. +The optional +.I map +argument is a dictionary +that can be used to convert +input values into specific legal values +in the +.I allowed_values +list. +If the value of +.I ignore_case +is +.B 0 +(the default), +then the values are case-sensitive. +If the value of +.I ignore_case +is +.BR 1 , +then values will be matched +case-insensitive. +If the value of +.I ignore_case +is +.BR 1 , +then values will be matched +case-insensitive, +and all input values will be +converted to lower case. + +.TP +.RI ListVariable( key ", " help ", " default ", " names ", [", map ]) +Return a tuple of arguments +to set up an option +whose value may be one or more +of a specified list of legal enumerated values. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will only support the values +.BR all , +.BR none , +or the values in the +.I names +list. +More than one value may be specified, +with all values separated by commas. +The default may be a string of +comma-separated default values, +or a list of the default values. +The optional +.I map +argument is a dictionary +that can be used to convert +input values into specific legal values +in the +.I names +list. + +.TP +.RI PackageVariable( key ", " help ", " default ) +Return a tuple of arguments +to set up an option +whose value is a path name +of a package that may be +enabled, disabled or +given an explicit path name. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +The option will support the values +.BR yes , +.BR true , +.BR on , +.BR enable +or +.BR search , +in which case the specified +.I default +will be used, +or the option may be set to an +arbitrary string +(typically the path name to a package +that is being enabled). +The option will also support the values +.BR no , +.BR false , +.BR off +or +.BR disable +to disable use of the specified option. + +.TP +.RI PathVariable( key ", " help ", " default ", [" validator ]) +Return a tuple of arguments +to set up an option +whose value is expected to be a path name. +The option will use +the specified name +.IR key , +have a default value of +.IR default , +and display the specified +.I help +text. +An additional +.I validator +may be specified +that will be called to +verify that the specified path +is acceptable. +SCons supplies the +following ready-made validators: +.BR PathVariable.PathExists +(the default), +which verifies that the specified path exists; +.BR PathVariable.PathIsFile , +which verifies that the specified path is an existing file; +.BR PathVariable.PathIsDir , +which verifies that the specified path is an existing directory; +.BR PathVariable.PathIsDirCreate , +which verifies that the specified path is a directory +and will create the specified directory if the path does not exist; +and +.BR PathVariable.PathAccept , +which simply accepts the specific path name argument without validation, +and which is suitable if you want your users +to be able to specify a directory path that will be +created as part of the build process, for example. +You may supply your own +.I validator +function, +which must take three arguments +.RI ( key , +the name of the variable to be set; +.IR val , +the specified value being checked; +and +.IR env , +the construction environment) +and should raise an exception +if the specified value is not acceptable. + +.RE +These functions make it +convenient to create a number +of variables with consistent behavior +in a single call to the +.B AddVariables +method: + +.ES +vars.AddVariables( + BoolVariable('warnings', 'compilation with -Wall and similiar', 1), + EnumVariable('debug', 'debug output and symbols', 'no' + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + ListVariable('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageVariable('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), + PathVariable('qtdir', 'where the root of Qt is installed', qtdir), + PathVariable('foopath', 'where the foo library is installed', foopath, + PathVariable.PathIsDir), + +) +.EE + +.SS File and Directory Nodes + +The +.IR File () +and +.IR Dir () +functions return +.I File +and +.I Dir +Nodes, respectively. +python objects, respectively. +Those objects have several user-visible attributes +and methods that are often useful: + +.IP path +The build path +of the given +file or directory. +This path is relative to the top-level directory +(where the +.B SConstruct +file is found). +The build path is the same as the source path if +.I variant_dir +is not being used. + +.IP abspath +The absolute build path of the given file or directory. + +.IP srcnode() +The +.IR srcnode () +method +returns another +.I File +or +.I Dir +object representing the +.I source +path of the given +.I File +or +.IR Dir . +The + +.ES +# Get the current build dir's path, relative to top. +Dir('.').path +# Current dir's absolute path +Dir('.').abspath +# Next line is always '.', because it is the top dir's path relative to itself. +Dir('#.').path +File('foo.c').srcnode().path # source path of the given source file. + +# Builders also return File objects: +foo = env.Program('foo.c') +print "foo will be built in %s"%foo.path +.EE + +A +.I Dir +Node or +.I File +Node can also be used to create +file and subdirectory Nodes relative to the generating Node. +A +.I Dir +Node will place the new Nodes within the directory it represents. +A +.I File +node will place the new Nodes within its parent directory +(that is, "beside" the file in question). +If +.I d +is a +.I Dir +(directory) Node and +.I f +is a +.I File +(file) Node, +then these methods are available: + +.TP +.IR d .Dir( name ) +Returns a directory Node for a subdirectory of +.I d +named +.IR name . + +.TP +.IR d .File( name ) +Returns a file Node for a file within +.I d +named +.IR name . + +.TP +.IR d .Entry( name ) +Returns an unresolved Node within +.I d +named +.IR name . + +.TP +.IR f .Dir( name ) +Returns a directory named +.I name +within the parent directory of +.IR f . + +.TP +.IR f .File( name ) +Returns a file named +.I name +within the parent directory of +.IR f . + +.TP +.IR f .Entry( name ) +Returns an unresolved Node named +.I name +within the parent directory of +.IR f . + +.RE +For example: + +.ES +# Get a Node for a file within a directory +incl = Dir('include') +f = incl.File('header.h') + +# Get a Node for a subdirectory within a directory +dist = Dir('project-3.2.1) +src = dist.Dir('src') + +# Get a Node for a file in the same directory +cfile = File('sample.c') +hfile = cfile.File('sample.h') + +# Combined example +docs = Dir('docs') +html = docs.Dir('html') +index = html.File('index.html') +css = index.File('app.css') +.EE + +.SH EXTENDING SCONS +.SS Builder Objects +.B scons +can be extended to build different types of targets +by adding new Builder objects +to a construction environment. +.IR "In general" , +you should only need to add a new Builder object +when you want to build a new type of file or other external target. +If you just want to invoke a different compiler or other tool +to build a Program, Object, Library, or any other +type of output file for which +.B scons +already has an existing Builder, +it is generally much easier to +use those existing Builders +in a construction environment +that sets the appropriate construction variables +(CC, LINK, etc.). + +Builder objects are created +using the +.B Builder +function. +The +.B Builder +function accepts the following arguments: + +.IP action +The command line string used to build the target from the source. +.B action +can also be: +a list of strings representing the command +to be executed and its arguments +(suitable for enclosing white space in an argument), +a dictionary +mapping source file name suffixes to +any combination of command line strings +(if the builder should accept multiple source file extensions), +a Python function; +an Action object +(see the next section); +or a list of any of the above. + +An action function +takes three arguments: +.I source +- a list of source nodes, +.I target +- a list of target nodes, +.I env +- the construction environment. + +.IP prefix +The prefix that will be prepended to the target file name. +This may be specified as a: + +.RS 10 +.HP 6 +* +.IR string , + +.HP 6 +* +.I callable object +- a function or other callable that takes +two arguments (a construction environment and a list of sources) +and returns a prefix, + +.HP 6 +* +.I dictionary +- specifies a mapping from a specific source suffix (of the first +source specified) to a corresponding target prefix. Both the source +suffix and target prefix specifications may use environment variable +substitution, and the target prefix (the 'value' entries in the +dictionary) may also be a callable object. The default target prefix +may be indicated by a dictionary entry with a key value of None. +.RE +.P + +.ES +b = Builder("build_it < $SOURCE > $TARGET" + prefix = "file-") + +def gen_prefix(env, sources): + return "file-" + env['PLATFORM'] + '-' +b = Builder("build_it < $SOURCE > $TARGET", + prefix = gen_prefix) + +b = Builder("build_it < $SOURCE > $TARGET", + suffix = { None: "file-", + "$SRC_SFX_A": gen_prefix }) +.EE + +.IP suffix +The suffix that will be appended to the target file name. +This may be specified in the same manner as the prefix above. +If the suffix is a string, then +.B scons +will append a '.' to the beginning of the suffix if it's not already +there. The string returned by callable object (or obtained from the +dictionary) is untouched and must append its own '.' to the beginning +if one is desired. + +.ES +b = Builder("build_it < $SOURCE > $TARGET" + suffix = "-file") + +def gen_suffix(env, sources): + return "." + env['PLATFORM'] + "-file" +b = Builder("build_it < $SOURCE > $TARGET", + suffix = gen_suffix) + +b = Builder("build_it < $SOURCE > $TARGET", + suffix = { None: ".sfx1", + "$SRC_SFX_A": gen_suffix }) +.EE + +.IP ensure_suffix +When set to any true value, causes +.B scons +to add the target suffix specified by the +.I suffix +keyword to any target strings +that have a different suffix. +(The default behavior is to leave untouched +any target file name that looks like it already has any suffix.) + +.ES +b1 = Builder("build_it < $SOURCE > $TARGET" + suffix = ".out") +b2 = Builder("build_it < $SOURCE > $TARGET" + suffix = ".out", + ensure_suffix) +env = Environment() +env['BUILDERS']['B1'] = b1 +env['BUILDERS']['B2'] = b2 + +# Builds "foo.txt" because ensure_suffix is not set. +env.B1('foo.txt', 'foo.in') + +# Builds "bar.txt.out" because ensure_suffix is set. +env.B2('bar.txt', 'bar.in') +.EE + +.IP src_suffix +The expected source file name suffix. This may be a string or a list +of strings. + +.IP target_scanner +A Scanner object that +will be invoked to find +implicit dependencies for this target file. +This keyword argument should be used +for Scanner objects that find +implicit dependencies +based only on the target file +and the construction environment, +.I not +for implicit +(See the section "Scanner Objects," below, +for information about creating Scanner objects.) + +.IP source_scanner +A Scanner object that +will be invoked to +find implicit dependences in +any source files +used to build this target file. +This is where you would +specify a scanner to +find things like +.B #include +lines in source files. +The pre-built +.B DirScanner +Scanner object may be used to +indicate that this Builder +should scan directory trees +for on-disk changes to files +that +.B scons +does not know about from other Builder or function calls. +(See the section "Scanner Objects," below, +for information about creating your own Scanner objects.) + +.IP target_factory +A factory function that the Builder will use +to turn any targets specified as strings into SCons Nodes. +By default, +SCons assumes that all targets are files. +Other useful target_factory +values include +.BR Dir , +for when a Builder creates a directory target, +and +.BR Entry , +for when a Builder can create either a file +or directory target. + +Example: + +.ES +MakeDirectoryBuilder = Builder(action=my_mkdir, target_factory=Dir) +env = Environment() +env.Append(BUILDERS = {'MakeDirectory':MakeDirectoryBuilder}) +env.MakeDirectory('new_directory', []) +.EE + +.IP +Note that the call to the MakeDirectory Builder +needs to specify an empty source list +to make the string represent the builder's target; +without that, it would assume the argument is the source, +and would try to deduce the target name from it, +which in the absence of an automatically-added prefix or suffix +would lead to a matching target and source name +and a circular dependency. + +.IP source_factory +A factory function that the Builder will use +to turn any sources specified as strings into SCons Nodes. +By default, +SCons assumes that all source are files. +Other useful source_factory +values include +.BR Dir , +for when a Builder uses a directory as a source, +and +.BR Entry , +for when a Builder can use files +or directories (or both) as sources. + +Example: + +.ES +CollectBuilder = Builder(action=my_mkdir, source_factory=Entry) +env = Environment() +env.Append(BUILDERS = {'Collect':CollectBuilder}) +env.Collect('archive', ['directory_name', 'file_name']) +.EE + +.IP emitter +A function or list of functions to manipulate the target and source +lists before dependencies are established +and the target(s) are actually built. +.B emitter +can also be a string containing a construction variable to expand +to an emitter function or list of functions, +or a dictionary mapping source file suffixes +to emitter functions. +(Only the suffix of the first source file +is used to select the actual emitter function +from an emitter dictionary.) + +An emitter function +takes three arguments: +.I source +- a list of source nodes, +.I target +- a list of target nodes, +.I env +- the construction environment. +An emitter must return a tuple containing two lists, +the list of targets to be built by this builder, +and the list of sources for this builder. + +Example: + +.ES +def e(target, source, env): + return (target + ['foo.foo'], source + ['foo.src']) + +# Simple association of an emitter function with a Builder. +b = Builder("my_build < $TARGET > $SOURCE", + emitter = e) + +def e2(target, source, env): + return (target + ['bar.foo'], source + ['bar.src']) + +# Simple association of a list of emitter functions with a Builder. +b = Builder("my_build < $TARGET > $SOURCE", + emitter = [e, e2]) + +# Calling an emitter function through a construction variable. +env = Environment(MY_EMITTER = e) +b = Builder("my_build < $TARGET > $SOURCE", + emitter = '$MY_EMITTER') + +# Calling a list of emitter functions through a construction variable. +env = Environment(EMITTER_LIST = [e, e2]) +b = Builder("my_build < $TARGET > $SOURCE", + emitter = '$EMITTER_LIST') + +# Associating multiple emitters with different file +# suffixes using a dictionary. +def e_suf1(target, source, env): + return (target + ['another_target_file'], source) +def e_suf2(target, source, env): + return (target, source + ['another_source_file']) +b = Builder("my_build < $TARGET > $SOURCE", + emitter = {'.suf1' : e_suf1, + '.suf2' : e_suf2}) +.EE + +.IP multi +Specifies whether this builder is allowed to be called multiple times for +the same target file(s). The default is 0, which means the builder +can not be called multiple times for the same target file(s). Calling a +builder multiple times for the same target simply adds additional source +files to the target; it is not allowed to change the environment associated +with the target, specify addition environment overrides, or associate a different +builder with the target. + +.IP env +A construction environment that can be used +to fetch source code using this Builder. +(Note that this environment is +.I not +used for normal builds of normal target files, +which use the environment that was +used to call the Builder for the target file.) + +.IP generator +A function that returns a list of actions that will be executed to build +the target(s) from the source(s). +The returned action(s) may be +an Action object, or anything that +can be converted into an Action object +(see the next section). + +The generator function +takes four arguments: +.I source +- a list of source nodes, +.I target +- a list of target nodes, +.I env +- the construction environment, +.I for_signature +- a Boolean value that specifies +whether the generator is being called +for generating a build signature +(as opposed to actually executing the command). +Example: + +.ES +def g(source, target, env, for_signature): + return [["gcc", "-c", "-o"] + target + source] + +b = Builder(generator=g) +.EE + +.IP +The +.I generator +and +.I action +arguments must not both be used for the same Builder. + +.IP src_builder +Specifies a builder to use when a source file name suffix does not match +any of the suffixes of the builder. Using this argument produces a +multi-stage builder. + +.IP single_source +Specifies that this builder expects exactly one source file per call. Giving +more than one source files without target files results in implicitely calling +the builder multiple times (once for each source given). Giving multiple +source files together with target files results in a UserError exception. + +.RE +.IP +The +.I generator +and +.I action +arguments must not both be used for the same Builder. + +.IP source_ext_match +When the specified +.I action +argument is a dictionary, +the default behavior when a builder is passed +multiple source files is to make sure that the +extensions of all the source files match. +If it is legal for this builder to be +called with a list of source files with different extensions, +this check can be suppressed by setting +.B source_ext_match +to +.B None +or some other non-true value. +When +.B source_ext_match +is disable, +.B scons +will use the suffix of the first specified +source file to select the appropriate action from the +.I action +dictionary. + +In the following example, +the setting of +.B source_ext_match +prevents +.B scons +from exiting with an error +due to the mismatched suffixes of +.B foo.in +and +.BR foo.extra . + +.ES +b = Builder(action={'.in' : 'build $SOURCES > $TARGET'}, + source_ext_match = None) + +env = Environment(BUILDERS = {'MyBuild':b}) +env.MyBuild('foo.out', ['foo.in', 'foo.extra']) +.EE + +.IP env +A construction environment that can be used +to fetch source code using this Builder. +(Note that this environment is +.I not +used for normal builds of normal target files, +which use the environment that was +used to call the Builder for the target file.) + +.ES +b = Builder(action="build < $SOURCE > $TARGET") +env = Environment(BUILDERS = {'MyBuild' : b}) +env.MyBuild('foo.out', 'foo.in', my_arg = 'xyzzy') +.EE + +.IP chdir +A directory from which scons +will execute the +action(s) specified +for this Builder. +If the +.B chdir +argument is +a string or a directory Node, +scons will change to the specified directory. +If the +.B chdir +is not a string or Node +and is non-zero, +then scons will change to the +target file's directory. + +Note that scons will +.I not +automatically modify +its expansion of +construction variables like +.B $TARGET +and +.B $SOURCE +when using the chdir +keyword argument--that is, +the expanded file names +will still be relative to +the top-level SConstruct directory, +and consequently incorrect +relative to the chdir directory. +Builders created using chdir keyword argument, +will need to use construction variable +expansions like +.B ${TARGET.file} +and +.B ${SOURCE.file} +to use just the filename portion of the +targets and source. + +.ES +b = Builder(action="build < ${SOURCE.file} > ${TARGET.file}", + chdir=1) +env = Environment(BUILDERS = {'MyBuild' : b}) +env.MyBuild('sub/dir/foo.out', 'sub/dir/foo.in') +.EE + +.B WARNING: +Python only keeps one current directory +location for all of the threads. +This means that use of the +.B chdir +argument +will +.I not +work with the SCons +.B -j +option, +because individual worker threads spawned +by SCons interfere with each other +when they start changing directory. + +.RE +Any additional keyword arguments supplied +when a Builder object is created +(that is, when the Builder() function is called) +will be set in the executing construction +environment when the Builder object is called. +The canonical example here would be +to set a construction variable to +the repository of a source code system. + +Any additional keyword arguments supplied +when a Builder +.I object +is called +will only be associated with the target +created by that particular Builder call +(and any other files built as a +result of the call). + +These extra keyword arguments are passed to the +following functions: +command generator functions, +function Actions, +and emitter functions. + +.SS Action Objects + +The +.BR Builder () +function will turn its +.B action +keyword argument into an appropriate +internal Action object. +You can also explicity create Action objects +using the +.BR Action () +global function, +which can then be passed to the +.BR Builder () +function. +This can be used to configure +an Action object more flexibly, +or it may simply be more efficient +than letting each separate Builder object +create a separate Action +when multiple +Builder objects need to do the same thing. + +The +.BR Action () +global function +returns an appropriate object for the action +represented by the type of the first argument: + +.IP Action +If the first argument is already an Action object, +the object is simply returned. + +.IP String +If the first argument is a string, +a command-line Action is returned. +Note that the command-line string +may be preceded by an +.B @ +(at-sign) +to suppress printing of the specified command line, +or by a +.B \- +(hyphen) +to ignore the exit status from the specified command: + +.ES +Action('$CC -c -o $TARGET $SOURCES') + +# Doesn't print the line being executed. +Action('@build $TARGET $SOURCES') + +# Ignores return value +Action('-build $TARGET $SOURCES') +.EE +.\" XXX From Gary Ruben, 23 April 2002: +.\" What would be useful is a discussion of how you execute command +.\" shell commands ie. what is the process used to spawn the shell, pass +.\" environment variables to it etc., whether there is one shell per +.\" environment or one per command etc. It might help to look at the Gnu +.\" make documentation to see what they think is important to discuss about +.\" a build system. I'm sure you can do a better job of organising the +.\" documentation than they have :-) + +.IP List +If the first argument is a list, +then a list of Action objects is returned. +An Action object is created as necessary +for each element in the list. +If an element +.I within +the list is itself a list, +the internal list is the +command and arguments to be executed via +the command line. +This allows white space to be enclosed +in an argument by defining +a command in a list within a list: + +.ES +Action([['cc', '-c', '-DWHITE SPACE', '-o', '$TARGET', '$SOURCES']]) +.EE + +.IP Function +If the first argument is a Python function, +a function Action is returned. +The Python function must take three keyword arguments, +.B target +(a Node object representing the target file), +.B source +(a Node object representing the source file) +and +.B env +(the construction environment +used for building the target file). +The +.B target +and +.B source +arguments may be lists of Node objects if there is +more than one target file or source file. +The actual target and source file name(s) may +be retrieved from their Node objects +via the built-in Python str() function: + +.ES +target_file_name = str(target) +source_file_names = map(lambda x: str(x), source) +.EE +.IP +The function should return +.B 0 +or +.B None +to indicate a successful build of the target file(s). +The function may raise an exception +or return a non-zero exit status +to indicate an unsuccessful build. + +.ES +def build_it(target = None, source = None, env = None): + # build the target from the source + return 0 + +a = Action(build_it) +.EE + +If the action argument is not one of the above, +None is returned. +.PP + +The second argument is optional and is used to define the output +which is printed when the Action is actually performed. +In the absence of this parameter, +or if it's an empty string, +a default output depending on the type of the action is used. +For example, a command-line action will print the executed command. +The argument must be either a Python function or a string. + +In the first case, +it's a function that returns a string to be printed +to describe the action being executed. +The function may also be specified by the +.IR strfunction = +keyword argument. +Like a function to build a file, +this function must take three keyword arguments: +.B target +(a Node object representing the target file), +.B source +(a Node object representing the source file) +and +.BR env +(a construction environment). +The +.B target +and +.B source +arguments may be lists of Node objects if there is +more than one target file or source file. + +In the second case, you provide the string itself. +The string may also be specified by the +.IR cmdstr = +keyword argument. +The string typically contains variables, notably +$TARGET(S) and $SOURCE(S), or consists of just a single +variable, which is optionally defined somewhere else. +SCons itself heavily uses the latter variant. + +Examples: + +.ES +def build_it(target, source, env): + # build the target from the source + return 0 + +def string_it(target, source, env): + return "building '%s' from '%s'" % (target[0], source[0]) + +# Use a positional argument. +f = Action(build_it, string_it) +s = Action(build_it, "building '$TARGET' from '$SOURCE'") + +# Alternatively, use a keyword argument. +f = Action(build_it, strfunction=string_it) +s = Action(build_it, cmdstr="building '$TARGET' from '$SOURCE'") + +# You can provide a configurable variable. +l = Action(build_it, '$STRINGIT') +.EE + +The third and succeeding arguments, if present, +may either be a construction variable or a list of construction variables +whose values will be included in the signature of the Action +when deciding whether a target should be rebuilt because the action changed. +The variables may also be specified by a +.IR varlist = +keyword parameter; +if both are present, they are combined. +This is necessary whenever you want a target to be rebuilt +when a specific construction variable changes. +This is not often needed for a string action, +as the expanded variables will normally be part of the command line, +but may be needed if a Python function action uses +the value of a construction variable when generating the command line. + +.ES +def build_it(target, source, env): + # build the target from the 'XXX' construction variable + open(target[0], 'w').write(env['XXX']) + return 0 + +# Use positional arguments. +a = Action(build_it, '$STRINGIT', ['XXX']) + +# Alternatively, use a keyword argument. +a = Action(build_it, varlist=['XXX']) +.EE + +The +.BR Action () +global function +can be passed the following +optional keyword arguments +to modify the Action object's behavior: + +.IP +.B chdir +The +.B chdir +keyword argument specifies that +scons will execute the action +after changing to the specified directory. +If the +.B chdir +argument is +a string or a directory Node, +scons will change to the specified directory. +If the +.B chdir +argument +is not a string or Node +and is non-zero, +then scons will change to the +target file's directory. + +Note that scons will +.I not +automatically modify +its expansion of +construction variables like +.B $TARGET +and +.B $SOURCE +when using the chdir +keyword argument--that is, +the expanded file names +will still be relative to +the top-level SConstruct directory, +and consequently incorrect +relative to the chdir directory. +Builders created using chdir keyword argument, +will need to use construction variable +expansions like +.B ${TARGET.file} +and +.B ${SOURCE.file} +to use just the filename portion of the +targets and source. + +.ES +a = Action("build < ${SOURCE.file} > ${TARGET.file}", + chdir=1) +.EE + +.IP +.B exitstatfunc +The +.BR Action () +global function +also takes an +.B exitstatfunc +keyword argument +which specifies a function +that is passed the exit status +(or return value) +from the specified action +and can return an arbitrary +or modified value. +This can be used, for example, +to specify that an Action object's +return value should be ignored +under special conditions +and SCons should, therefore, +consider that the action always suceeds: + +.ES +def always_succeed(s): + # Always return 0, which indicates success. + return 0 +a = Action("build < ${SOURCE.file} > ${TARGET.file}", + exitstatfunc=always_succeed) +.EE + +.IP +.B batch_key +The +.B batch_key +keyword argument can be used +to specify that the Action can create multiple target files +by processing multiple independent source files simultaneously. +(The canonical example is "batch compilation" +of multiple object files +by passing multiple source files +to a single invocation of a compiler +such as Microsoft's Visual C / C++ compiler.) +If the +.B batch_key +argument is any non-False, non-callable Python value, +the configured Action object will cause +.B scons +to collect all targets built with the Action object +and configured with the same construction environment +into single invocations of the Action object's +command line or function. +Command lines will typically want to use the +.BR CHANGED_SOURCES +construction variable +(and possibly +.BR CHANGED_TARGETS +as well) +to only pass to the command line those sources that +have actually changed since their targets were built. + +Example: + +.ES +a = Action('build $CHANGED_SOURCES', batch_key=True) +.EE + +The +.B batch_key +argument may also be +a callable function +that returns a key that +will be used to identify different +"batches" of target files to be collected +for batch building. +A +.B batch_key +function must take the following arguments: + +.IP action +The action object. + +.IP env +The construction environment +configured for the target. + +.IP target +The list of targets for a particular configured action. + +.IP source +The list of source for a particular configured action. + +The returned key should typically +be a tuple of values derived from the arguments, +using any appropriate logic to decide +how multiple invocations should be batched. +For example, a +.B batch_key +function may decide to return +the value of a specific construction +variable from the +.B env +argument +which will cause +.B scons +to batch-build targets +with matching values of that variable, +or perhaps return the +.BR id () +of the entire construction environment, +in which case +.B scons +will batch-build +all targets configured with the same construction environment. +Returning +.B None +indicates that +the particular target should +.I not +be part of any batched build, +but instead will be built +by a separate invocation of action's +command or function. +Example: + +.ES +def batch_key(action, env, target, source): + tdir = target[0].dir + if tdir.name == 'special': + # Don't batch-build any target + # in the special/ subdirectory. + return None + return (id(action), id(env), tdir) +a = Action('build $CHANGED_SOURCES', batch_key=batch_key) +.EE + +.SS Miscellaneous Action Functions + +.B scons +supplies a number of functions +that arrange for various common +file and directory manipulations +to be performed. +These are similar in concept to "tasks" in the +Ant build tool, +although the implementation is slightly different. +These functions do not actually +perform the specified action +at the time the function is called, +but instead return an Action object +that can be executed at the +appropriate time. +(In Object-Oriented terminology, +these are actually +Action +.I Factory +functions +that return Action objects.) + +In practice, +there are two natural ways +that these +Action Functions +are intended to be used. + +First, +if you need +to perform the action +at the time the SConscript +file is being read, +you can use the +.B Execute +global function to do so: +.ES +Execute(Touch('file')) +.EE + +Second, +you can use these functions +to supply Actions in a list +for use by the +.B Command +method. +This can allow you to +perform more complicated +sequences of file manipulation +without relying +on platform-specific +external commands: +that +.ES +env = Environment(TMPBUILD = '/tmp/builddir') +env.Command('foo.out', 'foo.in', + [Mkdir('$TMPBUILD'), + Copy('$TMPBUILD', '${SOURCE.dir}'), + "cd $TMPBUILD && make", + Delete('$TMPBUILD')]) +.EE + +.TP +.RI Chmod( dest ", " mode ) +Returns an Action object that +changes the permissions on the specified +.I dest +file or directory to the specified +.IR mode . +Examples: + +.ES +Execute(Chmod('file', 0755)) + +env.Command('foo.out', 'foo.in', + [Copy('$TARGET', '$SOURCE'), + Chmod('$TARGET', 0755)]) +.EE + +.TP +.RI Copy( dest ", " src ) +Returns an Action object +that will copy the +.I src +source file or directory to the +.I dest +destination file or directory. +Examples: + +.ES +Execute(Copy('foo.output', 'foo.input')) + +env.Command('bar.out', 'bar.in', + Copy('$TARGET', '$SOURCE')) +.EE + +.TP +.RI Delete( entry ", [" must_exist ]) +Returns an Action that +deletes the specified +.IR entry , +which may be a file or a directory tree. +If a directory is specified, +the entire directory tree +will be removed. +If the +.I must_exist +flag is set, +then a Python error will be thrown +if the specified entry does not exist; +the default is +.BR must_exist=0 , +that is, the Action will silently do nothing +if the entry does not exist. +Examples: + +.ES +Execute(Delete('/tmp/buildroot')) + +env.Command('foo.out', 'foo.in', + [Delete('${TARGET.dir}'), + MyBuildAction]) + +Execute(Delete('file_that_must_exist', must_exist=1)) +.EE + +.TP +.RI Mkdir( dir ) +Returns an Action +that creates the specified +directory +.I dir . +Examples: + +.ES +Execute(Mkdir('/tmp/outputdir')) + +env.Command('foo.out', 'foo.in', + [Mkdir('/tmp/builddir'), + Copy('/tmp/builddir/foo.in', '$SOURCE'), + "cd /tmp/builddir && make", + Copy('$TARGET', '/tmp/builddir/foo.out')]) +.EE + +.TP +.RI Move( dest ", " src ) +Returns an Action +that moves the specified +.I src +file or directory to +the specified +.I dest +file or directory. +Examples: + +.ES +Execute(Move('file.destination', 'file.source')) + +env.Command('output_file', 'input_file', + [MyBuildAction, + Move('$TARGET', 'file_created_by_MyBuildAction')]) +.EE + +.TP +.RI Touch( file ) +Returns an Action +that updates the modification time +on the specified +.IR file . +Examples: + +.ES +Execute(Touch('file_to_be_touched')) + +env.Command('marker', 'input_file', + [MyBuildAction, + Touch('$TARGET')]) +.EE + +.SS Variable Substitution + +Before executing a command, +.B scons +performs construction variable interpolation on the strings that make up +the command line of builders. +Variables are introduced by a +.B $ +prefix. +Besides construction variables, scons provides the following +variables for each command execution: + +.IP CHANGED_SOURCES +The file names of all sources of the build command +that have changed since the target was last built. + +.IP CHANGED_TARGETS +The file names of all targets that would be built +from sources that have changed since the target was last built. + +.IP SOURCE +The file name of the source of the build command, +or the file name of the first source +if multiple sources are being built. + +.IP SOURCES +The file names of the sources of the build command. + +.IP TARGET +The file name of the target being built, +or the file name of the first target +if multiple targets are being built. + +.IP TARGETS +The file names of all targets being built. + +.IP UNCHANGED_SOURCES +The file names of all sources of the build command +that have +.I not +changed since the target was last built. + +.IP UNCHANGED_TARGETS +The file names of all targets that would be built +from sources that have +.I not +changed since the target was last built. + +(Note that the above variables are reserved +and may not be set in a construction environment.) + +.LP +For example, given the construction variable CC='cc', targets=['foo'], and +sources=['foo.c', 'bar.c']: + +.ES +action='$CC -c -o $TARGET $SOURCES' +.EE + +would produce the command line: + +.ES +cc -c -o foo foo.c bar.c +.EE + +Variable names may be surrounded by curly braces ({}) +to separate the name from the trailing characters. +Within the curly braces, a variable name may have +a Python slice subscript appended to select one +or more items from a list. +In the previous example, the string: + +.ES +${SOURCES[1]} +.EE + +would produce: + +.ES +bar.c +.EE + +Additionally, a variable name may +have the following special +modifiers appended within the enclosing curly braces +to modify the interpolated string: + +.IP base +The base path of the file name, +including the directory path +but excluding any suffix. + +.IP dir +The name of the directory in which the file exists. + +.IP file +The file name, +minus any directory portion. + +.IP filebase +Just the basename of the file, +minus any suffix +and minus the directory. + +.IP suffix +Just the file suffix. + +.IP abspath +The absolute path name of the file. + +.IP posix +The POSIX form of the path, +with directories separated by +.B / +(forward slashes) +not backslashes. +This is sometimes necessary on Windows systems +when a path references a file on other (POSIX) systems. + +.IP srcpath +The directory and file name to the source file linked to this file through +.BR VariantDir (). +If this file isn't linked, +it just returns the directory and filename unchanged. + +.IP srcdir +The directory containing the source file linked to this file through +.BR VariantDir (). +If this file isn't linked, +it just returns the directory part of the filename. + +.IP rsrcpath +The directory and file name to the source file linked to this file through +.BR VariantDir (). +If the file does not exist locally but exists in a Repository, +the path in the Repository is returned. +If this file isn't linked, it just returns the +directory and filename unchanged. + +.IP rsrcdir +The Repository directory containing the source file linked to this file through +.BR VariantDir (). +If this file isn't linked, +it just returns the directory part of the filename. + +.LP +For example, the specified target will +expand as follows for the corresponding modifiers: + +.ES +$TARGET => sub/dir/file.x +${TARGET.base} => sub/dir/file +${TARGET.dir} => sub/dir +${TARGET.file} => file.x +${TARGET.filebase} => file +${TARGET.suffix} => .x +${TARGET.abspath} => /top/dir/sub/dir/file.x + +SConscript('src/SConscript', variant_dir='sub/dir') +$SOURCE => sub/dir/file.x +${SOURCE.srcpath} => src/file.x +${SOURCE.srcdir} => src + +Repository('/usr/repository') +$SOURCE => sub/dir/file.x +${SOURCE.rsrcpath} => /usr/repository/src/file.x +${SOURCE.rsrcdir} => /usr/repository/src +.EE + +Note that curly braces braces may also be used +to enclose arbitrary Python code to be evaluated. +(In fact, this is how the above modifiers are substituted, +they are simply attributes of the Python objects +that represent TARGET, SOURCES, etc.) +See the section "Python Code Substitution," below, +for more thorough examples of +how this can be used. + +Lastly, a variable name +may be a callable Python function +associated with a +construction variable in the environment. +The function should +take four arguments: +.I target +- a list of target nodes, +.I source +- a list of source nodes, +.I env +- the construction environment, +.I for_signature +- a Boolean value that specifies +whether the function is being called +for generating a build signature. +SCons will insert whatever +the called function returns +into the expanded string: + +.ES +def foo(target, source, env, for_signature): + return "bar" + +# Will expand $BAR to "bar baz" +env=Environment(FOO=foo, BAR="$FOO baz") +.EE + +You can use this feature to pass arguments to a +Python function by creating a callable class +that stores one or more arguments in an object, +and then uses them when the +.B __call__() +method is called. +Note that in this case, +the entire variable expansion must +be enclosed by curly braces +so that the arguments will +be associated with the +instantiation of the class: + +.ES +class foo: + def __init__(self, arg): + self.arg = arg + + def __call__(self, target, source, env, for_signature): + return self.arg + " bar" + +# Will expand $BAR to "my argument bar baz" +env=Environment(FOO=foo, BAR="${FOO('my argument')} baz") +.EE + +.LP +The special pseudo-variables +.B "$(" +and +.B "$)" +may be used to surround parts of a command line +that may change +.I without +causing a rebuild--that is, +which are not included in the signature +of target files built with this command. +All text between +.B "$(" +and +.B "$)" +will be removed from the command line +before it is added to file signatures, +and the +.B "$(" +and +.B "$)" +will be removed before the command is executed. +For example, the command line: + +.ES +echo Last build occurred $( $TODAY $). > $TARGET +.EE + +.LP +would execute the command: + +.ES +echo Last build occurred $TODAY. > $TARGET +.EE + +.LP +but the command signature added to any target files would be: + +.ES +echo Last build occurred . > $TARGET +.EE + +.SS Python Code Substitution + +Any python code within +.BR "${" - "}" +pairs gets evaluated by python 'eval', with the python globals set to +the current environment's set of construction variables. +So in the following case: +.ES +env['COND'] = 0 +env.Command('foo.out', 'foo.in', + '''echo ${COND==1 and 'FOO' or 'BAR'} > $TARGET''') +.EE +the command executed will be either +.ES +echo FOO > foo.out +.EE +or +.ES +echo BAR > foo.out +.EE +according to the current value of env['COND'] when the command is +executed. The evaluation occurs when the target is being +built, not when the SConscript is being read. So if env['COND'] is changed +later in the SConscript, the final value will be used. + +Here's a more interesting example. Note that all of COND, FOO, and +BAR are environment variables, and their values are substituted into +the final command. FOO is a list, so its elements are interpolated +separated by spaces. + +.ES +env=Environment() +env['COND'] = 0 +env['FOO'] = ['foo1', 'foo2'] +env['BAR'] = 'barbar' +env.Command('foo.out', 'foo.in', + 'echo ${COND==1 and FOO or BAR} > $TARGET') + +# Will execute this: +# echo foo1 foo2 > foo.out +.EE + +SCons uses the following rules when converting construction variables into +command lines: + +.IP String +When the value is a string it is interpreted as a space delimited list of +command line arguments. + +.IP List +When the value is a list it is interpreted as a list of command line +arguments. Each element of the list is converted to a string. + +.IP Other +Anything that is not a list or string is converted to a string and +interpreted as a single command line argument. + +.IP Newline +Newline characters (\\n) delimit lines. The newline parsing is done after +all other parsing, so it is not possible for arguments (e.g. file names) to +contain embedded newline characters. This limitation will likely go away in +a future version of SCons. + +.SS Scanner Objects + +You can use the +.B Scanner +function to define +objects to scan +new file types for implicit dependencies. +Scanner accepts the following arguments: + +.IP function +This can be either: +1) a Python function that will process +the Node (file) +and return a list of strings (file names) +representing the implicit +dependencies found in the contents; +or: +2) a dictionary that maps keys +(typically the file suffix, but see below for more discussion) +to other Scanners that should be called. + +If the argument is actually a Python function, +the function must take three or four arguments: + + def scanner_function(node, env, path): + + def scanner_function(node, env, path, arg=None): + +The +.B node +argument is the internal +SCons node representing the file. +Use +.B str(node) +to fetch the name of the file, and +.B node.get_contents() +to fetch contents of the file. +Note that the file is +.I not +guaranteed to exist before the scanner is called, +so the scanner function should check that +if there's any chance that the scanned file +might not exist +(for example, if it's built from other files). + +The +.B env +argument is the construction environment for the scan. +Fetch values from it using the +.B env.Dictionary() +method. + +The +.B path +argument is a tuple (or list) +of directories that can be searched +for files. +This will usually be the tuple returned by the +.B path_function +argument (see below). + +The +.B arg +argument is the argument supplied +when the scanner was created, if any. + +.IP name +The name of the Scanner. +This is mainly used +to identify the Scanner internally. + +.IP argument +An optional argument that, if specified, +will be passed to the scanner function +(described above) +and the path function +(specified below). + +.IP skeys +An optional list that can be used to +determine which scanner should be used for +a given Node. +In the usual case of scanning for file names, +this argument will be a list of suffixes +for the different file types that this +Scanner knows how to scan. +If the argument is a string, +then it will be expanded +into a list by the current environment. + +.IP path_function +A Python function that takes four or five arguments: +a construction environment, +a Node for the directory containing +the SConscript file in which +the first target was defined, +a list of target nodes, +a list of source nodes, +and an optional argument supplied +when the scanner was created. +The +.B path_function +returns a tuple of directories +that can be searched for files to be returned +by this Scanner object. +(Note that the +.BR FindPathDirs () +function can be used to return a ready-made +.B path_function +for a given construction variable name, +instead of having to write your own function from scratch.) + +.IP node_class +The class of Node that should be returned +by this Scanner object. +Any strings or other objects returned +by the scanner function +that are not of this class +will be run through the +.B node_factory +function. + +.IP node_factory +A Python function that will take a string +or other object +and turn it into the appropriate class of Node +to be returned by this Scanner object. + +.IP scan_check +An optional Python function that takes two arguments, +a Node (file) and a construction environment, +and returns whether the +Node should, in fact, +be scanned for dependencies. +This check can be used to eliminate unnecessary +calls to the scanner function when, +for example, the underlying file +represented by a Node does not yet exist. + +.IP recursive +An optional flag that +specifies whether this scanner should be re-invoked +on the dependency files returned by the scanner. +When this flag is not set, +the Node subsystem will +only invoke the scanner on the file being scanned, +and not (for example) also on the files +specified by the #include lines +in the file being scanned. +.I recursive +may be a callable function, +in which case it will be called with a list of +Nodes found and +should return a list of Nodes +that should be scanned recursively; +this can be used to select a specific subset of +Nodes for additional scanning. + +Note that +.B scons +has a global +.B SourceFileScanner +object that is used by +the +.BR Object (), +.BR SharedObject (), +and +.BR StaticObject () +builders to decide +which scanner should be used +for different file extensions. +You can using the +.BR SourceFileScanner.add_scanner () +method to add your own Scanner object +to the +.B scons +infrastructure +that builds target programs or +libraries from a list of +source files of different types: + +.ES +def xyz_scan(node, env, path): + contents = node.get_text_contents() + # Scan the contents and return the included files. + +XYZScanner = Scanner(xyz_scan) + +SourceFileScanner.add_scanner('.xyx', XYZScanner) + +env.Program('my_prog', ['file1.c', 'file2.f', 'file3.xyz']) +.EE + +.SH SYSTEM-SPECIFIC BEHAVIOR +SCons and its configuration files are very portable, +due largely to its implementation in Python. +There are, however, a few portability +issues waiting to trap the unwary. +.SS .C file suffix +SCons handles the upper-case +.B .C +file suffix differently, +depending on the capabilities of +the underlying system. +On a case-sensitive system +such as Linux or UNIX, +SCons treats a file with a +.B .C +suffix as a C++ source file. +On a case-insensitive system +such as Windows, +SCons treats a file with a +.B .C +suffix as a C source file. +.SS .F file suffix +SCons handles the upper-case +.B .F +file suffix differently, +depending on the capabilities of +the underlying system. +On a case-sensitive system +such as Linux or UNIX, +SCons treats a file with a +.B .F +suffix as a Fortran source file +that is to be first run through +the standard C preprocessor. +On a case-insensitive system +such as Windows, +SCons treats a file with a +.B .F +suffix as a Fortran source file that should +.I not +be run through the C preprocessor. +.SS Windows: Cygwin Tools and Cygwin Python vs. Windows Pythons +Cygwin supplies a set of tools and utilities +that let users work on a +Windows system using a more POSIX-like environment. +The Cygwin tools, including Cygwin Python, +do this, in part, +by sharing an ability to interpret UNIX-like path names. +For example, the Cygwin tools +will internally translate a Cygwin path name +like /cygdrive/c/mydir +to an equivalent Windows pathname +of C:/mydir (equivalent to C:\\mydir). + +Versions of Python +that are built for native Windows execution, +such as the python.org and ActiveState versions, +do not have the Cygwin path name semantics. +This means that using a native Windows version of Python +to build compiled programs using Cygwin tools +(such as gcc, bison, and flex) +may yield unpredictable results. +"Mixing and matching" in this way +can be made to work, +but it requires careful attention to the use of path names +in your SConscript files. + +In practice, users can sidestep +the issue by adopting the following rules: +When using gcc, +use the Cygwin-supplied Python interpreter +to run SCons; +when using Microsoft Visual C/C++ +(or some other Windows compiler) +use the python.org or ActiveState version of Python +to run SCons. +.SS Windows: scons.bat file +On Windows systems, +SCons is executed via a wrapper +.B scons.bat +file. +This has (at least) two ramifications: + +First, Windows command-line users +that want to use variable assignment +on the command line +may have to put double quotes +around the assignments: + +.ES +scons "FOO=BAR" "BAZ=BLEH" +.EE + +Second, the Cygwin shell does not +recognize this file as being the same +as an +.B scons +command issued at the command-line prompt. +You can work around this either by +executing +.B scons.bat +from the Cygwin command line, +or by creating a wrapper shell +script named +.B scons . + +.SS MinGW + +The MinGW bin directory must be in your PATH environment variable or the +PATH variable under the ENV construction variable for SCons +to detect and use the MinGW tools. When running under the native Windows +Python interpreter, SCons will prefer the MinGW tools over the Cygwin +tools, if they are both installed, regardless of the order of the bin +directories in the PATH variable. If you have both MSVC and MinGW +installed and you want to use MinGW instead of MSVC, +then you must explictly tell SCons to use MinGW by passing + +.ES +tools=['mingw'] +.EE + +to the Environment() function, because SCons will prefer the MSVC tools +over the MinGW tools. + +.SH EXAMPLES + +To help you get started using SCons, +this section contains a brief overview of some common tasks. + +.SS Basic Compilation From a Single Source File + +.ES +env = Environment() +env.Program(target = 'foo', source = 'foo.c') +.EE + +Note: Build the file by specifying +the target as an argument +("scons foo" or "scons foo.exe"). +or by specifying a dot ("scons ."). + +.SS Basic Compilation From Multiple Source Files + +.ES +env = Environment() +env.Program(target = 'foo', source = Split('f1.c f2.c f3.c')) +.EE + +.SS Setting a Compilation Flag + +.ES +env = Environment(CCFLAGS = '-g') +env.Program(target = 'foo', source = 'foo.c') +.EE + +.SS Search The Local Directory For .h Files + +Note: You do +.I not +need to set CCFLAGS to specify -I options by hand. +SCons will construct the right -I options from CPPPATH. + +.ES +env = Environment(CPPPATH = ['.']) +env.Program(target = 'foo', source = 'foo.c') +.EE + +.SS Search Multiple Directories For .h Files + +.ES +env = Environment(CPPPATH = ['include1', 'include2']) +env.Program(target = 'foo', source = 'foo.c') +.EE + +.SS Building a Static Library + +.ES +env = Environment() +env.StaticLibrary(target = 'foo', source = Split('l1.c l2.c')) +env.StaticLibrary(target = 'bar', source = ['l3.c', 'l4.c']) +.EE + +.SS Building a Shared Library + +.ES +env = Environment() +env.SharedLibrary(target = 'foo', source = ['l5.c', 'l6.c']) +env.SharedLibrary(target = 'bar', source = Split('l7.c l8.c')) +.EE + +.SS Linking a Local Library Into a Program + +.ES +env = Environment(LIBS = 'mylib', LIBPATH = ['.']) +env.Library(target = 'mylib', source = Split('l1.c l2.c')) +env.Program(target = 'prog', source = ['p1.c', 'p2.c']) +.EE + +.SS Defining Your Own Builder Object + +Notice that when you invoke the Builder, +you can leave off the target file suffix, +and SCons will add it automatically. + +.ES +bld = Builder(action = 'pdftex < $SOURCES > $TARGET' + suffix = '.pdf', + src_suffix = '.tex') +env = Environment(BUILDERS = {'PDFBuilder' : bld}) +env.PDFBuilder(target = 'foo.pdf', source = 'foo.tex') + +# The following creates "bar.pdf" from "bar.tex" +env.PDFBuilder(target = 'bar', source = 'bar') +.EE + +Note also that the above initialization +overwrites the default Builder objects, +so the Environment created above +can not be used call Builders like env.Program(), +env.Object(), env.StaticLibrary(), etc. + +.SS Adding Your Own Builder Object to an Environment + +.ES +bld = Builder(action = 'pdftex < $SOURCES > $TARGET' + suffix = '.pdf', + src_suffix = '.tex') +env = Environment() +env.Append(BUILDERS = {'PDFBuilder' : bld}) +env.PDFBuilder(target = 'foo.pdf', source = 'foo.tex') +env.Program(target = 'bar', source = 'bar.c') +.EE + +You also can use other Pythonic techniques to add +to the BUILDERS construction variable, such as: + +.ES +env = Environment() +env['BUILDERS]['PDFBuilder'] = bld +.EE + +.SS Defining Your Own Scanner Object + +The following example shows an extremely simple scanner (the +.BR kfile_scan () +function) +that doesn't use a search path at all +and simply returns the +file names present on any +.B include +lines in the scanned file. +This would implicitly assume that all included +files live in the top-level directory: + +.ES +import re + +'\" Note: the \\ in the following are for the benefit of nroff/troff, +'\" not inappropriate doubled escape characters within the r'' raw string. +include_re = re.compile(r'^include\\s+(\\S+)$', re.M) + +def kfile_scan(node, env, path, arg): + contents = node.get_text_contents() + includes = include_re.findall(contents) + return includes + +kscan = Scanner(name = 'kfile', + function = kfile_scan, + argument = None, + skeys = ['.k']) +scanners = Environment().Dictionary('SCANNERS') +env = Environment(SCANNERS = scanners + [kscan]) + +env.Command('foo', 'foo.k', 'kprocess < $SOURCES > $TARGET') + +bar_in = File('bar.in') +env.Command('bar', bar_in, 'kprocess $SOURCES > $TARGET') +bar_in.target_scanner = kscan +.EE + +Here is a similar but more complete example that searches +a path of directories +(specified as the +.B MYPATH +construction variable) +for files that actually exist: + +.ES +include_re = re.compile(r'^include\\s+(\\S+)$', re.M) + +def my_scan(node, env, path, arg): + contents = node.get_text_contents() + includes = include_re.findall(contents) + if includes == []: + return [] + results = [] + for inc in includes: + for dir in path: + file = dir + os.sep + inc + if os.path.exists(file): + results.append(file) + break + return results + +scanner = Scanner(name = 'myscanner', + function = my_scan, + argument = None, + skeys = ['.x'], + path_function = FindPathDirs('MYPATH'), + ) +scanners = Environment().Dictionary('SCANNERS') +env = Environment(SCANNERS = scanners + [scanner]) +.EE + +The +.BR FindPathDirs () +function used in the previous example returns a function +(actually a callable Python object) +that will return a list of directories +specified in the +.B $MYPATH +construction variable. +If you need to customize how the search path is derived, +you would provide your own +.B path_function +argument when creating the Scanner object, +as follows: + +.ES +# MYPATH is a list of directories to search for files in +def pf(env, dir, target, source, arg): + top_dir = Dir('#').abspath + results = [] + if env.has_key('MYPATH'): + for p in env['MYPATH']: + results.append(top_dir + os.sep + p) + return results + +scanner = Scanner(name = 'myscanner', + function = my_scan, + argument = None, + skeys = ['.x'], + path_function = pf, + ) +.EE + +.SS Creating a Hierarchical Build + +Notice that the file names specified in a subdirectory's +SConscript +file are relative to that subdirectory. + +.ES +SConstruct: + + env = Environment() + env.Program(target = 'foo', source = 'foo.c') + + SConscript('sub/SConscript') + +sub/SConscript: + + env = Environment() + # Builds sub/foo from sub/foo.c + env.Program(target = 'foo', source = 'foo.c') + + SConscript('dir/SConscript') + +sub/dir/SConscript: + + env = Environment() + # Builds sub/dir/foo from sub/dir/foo.c + env.Program(target = 'foo', source = 'foo.c') +.EE + +.SS Sharing Variables Between SConscript Files + +You must explicitly Export() and Import() variables that +you want to share between SConscript files. + +.ES +SConstruct: + + env = Environment() + env.Program(target = 'foo', source = 'foo.c') + + Export("env") + SConscript('subdirectory/SConscript') + +subdirectory/SConscript: + + Import("env") + env.Program(target = 'foo', source = 'foo.c') +.EE + +.SS Building Multiple Variants From the Same Source + +Use the variant_dir keyword argument to +the SConscript function to establish +one or more separate variant build directory trees +for a given source directory: + +.ES +SConstruct: + + cppdefines = ['FOO'] + Export("cppdefines") + SConscript('src/SConscript', variant_dir='foo') + + cppdefines = ['BAR'] + Export("cppdefines") + SConscript('src/SConscript', variant_dir='bar') + +src/SConscript: + + Import("cppdefines") + env = Environment(CPPDEFINES = cppdefines) + env.Program(target = 'src', source = 'src.c') +.EE + +Note the use of the Export() method +to set the "cppdefines" variable to a different +value each time we call the SConscript function. + +.SS Hierarchical Build of Two Libraries Linked With a Program + +.ES +SConstruct: + + env = Environment(LIBPATH = ['#libA', '#libB']) + Export('env') + SConscript('libA/SConscript') + SConscript('libB/SConscript') + SConscript('Main/SConscript') + +libA/SConscript: + + Import('env') + env.Library('a', Split('a1.c a2.c a3.c')) + +libB/SConscript: + + Import('env') + env.Library('b', Split('b1.c b2.c b3.c')) + +Main/SConscript: + + Import('env') + e = env.Copy(LIBS = ['a', 'b']) + e.Program('foo', Split('m1.c m2.c m3.c')) +.EE + +The '#' in the LIBPATH directories specify that they're relative to the +top-level directory, so they don't turn into "Main/libA" when they're +used in Main/SConscript. + +Specifying only 'a' and 'b' for the library names +allows SCons to append the appropriate library +prefix and suffix for the current platform +(for example, 'liba.a' on POSIX systems, +\&'a.lib' on Windows). + +.SS Customizing construction variables from the command line. + +The following would allow the C compiler to be specified on the command +line or in the file custom.py. + +.ES +vars = Variables('custom.py') +vars.Add('CC', 'The C compiler.') +env = Environment(variables=vars) +Help(vars.GenerateHelpText(env)) +.EE + +The user could specify the C compiler on the command line: + +.ES +scons "CC=my_cc" +.EE + +or in the custom.py file: + +.ES +CC = 'my_cc' +.EE + +or get documentation on the options: + +.ES +$ scons -h + +CC: The C compiler. + default: None + actual: cc + +.EE + +.SS Using Microsoft Visual C++ precompiled headers + +Since windows.h includes everything and the kitchen sink, it can take quite +some time to compile it over and over again for a bunch of object files, so +Microsoft provides a mechanism to compile a set of headers once and then +include the previously compiled headers in any object file. This +technology is called precompiled headers. The general recipe is to create a +file named "StdAfx.cpp" that includes a single header named "StdAfx.h", and +then include every header you want to precompile in "StdAfx.h", and finally +include "StdAfx.h" as the first header in all the source files you are +compiling to object files. For example: + +StdAfx.h: +.ES +#include +#include +.EE + +StdAfx.cpp: +.ES +#include +.EE + +Foo.cpp: +.ES +#include + +/* do some stuff */ +.EE + +Bar.cpp: +.ES +#include + +/* do some other stuff */ +.EE + +SConstruct: +.ES +env=Environment() +env['PCHSTOP'] = 'StdAfx.h' +env['PCH'] = env.PCH('StdAfx.cpp')[0] +env.Program('MyApp', ['Foo.cpp', 'Bar.cpp']) +.EE + +For more information see the document for the PCH builder, and the PCH and +PCHSTOP construction variables. To learn about the details of precompiled +headers consult the MSDN documention for /Yc, /Yu, and /Yp. + +.SS Using Microsoft Visual C++ external debugging information + +Since including debugging information in programs and shared libraries can +cause their size to increase significantly, Microsoft provides a mechanism +for including the debugging information in an external file called a PDB +file. SCons supports PDB files through the PDB construction +variable. + +SConstruct: +.ES +env=Environment() +env['PDB'] = 'MyApp.pdb' +env.Program('MyApp', ['Foo.cpp', 'Bar.cpp']) +.EE + +For more information see the document for the PDB construction variable. + +.SH ENVIRONMENT + +.IP SCONS_LIB_DIR +Specifies the directory that contains the SCons Python module directory +(e.g. /home/aroach/scons-src-0.01/src/engine). + +.IP SCONSFLAGS +A string of options that will be used by scons in addition to those passed +on the command line. + +.SH "SEE ALSO" +.B scons +User Manual, +.B scons +Design Document, +.B scons +source code. + +.SH AUTHORS +Steven Knight +.br +Anthony Roach + diff --git a/doc/man/sconsign.1 b/doc/man/sconsign.1 new file mode 100644 index 0000000..6694784 --- /dev/null +++ b/doc/man/sconsign.1 @@ -0,0 +1,208 @@ +.\" Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +.\" +.\" Permission is hereby granted, free of charge, to any person obtaining +.\" a copy of this software and associated documentation files (the +.\" "Software"), to deal in the Software without restriction, including +.\" without limitation the rights to use, copy, modify, merge, publish, +.\" distribute, sublicense, and/or sell copies of the Software, and to +.\" permit persons to whom the Software is furnished to do so, subject to +.\" the following conditions: +.\" +.\" The above copyright notice and this permission notice shall be included +.\" in all copies or substantial portions of the Software. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +.\" KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +.\" WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +.\" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +.\" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +.\" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +.\" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +.\" +.\" doc/man/sconsign.1 4577 2009/12/27 19:44:43 scons +.\" +.\" ES - Example Start - indents and turns off line fill +.de ES +.RS +.nf +.. +.\" EE - Example End - ends indent and turns line fill back on +.de EE +.RE +.fi +.. +.TH SCONSIGN 1 "December 2009" +.SH NAME +sconsign \- print SCons .sconsign file information +.SH SYNOPSIS +.B sconsign +[ +.IR options ... +] +.IR file +[ ... ] +.SH DESCRIPTION + +The +.B sconsign +command +displays the contents of one or more +.B .sconsign +files specified by the user. + +By default, +.B sconsign +dumps the entire contents of the +specified file(s). +Each entry is printed in the following format: + + file: signature timestamp length + implicit_dependency_1: signature timestamp length + implicit_dependency_2: signature timestamp length + action_signature [action string] + +.B None +is printed +in place of any missing timestamp, bsig, or csig +values for +any entry +or any of its dependencies. +If the entry has no implicit dependencies, +or no build action, +the lines are simply omitted. + +By default, +.B sconsign +assumes that any +.I file +arguments that end with a +.B .dbm +suffix contains +signature entries for +more than one directory +(that is, +was specified by the +.B SConsignFile () +function). +Any +.I file +argument that does not end in +.B .dbm +is assumed to be a traditional +.B .sconsign +file containing the signature entries +for a single directory. +An explicit format +may be specified using the +.B -f +or +.B --file= +options. + +.SH OPTIONS + +Various options control what information is printed +and the format: + +.TP +-a, --act, --action +Prints the build action information +for all entries or the specified entries. + +.TP +-c, --csig +Prints the content signature (csig) information +for all entries or the specified entries. + +.TP +-d DIRECTORY, --dir=DIRECTORY +When the signatures are being +read from a +.B .dbm +file, or the +.B -f dbm +or +.B --format=dbm +options are used, +prints information about +only the signatures +for entries in the specified +.IR DIRECTORY . + +.TP +-e ENTRY, --entry=ENTRY +Prints information about only the specified +.IR ENTRY . +Multiple -e options may be used, +in which case information about each +.I ENTRY +is printed in the order in which the +options are specified on the command line. + +.TP +-f FORMAT, --format=FORMAT +The file(s) to be printed +are in the specified +.IR FORMAT . +Legal values are +.B dbm +(the DBM format used +when the +.BR SConsignFile () +function is used) +or +.B sconsign +(the default format +used for an individual +.B .sconsign +file in each directory). + +.TP +-h, --help +Prints a help message and exits. + +.TP +-i, --implicit +Prints the list of cached implicit dependencies +for all entries or the the specified entries. + +.TP +--raw +Prints a pretty-printed representation +of the raw Python dictionary that holds +build information about individual entry +(both the entry itself or its implicit dependencies). +An entry's build action is still printed in its usual format. + +.TP +-r, --readable +Prints timestamps in a human-readable string, +enclosed in single quotes. + +.TP +-t, --timestamp +Prints the timestamp information +for all entries or the specified entries. + +.TP +-v, --verbose +Prints labels identifying each field being printed. + +.SH ENVIRONMENT + +.IP SCONS_LIB_DIR +Specifies the directory that contains the SCons Python module directory +(e.g. /home/aroach/scons-src-0.01/src/engine). +on the command line. + +.SH "SEE ALSO" +.BR scons , +.B scons +User Manual, +.B scons +Design Document, +.B scons +source code. + +.SH AUTHORS +Steven Knight diff --git a/doc/python10/MANIFEST b/doc/python10/MANIFEST new file mode 100644 index 0000000..c9484d8 --- /dev/null +++ b/doc/python10/MANIFEST @@ -0,0 +1,16 @@ +abstract.xml +acks.xml +arch.fig +builder.fig +copyright.xml +design.xml +future.xml +install.xml +intro.xml +job-task.fig +main.xml +node.fig +process.xml +scanner.fig +scons.mod +sig.fig diff --git a/doc/python10/abstract.xml b/doc/python10/abstract.xml new file mode 100644 index 0000000..294180b --- /dev/null +++ b/doc/python10/abstract.xml @@ -0,0 +1,32 @@ + + + &SCons; is a software construction tool (build tool, or make tool) + implemented in Python, which uses Python scripts as "configuration + files" for software builds. Based on the design which won the + Software Carpentry build tool competition, &SCons solves a number of + problems associated with other build tools, especially including the + classic and ubiquitous &Make; itself. + + + + + + Distinctive features of &SCons; include: a modular design that + lends itself to being embedded in other applications; a global + view of all dependencies in the source tree; an improved model for + parallel () builds; automatic scanning of files for + dependencies; use of MD5 signatures for deciding whether a file + is up-to-date; use of traditional file timestamps instead of + MD5 signatures available as an option; + use of Python functions or objects to build target files; easy user + extensibility. + + + + + + This paper discusses the goals of the &SCons; project, gives an overview + of the design of &SCons; itself, describes the development process used, + and discusses future plans and directions for the tool. + + diff --git a/doc/python10/acks.xml b/doc/python10/acks.xml new file mode 100644 index 0000000..895bad7 --- /dev/null +++ b/doc/python10/acks.xml @@ -0,0 +1,27 @@ + + + First, many thanks to the great group of developers who dove in right + from the beginning and have contributed the code and ideas to make + &SCons; a success: Chad Austin, Charles Crain, Steve Leblanc, and + Anthony Roach. Thanks also to those on the scons-devel mailing list + who have contributed greatly to the discussion, notably including + David Abrahams, Trent Mick, and Steven Shaw. + + + + + + &SCons; would not exist today without the pioneering work of Bob + Sidebotham on the original &Cons; tool, and without Greg Wilson's + having started the Software Carpentry contest. + + + + + + Thanks also to Peter Miller for: Aegis; the testing discipline that it + enforces, without which creating a stable but flexible tool would be + impossible; the "Recursive Make Considered Harmful" paper which led me + to experiment with &Cons; in the first place. + + diff --git a/doc/python10/arch.eps b/doc/python10/arch.eps new file mode 100644 index 0000000..1fdd51f --- /dev/null +++ b/doc/python10/arch.eps @@ -0,0 +1,134 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: build/doc/python10/arch.fig +%%Creator: /usr/bin/fig2dev Version 3.2 Patchlevel 3d +%%CreationDate: Sun Jan 2 01:21:05 2005 +%%For: knight@casablanca.home.baldmt.com (Steven Knight) +%%BoundingBox: 0 0 218 182 +%%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 182 moveto 0 0 lineto 218 0 lineto 218 182 lineto closepath clip newpath +-215.3 324.7 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit + 0.06000 0.06000 sc +% +% Fig objects follow +% +/Courier-Bold ff 300.00 scf sf +3825 2925 m +gs 1 -1 sc (scons) col0 sh gr +/Times-Roman ff 300.00 scf sf +3825 3225 m +gs 1 -1 sc (Script) col0 sh gr +/Times-Roman ff 300.00 scf sf +5100 4875 m +gs 1 -1 sc (Build Engine) col0 sh gr +/Courier-Bold ff 300.00 scf sf +4200 4875 m +gs 1 -1 sc (SCons) col0 sh gr +% Polyline +7.500 slw +n 3600 4200 m 7200 4200 l 7200 5400 l 3600 5400 l + cp gs col0 s gr +/Courier-Bold ff 300.00 scf sf +4725 4050 m +gs 1 -1 sc (SCons) col0 sh gr +/Times-Roman ff 300.00 scf sf +5625 4050 m +gs 1 -1 sc (API) col0 sh gr +% Polyline +n 3600 2400 m 3600 2400 l 3600 2400 l 3600 2400 l + cp gs col0 s gr +% Polyline +n 3600 2400 m 4800 2400 l 4800 3600 l 3600 3600 l + cp gs col0 s gr +% Polyline +n 3600 3600 m 7200 3600 l 7200 4200 l 3600 4200 l + cp gs col0 s gr +% Polyline + [60] 0 sd +n 6000 3600 m 7200 3600 l 7200 2400 l 6000 2400 l + cp gs col0 s gr [] 0 sd +/Times-Italic ff 300.00 scf sf +6300 2925 m +gs 1 -1 sc (other) col0 sh gr +/Times-Italic ff 300.00 scf sf +6150 3225 m +gs 1 -1 sc (interface) col0 sh gr +$F2psEnd +rs diff --git a/doc/python10/arch.fig b/doc/python10/arch.fig new file mode 100644 index 0000000..ae20bd4 --- /dev/null +++ b/doc/python10/arch.fig @@ -0,0 +1,35 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 3825 2775 4650 3300 +4 0 0 50 0 14 20 0.0000 4 135 825 3825 2925 scons\001 +4 0 0 50 0 0 20 0.0000 4 255 690 3825 3225 Script\001 +-6 +6 3600 4200 7200 5400 +6 4200 4650 6675 4950 +4 0 0 50 0 0 20 0.0000 4 255 1515 5100 4875 Build Engine\001 +4 0 0 50 0 14 20 0.0000 4 165 825 4200 4875 SCons\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3600 4200 7200 4200 7200 5400 3600 5400 3600 4200 +-6 +6 4725 3825 6150 4050 +4 0 0 50 0 14 20 0.0000 4 165 825 4725 4050 SCons\001 +4 0 0 50 0 0 20 0.0000 4 195 465 5625 4050 API\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3600 2400 3600 2400 3600 2400 3600 2400 3600 2400 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3600 2400 4800 2400 4800 3600 3600 3600 3600 2400 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3600 3600 7200 3600 7200 4200 3600 4200 3600 3600 +2 2 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 5 + 6000 3600 7200 3600 7200 2400 6000 2400 6000 3600 +4 0 0 50 0 1 20 0.0000 4 210 570 6300 2925 other\001 +4 0 0 50 0 1 20 0.0000 4 270 975 6150 3225 interface\001 diff --git a/doc/python10/arch.jpg b/doc/python10/arch.jpg new file mode 100644 index 0000000..4e69437 Binary files /dev/null and b/doc/python10/arch.jpg differ diff --git a/doc/python10/builder.eps b/doc/python10/builder.eps new file mode 100644 index 0000000..db87afc --- /dev/null +++ b/doc/python10/builder.eps @@ -0,0 +1,325 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: build/doc/python10/builder.fig +%%Creator: /usr/bin/fig2dev Version 3.2 Patchlevel 3d +%%CreationDate: Sun Jan 2 01:21:05 2005 +%%For: knight@casablanca.home.baldmt.com (Steven Knight) +%%BoundingBox: 0 0 668 290 +%%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 290 moveto 0 0 lineto 668 0 lineto 668 290 lineto closepath clip newpath +-53.3 342.7 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/reencdict 12 dict def /ReEncode { reencdict begin +/newcodesandnames exch def /newfontname exch def /basefontname exch def +/basefontdict basefontname findfont def /newfont basefontdict maxlength dict def +basefontdict { exch dup /FID ne { dup /Encoding eq +{ exch dup length array copy newfont 3 1 roll put } +{ exch newfont 3 1 roll put } ifelse } { pop pop } ifelse } forall +newfont /FontName newfontname put newcodesandnames aload pop +128 1 255 { newfont /Encoding get exch /.notdef put } for +newcodesandnames length 2 idiv { newfont /Encoding get 3 1 roll put } repeat +newfontname newfont definefont pop end } def +/isovec [ +8#055 /minus 8#200 /grave 8#201 /acute 8#202 /circumflex 8#203 /tilde +8#204 /macron 8#205 /breve 8#206 /dotaccent 8#207 /dieresis +8#210 /ring 8#211 /cedilla 8#212 /hungarumlaut 8#213 /ogonek 8#214 /caron +8#220 /dotlessi 8#230 /oe 8#231 /OE +8#240 /space 8#241 /exclamdown 8#242 /cent 8#243 /sterling +8#244 /currency 8#245 /yen 8#246 /brokenbar 8#247 /section 8#250 /dieresis +8#251 /copyright 8#252 /ordfeminine 8#253 /guillemotleft 8#254 /logicalnot +8#255 /hyphen 8#256 /registered 8#257 /macron 8#260 /degree 8#261 /plusminus +8#262 /twosuperior 8#263 /threesuperior 8#264 /acute 8#265 /mu 8#266 /paragraph +8#267 /periodcentered 8#270 /cedilla 8#271 /onesuperior 8#272 /ordmasculine +8#273 /guillemotright 8#274 /onequarter 8#275 /onehalf +8#276 /threequarters 8#277 /questiondown 8#300 /Agrave 8#301 /Aacute +8#302 /Acircumflex 8#303 /Atilde 8#304 /Adieresis 8#305 /Aring +8#306 /AE 8#307 /Ccedilla 8#310 /Egrave 8#311 /Eacute +8#312 /Ecircumflex 8#313 /Edieresis 8#314 /Igrave 8#315 /Iacute +8#316 /Icircumflex 8#317 /Idieresis 8#320 /Eth 8#321 /Ntilde 8#322 /Ograve +8#323 /Oacute 8#324 /Ocircumflex 8#325 /Otilde 8#326 /Odieresis 8#327 /multiply +8#330 /Oslash 8#331 /Ugrave 8#332 /Uacute 8#333 /Ucircumflex +8#334 /Udieresis 8#335 /Yacute 8#336 /Thorn 8#337 /germandbls 8#340 /agrave +8#341 /aacute 8#342 /acircumflex 8#343 /atilde 8#344 /adieresis 8#345 /aring +8#346 /ae 8#347 /ccedilla 8#350 /egrave 8#351 /eacute +8#352 /ecircumflex 8#353 /edieresis 8#354 /igrave 8#355 /iacute +8#356 /icircumflex 8#357 /idieresis 8#360 /eth 8#361 /ntilde 8#362 /ograve +8#363 /oacute 8#364 /ocircumflex 8#365 /otilde 8#366 /odieresis 8#367 /divide +8#370 /oslash 8#371 /ugrave 8#372 /uacute 8#373 /ucircumflex +8#374 /udieresis 8#375 /yacute 8#376 /thorn 8#377 /ydieresis] def +/Times-Roman /Times-Roman-iso isovec ReEncode +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit + 0.06000 0.06000 sc +% +% Fig objects follow +% +% Polyline +7.500 slw +n 2700 1200 m 4500 1200 l 4500 1800 l 2700 1800 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +2925 1575 m +gs 1 -1 sc (Environment) col0 sh gr +% Polyline +n 2700 2400 m 4500 2400 l 4500 3000 l 2700 3000 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +3600 2775 m +gs 1 -1 sc (BuilderWrapper) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 2700 3600 m 4500 3600 l 4500 4200 l 2700 4200 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +3600 3975 m +gs 1 -1 sc (BuilderBase) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 8400 3600 m 9900 3600 l 9900 4200 l 8400 4200 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +9150 3975 m +gs 1 -1 sc (ActionBase) dup sw pop 2 div neg 0 rm col0 sh gr +/Times-Roman-iso ff 240.00 scf sf +4650 5175 m +gs 1 -1 sc (MultiStep-) dup sw pop 2 div neg 0 rm col0 sh gr +/Times-Roman-iso ff 240.00 scf sf +4650 5460 m +gs 1 -1 sc (Builder) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 3900 4800 m 5400 4800 l 5400 5700 l 3900 5700 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +2550 5175 m +gs 1 -1 sc (Composite-) dup sw pop 2 div neg 0 rm col0 sh gr +/Times-Roman-iso ff 240.00 scf sf +2550 5460 m +gs 1 -1 sc (Builder) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 1800 4800 m 3300 4800 l 3300 5700 l 1800 5700 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +7050 5175 m +gs 1 -1 sc (Command) dup sw pop 2 div neg 0 rm col0 sh gr +/Times-Roman-iso ff 240.00 scf sf +7050 5460 m +gs 1 -1 sc (Action) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 6300 4800 m 7800 4800 l 7800 5700 l 6300 5700 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +9150 5460 m +gs 1 -1 sc (Action) dup sw pop 2 div neg 0 rm col0 sh gr +/Times-Roman-iso ff 240.00 scf sf +9150 5175 m +gs 1 -1 sc (Function) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 8400 4800 m 9900 4800 l 9900 5700 l 8400 5700 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +11250 5175 m +gs 1 -1 sc (List) dup sw pop 2 div neg 0 rm col0 sh gr +/Times-Roman-iso ff 240.00 scf sf +11250 5460 m +gs 1 -1 sc (Action) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 10500 4800 m 12000 4800 l 12000 5700 l 10500 5700 l + cp gs col0 s gr +% Polyline +n 900 2400 m 2100 2400 l 2100 3000 l 900 3000 l + cp gs col0 s gr +/Times-Roman-iso ff 240.00 scf sf +1500 2775 m +gs 1 -1 sc (Node) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 3600 4200 m 3525 4350 l 3675 4350 l + cp gs col0 s gr +% Polyline +n 3150 4800 m 3150 4500 l 4050 4500 l + 4050 4800 l gs col0 s gr +% Polyline +n 3600 4350 m + 3600 4500 l gs col0 s gr +% Polyline +n 9150 4200 m 9075 4350 l 9225 4350 l + cp gs col0 s gr +% Polyline +n 7050 4800 m 7050 4500 l 10950 4500 l + 10950 4800 l gs col0 s gr +% Polyline +n 9150 4350 m + 9150 4800 l gs col0 s gr +% Polyline +gs clippath +9885 3870 m 9885 3930 l 10036 3930 l 9916 3900 l 10036 3870 l cp +eoclip +n 11550 4650 m 11550 3900 l + 9900 3900 l gs col0 s gr gr + +% arrowhead +n 10036 3870 m 9916 3900 l 10036 3930 l 10036 3870 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +8415 3930 m 8415 3870 l 8264 3870 l 8384 3900 l 8264 3930 l cp +eoclip +n 4650 3900 m + 8400 3900 l gs col0 s gr gr + +% arrowhead +n 8264 3930 m 8384 3900 l 8264 3870 l 8264 3930 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +3930 1785 m 3870 1785 l 3870 1936 l 3900 1816 l 3930 1936 l cp +eoclip +n 3900 2250 m + 3900 1800 l gs col0 s gr gr + +% arrowhead +n 3930 1936 m 3900 1816 l 3870 1936 l 3930 1936 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +3270 2415 m 3330 2415 l 3330 2264 l 3300 2384 l 3270 2264 l cp +eoclip +n 3300 1950 m + 3300 2400 l gs col0 s gr gr + +% arrowhead +n 3270 2264 m 3300 2384 l 3330 2264 l 3270 2264 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +3570 3615 m 3630 3615 l 3630 3464 l 3600 3584 l 3570 3464 l cp +eoclip +n 3600 3150 m + 3600 3600 l gs col0 s gr gr + +% arrowhead +n 3570 3464 m 3600 3584 l 3630 3464 l 3570 3464 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4380 4185 m 4320 4185 l 4320 4336 l 4350 4216 l 4380 4336 l cp +eoclip +n 4350 4650 m + 4350 4200 l gs col0 s gr gr + +% arrowhead +n 4380 4336 m 4350 4216 l 4320 4336 l 4380 4336 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +2880 4185 m 2820 4185 l 2820 4336 l 2850 4216 l 2880 4336 l cp +eoclip +n 2850 4650 m + 2850 4200 l gs col0 s gr gr + +% arrowhead +n 2880 4336 m 2850 4216 l 2820 4336 l 2880 4336 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +2715 3930 m 2715 3870 l 2564 3870 l 2684 3900 l 2564 3930 l cp +eoclip +n 1500 3150 m 1500 3900 l + 2700 3900 l gs col0 s gr gr + +% arrowhead +n 2564 3930 m 2684 3900 l 2564 3870 l 2564 3930 l cp gs 0.00 setgray ef gr col0 s +% Polyline +n 4650 3900 m 4575 3860 l 4500 3900 l 4575 3940 l + cp gs col0 s gr +% Polyline +n 1500 3000 m 1460 3075 l 1500 3150 l 1540 3075 l + cp gs col0 s gr +% Polyline +n 3600 3000 m 3560 3075 l 3600 3150 l 3640 3075 l + cp gs col0 s gr +% Polyline +n 3300 1800 m 3260 1875 l 3300 1950 l 3340 1875 l + cp gs col0 s gr +% Polyline +n 3900 2250 m 3860 2325 l 3900 2400 l 3940 2325 l + cp gs col0 s gr +% Polyline +n 4350 4650 m 4310 4725 l 4350 4800 l 4390 4725 l + cp gs col0 s gr +% Polyline +n 2850 4650 m 2810 4725 l 2850 4800 l 2890 4725 l + cp gs col0 s gr +% Polyline +n 11550 4650 m 11510 4725 l 11550 4800 l 11590 4725 l + cp gs col0 s gr +% Polyline + [60] 0 sd +n 3600 1200 m + 3600 900 l gs col0 s gr [] 0 sd +$F2psEnd +rs diff --git a/doc/python10/builder.fig b/doc/python10/builder.fig new file mode 100644 index 0000000..75e5ec0 --- /dev/null +++ b/doc/python10/builder.fig @@ -0,0 +1,128 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 2700 1200 4500 1800 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 1200 4500 1200 4500 1800 2700 1800 2700 1200 +4 0 0 50 0 0 16 0.0000 4 165 1290 2925 1575 Environment\001 +-6 +6 2700 2400 4500 3000 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 2400 4500 2400 4500 3000 2700 3000 2700 2400 +4 1 0 50 0 0 16 0.0000 4 225 1620 3600 2775 BuilderWrapper\001 +-6 +6 2700 3600 4500 4200 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 3600 4500 3600 4500 4200 2700 4200 2700 3600 +4 1 0 50 0 0 16 0.0000 4 165 1215 3600 3975 BuilderBase\001 +-6 +6 8400 3600 9900 4200 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 8400 3600 9900 3600 9900 4200 8400 4200 8400 3600 +4 1 0 50 0 0 16 0.0000 4 165 1140 9150 3975 ActionBase\001 +-6 +6 3900 4800 5400 5700 +6 4050 4950 5250 5475 +4 1 0 50 0 0 16 0.0000 4 225 1140 4650 5175 MultiStep-\001 +4 1 0 50 0 0 16 0.0000 4 165 750 4650 5460 Builder\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3900 4800 5400 4800 5400 5700 3900 5700 3900 4800 +-6 +6 1800 4800 3300 5700 +6 1950 4950 3150 5475 +4 1 0 50 0 0 16 0.0000 4 225 1200 2550 5175 Composite-\001 +4 1 0 50 0 0 16 0.0000 4 165 750 2550 5460 Builder\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 1800 4800 3300 4800 3300 5700 1800 5700 1800 4800 +-6 +6 6300 4800 7800 5700 +6 6525 4950 7575 5475 +4 1 0 50 0 0 16 0.0000 4 165 1020 7050 5175 Command\001 +4 1 0 50 0 0 16 0.0000 4 165 675 7050 5460 Action\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 6300 4800 7800 4800 7800 5700 6300 5700 6300 4800 +-6 +6 8400 4800 9900 5700 +6 8700 4950 9600 5475 +4 1 0 50 0 0 16 0.0000 4 165 675 9150 5460 Action\001 +4 1 0 50 0 0 16 0.0000 4 165 870 9150 5175 Function\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 8400 4800 9900 4800 9900 5700 8400 5700 8400 4800 +-6 +6 10500 4800 12000 5700 +6 10875 4950 11625 5475 +4 1 0 50 0 0 16 0.0000 4 165 390 11250 5175 List\001 +4 1 0 50 0 0 16 0.0000 4 165 675 11250 5460 Action\001 +-6 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 10500 4800 12000 4800 12000 5700 10500 5700 10500 4800 +-6 +6 900 2400 2100 3000 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 900 2400 2100 2400 2100 3000 900 3000 900 2400 +4 1 0 50 0 0 16 0.0000 4 165 525 1500 2775 Node\001 +-6 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 3600 4200 3525 4350 3675 4350 3600 4200 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 3150 4800 3150 4500 4050 4500 4050 4800 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 2 + 3600 4350 3600 4500 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 9150 4200 9075 4350 9225 4350 9150 4200 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 7050 4800 7050 4500 10950 4500 10950 4800 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 2 + 9150 4350 9150 4800 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 3 + 1 1 1.00 60.00 120.00 + 11550 4650 11550 3900 9900 3900 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 4650 3900 8400 3900 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3900 2250 3900 1800 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3300 1950 3300 2400 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3600 3150 3600 3600 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 4350 4650 4350 4200 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 2850 4650 2850 4200 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 3 + 1 1 1.00 60.00 120.00 + 1500 3150 1500 3900 2700 3900 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 4650 3900 4575 3860 4500 3900 4575 3940 4650 3900 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 1500 3000 1460 3075 1500 3150 1540 3075 1500 3000 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3600 3000 3560 3075 3600 3150 3640 3075 3600 3000 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3300 1800 3260 1875 3300 1950 3340 1875 3300 1800 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3900 2250 3860 2325 3900 2400 3940 2325 3900 2250 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 4350 4650 4310 4725 4350 4800 4390 4725 4350 4650 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 2850 4650 2810 4725 2850 4800 2890 4725 2850 4650 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 11550 4650 11510 4725 11550 4800 11590 4725 11550 4650 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 3600 1200 3600 900 diff --git a/doc/python10/builder.jpg b/doc/python10/builder.jpg new file mode 100644 index 0000000..e9085e8 Binary files /dev/null and b/doc/python10/builder.jpg differ diff --git a/doc/python10/copyright.xml b/doc/python10/copyright.xml new file mode 100644 index 0000000..d141fc6 --- /dev/null +++ b/doc/python10/copyright.xml @@ -0,0 +1,32 @@ + + +
+ + + Copyright (c) 2001, 2002 Steven Knight + + +
diff --git a/doc/python10/design.xml b/doc/python10/design.xml new file mode 100644 index 0000000..cb58af9 --- /dev/null +++ b/doc/python10/design.xml @@ -0,0 +1,898 @@ + + + The &SCons; architecture consists of three layers: + + + + + + + + + + + + + + + + + + + The &SCons; Build Engine, a package of Python + modules that handle dependency management and updating out-of-date + objects. + + + + + + + + The &SCons; API (applications programming + interface) between the Build Engine + and the user interface. + + + + + + + + The &scons; script itself (note lower case + sc), which is the pre-provided interface to + the Build Engine. + + + + + + + + + Notice that this architecture separates the internal workings of + &SCons; (the Build Engine) from the + external user interface. The benefit is that the &SCons; Build Engine + can be imported into any other software package written in Python + to support a variety of user interfaces—or, to look at it + in reverse, other software interfaces can use the &SCons; Build + Engine to manage dependencies between their objects. + + + + + + Because the + &SCons; package itself is modular, only those parts of the package + relevant to the embedding interface need be imported; for example, + a utility that wants to use only file timestamps for checking + whether a file is up-to-date + need not import the MD5 signature module. + + + +
+ The &SCons; Build Engine + + + + The Build Engine is a package of Python modules that + form the heart of &SCons;. + + The Build Engine can be broadly divided into five + architectural subsystems, each responsible + for a crucial part of &SCons; functionality: + + + + + + + + + A node subsystem, responsible for managing + the files (or other objects) to be built, and the dependency + relationships between them. + + + + + + + + A scanner subsystem, responsible for + scanning various file types for implicit dependencies. + + + + + + + + A signature subsystem, responsible for + deciding whether a given file (or other object) requires + rebuilding. + + + + + + + + A builder subsystem, responsible for + actually executing the necessary command or function to + build a file (or other object). + + + + + + + + A job/task subsystem, responsible for + handling parallelization of builds. + + + + + + + + + The rest of this section will provide a high-level overview of the + class structure of each of these Build Engine subsystems. + + + +
+ Node Subsystem + + + + The node subsystem of the Build Engine is + responsible for managing the knowledge in &SCons; of + the relationships among the external objects + (files) it is responsible for updating. + The most important of these relationships is + the dependency relationship between various &Node; objects, + which &SCons; uses to determine the order + in which builds should be performed. + + + + + + + + + + + + + + + + The &scons; script (or other + user interface) + tells the Build Engine + about dependencies + through its &consenv; API. + The Build Engine also discovers + dependencies automatically through the use of &Scanner; objects. + + + + + + Subclasses of the &Node; class maintain additional + relationships that reflect the real-world + existence of these objects. + For example, the &Node_FS; subclass + is responsible for managing a + representation of the directory hierarchy + of a file system. + + + + + + A &Walker; class is used by other subsystems + to walk the dependency tree maintained by the &Node; class. + The &Walker; class maintains a stack of &Node; objects + visited during its depth-first traversal of the + dependency tree, + and uses an intermediate node &Wrapper; class + to maintain state information about a + &Node; object's dependencies. + + + +
+ +
+ Scanner Subsystem + + + + The scanner subsystem is responsible for maintaining + objects that can scan the contents of a &Node;'s + for implicit dependencies. + + + + + + + + + + + + + + + + In practice, a given &Scanner; subclass object + functions as a prototype, + returning clones of itself + depending on the &consenv; + values governing how the &Node; + should be scanned. + + + +
+ +
+ Signature Subsystem + + + + The signature subsystem is responsible for computing + signature information for &Node; objects. + The signature subsystem in &SCons; + supports multiple ways to + determine whether a &Node is up-to-date + by using an abstract &Sig; class + as a strategy wrapper: + + + + + + + + + + + + + + + + By default, &SCons; tracks dependencies by computing and + maintaining MD5 signatures for the contents of each source file + (or other object). The signature of a derived + file consists of the aggregate of the signatures of all the source + files plus the command-line string used to + build the file. These signatures are stored in a &sconsign; file + in each directory. + + + + + + If the contents of any of the source files changes, the change to its + MD5 signature is propogated to the signature of the derived file(s). The + simple fact that the new signature does not match the stored signature + indicates that the derived file is not up to date and must be rebuilt. + + + + + + A separate &TimeStamp; subclass of the &Sig; class supports + the use of traditional file timestamps for + deciding whether files are up-to-date. + + + +
+ +
+ Builder Subsystem + + + + The &SCons; Build Engine records how out-of-date files + (or other objects) should be rebuilt in &Builder; objects, + maintained by the builder subsystem: + + + + + + + + + + + + + + + + The actual underlying class name is &BuilderBase;, + and there are subclasses that can encapsulate + multiple &Builder; objects for special purposes. + One subclass + (&CompositeBuilder;) + selects an appropriate encapsulated &Builder; + based on the file suffix of the target object. + The other + (&MultiStepBuilder;). + can chain together multiple + &Builder; objects, + for example, + to build an executable program from a source file + through an implicit intermediate object file. + + + + + + A &BuilderBase; object has an associated + &ActionBase; object + responsible for actually executing + the appropriate steps + to update the target file. + There are three subclasses, + one for externally executable commands + (&CommandAction;), + one for Python functions + (&FunctionAction;), + and one for lists of + multiple &Action; objects + (&ListAction;). + + + +
+ +
+ Job/Task Subsystem + + + + &SCons; supports parallel builds with a thread-based tasking + model, managed by the job/task subsystem. + + + + + + + + + + + + + + + + Instead of performing an outer-loop recursive descent + of the dependency tree and then forking a task when it finds a + file that needs updating, &SCons; starts as many threads as are + requested, each thread managed by the &Jobs; class. + As a performance optimization, + the &Jobs; class maintains an internal + distinction between + &Serial; and &Parallel; + build jobs, + so that serial builds + don't pay any performance penalty + by using a multi-threaded implementation + written for &Parallel; builds. + + + + + + Each &Jobs; object, running in its own thread, + then requests a &Task; from a central &Taskmaster;, + which is responsible + for handing out available &Task; objects for (re-)building + out-of-date nodes. A condition variable + makes sure that the &Jobs; objects + query the &Taskmaster; one at a time. + + + + + + The &Taskmaster uses the node subsystem's + &Walker; class to walk the dependency tree, + and the &Sig; class to use the + appropriate method + of deciding if a &Node; is up-to-date. + + + + + + This scheme has many advantages over the standard &Make; + implementation of . + Effective use of is difficult + with the usual recursive use of Make, + because the number of jobs started by multiply + at each level of the source tree. + This makes the actual number of jobs + executed at any moment very dependent on the size and layout of + the tree. &SCons;, in contrast, starts only as many jobs as are + requested, and keeps them constantly busy (excepting jobs that + block waiting for their dependency files to finish building). + + + +
+ +
+ +
+ The &SCons; API + + + + This section provides an overview of the &SCons; interface. The + complete interface specification is both more detailed and flexible + than this overview. + + + +
+ &ConsVars; + + + + In &SCons;, a &consenv; is an object through which an external + interface (such as the &scons; script) communicates dependency + information to the &SCons; Build Engine. + + + + + + A construction environment is implemented as a dictionary + containing: + + + + + + + + + construction variables, string values that are substituted + into command lines or used by builder functions; + + + + + + + + one or more &Builder; objects that can be invoked to update a + file or other object; + + + + + + + + one or more &Scanner; objects that can be used to + scan a file automatically for dependencies (such as + files specified on #include lines). + + + + + + + + + &Consenvs; are instantiated as follows: + + + + + env = Environment() + env_debug = Environment(CCFLAGS = '-g') + + +
+ +
+ &Builder; Objects + + + + An &SCons; &Builder; object encapsulates information about how to + build a specific type of file: an executable program, an object + file, a library, etc. A &Builder; object is associated with a + file through an associated &consenv; method and later invoked to + actually build the file. The &Builder; object will typically use + construction variables (such as &CCFLAGS;, &LIBPATH;) to influence + the specific build execution. + + + + + + &Builder; objects are instantiated as follows: + + + + + bld = Builder(name = 'Program', action = "$CC -o $TARGET $SOURCES") + + + + + In the above example, the action is a + command-line string in which the Build Engine will + interpolate the values of construction + variables before execution. The actual + action specified, though, + may be a function: + + + + + def update(dest): + # [code to update the object] + return 0 + + bld = Builder(name = 'Program', function = update) + + + + + Or a callable Python object (or class): + + + + + class class_a: + def __call__(self, kw): + # build the desired object + return 0 + + builder = SCons.Builder.Builder(action = class_a()) + + + + + A &Builder; object may have the prefix and + suffix of its target file type specified + as keyword arguments at instantiation. Additionally, the + suffix of the source files used by this + &Builder; to build its target files may be specified using the + src_suffix keyword argument: + + + + + bld_lib = Builder(name = 'Library', action = "$AR r $TARGET $SOURCES", + prefix = 'lib', suffix = '.a', src_suffix = '.o') + + + + + The specified prefix and + suffix will be appended to the name of any + target file built by this &Builder; object, if they are not + already part of the file name. The src_suffix + is used by the &SCons; Build Engine to chain together + multiple &Builder; objects to create, + for example, a library from the original source + files without having to specify the + intermediate .o files. + + + + + + &Builder; objects are associated with a &consenv; through a + &consvar; named &BUILDERS;, a list of the &Builder objects that + will be available for execution through the &consenv: + + + + + env = Environment(BUILDERS = [ Object, Library, WebPage, Program ]) + + +
+ +
+ &Scanner; Objects + + + + &Scanner; objects perform automatic checking for dependencies + by scanning the contents of files. The canonical + example is scanning a C source file or header file for + files specified on #include lines. + + + + + + A &Scanner; object is instantiated as follows: + + + + + def c_scan(contents): + # scan contents of file + return # list of files found + + c_scanner = Scanner(name = 'CScan', function = c_scan, + argument = None, + skeys = ['.c', '.C', '.h', '.H') + + + + + The skeys argument specifies a list of file + suffixes for file types that this &Scanner; knows how to scan. + + + + + + &Scanner; objects are associated with a &consenv; through a + &consvar; named &SCANNERS;, a list of the &Scanner; objects that + will be available through the &consenv: + + + + + env = Environment(SCANNERS = [ CScan, M4Scan ]) + + + + + For utilities that will build files with a variety of file + suffixes, or which require unusual scanning rules, a &Scanner; + object may be associated explicitly with a &Builder; object as + follows: + + + + + def tool_scan(contents): + # scan contents of file + return # list of files found + + tool_scanner = Scanner(name = 'TScan', function = tool_scan) + + bld = Builder(name = 'Tool', scanner = tool_scanner) + + +
+ +
+ &BuildDir; + + + + &SCons; supports a flexible mechanism for building target + files in a separate build directory from the source files. + The &BuildDir; syntax is straightforward: + + + + + BuildDir(source = 'src', build = 'bld') + + + + + By + default, source files are linked or copied into the build + directory, because exactly replicating the source directory + is sometimes necessary for certain combinations of use of + #include "..." and search + paths. + + An option exists to specify that only output files should be placed in + the build directory: + + + + + BuildDir(source = 'src', build = 'bld', no_sources = 1) + + +
+ +
+ &Repository; + + + + &SCons; supports the ability to search a list of code repositories + for source files and derived files. This works much like + &Make;'s VPATH feature, as implemented in + recent versions of GNU &Make;. + (The POSIX standard for &Make; specifies slightly + different behavior for VPATH.) + The syntax is: + + + + + Repository('/home/source/1.1', '/home/source/1.0') + + + + + A command-line option exists to allow + repositories to be specified on the command line, or in the + &SCONSFLAGS; environment variable (not construction variable!). + This avoids a chicken-and-egg situation and allows the top-level + &SConstruct; file to be found in a repository as well. + + + +
+ +
+ &Cache; + + + + &SCons; supports a way for developers to share derived files. Again, the + syntax is straightforward: + + + + + Cache('/var/build.cache/i386') + + + + + Copies of any derived files built will be placed in the specified + directory with their MD5 signature. If another build results in an + out-of-date derived file with the same signature, the derived file + will be copied from the cache instead of being rebuilt. + + + +
+ +
+ +
+ The &scons; Script + + + + The &scons; script provides an interface + that looks roughly equivalent to the + classic &Make; utility—that is, execution from the command + line, and dependency information read from configuration files. + + + + + + The most noticeable difference between &scons; and &Make;, or most + other build tools, is that the configuration files are actually + Python scripts, generically called "SConscripts" (although the + top-level "Makefile" is named &SConstruct). Users do not have to + learn a new language syntax, but instead configure dependency + information by making direct calls to the Python API of the + &SCons; Build Engine. Here is an example &SConstruct file which + builds a program in side-by-side normal and debug versions: + + + + + env = Environment() + debug = env.Copy(CCFLAGS = '-g') + + source_files = ['f1.c', 'f2.c', 'f3.c'] + + env.Program(target = 'foo', sources = source_files) + debug.Program(target = 'foo-debug', sources = source_files) + + + + + Notice the fact that this file is a Python script, which allows us + to define and re-use an array that lists the source files. + + + + + + Because quoting individul strings in long + lists of files can get tedious and error-prone, the &SCons; + methods support a short-cut of listing multiple files in a single + string, separated by white space. + This would change + the assignment in the above example to a more easily-readable: + + + + + source_files = 'f1.c f2.c f3.c' + + + + + The mechanism to establish hierarchical builds is to "include" any + subsidiary configuration files in the build by listing them explicitly + in a call to the &SConscript; function: + + + + + SConscript('src/SConscript', 'lib/SConscript') + + + + + By convention, configuration files in subdirectories are named + &SConscript;. + + + + + + The &scons; script has intentionally been made to look, from + the outside, as much like &Make; as is practical. To this + end, the &scons; script supports all of the same command-line + options supported by GNU &Make;: FILE, + , , , + etc. For compatibility, &scons; ignores those GNU &Make; options + that don't make sense for the &SCons; architecture, such as + , , , + and . The + intention is that, given an equivalent &SConstruct; file for a + &Makefile;, a user could use &SCons; as a drop-in replacement for + &Make;. Additional command-line options are, where possible, taken + from the Perl &Cons; utility on which the &SCons; design is based. + + + +
diff --git a/doc/python10/future.xml b/doc/python10/future.xml new file mode 100644 index 0000000..272d508 --- /dev/null +++ b/doc/python10/future.xml @@ -0,0 +1,170 @@ + + + There are a number of things we would like to do to continue to + improve &SCons; in the future. + + + +
+ Distutils Cooperation + + + + There is a certain amount of overlap between what &SCons; does + to search out and make use of various compilers on a system, and + the impressively complete job that the Distutils do of describing + much the same thing. Collaborating to provide some sort of common + interface between the two tools would benefit both tools. + + + +
+ +
+ Additional Builder Support + + + + Adding additional builders would broaden the + potential user base. In rough order of importance: + + + + + + + Java + + + + Given the popularity of Java, support for it would greatly + increase the appeal of &SCons; in the large community of Java + users. + + + + + + Good support for Java is, however, a tricky + proposition. Because the Java compiler can make decisions + about compiling other files based on what classes it finds + in a file, it behaves "unpredictably" from the point of + view of an outside build tool like &SCons; or &Make;. Some + sort of sophisticated scanning of Java source code to + identify what other classes are likely to be compiled + would be an obvious first step, but notice that here + &SCons; would be scanning the file to find additional + targets to be built. This is the inverse of the sort of + #include scanning performed + for C files, in which &SCons; is looking for additional + dependencies. + + + + + + + Documentation toolchains + + + + A number of early adopters + are using &SCons; to + build documents + from TeX or DocBook source files. + Built-in support for + various documentation toolchains + would be an obvious boon + for many people. + + + + + + + C# + + + + The reality is that anything that Microsoft does will doubtless + have a wide audience. Turning &SCons;' back on that would be + cutting off its nose to spite its face. + + + + + + + Fortran + + + + Despite the fact that &SCons; is no longer directly + associated with Software Carpentry, it still shares the + same goal: to make programming easier for more than just + programmers. To that end, good Fortran support would + help a great many physical scientists and other computer + users out there who still rely on Fortran + for a great deal of their work. + + + + + + + +
+ +
+ Database Interface + + + + The Nodes in an &SCons; dependency graph aren't only restricted to + files. Creating an interface to mSQL or MySQL databases would allow + the possibility of updating external files in response to changes in + database fields, or vice versa. This could be handy, for example, + for generating a cache of static web pages from a database that only + need re-generating when the appropriate database objects change. + + + +
+ +
+ Tool Integration + + + + &SCons; should work well with as many popular Integrated Development + Environments (IDEs) and tool chains as possible: Komodo, Microsoft + Visual Studio, ClearCase, etc. Suggestions for additional tools are + welcome. + + + +
+ +
+ Makefile Interface + + + + Because the &SCons; Build Engine can be embedded in any Python + interface, there isn't any technical reason why a &Makefile; + interpreter couldn't be written in Python and use the &SCons; Build + Engine for its dependency analysis. + + + + + + Proof-of-concept for the idea already exists. Gary Holt's + make++ (also known as makepp) + is a Perl implementation of just such a &Makefile; interpreter. It + could possible serve as a model for a Python version, in much the + same way the &Cons; design served as the prototype for &SCons;. + + + +
diff --git a/doc/python10/install.xml b/doc/python10/install.xml new file mode 100644 index 0000000..d150beb --- /dev/null +++ b/doc/python10/install.xml @@ -0,0 +1,179 @@ + + + Initial installation of a new utility provides the first, lasting + impression of how well the software is likely to perform. From the + start, &SCons; has made clean installation a priority. + + + +
+ Version Control + + + + Distributing an application like &SCons; that depends + on a package normally found in a library poses a + problem. If the &scons; script and the &SCons; Build Engine + are installed separately, it could be easy + to introduce a version mismatch between the Build Engine + installed in + /usr/lib/python*/site-packages + and the &scons; script installed in + /usr/bin. + Such a mismatch + could possible mean exceptions that prevent builds, or even worse, + silently unreliable builds. + + + + + + To reduce the possibility of a version mismatch, + the &scons; script looks first for its + imported modules in /usr/lib/scons-{version}/, + then in /usr/lib/scons/, + and then in the normal &PYTHONPATH; locations, + including /usr/lib/python*/site-packages). + Searching in a version-specific library directory first + makes it convenient to install and use multiple + side-by-side versions of &SCons;, + which is sometimes important + when verifying that a new version does not introduce any + errors into the local build process. + Searching next in an &SCons;-specific library directory + makes it convenient for other software to find + the &SCons; Build Engine without having to worry about + installing separate copies for + multiple versions of Python. + + + +
+ +
+ Packages + + + + &SCons; is currently distributed in the following packages: + + + + + + + + + + + scons-version.tar.gz + + + + The traditional .tar.gz file, + installable by running setup.py. + + + + + + + scons-version.noarch.rpm + + + + An RPM file for typical installation. + + + + + + + scons-version_all.deb + + + + A Debian package. + + + + + + + scons-version.win32.exe + + + + A Windows installer. + + + + + + + scons-version.src.rpm + + + + A source RPM file. + + + + + + + scons-src-version.tar.gz + + + + A tarball of the &SCons; source tree, + including the full set of regression tests. + + + + + + +
+ + + + Like other software written in Python, &SCons; benefits greatly from + the tremendous effort put into the distutils by + Greg Ward and others. These take care of 90% of the work by making + it almost trivial to generate the appropriate RPM files, Debian + packages, and Windows installer. + + + +
+ +
+ Default Builder Objects + + + + As part of the installation process, &SCons; runs a set of scripts + that look for popular compilers and other tools and set up + appropriate default &Builder; objects for the tools found. These + &Builder; objects are then used to initialize the default &consenv; + values. + + + +
+ +
+ Default Scanner Objects + + + + Additionally, &SCons; comes with a stock set of &Scanner; objects + for the various file types that it supports out of the box. Any + unusal &Scanner; objects required for a specific tool will be + detected at installation time and associated with the appropriate + &Builder; object for the tool. + + + +
diff --git a/doc/python10/intro.xml b/doc/python10/intro.xml new file mode 100644 index 0000000..d3057be --- /dev/null +++ b/doc/python10/intro.xml @@ -0,0 +1,212 @@ + + + More than twenty years after its creation, the classic UNIX &Make; + utility and its descendants are still the dominant way in which + software is built. &Make; has maintained this position despite the + fact that the intervening years have revealed many + shortcomings of the &Make; model for building software: + + + + + + + + + The use of timestamps to decide when a file has been updated is + imprecise and prone to error, especially across distributed file + systems such as NFS. + + + + + + + + Builds of typical large software systems still take hours, if not + days, despite the tremendous advances in CPU and disk speeds over + recent years. + + + + + + + + &Make; maintains static definitions of dependencies in its + &Makefiles;. Much effort has been put into + utilities (mkdepend, gcc + -M) and schemes (Makefile.d + files) to try to keep &Makefile; dependencies up-to-date, + but these only confirm that &Make;'s static dependencies are + inherently fragile. + + + + + + + + The standard recursive use of &Make; for build hierarchies leads + to incomplete dependency graphs, which must be overcome by + manually changing the order in which directories are built, or + through the use of multiple build passes. + + + + + + + + + One need only look at the plethora of helper and wrapper utilities + (automake, easymake, imake, jmake, makeLib, maketool, mkmed, shake, + SMake, TMAKE) and complete alternatives to &Make; (Ant, bake, bau, + bras, Cake, Cons, Cook, Jam, jmk, jus, makeme, mash, MK, nmake, Odin, + VMake) that have been created over the years to realize that vanilla + &Make; is not satisfying everyone's build requirements. So why Yet + Another build tool? + + + +
+ Enter Software Carpentry + + + + Most of the build tools just mentioned + were written by programmers and for + programmers. The fact that most programmer-friendly + utilities do a poor job of fulfilling the needs + of non-programmers prompted Greg Wilson to + organize the Software Carpentry competition in January 2000. + Software Carpentry was an + open design contest with the express goal of producing a set of + next-generation utilities, including a build tool, that would be + accessible + not only to + programmers + but also to computer users + such as physical scientists. + + + + + + The key to this usability would be that all of + these utilities, including the build tool, would be + written in Python. + This provided the catalyst for actually + pursuing an idea + that had been floating around one of the more + intriguing &Make; alternatives, + a Perl utility called &Cons;. + What if the friendlier syntax of Python + could be married to the + architectural advantages of &Cons;? + + + + + + The resulting merged design, at that time named &ScCons;, + won the Software Carpentry build tool competition. CodeSourcery (by + then the administrators of the competition) ultimately decided not to + fund development of the build tool, but the seed had been planted and the + design had taken root. + + + +
+ +
+ Cons + + + + It helps to know something about &Cons;. + &Cons; was first released in 1996 by Bob Sidebotham, + then an employee of Fore Systems, + and it has a number of + distinctive features that set it apart from most &Make;-alikes: + + + + + + + + + &Cons; "configuration files" are not Yet Another + invented mini-language, but are actually Perl + scripts, which means the full power and flexibility of + a real scripting language can be applied to build problems. + + + + + + + + &Cons; builds everything from a single process at the top of the + source tree, with a global view of the dependencies. + + + + + + + + &Cons; scans files automatically for dependencies such as + files specified on #include lines. + + + + + + + + &Cons; decides if a file was out-of-date by using MD5 checksums of + the contents of files, not timestamps. + + + + + + + + + Despite all of these intriguing architectural features, the great + strength of &Cons;—being written in Perl—was also one of + its weaknesses, turning away many potential users due to the + (real or perceived) steep learning curve of Perl. + + + +
+ +
+ &SCons; + + + + Through the &ScCons; contest entry, + &SCons; is the direct descendant of the &Cons; architecture, + and is currently + under active, supported development with a growing body of + users. Its first release was 13 December 2001, under the simple and + non-restrictive MIT license, and from the outset, the goal of the + members of the &SCons; project has been to deliver a stable, reliable + tool that can be used for industrial-strength software builds. + + + + + + The rest of this paper will give an overview of the &SCons; design + (including its architecture and interface), describe the development + methodology used, and discuss future directions for &SCons;. + + + +
diff --git a/doc/python10/job-task.eps b/doc/python10/job-task.eps new file mode 100644 index 0000000..b3eeaff --- /dev/null +++ b/doc/python10/job-task.eps @@ -0,0 +1,238 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: build/doc/python10/job-task.fig +%%Creator: /usr/bin/fig2dev Version 3.2 Patchlevel 3d +%%CreationDate: Sun Jan 2 01:21:05 2005 +%%For: knight@casablanca.home.baldmt.com (Steven Knight) +%%BoundingBox: 0 0 416 236 +%%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 236 moveto 0 0 lineto 416 0 lineto 416 236 lineto closepath clip newpath +-35.3 342.7 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit + 0.06000 0.06000 sc +% +% Fig objects follow +% +% Polyline +7.500 slw +n 4200 3900 m 5100 3900 l 5100 4500 l 4200 4500 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +4650 4275 m +gs 1 -1 sc (Task) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 4200 5100 m 5100 5100 l 5100 5700 l 4200 5700 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +4650 5475 m +gs 1 -1 sc (Node) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 6300 2100 m 7200 2100 l 7200 2700 l 6300 2700 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +6750 2475 m +gs 1 -1 sc (Sig) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 6300 3300 m 7500 3300 l 7500 3900 l 6300 3900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +6900 3675 m +gs 1 -1 sc (Walker) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 4200 2100 m 5700 2100 l 5700 2700 l 4200 2700 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +4950 2475 m +gs 1 -1 sc (TaskMaster) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 2400 3300 m 3600 3300 l 3600 3900 l 2400 3900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +3000 3675 m +gs 1 -1 sc (Parallel) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 600 3300 m 1800 3300 l 1800 3900 l 600 3900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +1200 3675 m +gs 1 -1 sc (Serial) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 1200 2100 m 3000 2100 l 3000 2700 l 1200 2700 l + cp gs col0 s gr +/Times-Roman ff 255.00 scf sf +2099 2475 m +gs 1 -1 sc (Jobs) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 2700 2700 m 2660 2775 l 2700 2850 l 2740 2775 l + cp gs col0 s gr +% Polyline +n 5700 2400 m 5775 2440 l 5850 2400 l 5775 2360 l + cp gs col0 s gr +% Polyline +n 5400 2700 m 5360 2775 l 5400 2850 l 5440 2775 l + cp gs col0 s gr +% Polyline +n 4650 2700 m 4610 2775 l 4650 2850 l 4690 2775 l + cp gs col0 s gr +% Polyline +n 1500 2700 m 1460 2775 l 1500 2850 l 1540 2775 l + cp gs col0 s gr +% Polyline +n 4650 4500 m 4610 4575 l 4650 4650 l 4690 4575 l + cp gs col0 s gr +% Polyline +gs clippath +1470 3315 m 1530 3315 l 1530 3164 l 1500 3284 l 1470 3164 l cp +eoclip +n 1500 2850 m + 1500 3300 l gs col0 s gr gr + +% arrowhead +n 1470 3164 m 1500 3284 l 1530 3164 l 1470 3164 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +2670 3315 m 2730 3315 l 2730 3164 l 2700 3284 l 2670 3164 l cp +eoclip +n 2700 2850 m + 2700 3300 l gs col0 s gr gr + +% arrowhead +n 2670 3164 m 2700 3284 l 2730 3164 l 2670 3164 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4620 3915 m 4680 3915 l 4680 3764 l 4650 3884 l 4620 3764 l cp +eoclip +n 4650 2850 m + 4650 3900 l gs col0 s gr gr + +% arrowhead +n 4620 3764 m 4650 3884 l 4680 3764 l 4620 3764 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4620 5115 m 4680 5115 l 4680 4964 l 4650 5084 l 4620 4964 l cp +eoclip +n 4650 4650 m + 4650 5100 l gs col0 s gr gr + +% arrowhead +n 4620 4964 m 4650 5084 l 4680 4964 l 4620 4964 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +6315 3630 m 6315 3570 l 6164 3570 l 6284 3600 l 6164 3630 l cp +eoclip +n 5400 2850 m 5400 3600 l + 6300 3600 l gs col0 s gr gr + +% arrowhead +n 6164 3630 m 6284 3600 l 6164 3570 l 6164 3630 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +6315 2430 m 6315 2370 l 6164 2370 l 6284 2400 l 6164 2430 l cp +eoclip +n 5850 2400 m + 6300 2400 l gs col0 s gr gr + +% arrowhead +n 6164 2430 m 6284 2400 l 6164 2370 l 6164 2430 l cp gs 0.00 setgray ef gr col0 s +% Polyline +n 3000 2400 m 3075 2440 l 3150 2400 l 3075 2360 l + cp gs col0 s gr +% Polyline +gs clippath +4215 2430 m 4215 2370 l 4064 2370 l 4184 2400 l 4064 2430 l cp +eoclip +n 3150 2400 m + 4200 2400 l gs col0 s gr gr + +% arrowhead +n 4064 2430 m 4184 2400 l 4064 2370 l 4064 2430 l cp gs 0.00 setgray ef gr col0 s +% Polyline + [60] 0 sd +n 2100 2100 m + 2100 1800 l gs col0 s gr [] 0 sd +% Polyline + [60] 0 sd +n 4950 2100 m + 4950 1800 l gs col0 s gr [] 0 sd +% Polyline + [60] 0 sd +n 6750 2100 m + 6750 1800 l gs col0 s gr [] 0 sd +$F2psEnd +rs diff --git a/doc/python10/job-task.fig b/doc/python10/job-task.fig new file mode 100644 index 0000000..6febd97 --- /dev/null +++ b/doc/python10/job-task.fig @@ -0,0 +1,90 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 4200 3900 5100 4500 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 4200 3900 5100 3900 5100 4500 4200 4500 4200 3900 +4 1 0 50 0 0 16 0.0000 4 165 465 4650 4275 Task\001 +-6 +6 4200 5100 5100 5700 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 4200 5100 5100 5100 5100 5700 4200 5700 4200 5100 +4 1 0 50 0 0 16 0.0000 4 165 525 4650 5475 Node\001 +-6 +6 6300 2100 7200 2700 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 6300 2100 7200 2100 7200 2700 6300 2700 6300 2100 +4 1 0 50 0 0 16 0.0000 4 225 330 6750 2475 Sig\001 +-6 +6 6300 3300 7500 3900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 6300 3300 7500 3300 7500 3900 6300 3900 6300 3300 +4 1 0 50 0 0 16 0.0000 4 165 735 6900 3675 Walker\001 +-6 +6 4200 2100 5700 2700 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 4200 2100 5700 2100 5700 2700 4200 2700 4200 2100 +4 1 0 50 0 0 16 0.0000 4 165 1155 4950 2475 TaskMaster\001 +-6 +6 2400 3300 3600 3900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2400 3300 3600 3300 3600 3900 2400 3900 2400 3300 +4 1 0 50 0 0 16 0.0000 4 165 765 3000 3675 Parallel\001 +-6 +6 600 3300 1800 3900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 600 3300 1800 3300 1800 3900 600 3900 600 3300 +4 1 0 50 0 0 16 0.0000 4 165 585 1200 3675 Serial\001 +-6 +6 1200 2100 3000 2700 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 1200 2100 3000 2100 3000 2700 1200 2700 1200 2100 +4 1 0 50 0 0 17 0.0000 4 165 420 2099 2475 Jobs\001 +-6 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 2700 2700 2660 2775 2700 2850 2740 2775 2700 2700 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 5700 2400 5775 2440 5850 2400 5775 2360 5700 2400 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 5400 2700 5360 2775 5400 2850 5440 2775 5400 2700 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 4650 2700 4610 2775 4650 2850 4690 2775 4650 2700 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 1500 2700 1460 2775 1500 2850 1540 2775 1500 2700 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 4650 4500 4610 4575 4650 4650 4690 4575 4650 4500 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 1500 2850 1500 3300 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 2700 2850 2700 3300 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 4650 2850 4650 3900 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 4650 4650 4650 5100 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 3 + 1 1 1.00 60.00 120.00 + 5400 2850 5400 3600 6300 3600 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 5850 2400 6300 2400 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3000 2400 3075 2440 3150 2400 3075 2360 3000 2400 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3150 2400 4200 2400 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 2100 2100 2100 1800 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 4950 2100 4950 1800 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 6750 2100 6750 1800 diff --git a/doc/python10/job-task.jpg b/doc/python10/job-task.jpg new file mode 100644 index 0000000..ff3ded0 Binary files /dev/null and b/doc/python10/job-task.jpg differ diff --git a/doc/python10/main.xml b/doc/python10/main.xml new file mode 100644 index 0000000..42bc4af --- /dev/null +++ b/doc/python10/main.xml @@ -0,0 +1,221 @@ + + + + + + %scons; + + + + + + + + + + +]> + +
+ + SCons Design and Implementation + + + Steven + Knight + + + + 2001 + 2002 + Steven Knight + + + 2002 + + + 4-7 February 2002 + The Tenth International Python Conference +
Alexandria, Virginia
+
+ + + + 0.2 + 16 December 2001 + Internal re-review. + + + 0.1 + 8 October 2001 + Submitted for Python10 conference. + + + +
+ + + &abstract; + + +
+ Introduction + &intro; +
+ +
+ Architecture + &design; +
+ +
+ Installation + &install; +
+ +
+ Development Process + &process; +
+ +
+ Future Directions + &future; +
+ +
+ Summary + + + This paper has introduced &SCons;, a next-generation build tool + with a modular, embeddable architecture and a direct Python + interface. &SCons; has a global view of the dependencies in a source + tree, uses MD5 signatures to decide if derived files are out of date, + and automatically scans files for dependencies, all of which make &SCons; + builds exceptionally reliable. The &SCons; development methodology has + been described, notable for its emphasis on automated regression + testing to ensure a robust and reliable tool from day one. Several + future directions for &SCons; have also been discussed. + + +
+ +
+ Acknowledgements + &acks; +
+ + + + + References + + + 1 + + Stuart I.Feldman + + + Aug 1978 + + + Bell Laboratories + + Make - A Program for Maintaining Computer Programs + + + + 2 + + PeterMiller + + + 1997 + Peter Miller + + Recursive Make Considered Harmful + + + + + 3 + + AndrewOram + SteveTalbott + + + 1986 + 1991 + O'Reilly & Associates, Inc. + + + O'Reilly & Associates, Inc. + + Managing Projects with Make, 2nd Ed. + + + + 4 + + Richard M.Stallman + RolandMcGrath + + + 1988 + '89 + '90 + '91 + '92 + '93 + '94 + '95 + '96 + '97 + '98 + '99 + 2000 + Free Software Foundation, Inc. + + + Free Software Foundation, Inc. + + GNU Make + A Program for Directing Recompilation + + + + +
diff --git a/doc/python10/node.eps b/doc/python10/node.eps new file mode 100644 index 0000000..995235d --- /dev/null +++ b/doc/python10/node.eps @@ -0,0 +1,351 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: build/doc/python10/node.fig +%%Creator: /usr/bin/fig2dev Version 3.2 Patchlevel 3d +%%CreationDate: Sun Jan 2 01:21:05 2005 +%%For: knight@casablanca.home.baldmt.com (Steven Knight) +%%BoundingBox: 0 0 452 362 +%%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 362 moveto 0 0 lineto 452 0 lineto 452 362 lineto closepath clip newpath +0.7 414.7 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit + 0.06000 0.06000 sc +% +% Fig objects follow +% +% Polyline +7.500 slw +n 2700 1200 m 4500 1200 l 4500 1800 l 2700 1800 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +2925 1575 m +gs 1 -1 sc (Environment) col0 sh gr +% Polyline +n 2700 3600 m 4500 3600 l 4500 4200 l 2700 4200 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +3375 3975 m +gs 1 -1 sc (Node) col0 sh gr +% Polyline +n 5700 1800 m 6900 1800 l 6900 2400 l 5700 2400 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +5925 2175 m +gs 1 -1 sc (Walker) col0 sh gr +% Polyline +n 2100 2400 m 3300 2400 l 3300 3000 l 2100 3000 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +2325 2775 m +gs 1 -1 sc (Builder) col0 sh gr +% Polyline +n 3900 2400 m 5100 2400 l 5100 3000 l 3900 3000 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +4125 2775 m +gs 1 -1 sc (Scanner) col0 sh gr +% Polyline +n 2400 6300 m 3300 6300 l 3300 6900 l 2400 6900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +2700 6675 m +gs 1 -1 sc (Dir) col0 sh gr +% Polyline +n 0 6300 m 900 6300 l 900 6900 l 0 6900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +150 6675 m +gs 1 -1 sc (Entry) col0 sh gr +% Polyline +n 1200 6300 m 2100 6300 l 2100 6900 l 1200 6900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +1425 6675 m +gs 1 -1 sc (File) col0 sh gr +% Polyline +n 1050 5100 m 2250 5100 l 2250 5700 l 1050 5700 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +1200 5475 m +gs 1 -1 sc (Node.FS) col0 sh gr +% Polyline +n 1650 5700 m 1575 5850 l 1725 5850 l + cp gs col0 s gr +% Polyline +n 450 6300 m 450 6000 l 2700 6000 l + 2700 6300 l gs col0 s gr +% Polyline +n 1650 6300 m + 1650 5850 l gs col0 s gr +% Polyline + [60] 0 sd +n 5100 6300 m 6300 6300 l 6300 6900 l 5100 6900 l + cp gs col0 s gr [] 0 sd +/Times-Roman ff 240.00 scf sf +5325 6675 m +gs 1 -1 sc (Record) col0 sh gr +% Polyline + [60] 0 sd +n 6600 6300 m 7500 6300 l 7500 6900 l 6600 6900 l + cp gs col0 s gr [] 0 sd +/Times-Roman ff 240.00 scf sf +6750 6675 m +gs 1 -1 sc (Field) col0 sh gr +% Polyline + [60] 0 sd +n 4950 5100 m 6150 5100 l 6150 5700 l 4950 5700 l + cp gs col0 s gr [] 0 sd +/Times-Roman ff 240.00 scf sf +5100 5475 m +gs 1 -1 sc (Node.DB) col0 sh gr +% Polyline +n 5550 5700 m 5475 5850 l 5625 5850 l + cp gs col0 s gr +% Polyline + [60] 0 sd +n 4350 6300 m 4350 6000 l 7050 6000 l + 7050 6300 l gs col0 s gr [] 0 sd +% Polyline + [60] 0 sd +n 5550 5850 m + 5550 6300 l gs col0 s gr [] 0 sd +% Polyline + [60] 0 sd +n 3900 6300 m 4800 6300 l 4800 6900 l 3900 6900 l + cp gs col0 s gr [] 0 sd +/Times-Roman ff 240.00 scf sf +4050 6675 m +gs 1 -1 sc (Table) col0 sh gr +% Polyline +n 5700 3000 m 6900 3000 l 6900 3600 l 5700 3600 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +5850 3375 m +gs 1 -1 sc (Wrapper) col0 sh gr +% Polyline +n 900 1200 m 1800 1200 l 1800 1800 l 900 1800 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +1200 1575 m +gs 1 -1 sc (FS) col0 sh gr +% Polyline +n 3600 4200 m 3525 4350 l 3675 4350 l + cp gs col0 s gr +% Polyline +n 1800 5100 m 1800 4800 l 5550 4800 l + 5550 5100 l gs col0 s gr +% Polyline +n 3600 4800 m + 3600 4350 l gs col0 s gr +% Polyline +n 4200 1800 m 4160 1875 l 4200 1950 l 4240 1875 l + cp gs col0 s gr +% Polyline +n 3000 6150 m 2960 6225 l 3000 6300 l 3040 6225 l + cp gs col0 s gr +% Polyline +n 6300 3600 m 6260 3675 l 6300 3750 l 6340 3675 l + cp gs col0 s gr +% Polyline +n 6300 2400 m 6260 2475 l 6300 2550 l 6340 2475 l + cp gs col0 s gr +% Polyline +n 3000 4200 m 2960 4275 l 3000 4350 l 3040 4275 l + cp gs col0 s gr +% Polyline +n 4200 3450 m 4160 3525 l 4200 3600 l 4240 3525 l + cp gs col0 s gr +% Polyline +n 3000 3450 m 2960 3525 l 3000 3600 l 3040 3525 l + cp gs col0 s gr +% Polyline +gs clippath +2235 5370 m 2235 5430 l 2386 5430 l 2266 5400 l 2386 5370 l cp +eoclip +n 3000 6150 m 3000 5400 l + 2250 5400 l gs col0 s gr gr + +% arrowhead +n 2386 5370 m 2266 5400 l 2386 5430 l 2386 5370 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +2715 3930 m 2715 3870 l 2564 3870 l 2684 3900 l 2564 3930 l cp +eoclip +n 3000 4350 m 3000 4500 l 1800 4500 l 1800 3900 l + 2700 3900 l gs col0 s gr gr + +% arrowhead +n 2564 3930 m 2684 3900 l 2564 3870 l 2564 3930 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4485 3870 m 4485 3930 l 4636 3930 l 4516 3900 l 4636 3870 l cp +eoclip +n 6300 3750 m 6300 3900 l + 4500 3900 l gs col0 s gr gr + +% arrowhead +n 4636 3870 m 4516 3900 l 4636 3930 l 4636 3870 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4230 2985 m 4170 2985 l 4170 3136 l 4200 3016 l 4230 3136 l cp +eoclip +n 4200 3450 m + 4200 3000 l gs col0 s gr gr + +% arrowhead +n 4230 3136 m 4200 3016 l 4170 3136 l 4230 3136 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +3030 2985 m 2970 2985 l 2970 3136 l 3000 3016 l 3030 3136 l cp +eoclip +n 3000 3450 m + 3000 3000 l gs col0 s gr gr + +% arrowhead +n 3030 3136 m 3000 3016 l 2970 3136 l 3030 3136 l cp gs 0.00 setgray ef gr col0 s +% Polyline +n 3000 1800 m 2960 1875 l 3000 1950 l 3040 1875 l + cp gs col0 s gr +% Polyline +gs clippath +2970 2415 m 3030 2415 l 3030 2264 l 3000 2384 l 2970 2264 l cp +eoclip +n 3000 1950 m + 3000 2400 l gs col0 s gr gr + +% arrowhead +n 2970 2264 m 3000 2384 l 3030 2264 l 2970 2264 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4170 2415 m 4230 2415 l 4230 2264 l 4200 2384 l 4170 2264 l cp +eoclip +n 4200 1950 m + 4200 2400 l gs col0 s gr gr + +% arrowhead +n 4170 2264 m 4200 2384 l 4230 2264 l 4170 2264 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +6270 3015 m 6330 3015 l 6330 2864 l 6300 2984 l 6270 2864 l cp +eoclip +n 6300 2550 m + 6300 3000 l gs col0 s gr gr + +% arrowhead +n 6270 2864 m 6300 2984 l 6330 2864 l 6270 2864 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +4785 6570 m 4785 6630 l 4936 6630 l 4816 6600 l 4936 6570 l cp +eoclip +n 5100 6600 m + 4800 6600 l gs col0 s gr gr + +% arrowhead +n 4936 6570 m 4816 6600 l 4936 6630 l 4936 6570 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +6285 6570 m 6285 6630 l 6436 6630 l 6316 6600 l 6436 6570 l cp +eoclip +n 6600 6600 m + 6300 6600 l gs col0 s gr gr + +% arrowhead +n 6436 6570 m 6316 6600 l 6436 6630 l 6436 6570 l cp gs 0.00 setgray ef gr col0 s +% Polyline +n 1350 1800 m 1310 1875 l 1350 1950 l 1390 1875 l + cp gs col0 s gr +% Polyline +gs clippath +1320 5115 m 1380 5115 l 1380 4964 l 1350 5084 l 1320 4964 l cp +eoclip +n 1350 1950 m + 1350 5100 l gs col0 s gr gr + +% arrowhead +n 1320 4964 m 1350 5084 l 1380 4964 l 1320 4964 l cp gs 0.00 setgray ef gr col0 s +% Polyline + [60] 0 sd +n 1350 1200 m + 1350 900 l gs col0 s gr [] 0 sd +% Polyline + [60] 0 sd +n 3600 1200 m + 3600 900 l gs col0 s gr [] 0 sd +$F2psEnd +rs diff --git a/doc/python10/node.fig b/doc/python10/node.fig new file mode 100644 index 0000000..3c40f07 --- /dev/null +++ b/doc/python10/node.fig @@ -0,0 +1,165 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 2700 1200 4500 1800 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 1200 4500 1200 4500 1800 2700 1800 2700 1200 +4 0 0 50 0 0 16 0.0000 4 165 1290 2925 1575 Environment\001 +-6 +6 2700 3600 4500 4200 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 3600 4500 3600 4500 4200 2700 4200 2700 3600 +4 0 0 50 0 0 16 0.0000 4 165 525 3375 3975 Node\001 +-6 +6 5700 1800 6900 2400 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 5700 1800 6900 1800 6900 2400 5700 2400 5700 1800 +4 0 0 50 0 0 16 0.0000 4 165 735 5925 2175 Walker\001 +-6 +6 2100 2400 3300 3000 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2100 2400 3300 2400 3300 3000 2100 3000 2100 2400 +4 0 0 50 0 0 16 0.0000 4 165 750 2325 2775 Builder\001 +-6 +6 3900 2400 5100 3000 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3900 2400 5100 2400 5100 3000 3900 3000 3900 2400 +4 0 0 50 0 0 16 0.0000 4 165 780 4125 2775 Scanner\001 +-6 +6 0 5100 3300 6900 +6 2400 6300 3300 6900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2400 6300 3300 6300 3300 6900 2400 6900 2400 6300 +4 0 0 50 0 0 16 0.0000 4 165 345 2700 6675 Dir\001 +-6 +6 0 6300 900 6900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 0 6300 900 6300 900 6900 0 6900 0 6300 +4 0 0 50 0 0 16 0.0000 4 225 555 150 6675 Entry\001 +-6 +6 1200 6300 2100 6900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 1200 6300 2100 6300 2100 6900 1200 6900 1200 6300 +4 0 0 50 0 0 16 0.0000 4 165 390 1425 6675 File\001 +-6 +6 1050 5100 2250 5700 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 1050 5100 2250 5100 2250 5700 1050 5700 1050 5100 +4 0 0 50 0 0 16 0.0000 4 165 855 1200 5475 Node.FS\001 +-6 +6 450 5700 2700 6300 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 1650 5700 1575 5850 1725 5850 1650 5700 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 450 6300 450 6000 2700 6000 2700 6300 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 2 + 1650 6300 1650 5850 +-6 +-6 +6 3900 5100 7500 6900 +6 5100 6300 6300 6900 +2 2 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 5 + 5100 6300 6300 6300 6300 6900 5100 6900 5100 6300 +4 0 0 50 0 0 16 0.0000 4 165 705 5325 6675 Record\001 +-6 +6 6600 6300 7500 6900 +2 2 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 5 + 6600 6300 7500 6300 7500 6900 6600 6900 6600 6300 +4 0 0 50 0 0 16 0.0000 4 165 510 6750 6675 Field\001 +-6 +6 4950 5100 6150 5700 +2 2 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 5 + 4950 5100 6150 5100 6150 5700 4950 5700 4950 5100 +4 0 0 50 0 0 16 0.0000 4 165 930 5100 5475 Node.DB\001 +-6 +6 4350 5700 7050 6300 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 5550 5700 5475 5850 5625 5850 5550 5700 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 4 + 4350 6300 4350 6000 7050 6000 7050 6300 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 5550 5850 5550 6300 +-6 +6 3900 6300 4800 6900 +2 2 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 5 + 3900 6300 4800 6300 4800 6900 3900 6900 3900 6300 +4 0 0 50 0 0 16 0.0000 4 165 555 4050 6675 Table\001 +-6 +-6 +6 5700 3000 6900 3600 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 5700 3000 6900 3000 6900 3600 5700 3600 5700 3000 +4 0 0 50 0 0 16 0.0000 4 225 870 5850 3375 Wrapper\001 +-6 +6 900 1200 1800 1800 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 900 1200 1800 1200 1800 1800 900 1800 900 1200 +4 0 0 50 0 0 16 0.0000 4 165 270 1200 1575 FS\001 +-6 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 3600 4200 3525 4350 3675 4350 3600 4200 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 1800 5100 1800 4800 5550 4800 5550 5100 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 2 + 3600 4800 3600 4350 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 4200 1800 4160 1875 4200 1950 4240 1875 4200 1800 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3000 6150 2960 6225 3000 6300 3040 6225 3000 6150 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 6300 3600 6260 3675 6300 3750 6340 3675 6300 3600 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 6300 2400 6260 2475 6300 2550 6340 2475 6300 2400 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3000 4200 2960 4275 3000 4350 3040 4275 3000 4200 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 4200 3450 4160 3525 4200 3600 4240 3525 4200 3450 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3000 3450 2960 3525 3000 3600 3040 3525 3000 3450 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 3 + 1 1 1.00 60.00 120.00 + 3000 6150 3000 5400 2250 5400 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 5 + 1 1 1.00 60.00 120.00 + 3000 4350 3000 4500 1800 4500 1800 3900 2700 3900 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 3 + 1 1 1.00 60.00 120.00 + 6300 3750 6300 3900 4500 3900 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 4200 3450 4200 3000 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 3000 3450 3000 3000 +2 3 0 1 0 7 50 0 -1 0.000 0 0 7 0 0 5 + 3000 1800 2960 1875 3000 1950 3040 1875 3000 1800 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 3000 1950 3000 2400 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 4200 1950 4200 2400 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 6300 2550 6300 3000 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 5100 6600 4800 6600 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 6600 6600 6300 6600 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 1350 1800 1310 1875 1350 1950 1390 1875 1350 1800 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 1 0 2 + 1 1 1.00 60.00 120.00 + 1350 1950 1350 5100 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 1350 1200 1350 900 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 3600 1200 3600 900 diff --git a/doc/python10/node.jpg b/doc/python10/node.jpg new file mode 100644 index 0000000..98ceb5e Binary files /dev/null and b/doc/python10/node.jpg differ diff --git a/doc/python10/process.xml b/doc/python10/process.xml new file mode 100644 index 0000000..f1b2479 --- /dev/null +++ b/doc/python10/process.xml @@ -0,0 +1,353 @@ + + + The &SCons; project has paid particular attention from day one to the + development process. One of the first internal documents produced was + a set of Developer's Guidelines to provide a loose framework for what + we were trying to accomplish and how we would go about accomplishing + it. These Guidelines cover things like: + + + + + + + + + &SCons; will be written to Python version 1.5.2 (to ensure + usability by a wide install base). + + + + + + + + How &SCons; is be tested: which infrastructure modules to use, + what platforms to test on, etc. + + + + + + + + Expectations for developers (subscribe to the mailing list, + encouraged to register at SourceForge). + + + + + + + + Brief outline of how to use the change management systems (Aegis and + CVS) for &SCons; development;. + + + + + + + + + Establishing these guidelines up front had two purposes: 1) + Demonstrate the seriousness of the project to anyone wondering about + joining the effort; 2) Give potential developers an idea up front as + to whether their development style would mesh with the rest of the + project. + + + +
+ Aegis + + + + One of the most important aspects of the &SCons; development process + is the use of Peter Miller's Aegis change management system. I + had been using Aegis for personal projects for several years, and + found its development methodology vastly improved the quality of my + programming. I was consequently committed to using it for &SCons; + development. + + + + + + Aegis provides a number of things, including: + + + + + + + + + A flexible source code control and branching model. + + + + + + + + A defined process with separate development, review and + integration steps. + + + + + + + + A distributed development model based on distribution of atomic + change sets. + + + + + + + + + The single most important reason for using Aegis, however, is its + management of automated tests as part of the development process. + + + +
+ +
+ Testing, Testing, Testing + + + + The &SCons; project has made extensive use of automated tests from day + one, taking inspiration mostly from Aegis, partly from the eXtreme + Programming model, and with a little home-brew scripting for glue. + + + +
+ Testing Criteria + + + + The underlying criteria for testing changes to the &SCons; code + are taken from Aegis: + + + + + + + + + Every change must have one or more new or modified tests + checked in along with the code. + + + + + + + + The new code being checked in must pass all of the new and/or + modified tests. + + + + + + + + The old, already checked-in code in must + fail all of the new and/or modified + tests. + + + + + + + + The new code being checked in must pass all unmodified, + already checked-in tests. + + + + + + + + + In practice, these restrictions can be overridden as necessary­for + example, when changing comments or documentation. + + + + + + The criterion that surprises many people is having the old code + fail the tests in the change. This makes sure that the new tests + or modified tests really do exercise the bug fix or feature being + added by the change. + + + + + + Together, these criteria ensure that every newly checked-in + version &SCons; conforms to defined behavior, as defined by + the tests. Whenever a bug is found, its fix is checked in with + a new or modified test that guarantees the bug will not recur + in the future. We have already built up a regression test base + of almost 90 tests that cover the vast majority of &SCons;' + functionality. + + + +
+ +
+ Testing Infrastructure + + + + Testing standards are no good if they're too much of a burden for + developers, who will at best work around or ignore the testing + requirements, or at worst stop contributing code and go join a + project that's more fun. To this end, good testing infrastructure + that makes it easy to write tests is crucial. + + + + + + &SCons; development uses two development methodologies, one for + the individual modules in the build engine, and the other for + end-to-end tests of the &SCons; script. + + + + + + For the build engine modules, we use PyUnit. Every change to a + build engine module must have a change to its corresponding unit + tests, which live side-by-side in a separate file that imports + module. As we build up a large body of unit tests, this ensures + that the build engine will perform correctly whenever someone uses + it in some application other than the &SCons; script itself. + + + + + + For end-to-end script tests, we have developed two modules to make + writing tests easy. The first, TestCmd.py, + is a generic module for + testing commands or scripts (in any language, not just Python). + + The second module, TestScons.py, + is a subclass of the generic + TestCmd.py module. + TestScons.py + takes care of initialization and + displaying error conditions + specific to testing &SCons;. + + + + + + In practice, simple tests only + need to initialize a test object, use the object to write some + input files, run &SCons;, and then check whatever criteria + determine whether the test passed or failed. A complete test of + the &Program; method, for example, looks like this: + + + + + test = TestSCons.TestSCons() + + test.write('SConstruct', + """env = Environment() + env.Program(target = 'foo', source = 'foo.c') + """) + + test.write('foo.c', + """ + int + main(int argc, char *argv[]) + { + argv[argc++] = "-"; /* dummy use of args */ + printf("foo.c successfully compiled\\n"); + exit (0); + } + """) + + test.run(arguments = 'foo') # runs SCons + + test.run(program = test.workpath('foo')) + + test.fail_test(test.stdout() != "foo.c successfully compiled\n") + + test.pass_test() + + +
+ +
+ +
+ SourceForge + + + + Registration of the &SCons; project was approved at SourceForge on + 29 June 2001. Within a week, the initial code base was checked in, + mailing lists were created, and the web site was set up. We started + making use of the task-list manager to track what we had to finish + for initial release. + + + + + + The obvious complication was how to use + structured testing methodology of Aegis when SourceForge uses + CVS for source control. Not using the SourceForge CVS tree would + have had two significant disadvantages: one, missing out on the + archiving and central location in the event of disaster; two, people + coming to the SourceForge project page wouldn't be able to browse + the source. The latter was particularly important in + the early stages of development, in order to avoid any impression + that this was Yet Another Project that starts with a bang and then + dwindles as the initial enthusiasm starts to wear off. + + + + + + The solution was to use the SourceForge CVS repository for read-only + access to the source. &SCons; developers are welcome to use CVS for + their development, but the changes are not + committed to the SourceForge repository. Instead, patches are sent + to the integrator for processing through Aegis. When the change + has been integrated into the Aegis repository, a home-brew + script translates the Aegis change into a virtual shell script + of commands that copy the necessary files from Aegis and check them + in to CVS at SourceForge. + + + + + + (In practice, write access is not actually disabled for registered + developers, but if they do make any changes directly at SourceForge, + they can be overwritten at the next Aegis update.) + + + +
diff --git a/doc/python10/scanner.eps b/doc/python10/scanner.eps new file mode 100644 index 0000000..35614f8 --- /dev/null +++ b/doc/python10/scanner.eps @@ -0,0 +1,168 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: build/doc/python10/scanner.fig +%%Creator: /usr/bin/fig2dev Version 3.2 Patchlevel 3d +%%CreationDate: Sun Jan 2 01:21:05 2005 +%%For: knight@casablanca.home.baldmt.com (Steven Knight) +%%BoundingBox: 0 0 398 200 +%%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 200 moveto 0 0 lineto 398 0 lineto 398 200 lineto closepath clip newpath +-17.3 360.7 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit + 0.06000 0.06000 sc +% +% Fig objects follow +% +% Polyline +7.500 slw +n 2700 5400 m 4500 5400 l 4500 6000 l 2700 6000 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +3000 5775 m +gs 1 -1 sc (ProgScanner) col0 sh gr +% Polyline +n 2700 4200 m 4500 4200 l 4500 4800 l 2700 4800 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +3225 4575 m +gs 1 -1 sc (Scanner) col0 sh gr +% Polyline +n 2700 3000 m 4500 3000 l 4500 3600 l 2700 3600 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +2925 3375 m +gs 1 -1 sc (Environment) col0 sh gr +% Polyline + [60] 0 sd +n 5100 5400 m 6900 5400 l 6900 6000 l 5100 6000 l + cp gs col0 s gr [] 0 sd +/Times-Roman ff 240.00 scf sf +5400 5775 m +gs 1 -1 sc (JavaScanner) col0 sh gr +% Polyline +n 300 5400 m 2100 5400 l 2100 6000 l 300 6000 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +750 5775 m +gs 1 -1 sc (CScanner) col0 sh gr +% Polyline +n 600 3300 m 1500 3300 l 1500 3900 l 600 3900 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +825 3675 m +gs 1 -1 sc (Node) col0 sh gr +% Polyline +n 1200 5400 m 1200 5100 l 6000 5100 l + 6000 5400 l gs col0 s gr +% Polyline +n 3600 4950 m + 3600 5400 l gs col0 s gr +% Polyline +n 3600 4800 m 3525 4950 l 3675 4950 l + cp gs col0 s gr +% Polyline +n 3600 3600 m 3560 3675 l 3600 3750 l 3640 3675 l + cp gs col0 s gr +% Polyline +n 1050 3900 m 1010 3975 l 1050 4050 l 1090 3975 l + cp gs col0 s gr +% Polyline +gs clippath +2715 4530 m 2715 4470 l 2564 4470 l 2684 4500 l 2564 4530 l cp +eoclip +n 1050 4050 m 1050 4500 l + 2700 4500 l gs col0 s gr gr + +% arrowhead +n 2564 4530 m 2684 4500 l 2564 4470 l 2564 4530 l cp gs 0.00 setgray ef gr col0 s +% Polyline +gs clippath +3570 4215 m 3630 4215 l 3630 4064 l 3600 4184 l 3570 4064 l cp +eoclip +n 3600 3750 m + 3600 4200 l gs col0 s gr gr + +% arrowhead +n 3570 4064 m 3600 4184 l 3630 4064 l 3570 4064 l cp gs 0.00 setgray ef gr col0 s +% Polyline + [60] 0 sd +n 3600 3000 m + 3600 2700 l gs col0 s gr [] 0 sd +$F2psEnd +rs diff --git a/doc/python10/scanner.fig b/doc/python10/scanner.fig new file mode 100644 index 0000000..01488e8 --- /dev/null +++ b/doc/python10/scanner.fig @@ -0,0 +1,57 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 2700 5400 4500 6000 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 5400 4500 5400 4500 6000 2700 6000 2700 5400 +4 0 0 50 0 0 16 0.0000 4 225 1245 3000 5775 ProgScanner\001 +-6 +6 2700 4200 4500 4800 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 4200 4500 4200 4500 4800 2700 4800 2700 4200 +4 0 0 50 0 0 16 0.0000 4 165 780 3225 4575 Scanner\001 +-6 +6 2700 3000 4500 3600 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 2700 3000 4500 3000 4500 3600 2700 3600 2700 3000 +4 0 0 50 0 0 16 0.0000 4 165 1290 2925 3375 Environment\001 +-6 +6 5100 5400 6900 6000 +2 2 1 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 5100 5400 6900 5400 6900 6000 5100 6000 5100 5400 +4 0 0 50 0 0 16 0.0000 4 165 1200 5400 5775 JavaScanner\001 +-6 +6 300 5400 2100 6000 +2 2 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 300 5400 2100 5400 2100 6000 300 6000 300 5400 +4 0 0 50 0 0 16 0.0000 4 165 945 750 5775 CScanner\001 +-6 +6 600 3300 1500 3900 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 600 3300 1500 3300 1500 3900 600 3900 600 3300 +4 0 0 50 0 0 16 0.0000 4 165 525 825 3675 Node\001 +-6 +2 1 0 1 0 7 50 0 -1 4.000 0 0 -1 0 0 4 + 1200 5400 1200 5100 6000 5100 6000 5400 +2 1 0 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 3600 4950 3600 5400 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 3600 4800 3525 4950 3675 4950 3600 4800 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 3600 3600 3560 3675 3600 3750 3640 3675 3600 3600 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 1050 3900 1010 3975 1050 4050 1090 3975 1050 3900 +2 1 0 1 0 7 50 0 -1 4.000 0 0 -1 1 0 3 + 1 1 1.00 60.00 120.00 + 1050 4050 1050 4500 2700 4500 +2 1 0 1 0 7 50 0 -1 4.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 3600 3750 3600 4200 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 3600 3000 3600 2700 diff --git a/doc/python10/scanner.jpg b/doc/python10/scanner.jpg new file mode 100644 index 0000000..08e5dcb Binary files /dev/null and b/doc/python10/scanner.jpg differ diff --git a/doc/python10/scons.mod b/doc/python10/scons.mod new file mode 100644 index 0000000..8df6d2c --- /dev/null +++ b/doc/python10/scons.mod @@ -0,0 +1,428 @@ + + + + + + +Aegis"> +Ant"> +Autoconf"> +Automake"> +cc"> +Cons"> +cp"> +csh"> +gcc"> +Jam"> +jar"> +javac"> +javah"> +Make"> +Make++"> +Python"> +ranlib"> +rmic"> +SCons"> +scons"> +ScCons"> +tar"> +touch"> +zip"> + + + + +Action"> +ActionBase"> +CommandAction"> +FunctionAction"> +ListAction"> +Builder"> +BuilderBase"> +CompositeBuilder"> +MultiStepBuilder"> +Job"> +Jobs"> +Serial"> +Parallel"> +Node"> +Node.FS"> +Scanner"> +Sig"> +Signature"> +Taskmaster"> +TimeStamp"> +Walker"> +Wrapper"> + + + + + +--debug=explain"> +--implicit-cache"> +--implicit-deps-changed"> +--implicit-deps-unchanged"> +-Q"> + + + +implicit_cache"> +implicit_deps_changed"> +implicit_deps_unchanged"> + + + + + +build"> +Makefile"> +Makefiles"> +SConscript"> +SConstruct"> +Sconstruct"> +sconstruct"> +.sconsign"> +src"> + + + + + +Add"> +AddOptions"> +Alias"> +Aliases"> +Append"> +BoolOption"> +Build"> +CacheDir"> +Clean"> +Clone"> +Command"> +Configure"> +Copy"> +Default"> +DefaultRules"> +Depends"> +Dir"> +Entry"> +EnumOption"> +Environment"> +Export"> +File"> +Finish"> +GenerateHelpText"> +Help"> +Ignore"> +Import"> +Install"> +InstallAs"> +Link"> +ListOption"> +Local"> +Module"> +Objects"> +Options"> +PackageOption"> +PathOption"> +Precious"> +Prepend"> +Replace"> +Repository"> +Return"> +RuleSet"> +Salt"> +SetBuildSignatureType"> +SetContentSignatureType"> +SourceSignature"> +SourceSignatures"> +Split"> +TargetSignatures"> +Task"> + + +subst"> + + +Message"> +Result"> +CheckCHeader"> +CheckCXXHeader"> +CheckFunc"> +CheckHeader"> +CheckLib"> +CheckLibWithHeader"> +CheckType"> +TryAction"> +TryBuild"> +TryCompile"> +TryLink"> +TryRun"> + + +str"> +zipfile"> + + +Cache"> + + + + + +ARGUMENTS"> +BUILD_TARGETS"> +COMMAND_LINE_TARGETS"> +DEFAULT_TARGETS"> + + + + + +BUILDERMAP"> +BUILDERS"> +CC"> +CCFLAGS"> +CCCOM"> +COLOR"> +COLORS"> +CONFIG"> +CPPDEFINES"> +ENV"> +JAVACLASSDIR"> +LIBDIRPREFIX"> +LIBDIRSUFFIX"> +LIBLINKPREFIX"> +LIBLINKSUFFIX"> +LIBPATH"> +LIBS"> +LINK"> +LINKCOM"> +LINKFLAGS"> +RELEASE"> +RELEASE_BUILD"> +SCANNERMAP"> +SCANNERS"> +TARFLAGS"> +TARSUFFIX"> + + + + + +PATH"> +PYTHONPATH"> +SCONSFLAGS"> + + + + + +allowed_values"> +build_dir"> +map"> +ignorecase"> +options"> +exports"> +source"> +target"> + + + + + +all"> +none"> + + + + + +BuildDir"> +CFile"> +CXXFile"> +DVI"> +Jar"> +Java"> +JavaH"> +Library"> +Object"> +PCH"> +PDF"> +PostScript"> +Program"> +RES"> +RMIC"> +SharedLibrary"> +SharedObject"> +StaticLibrary"> +StaticObject"> +Tar"> +Zip"> + + +Make"> + + + + + +builder function"> +builder method"> + +Configure Contexts"> +configure context"> + +Construction Environment"> +Construction Environments"> +Construction environment"> +Construction environments"> +construction environment"> +construction environments"> + +Construction Variable"> +Construction Variables"> +Construction variable"> +Construction variables"> +construction variable"> +construction variables"> + +CPPPATH"> + +Dictionary"> + +Emitter"> +emitter"> +Generator"> +generator"> + +Nodes"> + +signature"> +build signature"> + +true"> +false"> + +typedef"> + + + +bar"> +common1.c"> +common2.c"> +custom.py"> +goodbye"> +goodbye.o"> +goodbye.obj"> +file.dll"> +file.in"> +file.lib"> +file.o"> +file.obj"> +file.out"> +foo"> +foo.o"> +foo.obj"> +hello"> +hello.c"> +hello.exe"> +hello.h"> +hello.o"> +hello.obj"> +libfile_a"> +libfile_so"> +new_hello"> +new_hello.exe"> +prog"> +prog1"> +prog2"> +prog.c"> +prog.exe"> +stdio.h"> + + + ++"> +#"> + + + +announce@scons.tigris.org"> +dev@scons.tigris.org"> +users@scons.tigris.org"> diff --git a/doc/python10/sig.eps b/doc/python10/sig.eps new file mode 100644 index 0000000..26aabaa --- /dev/null +++ b/doc/python10/sig.eps @@ -0,0 +1,147 @@ +%!PS-Adobe-2.0 EPSF-2.0 +%%Title: build/doc/python10/sig.fig +%%Creator: /usr/bin/fig2dev Version 3.2 Patchlevel 3d +%%CreationDate: Sun Jan 2 01:21:05 2005 +%%For: knight@casablanca.home.baldmt.com (Steven Knight) +%%BoundingBox: 0 0 308 128 +%%Magnification: 1.0000 +%%EndComments +/$F2psDict 200 dict def +$F2psDict begin +$F2psDict /mtrx matrix put +/col-1 {0 setgray} bind def +/col0 {0.000 0.000 0.000 srgb} bind def +/col1 {0.000 0.000 1.000 srgb} bind def +/col2 {0.000 1.000 0.000 srgb} bind def +/col3 {0.000 1.000 1.000 srgb} bind def +/col4 {1.000 0.000 0.000 srgb} bind def +/col5 {1.000 0.000 1.000 srgb} bind def +/col6 {1.000 1.000 0.000 srgb} bind def +/col7 {1.000 1.000 1.000 srgb} bind def +/col8 {0.000 0.000 0.560 srgb} bind def +/col9 {0.000 0.000 0.690 srgb} bind def +/col10 {0.000 0.000 0.820 srgb} bind def +/col11 {0.530 0.810 1.000 srgb} bind def +/col12 {0.000 0.560 0.000 srgb} bind def +/col13 {0.000 0.690 0.000 srgb} bind def +/col14 {0.000 0.820 0.000 srgb} bind def +/col15 {0.000 0.560 0.560 srgb} bind def +/col16 {0.000 0.690 0.690 srgb} bind def +/col17 {0.000 0.820 0.820 srgb} bind def +/col18 {0.560 0.000 0.000 srgb} bind def +/col19 {0.690 0.000 0.000 srgb} bind def +/col20 {0.820 0.000 0.000 srgb} bind def +/col21 {0.560 0.000 0.560 srgb} bind def +/col22 {0.690 0.000 0.690 srgb} bind def +/col23 {0.820 0.000 0.820 srgb} bind def +/col24 {0.500 0.190 0.000 srgb} bind def +/col25 {0.630 0.250 0.000 srgb} bind def +/col26 {0.750 0.380 0.000 srgb} bind def +/col27 {1.000 0.500 0.500 srgb} bind def +/col28 {1.000 0.630 0.630 srgb} bind def +/col29 {1.000 0.750 0.750 srgb} bind def +/col30 {1.000 0.880 0.880 srgb} bind def +/col31 {1.000 0.840 0.000 srgb} bind def + +end +save +newpath 0 128 moveto 0 0 lineto 308 0 lineto 308 128 lineto closepath clip newpath +-71.3 288.7 translate +1 -1 scale + +/cp {closepath} bind def +/ef {eofill} bind def +/gr {grestore} bind def +/gs {gsave} bind def +/sa {save} bind def +/rs {restore} bind def +/l {lineto} bind def +/m {moveto} bind def +/rm {rmoveto} bind def +/n {newpath} bind def +/s {stroke} bind def +/sh {show} bind def +/slc {setlinecap} bind def +/slj {setlinejoin} bind def +/slw {setlinewidth} bind def +/srgb {setrgbcolor} bind def +/rot {rotate} bind def +/sc {scale} bind def +/sd {setdash} bind def +/ff {findfont} bind def +/sf {setfont} bind def +/scf {scalefont} bind def +/sw {stringwidth} bind def +/tr {translate} bind def +/tnt {dup dup currentrgbcolor + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add + 4 -2 roll dup 1 exch sub 3 -1 roll mul add srgb} + bind def +/shd {dup dup currentrgbcolor 4 -2 roll mul 4 -2 roll mul + 4 -2 roll mul srgb} bind def +/$F2psBegin {$F2psDict begin /$F2psEnteredState save def} def +/$F2psEnd {$F2psEnteredState restore end} def + +$F2psBegin +10 setmiterlimit + 0.06000 0.06000 sc +% +% Fig objects follow +% +% Polyline +7.500 slw +n 1200 3000 m 2700 3000 l 2700 3600 l 1200 3600 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +1950 3375 m +gs 1 -1 sc (Taskmaster) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 3300 4200 m 4500 4200 l 4500 4800 l 3300 4800 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +3900 4575 m +gs 1 -1 sc (MD5) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 5100 4200 m 6300 4200 l 6300 4800 l 5100 4800 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +5700 4575 m +gs 1 -1 sc (TStamp) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 4200 3000 m 5400 3000 l 5400 3600 l 4200 3600 l + cp gs col0 s gr +/Times-Roman ff 240.00 scf sf +4800 3375 m +gs 1 -1 sc (Sig) dup sw pop 2 div neg 0 rm col0 sh gr +% Polyline +n 2700 3300 m 2775 3340 l 2850 3300 l 2775 3260 l + cp gs col0 s gr +% Polyline +n 4800 3600 m 4725 3750 l 4875 3750 l + cp gs col0 s gr +% Polyline +n 3900 4200 m 3900 3900 l 5700 3900 l + 5700 4200 l gs col0 s gr +% Polyline +n 4800 3750 m + 4800 3900 l gs col0 s gr +% Polyline +gs clippath +4215 3330 m 4215 3270 l 4064 3270 l 4184 3300 l 4064 3330 l cp +eoclip +n 2850 3300 m + 4200 3300 l gs col0 s gr gr + +% arrowhead +n 4064 3330 m 4184 3300 l 4064 3270 l 4064 3330 l cp gs 0.00 setgray ef gr col0 s +% Polyline + [60] 0 sd +n 1950 3000 m + 1950 2700 l gs col0 s gr [] 0 sd +% Polyline + [60] 0 sd +n 4800 3000 m + 4800 2700 l gs col0 s gr [] 0 sd +$F2psEnd +rs diff --git a/doc/python10/sig.fig b/doc/python10/sig.fig new file mode 100644 index 0000000..823d59e --- /dev/null +++ b/doc/python10/sig.fig @@ -0,0 +1,44 @@ +#FIG 3.2 +Landscape +Center +Inches +Letter +100.00 +Single +-2 +1200 2 +6 1200 3000 2700 3600 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 1200 3000 2700 3000 2700 3600 1200 3600 1200 3000 +4 1 0 50 0 0 16 0.0000 4 165 1125 1950 3375 Taskmaster\001 +-6 +6 3300 4200 4500 4800 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 3300 4200 4500 4200 4500 4800 3300 4800 3300 4200 +4 1 0 50 0 0 16 0.0000 4 165 525 3900 4575 MD5\001 +-6 +6 5100 4200 6300 4800 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 5100 4200 6300 4200 6300 4800 5100 4800 5100 4200 +4 1 0 50 0 0 16 0.0000 4 225 780 5700 4575 TStamp\001 +-6 +6 4200 3000 5400 3600 +2 2 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 5 + 4200 3000 5400 3000 5400 3600 4200 3600 4200 3000 +4 1 0 50 0 0 16 0.0000 4 225 330 4800 3375 Sig\001 +-6 +2 3 0 1 0 7 50 0 -1 4.000 0 0 7 0 0 5 + 2700 3300 2775 3340 2850 3300 2775 3260 2700 3300 +2 3 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 4 + 4800 3600 4725 3750 4875 3750 4800 3600 +2 1 0 1 0 7 50 0 -1 0.000 0 0 7 0 0 4 + 3900 4200 3900 3900 5700 3900 5700 4200 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 0 0 2 + 4800 3750 4800 3900 +2 1 0 1 0 7 50 0 -1 0.000 0 0 -1 1 0 2 + 1 1 1.00 60.00 120.00 + 2850 3300 4200 3300 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 1950 3000 1950 2700 +2 1 1 1 0 7 50 0 -1 4.000 0 0 -1 0 0 2 + 4800 3000 4800 2700 diff --git a/doc/python10/sig.jpg b/doc/python10/sig.jpg new file mode 100644 index 0000000..0c7e0df Binary files /dev/null and b/doc/python10/sig.jpg differ diff --git a/doc/reference/Alias.xml b/doc/reference/Alias.xml new file mode 100644 index 0000000..b87967d --- /dev/null +++ b/doc/reference/Alias.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &Alias; Method + + + + X + + + +
diff --git a/doc/reference/CFile.xml b/doc/reference/CFile.xml new file mode 100644 index 0000000..f76c390 --- /dev/null +++ b/doc/reference/CFile.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &CFile; Method + + + + X + + + +
diff --git a/doc/reference/CXXFile.xml b/doc/reference/CXXFile.xml new file mode 100644 index 0000000..c1c038e --- /dev/null +++ b/doc/reference/CXXFile.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &CXXFile; Method + + + + X + + + +
diff --git a/doc/reference/Command.xml b/doc/reference/Command.xml new file mode 100644 index 0000000..abb3a58 --- /dev/null +++ b/doc/reference/Command.xml @@ -0,0 +1,73 @@ + + + + + + + X + + + +
+ The &Command; Method + + + + X + + + +
diff --git a/doc/reference/Install.xml b/doc/reference/Install.xml new file mode 100644 index 0000000..2d06e3b --- /dev/null +++ b/doc/reference/Install.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &Install; Method + + + + X + + + +
diff --git a/doc/reference/InstallAs.xml b/doc/reference/InstallAs.xml new file mode 100644 index 0000000..ed8cb78 --- /dev/null +++ b/doc/reference/InstallAs.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &InstallAs; Method + + + + X + + + +
diff --git a/doc/reference/Library.xml b/doc/reference/Library.xml new file mode 100644 index 0000000..19a3e96 --- /dev/null +++ b/doc/reference/Library.xml @@ -0,0 +1,152 @@ + + + + +
+ Linking With a Library + + + env = Environment(CC = 'gcc', + LIBS = 'world') + env.Program('hello.c') + + + + % scons + gcc -c hello.c -o hello.o + gcc -c world.c -o world.o + gcc -o hello hello.o -lworld + + +
+ +
+ Creating a Library + + + env = Environment(CC = 'gcc', + LIBS = 'world') + env.Program('hello.c') + env.Library('world.c') + + + + % scons + gcc -c hello.c -o hello.o + gcc -c world.c -o world.o + ar r libworld.a world.o + ar: creating libworld.a + ranlib libworld.a + gcc -o hello hello.o libworld.a + + +
+ + + +
+ The &Library; Builder + + + + X + + + +
diff --git a/doc/reference/MANIFEST b/doc/reference/MANIFEST new file mode 100644 index 0000000..438aada --- /dev/null +++ b/doc/reference/MANIFEST @@ -0,0 +1,21 @@ +Alias.xml +CFile.xml +CXXFile.xml +Command.xml +Install.xml +InstallAs.xml +Library.xml +Object.xml +PCH.xml +PDF.xml +PostScript.xml +Program.xml +RES.xml +SharedLibrary.xml +SharedObject.xml +StaticLibrary.xml +StaticObject.xml +copyright.xml +errors.xml +main.xml +preface.xml diff --git a/doc/reference/Object.xml b/doc/reference/Object.xml new file mode 100644 index 0000000..9e887d8 --- /dev/null +++ b/doc/reference/Object.xml @@ -0,0 +1,71 @@ + + + + + + + X + + + +
+ The &Object; Method + + + + X + + + +
diff --git a/doc/reference/PCH.xml b/doc/reference/PCH.xml new file mode 100644 index 0000000..b2a4d75 --- /dev/null +++ b/doc/reference/PCH.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &PCH; Method + + + + X + + + +
diff --git a/doc/reference/PDF.xml b/doc/reference/PDF.xml new file mode 100644 index 0000000..b3a25dc --- /dev/null +++ b/doc/reference/PDF.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &PDF; Method + + + + X + + + +
diff --git a/doc/reference/PostScript.xml b/doc/reference/PostScript.xml new file mode 100644 index 0000000..f5a6579 --- /dev/null +++ b/doc/reference/PostScript.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &PostScript; Method + + + + X + + + +
diff --git a/doc/reference/Program.xml b/doc/reference/Program.xml new file mode 100644 index 0000000..30f90d2 --- /dev/null +++ b/doc/reference/Program.xml @@ -0,0 +1,77 @@ + + + + + + + X + + + +
+ The &Program; Builder + + + + X + + + +
diff --git a/doc/reference/RES.xml b/doc/reference/RES.xml new file mode 100644 index 0000000..15c0aea --- /dev/null +++ b/doc/reference/RES.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &RES; Method + + + + X + + + +
diff --git a/doc/reference/SharedLibrary.xml b/doc/reference/SharedLibrary.xml new file mode 100644 index 0000000..603dab1 --- /dev/null +++ b/doc/reference/SharedLibrary.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &SharedLibrary; Method + + + + X + + + +
diff --git a/doc/reference/SharedObject.xml b/doc/reference/SharedObject.xml new file mode 100644 index 0000000..0860769 --- /dev/null +++ b/doc/reference/SharedObject.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &SharedObject; Method + + + + X + + + +
diff --git a/doc/reference/StaticLibrary.xml b/doc/reference/StaticLibrary.xml new file mode 100644 index 0000000..ea7ae5b --- /dev/null +++ b/doc/reference/StaticLibrary.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &StaticLibrary; Method + + + + X + + + +
diff --git a/doc/reference/StaticObject.xml b/doc/reference/StaticObject.xml new file mode 100644 index 0000000..ff8dae8 --- /dev/null +++ b/doc/reference/StaticObject.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ The &StaticObject; Method + + + + X + + + +
diff --git a/doc/reference/copyright.xml b/doc/reference/copyright.xml new file mode 100644 index 0000000..7f6059c --- /dev/null +++ b/doc/reference/copyright.xml @@ -0,0 +1,32 @@ + + +
+ + + SCons User's Guide Copyright (c) 2003 Steven Knight + + +
diff --git a/doc/reference/errors.xml b/doc/reference/errors.xml new file mode 100644 index 0000000..448777f --- /dev/null +++ b/doc/reference/errors.xml @@ -0,0 +1,41 @@ + + + + + X + + + +
+ X + + + + X + + + +
diff --git a/doc/reference/main.xml b/doc/reference/main.xml new file mode 100644 index 0000000..ed122f6 --- /dev/null +++ b/doc/reference/main.xml @@ -0,0 +1,207 @@ + + + + + + %version; + + + %scons; + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + SCons Reference Manual &buildversion; + + + Steven + Knight + + + Revision &buildrevision; (&builddate;) + + 2003 + + + 2003 + Steven Knight + + + + ©right; + + + version &buildversion; + + + + + Preface + &preface; + + + + Builder Reference + +
+ The Alias Builder + &Alias_file; +
+ +
+ The CFile Builder + &CFile_file; +
+ +
+ The Command Builder + &Command_file; +
+ +
+ The CXXFile Builder + &CXXFile_file; +
+ +
+ The Install Builder + &Install_file; +
+ +
+ The InstallAs Builder + &InstallAs_file; +
+ +
+ The Library Builder + &Library_file; +
+ +
+ The Object Builder + &Object_file; +
+ +
+ The PCH Builder + &PCH_file; +
+ +
+ The PDF Builder + &PDF_file; +
+ +
+ The PDF Builder + &PostScript_file; +
+ +
+ The Program Builder + &Program_file; +
+ +
+ The RES Builder + &RES_file; +
+ +
+ The SharedLibrary Builder + &SharedLibrary_file; +
+ +
+ The SharedObject Builder + &SharedObject_file; +
+ +
+ The StaticLibrary Builder + &StaticLibrary_file; +
+ +
+ The StaticObject Builder + &StaticObject_file; +
+ +
+ + + &ConsVar; Reference + +
+ AR + + + + X + + + +
+ +
+ + + Errors Generated by &SCons; + &errors; + + +
diff --git a/doc/reference/preface.xml b/doc/reference/preface.xml new file mode 100644 index 0000000..82ea44a --- /dev/null +++ b/doc/reference/preface.xml @@ -0,0 +1,85 @@ + + + + + X + + + +
+ Why &SCons;? + + + + X + + + +
+ +
+ History + + + + X + + + +
+ +
+ Conventions + + + + X + + + +
+ +
+ Acknowledgements + + + + X + + + +
+ +
+ Contact + + + + X + + + +
diff --git a/doc/scons.mod b/doc/scons.mod new file mode 100644 index 0000000..8ac58ca --- /dev/null +++ b/doc/scons.mod @@ -0,0 +1,521 @@ + + + + + + +Aegis"> +Ant"> +ar"> +as"> +Autoconf"> +Automake"> +bison"> +cc"> +Cons"> +cp"> +csh"> +f77"> +f90"> +f95"> +gas"> +gcc"> +g77"> +gXX"> +Jam"> +jar"> +javac"> +javah"> +latex"> +lex"> +m4"> +Make"> +Make++"> +pdflatex"> +pdftex"> +Python"> +ranlib"> +rmic"> +SCons"> +ScCons"> +swig"> +tar"> +tex"> +touch"> +yacc"> +zip"> + + + + +Action"> +ActionBase"> +CommandAction"> +FunctionAction"> +ListAction"> +Builder"> +BuilderBase"> +CompositeBuilder"> +MultiStepBuilder"> +Job"> +Jobs"> +Serial"> +Parallel"> +Node"> +Node.FS"> +Scanner"> +Sig"> +Signature"> +Taskmaster"> +TimeStamp"> +Walker"> +Wrapper"> + + + + + +--config"> +--debug=explain"> +--debug=findlibs"> +--debug=includes"> +--debug=presub"> +--debug=stacktrace"> +--implicit-cache"> +--implicit-deps-changed"> +--implicit-deps-unchanged"> +--profile"> +--taskmastertrace"> +--tree"> +-Q"> + + + +implicit_cache"> +implicit_deps_changed"> +implicit_deps_unchanged"> + + + + + +build"> +Makefile"> +Makefiles"> +scons"> +SConscript"> +SConstruct"> +Sconstruct"> +sconstruct"> +.sconsign"> +src"> + + + + + +Add"> +AddMethod"> +AddPostAction"> +AddPreAction"> +AddOption"> +AddOptions"> +AddVariables"> +Alias"> +Aliases"> +AlwaysBuild"> +Append"> +AppendENVPath"> +AppendUnique"> +BoolOption"> +BoolVariable"> +Build"> +CacheDir"> +Chmod"> +Clean"> +Clone"> +Command"> +Configure"> +Copy"> +Decider"> +Default"> +DefaultEnvironment"> +DefaultRules"> +Delete"> +Depends"> +Dir"> +Dump"> +Entry"> +EnumOption"> +EnumVariable"> +EnsurePythonVersion"> +EnsureSConsVersion"> +Environment"> +Execute"> +Exit"> +Export"> +File"> +FindFile"> +FindInstalledFiles"> +Finish"> +Flatten"> +GenerateHelpText"> +GetBuildFailures"> +GetLaunchDir"> +GetOption"> +Glob"> +Help"> +Ignore"> +Import"> +Install"> +InstallAs"> +Link"> +ListOption"> +ListVariable"> +Local"> +MergeFlags"> +Mkdir"> +Module"> +Move"> +NoClean"> +NoCache"> +Objects"> +Options"> +Variables"> +PackageOption"> +PackageVariable"> +ParseConfig"> +ParseDepends"> +ParseFlags"> +PathOption"> +PathOption.PathAccept"> +PathOption.PathExists"> +PathOption.PathIsDir"> +PathOption.PathIsDirCreate"> +PathOption.PathIsFile"> +PathVariable"> +PathVariable.PathAccept"> +PathVariable.PathExists"> +PathVariable.PathIsDir"> +PathVariable.PathIsDirCreate"> +PathVariable.PathIsFile"> +Precious"> +Prepend"> +PrependENVPath"> +PrependUnique"> +Progress"> +Replace"> +Repository"> +Requires"> +Return"> +RuleSet"> +Salt"> +SetBuildSignatureType"> +SetContentSignatureType"> +SetDefault"> +SetOption"> +SideEffect"> +SourceSignature"> +SourceSignatures"> +Split"> +Tag"> +TargetSignatures"> +Task"> +Touch"> +UnknownOptions"> +UnknownVariables"> + + +subst"> + + +Message"> +Result"> +CheckCHeader"> +CheckCXXHeader"> +CheckFunc"> +CheckHeader"> +CheckLib"> +CheckLibWithHeader"> +CheckType"> +TryAction"> +TryBuild"> +TryCompile"> +TryLink"> +TryRun"> + + + +str"> +zipfile"> + + +Cache"> + + + + + +ARGLIST"> +ARGUMENTS"> +BUILD_TARGETS"> +COMMAND_LINE_TARGETS"> +DEFAULT_TARGETS"> + + + + + +BUILDERMAP"> +COLOR"> +COLORS"> +CONFIG"> +CPPDEFINES"> +RELEASE"> +RELEASE_BUILD"> +SCANNERMAP"> +TARFLAGS"> +TARSUFFIX"> + + + + + +PATH"> +PYTHONPATH"> +SCONSFLAGS"> + + + + + +allowed_values"> +build_dir"> +map"> +ignorecase"> +options"> +exports"> +source"> +target"> +variables"> +variant_dir"> + + + + + +all"> +none"> + + + + + +BuildDir"> +CFile"> +CXXFile"> +DVI"> +Jar"> +Java"> +JavaH"> +Library"> +Object"> +PCH"> +PDF"> +PostScript"> +Program"> +RES"> +RMIC"> +SharedLibrary"> +SharedObject"> +StaticLibrary"> +StaticObject"> +Substfile"> +Tar"> +Textfile"> +VariantDir"> +Zip"> + + +Make"> + + + + + +builder function"> +build action"> +build actions"> +builder method"> + +Configure Contexts"> +configure context"> + +Construction Environment"> +Construction Environments"> +Construction environment"> +Construction environments"> +construction environment"> +construction environments"> + +Construction Variable"> +Construction Variables"> +Construction variable"> +Construction variables"> +construction variable"> +construction variables"> + +CPPPATH"> + +Dictionary"> + +Emitter"> +emitter"> + +factory"> + +Generator"> +generator"> + +Nodes"> + +signature"> +build signature"> + +true"> +false"> + +typedef"> + + + +action="> +batch_key="> +cmdstr="> +exitstatfunc="> +strfunction="> +varlist="> + + + +bar"> +common1.c"> +common2.c"> +custom.py"> +goodbye"> +goodbye.o"> +goodbye.obj"> +file.dll"> +file.in"> +file.lib"> +file.o"> +file.obj"> +file.out"> +foo"> +foo.o"> +foo.obj"> +hello"> +hello.c"> +hello.exe"> +hello.h"> +hello.o"> +hello.obj"> +libfile_a"> +libfile_so"> +new_hello"> +new_hello.exe"> +prog"> +prog1"> +prog2"> +prog.c"> +prog.exe"> +stdio.h"> + + + ++"> +#"> + + + +announce@scons.tigris.org"> +dev@scons.tigris.org"> +users@scons.tigris.org"> diff --git a/doc/user/MANIFEST b/doc/user/MANIFEST new file mode 100644 index 0000000..ef273d3 --- /dev/null +++ b/doc/user/MANIFEST @@ -0,0 +1,50 @@ +actions.xml +add-method.xml +alias.xml +ant.xml +builders.xml +builders-built-in.xml +builders-commands.xml +builders-writing.xml +build-install.xml +caching.xml +command-line.xml +cons.pl +copyright.xml +depends.xml +environments.xml +errors.xml +example.xml +factories.xml +file-removal.xml +hierarchy.xml +install.xml +java.xml +libraries.xml +less-simple.xml +main.xml +make.xml +mergeflags.xml +misc.xml +nodes.xml +output.xml +parseconfig.xml +parseflags.xml +preface.xml +python.xml +repositories.xml +run.xml +scanners.xml +sconf.xml +separate.xml +simple.xml +sourcecode.xml +tasks.xml +tools.xml +troubleshoot.xml +variants.xml +variables.xml +SCons-win32-install-1.jpg +SCons-win32-install-2.jpg +SCons-win32-install-3.jpg +SCons-win32-install-4.jpg diff --git a/doc/user/README b/doc/user/README new file mode 100644 index 0000000..aef479a --- /dev/null +++ b/doc/user/README @@ -0,0 +1,21 @@ +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation + +When adding a new file, add it to main.xml and MANIFEST. + +To build the .xml files from the .in files: + scons -D BUILDDOC=1 foo.xml +To build the whole PDF doc from this dir, for testing: + scons -D ../../build/doc/PDF/scons-user.pdf + +Writing examples: here's a simple template. + + + + env = Environment() + print env.Dump("CC") + + + + + scons -Q + diff --git a/doc/user/SCons-win32-install-1.jpg b/doc/user/SCons-win32-install-1.jpg new file mode 100644 index 0000000..ecc439d Binary files /dev/null and b/doc/user/SCons-win32-install-1.jpg differ diff --git a/doc/user/SCons-win32-install-2.jpg b/doc/user/SCons-win32-install-2.jpg new file mode 100644 index 0000000..f468526 Binary files /dev/null and b/doc/user/SCons-win32-install-2.jpg differ diff --git a/doc/user/SCons-win32-install-3.jpg b/doc/user/SCons-win32-install-3.jpg new file mode 100644 index 0000000..90d2ed4 Binary files /dev/null and b/doc/user/SCons-win32-install-3.jpg differ diff --git a/doc/user/SCons-win32-install-4.jpg b/doc/user/SCons-win32-install-4.jpg new file mode 100644 index 0000000..d37973b Binary files /dev/null and b/doc/user/SCons-win32-install-4.jpg differ diff --git a/doc/user/actions.in b/doc/user/actions.in new file mode 100644 index 0000000..1ea5c49 --- /dev/null +++ b/doc/user/actions.in @@ -0,0 +1,404 @@ + + + + + + + &SCons; supports several types of &build_actions; + that can be performed to build one or more target files. + Usually, a &build_action; is a command-line string + that invokes an external command. + A build action can also be an external command + specified as a list of arguments, + or even a Python function. + + + + + + Build action objects are created by the &Action; function. + This function is, in fact, what &SCons; uses + to interpret the &action; + keyword argument when you call the &Builder; function. + So the following line that creates a simple Builder: + + + + + b = Builder(action = 'build < $SOURCE > $TARGET') + + + + + Is equivalent to: + + + + + b = Builder(action = Action('build < $SOURCE > $TARGET')) + + + + + The advantage of using the &Action; function directly + is that it can take a number of additional options + to modify the action's behavior in many useful ways. + + + +
+ Command Strings as Actions + +
+ Suppressing Command-Line Printing + + + + XXX + + + +
+ +
+ Ignoring Exit Status + + + + XXX + + + +
+ +
+ +
+ Argument Lists as Actions + + + + XXX + + + +
+ +
+ Python Functions as Actions + + + + XXX + + + +
+ +
+ Modifying How an Action is Printed + +
+ XXX: the &strfunction; keyword argument + + + + XXX + + + +
+ +
+ XXX: the &cmdstr; keyword argument + + + + XXX + + + +
+ +
+ +
+ Making an Action Depend on Variable Contents: the &varlist; keyword argument + + + + XXX + + + +
+ +
+ chdir=1 + + + + XXX + + + +
+ +
+ Batch Building of Multiple Targets from Separate Sources: the &batch_key; keyword argument + + + + XXX + + + +
+ +
+ Manipulating the Exit Status of an Action: the &exitstatfunc; keyword argument + + + + XXX + + + +
+ + diff --git a/doc/user/actions.xml b/doc/user/actions.xml new file mode 100644 index 0000000..c3b8cf9 --- /dev/null +++ b/doc/user/actions.xml @@ -0,0 +1,404 @@ + + + + + + + &SCons; supports several types of &build_actions; + that can be performed to build one or more target files. + Usually, a &build_action; is a command-line string + that invokes an external command. + A build action can also be an external command + specified as a list of arguments, + or even a Python function. + + + + + + Build action objects are created by the &Action; function. + This function is, in fact, what &SCons; uses + to interpret the &action; + keyword argument when you call the &Builder; function. + So the following line that creates a simple Builder: + + + + + b = Builder(action = 'build < $SOURCE > $TARGET') + + + + + Is equivalent to: + + + + + b = Builder(action = Action('build < $SOURCE > $TARGET')) + + + + + The advantage of using the &Action; function directly + is that it can take a number of additional options + to modify the action's behavior in many useful ways. + + + +
+ Command Strings as Actions + +
+ Suppressing Command-Line Printing + + + + XXX + + + +
+ +
+ Ignoring Exit Status + + + + XXX + + + +
+ +
+ +
+ Argument Lists as Actions + + + + XXX + + + +
+ +
+ Python Functions as Actions + + + + XXX + + + +
+ +
+ Modifying How an Action is Printed + +
+ XXX: the &strfunction; keyword argument + + + + XXX + + + +
+ +
+ XXX: the &cmdstr; keyword argument + + + + XXX + + + +
+ +
+ +
+ Making an Action Depend on Variable Contents: the &varlist; keyword argument + + + + XXX + + + +
+ +
+ chdir=1 + + + + XXX + + + +
+ +
+ Batch Building of Multiple Targets from Separate Sources: the &batch_key; keyword argument + + + + XXX + + + +
+ +
+ Manipulating the Exit Status of an Action: the &exitstatfunc; keyword argument + + + + XXX + + + +
+ + diff --git a/doc/user/add-method.in b/doc/user/add-method.in new file mode 100644 index 0000000..61f13cc --- /dev/null +++ b/doc/user/add-method.in @@ -0,0 +1,127 @@ + + + + + The &AddMethod; function is used to add a method + to an environment. It's typically used to add a "pseudo-builder," + a function that looks like a &Builder; but + wraps up calls to multiple other &Builder;s + or otherwise processes its arguments + before calling one or more &Builder;s. + In the following example, + we want to install the program into the standard + /usr/bin directory hierarchy, + but also copy it into a local install/bin + directory from which a package might be built: + + + + + + def install_in_bin_dirs(env, source): + """Install source in both bin dirs""" + i1 = env.Install("$BIN", source) + i2 = env.Install("$LOCALBIN", source) + return [i1[0], i2[0]] # Return a list, like a normal builder + env = Environment(BIN='__ROOT__/usr/bin', LOCALBIN='#install/bin') + env.AddMethod(install_in_bin_dirs, "InstallInBinDirs") + env.InstallInBinDirs(Program('hello.c')) # installs hello in both bin dirs + + + int main() { printf("Hello, world!\n"); } + + + + + This produces the following: + + + + scons -Q / + + + + + As mentioned, a psuedo-builder also provides more flexibility + in parsing arguments than you can get with a &Builder;. + The next example shows a pseudo-builder with a + named argument that modifies the filename, and a separate argument + for the resource file (rather than having the builder figure it out + by file extension). This example also demonstrates using the global + &AddMethod; function to add a method to the global Environment class, + so it will be used in all subsequently created environments. + + + + + + def BuildTestProg(env, testfile, resourcefile, testdir="tests"): + """Build the test program; + prepends "test_" to src and target, + and puts target into testdir.""" + srcfile = "test_%s.c" % testfile + target = "%s/test_%s" % (testdir, testfile) + if env['PLATFORM'] == 'win32': + resfile = env.RES(resourcefile) + p = env.Program(target, [srcfile, resfile]) + else: + p = env.Program(target, srcfile) + return p + AddMethod(Environment, BuildTestProg) + + env = Environment() + env.BuildTestProg('stuff', resourcefile='res.rc') + + + int main() { printf("Hello, world!\n"); } + + + res.rc + + + + + This produces the following on Linux: + + + + scons -Q + + + + And the following on Windows: + + + + scons -Q + + + + Using &AddMethod; is better than just adding an instance method + to a &consenv; because it gets called as a proper method, + and because &AddMethod; provides for copying the method + to any clones of the &consenv; instance. + diff --git a/doc/user/add-method.xml b/doc/user/add-method.xml new file mode 100644 index 0000000..761765f --- /dev/null +++ b/doc/user/add-method.xml @@ -0,0 +1,135 @@ + + + + + The &AddMethod; function is used to add a method + to an environment. It's typically used to add a "pseudo-builder," + a function that looks like a &Builder; but + wraps up calls to multiple other &Builder;s + or otherwise processes its arguments + before calling one or more &Builder;s. + In the following example, + we want to install the program into the standard + /usr/bin directory hierarchy, + but also copy it into a local install/bin + directory from which a package might be built: + + + + + def install_in_bin_dirs(env, source): + """Install source in both bin dirs""" + i1 = env.Install("$BIN", source) + i2 = env.Install("$LOCALBIN", source) + return [i1[0], i2[0]] # Return a list, like a normal builder + env = Environment(BIN='/usr/bin', LOCALBIN='#install/bin') + env.AddMethod(install_in_bin_dirs, "InstallInBinDirs") + env.InstallInBinDirs(Program('hello.c')) # installs hello in both bin dirs + + + + This produces the following: + + + + % scons -Q / + cc -o hello.o -c hello.c + cc -o hello hello.o + Install file: "hello" as "/usr/bin/hello" + Install file: "hello" as "install/bin/hello" + + + + + As mentioned, a psuedo-builder also provides more flexibility + in parsing arguments than you can get with a &Builder;. + The next example shows a pseudo-builder with a + named argument that modifies the filename, and a separate argument + for the resource file (rather than having the builder figure it out + by file extension). This example also demonstrates using the global + &AddMethod; function to add a method to the global Environment class, + so it will be used in all subsequently created environments. + + + + + def BuildTestProg(env, testfile, resourcefile, testdir="tests"): + """Build the test program; + prepends "test_" to src and target, + and puts target into testdir.""" + srcfile = "test_%s.c" % testfile + target = "%s/test_%s" % (testdir, testfile) + if env['PLATFORM'] == 'win32': + resfile = env.RES(resourcefile) + p = env.Program(target, [srcfile, resfile]) + else: + p = env.Program(target, srcfile) + return p + AddMethod(Environment, BuildTestProg) + + env = Environment() + env.BuildTestProg('stuff', resourcefile='res.rc') + + + + This produces the following on Linux: + + + + % scons -Q + cc -o test_stuff.o -c test_stuff.c + cc -o tests/test_stuff test_stuff.o + + + + And the following on Windows: + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + rc /fores.res res.rc + cl /Fotest_stuff.obj /c test_stuff.c /nologo + link /nologo /OUT:tests\test_stuff.exe test_stuff.obj res.res + + + + Using &AddMethod; is better than just adding an instance method + to a &consenv; because it gets called as a proper method, + and because &AddMethod; provides for copying the method + to any clones of the &consenv; instance. + diff --git a/doc/user/alias.in b/doc/user/alias.in new file mode 100644 index 0000000..45a1534 --- /dev/null +++ b/doc/user/alias.in @@ -0,0 +1,102 @@ + + + + + We've already seen how you can use the &Alias; + function to create a target named install: + + + + + + env = Environment() + hello = env.Program('hello.c') + env.Install('__ROOT__/usr/bin', hello) + env.Alias('install', '__ROOT__/usr/bin') + + + int main() { printf("Hello, world!\n"); } + + + + + + You can then use this alias on the command line + to tell &SCons; more naturally that you want to install files: + + + + + scons -Q install + + + + + Like other &Builder; methods, though, + the &Alias; method returns an object + representing the alias being built. + You can then use this object as input to anothother &Builder;. + This is especially useful if you use such an object + as input to another call to the &Alias; &Builder;, + allowing you to create a hierarchy + of nested aliases: + + + + + + env = Environment() + p = env.Program('foo.c') + l = env.Library('bar.c') + env.Install('__ROOT__/usr/bin', p) + env.Install('__ROOT__/usr/lib', l) + ib = env.Alias('install-bin', '__ROOT__/usr/bin') + il = env.Alias('install-lib', '__ROOT__/usr/lib') + env.Alias('install', [ib, il]) + + + int main() { printf("foo.c\n"); } + + + void bar() { printf("bar.c\n"); } + + + + + + This example defines separate install, + install-bin, + and install-lib aliases, + allowing you finer control over what gets installed: + + + + + scons -Q install-bin + scons -Q install-lib + scons -Q -c __ROOT__/ + scons -Q install + diff --git a/doc/user/alias.xml b/doc/user/alias.xml new file mode 100644 index 0000000..04ebd5f --- /dev/null +++ b/doc/user/alias.xml @@ -0,0 +1,112 @@ + + + + + We've already seen how you can use the &Alias; + function to create a target named install: + + + + + env = Environment() + hello = env.Program('hello.c') + env.Install('/usr/bin', hello) + env.Alias('install', '/usr/bin') + + + + + You can then use this alias on the command line + to tell &SCons; more naturally that you want to install files: + + + + + % scons -Q install + cc -o hello.o -c hello.c + cc -o hello hello.o + Install file: "hello" as "/usr/bin/hello" + + + + + Like other &Builder; methods, though, + the &Alias; method returns an object + representing the alias being built. + You can then use this object as input to anothother &Builder;. + This is especially useful if you use such an object + as input to another call to the &Alias; &Builder;, + allowing you to create a hierarchy + of nested aliases: + + + + + env = Environment() + p = env.Program('foo.c') + l = env.Library('bar.c') + env.Install('/usr/bin', p) + env.Install('/usr/lib', l) + ib = env.Alias('install-bin', '/usr/bin') + il = env.Alias('install-lib', '/usr/lib') + env.Alias('install', [ib, il]) + + + + + This example defines separate install, + install-bin, + and install-lib aliases, + allowing you finer control over what gets installed: + + + + + % scons -Q install-bin + cc -o foo.o -c foo.c + cc -o foo foo.o + Install file: "foo" as "/usr/bin/foo" + % scons -Q install-lib + cc -o bar.o -c bar.c + ar rc libbar.a bar.o + ranlib libbar.a + Install file: "libbar.a" as "/usr/lib/libbar.a" + % scons -Q -c / + Removed foo.o + Removed foo + Removed /usr/bin/foo + Removed bar.o + Removed libbar.a + Removed /usr/lib/libbar.a + % scons -Q install + cc -o foo.o -c foo.c + cc -o foo foo.o + Install file: "foo" as "/usr/bin/foo" + cc -o bar.o -c bar.c + ar rc libbar.a bar.o + ranlib libbar.a + Install file: "libbar.a" as "/usr/lib/libbar.a" + diff --git a/doc/user/ant.in b/doc/user/ant.in new file mode 100644 index 0000000..41b441d --- /dev/null +++ b/doc/user/ant.in @@ -0,0 +1,52 @@ + + + + + XXX + + + +
+ Differences Between &Ant; and &SCons; + + + + XXX + + + +
+ +
+ Advantages of &SCons; Over &Ant; + + + + XXX + + + +
diff --git a/doc/user/ant.xml b/doc/user/ant.xml new file mode 100644 index 0000000..41b441d --- /dev/null +++ b/doc/user/ant.xml @@ -0,0 +1,52 @@ + + + + + XXX + + + +
+ Differences Between &Ant; and &SCons; + + + + XXX + + + +
+ +
+ Advantages of &SCons; Over &Ant; + + + + XXX + + + +
diff --git a/doc/user/build-install.in b/doc/user/build-install.in new file mode 100644 index 0000000..ab289b1 --- /dev/null +++ b/doc/user/build-install.in @@ -0,0 +1,719 @@ + + + + + This chapter will take you through the basic steps + of installing &SCons; on your system, + and building &SCons; if you don't have a + pre-built package available + (or simply prefer the flexibility of building it yourself). + Before that, however, this chapter will also describe the basic steps + involved in installing Python on your system, + in case that is necessary. + Fortunately, both &SCons; and Python + are very easy to install on almost any system, + and Python already comes installed on many systems. + + + + + +
+ Installing Python + + + + Because &SCons; is written in Python, + you must obviously have Python installed on your system + to use &SCons;. + Before you try to install Python, + you should check to see if Python is already + available on your system by typing + python -V + (capital 'V') + or + python --version + at your system's command-line prompt. + + + + + $ python -V + Python 2.5.1 + + + + + And on a Windows system with Python installed: + + + + + C:\>python -V + Python 2.5.1 + + + + + If Python is not installed on your system, + you will see an error message + stating something like "command not found" + (on UNIX or Linux) + or "'python' is not recognized + as an internal or external command, operable progam or batch file" + (on Windows). + In that case, you need to install Python + before you can install &SCons;. + + + + + + (Note that the option + was added to Python version 2.0, + so if your system only has an earlier version available + you may see an + "Unknown option: -V" + error message.) + + + + + + The standard location for information + about downloading and installing Python is + http://www.python.org/download/. + See that page for information about + how to download and install Python on your system. + + + + + + &SCons; will work with any version of Python from 1.5.2 or later. + If you need to install Python and have a choice, + we recommend using the most recent Python 2.5 version available. + Python 2.5 has significant improvements + that help speed up the performance of &SCons;. + + + +
+ +
+ Installing &SCons; From Pre-Built Packages + + + + &SCons; comes pre-packaged for installation on a number of systems, + including Linux and Windows systems. + You do not need to read this entire section, + you should need to read only the section + appropriate to the type of system you're running on. + + + +
+ Installing &SCons; on Red Hat (and Other RPM-based) Linux Systems + + + + &SCons; comes in RPM (Red Hat Package Manager) format, + pre-built and ready to install on Red Hat Linux, + Fedora, + or any other Linux distribution that uses RPM. + Your distribution may + already have an &SCons; RPM built specifically for it; + many do, including SUSE, Mandrake and Fedora. + You can check for the availability of an &SCons; RPM + on your distribution's download servers, + or by consulting an RPM search site like + http://www.rpmfind.net/ or + http://rpm.pbone.net/. + + + + + + If your distribution supports installation via + yum, + you should be able to install &SCons; by running: + + + + + # yum install scons + + + + + If your Linux distribution does not already have + a specific &SCons; RPM file, + you can download and install from the + generic RPM provided by the &SCons; project. + This will install the + SCons script(s) in /usr/bin, + and the SCons library modules in + /usr/lib/scons. + + + + + + To install from the command line, simply download the + appropriate .rpm file, + and then run: + + + + + # rpm -Uvh scons-1.2.0.d20091224-1.noarch.rpm + + + + + Or, you can use a graphical RPM package manager. + See your package manager application's documention + for specific instructions about + how to use it to install a downloaded RPM. + + + +
+ +
+ Installing &SCons; on Debian Linux Systems + + + + Debian Linux systems use a different package management + format that also makes it very easy to install &SCons;. + + + + + + If your system is connected to the Internet, + you can install the latest official Debian package + by running: + + + + + # apt-get install scons + + + + +
+ +
+ Installing &SCons; on Windows Systems + + + + &SCons; provides a Windows installer + that makes installation extremely easy. + Download the scons-1.2.0.d20091224.win32.exe + file from the &SCons; download page at + http://www.scons.org/download.php. + Then all you need to do is execute the file + (usually by clicking on its icon in Windows Explorer). + These will take you through a small + sequence of windows that will install + &SCons; on your system. + + + + + + + + + +
+ +
+ +
+ Building and Installing &SCons; on Any System + + + + If a pre-built &SCons; package is not available for your system, + then you can still easily build and install &SCons; using the native + Python distutils package. + + + + + + The first step is to download either the + scons-1.2.0.d20091224.tar.gz + or scons-1.2.0.d20091224.zip, + which are available from the SCons download page at + http://www.scons.org/download.html. + + + + + + Unpack the archive you downloaded, + using a utility like tar + on Linux or UNIX, + or WinZip on Windows. + This will create a directory called + scons-1.2.0.d20091224, + usually in your local directory. + Then change your working directory to that directory + and install &SCons; by executing the following commands: + + + + + # cd scons-1.2.0.d20091224 + # python setup.py install + + + + + This will build &SCons;, + install the &scons; script + in the default system scripts directory + (/usr/local/bin or + C:\Python25\Scripts), + and will install the &SCons; build engine + in an appropriate stand-alone library directory + (/usr/local/lib/scons or + C:\Python25\scons). + Because these are system directories, + you may need root (on Linux or UNIX) or Administrator (on Windows) + privileges to install &SCons; like this. + + + + + +
+ Building and Installing Multiple Versions of &SCons; Side-by-Side + + + + The &SCons; setup.py script + has some extensions that support + easy installation of multiple versions of &SCons; + in side-by-side locations. + This makes it easier to download and + experiment with different versions of &SCons; + before moving your official build process to a new version, + for example. + + + + + + To install &SCons; in a version-specific location, + add the option + when you call setup.py: + + + + + # python setup.py install --version-lib + + + + + This will install the &SCons; build engine + in the + /usr/lib/scons-1.2.0.d20091224 + or + C:\Python25\scons-1.2.0.d20091224 + directory, for example. + + + + + + If you use the option + the first time you install &SCons;, + you do not need to specify it each time you install + a new version. + The &SCons; setup.py script + will detect the version-specific directory name(s) + and assume you want to install all versions + in version-specific directories. + You can override that assumption in the future + by explicitly specifying the option. + + + +
+ +
+ Installing &SCons; in Other Locations + + + + You can install &SCons; in locations other than + the default by specifying the option: + + + + + # python setup.py install --prefix=/opt/scons + + + + + This would + install the scons script in + /opt/scons/bin + and the build engine in + /opt/scons/lib/scons, + + + + + + Note that you can specify both the + and the options + at the same type, + in which case setup.py + will install the build engine + in a version-specific directory + relative to the specified prefix. + Adding to the + above example would install the build engine in + /opt/scons/lib/scons-1.2.0.d20091224. + + + +
+ +
+ Building and Installing &SCons; Without Administrative Privileges + + + + If you don't have the right privileges to install &SCons; + in a system location, + simply use the --prefix= option + to install it in a location of your choosing. + For example, + to install &SCons; in appropriate locations + relative to the user's $HOME directory, + the &scons; script in + $HOME/bin + and the build engine in + $HOME/lib/scons, + simply type: + + + + + $ python setup.py install --prefix=$HOME + + + + + You may, of course, specify any other location you prefer, + and may use the option + if you would like to install version-specific directories + relative to the specified prefix. + + + + + + This can also be used to experiment with a newer + version of &SCons; than the one installed + in your system locations. + Of course, the location in which you install the + newer version of the &scons; script + ($HOME/bin in the above example) + must be configured in your &PATH; variable + before the directory containing + the system-installed version + of the &scons; script. + + + +
+ +
+ + diff --git a/doc/user/build-install.xml b/doc/user/build-install.xml new file mode 100644 index 0000000..ab289b1 --- /dev/null +++ b/doc/user/build-install.xml @@ -0,0 +1,719 @@ + + + + + This chapter will take you through the basic steps + of installing &SCons; on your system, + and building &SCons; if you don't have a + pre-built package available + (or simply prefer the flexibility of building it yourself). + Before that, however, this chapter will also describe the basic steps + involved in installing Python on your system, + in case that is necessary. + Fortunately, both &SCons; and Python + are very easy to install on almost any system, + and Python already comes installed on many systems. + + + + + +
+ Installing Python + + + + Because &SCons; is written in Python, + you must obviously have Python installed on your system + to use &SCons;. + Before you try to install Python, + you should check to see if Python is already + available on your system by typing + python -V + (capital 'V') + or + python --version + at your system's command-line prompt. + + + + + $ python -V + Python 2.5.1 + + + + + And on a Windows system with Python installed: + + + + + C:\>python -V + Python 2.5.1 + + + + + If Python is not installed on your system, + you will see an error message + stating something like "command not found" + (on UNIX or Linux) + or "'python' is not recognized + as an internal or external command, operable progam or batch file" + (on Windows). + In that case, you need to install Python + before you can install &SCons;. + + + + + + (Note that the option + was added to Python version 2.0, + so if your system only has an earlier version available + you may see an + "Unknown option: -V" + error message.) + + + + + + The standard location for information + about downloading and installing Python is + http://www.python.org/download/. + See that page for information about + how to download and install Python on your system. + + + + + + &SCons; will work with any version of Python from 1.5.2 or later. + If you need to install Python and have a choice, + we recommend using the most recent Python 2.5 version available. + Python 2.5 has significant improvements + that help speed up the performance of &SCons;. + + + +
+ +
+ Installing &SCons; From Pre-Built Packages + + + + &SCons; comes pre-packaged for installation on a number of systems, + including Linux and Windows systems. + You do not need to read this entire section, + you should need to read only the section + appropriate to the type of system you're running on. + + + +
+ Installing &SCons; on Red Hat (and Other RPM-based) Linux Systems + + + + &SCons; comes in RPM (Red Hat Package Manager) format, + pre-built and ready to install on Red Hat Linux, + Fedora, + or any other Linux distribution that uses RPM. + Your distribution may + already have an &SCons; RPM built specifically for it; + many do, including SUSE, Mandrake and Fedora. + You can check for the availability of an &SCons; RPM + on your distribution's download servers, + or by consulting an RPM search site like + http://www.rpmfind.net/ or + http://rpm.pbone.net/. + + + + + + If your distribution supports installation via + yum, + you should be able to install &SCons; by running: + + + + + # yum install scons + + + + + If your Linux distribution does not already have + a specific &SCons; RPM file, + you can download and install from the + generic RPM provided by the &SCons; project. + This will install the + SCons script(s) in /usr/bin, + and the SCons library modules in + /usr/lib/scons. + + + + + + To install from the command line, simply download the + appropriate .rpm file, + and then run: + + + + + # rpm -Uvh scons-1.2.0.d20091224-1.noarch.rpm + + + + + Or, you can use a graphical RPM package manager. + See your package manager application's documention + for specific instructions about + how to use it to install a downloaded RPM. + + + +
+ +
+ Installing &SCons; on Debian Linux Systems + + + + Debian Linux systems use a different package management + format that also makes it very easy to install &SCons;. + + + + + + If your system is connected to the Internet, + you can install the latest official Debian package + by running: + + + + + # apt-get install scons + + + + +
+ +
+ Installing &SCons; on Windows Systems + + + + &SCons; provides a Windows installer + that makes installation extremely easy. + Download the scons-1.2.0.d20091224.win32.exe + file from the &SCons; download page at + http://www.scons.org/download.php. + Then all you need to do is execute the file + (usually by clicking on its icon in Windows Explorer). + These will take you through a small + sequence of windows that will install + &SCons; on your system. + + + + + + + + + +
+ +
+ +
+ Building and Installing &SCons; on Any System + + + + If a pre-built &SCons; package is not available for your system, + then you can still easily build and install &SCons; using the native + Python distutils package. + + + + + + The first step is to download either the + scons-1.2.0.d20091224.tar.gz + or scons-1.2.0.d20091224.zip, + which are available from the SCons download page at + http://www.scons.org/download.html. + + + + + + Unpack the archive you downloaded, + using a utility like tar + on Linux or UNIX, + or WinZip on Windows. + This will create a directory called + scons-1.2.0.d20091224, + usually in your local directory. + Then change your working directory to that directory + and install &SCons; by executing the following commands: + + + + + # cd scons-1.2.0.d20091224 + # python setup.py install + + + + + This will build &SCons;, + install the &scons; script + in the default system scripts directory + (/usr/local/bin or + C:\Python25\Scripts), + and will install the &SCons; build engine + in an appropriate stand-alone library directory + (/usr/local/lib/scons or + C:\Python25\scons). + Because these are system directories, + you may need root (on Linux or UNIX) or Administrator (on Windows) + privileges to install &SCons; like this. + + + + + +
+ Building and Installing Multiple Versions of &SCons; Side-by-Side + + + + The &SCons; setup.py script + has some extensions that support + easy installation of multiple versions of &SCons; + in side-by-side locations. + This makes it easier to download and + experiment with different versions of &SCons; + before moving your official build process to a new version, + for example. + + + + + + To install &SCons; in a version-specific location, + add the option + when you call setup.py: + + + + + # python setup.py install --version-lib + + + + + This will install the &SCons; build engine + in the + /usr/lib/scons-1.2.0.d20091224 + or + C:\Python25\scons-1.2.0.d20091224 + directory, for example. + + + + + + If you use the option + the first time you install &SCons;, + you do not need to specify it each time you install + a new version. + The &SCons; setup.py script + will detect the version-specific directory name(s) + and assume you want to install all versions + in version-specific directories. + You can override that assumption in the future + by explicitly specifying the option. + + + +
+ +
+ Installing &SCons; in Other Locations + + + + You can install &SCons; in locations other than + the default by specifying the option: + + + + + # python setup.py install --prefix=/opt/scons + + + + + This would + install the scons script in + /opt/scons/bin + and the build engine in + /opt/scons/lib/scons, + + + + + + Note that you can specify both the + and the options + at the same type, + in which case setup.py + will install the build engine + in a version-specific directory + relative to the specified prefix. + Adding to the + above example would install the build engine in + /opt/scons/lib/scons-1.2.0.d20091224. + + + +
+ +
+ Building and Installing &SCons; Without Administrative Privileges + + + + If you don't have the right privileges to install &SCons; + in a system location, + simply use the --prefix= option + to install it in a location of your choosing. + For example, + to install &SCons; in appropriate locations + relative to the user's $HOME directory, + the &scons; script in + $HOME/bin + and the build engine in + $HOME/lib/scons, + simply type: + + + + + $ python setup.py install --prefix=$HOME + + + + + You may, of course, specify any other location you prefer, + and may use the option + if you would like to install version-specific directories + relative to the specified prefix. + + + + + + This can also be used to experiment with a newer + version of &SCons; than the one installed + in your system locations. + Of course, the location in which you install the + newer version of the &scons; script + ($HOME/bin in the above example) + must be configured in your &PATH; variable + before the directory containing + the system-installed version + of the &scons; script. + + + +
+ +
+ + diff --git a/doc/user/builders-built-in.in b/doc/user/builders-built-in.in new file mode 100644 index 0000000..245cb8c --- /dev/null +++ b/doc/user/builders-built-in.in @@ -0,0 +1,963 @@ + + + + + &SCons; provides the ability to build a lot of different + types of files right "out of the box." + So far, we've been using &SCons;' ability to build + programs, objects and libraries to + illustrate much of the underlying functionality of &SCons; + This section will describe all of the different + types of files that you can build with &SCons;, + and the built-in &Builder; objects used to build them. + By default, all of the &Builder; objects in this section + can be built either with or without an explicit + construction environment. + + + +
+ Programs: the &Program; Builder + + + + As we've seen, the &b-link-Program; Builder + is used to build an executable program. + The &source; argument is one or more + source-code files or object files, + and the ⌖ argument is the + name of the executable program name to be created. + For example: + + + + + Program('prog', 'file1.o') + + + + + Will create the &prog; + executable on a POSIX system, + the &prog_exe; executable on a Windows system. + + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-PROGPREFIX; + and + &cv-link-PROGSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(PROGPREFIX='my', PROGSUFFIX='.xxx') + env.Program('prog', ['file1.o', 'file2.o']) + + + + + Will create a program named + myprog.xxx + regardless of the system on which it is run. + + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the target + program created. + For example: + + + + + Program(['hello.c', 'goodbye.c']) + + + + + Will create the &hello; + executable on a POSIX system, + the &hello_exe; executable on a Windows system. + + + + + + Two construction variables control what libraries + will be linked with the resulting program. + The &cv-link-LIBS; variable is a list of the names of + libraries that will be linked into any programs, + and the &cv-link-LIBPATH; variables is a list of + directories that will be searched for + the specified libraries. + &SCons; will construct the right command-line + options for the running system. + For example: + + + + + + env = Environment(LIBS = ['foo1', 'foo2'], + LIBPATH = ['/usr/dir1', 'dir2']) + env.Program(['hello.c', 'goodbye.c']) + + + int hello() { printf("Hello, world!\n"); } + + + int goodbye() { printf("Goodbye, world!\n"); } + + + + + + Will execute as follows on a POSIX system: + + + + + scons -Q + + + + + And execute as follows on a Windows system: + + + + + scons -Q + + + + + The &cv-LIBS; construction variable + is turned into command line options + by appending the &cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; + construction variables to the beginning and end, + respectively, of each specified library. + + + + + + The &cv-LIBPATH; construction variable + is turned into command line options + by appending the &cv-link-LIBDIRPREFIX; and &cv-link-LIBDIRSUFFIX; + construction variables to the beginning and end, + respectively, of each specified library. + + + + + + Other relevant construction variables + include those used by the &b-link-Object; + builders to affect how the + source files specified as input to the &t-Program; + builders are turned into object files; + see the next section. + + + + + + The command line used to control how a program is linked + is specified by the &cv-link-LINKCOM; construction variable. + By default, it uses the + &cv-link-LINK; construction variable + and the &cv-link-LINKFLAGS; construction variable. + + + +
+ +
+ Object-File Builders + + + + &SCons; provides separate Builder objects + to create static and shared object files. + The distinction becomes especially important when + archiving object files into different types of libraries. + + + +
+ The &StaticObject; Builder + + + + The &b-link-StaticObject; Builder + is used to build an object file + suitable for static linking into a program, + or for inclusion in a static library. + The &source; argument is a single source-code file, + and the ⌖ argument is the + name of the static object file to be created. + For example: + + + + + StaticObject('file', 'file.c') + + + + + Will create the &file_o; + object file on a POSIX system, + the &file_obj; executable on a Windows system. + + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-OBJPREFIX; + and + &cv-link-OBJSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(OBJPREFIX='my', OBJSUFFIX='.xxx') + env.StaticObject('file', 'file.c') + + + + + Will create an object file named + myfile.xxx + regardless of the system on which it is run. + + + + + + If you omit the ⌖, + the base of the first input + file name specified + beomces the base of the name + of the static object file to be created. + For example: + + + + + StaticObject('file.c') + + + + + Will create the &file_o; + executable on a POSIX system, + the &file_obj; executable on a Windows system. + + + +
+ +
+ The &SharedObject; Builder + + + + The &b-link-SharedObject; Builder + is used to build an object file + suitable for shared linking into a program, + or for inclusion in a shared library. + The &source; argument is a single source-code file, + and the ⌖ argument is the + name of the shared object file to be created. + For example: + + + + + SharedObject('file', 'file.c') + + + + + Will create the &file_o; + object file on a POSIX system, + the &file_obj; executable on a Windows system. + + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-SHOBJPREFIX; + and + &cv-link-SHOBJSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(SHOBJPREFIX='my', SHOBJSUFFIX='.xxx') + env.SharedObject('file', 'file.c') + + + + + Will create an object file named + myfile.xxx + regardless of the system on which it is run. + + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the name + of the shared object file to be created. + For example: + + + + + SharedObject('file.c') + + + + + Will create the &file_o; + executable on a POSIX system, + the &file_obj; executable on a Windows system. + + + +
+ +
+ The &Object; Builder + + + + The &b-link-Object; Builder is a synonym for &b-link-StaticObject; + and is completely equivalent. + + + +
+ +
+ +
+ Library Builders + + + + &SCons; provides separate Builder objects + to create static and shared libraries. + + + +
+ The &StaticLibrary; Builder + + + + The &b-link-StaticLibrary; Builder + is used to create a library + suitable for static linking into a program. + The &source; argument is one or more + source-code files or object files, + and the ⌖ argument is the + name of the static library to be created. + For example: + + + + + StaticLibrary('foo', ['file1.c', 'file2.c']) + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-LIBPREFIX; + and + &cv-link-LIBSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(LIBPREFIX='my', LIBSUFFIX='.xxx') + env.StaticLibrary('lib', ['file1.o', 'file2.o']) + + + + + Will create an object file named + mylib.xxx + regardless of the system on which it is run. + + + + + StaticLibrary('foo', ['file1.c', 'file2.c']) + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the name of the static object file to be created. + For example: + + + + + StaticLibrary(['file.c', 'another.c']) + + + + + Will create the &libfile_a; + library on a POSIX system, + the &file_lib; library on a Windows system. + + + +
+ +
+ The &SharedLibrary; Builder + + + + The &b-link-SharedLibrary; Builder + is used to create a shared library + suitable for linking with a program. + The &source; argument is one or more + source-code files or object files, + and the ⌖ argument is the + name of the shared library to be created. + For example: + + + + + SharedLibrary('foo', ['file1.c', 'file2.c']) + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-SHLIBPREFIX; + and + &cv-link-SHLIBSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(SHLIBPREFIX='my', SHLIBSUFFIX='.xxx') + env.SharedLibrary('shared', ['file1.o', 'file2.o']) + + + + + Will create an object file named + myshared.xxx + regardless of the system on which it is run. + + + + + SharedLibrary('foo', ['file1.c', 'file2.c']) + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the name of the shared library to be created. + For example: + + + + + SharedLibrary(['file.c', 'another.c']) + + + + + Will create the &libfile_so; + library on a POSIX system, + the &file_dll; library on a Windows system. + + + +
+ +
+ The &Library; Builder + + + + The &b-link-Library; Builder is a synonym for &b-link-StaticLibrary; + and is completely equivalent. + + + +
+ +
+ +
+ Pre-Compiled Headers: the &PCH; Builder + + + + XXX PCH() + + + +
+ +
+ Microsoft Visual C++ Resource Files: the &RES; Builder + + + + XXX RES() + + + +
+ +
+ Source Files + + + + By default + &SCons; supports two Builder objects + that know how to build source files + from other input files. + These are typically invoked "internally" + to turn files that need preprocessing into other source files. + + + +
+ The &CFile; Builder + + + + XXX CFile() + + + + + XXX CFile() programlisting + + + + XXX CFile() screen + + +
+ +
+ The &CXXFile; Builder + + + + XXX CXXFILE() + + + + + XXX CXXFILE() programlisting + + + + XXX CXXFILE() screen + + +
+ +
+ +
+ Documents + + + + &SCons; provides a number of Builder objects + for creating different types of documents. + + + +
+ The &DVI; Builder + + + + XXX DVI() para + + + + + XXX DVI() programlisting + + + + XXX DVI() screen + + +
+ +
+ The &PDF; Builder + + + + XXX PDF() para + + + +
+ +
+ The &PostScript; Builder + + + + XXX PostScript() para + + + + + XXX PostScript() programlisting + + + + XXX PostScript() screen + + +
+ +
+ +
+ Archives + + + + &SCons; provides Builder objects + for creating two different types of archive files. + + + +
+ The &Tar; Builder + + + + The &b-link-Tar; Builder object uses the &tar; + utility to create archives of files + and/or directory trees: + + + + + + env = Environment() + env.Tar('out1.tar', ['file1', 'file2']) + env.Tar('out2', 'directory') + + + file1 + + + file2 + + + directory/file3 + + + + + scons -Q . + + + + + One common requirement when creating a &tar; archive + is to create a compressed archive using the + option. + This is easily handled by specifying + the value of the &cv-link-TARFLAGS; variable + when you create the construction environment. + Note, however, that the used to + to instruct &tar; to create the archive + is part of the default value of &cv-TARFLAGS;, + so you need to set it both options: + + + + + + env = Environment(TARFLAGS = '-c -z') + env.Tar('out.tar.gz', 'directory') + + + directory/file + + + + + scons -Q . + + + + + you may also wish to set the value of the + &cv-link-TARSUFFIX; construction variable + to your desired suffix for compress &tar; archives, + so that &SCons; can append it to the target file name + without your having to specify it explicitly: + + + + + + env = Environment(TARFLAGS = '-c -z', + TARSUFFIX = '.tgz') + env.Tar('out', 'directory') + + + directory/file + + + + + scons -Q . + + +
+ +
+ The &Zip; Builder + + + + The &b-link-Zip; Builder object creates archives of files + and/or directory trees in the ZIP file format. + Python versions 1.6 or later + contain an internal &zipfile; module + that &SCons; will use. + In this case, given the following + &SConstruct; file: + + + + + + env = Environment() + env.Zip('out', ['file1', 'file2']) + + + file1 + + + file2 + + + + + + Your output will reflect the fact + that an internal Python function + is being used to create the output ZIP archive: + + + + + scons -Q . + + + + + If you're using Python version 1.5.2 to run &SCons;, + then &SCons; will try to use an external + &zip; program as follows: + + + + + % scons -Q . + zip /home/my/project/zip.out file1 file2 + + +
+ +
+ +
+ Java + + + + &SCons; provides Builder objects + for creating various types of Java output files. + + + +
+ Building Class Files: the &Java; Builder + + + + The &b-link-Java; builder takes one or more input + .java files + and turns them into one or more + .class files + Unlike most builders, however, + the &Java; builder takes + target and source directories, + not files, as input. + + + + + env = Environment() + env.Java(target = 'classes', source = 'src') + + + + + The &Java; builder will then + search the specified source directory + tree for all .java files, + and pass any out-of-date + + + + + XXX Java() screen + + +
+ +
+ The &Jar; Builder + + + + XXX The &Jar; builder object + + + + + env = Environment() + env.Java(target = 'classes', source = 'src') + env.Jar(target = '', source = 'classes') + + + + XXX Jar() screen + + +
+ +
+ Building C header and stub files: the &JavaH; Builder + + + + XXX JavaH() para + + + + + XXX JavaH() programlisting + + + + XXX JavaH() screen + + +
+ +
+ Building RMI stub and skeleton class files: the &RMIC; Builder + + + + XXX RMIC() para + + + + + XXX RMIC() programlisting + + + + XXX RMIC() screen + + +
+ +
diff --git a/doc/user/builders-built-in.xml b/doc/user/builders-built-in.xml new file mode 100644 index 0000000..bee5b38 --- /dev/null +++ b/doc/user/builders-built-in.xml @@ -0,0 +1,949 @@ + + + + + &SCons; provides the ability to build a lot of different + types of files right "out of the box." + So far, we've been using &SCons;' ability to build + programs, objects and libraries to + illustrate much of the underlying functionality of &SCons; + This section will describe all of the different + types of files that you can build with &SCons;, + and the built-in &Builder; objects used to build them. + By default, all of the &Builder; objects in this section + can be built either with or without an explicit + construction environment. + + + +
+ Programs: the &Program; Builder + + + + As we've seen, the &b-link-Program; Builder + is used to build an executable program. + The &source; argument is one or more + source-code files or object files, + and the ⌖ argument is the + name of the executable program name to be created. + For example: + + + + + Program('prog', 'file1.o') + + + + + Will create the &prog; + executable on a POSIX system, + the &prog_exe; executable on a Windows system. + + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-PROGPREFIX; + and + &cv-link-PROGSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(PROGPREFIX='my', PROGSUFFIX='.xxx') + env.Program('prog', ['file1.o', 'file2.o']) + + + + + Will create a program named + myprog.xxx + regardless of the system on which it is run. + + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the target + program created. + For example: + + + + + Program(['hello.c', 'goodbye.c']) + + + + + Will create the &hello; + executable on a POSIX system, + the &hello_exe; executable on a Windows system. + + + + + + Two construction variables control what libraries + will be linked with the resulting program. + The &cv-link-LIBS; variable is a list of the names of + libraries that will be linked into any programs, + and the &cv-link-LIBPATH; variables is a list of + directories that will be searched for + the specified libraries. + &SCons; will construct the right command-line + options for the running system. + For example: + + + + + env = Environment(LIBS = ['foo1', 'foo2'], + LIBPATH = ['/usr/dir1', 'dir2']) + env.Program(['hello.c', 'goodbye.c']) + + + + + Will execute as follows on a POSIX system: + + + + + % scons -Q + cc -o goodbye.o -c goodbye.c + cc -o hello.o -c hello.c + cc -o hello hello.o goodbye.o -L/usr/dir1 -Ldir2 -lfoo1 -lfoo2 + + + + + And execute as follows on a Windows system: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fogoodbye.obj /c goodbye.c /nologo + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:hello.exe /LIBPATH:\usr\dir1 /LIBPATH:dir2 foo1.lib foo2.lib hello.obj goodbye.obj + + + + + The &cv-LIBS; construction variable + is turned into command line options + by appending the &cv-link-LIBLINKPREFIX; and &cv-link-LIBLINKSUFFIX; + construction variables to the beginning and end, + respectively, of each specified library. + + + + + + The &cv-LIBPATH; construction variable + is turned into command line options + by appending the &cv-link-LIBDIRPREFIX; and &cv-link-LIBDIRSUFFIX; + construction variables to the beginning and end, + respectively, of each specified library. + + + + + + Other relevant construction variables + include those used by the &b-link-Object; + builders to affect how the + source files specified as input to the &t-Program; + builders are turned into object files; + see the next section. + + + + + + The command line used to control how a program is linked + is specified by the &cv-link-LINKCOM; construction variable. + By default, it uses the + &cv-link-LINK; construction variable + and the &cv-link-LINKFLAGS; construction variable. + + + +
+ +
+ Object-File Builders + + + + &SCons; provides separate Builder objects + to create static and shared object files. + The distinction becomes especially important when + archiving object files into different types of libraries. + + + +
+ The &StaticObject; Builder + + + + The &b-link-StaticObject; Builder + is used to build an object file + suitable for static linking into a program, + or for inclusion in a static library. + The &source; argument is a single source-code file, + and the ⌖ argument is the + name of the static object file to be created. + For example: + + + + + StaticObject('file', 'file.c') + + + + + Will create the &file_o; + object file on a POSIX system, + the &file_obj; executable on a Windows system. + + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-OBJPREFIX; + and + &cv-link-OBJSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(OBJPREFIX='my', OBJSUFFIX='.xxx') + env.StaticObject('file', 'file.c') + + + + + Will create an object file named + myfile.xxx + regardless of the system on which it is run. + + + + + + If you omit the ⌖, + the base of the first input + file name specified + beomces the base of the name + of the static object file to be created. + For example: + + + + + StaticObject('file.c') + + + + + Will create the &file_o; + executable on a POSIX system, + the &file_obj; executable on a Windows system. + + + +
+ +
+ The &SharedObject; Builder + + + + The &b-link-SharedObject; Builder + is used to build an object file + suitable for shared linking into a program, + or for inclusion in a shared library. + The &source; argument is a single source-code file, + and the ⌖ argument is the + name of the shared object file to be created. + For example: + + + + + SharedObject('file', 'file.c') + + + + + Will create the &file_o; + object file on a POSIX system, + the &file_obj; executable on a Windows system. + + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-SHOBJPREFIX; + and + &cv-link-SHOBJSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(SHOBJPREFIX='my', SHOBJSUFFIX='.xxx') + env.SharedObject('file', 'file.c') + + + + + Will create an object file named + myfile.xxx + regardless of the system on which it is run. + + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the name + of the shared object file to be created. + For example: + + + + + SharedObject('file.c') + + + + + Will create the &file_o; + executable on a POSIX system, + the &file_obj; executable on a Windows system. + + + +
+ +
+ The &Object; Builder + + + + The &b-link-Object; Builder is a synonym for &b-link-StaticObject; + and is completely equivalent. + + + +
+ +
+ +
+ Library Builders + + + + &SCons; provides separate Builder objects + to create static and shared libraries. + + + +
+ The &StaticLibrary; Builder + + + + The &b-link-StaticLibrary; Builder + is used to create a library + suitable for static linking into a program. + The &source; argument is one or more + source-code files or object files, + and the ⌖ argument is the + name of the static library to be created. + For example: + + + + + StaticLibrary('foo', ['file1.c', 'file2.c']) + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-LIBPREFIX; + and + &cv-link-LIBSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(LIBPREFIX='my', LIBSUFFIX='.xxx') + env.StaticLibrary('lib', ['file1.o', 'file2.o']) + + + + + Will create an object file named + mylib.xxx + regardless of the system on which it is run. + + + + + StaticLibrary('foo', ['file1.c', 'file2.c']) + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the name of the static object file to be created. + For example: + + + + + StaticLibrary(['file.c', 'another.c']) + + + + + Will create the &libfile_a; + library on a POSIX system, + the &file_lib; library on a Windows system. + + + +
+ +
+ The &SharedLibrary; Builder + + + + The &b-link-SharedLibrary; Builder + is used to create a shared library + suitable for linking with a program. + The &source; argument is one or more + source-code files or object files, + and the ⌖ argument is the + name of the shared library to be created. + For example: + + + + + SharedLibrary('foo', ['file1.c', 'file2.c']) + + + + + The target file's prefix and suffix may be omitted, + and the values from the + &cv-link-SHLIBPREFIX; + and + &cv-link-SHLIBSUFFIX; + construction variables + will be appended appropriately. + For example: + + + + + env = Environment(SHLIBPREFIX='my', SHLIBSUFFIX='.xxx') + env.SharedLibrary('shared', ['file1.o', 'file2.o']) + + + + + Will create an object file named + myshared.xxx + regardless of the system on which it is run. + + + + + SharedLibrary('foo', ['file1.c', 'file2.c']) + + + + + If you omit the ⌖, + the base of the first input + file name specified + becomes the base of the name of the shared library to be created. + For example: + + + + + SharedLibrary(['file.c', 'another.c']) + + + + + Will create the &libfile_so; + library on a POSIX system, + the &file_dll; library on a Windows system. + + + +
+ +
+ The &Library; Builder + + + + The &b-link-Library; Builder is a synonym for &b-link-StaticLibrary; + and is completely equivalent. + + + +
+ +
+ +
+ Pre-Compiled Headers: the &PCH; Builder + + + + XXX PCH() + + + +
+ +
+ Microsoft Visual C++ Resource Files: the &RES; Builder + + + + XXX RES() + + + +
+ +
+ Source Files + + + + By default + &SCons; supports two Builder objects + that know how to build source files + from other input files. + These are typically invoked "internally" + to turn files that need preprocessing into other source files. + + + +
+ The &CFile; Builder + + + + XXX CFile() + + + + + XXX CFile() programlisting + + + + XXX CFile() screen + + +
+ +
+ The &CXXFile; Builder + + + + XXX CXXFILE() + + + + + XXX CXXFILE() programlisting + + + + XXX CXXFILE() screen + + +
+ +
+ +
+ Documents + + + + &SCons; provides a number of Builder objects + for creating different types of documents. + + + +
+ The &DVI; Builder + + + + XXX DVI() para + + + + + XXX DVI() programlisting + + + + XXX DVI() screen + + +
+ +
+ The &PDF; Builder + + + + XXX PDF() para + + + +
+ +
+ The &PostScript; Builder + + + + XXX PostScript() para + + + + + XXX PostScript() programlisting + + + + XXX PostScript() screen + + +
+ +
+ +
+ Archives + + + + &SCons; provides Builder objects + for creating two different types of archive files. + + + +
+ The &Tar; Builder + + + + The &b-link-Tar; Builder object uses the &tar; + utility to create archives of files + and/or directory trees: + + + + + env = Environment() + env.Tar('out1.tar', ['file1', 'file2']) + env.Tar('out2', 'directory') + + + + % scons -Q . + tar -c -f out1.tar file1 file2 + tar -c -f out2.tar directory + + + + + One common requirement when creating a &tar; archive + is to create a compressed archive using the + option. + This is easily handled by specifying + the value of the &cv-link-TARFLAGS; variable + when you create the construction environment. + Note, however, that the used to + to instruct &tar; to create the archive + is part of the default value of &cv-TARFLAGS;, + so you need to set it both options: + + + + + env = Environment(TARFLAGS = '-c -z') + env.Tar('out.tar.gz', 'directory') + + + + % scons -Q . + tar -c -z -f out.tar.gz directory + + + + + you may also wish to set the value of the + &cv-link-TARSUFFIX; construction variable + to your desired suffix for compress &tar; archives, + so that &SCons; can append it to the target file name + without your having to specify it explicitly: + + + + + env = Environment(TARFLAGS = '-c -z', + TARSUFFIX = '.tgz') + env.Tar('out', 'directory') + + + + % scons -Q . + tar -c -z -f out.tgz directory + + +
+ +
+ The &Zip; Builder + + + + The &b-link-Zip; Builder object creates archives of files + and/or directory trees in the ZIP file format. + Python versions 1.6 or later + contain an internal &zipfile; module + that &SCons; will use. + In this case, given the following + &SConstruct; file: + + + + + env = Environment() + env.Zip('out', ['file1', 'file2']) + + + + + Your output will reflect the fact + that an internal Python function + is being used to create the output ZIP archive: + + + + + % scons -Q . + zip(["out.zip"], ["file1", "file2"]) + + + + + If you're using Python version 1.5.2 to run &SCons;, + then &SCons; will try to use an external + &zip; program as follows: + + + + + % scons -Q . + zip /home/my/project/zip.out file1 file2 + + +
+ +
+ +
+ Java + + + + &SCons; provides Builder objects + for creating various types of Java output files. + + + +
+ Building Class Files: the &Java; Builder + + + + The &b-link-Java; builder takes one or more input + .java files + and turns them into one or more + .class files + Unlike most builders, however, + the &Java; builder takes + target and source directories, + not files, as input. + + + + + env = Environment() + env.Java(target = 'classes', source = 'src') + + + + + The &Java; builder will then + search the specified source directory + tree for all .java files, + and pass any out-of-date + + + + + XXX Java() screen + + +
+ +
+ The &Jar; Builder + + + + XXX The &Jar; builder object + + + + + env = Environment() + env.Java(target = 'classes', source = 'src') + env.Jar(target = '', source = 'classes') + + + + XXX Jar() screen + + +
+ +
+ Building C header and stub files: the &JavaH; Builder + + + + XXX JavaH() para + + + + + XXX JavaH() programlisting + + + + XXX JavaH() screen + + +
+ +
+ Building RMI stub and skeleton class files: the &RMIC; Builder + + + + XXX RMIC() para + + + + + XXX RMIC() programlisting + + + + XXX RMIC() screen + + +
+ +
diff --git a/doc/user/builders-commands.in b/doc/user/builders-commands.in new file mode 100644 index 0000000..687d12d --- /dev/null +++ b/doc/user/builders-commands.in @@ -0,0 +1,156 @@ + + + + + + + Creating a &Builder; and attaching it to a &consenv; + allows for a lot of flexibility when you + want to re-use actions + to build multiple files of the same type. + This can, however, be cumbersome + if you only need to execute one specific command + to build a single file (or group of files). + For these situations, &SCons; supports a + &Command; &Builder; that arranges + for a specific action to be executed + to build a specific file or files. + This looks a lot like the other builders + (like &b-link-Program;, &b-link-Object;, etc.), + but takes as an additional argument + the command to be executed to build the file: + + + + + + env = Environment() + env.Command('foo.out', 'foo.in', "sed 's/x/y/' < $SOURCE > $TARGET") + + + foo.in + + + + + + When executed, + &SCons; runs the specified command, + substituting &cv-link-SOURCE; and &cv-link-TARGET; + as expected: + + + + + scons -Q + + + + + This is often more convenient than + creating a &Builder; object + and adding it to the &cv-link-BUILDERS; variable + of a &consenv; + + + + + + Note that the action you specify to the + &Command; &Builder; can be any legal &SCons; &Action;, + such as a Python function: + + + + + + env = Environment() + def build(target, source, env): + # Whatever it takes to build + return None + env.Command('foo.out', 'foo.in', build) + + + foo.in + + + + + + Which executes as follows: + + + + + scons -Q + + + + + Note that &cv-link-SOURCE; and &cv-link-TARGET; are expanded + in the source and target as well as of SCons 1.1, + so you can write: + + + + + + env.Command('${SOURCE.basename}.out', 'foo.in', build) + + + + + + + which does the same thing as the previous example, but allows you + to avoid repeating yourself. + + + diff --git a/doc/user/builders-commands.xml b/doc/user/builders-commands.xml new file mode 100644 index 0000000..111b1d2 --- /dev/null +++ b/doc/user/builders-commands.xml @@ -0,0 +1,146 @@ + + + + + + + Creating a &Builder; and attaching it to a &consenv; + allows for a lot of flexibility when you + want to re-use actions + to build multiple files of the same type. + This can, however, be cumbersome + if you only need to execute one specific command + to build a single file (or group of files). + For these situations, &SCons; supports a + &Command; &Builder; that arranges + for a specific action to be executed + to build a specific file or files. + This looks a lot like the other builders + (like &b-link-Program;, &b-link-Object;, etc.), + but takes as an additional argument + the command to be executed to build the file: + + + + + env = Environment() + env.Command('foo.out', 'foo.in', "sed 's/x/y/' < $SOURCE > $TARGET") + + + + + When executed, + &SCons; runs the specified command, + substituting &cv-link-SOURCE; and &cv-link-TARGET; + as expected: + + + + + % scons -Q + sed 's/x/y/' < foo.in > foo.out + + + + + This is often more convenient than + creating a &Builder; object + and adding it to the &cv-link-BUILDERS; variable + of a &consenv; + + + + + + Note that the action you specify to the + &Command; &Builder; can be any legal &SCons; &Action;, + such as a Python function: + + + + + env = Environment() + def build(target, source, env): + # Whatever it takes to build + return None + env.Command('foo.out', 'foo.in', build) + + + + + Which executes as follows: + + + + + % scons -Q + build(["foo.out"], ["foo.in"]) + + + + + Note that &cv-link-SOURCE; and &cv-link-TARGET; are expanded + in the source and target as well as of SCons 1.1, + so you can write: + + + + + env.Command('${SOURCE.basename}.out', 'foo.in', build) + + + + + + which does the same thing as the previous example, but allows you + to avoid repeating yourself. + + + diff --git a/doc/user/builders-writing.in b/doc/user/builders-writing.in new file mode 100644 index 0000000..4f54d99 --- /dev/null +++ b/doc/user/builders-writing.in @@ -0,0 +1,1087 @@ + + + + + + + Although &SCons; provides many useful methods + for building common software products: + programs, libraries, documents. + you frequently want to be + able to build some other type of file + not supported directly by &SCons;. + Fortunately, &SCons; makes it very easy + to define your own &Builder; objects + for any custom file types you want to build. + (In fact, the &SCons; interfaces for creating + &Builder; objects are flexible enough and easy enough to use + that all of the the &SCons; built-in &Builder; objects + are created the mechanisms described in this section.) + + + +
+ Writing Builders That Execute External Commands + + + + The simplest &Builder; to create is + one that executes an external command. + For example, if we want to build + an output file by running the contents + of the input file through a command named + foobuild, + creating that &Builder; might look like: + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + + + + + All the above line does is create a free-standing + &Builder; object. + The next section will show us how to actually use it. + + + +
+ +
+ Attaching a Builder to a &ConsEnv; + + + + A &Builder; object isn't useful + until it's attached to a &consenv; + so that we can call it to arrange + for files to be built. + This is done through the &cv-link-BUILDERS; + &consvar; in an environment. + The &cv-BUILDERS; variable is a Python dictionary + that maps the names by which you want to call + various &Builder; objects to the objects themselves. + For example, if we want to call the + &Builder; we just defined by the name + Foo, + our &SConstruct; file might look like: + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + env.Foo('file.foo', 'file.input') + + + file.input + + + cat + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + + + + + With the &Builder; attached to our &consenv; + with the name Foo, + we can now actually call it like so: + + + + + env.Foo('file.foo', 'file.input') + + + + + Then when we run &SCons; it looks like: + + + + + scons -Q + + + + + Note, however, that the default &cv-BUILDERS; + variable in a &consenv; + comes with a default set of &Builder; objects + already defined: + &b-link-Program;, &b-link-Library;, etc. + And when we explicitly set the &cv-BUILDERS; variable + when we create the &consenv;, + the default &Builder;s are no longer part of + the environment: + + + + + + + import SCons.Defaults; SCons.Defaults.ConstructionEnvironment['TOOLS'] = {}; bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + file.input + + + hello.c + + + + + scons -Q + + + + + To be able to use both our own defined &Builder; objects + and the default &Builder; objects in the same &consenv;, + you can either add to the &cv-BUILDERS; variable + using the &Append; function: + + + + + + env = Environment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env.Append(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + file.input + + + hello.c + + + cat + + + + + env = Environment() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env.Append(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + + Or you can explicitly set the appropriately-named + key in the &cv-BUILDERS; dictionary: + + + + + env = Environment() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env['BUILDERS']['Foo'] = bld + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + + Either way, the same &consenv; + can then use both the newly-defined + Foo &Builder; + and the default &b-link-Program; &Builder;: + + + + + scons -Q + + +
+ +
+ Letting &SCons; Handle The File Suffixes + + + + By supplying additional information + when you create a &Builder;, + you can let &SCons; add appropriate file + suffixes to the target and/or the source file. + For example, rather than having to specify + explicitly that you want the Foo + &Builder; to build the file.foo + target file from the file.input source file, + you can give the .foo + and .input suffixes to the &Builder;, + making for more compact and readable calls to + the Foo &Builder;: + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET', + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + env.Foo('file1') + env.Foo('file2') + + + file1.input + + + file2.input + + + cat + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET', + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file1') + env.Foo('file2') + + + + scons -Q + + + + + You can also supply a prefix keyword argument + if it's appropriate to have &SCons; append a prefix + to the beginning of target file names. + + + +
+ +
+ Builders That Execute Python Functions + + + + In &SCons;, you don't have to call an external command + to build a file. + You can, instead, define a Python function + that a &Builder; object can invoke + to build your target file (or files). + Such a &buildfunc; definition looks like: + + + + + def build_function(target, source, env): + # Code to build "target" from "source" + return None + + + + + The arguments of a &buildfunc; are: + + + + + + + target + + + + + A list of Node objects representing + the target or targets to be + built by this builder function. + The file names of these target(s) + may be extracted using the Python &str; function. + + + + + + + source + + + + + A list of Node objects representing + the sources to be + used by this builder function to build the targets. + The file names of these source(s) + may be extracted using the Python &str; function. + + + + + + + env + + + + + The &consenv; used for building the target(s). + The builder function may use any of the + environment's construction variables + in any way to affect how it builds the targets. + + + + + + + + + + The builder function must + return a 0 or None value + if the target(s) are built successfully. + The builder function + may raise an exception + or return any non-zero value + to indicate that the build is unsuccessful, + + + + + + Once you've defined the Python function + that will build your target file, + defining a &Builder; object for it is as + simple as specifying the name of the function, + instead of an external command, + as the &Builder;'s + action + argument: + + + + + + def build_function(target, source, env): + # Code to build "target" from "source" + return None + bld = Builder(action = build_function, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + file.input + + + + + + And notice that the output changes slightly, + reflecting the fact that a Python function, + not an external command, + is now called to build the target file: + + + + + scons -Q + + +
+ +
+ Builders That Create Actions Using a &Generator; + + + + &SCons; Builder objects can create an action "on the fly" + by using a function called a &generator;. + This provides a great deal of flexibility to + construct just the right list of commands + to build your target. + A &generator; looks like: + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (target[0], source[0]) + + + + + The arguments of a &generator; are: + + + + + + + source + + + + + A list of Node objects representing + the sources to be built + by the command or other action + generated by this function. + The file names of these source(s) + may be extracted using the Python &str; function. + + + + + + + + target + + + + + A list of Node objects representing + the target or targets to be built + by the command or other action + generated by this function. + The file names of these target(s) + may be extracted using the Python &str; function. + + + + + + + + env + + + + + The &consenv; used for building the target(s). + The generator may use any of the + environment's construction variables + in any way to determine what command + or other action to return. + + + + + + + + for_signature + + + + + A flag that specifies whether the + generator is being called to contribute to a build signature, + as opposed to actually executing the command. + + + + + + + + + + + + + The &generator; must return a + command string or other action that will be used to + build the specified target(s) from the specified source(s). + + + + + + Once you've defined a &generator;, + you create a &Builder; to use it + by specifying the generator keyword argument + instead of action. + + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (source[0], target[0]) + bld = Builder(generator = generate_actions, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + env.Foo('file') + + + file.input + + + cat + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (source[0], target[0]) + bld = Builder(generator = generate_actions, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + scons -Q + + + + + Note that it's illegal to specify both an + action + and a + generator + for a &Builder;. + + + +
+ +
+ Builders That Modify the Target or Source Lists Using an &Emitter; + + + + &SCons; supports the ability for a Builder to modify the + lists of target(s) from the specified source(s). + You do this by defining an &emitter; function + that takes as its arguments + the list of the targets passed to the builder, + the list of the sources passed to the builder, + and the construction environment. + The emitter function should return the modified + lists of targets that should be built + and sources from which the targets will be built. + + + + + + For example, suppose you want to define a Builder + that always calls a foobuild program, + and you want to automatically add + a new target file named + new_target + and a new source file named + new_source + whenever it's called. + The &SConstruct; file might look like this: + + + + + + def modify_targets(target, source, env): + target.append('new_target') + source.append('new_source') + return target, source + bld = Builder(action = 'foobuild $TARGETS - $SOURCES', + suffix = '.foo', + src_suffix = '.input', + emitter = modify_targets) + env = Environment(BUILDERS = {'Foo' : bld}) + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + env.Foo('file') + + + file.input + + + new_source + + + cat + + + + + def modify_targets(target, source, env): + target.append('new_target') + source.append('new_source') + return target, source + bld = Builder(action = 'foobuild $TARGETS - $SOURCES', + suffix = '.foo', + src_suffix = '.input', + emitter = modify_targets) + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + + And would yield the following output: + + + + + scons -Q + + + + + One very flexible thing that you can do is + use a construction variable to specify + different emitter functions for different + construction variable. + To do this, specify a string + containing a construction variable + expansion as the emitter when you call + the &Builder; function, + and set that construction variable to + the desired emitter function + in different construction environments: + + + + + + + bld = Builder(action = 'my_command $SOURCES > $TARGET', + suffix = '.foo', + src_suffix = '.input', + emitter = '$MY_EMITTER') + def modify1(target, source, env): + return target, source + ['modify1.in'] + def modify2(target, source, env): + return target, source + ['modify2.in'] + env1 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify1) + env2 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify2) + env1.Foo('file1') + env2.Foo('file2') + import os + env1['ENV']['PATH'] = env2['ENV']['PATH'] + os.pathsep + os.getcwd() + env2['ENV']['PATH'] = env2['ENV']['PATH'] + os.pathsep + os.getcwd() + + + file1.input + + + file2.input + + + modify1.input + + + modify2.input + + + cat + + + + + + + bld = Builder(action = 'my_command $SOURCES > $TARGET', + suffix = '.foo', + src_suffix = '.input', + emitter = '$MY_EMITTER') + def modify1(target, source, env): + return target, source + ['modify1.in'] + def modify2(target, source, env): + return target, source + ['modify2.in'] + env1 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify1) + env2 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify2) + env1.Foo('file1') + env2.Foo('file2') + + + + + + In this example, the modify1.in + and modify2.in files + get added to the source lists + of the different commands: + + + + + scons -Q + + +
+ + + +
+ Where To Put Your Custom Builders and Tools + + + + The site_scons directory gives you a place to + put Python modules you can import into your SConscripts + (site_scons), add-on tools that can integrate into &SCons; + (site_scons/site_tools), and a site_scons/site_init.py file that + gets read before any &SConstruct; or &SConscript;, allowing you to + change &SCons;'s default behavior. + + + + + + If you get a tool from somewhere (the &SCons; wiki or a third party, + for instance) and you'd like to use it in your project, the + site_scons dir is the simplest place to put it. + Tools come in two flavors; either a Python function that operates on + an &Environment; or a Python file containing two functions, exists() + and generate(). + + + + + + A single-function Tool can just be included in your + site_scons/site_init.py file where it will be + parsed and made available for use. For instance, you could have a + site_scons/site_init.py file like this: + + + + + + def TOOL_ADD_HEADER(env): + """A Tool to add a header from $HEADER to the source file""" + add_header = Builder(action=['echo "$HEADER" > $TARGET', + 'cat $SOURCE >> $TARGET']) + env.Append(BUILDERS = {'AddHeader' : add_header}) + env['HEADER'] = '' # set default value + + + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + + + hi there + + + + + + and a &SConstruct; like this: + + + + + # Use TOOL_ADD_HEADER from site_scons/site_init.py + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + + + + + The TOOL_ADD_HEADER tool method will be + called to add the AddHeader tool to the + environment. + + + + + + + Similarly, a more full-fledged tool with + exists() and generate() + methods can be installed in + site_scons/site_tools/toolname.py. Since + site_scons/site_tools is automatically added + to the head of the tool search path, any tool found there will be + available to all environments. Furthermore, a tool found there + will override a built-in tool of the same name, so if you need to + change the behavior of a built-in tool, site_scons gives you the + hook you need. + + + + Many people have a library of utility Python functions they'd like + to include in &SConscript;s; just put that module in + site_scons/my_utils.py or any valid Python module name of your + choice. For instance you can do something like this in + site_scons/my_utils.py to add build_id and MakeWorkDir functions: + + + + + from SCons.Script import * # for Execute and Mkdir + def build_id(): + """Return a build ID (stub version)""" + return "100" + def MakeWorkDir(workdir): + """Create the specified dir immediately""" + Execute(Mkdir(workdir)) + + + import my_utils + MakeWorkDir('/tmp/work') + print "build_id=" + my_utils.build_id() + + + + + + And then in your &SConscript; or any sub-&SConscript; anywhere in + your build, you can import my_utils and use it: + + + + + import my_utils + print "build_id=" + my_utils.build_id() + my_utils.MakeWorkDir('/tmp/work') + + + + Note that although you can put this library in + site_scons/site_init.py, + it is no better there than site_scons/my_utils.py + since you still have to import that module into your &SConscript;. + Also note that in order to refer to objects in the SCons namespace + such as &Environment; or &Mkdir; or &Execute; in any file other + than a &SConstruct; or &SConscript; you always need to do + + + from SCons.Script import * + + + + This is true in modules in site_scons such as + site_scons/site_init.py as well. + + + + + If you have a machine-wide site dir you'd like to use instead of + ./site_scons, use the + --site-dir option to point to your dir. + site_init.py and + site_tools will be located under that dir. + To avoid using a site_scons dir at all, even + if it exists, use the --no-site-dir option. + + + +
+ + + diff --git a/doc/user/builders-writing.xml b/doc/user/builders-writing.xml new file mode 100644 index 0000000..b05cce6 --- /dev/null +++ b/doc/user/builders-writing.xml @@ -0,0 +1,953 @@ + + + + + + + Although &SCons; provides many useful methods + for building common software products: + programs, libraries, documents. + you frequently want to be + able to build some other type of file + not supported directly by &SCons;. + Fortunately, &SCons; makes it very easy + to define your own &Builder; objects + for any custom file types you want to build. + (In fact, the &SCons; interfaces for creating + &Builder; objects are flexible enough and easy enough to use + that all of the the &SCons; built-in &Builder; objects + are created the mechanisms described in this section.) + + + +
+ Writing Builders That Execute External Commands + + + + The simplest &Builder; to create is + one that executes an external command. + For example, if we want to build + an output file by running the contents + of the input file through a command named + foobuild, + creating that &Builder; might look like: + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + + + + + All the above line does is create a free-standing + &Builder; object. + The next section will show us how to actually use it. + + + +
+ +
+ Attaching a Builder to a &ConsEnv; + + + + A &Builder; object isn't useful + until it's attached to a &consenv; + so that we can call it to arrange + for files to be built. + This is done through the &cv-link-BUILDERS; + &consvar; in an environment. + The &cv-BUILDERS; variable is a Python dictionary + that maps the names by which you want to call + various &Builder; objects to the objects themselves. + For example, if we want to call the + &Builder; we just defined by the name + Foo, + our &SConstruct; file might look like: + + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + + + + + With the &Builder; attached to our &consenv; + with the name Foo, + we can now actually call it like so: + + + + + env.Foo('file.foo', 'file.input') + + + + + Then when we run &SCons; it looks like: + + + + + % scons -Q + foobuild < file.input > file.foo + + + + + Note, however, that the default &cv-BUILDERS; + variable in a &consenv; + comes with a default set of &Builder; objects + already defined: + &b-link-Program;, &b-link-Library;, etc. + And when we explicitly set the &cv-BUILDERS; variable + when we create the &consenv;, + the default &Builder;s are no longer part of + the environment: + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + % scons -Q + AttributeError: SConsEnvironment instance has no attribute 'Program': + File "/home/my/project/SConstruct", line 4: + env.Program('hello.c') + + + + + To be able to use both our own defined &Builder; objects + and the default &Builder; objects in the same &consenv;, + you can either add to the &cv-BUILDERS; variable + using the &Append; function: + + + + + + + env = Environment() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env.Append(BUILDERS = {'Foo' : bld}) + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + + Or you can explicitly set the appropriately-named + key in the &cv-BUILDERS; dictionary: + + + + + env = Environment() + bld = Builder(action = 'foobuild < $SOURCE > $TARGET') + env['BUILDERS']['Foo'] = bld + env.Foo('file.foo', 'file.input') + env.Program('hello.c') + + + + + Either way, the same &consenv; + can then use both the newly-defined + Foo &Builder; + and the default &b-link-Program; &Builder;: + + + + + % scons -Q + foobuild < file.input > file.foo + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ +
+ Letting &SCons; Handle The File Suffixes + + + + By supplying additional information + when you create a &Builder;, + you can let &SCons; add appropriate file + suffixes to the target and/or the source file. + For example, rather than having to specify + explicitly that you want the Foo + &Builder; to build the file.foo + target file from the file.input source file, + you can give the .foo + and .input suffixes to the &Builder;, + making for more compact and readable calls to + the Foo &Builder;: + + + + + + + bld = Builder(action = 'foobuild < $SOURCE > $TARGET', + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file1') + env.Foo('file2') + + + + % scons -Q + foobuild < file1.input > file1.foo + foobuild < file2.input > file2.foo + + + + + You can also supply a prefix keyword argument + if it's appropriate to have &SCons; append a prefix + to the beginning of target file names. + + + +
+ +
+ Builders That Execute Python Functions + + + + In &SCons;, you don't have to call an external command + to build a file. + You can, instead, define a Python function + that a &Builder; object can invoke + to build your target file (or files). + Such a &buildfunc; definition looks like: + + + + + def build_function(target, source, env): + # Code to build "target" from "source" + return None + + + + + The arguments of a &buildfunc; are: + + + + + + + target + + + + + A list of Node objects representing + the target or targets to be + built by this builder function. + The file names of these target(s) + may be extracted using the Python &str; function. + + + + + + + source + + + + + A list of Node objects representing + the sources to be + used by this builder function to build the targets. + The file names of these source(s) + may be extracted using the Python &str; function. + + + + + + + env + + + + + The &consenv; used for building the target(s). + The builder function may use any of the + environment's construction variables + in any way to affect how it builds the targets. + + + + + + + + + + The builder function must + return a 0 or None value + if the target(s) are built successfully. + The builder function + may raise an exception + or return any non-zero value + to indicate that the build is unsuccessful, + + + + + + Once you've defined the Python function + that will build your target file, + defining a &Builder; object for it is as + simple as specifying the name of the function, + instead of an external command, + as the &Builder;'s + action + argument: + + + + + def build_function(target, source, env): + # Code to build "target" from "source" + return None + bld = Builder(action = build_function, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + + And notice that the output changes slightly, + reflecting the fact that a Python function, + not an external command, + is now called to build the target file: + + + + + % scons -Q + build_function(["file.foo"], ["file.input"]) + + +
+ +
+ Builders That Create Actions Using a &Generator; + + + + &SCons; Builder objects can create an action "on the fly" + by using a function called a &generator;. + This provides a great deal of flexibility to + construct just the right list of commands + to build your target. + A &generator; looks like: + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (target[0], source[0]) + + + + + The arguments of a &generator; are: + + + + + + + source + + + + + A list of Node objects representing + the sources to be built + by the command or other action + generated by this function. + The file names of these source(s) + may be extracted using the Python &str; function. + + + + + + + + target + + + + + A list of Node objects representing + the target or targets to be built + by the command or other action + generated by this function. + The file names of these target(s) + may be extracted using the Python &str; function. + + + + + + + + env + + + + + The &consenv; used for building the target(s). + The generator may use any of the + environment's construction variables + in any way to determine what command + or other action to return. + + + + + + + + for_signature + + + + + A flag that specifies whether the + generator is being called to contribute to a build signature, + as opposed to actually executing the command. + + + + + + + + + + + + + The &generator; must return a + command string or other action that will be used to + build the specified target(s) from the specified source(s). + + + + + + Once you've defined a &generator;, + you create a &Builder; to use it + by specifying the generator keyword argument + instead of action. + + + + + + + def generate_actions(source, target, env, for_signature): + return 'foobuild < %s > %s' % (source[0], target[0]) + bld = Builder(generator = generate_actions, + suffix = '.foo', + src_suffix = '.input') + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + % scons -Q + foobuild < file.input > file.foo + + + + + Note that it's illegal to specify both an + action + and a + generator + for a &Builder;. + + + +
+ +
+ Builders That Modify the Target or Source Lists Using an &Emitter; + + + + &SCons; supports the ability for a Builder to modify the + lists of target(s) from the specified source(s). + You do this by defining an &emitter; function + that takes as its arguments + the list of the targets passed to the builder, + the list of the sources passed to the builder, + and the construction environment. + The emitter function should return the modified + lists of targets that should be built + and sources from which the targets will be built. + + + + + + For example, suppose you want to define a Builder + that always calls a foobuild program, + and you want to automatically add + a new target file named + new_target + and a new source file named + new_source + whenever it's called. + The &SConstruct; file might look like this: + + + + + + + def modify_targets(target, source, env): + target.append('new_target') + source.append('new_source') + return target, source + bld = Builder(action = 'foobuild $TARGETS - $SOURCES', + suffix = '.foo', + src_suffix = '.input', + emitter = modify_targets) + env = Environment(BUILDERS = {'Foo' : bld}) + env.Foo('file') + + + + + And would yield the following output: + + + + + % scons -Q + foobuild file.foo new_target - file.input new_source + + + + + One very flexible thing that you can do is + use a construction variable to specify + different emitter functions for different + construction variable. + To do this, specify a string + containing a construction variable + expansion as the emitter when you call + the &Builder; function, + and set that construction variable to + the desired emitter function + in different construction environments: + + + + + bld = Builder(action = 'my_command $SOURCES > $TARGET', + suffix = '.foo', + src_suffix = '.input', + emitter = '$MY_EMITTER') + def modify1(target, source, env): + return target, source + ['modify1.in'] + def modify2(target, source, env): + return target, source + ['modify2.in'] + env1 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify1) + env2 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify2) + env1.Foo('file1') + env2.Foo('file2') + import os + env1['ENV']['PATH'] = env2['ENV']['PATH'] + os.pathsep + os.getcwd() + env2['ENV']['PATH'] = env2['ENV']['PATH'] + os.pathsep + os.getcwd() + + + + + + bld = Builder(action = 'my_command $SOURCES > $TARGET', + suffix = '.foo', + src_suffix = '.input', + emitter = '$MY_EMITTER') + def modify1(target, source, env): + return target, source + ['modify1.in'] + def modify2(target, source, env): + return target, source + ['modify2.in'] + env1 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify1) + env2 = Environment(BUILDERS = {'Foo' : bld}, + MY_EMITTER = modify2) + env1.Foo('file1') + env2.Foo('file2') + + + + + + In this example, the modify1.in + and modify2.in files + get added to the source lists + of the different commands: + + + + + % scons -Q + my_command file1.input modify1.in > file1.foo + my_command file2.input modify2.in > file2.foo + + +
+ + + +
+ Where To Put Your Custom Builders and Tools + + + + The site_scons directory gives you a place to + put Python modules you can import into your SConscripts + (site_scons), add-on tools that can integrate into &SCons; + (site_scons/site_tools), and a site_scons/site_init.py file that + gets read before any &SConstruct; or &SConscript;, allowing you to + change &SCons;'s default behavior. + + + + + + If you get a tool from somewhere (the &SCons; wiki or a third party, + for instance) and you'd like to use it in your project, the + site_scons dir is the simplest place to put it. + Tools come in two flavors; either a Python function that operates on + an &Environment; or a Python file containing two functions, exists() + and generate(). + + + + + + A single-function Tool can just be included in your + site_scons/site_init.py file where it will be + parsed and made available for use. For instance, you could have a + site_scons/site_init.py file like this: + + + + + def TOOL_ADD_HEADER(env): + """A Tool to add a header from $HEADER to the source file""" + add_header = Builder(action=['echo "$HEADER" > $TARGET', + 'cat $SOURCE >> $TARGET']) + env.Append(BUILDERS = {'AddHeader' : add_header}) + env['HEADER'] = '' # set default value + + + + + and a &SConstruct; like this: + + + + + # Use TOOL_ADD_HEADER from site_scons/site_init.py + env=Environment(tools=['default', TOOL_ADD_HEADER], HEADER="=====") + env.AddHeader('tgt', 'src') + + + + + The TOOL_ADD_HEADER tool method will be + called to add the AddHeader tool to the + environment. + + + + + + + Similarly, a more full-fledged tool with + exists() and generate() + methods can be installed in + site_scons/site_tools/toolname.py. Since + site_scons/site_tools is automatically added + to the head of the tool search path, any tool found there will be + available to all environments. Furthermore, a tool found there + will override a built-in tool of the same name, so if you need to + change the behavior of a built-in tool, site_scons gives you the + hook you need. + + + + Many people have a library of utility Python functions they'd like + to include in &SConscript;s; just put that module in + site_scons/my_utils.py or any valid Python module name of your + choice. For instance you can do something like this in + site_scons/my_utils.py to add build_id and MakeWorkDir functions: + + + + from SCons.Script import * # for Execute and Mkdir + def build_id(): + """Return a build ID (stub version)""" + return "100" + def MakeWorkDir(workdir): + """Create the specified dir immediately""" + Execute(Mkdir(workdir)) + + + + + And then in your &SConscript; or any sub-&SConscript; anywhere in + your build, you can import my_utils and use it: + + + + + import my_utils + print "build_id=" + my_utils.build_id() + my_utils.MakeWorkDir('/tmp/work') + + + + Note that although you can put this library in + site_scons/site_init.py, + it is no better there than site_scons/my_utils.py + since you still have to import that module into your &SConscript;. + Also note that in order to refer to objects in the SCons namespace + such as &Environment; or &Mkdir; or &Execute; in any file other + than a &SConstruct; or &SConscript; you always need to do + + + from SCons.Script import * + + + + This is true in modules in site_scons such as + site_scons/site_init.py as well. + + + + + If you have a machine-wide site dir you'd like to use instead of + ./site_scons, use the + --site-dir option to point to your dir. + site_init.py and + site_tools will be located under that dir. + To avoid using a site_scons dir at all, even + if it exists, use the --no-site-dir option. + + + +
+ + + diff --git a/doc/user/builders.in b/doc/user/builders.in new file mode 100644 index 0000000..a2f94ce --- /dev/null +++ b/doc/user/builders.in @@ -0,0 +1,57 @@ + + + + + + +This appendix contains descriptions of all of the +Builders that are potentially +available "out of the box" in this version of SCons. + + + + + +&builders-gen; + + diff --git a/doc/user/builders.xml b/doc/user/builders.xml new file mode 100644 index 0000000..a2f94ce --- /dev/null +++ b/doc/user/builders.xml @@ -0,0 +1,57 @@ + + + + + + +This appendix contains descriptions of all of the +Builders that are potentially +available "out of the box" in this version of SCons. + + + + + +&builders-gen; + + diff --git a/doc/user/caching.in b/doc/user/caching.in new file mode 100644 index 0000000..31b1103 --- /dev/null +++ b/doc/user/caching.in @@ -0,0 +1,502 @@ + + + + + On multi-developer software projects, + you can sometimes speed up every developer's builds a lot by + allowing them to share the derived files that they build. + &SCons; makes this easy, as well as reliable. + + + +
+ Specifying the Shared Cache Directory + + + + To enable sharing of derived files, + use the &CacheDir; function + in any &SConscript; file: + + + + + + env = Environment() + env.Program('hello.c') + CacheDir('cache') + + + hello.c + + + + + CacheDir('/usr/local/build_cache') + + + + + + Note that the directory you specify must already exist + and be readable and writable by all developers + who will be sharing derived files. + It should also be in some central location + that all builds will be able to access. + In environments where developers are using separate systems + (like individual workstations) for builds, + this directory would typically be + on a shared or NFS-mounted file system. + + + + + + Here's what happens: + When a build has a &CacheDir; specified, + every time a file is built, + it is stored in the shared cache directory + along with its MD5 build signature. + + + Actually, the MD5 signature is used as the name of the file + in the shared cache directory in which the contents are stored. + + + On subsequent builds, + before an action is invoked to build a file, + &SCons; will check the shared cache directory + to see if a file with the exact same build + signature already exists. + If so, the derived file will not be built locally, + but will be copied into the local build directory + from the shared cache directory, + like so: + + + + + scons -Q + scons -Q -c + scons -Q + + + + + Note that the &CacheDir; feature still calculates + MD5 build sigantures for the shared cache file names + even if you configure &SCons; to use timestamps + to decide if files are up to date. + (See the + chapter for information about the &Decider; function.) + Consequently, using &CacheDir; may reduce or eliminate any + potential performance improvements + from using timestamps for up-to-date decisions. + + + +
+ +
+ Keeping Build Output Consistent + + + + One potential drawback to using a shared cache + is that the output printed by &SCons; + can be inconsistent from invocation to invocation, + because any given file may be rebuilt one time + and retrieved from the shared cache the next time. + This can make analyzing build output more difficult, + especially for automated scripts that + expect consistent output each time. + + + + + + If, however, you use the --cache-show option, + &SCons; will print the command line that it + would have executed + to build the file, + even when it is retrieving the file from the shared cache. + This makes the build output consistent + every time the build is run: + + + + + scons -Q + scons -Q -c + scons -Q --cache-show + + + + + The trade-off, of course, is that you no longer + know whether or not &SCons; + has retrieved a derived file from cache + or has rebuilt it locally. + + + +
+ +
+ Not Using the Shared Cache for Specific Files + + + + You may want to disable caching for certain + specific files in your configuration. + For example, if you only want to put + executable files in a central cache, + but not the intermediate object files, + you can use the &NoCache; + function to specify that the + object files should not be cached: + + + + + + env = Environment() + obj = env.Object('hello.c') + env.Program('hello.c') + CacheDir('cache') + NoCache('hello.o') + + + hello.c + + + + + + + + Then when you run &scons; after cleaning + the built targets, + it will recompile the object file locally + (since it doesn't exist in the shared cache directory), + but still realize that the shared cache directory + contains an up-to-date executable program + that can be retrieved instead of re-linking: + + + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q + cc -o hello.o -c hello.c + Retrieved `hello' from cache + + +
+ +
+ Disabling the Shared Cache + + + + Retrieving an already-built file + from the shared cache + is usually a significant time-savings + over rebuilding the file, + but how much of a savings + (or even whether it saves time at all) + can depend a great deal on your + system or network configuration. + For example, retrieving cached files + from a busy server over a busy network + might end up being slower than + rebuilding the files locally. + + + + + + In these cases, you can specify + the --cache-disable + command-line option to tell &SCons; + to not retrieve already-built files from the + shared cache directory: + + + + + scons -Q + scons -Q -c + scons -Q + scons -Q -c + scons -Q --cache-disable + + +
+ +
+ Populating a Shared Cache With Already-Built Files + + + + Sometimes, you may have one or more derived files + already built in your local build tree + that you wish to make available to other people doing builds. + For example, you may find it more effective to perform + integration builds with the cache disabled + (per the previous section) + and only populate the shared cache directory + with the built files after the integration build + has completed successfully. + This way, the cache will only get filled up + with derived files that are part of a complete, successful build + not with files that might be later overwritten + while you debug integration problems. + + + + + + In this case, you can use the + the --cache-force option + to tell &SCons; to put all derived files in the cache, + even if the files already exist in your local tree + from having been built by a previous invocation: + + + + + scons -Q --cache-disable + scons -Q -c + scons -Q --cache-disable + scons -Q --cache-force + scons -Q + + + + + Notice how the above sample run + demonstrates that the --cache-disable + option avoids putting the built + hello.o + and + hello files in the cache, + but after using the --cache-force option, + the files have been put in the cache + for the next invocation to retrieve. + + + +
+ +
+ Minimizing Cache Contention: the <literal>--random</literal> Option + + + + If you allow multiple builds to update the + shared cache directory simultaneously, + two builds that occur at the same time + can sometimes start "racing" + with one another to build the same files + in the same order. + If, for example, + you are linking multiple files into an executable program: + + + + + + Program('prog', + ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c']) + + f1.c + f2.c + f3.c + f4.c + f5.c + f6.c + + + + + &SCons; will normally build the input object files + on which the program depends in their normal, sorted order: + + + + + scons -Q + + + + + But if two such builds take place simultaneously, + they may each look in the cache at nearly the same + time and both decide that f1.o + must be rebuilt and pushed into the shared cache directory, + then both decide that f2.o + must be rebuilt (and pushed into the shared cache directory), + then both decide that f3.o + must be rebuilt... + This won't cause any actual build problems--both + builds will succeed, + generate correct output files, + and populate the cache--but + it does represent wasted effort. + + + + + + To alleviate such contention for the cache, + you can use the --random command-line option + to tell &SCons; to build dependencies + in a random order: + + + + + + + % scons -Q --random + cc -o f3.o -c f3.c + cc -o f1.o -c f1.c + cc -o f5.o -c f5.c + cc -o f2.o -c f2.c + cc -o f4.o -c f4.c + cc -o prog f1.o f2.o f3.o f4.o f5.o + + + + + Multiple builds using the --random option + will usually build their dependencies in different, + random orders, + which minimizes the chances for a lot of + contention for same-named files + in the shared cache directory. + Multiple simultaneous builds might still race to try to build + the same target file on occasion, + but long sequences of inefficient contention + should be rare. + + + + + + Note, of course, + the --random option + will cause the output that &SCons; prints + to be inconsistent from invocation to invocation, + which may be an issue when + trying to compare output from different build runs. + + + + + + If you want to make sure dependencies will be built + in a random order without having to specify + the --random on very command line, + you can use the &SetOption; function to + set the random option + within any &SConscript; file: + + + + + + SetOption('random', 1) + Program('prog', + ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c']) + + f1.c + f2.c + f3.c + f4.c + f5.c + f6.c + + +
+ + + + diff --git a/doc/user/caching.xml b/doc/user/caching.xml new file mode 100644 index 0000000..073539c --- /dev/null +++ b/doc/user/caching.xml @@ -0,0 +1,506 @@ + + + + + On multi-developer software projects, + you can sometimes speed up every developer's builds a lot by + allowing them to share the derived files that they build. + &SCons; makes this easy, as well as reliable. + + + +
+ Specifying the Shared Cache Directory + + + + To enable sharing of derived files, + use the &CacheDir; function + in any &SConscript; file: + + + + + CacheDir('/usr/local/build_cache') + + + + + Note that the directory you specify must already exist + and be readable and writable by all developers + who will be sharing derived files. + It should also be in some central location + that all builds will be able to access. + In environments where developers are using separate systems + (like individual workstations) for builds, + this directory would typically be + on a shared or NFS-mounted file system. + + + + + + Here's what happens: + When a build has a &CacheDir; specified, + every time a file is built, + it is stored in the shared cache directory + along with its MD5 build signature. + + + Actually, the MD5 signature is used as the name of the file + in the shared cache directory in which the contents are stored. + + + On subsequent builds, + before an action is invoked to build a file, + &SCons; will check the shared cache directory + to see if a file with the exact same build + signature already exists. + If so, the derived file will not be built locally, + but will be copied into the local build directory + from the shared cache directory, + like so: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q + Retrieved `hello.o' from cache + Retrieved `hello' from cache + + + + + Note that the &CacheDir; feature still calculates + MD5 build sigantures for the shared cache file names + even if you configure &SCons; to use timestamps + to decide if files are up to date. + (See the + chapter for information about the &Decider; function.) + Consequently, using &CacheDir; may reduce or eliminate any + potential performance improvements + from using timestamps for up-to-date decisions. + + + +
+ +
+ Keeping Build Output Consistent + + + + One potential drawback to using a shared cache + is that the output printed by &SCons; + can be inconsistent from invocation to invocation, + because any given file may be rebuilt one time + and retrieved from the shared cache the next time. + This can make analyzing build output more difficult, + especially for automated scripts that + expect consistent output each time. + + + + + + If, however, you use the --cache-show option, + &SCons; will print the command line that it + would have executed + to build the file, + even when it is retrieving the file from the shared cache. + This makes the build output consistent + every time the build is run: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q --cache-show + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + The trade-off, of course, is that you no longer + know whether or not &SCons; + has retrieved a derived file from cache + or has rebuilt it locally. + + + +
+ +
+ Not Using the Shared Cache for Specific Files + + + + You may want to disable caching for certain + specific files in your configuration. + For example, if you only want to put + executable files in a central cache, + but not the intermediate object files, + you can use the &NoCache; + function to specify that the + object files should not be cached: + + + + + env = Environment() + obj = env.Object('hello.c') + env.Program('hello.c') + CacheDir('cache') + NoCache('hello.o') + + + + + Then when you run &scons; after cleaning + the built targets, + it will recompile the object file locally + (since it doesn't exist in the shared cache directory), + but still realize that the shared cache directory + contains an up-to-date executable program + that can be retrieved instead of re-linking: + + + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q + cc -o hello.o -c hello.c + Retrieved `hello' from cache + + +
+ +
+ Disabling the Shared Cache + + + + Retrieving an already-built file + from the shared cache + is usually a significant time-savings + over rebuilding the file, + but how much of a savings + (or even whether it saves time at all) + can depend a great deal on your + system or network configuration. + For example, retrieving cached files + from a busy server over a busy network + might end up being slower than + rebuilding the files locally. + + + + + + In these cases, you can specify + the --cache-disable + command-line option to tell &SCons; + to not retrieve already-built files from the + shared cache directory: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q + Retrieved `hello.o' from cache + Retrieved `hello' from cache + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q --cache-disable + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ +
+ Populating a Shared Cache With Already-Built Files + + + + Sometimes, you may have one or more derived files + already built in your local build tree + that you wish to make available to other people doing builds. + For example, you may find it more effective to perform + integration builds with the cache disabled + (per the previous section) + and only populate the shared cache directory + with the built files after the integration build + has completed successfully. + This way, the cache will only get filled up + with derived files that are part of a complete, successful build + not with files that might be later overwritten + while you debug integration problems. + + + + + + In this case, you can use the + the --cache-force option + to tell &SCons; to put all derived files in the cache, + even if the files already exist in your local tree + from having been built by a previous invocation: + + + + + % scons -Q --cache-disable + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q -c + Removed hello.o + Removed hello + % scons -Q --cache-disable + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q --cache-force + scons: `.' is up to date. + % scons -Q + scons: `.' is up to date. + + + + + Notice how the above sample run + demonstrates that the --cache-disable + option avoids putting the built + hello.o + and + hello files in the cache, + but after using the --cache-force option, + the files have been put in the cache + for the next invocation to retrieve. + + + +
+ +
+ Minimizing Cache Contention: the <literal>--random</literal> Option + + + + If you allow multiple builds to update the + shared cache directory simultaneously, + two builds that occur at the same time + can sometimes start "racing" + with one another to build the same files + in the same order. + If, for example, + you are linking multiple files into an executable program: + + + + + Program('prog', + ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c']) + + + + + &SCons; will normally build the input object files + on which the program depends in their normal, sorted order: + + + + + % scons -Q + cc -o f1.o -c f1.c + cc -o f2.o -c f2.c + cc -o f3.o -c f3.c + cc -o f4.o -c f4.c + cc -o f5.o -c f5.c + cc -o prog f1.o f2.o f3.o f4.o f5.o + + + + + But if two such builds take place simultaneously, + they may each look in the cache at nearly the same + time and both decide that f1.o + must be rebuilt and pushed into the shared cache directory, + then both decide that f2.o + must be rebuilt (and pushed into the shared cache directory), + then both decide that f3.o + must be rebuilt... + This won't cause any actual build problems--both + builds will succeed, + generate correct output files, + and populate the cache--but + it does represent wasted effort. + + + + + + To alleviate such contention for the cache, + you can use the --random command-line option + to tell &SCons; to build dependencies + in a random order: + + + + + + + % scons -Q --random + cc -o f3.o -c f3.c + cc -o f1.o -c f1.c + cc -o f5.o -c f5.c + cc -o f2.o -c f2.c + cc -o f4.o -c f4.c + cc -o prog f1.o f2.o f3.o f4.o f5.o + + + + + Multiple builds using the --random option + will usually build their dependencies in different, + random orders, + which minimizes the chances for a lot of + contention for same-named files + in the shared cache directory. + Multiple simultaneous builds might still race to try to build + the same target file on occasion, + but long sequences of inefficient contention + should be rare. + + + + + + Note, of course, + the --random option + will cause the output that &SCons; prints + to be inconsistent from invocation to invocation, + which may be an issue when + trying to compare output from different build runs. + + + + + + If you want to make sure dependencies will be built + in a random order without having to specify + the --random on very command line, + you can use the &SetOption; function to + set the random option + within any &SConscript; file: + + + + + Program('prog', + ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c']) + + SetOption('random', 1) + Program('prog', + ['f1.c', 'f2.c', 'f3.c', 'f4.c', 'f5.c']) + + +
+ + + + diff --git a/doc/user/command-line.in b/doc/user/command-line.in new file mode 100644 index 0000000..686146e --- /dev/null +++ b/doc/user/command-line.in @@ -0,0 +1,2327 @@ + + + + + &SCons; provides a number of ways + for the writer of the &SConscript; files + to give the users who will run &SCons; + a great deal of control over the build execution. + The arguments that the user can specify on + the command line are broken down into three types: + + + + + + + Options + + + + + Command-line options always begin with + one or two - (hyphen) characters. + &SCons; provides ways for you to examine + and set options values from within your &SConscript; files, + as well as the ability to define your own + custom options. + See , below. + + + + + + + Variables + + + + + Any command-line argument containing an = + (equal sign) is considered a variable setting with the form + variable=value + &SCons; provides direct access to + all of the command-line variable settings, + the ability to apply command-line variable settings + to construction environments, + and functions for configuring + specific types of variables + (Boolean values, path names, etc.) + with automatic validation of the user's specified values. + See , below. + + + + + + + Targets + + + + + Any command-line argument that is not an option + or a variable setting + (does not begin with a hyphen + and does not contain an equal sign) + is considered a target that the user + (presumably) wants &SCons; to build. + A list of Node objects representing + the target or targets to build. + &SCons; provides access to the list of specified targets, + as well as ways to set the default list of targets + from within the &SConscript; files. + See , below. + + + + + + + +
+ Command-Line Options + + + + &SCons; has many command-line options + that control its behavior. + A &SCons; command-line option + always begins with one or two - (hyphen) + characters. + + + +
+ Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable + + + + Users may find themselves supplying + the same command-line options every time + they run &SCons;. + For example, you might find it saves time + to specify a value of -j 2 + to have &SCons; run up to two build commands in parallel. + To avoid having to type -j 2 by hand + every time, + you can set the external environment variable + &SCONSFLAGS; to a string containing + command-line options that you want &SCons; to use. + + + + + + If, for example, + you're using a POSIX shell that's + compatible with the Bourne shell, + and you always want &SCons; to use the + -Q option, + you can set the &SCONSFLAGS; + environment as follows: + + + + + + def b(target, source, env): + pass + def s(target, source, env): + return " ... [build output] ..." + a = Action(b, strfunction = s) + env = Environment(BUILDERS = {'A' : Builder(action=a)}) + env.A('foo.out', 'foo.in') + + + foo.in + + + + + scons + export SCONSFLAGS="-Q" + scons + + + + + Users of &csh;-style shells on POSIX systems + can set the &SCONSFLAGS; environment as follows: + + + + + $ setenv SCONSFLAGS "-Q" + + + + + Windows users may typically want to set the + &SCONSFLAGS; in the appropriate tab of the + System Properties window. + + + +
+ +
+ Getting Values Set by Command-Line Options: the &GetOption; Function + + + + &SCons; provides the &GetOption; function + to get the values set by the various command-line options. + One common use of this is to check whether or not + the -h or --help option + has been specified. + Normally, &SCons; does not print its help text + until after it has read all of the &SConscript; files, + because it's possible that help text has been added + by some subsidiary &SConscript; file deep in the + source tree hierarchy. + Of course, reading all of the &SConscript; files + takes extra time. + + + + + + If you know that your configuration does not define + any additional help text in subsidiary &SConscript; files, + you can speed up the command-line help available to users + by using the &GetOption; function to load the + subsidiary &SConscript; files only if the + the user has not specified + the -h or --help option, + like so: + + + + + + + + In general, the string that you pass to the + &GetOption; function to fetch the value of a command-line + option setting is the same as the "most common" long option name + (beginning with two hyphen characters), + although there are some exceptions. + The list of &SCons; command-line options + and the &GetOption; strings for fetching them, + are available in the + section, + below. + + + +
+ +
+ Setting Values of Command-Line Options: the &SetOption; Function + + + + You can also set the values of &SCons; + command-line options from within the &SConscript; files + by using the &SetOption; function. + The strings that you use to set the values of &SCons; + command-line options are available in the + section, + below. + + + + + + One use of the &SetOption; function is to + specify a value for the -j + or --jobs option, + so that users get the improved performance + of a parallel build without having to specify the option by hand. + A complicating factor is that a good value + for the -j option is + somewhat system-dependent. + One rough guideline is that the more processors + your system has, + the higher you want to set the + -j value, + in order to take advantage of the number of CPUs. + + + + + + For example, suppose the administrators + of your development systems + have standardized on setting a + NUM_CPU environment variable + to the number of processors on each system. + A little bit of Python code + to access the environment variable + and the &SetOption; function + provide the right level of flexibility: + + + + + + import os + num_cpu = int(os.environ.get('NUM_CPU', 2)) + SetOption('num_jobs', num_cpu) + print "running with -j", GetOption('num_jobs') + + + foo.in + + + + + + The above snippet of code + sets the value of the --jobs option + to the value specified in the + $NUM_CPU environment variable. + (This is one of the exception cases + where the string is spelled differently from + the from command-line option. + The string for fetching or setting the --jobs + value is num_jobs + for historical reasons.) + The code in this example prints the num_jobs + value for illustrative purposes. + It uses a default value of 2 + to provide some minimal parallelism even on + single-processor systems: + + + + + scons -Q + + + + + But if the $NUM_CPU + environment variable is set, + then we use that for the default number of jobs: + + + + + export NUM_CPU="4" + scons -Q + + + + + But any explicit + -j or --jobs + value the user specifies an the command line is used first, + regardless of whether or not + the $NUM_CPU environment + variable is set: + + + + + scons -Q -j 7 + export NUM_CPU="4" + scons -Q -j 3 + + +
+ +
+ Strings for Getting or Setting Values of &SCons; Command-Line Options + + + + The strings that you can pass to the &GetOption; + and &SetOption; functions usually correspond to the + first long-form option name + (beginning with two hyphen characters: --), + after replacing any remaining hyphen characters + with underscores. + + + + + + The full list of strings and the variables they + correspond to is as follows: + + + + + + + + + + String for &GetOption; and &SetOption; + Command-Line Option(s) + + + + + + + + cache_debug + + + + + cache_disable + + + + + cache_force + + + + + cache_show + + + + + clean + , + , + + + + + config + + + + + directory + , + + + + + diskcheck + + + + + duplicate + + + + + file + , + , + , + + + + + help + , + + + + + ignore_errors + + + + + implicit_cache + + + + + implicit_deps_changed + + + + + implicit_deps_unchanged + + + + + interactive + , + + + + + keep_going + , + + + + + max_drift + + + + + no_exec + , + , + , + , + + + + + no_site_dir + + + + + num_jobs + , + + + + + profile_file + + + + + question + , + + + + + random + + + + + repository + , + , + + + + + silent + , + , + + + + + site_dir + + + + + stack_size + + + + + taskmastertrace_file + + + + + warn + + + + + + + + +
+ +
+ Adding Custom Command-Line Options: the &AddOption; Function + + + + &SCons; also allows you to define your own + command-line options with the &AddOption; function. + The &AddOption; function takes the same arguments + as the optparse.add_option function + from the standard Python library. + + + The &AddOption; function is, + in fact, implemented using a subclass + of the optparse.OptionParser. + + + Once you have added a custom command-line option + with the &AddOption; function, + the value of the option (if any) is immediately available + using the standard &GetOption; function. + (The value can also be set using &SetOption;, + although that's not very useful in practice + because a default value can be specified in + directly in the &AddOption; call.) + + + + + + One useful example of using this functionality + is to provide a for users: + + + + + + AddOption('--prefix', + dest='prefix', + type='string', + nargs=1, + action='store', + metavar='DIR', + help='installation prefix') + + env = Environment(PREFIX = GetOption('prefix')) + + installed_foo = env.Install('$PREFIX/usr/bin', 'foo.in') + Default(installed_foo) + + + foo.in + + + + + + The above code uses the &GetOption; function + to set the $PREFIX + construction variable to any + value that the user specifies with a command-line + option of --prefix. + Because $PREFIX + will expand to a null string if it's not initialized, + running &SCons; without the + option of --prefix + will install the file in the + /usr/bin/ directory: + + + + + scons -Q -n + + + + + But specifying --prefix=/tmp/install + on the command line causes the file to be installed in the + /tmp/install/usr/bin/ directory: + + + + + scons -Q -n --prefix=/tmp/install + + +
+ +
+ +
+ Command-Line <varname>variable</varname>=<varname>value</varname> Build Variables + + + + You may want to control various aspects + of your build by allowing the user + to specify variable=value + values on the command line. + For example, suppose you + want users to be able to + build a debug version of a program + by running &SCons; as follows: + + + + + % scons -Q debug=1 + + + + + &SCons; provides an &ARGUMENTS; dictionary + that stores all of the + variable=value + assignments from the command line. + This allows you to modify + aspects of your build in response + to specifications on the command line. + (Note that unless you want to require + that users always + specify a variable, + you probably want to use + the Python + ARGUMENTS.get() function, + which allows you to specify a default value + to be used if there is no specification + on the command line.) + + + + + + The following code sets the &cv-link-CCFLAGS; construction + variable in response to the debug + flag being set in the &ARGUMENTS; dictionary: + + + + + + env = Environment() + debug = ARGUMENTS.get('debug', 0) + if int(debug): + env.Append(CCFLAGS = '-g') + env.Program('prog.c') + + + prog.c + + + + + + This results in the -g + compiler option being used when + debug=1 + is used on the command line: + + + + + scons -Q debug=0 + scons -Q debug=0 + scons -Q debug=1 + scons -Q debug=1 + + + + + Notice that &SCons; keeps track of + the last values used to build the object files, + and as a result correctly rebuilds + the object and executable files + only when the value of the debug + argument has changed. + + + + + + The &ARGUMENTS; dictionary has two minor drawbacks. + First, because it is a dictionary, + it can only store one value for each specified keyword, + and thus only "remembers" the last setting + for each keyword on the command line. + This makes the &ARGUMENTS; dictionary + inappropriate if users should be able to + specify multiple values + on the command line for a given keyword. + Second, it does not preserve + the order in which the variable settings + were specified, + which is a problem if + you want the configuration to + behave differently in response + to the order in which the build + variable settings were specified on the command line. + + + + + + To accomodate these requirements, + &SCons; provides an &ARGLIST; variable + that gives you direct access to + variable=value + settings on the command line, + in the exact order they were specified, + and without removing any duplicate settings. + Each element in the &ARGLIST; variable + is itself a two-element list + containing the keyword and the value + of the setting, + and you must loop through, + or otherwise select from, + the elements of &ARGLIST; to + process the specific settings you want + in whatever way is appropriate for your configuration. + For example, + the following code to let the user + add to the &CPPDEFINES; construction variable + by specifying multiple + define= + settings on the command line: + + + + + + cppdefines = [] + for key, value in ARGLIST: + if key == 'define': + cppdefines.append(value) + env = Environment(CPPDEFINES = cppdefines) + env.Object('prog.c') + + + prog.c + + + + + + Yields the following output: + + + + + scons -Q define=FOO + scons -Q define=FOO define=BAR + + + + + Note that the &ARGLIST; and &ARGUMENTS; + variables do not interfere with each other, + but merely provide slightly different views + into how the user specified + variable=value + settings on the command line. + You can use both variables in the same + &SCons; configuration. + In general, the &ARGUMENTS; dictionary + is more convenient to use, + (since you can just fetch variable + settings through a dictionary access), + and the &ARGLIST; list + is more flexible + (since you can examine the + specific order in which + the user's command-line variabe settings). + + + +
+ Controlling Command-Line Build Variables + + + + Being able to use a command-line build variable like + debug=1 is handy, + but it can be a chore to write specific Python code + to recognize each such variable, + check for errors and provide appropriate messages, + and apply the values to a construction variable. + To help with this, + &SCons; supports a class to + define such build variables easily, + and a mechanism to apply the + build variables to a construction environment. + This allows you to control how the build variables affect + construction environments. + + + + + + For example, suppose that you want users to set + a &RELEASE; construction variable on the + command line whenever the time comes to build + a program for release, + and that the value of this variable + should be added to the command line + with the appropriate -D option + (or other command line option) + to pass the value to the C compiler. + Here's how you might do that by setting + the appropriate value in a dictionary for the + &cv-link-CPPDEFINES; construction variable: + + + + + + vars = Variables() + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + + + foo.c + + + bar.c + + + + + + This &SConstruct; file first creates a &Variables; object + (the vars = Variables() call), + and then uses the object's &Add; + method to indicate that the &RELEASE; + variable can be set on the command line, + and that its default value will be 0 + (the third argument to the &Add; method). + The second argument is a line of help text; + we'll learn how to use it in the next section. + + + + + + We then pass the created &Variables; + object as a &variables; keyword argument + to the &Environment; call + used to create the construction environment. + This then allows a user to set the + &RELEASE; build variable on the command line + and have the variable show up in + the command line used to build each object from + a C source file: + + + + + scons -Q RELEASE=1 + + + + + NOTE: Before &SCons; release 0.98.1, these build variables + were known as "command-line build options." + The class was actually named the &Options; class, + and in the sections below, + the various functions were named + &BoolOption;, &EnumOption;, &ListOption;, + &PathOption;, &PackageOption; and &AddOptions;. + These older names still work, + and you may encounter them in older + &SConscript; fles, + but their use is discouraged + and will be officially deprecated some day. + + + +
+ +
+ Providing Help for Command-Line Build Variables + + + + To make command-line build variables most useful, + you ideally want to provide + some help text that will describe + the available variables + when the user runs scons -h. + You could write this text by hand, + but &SCons; provides an easier way. + &Variables; objects support a + &GenerateHelpText; method + that will, as its name suggests, + generate text that describes + the various variables that + have been added to it. + You then pass the output from this method to + the &Help; function: + + + + + + vars = Variables('custom.py') + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars) + Help(vars.GenerateHelpText(env)) + + + + + + &SCons; will now display some useful text + when the -h option is used: + + + + + scons -Q -h + + + + + Notice that the help output shows the default value, + and the current actual value of the build variable. + + + +
+ +
+ Reading Build Variables From a File + + + + Giving the user a way to specify the + value of a build variable on the command line + is useful, + but can still be tedious + if users must specify the variable + every time they run &SCons;. + We can let users provide customized build variable settings + in a local file by providing a + file name when we create the + &Variables; object: + + + + + + vars = Variables('custom.py') + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + Help(vars.GenerateHelpText(env)) + + + foo.c + + + bar.c + + + RELEASE = 1 + + + + + + This then allows the user to control the &RELEASE; + variable by setting it in the &custom_py; file: + + + + + + + + Note that this file is actually executed + like a Python script. + Now when we run &SCons;: + + + + + scons -Q + + + + + And if we change the contents of &custom_py; to: + + + + + + vars = Variables('custom.py') + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + Help(vars.GenerateHelpText(env)) + + + foo.c + + + bar.c + + + RELEASE = 0 + + + + + + The object files are rebuilt appropriately + with the new variable: + + + + + scons -Q + + +
+ +
+ Pre-Defined Build Variable Functions + + + + &SCons; provides a number of functions + that provide ready-made behaviors + for various types of command-line build variables. + + + +
+ True/False Values: the &BoolVariable; Build Variable Function + + + + It's often handy to be able to specify a + variable that controls a simple Boolean variable + with a &true; or &false; value. + It would be even more handy to accomodate + users who have different preferences for how to represent + &true; or &false; values. + The &BoolVariable; function + makes it easy to accomodate these + common representations of + &true; or &false;. + + + + + + The &BoolVariable; function takes three arguments: + the name of the build variable, + the default value of the build variable, + and the help string for the variable. + It then returns appropriate information for + passing to the &Add; method of a &Variables; object, like so: + + + + + + vars = Variables('custom.py') + vars.Add(BoolVariable('RELEASE', 'Set to build for release', 0)) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program('foo.c') + + + foo.c + + + + + + With this build variable, + the &RELEASE; variable can now be enabled by + setting it to the value yes + or t: + + + + + scons -Q RELEASE=yes foo.o + + + + scons -Q RELEASE=t foo.o + + + + + Other values that equate to &true; include + y, + 1, + on + and + all. + + + + + + Conversely, &RELEASE; may now be given a &false; + value by setting it to + no + or + f: + + + + + scons -Q RELEASE=no foo.o + + + + scons -Q RELEASE=f foo.o + + + + + Other values that equate to &false; include + n, + 0, + off + and + none. + + + + + + Lastly, if a user tries to specify + any other value, + &SCons; supplies an appropriate error message: + + + + + scons -Q RELEASE=bad_value foo.o + + +
+ +
+ Single Value From a List: the &EnumVariable; Build Variable Function + + + + Suppose that we want a user to be able to + set a &COLOR; variable + that selects a background color to be + displayed by an application, + but that we want to restrict the + choices to a specific set of allowed colors. + This can be set up quite easily + using the &EnumVariable;, + which takes a list of &allowed_values + in addition to the variable name, + default value, + and help text arguments: + + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'))) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + foo.c + + + + + + The user can now explicity set the &COLOR; build variable + to any of the specified allowed values: + + + + + scons -Q COLOR=red foo.o + scons -Q COLOR=blue foo.o + scons -Q COLOR=green foo.o + + + + + But, almost more importantly, + an attempt to set &COLOR; + to a value that's not in the list + generates an error message: + + + + + scons -Q COLOR=magenta foo.o + + + + + The &EnumVariable; function also supports a way + to map alternate names to allowed values. + Suppose, for example, + that we want to allow the user + to use the word navy as a synonym for + blue. + We do this by adding a ↦ dictionary + that will map its key values + to the desired legal value: + + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'})) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + foo.c + + + + + + As desired, the user can then use + navy on the command line, + and &SCons; will translate it into blue + when it comes time to use the &COLOR; + variable to build a target: + + + + + scons -Q COLOR=navy foo.o + + + + + By default, when using the &EnumVariable; function, + arguments that differ + from the legal values + only in case + are treated as illegal values: + + + + + scons -Q COLOR=Red foo.o + scons -Q COLOR=BLUE foo.o + scons -Q COLOR=nAvY foo.o + + + + + The &EnumVariable; function can take an additional + &ignorecase; keyword argument that, + when set to 1, + tells &SCons; to allow case differences + when the values are specified: + + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=1)) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + foo.c + + + + + + Which yields the output: + + + + + scons -Q COLOR=Red foo.o + scons -Q COLOR=BLUE foo.o + scons -Q COLOR=nAvY foo.o + scons -Q COLOR=green foo.o + + + + + Notice that an &ignorecase; value of 1 + preserves the case-spelling that the user supplied. + If you want &SCons; to translate the names + into lower-case, + regardless of the case used by the user, + specify an &ignorecase; value of 2: + + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=2)) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + foo.c + + + + + + Now &SCons; will use values of + red, + green or + blue + regardless of how the user spells + those values on the command line: + + + + + scons -Q COLOR=Red foo.o + scons -Q COLOR=nAvY foo.o + scons -Q COLOR=GREEN foo.o + + +
+ +
+ Multiple Values From a List: the &ListVariable; Build Variable Function + + + + Another way in which you might want to allow users + to control a build variable is to + specify a list of one or more legal values. + &SCons; supports this through the &ListVariable; function. + If, for example, we want a user to be able to set a + &COLORS; variable to one or more of the legal list of values: + + + + + + vars = Variables('custom.py') + vars.Add(ListVariable('COLORS', 'List of colors', 0, + ['red', 'green', 'blue'])) + env = Environment(variables = vars, + CPPDEFINES={'COLORS' : '"${COLORS}"'}) + env.Program('foo.c') + + + foo.c + + + + + + A user can now specify a comma-separated list + of legal values, + which will get translated into a space-separated + list for passing to the any build commands: + + + + + scons -Q COLORS=red,blue foo.o + scons -Q COLORS=blue,green,red foo.o + + + + + In addition, the &ListVariable; function + allows the user to specify explicit keywords of + &all; or &none; + to select all of the legal values, + or none of them, respectively: + + + + + scons -Q COLORS=all foo.o + scons -Q COLORS=none foo.o + + + + + And, of course, an illegal value + still generates an error message: + + + + + scons -Q COLORS=magenta foo.o + + +
+ +
+ Path Names: the &PathVariable; Build Variable Function + + + + &SCons; supports a &PathVariable; function + to make it easy to create a build variable + to control an expected path name. + If, for example, you need to + define a variable in the preprocessor + that controls the location of a + configuration file: + + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('CONFIG', + 'Path to configuration file', + '__ROOT__/etc/my_config')) + env = Environment(variables = vars, + CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) + env.Program('foo.c') + + + foo.c + + + /opt/location + + + /opt/location + + + + + + This then allows the user to + override the &CONFIG; build variable + on the command line as necessary: + + + + + scons -Q foo.o + scons -Q CONFIG=__ROOT__/usr/local/etc/other_config foo.o + + + + + By default, &PathVariable; checks to make sure + that the specified path exists and generates an error if it + doesn't: + + + + + scons -Q CONFIG=__ROOT__/does/not/exist foo.o + + + + + &PathVariable; provides a number of methods + that you can use to change this behavior. + If you want to ensure that any specified paths are, + in fact, files and not directories, + use the &PathVariable_PathIsFile; method: + + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('CONFIG', + 'Path to configuration file', + '__ROOT__/etc/my_config', + PathVariable.PathIsFile)) + env = Environment(variables = vars, + CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) + env.Program('foo.c') + + + foo.c + + + /opt/location + + + + + + Conversely, to ensure that any specified paths are + directories and not files, + use the &PathVariable_PathIsDir; method: + + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('DBDIR', + 'Path to database directory', + '__ROOT__/var/my_dbdir', + PathVariable.PathIsDir)) + env = Environment(variables = vars, + CPPDEFINES={'DBDIR' : '"$DBDIR"'}) + env.Program('foo.c') + + + foo.c + + + /opt/location + + + + + + If you want to make sure that any specified paths + are directories, + and you would like the directory created + if it doesn't already exist, + use the &PathVariable_PathIsDirCreate; method: + + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('DBDIR', + 'Path to database directory', + '__ROOT__/var/my_dbdir', + PathVariable.PathIsDirCreate)) + env = Environment(variables = vars, + CPPDEFINES={'DBDIR' : '"$DBDIR"'}) + env.Program('foo.c') + + + foo.c + + + /opt/location + + + + + + Lastly, if you don't care whether the path exists, + is a file, or a directory, + use the &PathVariable_PathAccept; method + to accept any path that the user supplies: + + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('OUTPUT', + 'Path to output file or directory', + None, + PathVariable.PathAccept)) + env = Environment(variables = vars, + CPPDEFINES={'OUTPUT' : '"$OUTPUT"'}) + env.Program('foo.c') + + + foo.c + + + +
+ +
+ Enabled/Disabled Path Names: the &PackageVariable; Build Variable Function + + + + Sometimes you want to give users + even more control over a path name variable, + allowing them to explicitly enable or + disable the path name + by using yes or no keywords, + in addition to allow them + to supply an explicit path name. + &SCons; supports the &PackageVariable; + function to support this: + + + + + + vars = Variables('custom.py') + vars.Add(PackageVariable('PACKAGE', + 'Location package', + '__ROOT__/opt/location')) + env = Environment(variables = vars, + CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) + env.Program('foo.c') + + + foo.c + + + /opt/location + + + /opt/location + + + + + + When the &SConscript; file uses the &PackageVariable; funciton, + user can now still use the default + or supply an overriding path name, + but can now explicitly set the + specified variable to a value + that indicates the package should be enabled + (in which case the default should be used) + or disabled: + + + + + scons -Q foo.o + scons -Q PACKAGE=__ROOT__/usr/local/location foo.o + scons -Q PACKAGE=yes foo.o + scons -Q PACKAGE=no foo.o + + +
+ +
+ +
+ Adding Multiple Command-Line Build Variables at Once + + + + Lastly, &SCons; provides a way to add + multiple build variables to a &Variables; object at once. + Instead of having to call the &Add; method + multiple times, + you can call the &AddVariables; + method with a list of build variables + to be added to the object. + Each build variable is specified + as either a tuple of arguments, + just like you'd pass to the &Add; method itself, + or as a call to one of the pre-defined + functions for pre-packaged command-line build variables. + in any order: + + + + + + vars = Variables() + vars.AddVariables( + ('RELEASE', 'Set to 1 to build for release', 0), + ('CONFIG', 'Configuration file', '/etc/my_config'), + BoolVariable('warnings', 'compilation with -Wall and similiar', 1), + EnumVariable('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + ListVariable('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageVariable('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), + PathVariable('qtdir', 'where the root of Qt is installed', qtdir), + ) + + + + + + +
+ +
+ Handling Unknown Command-Line Build Variables: the &UnknownVariables; Function + + + + Users may, of course, + occasionally misspell variable names in their command-line settings. + &SCons; does not generate an error or warning + for any unknown variables the users specifies on the command line. + (This is in no small part because you may be + processing the arguments directly using the &ARGUMENTS; dictionary, + and therefore &SCons; can't know in the general case + whether a given "misspelled" variable is + really unknown and a potential problem, + or something that your &SConscript; file + will handle directly with some Python code.) + + + + + + If, however, you're using a &Variables; object to + define a specific set of command-line build variables + that you expect users to be able to set, + you may want to provide an error + message or warning of your own + if the user supplies a variable setting + that is not among + the defined list of variable names known to the &Variables; object. + You can do this by calling the &UnknownVariables; + method of the &Variables; object: + + + + + + vars = Variables(None) + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + unknown = vars.UnknownVariables() + if unknown: + print "Unknown variables:", unknown.keys() + Exit(1) + env.Program('foo.c') + + + foo.c + + + + + + The &UnknownVariables; method returns a dictionary + containing the keywords and values + of any variables the user specified on the command line + that are not + among the variables known to the &Variables; object + (from having been specified using + the &Variables; object's&Add; method). + In the examble above, + we check for whether the dictionary + returned by the &UnknownVariables; is non-empty, + and if so print the Python list + containing the names of the unknwown variables + and then call the &Exit; function + to terminate &SCons;: + + + + + scons -Q NOT_KNOWN=foo + + + + + Of course, you can process the items in the + dictionary returned by the &UnknownVariables; function + in any way appropriate to your build configuration, + including just printing a warning message + but not exiting, + logging an error somewhere, + etc. + + + + + + Note that you must delay the call of &UnknownVariables; + until after you have applied the &Variables; object + to a construction environment + with the variables= + keyword argument of an &Environment; call. + + + +
+ +
+ +
+ Command-Line Targets + +
+ Fetching Command-Line Targets: the &COMMAND_LINE_TARGETS; Variable + + + + &SCons; supports a &COMMAND_LINE_TARGETS; variable + that lets you fetch the list of targets that the + user specified on the command line. + You can use the targets to manipulate the + build in any way you wish. + As a simple example, + suppose that you want to print a reminder + to the user whenever a specific program is built. + You can do this by checking for the + target in the &COMMAND_LINE_TARGETS; list: + + + + + + if 'bar' in COMMAND_LINE_TARGETS: + print "Don't forget to copy `bar' to the archive!" + Default(Program('foo.c')) + Program('bar.c') + + + foo.c + + + foo.c + + + + + + Then, running &SCons; with the default target + works as it always does, + but explicity specifying the &bar; target + on the command line generates the warning message: + + + + + scons -Q + scons -Q bar + + + + + Another practical use for the &COMMAND_LINE_TARGETS; variable + might be to speed up a build + by only reading certain subsidiary &SConscript; + files if a specific target is requested. + + + +
+ +
+ Controlling the Default Targets: the &Default; Function + + + + One of the most basic things you can control + is which targets &SCons; will build by default--that is, + when there are no targets specified on the command line. + As mentioned previously, + &SCons; will normally build every target + in or below the current directory + by default--that is, when you don't + explicitly specify one or more targets + on the command line. + Sometimes, however, you may want + to specify explicitly that only + certain programs, or programs in certain directories, + should be built by default. + You do this with the &Default; function: + + + + + + env = Environment() + hello = env.Program('hello.c') + env.Program('goodbye.c') + Default(hello) + + + hello.c + + + goodbye.c + + + + + + This &SConstruct; file knows how to build two programs, + &hello; and &goodbye;, + but only builds the + &hello; program by default: + + + + + scons -Q + scons -Q + scons -Q goodbye + + + + + Note that, even when you use the &Default; + function in your &SConstruct; file, + you can still explicitly specify the current directory + (.) on the command line + to tell &SCons; to build + everything in (or below) the current directory: + + + + + scons -Q . + + + + + You can also call the &Default; + function more than once, + in which case each call + adds to the list of targets to be built by default: + + + + + + env = Environment() + prog1 = env.Program('prog1.c') + Default(prog1) + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog3) + + + prog1.c + + + prog2.c + + + prog3.c + + + + + + Or you can specify more than one target + in a single call to the &Default; function: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog1, prog3) + + + + + Either of these last two examples + will build only the + prog1 + and + prog3 + programs by default: + + + + + scons -Q + scons -Q . + + + + + You can list a directory as + an argument to &Default;: + + + + + + env = Environment() + env.Program(['prog1/main.c', 'prog1/foo.c']) + env.Program(['prog2/main.c', 'prog2/bar.c']) + Default('prog1') + + + + + int main() { printf("prog1/main.c\n"); } + + + int foo() { printf("prog1/foo.c\n"); } + + + int main() { printf("prog2/main.c\n"); } + + + int bar() { printf("prog2/bar.c\n"); } + + + + + + In which case only the target(s) in that + directory will be built by default: + + + + + scons -Q + scons -Q + scons -Q . + + + + + Lastly, if for some reason you don't want + any targets built by default, + you can use the Python None + variable: + + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + Default(None) + + + prog1.c + + + prog2.c + + + + + + Which would produce build output like: + + + + + scons -Q + scons -Q . + + +
+ Fetching the List of Default Targets: the &DEFAULT_TARGETS; Variable + + + + &SCons; supports a &DEFAULT_TARGETS; variable + that lets you get at the current list of default targets. + The &DEFAULT_TARGETS variable has + two important differences from the &COMMAND_LINE_TARGETS; variable. + First, the &DEFAULT_TARGETS; variable is a list of + internal &SCons; nodes, + so you need to convert the list elements to strings + if you want to print them or look for a specific target name. + Fortunately, you can do this easily + by using the Python map function + to run the list through str: + + + + + + prog1 = Program('prog1.c') + Default(prog1) + print "DEFAULT_TARGETS is", map(str, DEFAULT_TARGETS) + + + prog1.c + + + + + + (Keep in mind that all of the manipulation of the + &DEFAULT_TARGETS; list takes place during the + first phase when &SCons; is reading up the &SConscript; files, + which is obvious if + we leave off the -Q flag when we run &SCons;:) + + + + + scons + + + + + Second, + the contents of the &DEFAULT_TARGETS; list change + in response to calls to the &Default: function, + as you can see from the following &SConstruct; file: + + + + + + prog1 = Program('prog1.c') + Default(prog1) + print "DEFAULT_TARGETS is now", map(str, DEFAULT_TARGETS) + prog2 = Program('prog2.c') + Default(prog2) + print "DEFAULT_TARGETS is now", map(str, DEFAULT_TARGETS) + + + prog1.c + + + prog2.c + + + + + + Which yields the output: + + + + + scons + + + + + In practice, this simply means that you + need to pay attention to the order in + which you call the &Default; function + and refer to the &DEFAULT_TARGETS; list, + to make sure that you don't examine the + list before you've added the default targets + you expect to find in it. + + + +
+ +
+ +
+ Fetching the List of Build Targets, Regardless of Origin: the &BUILD_TARGETS; Variable + + + + We've already been introduced to the + &COMMAND_LINE_TARGETS; variable, + which contains a list of targets specified on the command line, + and the &DEFAULT_TARGETS; variable, + which contains a list of targets specified + via calls to the &Default; method or function. + Sometimes, however, + you want a list of whatever targets + &SCons; will try to build, + regardless of whether the targets came from the + command line or a &Default; call. + You could code this up by hand, as follows: + + + + + if COMMAND_LINE_TARGETS: + targets = COMMAND_LINE_TARGETS + else: + targets = DEFAULT_TARGETS + + + + + &SCons;, however, provides a convenient + &BUILD_TARGETS; variable + that eliminates the need for this by-hand manipulation. + Essentially, the &BUILD_TARGETS; variable + contains a list of the command-line targets, + if any were specified, + and if no command-line targets were specified, + it contains a list of the targets specified + via the &Default; method or function. + + + + + + Because &BUILD_TARGETS; may contain a list of &SCons; nodes, + you must convert the list elements to strings + if you want to print them or look for a specific target name, + just like the &DEFAULT_TARGETS; list: + + + + + + prog1 = Program('prog1.c') + Program('prog2.c') + Default(prog1) + print "BUILD_TARGETS is", map(str, BUILD_TARGETS) + + + prog1.c + + + prog2.c + + + + + + Notice how the value of &BUILD_TARGETS; + changes depending on whether a target is + specified on the command line: + + + + + scons -Q + scons -Q prog2 + scons -Q -c . + + +
+ +
diff --git a/doc/user/command-line.xml b/doc/user/command-line.xml new file mode 100644 index 0000000..630a9b2 --- /dev/null +++ b/doc/user/command-line.xml @@ -0,0 +1,2243 @@ + + + + + &SCons; provides a number of ways + for the writer of the &SConscript; files + to give the users who will run &SCons; + a great deal of control over the build execution. + The arguments that the user can specify on + the command line are broken down into three types: + + + + + + + Options + + + + + Command-line options always begin with + one or two - (hyphen) characters. + &SCons; provides ways for you to examine + and set options values from within your &SConscript; files, + as well as the ability to define your own + custom options. + See , below. + + + + + + + Variables + + + + + Any command-line argument containing an = + (equal sign) is considered a variable setting with the form + variable=value + &SCons; provides direct access to + all of the command-line variable settings, + the ability to apply command-line variable settings + to construction environments, + and functions for configuring + specific types of variables + (Boolean values, path names, etc.) + with automatic validation of the user's specified values. + See , below. + + + + + + + Targets + + + + + Any command-line argument that is not an option + or a variable setting + (does not begin with a hyphen + and does not contain an equal sign) + is considered a target that the user + (presumably) wants &SCons; to build. + A list of Node objects representing + the target or targets to build. + &SCons; provides access to the list of specified targets, + as well as ways to set the default list of targets + from within the &SConscript; files. + See , below. + + + + + + + +
+ Command-Line Options + + + + &SCons; has many command-line options + that control its behavior. + A &SCons; command-line option + always begins with one or two - (hyphen) + characters. + + + +
+ Not Having to Specify Command-Line Options Each Time: the &SCONSFLAGS; Environment Variable + + + + Users may find themselves supplying + the same command-line options every time + they run &SCons;. + For example, you might find it saves time + to specify a value of -j 2 + to have &SCons; run up to two build commands in parallel. + To avoid having to type -j 2 by hand + every time, + you can set the external environment variable + &SCONSFLAGS; to a string containing + command-line options that you want &SCons; to use. + + + + + + If, for example, + you're using a POSIX shell that's + compatible with the Bourne shell, + and you always want &SCons; to use the + -Q option, + you can set the &SCONSFLAGS; + environment as follows: + + + + + + + % scons + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Building targets ... + ... [build output] ... + scons: done building targets. + % export SCONSFLAGS="-Q" + % scons + ... [build output] ... + + + + + Users of &csh;-style shells on POSIX systems + can set the &SCONSFLAGS; environment as follows: + + + + + $ setenv SCONSFLAGS "-Q" + + + + + Windows users may typically want to set the + &SCONSFLAGS; in the appropriate tab of the + System Properties window. + + + +
+ +
+ Getting Values Set by Command-Line Options: the &GetOption; Function + + + + &SCons; provides the &GetOption; function + to get the values set by the various command-line options. + One common use of this is to check whether or not + the -h or --help option + has been specified. + Normally, &SCons; does not print its help text + until after it has read all of the &SConscript; files, + because it's possible that help text has been added + by some subsidiary &SConscript; file deep in the + source tree hierarchy. + Of course, reading all of the &SConscript; files + takes extra time. + + + + + + If you know that your configuration does not define + any additional help text in subsidiary &SConscript; files, + you can speed up the command-line help available to users + by using the &GetOption; function to load the + subsidiary &SConscript; files only if the + the user has not specified + the -h or --help option, + like so: + + + + + + + + In general, the string that you pass to the + &GetOption; function to fetch the value of a command-line + option setting is the same as the "most common" long option name + (beginning with two hyphen characters), + although there are some exceptions. + The list of &SCons; command-line options + and the &GetOption; strings for fetching them, + are available in the + section, + below. + + + +
+ +
+ Setting Values of Command-Line Options: the &SetOption; Function + + + + You can also set the values of &SCons; + command-line options from within the &SConscript; files + by using the &SetOption; function. + The strings that you use to set the values of &SCons; + command-line options are available in the + section, + below. + + + + + + One use of the &SetOption; function is to + specify a value for the -j + or --jobs option, + so that users get the improved performance + of a parallel build without having to specify the option by hand. + A complicating factor is that a good value + for the -j option is + somewhat system-dependent. + One rough guideline is that the more processors + your system has, + the higher you want to set the + -j value, + in order to take advantage of the number of CPUs. + + + + + + For example, suppose the administrators + of your development systems + have standardized on setting a + NUM_CPU environment variable + to the number of processors on each system. + A little bit of Python code + to access the environment variable + and the &SetOption; function + provide the right level of flexibility: + + + + + import os + num_cpu = int(os.environ.get('NUM_CPU', 2)) + SetOption('num_jobs', num_cpu) + print "running with -j", GetOption('num_jobs') + + + + + The above snippet of code + sets the value of the --jobs option + to the value specified in the + $NUM_CPU environment variable. + (This is one of the exception cases + where the string is spelled differently from + the from command-line option. + The string for fetching or setting the --jobs + value is num_jobs + for historical reasons.) + The code in this example prints the num_jobs + value for illustrative purposes. + It uses a default value of 2 + to provide some minimal parallelism even on + single-processor systems: + + + + + % scons -Q + running with -j 2 + scons: `.' is up to date. + + + + + But if the $NUM_CPU + environment variable is set, + then we use that for the default number of jobs: + + + + + % export NUM_CPU="4" + % scons -Q + running with -j 4 + scons: `.' is up to date. + + + + + But any explicit + -j or --jobs + value the user specifies an the command line is used first, + regardless of whether or not + the $NUM_CPU environment + variable is set: + + + + + % scons -Q -j 7 + running with -j 7 + scons: `.' is up to date. + % export NUM_CPU="4" + % scons -Q -j 3 + running with -j 3 + scons: `.' is up to date. + + +
+ +
+ Strings for Getting or Setting Values of &SCons; Command-Line Options + + + + The strings that you can pass to the &GetOption; + and &SetOption; functions usually correspond to the + first long-form option name + (beginning with two hyphen characters: --), + after replacing any remaining hyphen characters + with underscores. + + + + + + The full list of strings and the variables they + correspond to is as follows: + + + + + + + + + + String for &GetOption; and &SetOption; + Command-Line Option(s) + + + + + + + + cache_debug + + + + + cache_disable + + + + + cache_force + + + + + cache_show + + + + + clean + , + , + + + + + config + + + + + directory + , + + + + + diskcheck + + + + + duplicate + + + + + file + , + , + , + + + + + help + , + + + + + ignore_errors + + + + + implicit_cache + + + + + implicit_deps_changed + + + + + implicit_deps_unchanged + + + + + interactive + , + + + + + keep_going + , + + + + + max_drift + + + + + no_exec + , + , + , + , + + + + + no_site_dir + + + + + num_jobs + , + + + + + profile_file + + + + + question + , + + + + + random + + + + + repository + , + , + + + + + silent + , + , + + + + + site_dir + + + + + stack_size + + + + + taskmastertrace_file + + + + + warn + + + + + + + + +
+ +
+ Adding Custom Command-Line Options: the &AddOption; Function + + + + &SCons; also allows you to define your own + command-line options with the &AddOption; function. + The &AddOption; function takes the same arguments + as the optparse.add_option function + from the standard Python library. + + + The &AddOption; function is, + in fact, implemented using a subclass + of the optparse.OptionParser. + + + Once you have added a custom command-line option + with the &AddOption; function, + the value of the option (if any) is immediately available + using the standard &GetOption; function. + (The value can also be set using &SetOption;, + although that's not very useful in practice + because a default value can be specified in + directly in the &AddOption; call.) + + + + + + One useful example of using this functionality + is to provide a for users: + + + + + AddOption('--prefix', + dest='prefix', + type='string', + nargs=1, + action='store', + metavar='DIR', + help='installation prefix') + + env = Environment(PREFIX = GetOption('prefix')) + + installed_foo = env.Install('$PREFIX/usr/bin', 'foo.in') + Default(installed_foo) + + + + + The above code uses the &GetOption; function + to set the $PREFIX + construction variable to any + value that the user specifies with a command-line + option of --prefix. + Because $PREFIX + will expand to a null string if it's not initialized, + running &SCons; without the + option of --prefix + will install the file in the + /usr/bin/ directory: + + + + + % scons -Q -n + Install file: "foo.in" as "/usr/bin/foo.in" + + + + + But specifying --prefix=/tmp/install + on the command line causes the file to be installed in the + /tmp/install/usr/bin/ directory: + + + + + % scons -Q -n --prefix=/tmp/install + Install file: "foo.in" as "/tmp/install/usr/bin/foo.in" + + +
+ +
+ +
+ Command-Line <varname>variable</varname>=<varname>value</varname> Build Variables + + + + You may want to control various aspects + of your build by allowing the user + to specify variable=value + values on the command line. + For example, suppose you + want users to be able to + build a debug version of a program + by running &SCons; as follows: + + + + + % scons -Q debug=1 + + + + + &SCons; provides an &ARGUMENTS; dictionary + that stores all of the + variable=value + assignments from the command line. + This allows you to modify + aspects of your build in response + to specifications on the command line. + (Note that unless you want to require + that users always + specify a variable, + you probably want to use + the Python + ARGUMENTS.get() function, + which allows you to specify a default value + to be used if there is no specification + on the command line.) + + + + + + The following code sets the &cv-link-CCFLAGS; construction + variable in response to the debug + flag being set in the &ARGUMENTS; dictionary: + + + + + env = Environment() + debug = ARGUMENTS.get('debug', 0) + if int(debug): + env.Append(CCFLAGS = '-g') + env.Program('prog.c') + + + + + This results in the -g + compiler option being used when + debug=1 + is used on the command line: + + + + + % scons -Q debug=0 + cc -o prog.o -c prog.c + cc -o prog prog.o + % scons -Q debug=0 + scons: `.' is up to date. + % scons -Q debug=1 + scons: `.' is up to date. + % scons -Q debug=1 + scons: `.' is up to date. + + + + + Notice that &SCons; keeps track of + the last values used to build the object files, + and as a result correctly rebuilds + the object and executable files + only when the value of the debug + argument has changed. + + + + + + The &ARGUMENTS; dictionary has two minor drawbacks. + First, because it is a dictionary, + it can only store one value for each specified keyword, + and thus only "remembers" the last setting + for each keyword on the command line. + This makes the &ARGUMENTS; dictionary + inappropriate if users should be able to + specify multiple values + on the command line for a given keyword. + Second, it does not preserve + the order in which the variable settings + were specified, + which is a problem if + you want the configuration to + behave differently in response + to the order in which the build + variable settings were specified on the command line. + + + + + + To accomodate these requirements, + &SCons; provides an &ARGLIST; variable + that gives you direct access to + variable=value + settings on the command line, + in the exact order they were specified, + and without removing any duplicate settings. + Each element in the &ARGLIST; variable + is itself a two-element list + containing the keyword and the value + of the setting, + and you must loop through, + or otherwise select from, + the elements of &ARGLIST; to + process the specific settings you want + in whatever way is appropriate for your configuration. + For example, + the following code to let the user + add to the &CPPDEFINES; construction variable + by specifying multiple + define= + settings on the command line: + + + + + cppdefines = [] + for key, value in ARGLIST: + if key == 'define': + cppdefines.append(value) + env = Environment(CPPDEFINES = cppdefines) + env.Object('prog.c') + + + + + Yields the following output: + + + + + % scons -Q define=FOO + cc -o prog.o -c -DFOO prog.c + % scons -Q define=FOO define=BAR + scons: `.' is up to date. + + + + + Note that the &ARGLIST; and &ARGUMENTS; + variables do not interfere with each other, + but merely provide slightly different views + into how the user specified + variable=value + settings on the command line. + You can use both variables in the same + &SCons; configuration. + In general, the &ARGUMENTS; dictionary + is more convenient to use, + (since you can just fetch variable + settings through a dictionary access), + and the &ARGLIST; list + is more flexible + (since you can examine the + specific order in which + the user's command-line variabe settings). + + + +
+ Controlling Command-Line Build Variables + + + + Being able to use a command-line build variable like + debug=1 is handy, + but it can be a chore to write specific Python code + to recognize each such variable, + check for errors and provide appropriate messages, + and apply the values to a construction variable. + To help with this, + &SCons; supports a class to + define such build variables easily, + and a mechanism to apply the + build variables to a construction environment. + This allows you to control how the build variables affect + construction environments. + + + + + + For example, suppose that you want users to set + a &RELEASE; construction variable on the + command line whenever the time comes to build + a program for release, + and that the value of this variable + should be added to the command line + with the appropriate -D option + (or other command line option) + to pass the value to the C compiler. + Here's how you might do that by setting + the appropriate value in a dictionary for the + &cv-link-CPPDEFINES; construction variable: + + + + + vars = Variables() + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + + + + + This &SConstruct; file first creates a &Variables; object + (the vars = Variables() call), + and then uses the object's &Add; + method to indicate that the &RELEASE; + variable can be set on the command line, + and that its default value will be 0 + (the third argument to the &Add; method). + The second argument is a line of help text; + we'll learn how to use it in the next section. + + + + + + We then pass the created &Variables; + object as a &variables; keyword argument + to the &Environment; call + used to create the construction environment. + This then allows a user to set the + &RELEASE; build variable on the command line + and have the variable show up in + the command line used to build each object from + a C source file: + + + + + % scons -Q RELEASE=1 + cc -o bar.o -c -DRELEASE_BUILD=1 bar.c + cc -o foo.o -c -DRELEASE_BUILD=1 foo.c + cc -o foo foo.o bar.o + + + + + NOTE: Before &SCons; release 0.98.1, these build variables + were known as "command-line build options." + The class was actually named the &Options; class, + and in the sections below, + the various functions were named + &BoolOption;, &EnumOption;, &ListOption;, + &PathOption;, &PackageOption; and &AddOptions;. + These older names still work, + and you may encounter them in older + &SConscript; fles, + but their use is discouraged + and will be officially deprecated some day. + + + +
+ +
+ Providing Help for Command-Line Build Variables + + + + To make command-line build variables most useful, + you ideally want to provide + some help text that will describe + the available variables + when the user runs scons -h. + You could write this text by hand, + but &SCons; provides an easier way. + &Variables; objects support a + &GenerateHelpText; method + that will, as its name suggests, + generate text that describes + the various variables that + have been added to it. + You then pass the output from this method to + the &Help; function: + + + + + vars = Variables('custom.py') + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars) + Help(vars.GenerateHelpText(env)) + + + + + &SCons; will now display some useful text + when the -h option is used: + + + + + % scons -Q -h + + RELEASE: Set to 1 to build for release + default: 0 + actual: 0 + + Use scons -H for help about command-line options. + + + + + Notice that the help output shows the default value, + and the current actual value of the build variable. + + + +
+ +
+ Reading Build Variables From a File + + + + Giving the user a way to specify the + value of a build variable on the command line + is useful, + but can still be tedious + if users must specify the variable + every time they run &SCons;. + We can let users provide customized build variable settings + in a local file by providing a + file name when we create the + &Variables; object: + + + + + vars = Variables('custom.py') + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program(['foo.c', 'bar.c']) + Help(vars.GenerateHelpText(env)) + + + + + This then allows the user to control the &RELEASE; + variable by setting it in the &custom_py; file: + + + + + RELEASE = 1 + + + + + Note that this file is actually executed + like a Python script. + Now when we run &SCons;: + + + + + % scons -Q + cc -o bar.o -c -DRELEASE_BUILD=1 bar.c + cc -o foo.o -c -DRELEASE_BUILD=1 foo.c + cc -o foo foo.o bar.o + + + + + And if we change the contents of &custom_py; to: + + + + + RELEASE = 0 + + + + + The object files are rebuilt appropriately + with the new variable: + + + + + % scons -Q + cc -o bar.o -c -DRELEASE_BUILD=0 bar.c + cc -o foo.o -c -DRELEASE_BUILD=0 foo.c + cc -o foo foo.o bar.o + + +
+ +
+ Pre-Defined Build Variable Functions + + + + &SCons; provides a number of functions + that provide ready-made behaviors + for various types of command-line build variables. + + + +
+ True/False Values: the &BoolVariable; Build Variable Function + + + + It's often handy to be able to specify a + variable that controls a simple Boolean variable + with a &true; or &false; value. + It would be even more handy to accomodate + users who have different preferences for how to represent + &true; or &false; values. + The &BoolVariable; function + makes it easy to accomodate these + common representations of + &true; or &false;. + + + + + + The &BoolVariable; function takes three arguments: + the name of the build variable, + the default value of the build variable, + and the help string for the variable. + It then returns appropriate information for + passing to the &Add; method of a &Variables; object, like so: + + + + + vars = Variables('custom.py') + vars.Add(BoolVariable('RELEASE', 'Set to build for release', 0)) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + env.Program('foo.c') + + + + + With this build variable, + the &RELEASE; variable can now be enabled by + setting it to the value yes + or t: + + + + + % scons -Q RELEASE=yes foo.o + cc -o foo.o -c -DRELEASE_BUILD=True foo.c + + + + % scons -Q RELEASE=t foo.o + cc -o foo.o -c -DRELEASE_BUILD=True foo.c + + + + + Other values that equate to &true; include + y, + 1, + on + and + all. + + + + + + Conversely, &RELEASE; may now be given a &false; + value by setting it to + no + or + f: + + + + + % scons -Q RELEASE=no foo.o + cc -o foo.o -c -DRELEASE_BUILD=False foo.c + + + + % scons -Q RELEASE=f foo.o + cc -o foo.o -c -DRELEASE_BUILD=False foo.c + + + + + Other values that equate to &false; include + n, + 0, + off + and + none. + + + + + + Lastly, if a user tries to specify + any other value, + &SCons; supplies an appropriate error message: + + + + + % scons -Q RELEASE=bad_value foo.o + + scons: *** Error converting option: RELEASE + Invalid value for boolean option: bad_value + File "/home/my/project/SConstruct", line 4, in ? + + +
+ +
+ Single Value From a List: the &EnumVariable; Build Variable Function + + + + Suppose that we want a user to be able to + set a &COLOR; variable + that selects a background color to be + displayed by an application, + but that we want to restrict the + choices to a specific set of allowed colors. + This can be set up quite easily + using the &EnumVariable;, + which takes a list of &allowed_values + in addition to the variable name, + default value, + and help text arguments: + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'))) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + + + The user can now explicity set the &COLOR; build variable + to any of the specified allowed values: + + + + + % scons -Q COLOR=red foo.o + cc -o foo.o -c -DCOLOR="red" foo.c + % scons -Q COLOR=blue foo.o + scons: `foo.o' is up to date. + % scons -Q COLOR=green foo.o + scons: `foo.o' is up to date. + + + + + But, almost more importantly, + an attempt to set &COLOR; + to a value that's not in the list + generates an error message: + + + + + % scons -Q COLOR=magenta foo.o + + scons: *** Invalid value for option COLOR: magenta + File "/home/my/project/SConstruct", line 5, in ? + + + + + The &EnumVariable; function also supports a way + to map alternate names to allowed values. + Suppose, for example, + that we want to allow the user + to use the word navy as a synonym for + blue. + We do this by adding a ↦ dictionary + that will map its key values + to the desired legal value: + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'})) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + + + As desired, the user can then use + navy on the command line, + and &SCons; will translate it into blue + when it comes time to use the &COLOR; + variable to build a target: + + + + + % scons -Q COLOR=navy foo.o + cc -o foo.o -c -DCOLOR="blue" foo.c + + + + + By default, when using the &EnumVariable; function, + arguments that differ + from the legal values + only in case + are treated as illegal values: + + + + + % scons -Q COLOR=Red foo.o + + scons: *** Invalid value for option COLOR: Red + File "/home/my/project/SConstruct", line 5, in ? + % scons -Q COLOR=BLUE foo.o + + scons: *** Invalid value for option COLOR: BLUE + File "/home/my/project/SConstruct", line 5, in ? + % scons -Q COLOR=nAvY foo.o + + scons: *** Invalid value for option COLOR: nAvY + File "/home/my/project/SConstruct", line 5, in ? + + + + + The &EnumVariable; function can take an additional + &ignorecase; keyword argument that, + when set to 1, + tells &SCons; to allow case differences + when the values are specified: + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=1)) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + + + Which yields the output: + + + + + % scons -Q COLOR=Red foo.o + cc -o foo.o -c -DCOLOR="Red" foo.c + % scons -Q COLOR=BLUE foo.o + scons: `foo.o' is up to date. + % scons -Q COLOR=nAvY foo.o + scons: `foo.o' is up to date. + % scons -Q COLOR=green foo.o + scons: `foo.o' is up to date. + + + + + Notice that an &ignorecase; value of 1 + preserves the case-spelling that the user supplied. + If you want &SCons; to translate the names + into lower-case, + regardless of the case used by the user, + specify an &ignorecase; value of 2: + + + + + vars = Variables('custom.py') + vars.Add(EnumVariable('COLOR', 'Set background color', 'red', + allowed_values=('red', 'green', 'blue'), + map={'navy':'blue'}, + ignorecase=2)) + env = Environment(variables = vars, + CPPDEFINES={'COLOR' : '"${COLOR}"'}) + env.Program('foo.c') + + + + + Now &SCons; will use values of + red, + green or + blue + regardless of how the user spells + those values on the command line: + + + + + % scons -Q COLOR=Red foo.o + cc -o foo.o -c -DCOLOR="red" foo.c + % scons -Q COLOR=nAvY foo.o + scons: `foo.o' is up to date. + % scons -Q COLOR=GREEN foo.o + scons: `foo.o' is up to date. + + +
+ +
+ Multiple Values From a List: the &ListVariable; Build Variable Function + + + + Another way in which you might want to allow users + to control a build variable is to + specify a list of one or more legal values. + &SCons; supports this through the &ListVariable; function. + If, for example, we want a user to be able to set a + &COLORS; variable to one or more of the legal list of values: + + + + + vars = Variables('custom.py') + vars.Add(ListVariable('COLORS', 'List of colors', 0, + ['red', 'green', 'blue'])) + env = Environment(variables = vars, + CPPDEFINES={'COLORS' : '"${COLORS}"'}) + env.Program('foo.c') + + + + + A user can now specify a comma-separated list + of legal values, + which will get translated into a space-separated + list for passing to the any build commands: + + + + + % scons -Q COLORS=red,blue foo.o + cc -o foo.o -c -DCOLORS="red blue" foo.c + % scons -Q COLORS=blue,green,red foo.o + scons: `foo.o' is up to date. + + + + + In addition, the &ListVariable; function + allows the user to specify explicit keywords of + &all; or &none; + to select all of the legal values, + or none of them, respectively: + + + + + % scons -Q COLORS=all foo.o + cc -o foo.o -c -DCOLORS="red green blue" foo.c + % scons -Q COLORS=none foo.o + scons: `foo.o' is up to date. + + + + + And, of course, an illegal value + still generates an error message: + + + + + % scons -Q COLORS=magenta foo.o + + scons: *** Error converting option: COLORS + Invalid value(s) for option: magenta + File "/home/my/project/SConstruct", line 5, in ? + + +
+ +
+ Path Names: the &PathVariable; Build Variable Function + + + + &SCons; supports a &PathVariable; function + to make it easy to create a build variable + to control an expected path name. + If, for example, you need to + define a variable in the preprocessor + that controls the location of a + configuration file: + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('CONFIG', + 'Path to configuration file', + '/etc/my_config')) + env = Environment(variables = vars, + CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) + env.Program('foo.c') + + + + + This then allows the user to + override the &CONFIG; build variable + on the command line as necessary: + + + + + % scons -Q foo.o + cc -o foo.o -c -DCONFIG_FILE="/etc/my_config" foo.c + % scons -Q CONFIG=/usr/local/etc/other_config foo.o + scons: `foo.o' is up to date. + + + + + By default, &PathVariable; checks to make sure + that the specified path exists and generates an error if it + doesn't: + + + + + % scons -Q CONFIG=/does/not/exist foo.o + + scons: *** Path for option CONFIG does not exist: /does/not/exist + File "/home/my/project/SConstruct", line 6, in ? + + + + + &PathVariable; provides a number of methods + that you can use to change this behavior. + If you want to ensure that any specified paths are, + in fact, files and not directories, + use the &PathVariable_PathIsFile; method: + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('CONFIG', + 'Path to configuration file', + '/etc/my_config', + PathVariable.PathIsFile)) + env = Environment(variables = vars, + CPPDEFINES={'CONFIG_FILE' : '"$CONFIG"'}) + env.Program('foo.c') + + + + + Conversely, to ensure that any specified paths are + directories and not files, + use the &PathVariable_PathIsDir; method: + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('DBDIR', + 'Path to database directory', + '/var/my_dbdir', + PathVariable.PathIsDir)) + env = Environment(variables = vars, + CPPDEFINES={'DBDIR' : '"$DBDIR"'}) + env.Program('foo.c') + + + + + If you want to make sure that any specified paths + are directories, + and you would like the directory created + if it doesn't already exist, + use the &PathVariable_PathIsDirCreate; method: + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('DBDIR', + 'Path to database directory', + '/var/my_dbdir', + PathVariable.PathIsDirCreate)) + env = Environment(variables = vars, + CPPDEFINES={'DBDIR' : '"$DBDIR"'}) + env.Program('foo.c') + + + + + Lastly, if you don't care whether the path exists, + is a file, or a directory, + use the &PathVariable_PathAccept; method + to accept any path that the user supplies: + + + + + vars = Variables('custom.py') + vars.Add(PathVariable('OUTPUT', + 'Path to output file or directory', + None, + PathVariable.PathAccept)) + env = Environment(variables = vars, + CPPDEFINES={'OUTPUT' : '"$OUTPUT"'}) + env.Program('foo.c') + + +
+ +
+ Enabled/Disabled Path Names: the &PackageVariable; Build Variable Function + + + + Sometimes you want to give users + even more control over a path name variable, + allowing them to explicitly enable or + disable the path name + by using yes or no keywords, + in addition to allow them + to supply an explicit path name. + &SCons; supports the &PackageVariable; + function to support this: + + + + + vars = Variables('custom.py') + vars.Add(PackageVariable('PACKAGE', + 'Location package', + '/opt/location')) + env = Environment(variables = vars, + CPPDEFINES={'PACKAGE' : '"$PACKAGE"'}) + env.Program('foo.c') + + + + + When the &SConscript; file uses the &PackageVariable; funciton, + user can now still use the default + or supply an overriding path name, + but can now explicitly set the + specified variable to a value + that indicates the package should be enabled + (in which case the default should be used) + or disabled: + + + + + % scons -Q foo.o + cc -o foo.o -c -DPACKAGE="/opt/location" foo.c + % scons -Q PACKAGE=/usr/local/location foo.o + scons: `foo.o' is up to date. + % scons -Q PACKAGE=yes foo.o + scons: `foo.o' is up to date. + % scons -Q PACKAGE=no foo.o + scons: `foo.o' is up to date. + + +
+ +
+ +
+ Adding Multiple Command-Line Build Variables at Once + + + + Lastly, &SCons; provides a way to add + multiple build variables to a &Variables; object at once. + Instead of having to call the &Add; method + multiple times, + you can call the &AddVariables; + method with a list of build variables + to be added to the object. + Each build variable is specified + as either a tuple of arguments, + just like you'd pass to the &Add; method itself, + or as a call to one of the pre-defined + functions for pre-packaged command-line build variables. + in any order: + + + + + vars = Variables() + vars.AddVariables( + ('RELEASE', 'Set to 1 to build for release', 0), + ('CONFIG', 'Configuration file', '/etc/my_config'), + BoolVariable('warnings', 'compilation with -Wall and similiar', 1), + EnumVariable('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), # case sensitive + ListVariable('shared', + 'libraries to build as shared libraries', + 'all', + names = list_of_libs), + PackageVariable('x11', + 'use X11 installed here (yes = search some places)', + 'yes'), + PathVariable('qtdir', 'where the root of Qt is installed', qtdir), + ) + + + + + +
+ +
+ Handling Unknown Command-Line Build Variables: the &UnknownVariables; Function + + + + Users may, of course, + occasionally misspell variable names in their command-line settings. + &SCons; does not generate an error or warning + for any unknown variables the users specifies on the command line. + (This is in no small part because you may be + processing the arguments directly using the &ARGUMENTS; dictionary, + and therefore &SCons; can't know in the general case + whether a given "misspelled" variable is + really unknown and a potential problem, + or something that your &SConscript; file + will handle directly with some Python code.) + + + + + + If, however, you're using a &Variables; object to + define a specific set of command-line build variables + that you expect users to be able to set, + you may want to provide an error + message or warning of your own + if the user supplies a variable setting + that is not among + the defined list of variable names known to the &Variables; object. + You can do this by calling the &UnknownVariables; + method of the &Variables; object: + + + + + vars = Variables(None) + vars.Add('RELEASE', 'Set to 1 to build for release', 0) + env = Environment(variables = vars, + CPPDEFINES={'RELEASE_BUILD' : '${RELEASE}'}) + unknown = vars.UnknownVariables() + if unknown: + print "Unknown variables:", unknown.keys() + Exit(1) + env.Program('foo.c') + + + + + The &UnknownVariables; method returns a dictionary + containing the keywords and values + of any variables the user specified on the command line + that are not + among the variables known to the &Variables; object + (from having been specified using + the &Variables; object's&Add; method). + In the examble above, + we check for whether the dictionary + returned by the &UnknownVariables; is non-empty, + and if so print the Python list + containing the names of the unknwown variables + and then call the &Exit; function + to terminate &SCons;: + + + + + % scons -Q NOT_KNOWN=foo + Unknown variables: ['NOT_KNOWN'] + + + + + Of course, you can process the items in the + dictionary returned by the &UnknownVariables; function + in any way appropriate to your build configuration, + including just printing a warning message + but not exiting, + logging an error somewhere, + etc. + + + + + + Note that you must delay the call of &UnknownVariables; + until after you have applied the &Variables; object + to a construction environment + with the variables= + keyword argument of an &Environment; call. + + + +
+ +
+ +
+ Command-Line Targets + +
+ Fetching Command-Line Targets: the &COMMAND_LINE_TARGETS; Variable + + + + &SCons; supports a &COMMAND_LINE_TARGETS; variable + that lets you fetch the list of targets that the + user specified on the command line. + You can use the targets to manipulate the + build in any way you wish. + As a simple example, + suppose that you want to print a reminder + to the user whenever a specific program is built. + You can do this by checking for the + target in the &COMMAND_LINE_TARGETS; list: + + + + + if 'bar' in COMMAND_LINE_TARGETS: + print "Don't forget to copy `bar' to the archive!" + Default(Program('foo.c')) + Program('bar.c') + + + + + Then, running &SCons; with the default target + works as it always does, + but explicity specifying the &bar; target + on the command line generates the warning message: + + + + + % scons -Q + cc -o foo.o -c foo.c + cc -o foo foo.o + % scons -Q bar + Don't forget to copy `bar' to the archive! + cc -o bar.o -c bar.c + cc -o bar bar.o + + + + + Another practical use for the &COMMAND_LINE_TARGETS; variable + might be to speed up a build + by only reading certain subsidiary &SConscript; + files if a specific target is requested. + + + +
+ +
+ Controlling the Default Targets: the &Default; Function + + + + One of the most basic things you can control + is which targets &SCons; will build by default--that is, + when there are no targets specified on the command line. + As mentioned previously, + &SCons; will normally build every target + in or below the current directory + by default--that is, when you don't + explicitly specify one or more targets + on the command line. + Sometimes, however, you may want + to specify explicitly that only + certain programs, or programs in certain directories, + should be built by default. + You do this with the &Default; function: + + + + + env = Environment() + hello = env.Program('hello.c') + env.Program('goodbye.c') + Default(hello) + + + + + This &SConstruct; file knows how to build two programs, + &hello; and &goodbye;, + but only builds the + &hello; program by default: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q + scons: `hello' is up to date. + % scons -Q goodbye + cc -o goodbye.o -c goodbye.c + cc -o goodbye goodbye.o + + + + + Note that, even when you use the &Default; + function in your &SConstruct; file, + you can still explicitly specify the current directory + (.) on the command line + to tell &SCons; to build + everything in (or below) the current directory: + + + + + % scons -Q . + cc -o goodbye.o -c goodbye.c + cc -o goodbye goodbye.o + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + You can also call the &Default; + function more than once, + in which case each call + adds to the list of targets to be built by default: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + Default(prog1) + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog3) + + + + + Or you can specify more than one target + in a single call to the &Default; function: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + prog3 = env.Program('prog3.c') + Default(prog1, prog3) + + + + + Either of these last two examples + will build only the + prog1 + and + prog3 + programs by default: + + + + + % scons -Q + cc -o prog1.o -c prog1.c + cc -o prog1 prog1.o + cc -o prog3.o -c prog3.c + cc -o prog3 prog3.o + % scons -Q . + cc -o prog2.o -c prog2.c + cc -o prog2 prog2.o + + + + + You can list a directory as + an argument to &Default;: + + + + + env = Environment() + env.Program(['prog1/main.c', 'prog1/foo.c']) + env.Program(['prog2/main.c', 'prog2/bar.c']) + Default('prog1') + + + + + In which case only the target(s) in that + directory will be built by default: + + + + + % scons -Q + cc -o prog1/foo.o -c prog1/foo.c + cc -o prog1/main.o -c prog1/main.c + cc -o prog1/main prog1/main.o prog1/foo.o + % scons -Q + scons: `prog1' is up to date. + % scons -Q . + cc -o prog2/bar.o -c prog2/bar.c + cc -o prog2/main.o -c prog2/main.c + cc -o prog2/main prog2/main.o prog2/bar.o + + + + + Lastly, if for some reason you don't want + any targets built by default, + you can use the Python None + variable: + + + + + env = Environment() + prog1 = env.Program('prog1.c') + prog2 = env.Program('prog2.c') + Default(None) + + + + + Which would produce build output like: + + + + + % scons -Q + scons: *** No targets specified and no Default() targets found. Stop. + % scons -Q . + cc -o prog1.o -c prog1.c + cc -o prog1 prog1.o + cc -o prog2.o -c prog2.c + cc -o prog2 prog2.o + + +
+ Fetching the List of Default Targets: the &DEFAULT_TARGETS; Variable + + + + &SCons; supports a &DEFAULT_TARGETS; variable + that lets you get at the current list of default targets. + The &DEFAULT_TARGETS variable has + two important differences from the &COMMAND_LINE_TARGETS; variable. + First, the &DEFAULT_TARGETS; variable is a list of + internal &SCons; nodes, + so you need to convert the list elements to strings + if you want to print them or look for a specific target name. + Fortunately, you can do this easily + by using the Python map function + to run the list through str: + + + + + prog1 = Program('prog1.c') + Default(prog1) + print "DEFAULT_TARGETS is", map(str, DEFAULT_TARGETS) + + + + + (Keep in mind that all of the manipulation of the + &DEFAULT_TARGETS; list takes place during the + first phase when &SCons; is reading up the &SConscript; files, + which is obvious if + we leave off the -Q flag when we run &SCons;:) + + + + + % scons + scons: Reading SConscript files ... + DEFAULT_TARGETS is ['prog1'] + scons: done reading SConscript files. + scons: Building targets ... + cc -o prog1.o -c prog1.c + cc -o prog1 prog1.o + scons: done building targets. + + + + + Second, + the contents of the &DEFAULT_TARGETS; list change + in response to calls to the &Default;: function, + as you can see from the following &SConstruct; file: + + + + + prog1 = Program('prog1.c') + Default(prog1) + print "DEFAULT_TARGETS is now", map(str, DEFAULT_TARGETS) + prog2 = Program('prog2.c') + Default(prog2) + print "DEFAULT_TARGETS is now", map(str, DEFAULT_TARGETS) + + + + + Which yields the output: + + + + + % scons + scons: Reading SConscript files ... + DEFAULT_TARGETS is now ['prog1'] + DEFAULT_TARGETS is now ['prog1', 'prog2'] + scons: done reading SConscript files. + scons: Building targets ... + cc -o prog1.o -c prog1.c + cc -o prog1 prog1.o + cc -o prog2.o -c prog2.c + cc -o prog2 prog2.o + scons: done building targets. + + + + + In practice, this simply means that you + need to pay attention to the order in + which you call the &Default; function + and refer to the &DEFAULT_TARGETS; list, + to make sure that you don't examine the + list before you've added the default targets + you expect to find in it. + + + +
+ +
+ +
+ Fetching the List of Build Targets, Regardless of Origin: the &BUILD_TARGETS; Variable + + + + We've already been introduced to the + &COMMAND_LINE_TARGETS; variable, + which contains a list of targets specified on the command line, + and the &DEFAULT_TARGETS; variable, + which contains a list of targets specified + via calls to the &Default; method or function. + Sometimes, however, + you want a list of whatever targets + &SCons; will try to build, + regardless of whether the targets came from the + command line or a &Default; call. + You could code this up by hand, as follows: + + + + + if COMMAND_LINE_TARGETS: + targets = COMMAND_LINE_TARGETS + else: + targets = DEFAULT_TARGETS + + + + + &SCons;, however, provides a convenient + &BUILD_TARGETS; variable + that eliminates the need for this by-hand manipulation. + Essentially, the &BUILD_TARGETS; variable + contains a list of the command-line targets, + if any were specified, + and if no command-line targets were specified, + it contains a list of the targets specified + via the &Default; method or function. + + + + + + Because &BUILD_TARGETS; may contain a list of &SCons; nodes, + you must convert the list elements to strings + if you want to print them or look for a specific target name, + just like the &DEFAULT_TARGETS; list: + + + + + prog1 = Program('prog1.c') + Program('prog2.c') + Default(prog1) + print "BUILD_TARGETS is", map(str, BUILD_TARGETS) + + + + + Notice how the value of &BUILD_TARGETS; + changes depending on whether a target is + specified on the command line: + + + + + % scons -Q + BUILD_TARGETS is ['prog1'] + cc -o prog1.o -c prog1.c + cc -o prog1 prog1.o + % scons -Q prog2 + BUILD_TARGETS is ['prog2'] + cc -o prog2.o -c prog2.c + cc -o prog2 prog2.o + % scons -Q -c . + BUILD_TARGETS is ['.'] + Removed prog1.o + Removed prog1 + Removed prog2.o + Removed prog2 + + +
+ +
diff --git a/doc/user/cons.pl b/doc/user/cons.pl new file mode 100644 index 0000000..8afbfec --- /dev/null +++ b/doc/user/cons.pl @@ -0,0 +1,720 @@ +=head1 Introduction + +B is a system for constructing, primarily, software, but is quite +different from previous software construction systems. Cons was designed +from the ground up to deal easily with the construction of software spread +over multiple source directories. Cons makes it easy to create build scripts +that are simple, understandable and maintainable. Cons ensures that complex +software is easily and accurately reproducible. + +Cons uses a number of techniques to accomplish all of this. Construction +scripts are just Perl scripts, making them both easy to comprehend and very +flexible. Global scoping of variables is replaced with an import/export +mechanism for sharing information between scripts, significantly improving +the readability and maintainability of each script. B are introduced: these are Perl objects that capture the +information required for controlling the build process. Multiple +environments are used when different semantics are required for generating +products in the build tree. Cons implements automatic dependency analysis +and uses this to globally sequence the entire build. Variant builds are +easily produced from a single source tree. Intelligent build subsetting is +possible, when working on localized changes. Overrides can be setup to +easily override build instructions without modifying any scripts. MD5 +cryptographic B are associated with derived files, and are used +to accurately determine whether a given file needs to be rebuilt. + +While offering all of the above, and more, Cons remains simple and easy to +use. This will, hopefully, become clear as you read the remainder of this +document. + + + +=head2 Automatic global build sequencing + +Because Cons does full and accurate dependency analysis, and does this +globally, for the entire build, Cons is able to use this information to take +full control of the B of the build. This sequencing is evident +in the above examples, and is equivalent to what you would expect for make, +given a full set of dependencies. With Cons, this extends trivially to +larger, multi-directory builds. As a result, all of the complexity involved +in making sure that a build is organized correctly--including multi-pass +hierarchical builds--is eliminated. We'll discuss this further in the next +sections. + + + +=head1 A Model for sharing files + + +=head2 Some simple conventions + +In any complex software system, a method for sharing build products needs to +be established. We propose a simple set of conventions which are trivial to +implement with Cons, but very effective. + +The basic rule is to require that all build products which need to be shared +between directories are shared via an intermediate directory. We have +typically called this F, and, in a C environment, provided +conventional sub-directories of this directory, such as F, F, +F, etc. + +These directories are defined by the top-level F file. A simple +F file for a B application, organized using +multiple directories, might look like this: + + # Construct file for Hello, World! + + # Where to put all our shared products. + $EXPORT = '#export'; + + Export qw( CONS INCLUDE LIB BIN ); + + # Standard directories for sharing products. + $INCLUDE = "$EXPORT/include"; + $LIB = "$EXPORT/lib"; + $BIN = "$EXPORT/bin"; + + # A standard construction environment. + $CONS = new cons ( + CPPPATH => $INCLUDE, # Include path for C Compilations + LIBPATH => $LIB, # Library path for linking programs + LIBS => '-lworld', # List of standard libraries + ); + + Build qw( + hello/Conscript + world/Conscript + ); + +The F directory's F file looks like this: + + # Conscript file for directory world + Import qw( CONS INCLUDE LIB ); + + # Install the products of this directory + Install $CONS $LIB, 'libworld.a'; + Install $CONS $INCLUDE, 'world.h'; + + # Internal products + Library $CONS 'libworld.a', 'world.c'; + +and the F directory's F file looks like this: + + # Conscript file for directory hello + Import qw( CONS BIN ); + + # Exported products + Install $CONS $BIN, 'hello'; + + # Internal products + Program $CONS 'hello', 'hello.c'; + +To construct a B program with this directory structure, go to +the top-level directory, and invoke C with the appropriate +arguments. In the following example, we tell Cons to build the directory +F. To build a directory, Cons recursively builds all known products +within that directory (only if they need rebuilding, of course). If any of +those products depend upon other products in other directories, then those +will be built, too. + + % cons export + Install world/world.h as export/include/world.h + cc -Iexport/include -c hello/hello.c -o hello/hello.o + cc -Iexport/include -c world/world.c -o world/world.o + ar r world/libworld.a world/world.o + ar: creating world/libworld.a + ranlib world/libworld.a + Install world/libworld.a as export/lib/libworld.a + cc -o hello/hello hello/hello.o -Lexport/lib -lworld + Install hello/hello as export/bin/hello + + +=head2 Clean, understandable, location-independent scripts + +You'll note that the two F files are very clean and +to-the-point. They simply specify products of the directory and how to build +those products. The build instructions are minimal: they specify which +construction environment to use, the name of the product, and the name of +the inputs. Note also that the scripts are location-independent: if you wish +to reorganize your source tree, you are free to do so: you only have to +change the F file (in this example), to specify the new locations +of the F files. The use of an export tree makes this goal easy. + +Note, too, how Cons takes care of little details for you. All the F +directories, for example, were made automatically. And the installed files +were really hard-linked into the respective export directories, to save +space and time. This attention to detail saves considerable work, and makes +it even easier to produce simple, maintainable scripts. + + + +=head1 Signatures + +Cons uses file B to decide if a derived file is out-of-date +and needs rebuilding. In essence, if the contents of a file change, +or the manner in which the file is built changes, the file's signature +changes as well. This allows Cons to decide with certainty when a file +needs rebuilding, because Cons can detect, quickly and reliably, whether +any of its dependency files have been changed. + + +=head2 MD5 content and build signatures + +Cons uses the B (B) algorithm to compute file +signatures. The MD5 algorithm computes a strong cryptographic checksum +for any given input string. Cons can, based on configuration, use two +different MD5 signatures for a given file: + +The B of a file is an MD5 checksum of the file's +contents. Consequently, when the contents of a file change, its content +signature changes as well. + +The B of a file is a combined MD5 checksum of: + +=over 4 + +the signatures of all the input files used to build the file + +the signatures of all dependency files discovered by source scanners +(for example, C<.h> files) + +the signatures of all dependency files specified explicitly via the +C method) + +the command-line string used to build the file + +=back + +The build signature is, in effect, a digest of all the dependency +information for the specified file. Consequently, a file's build +signature changes whenever any part of its dependency information +changes: a new file is added, the contents of a file on which it depends +change, there's a change to the command line used to build the file (or +any of its dependency files), etc. + +For example, in the previous section, the build signature of the +F file will include: + +=over 4 + +the signature of the F file + +the signatures of any header files that Cons detects are included, +directly or indirectly, by F + +the text of the actual command line was used to generate F + +=back + +Similarly, the build signature of the F file will include +all the signatures of its constituents (and hence, transitively, the +signatures of B constituents), as well as the command line that +created the file. + +Note that there is no need for a derived file to depend upon any +particular F or F file. If changes to these files +affect a file, then this will be automatically reflected in its build +signature, since relevant parts of the command line are included in the +signature. Unrelated F or F changes will have no +effect. + + +=head2 Storing signatures in .consign files + +Before Cons exits, it stores the calculated signatures for all of the +files it built or examined in F<.consign> files, one per directory. +Cons uses this stored information on later invocations to decide if +derived files need to be rebuilt. + +After the previous example was compiled, the F<.consign> file in the +F directory looked like this: + + world.h:985533370 - d181712f2fdc07c1f05d97b16bfad904 + world.o:985533372 2a0f71e0766927c0532977b0d2158981 + world.c:985533370 - c712f77189307907f4189b5a7ab62ff3 + libworld.a:985533374 69e568fc5241d7d25be86d581e1fb6aa + +After the file name and colon, the first number is a timestamp of the +file's modification time (on UNIX systems, this is typically the number +of seconds since January 1st, 1970). The second value is the build +signature of the file (or ``-'' in the case of files with no build +signature--that is, source files). The third value, if any, is the +content signature of the file. + + +=head2 Using build signatures to decide when to rebuild files + +When Cons is deciding whether to build or rebuild a derived file, it +first computes the file's current build signature. If the file doesn't +exist, it must obviously be built. + +If, however, the file already exists, Cons next compares the +modification timestamp of the file against the timestamp value in +the F<.consign> file. If the timestamps match, Cons compares the +newly-computed build signature against the build signature in the +F<.consign> file. If the timestamps do not match or the build +signatures do not match, the derived file is rebuilt. + +After the file is built or rebuilt, Cons arranges to store the +newly-computed build signature in the F<.consign> file when it exits. + + +=head2 Signature example + +The use of these signatures is an extremely simple, efficient, and +effective method of improving--dramatically--the reproducibility of a +system. + +We'll demonstrate this with a simple example: + + # Simple "Hello, World!" Construct file + $CFLAGS = '-g' if $ARG{DEBUG} eq 'on'; + $CONS = new cons(CFLAGS => $CFLAGS); + Program $CONS 'hello', 'hello.c'; + +Notice how Cons recompiles at the appropriate times: + + % cons hello + cc -c hello.c -o hello.o + cc -o hello hello.o + % cons hello + cons: "hello" is up-to-date. + % cons DEBUG=on hello + cc -g -c hello.c -o hello.o + cc -o hello hello.o + % cons DEBUG=on hello + cons: "hello" is up-to-date. + % cons hello + cc -c hello.c -o hello.o + cc -o hello hello.o + + +=head2 Source-file signature configuration + +Cons provides a C method that allows you to configure +how the signature should be calculated for any source file when its +signature is being used to decide if a dependent file is up-to-date. +The arguments to the C method consist of one or more +pairs of strings: + + SourceSignature 'auto/*.c' => 'content', + '*' => 'stored-content'; + +The first string in each pair is a pattern to match against derived file +path names. The pattern is a file-globbing pattern, not a Perl regular +expression; the pattern <*.l> will match all Lex source files. The C<*> +wildcard will match across directory separators; the pattern C +would match all C source files in any subdirectory underneath the C +subdirectory. + +The second string in each pair contains one of the following keywords to +specify how signatures should be calculated for source files that match +the pattern. The available keywords are: + +=over 4 + +=item content + +Use the content signature of the source file when calculating signatures +of files that depend on it. This guarantees correct calculation of the +file's signature for all builds, by telling Cons to read the contents of +a source file to calculate its content signature each time it is run. + +=item stored-content + +Use the source file's content signature as stored in the F<.consign> +file, provided the file's timestamp matches the cached timestamp value +in the F<.consign> file. This optimizes performance, with the slight +risk of an incorrect build if a source file's contents have been changed +so quickly after its previous update that the timestamp still matches +the stored timestamp in the F<.consign> file even though the contents +have changed. + +=back + +The Cons default behavior of always calculating a source file's +signature from the file's contents is equivalent to specifying: + + SourceSignature '*' => 'content'; + +The C<*> will match all source files. The C keyword +specifies that Cons will read the contents of a source file to calculate +its signature each time it is run. + +A useful global performance optimization is: + + SourceSignature '*' => 'stored-content'; + +This specifies that Cons will use pre-computed content signatures +from F<.consign> files, when available, rather than re-calculating a +signature from the the source file's contents each time Cons is run. In +practice, this is safe for most build situations, and only a problem +when source files are changed automatically (by scripts, for example). +The Cons default, however, errs on the side of guaranteeing a correct +build in all situations. + +Cons tries to match source file path names against the patterns in the +order they are specified in the C arguments: + + SourceSignature '/usr/repository/objects/*' => 'stored-content', + '/usr/repository/*' => 'content', + '*.y' => 'content', + '*' => 'stored-content'; + +In this example, all source files under the F +directory will use F<.consign> file content signatures, source files +anywhere else underneath F will not use F<.consign> +signature values, all Yacc source files (C<*.y>) anywhere else will not +use F<.consign> signature values, and any other source file will use +F<.consign> signature values. + + +=head2 Derived-file signature configuration + +Cons provides a C construction variable that allows you to +configure how signatures are calculated for any derived file when its +signature is being used to decide if a dependent file is up-to-date. +The value of the C construction variable is a Perl array +reference that holds one or more pairs of strings, like the arguments to +the C method. + +The first string in each pair is a pattern to match against derived file +path names. The pattern is a file-globbing pattern, not a Perl regular +expression; the pattern `*.obj' will match all (Win32) object files. +The C<*> wildcard will match across directory separators; the pattern +`foo/*.a' would match all (UNIX) library archives in any subdirectory +underneath the foo subdirectory. + +The second string in each pair contains one of the following keywords +to specify how signatures should be calculated for derived files that +match the pattern. The available keywords are the same as for the +C method, with an additional keyword: + +=over 4 + +=item build + +Use the build signature of the derived file when calculating signatures +of files that depend on it. This guarantees correct builds by forcing +Cons to rebuild any and all files that depend on the derived file. + +=item content + +Use the content signature of the derived file when calculating signatures +of files that depend on it. This guarantees correct calculation of the +file's signature for all builds, by telling Cons to read the contents of +a derived file to calculate its content signature each time it is run. + +=item stored-content + +Use the derived file's content signature as stored in the F<.consign> +file, provided the file's timestamp matches the cached timestamp value +in the F<.consign> file. This optimizes performance, with the slight +risk of an incorrect build if a derived file's contents have been +changed so quickly after a Cons build that the file's timestamp still +matches the stored timestamp in the F<.consign> file. + +=back + +The Cons default behavior (as previously described) for using +derived-file signatures is equivalent to: + + $env = new cons(SIGNATURE => ['*' => 'build']); + +The C<*> will match all derived files. The C keyword specifies +that all derived files' build signatures will be used when calculating +whether a dependent file is up-to-date. + +A useful alternative default C configuration for many sites: + + $env = new cons(SIGNATURE => ['*' => 'content']); + +In this configuration, derived files have their signatures calculated +from the file contents. This adds slightly to Cons' workload, but has +the useful effect of "stopping" further rebuilds if a derived file is +rebuilt to exactly the same file contents as before, which usually +outweighs the additional computation Cons must perform. + +For example, changing a comment in a C file and recompiling should +generate the exact same object file (assuming the compiler doesn't +insert a timestamp in the object file's header). In that case, +specifying C or C for the signature calculation +will cause Cons to recognize that the object file did not actually +change as a result of being rebuilt, and libraries or programs that +include the object file will not be rebuilt. When C is +specified, however, Cons will only "know" that the object file was +rebuilt, and proceed to rebuild any additional files that include the +object file. + +Note that Cons tries to match derived file path names against the +patterns in the order they are specified in the C array +reference: + + $env = new cons(SIGNATURE => ['foo/*.o' => 'build', + '*.o' => 'content', + '*.a' => 'stored-content', + '*' => 'content']); + +In this example, all object files underneath the F subdirectory +will use build signatures, all other object files (including object +files underneath other subdirectories!) will use F<.consign> file +content signatures, libraries will use F<.consign> file build +signatures, and all other derived files will use content signatures. + + +=head2 Debugging signature calculation + +Cons provides a C<-S> option that can be used to specify what internal +Perl package Cons should use to calculate signatures. The default Cons +behavior is equivalent to specifying C<-S md5> on the command line. + +The only other package (currently) available is an C +package that prints out detailed information about the MD5 signature +calculations performed by Cons: + + % cons -S md5::debug hello + sig::md5::srcsig(hello.c) + => |52d891204c62fe93ecb95281e1571938| + sig::md5::collect(52d891204c62fe93ecb95281e1571938) + => |fb0660af4002c40461a2f01fbb5ffd03| + sig::md5::collect(52d891204c62fe93ecb95281e1571938, + fb0660af4002c40461a2f01fbb5ffd03, + cc -c %< -o %>) + => |f7128da6c3fe3c377dc22ade70647b39| + sig::md5::current(|| + eq |f7128da6c3fe3c377dc22ade70647b39|) + cc -c hello.c -o hello.o + sig::md5::collect() + => |d41d8cd98f00b204e9800998ecf8427e| + sig::md5::collect(f7128da6c3fe3c377dc22ade70647b39, + d41d8cd98f00b204e9800998ecf8427e, + cc -o %> %< ) + => |a0bdce7fd09e0350e7efbbdb043a00b0| + sig::md5::current(|| + eq |a0bdce7fd09e0350e7efbbdb043a00b0|) + cc -o hello, hello.o + + + + + + + +=head1 Temporary overrides + +Cons provides a very simple mechanism for overriding aspects of a build. The +essence is that you write an override file containing one or more +C commands, and you specify this on the command line, when you run +C: + + % cons -o over export + +will build the F directory, with all derived files subject to the +overrides present in the F file. If you leave out the C<-o> option, +then everything necessary to remove all overrides will be rebuilt. + + +=head2 Overriding environment variables + +The override file can contain two types of overrides. The first is incoming +environment variables. These are normally accessible by the F +file from the C<%ENV> hash variable. These can trivially be overridden in +the override file by setting the appropriate elements of C<%ENV> (these +could also be overridden in the user's environment, of course). + + +=head2 The Override command + +The second type of override is accomplished with the C command, +which looks like this: + + Override , => , => , ...; + +The regular expression I is matched against every derived file that +is a candidate for the build. If the derived file matches, then the +variable/value pairs are used to override the values in the construction +environment associated with the derived file. + +Let's suppose that we have a construction environment like this: + + $CONS = new cons( + COPT => '', + CDBG => '-g', + CFLAGS => '%COPT %CDBG', + ); + +Then if we have an override file F containing this command: + + Override '\.o$', COPT => '-O', CDBG => ''; + +then any C invocation with C<-o over> that creates F<.o> files via +this environment will cause them to be compiled with C<-O >and no C<-g>. The +override could, of course, be restricted to a single directory by the +appropriate selection of a regular expression. + +Here's the original version of the Hello, World! program, built with this +environment. Note that Cons rebuilds the appropriate pieces when the +override is applied or removed: + + % cons hello + cc -g -c hello.c -o hello.o + cc -o hello hello.o + % cons -o over hello + cc -O -c hello.c -o hello.o + cc -o hello hello.o + % cons -o over hello + cons: "hello" is up-to-date. + % cons hello + cc -g -c hello.c -o hello.o + cc -o hello hello.o + +It's important that the C command only be used for temporary, +on-the-fly overrides necessary for development because the overrides are not +platform independent and because they rely too much on intimate knowledge of +the workings of the scripts. For temporary use, however, they are exactly +what you want. + +Note that it is still useful to provide, say, the ability to create a fully +optimized version of a system for production use--from the F and +F files. This way you can tailor the optimized system to the +platform. Where optimizer trade-offs need to be made (particular files may +not be compiled with full optimization, for example), then these can be +recorded for posterity (and reproducibility) directly in the scripts. + + + +=head2 The C method + +The C method is a combination of the C and C +methods. Rather than generating an executable program directly, this command +allows you to specify your own command to actually generate a module. The +method is invoked as follows: + + Module $env , , ; + +This command is useful in instances where you wish to create, for example, +dynamically loaded modules, or statically linked code libraries. + + + + +=head2 The C method + +The C method returns the construction variables for building +various components with one of the rule sets supported by Cons. The +currently supported rule sets are: + +=over 4 + +=item msvc + +Rules for the Microsoft Visual C++ compiler suite. + +=item unix + +Generic rules for most UNIX-like compiler suites. + +=back + +On systems with more than one available compiler suite, this allows you +to easily create side-by-side environments for building software with +multiple tools: + + $msvcenv = new cons(RuleSet("msvc")); + $cygnusenv = new cons(RuleSet("unix")); + +In the future, this could also be extended to other platforms that +have different default rule sets. + + +=head2 The C method + +The C method sets the default construction variables that +will be returned by the C method to the specified arguments: + + DefaultRules(CC => 'gcc', + CFLAGS => '', + CCCOM => '%CC %CFLAGS %_IFLAGS -c %< -o %>'); + $env = new cons(); + # $env now contains *only* the CC, CFLAGS, + # and CCCOM construction variables + +Combined with the C method, this also provides an easy way +to set explicitly the default build environment to use some supported +toolset other than the Cons defaults: + + # use a UNIX-like tool suite (like cygwin) on Win32 + DefaultRules(RuleSet('unix')); + $env = new cons(); + +Note that the C method completely replaces the default +construction environment with the specified arguments, it does not +simply override the existing defaults. To override one or more +variables in a supported C, append the variables and values: + + DefaultRules(RuleSet('unix'), CFLAGS => '-O3'); + $env1 = new cons(); + $env2 = new cons(); + # both $env1 and $env2 have 'unix' defaults + # with CFLAGS set to '-O3' + + + + + + + + +=head2 The C method + +The C mathod returns the real source path name of a file, +as opposed to the path name within a build directory. It is invoked +as follows: + + $path = SourcePath ; + + +=head2 The C method + +The C method returns true if the supplied path is a derivable +file, and returns undef (false) otherwise. +It is invoked as follows: + + $result = ConsPath ; + + +=head2 The C method + +The C method looks up multiple path names in a string separated +by the default path separator for the operating system (':' on UNIX +systems, ';' on Windows NT), and returns the fully-qualified names. +It is invoked as follows: + + @paths = SplitPath ; + +The C method will convert names prefixed '#' to the +appropriate top-level build name (without the '#') and will convert +relative names to top-level names. + + +=head2 The C method + +The C method returns the build path name(s) of a directory or +list of directories. It is invoked as follows: + + $cwd = DirPath ; + +The most common use for the C method is: + + $cwd = DirPath '.'; + +to fetch the path to the current directory of a subsidiary F +file. + + +=head2 The C method + +The C method returns the build path name(s) of a file or +list of files. It is invoked as follows: + + $file = FilePath ; diff --git a/doc/user/copyright.in b/doc/user/copyright.in new file mode 100644 index 0000000..341698d --- /dev/null +++ b/doc/user/copyright.in @@ -0,0 +1,32 @@ + + +
+ + + SCons User's Guide Copyright (c) 2004, 2005, 2006, 2007 Steven Knight + + +
diff --git a/doc/user/copyright.xml b/doc/user/copyright.xml new file mode 100644 index 0000000..341698d --- /dev/null +++ b/doc/user/copyright.xml @@ -0,0 +1,32 @@ + + +
+ + + SCons User's Guide Copyright (c) 2004, 2005, 2006, 2007 Steven Knight + + +
diff --git a/doc/user/depends.in b/doc/user/depends.in new file mode 100644 index 0000000..dfd52e3 --- /dev/null +++ b/doc/user/depends.in @@ -0,0 +1,1816 @@ + + + + + So far we've seen how &SCons; handles one-time builds. + But one of the main functions of a build tool like &SCons; + is to rebuild only what is necessary + when source files change--or, put another way, + &SCons; should not + waste time rebuilding things that don't need to be rebuilt. + You can see this at work simply by re-invoking &SCons; + after building our simple &hello; example: + + + + + + Program('hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + scons -Q + scons -Q + + + + + The second time it is executed, + &SCons; realizes that the &hello; program + is up-to-date with respect to the current &hello_c; source file, + and avoids rebuilding it. + You can see this more clearly by naming + the &hello; program explicitly on the command line: + + + + + scons -Q hello + scons -Q hello + + + + + Note that &SCons; reports "...is up to date" + only for target files named explicitly on the command line, + to avoid cluttering the output. + + + +
+ Deciding When an Input File Has Changed: the &Decider; Function + + + + Another aspect of avoiding unnecessary rebuilds + is the fundamental build tool behavior + of rebuilding + things when an input file changes, + so that the built software is up to date. + By default, + &SCons; keeps track of this through an + MD5 &signature;, or checksum, of the contents of each file, + although you can easily configure + &SCons; to use the + modification times (or time stamps) + instead. + You can even specify your own Python function + for deciding if an input file has changed. + + + +
+ Using MD5 Signatures to Decide if a File Has Changed + + + + By default, + &SCons; keeps track of whether a file has changed + based on an MD5 checksum of the file's contents, + not the file's modification time. + This means that you may be surprised by the + default &SCons; behavior if you are used to the + &Make; convention of forcing + a rebuild by updating the file's modification time + (using the &touch; command, for example): + + + + + scons -Q hello + touch hello.c + scons -Q hello + + + + + Even though the file's modification time has changed, + &SCons; realizes that the contents of the + &hello_c; file have not changed, + and therefore that the &hello; program + need not be rebuilt. + This avoids unnecessary rebuilds when, + for example, someone rewrites the + contents of a file without making a change. + But if the contents of the file really do change, + then &SCons; detects the change + and rebuilds the program as required: + + + + + scons -Q hello + edit hello.c + scons -Q hello + + + + + Note that you can, if you wish, + specify this default behavior + (MD5 signatures) explicitly + using the &Decider; function as follows: + + + + + Program('hello.c') + Decider('MD5') + + + + + You can also use the string 'content' + as a synonym for 'MD5' + when calling the &Decider; function. + + + +
+ Ramifications of Using MD5 Signatures + + + + Using MD5 signatures to decide if an input file has changed + has one surprising benefit: + if a source file has been changed + in such a way that the contents of the + rebuilt target file(s) + will be exactly the same as the last time + the file was built, + then any "downstream" target files + that depend on the rebuilt-but-not-changed target + file actually need not be rebuilt. + + + + + + So if, for example, + a user were to only change a comment in a &hello_c; file, + then the rebuilt &hello_o; file + would be exactly the same as the one previously built + (assuming the compiler doesn't put any build-specific + information in the object file). + &SCons; would then realize that it would not + need to rebuild the &hello; program as follows: + + + + + scons -Q hello + edit hello.c + scons -Q hello + + + + + In essence, &SCons; + "short-circuits" any dependent builds + when it realizes that a target file + has been rebuilt to exactly the same file as the last build. + This does take some extra processing time + to read the contents of the target (&hello_o;) file, + but often saves time when the rebuild that was avoided + would have been time-consuming and expensive. + + + +
+ +
+ +
+ Using Time Stamps to Decide If a File Has Changed + + + + If you prefer, you can + configure &SCons; to use the modification time + of a file, not the file contents, + when deciding if a target needs to be rebuilt. + &SCons; gives you two ways to use time stamps + to decide if an input file has changed + since the last time a target has been built. + + + + + + The most familiar way to use time stamps + is the way &Make; does: + that is, have &SCons; decide + that a target must be rebuilt + if a source file's modification time is + newer + than the target file. + To do this, call the &Decider; + function as follows: + + + + + + Program('hello.c') + Decider('timestamp-newer') + + + int main() { printf("Hello, world!\n"); } + + + + + + This makes &SCons; act like &Make; + when a file's modification time is updated + (using the &touch; command, for example): + + + + + scons -Q hello + touch hello.c + scons -Q hello + + + + + And, in fact, because this behavior is the same + as the behavior of &Make;, + you can also use the string 'make' + as a synonym for 'timestamp-newer' + when calling the &Decider; function: + + + + + Program('hello.c') + Decider('make') + + + + + One drawback to using times stamps exactly like &Make; + is that if an input file's modification time suddenly + becomes older than a target file, + the target file will not be rebuilt. + This can happen if an old copy of a source file is restored + from a backup archive, for example. + The contents of the restored file will likely be different + than they were the last time a dependent target was built, + but the target won't be rebuilt + because the modification time of the source file + is not newer than the target. + + + + + + Because &SCons; actually stores information + about the source files' time stamps whenever a target is built, + it can handle this situation by checking for + an exact match of the source file time stamp, + instead of just whether or not the source file + is newer than the target file. + To do this, specify the argument + 'timestamp-match' + when calling the &Decider; function: + + + + + + Program('hello.c') + Decider('timestamp-match') + + + int main() { printf("Hello, world!\n"); } + + + + + + When configured this way, + &SCons; will rebuild a target whenever + a source file's modification time has changed. + So if we use the touch -t + option to change the modification time of + &hello_c; to an old date (January 1, 1989), + &SCons; will still rebuild the target file: + + + + + scons -Q hello + touch -t 198901010000 hello.c + scons -Q hello + + + + + In general, the only reason to prefer + timestamp-newer + instead of + timestamp-match, + would be if you have some specific reason + to require this &Make;-like behavior of + not rebuilding a target when an otherwise-modified + source file is older. + + + +
+ +
+ Deciding If a File Has Changed Using Both MD Signatures and Time Stamps + + + + As a performance enhancement, + &SCons; provides a way to use + MD5 checksums of file contents + but to read those contents + only when the file's timestamp has changed. + To do this, call the &Decider; + function with 'MD5-timestamp' + argument as follows: + + + + + + Program('hello.c') + Decider('MD5-timestamp') + + + int main() { printf("Hello, world!\n"); } + + + + + + So configured, &SCons will still behave like + it does when using Decider('MD5'): + + + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % touch hello.c + % scons -Q hello + scons: `hello' is up to date. + % edit hello.c + [CHANGE THE CONTENTS OF hello.c] + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + However, the second call to &SCons; in the above output, + when the build is up-to-date, + will have been performed by simply looking at the + modification time of the &hello_c; file, + not by opening it and performing + an MD5 checksum calcuation on its contents. + This can significantly speed up many up-to-date builds. + + + + + + The only drawback to using + Decider('MD5-timestamp') + is that &SCons; will not + rebuild a target file if a source file was modified + within one second of the last time &SCons; built the file. + While most developers are programming, + this isn't a problem in practice, + since it's unlikely that someone will have built + and then thought quickly enough to make a substantive + change to a source file within one second. + Certain build scripts or + continuous integration tools may, however, + rely on the ability to apply changes to files + automatically and then rebuild as quickly as possible, + in which case use of + Decider('MD5-timestamp') + may not be appropriate. + + + +
+ +
+ Writing Your Own Custom &Decider; Function + + + + The different string values that we've passed to + the &Decider; function are essentially used by &SCons; + to pick one of several specific internal functions + that implement various ways of deciding if a dependency + (usually a source file) + has changed since a target file has been built. + As it turns out, + you can also supply your own function + to decide if a dependency has changed. + + + + + + For example, suppose we have an input file + that contains a lot of data, + in some specific regular format, + that is used to rebuild a lot of different target files, + but each target file really only depends on + one particular section of the input file. + We'd like to have each target file depend on + only its section of the input file. + However, since the input file may contain a lot of data, + we want to open the input file only if its timestamp has changed. + This could done with a custom + &Decider; function that might look something like this: + + + + + + Program('hello.c') + def decide_if_changed(dependency, target, prev_ni): + if self.get_timestamp() != prev_ni.timestamp: + dep = str(dependency) + tgt = str(target) + if specific_part_of_file_has_changed(dep, tgt): + return True + return False + Decider(decide_if_changed) + + + int main() { printf("Hello, world!\n"); } + + + + + + Note that in the function definition, + the dependency + (input file) is the first argument, + and then the ⌖. + Both of these are passed to the functions as + SCons &Node; objects, + which we convert to strings using the Python + str(). + + + + + + The third argument, prev_ni, + is an object that holds the + signature or timestamp information + that was recorded about the dependency + the last time the target was built. + A prev_ni object can hold + different information, + depending on the type of thing that the + dependency argument represents. + For normal files, + the prev_ni object + has the following attributes: + + + + + + + .csig + + + + The content signature, + or MD5 checksum, of the contents of the + dependency + file the list time the ⌖ was built. + + + + + + + .size + + + + The size in bytes of the dependency + file the list time the target was built. + + + + + + + .timestamp + + + + The modification time of the dependency + file the list time the ⌖ was built. + + + + + + + + + + Note that ignoring some of the arguments + in your custom &Decider; function + is a perfectly normal thing to do, + if they don't impact the way you want to + decide if the dependency file has changed. + + + +
+ +
+ Mixing Different Ways of Deciding If a File Has Changed + + + + The previous examples have all demonstrated calling + the global &Decider; function + to configure all dependency decisions that &SCons; makes. + Sometimes, however, you want to be able to configure + different decision-making for different targets. + When that's necessary, you can use the + env.Decider + method to affect only the configuration + decisions for targets built with a + specific construction environment. + + + + + + For example, if we arbitrarily want to build + one program using MD5 checkums + and another using file modification times + from the same source + we might configure it this way: + + + + + + env1 = Environment(CPPPATH = ['.']) + env2 = env1.Clone() + env2.Decider('timestamp-match') + env1.Program('prog-MD5', 'program1.c') + env2.Program('prog-timestamp', 'program2.c') + + + #include "inc.h" + int main() { printf("Hello, world!\n"); } + + + #include "inc.h" + int main() { printf("Hello, world!\n"); } + + + #define INC 1 + + + + + + If both of the programs include the same + inc.h file, + then updating the modification time of + inc.h + (using the &touch; command) + will cause only prog-timestamp + to be rebuilt: + + + + + scons -Q + touch inc.h + scons -Q + + +
+ +
+ +
+ Older Functions for Deciding When an Input File Has Changed + + + + &SCons; still supports two functions that used to be the + primary methods for configuring the + decision about whether or not an input file has changed. + Although they're not officially deprecated yet, + their use is discouraged, + mainly because they rely on a somewhat + confusing distinction between how + source files and target files are handled. + These functions are documented here mainly in case you + encounter them in existing &SConscript; files. + + + +
+ The &SourceSignatures; Function + + + + The &SourceSignatures; function is fairly straightforward, + and supports two different argument values + to configure whether source file changes should be decided + using MD5 signatures: + + + + + Program('hello.c') + SourceSignatures('MD5') + + + + + Or using time stamps: + + + + + Program('hello.c') + SourceSignatures('timestamp') + + + + + These are roughly equivalent to specifying + Decider('MD5') + or + Decider('timestamp-match'), + respectively, + although it only affects how SCons makes + decisions about dependencies on + source files--that is, + files that are not built from any other files. + + + +
+ +
+ The &TargetSignatures; Function + + + + The &TargetSignatures; function + specifies how &SCons; decides + when a target file has changed + when it is used as a + dependency of (input to) another target--that is, + the &TargetSignatures; function configures + how the signatures of "intermediate" target files + are used when deciding if a "downstream" target file + must be rebuilt. + + This easily-overlooked distinction between + how &SCons; decides if the target itself must be rebuilt + and how the target is then used to decide if a different + target must be rebuilt is one of the confusing + things that has led to the &TargetSignatures; + and &SourceSignatures; functions being + replaced by the simpler &Decider; function. + + + + + + + The &TargetSignatures; function supports the same + 'MD5' and 'timestamp' + argument values that are supported by the &SourceSignatures;, + with the same meanings, but applied to target files. + That is, in the example: + + + + + Program('hello.c') + TargetSignatures('MD5') + + + + + The MD5 checksum of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + And in the example: + + + + + Program('hello.c') + TargetSignatures('timestamp') + + + + + The modification time of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + + + + + + The &TargetSignatures; function supports + two additional argument values: + 'source' and 'build'. + The 'source' argument + specifies that decisions involving + whether target files have changed + since a previous build + should use the same behavior + for the decisions configured for source files + (using the &SourceSignatures; function). + So in the example: + + + + + Program('hello.c') + TargetSignatures('source') + SourceSignatures('timestamp') + + + + + All files, both targets and sources, + will use modification times + when deciding if an input file + has changed since the last + time a target was built. + + + + + + Lastly, the 'build' argument + specifies that &SCons; should examine + the build status of a target file + and always rebuild a "downstream" target + if the target file was itself rebuilt, + without re-examining the contents or timestamp + of the newly-built target file. + If the target file was not rebuilt during + this &scons; invocation, + then the target file will be examined + the same way as configured by + the &SourceSignature; call + to decide if it has changed. + + + + + + This mimics the behavior of + build signatures + in earlier versions of &SCons;. + A &buildsignature; re-combined + signatures of all the input files + that went into making the target file, + so that the target file itself + did not need to have its contents read + to compute an MD5 signature. + This can improve performance for some configurations, + but is generally not as effective as using + Decider('MD5-timestamp'). + + + +
+ +
+ +
+ Implicit Dependencies: The &cv-CPPPATH; Construction Variable + + + + Now suppose that our "Hello, World!" program + actually has an #include line + to include the &hello_h; file in the compilation: + + + + + + Program('hello.c', CPPPATH = '.') + + + #include <hello.h> + int + main() + { + printf("Hello, %s!\n", string); + } + + + #define string "world" + + + + + + And, for completeness, the &hello_h; file looks like this: + + + + + + + + + In this case, we want &SCons; to recognize that, + if the contents of the &hello_h; file change, + the &hello; program must be recompiled. + To do this, we need to modify the + &SConstruct; file like so: + + + + + + + + + The &cv-link-CPPPATH; value + tells &SCons; to look in the current directory + ('.') + for any files included by C source files + (.c or .h files). + With this assignment in the &SConstruct; file: + + + + + scons -Q hello + scons -Q hello + edit hello.h + scons -Q hello + + + + + First, notice that &SCons; + added the -I. argument + from the &cv-CPPPATH; variable + so that the compilation would find the + &hello_h; file in the local directory. + + + + + + Second, realize that &SCons; knows that the &hello; + program must be rebuilt + because it scans the contents of + the &hello_c; file + for the #include lines that indicate + another file is being included in the compilation. + &SCons; records these as + implicit dependencies + of the target file, + Consequently, + when the &hello_h; file changes, + &SCons; realizes that the &hello_c; file includes it, + and rebuilds the resulting &hello; program + that depends on both the &hello_c; and &hello_h; files. + + + + + + Like the &cv-link-LIBPATH; variable, + the &cv-CPPPATH; variable + may be a list of directories, + or a string separated by + the system-specific path separation character + (':' on POSIX/Linux, ';' on Windows). + Either way, &SCons; creates the + right command-line options + so that the following example: + + + + + + Program('hello.c', CPPPATH = ['include', '/home/project/inc']) + + + int main() { printf("Hello, world!\n"); } + + + + + + Will look like this on POSIX or Linux: + + + + + scons -Q hello + + + + + And like this on Windows: + + + + + scons -Q hello.exe + + +
+ +
+ Caching Implicit Dependencies + + + + Scanning each file for #include lines + does take some extra processing time. + When you're doing a full build of a large system, + the scanning time is usually a very small percentage + of the overall time spent on the build. + You're most likely to notice the scanning time, + however, when you rebuild + all or part of a large system: + &SCons; will likely take some extra time to "think about" + what must be built before it issues the + first build command + (or decides that everything is up to date + and nothing must be rebuilt). + + + + + + + + In practice, having &SCons; scan files saves time + relative to the amount of potential time + lost to tracking down subtle problems + introduced by incorrect dependencies. + Nevertheless, the "waiting time" + while &SCons; scans files can annoy + individual developers waiting for their builds to finish. + Consequently, &SCons; lets you cache + the implicit dependencies + that its scanners find, + for use by later builds. + You can do this by specifying the + &implicit-cache; option on the command line: + + + + + scons -Q --implicit-cache hello + scons -Q hello + + + + + If you don't want to specify &implicit-cache; + on the command line each time, + you can make it the default behavior for your build + by setting the &implicit_cache; option + in an &SConscript; file: + + + + + SetOption('implicit_cache', 1) + + + + + &SCons; does not cache implicit dependencies like this by default + because the &implicit-cache; causes &SCons; to simply use the implicit + dependencies stored during the last run, without any checking + for whether or not those dependencies are still correct. + Specifically, this means &implicit-cache; instructs &SCons; + to not rebuild "correctly" in the + following cases: + + + + + + + + + + When &implicit-cache; is used, &SCons; will ignore any changes that + may have been made to search paths + (like &cv-CPPPATH; or &cv-LIBPATH;,). + This can lead to &SCons; not rebuilding a file if a change to + &cv-CPPPATH; would normally cause a different, same-named file from + a different directory to be used. + + + + + + + + When &implicit-cache; is used, &SCons; will not detect if a + same-named file has been added to a directory that is earlier in + the search path than the directory in which the file was found + last time. + + + + + + +
+ The &implicit-deps-changed; Option + + + + When using cached implicit dependencies, + sometimes you want to "start fresh" + and have &SCons; re-scan the files + for which it previously cached the dependencies. + For example, + if you have recently installed a new version of + external code that you use for compilation, + the external header files will have changed + and the previously-cached implicit dependencies + will be out of date. + You can update them by + running &SCons; with the &implicit-deps-changed; option: + + + + + scons -Q --implicit-deps-changed hello + scons -Q hello + + + + + In this case, &SCons; will re-scan all of the implicit dependencies + and cache updated copies of the information. + + + +
+ +
+ The &implicit-deps-unchanged; Option + + + + By default when caching dependencies, + &SCons; notices when a file has been modified + and re-scans the file for any updated + implicit dependency information. + Sometimes, however, you may want + to force &SCons; to use the cached implicit dependencies, + even if the source files changed. + This can speed up a build for example, + when you have changed your source files + but know that you haven't changed + any #include lines. + In this case, + you can use the &implicit-deps-unchanged; option: + + + + + scons -Q --implicit-deps-unchanged hello + scons -Q hello + + + + + In this case, + &SCons; will assume that the cached implicit + dependencies are correct and + will not bother to re-scan changed files. + For typical builds after small, + incremental changes to source files, + the savings may not be very big, + but sometimes every bit of + improved performance counts. + + + +
+ + + +
+ +
+ Explicit Dependencies: the &Depends; Function + + + + Sometimes a file depends on another file + that is not detected by an &SCons; scanner. + For this situation, + &SCons; allows you to specific explicitly that one file + depends on another file, + and must be rebuilt whenever that file changes. + This is specified using the &Depends; method: + + + + + hello = Program('hello.c') + Depends(hello, 'other_file') + + + + + + % scons -Q hello + cc -c hello.c -o hello.o + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + % edit other_file + [CHANGE THE CONTENTS OF other_file] + % scons -Q hello + cc -c hello.c -o hello.o + cc -o hello hello.o + + + + + Note that the dependency + (the second argument to &Depends;) + may also be a list of Node objects + (for example, as returned by a call to a Builder): + + + + + hello = Program('hello.c') + goodbye = Program('goodbye.c') + Depends(hello, goodbye) + + + + + in which case the dependency or dependencies + will be built before the target(s): + + + + + % scons -Q hello + cc -c goodbye.c -o goodbye.o + cc -o goodbye goodbye.o + cc -c hello.c -o hello.o + cc -o hello hello.o + + +
+ +
+ Dependencies From External Files: the &ParseDepends; + Function + + + + &SCons; has built-in scanners for a number of languages. Sometimes + these scanners fail to extract certain implicit dependencies due + to limitations of the scanner implementation. + + + + + + The following example illustrates a case where the built-in C + scanner is unable to extract the implicit dependency on a header + file. + + + + + + #define FOO_HEADER <foo.h> + #include FOO_HEADER + + int main() { + return FOO; + } + + + Program('hello', 'hello.c', CPPPATH='.') + + + #define FOO 42 + + + + + scons -Q + edit foo.h + scons -Q + + + + + Apparently, the scanner does not know about the header dependency. + Being not a full-fledged C preprocessor, the scanner does not + expand the macro. + + + + + + In these cases, you may also use the compiler to extract the + implicit dependencies. &ParseDepends; can parse the contents of + the compiler output in the style of &Make;, and explicitly + establish all of the listed dependencies. + + + + + + The following example uses &ParseDepends; to process a compiler + generated dependency file which is generated as a side effect + during compilation of the object file: + + + + + + + + #define FOO_HEADER <foo.h> + #include FOO_HEADER + + int main() { + return FOO; + } + + + obj = Object('hello.c', CCFLAGS='-MD -MF hello.d', CPPPATH='.') + SideEffect('hello.d', obj) + ParseDepends('hello.d') + Program('hello', obj) + + + #define FOO 42 + + + hello.o: hello.c foo.h + + + + + scons -Q + edit foo.h + scons -Q + + + + + Parsing dependencies from a compiler-generated + .d file has a chicken-and-egg problem, that + causes unnecessary rebuilds: + + + + + + #define FOO_HEADER <foo.h> + #include FOO_HEADER + + int main() { + return FOO; + } + + + obj = Object('hello.c', CCFLAGS='-MD -MF hello.d', CPPPATH='.') + SideEffect('hello.d', obj) + ParseDepends('hello.d') + Program('hello', obj) + + + #define FOO 42 + + + + + + + % scons -Q + cc -o hello.o -c -MD -MF hello.d -I. hello.c + cc -o hello hello.o + % scons -Q --debug=explain + scons: rebuilding `hello.o' because `foo.h' is a new dependency + cc -o hello.o -c -MD -MF hello.d -I. hello.c + % scons -Q + scons: `.' is up to date. + + + + + In the first pass, the dependency file is generated while the + object file is compiled. At that time, &SCons; does not know about + the dependency on foo.h. In the second pass, + the object file is regenerated because foo.h + is detected as a new dependency. + + + + + + &ParseDepends; immediately reads the specified file at invocation + time and just returns if the file does not exist. A dependency + file generated during the build process is not automatically + parsed again. Hence, the compiler-extracted dependencies are not + stored in the signature database during the same build pass. This + limitation of &ParseDepends; leads to unnecessary recompilations. + Therefore, &ParseDepends; should only be used if scanners are not + available for the employed language or not powerful enough for the + specific task. + + + +
+ +
+ Ignoring Dependencies: the &Ignore; Function + + + + Sometimes it makes sense + to not rebuild a program, + even if a dependency file changes. + In this case, + you would tell &SCons; specifically + to ignore a dependency as follows: + + + + + + hello = Program('hello.c') + Ignore(hello, 'hello.h') + + + #include "hello.h" + int main() { printf("Hello, %s!\n", string); } + + + #define string "world" + + + + + + + + + % scons -Q hello + cc -c -o hello.o hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + % edit hello.h + [CHANGE THE CONTENTS OF hello.h] + % scons -Q hello + scons: `hello' is up to date. + + + + + Now, the above example is a little contrived, + because it's hard to imagine a real-world situation + where you wouldn't want to rebuild &hello; + if the &hello_h; file changed. + A more realistic example + might be if the &hello; + program is being built in a + directory that is shared between multiple systems + that have different copies of the + &stdio_h; include file. + In that case, + &SCons; would notice the differences between + the different systems' copies of &stdio_h; + and would rebuild &hello; + each time you change systems. + You could avoid these rebuilds as follows: + + + + + hello = Program('hello.c', CPPPATH=['/usr/include']) + Ignore(hello, '/usr/include/stdio.h') + + + + &Ignore; can also be used to prevent a generated file from being built + by default. This is due to the fact that directories depend on + their contents. So to ignore a generated file from the default build, + you specify that the directory should ignore the generated file. + Note that the file will still be built if the user specifically + requests the target on scons command line, or if the file is + a dependency of another file which is requested and/or is built + by default. + + + + + hello_obj=Object('hello.c') + hello = Program(hello_obj) + Ignore('.',[hello,hello_obj]) + + + #include "stdio.h" + int main() { printf("Hello!\n"); } + + + + + scons -Q + scons -Q hello + scons -Q hello + +
+ +
+ Order-Only Dependencies: the &Requires; Function + + + + Occasionally, + it may be useful to specify that a certain + file or directory must, if necessary, + be built or created before some other target is built, + but that changes to that file or directory + do not + require that the target itself be rebuilt. + Such a relationship is called an + order-only dependency + because it only affects the order in which + things must be built--the dependency before the target--but + it is not a strict dependency relationship + because the target should not + change in response to changes in the dependent file. + + + + + + For example, suppose that you want to create a file + every time you run a build + that identifies the time the build was performed, + the version number, etc., + and which is included in every program that you build. + The version file's contents will change every build. + If you specify a normal dependency relationship, + then every program that depends on + that file would be rebuilt every time you ran &SCons;. + For example, we could use some Python code in + a &SConstruct; file to create a new version.c file + with a string containing the current date every time + we run &SCons;, + and then link a program with the resulting object file + by listing version.c in the sources: + + + + + + import time + + version_c_text = """ + char *date = "%s"; + """ % time.ctime(time.time()) + open('version.c', 'w').write(version_c_text) + + hello = Program(['hello.c', 'version.c']) + + + extern char *date; + int main() { printf("Hello, %s! I was built: %s\n", date); } + + + + + + If we list version.c as an actual source file, + though, then version.o + will get rebuilt every time we run &SCons; + (because the &SConstruct; file itself changes + the contents of version.c) + and the hello executable + will get re-linked every time + (because the version.o file changes): + + + + + + + % scons -Q + gcc -o hello.o -c hello.c + gcc -o version.o -c version.c + gcc -o hello hello.o version.o + % scons -Q + gcc -o version.o -c version.c + gcc -o hello hello.o version.o + % scons -Q + gcc -o version.o -c version.c + gcc -o hello hello.o version.o + + + + + One solution is to use the &Requires; function + to specify that the version.o + must be rebuilt before it is used by the link step, + but that changes to version.o + should not actually cause the hello + executable to be re-linked: + + + + + + import time + + version_c_text = """ + char *date = "%s"; + """ % time.ctime(time.time()) + open('version.c', 'w').write(version_c_text) + + version_obj = Object('version.c') + + hello = Program('hello.c', + LINKFLAGS = str(version_obj[0])) + + Requires(hello, version_obj) + + + extern char *date; + int main() { printf("Hello, %s! I was built: %s\n", date); } + + + + + + Notice that because we can no longer list version.c + as one of the sources for the hello program, + we have to find some other way to get it into the link command line. + For this example, we're cheating a bit and stuffing the + object file name (extracted from version_obj + list returned by the &b-Object; call) + into the &cv-link-LINKFLAGS; variable, + because &cv-LINKFLAGS; is already included + in the &cv-link-LINKCOM; command line. + + + + + + With these changes, + we get the desired behavior of + re-building the version.o file, + and therefore re-linking the hello executable, + only when the hello.c has changed: + + + + + scons -Q + scons -Q + edit hello.c + scons -Q + scons -Q + + +
+ +
+ The &AlwaysBuild; Function + + + + How &SCons; handles dependencies can also be affected + by the &AlwaysBuild; method. + When a file is passed to the &AlwaysBuild; method, + like so: + + + + + + hello = Program('hello.c') + AlwaysBuild(hello) + + + int main() { printf("Hello, %s!\n", string); } + + + + + + Then the specified target file (&hello; in our example) + will always be considered out-of-date and + rebuilt whenever that target file is evaluated + while walking the dependency graph: + + + + + scons -Q + scons -Q + + + + + The &AlwaysBuild; function has a somewhat misleading name, + because it does not actually mean the target file will + be rebuilt every single time &SCons; is invoked. + Instead, it means that the target will, in fact, + be rebuilt whenever the target file is encountered + while evaluating the targets specified on + the command line (and their dependencies). + So specifying some other target on the command line, + a target that does not + itself depend on the &AlwaysBuild; target, + will still be rebuilt only if it's out-of-date + with respect to its dependencies: + + + + + scons -Q + scons -Q hello.o + + + + +
+ + diff --git a/doc/user/depends.xml b/doc/user/depends.xml new file mode 100644 index 0000000..b72f41f --- /dev/null +++ b/doc/user/depends.xml @@ -0,0 +1,1776 @@ + + + + + So far we've seen how &SCons; handles one-time builds. + But one of the main functions of a build tool like &SCons; + is to rebuild only what is necessary + when source files change--or, put another way, + &SCons; should not + waste time rebuilding things that don't need to be rebuilt. + You can see this at work simply by re-invoking &SCons; + after building our simple &hello; example: + + + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q + scons: `.' is up to date. + + + + + The second time it is executed, + &SCons; realizes that the &hello; program + is up-to-date with respect to the current &hello_c; source file, + and avoids rebuilding it. + You can see this more clearly by naming + the &hello; program explicitly on the command line: + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + + + + + Note that &SCons; reports "...is up to date" + only for target files named explicitly on the command line, + to avoid cluttering the output. + + + +
+ Deciding When an Input File Has Changed: the &Decider; Function + + + + Another aspect of avoiding unnecessary rebuilds + is the fundamental build tool behavior + of rebuilding + things when an input file changes, + so that the built software is up to date. + By default, + &SCons; keeps track of this through an + MD5 &signature;, or checksum, of the contents of each file, + although you can easily configure + &SCons; to use the + modification times (or time stamps) + instead. + You can even specify your own Python function + for deciding if an input file has changed. + + + +
+ Using MD5 Signatures to Decide if a File Has Changed + + + + By default, + &SCons; keeps track of whether a file has changed + based on an MD5 checksum of the file's contents, + not the file's modification time. + This means that you may be surprised by the + default &SCons; behavior if you are used to the + &Make; convention of forcing + a rebuild by updating the file's modification time + (using the &touch; command, for example): + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % touch hello.c + % scons -Q hello + scons: `hello' is up to date. + + + + + Even though the file's modification time has changed, + &SCons; realizes that the contents of the + &hello_c; file have not changed, + and therefore that the &hello; program + need not be rebuilt. + This avoids unnecessary rebuilds when, + for example, someone rewrites the + contents of a file without making a change. + But if the contents of the file really do change, + then &SCons; detects the change + and rebuilds the program as required: + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % edit hello.c + [CHANGE THE CONTENTS OF hello.c] + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + Note that you can, if you wish, + specify this default behavior + (MD5 signatures) explicitly + using the &Decider; function as follows: + + + + + Program('hello.c') + Decider('MD5') + + + + + You can also use the string 'content' + as a synonym for 'MD5' + when calling the &Decider; function. + + + +
+ Ramifications of Using MD5 Signatures + + + + Using MD5 signatures to decide if an input file has changed + has one surprising benefit: + if a source file has been changed + in such a way that the contents of the + rebuilt target file(s) + will be exactly the same as the last time + the file was built, + then any "downstream" target files + that depend on the rebuilt-but-not-changed target + file actually need not be rebuilt. + + + + + + So if, for example, + a user were to only change a comment in a &hello_c; file, + then the rebuilt &hello_o; file + would be exactly the same as the one previously built + (assuming the compiler doesn't put any build-specific + information in the object file). + &SCons; would then realize that it would not + need to rebuild the &hello; program as follows: + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % edit hello.c + [CHANGE A COMMENT IN hello.c] + % scons -Q hello + cc -o hello.o -c hello.c + scons: `hello' is up to date. + + + + + In essence, &SCons; + "short-circuits" any dependent builds + when it realizes that a target file + has been rebuilt to exactly the same file as the last build. + This does take some extra processing time + to read the contents of the target (&hello_o;) file, + but often saves time when the rebuild that was avoided + would have been time-consuming and expensive. + + + +
+ +
+ +
+ Using Time Stamps to Decide If a File Has Changed + + + + If you prefer, you can + configure &SCons; to use the modification time + of a file, not the file contents, + when deciding if a target needs to be rebuilt. + &SCons; gives you two ways to use time stamps + to decide if an input file has changed + since the last time a target has been built. + + + + + + The most familiar way to use time stamps + is the way &Make; does: + that is, have &SCons; decide + that a target must be rebuilt + if a source file's modification time is + newer + than the target file. + To do this, call the &Decider; + function as follows: + + + + + Program('hello.c') + Decider('timestamp-newer') + + + + + This makes &SCons; act like &Make; + when a file's modification time is updated + (using the &touch; command, for example): + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % touch hello.c + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + And, in fact, because this behavior is the same + as the behavior of &Make;, + you can also use the string 'make' + as a synonym for 'timestamp-newer' + when calling the &Decider; function: + + + + + Program('hello.c') + Decider('make') + + + + + One drawback to using times stamps exactly like &Make; + is that if an input file's modification time suddenly + becomes older than a target file, + the target file will not be rebuilt. + This can happen if an old copy of a source file is restored + from a backup archive, for example. + The contents of the restored file will likely be different + than they were the last time a dependent target was built, + but the target won't be rebuilt + because the modification time of the source file + is not newer than the target. + + + + + + Because &SCons; actually stores information + about the source files' time stamps whenever a target is built, + it can handle this situation by checking for + an exact match of the source file time stamp, + instead of just whether or not the source file + is newer than the target file. + To do this, specify the argument + 'timestamp-match' + when calling the &Decider; function: + + + + + Program('hello.c') + Decider('timestamp-match') + + + + + When configured this way, + &SCons; will rebuild a target whenever + a source file's modification time has changed. + So if we use the touch -t + option to change the modification time of + &hello_c; to an old date (January 1, 1989), + &SCons; will still rebuild the target file: + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % touch -t 198901010000 hello.c + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + In general, the only reason to prefer + timestamp-newer + instead of + timestamp-match, + would be if you have some specific reason + to require this &Make;-like behavior of + not rebuilding a target when an otherwise-modified + source file is older. + + + +
+ +
+ Deciding If a File Has Changed Using Both MD Signatures and Time Stamps + + + + As a performance enhancement, + &SCons; provides a way to use + MD5 checksums of file contents + but to read those contents + only when the file's timestamp has changed. + To do this, call the &Decider; + function with 'MD5-timestamp' + argument as follows: + + + + + Program('hello.c') + Decider('MD5-timestamp') + + + + + So configured, &SCons; will still behave like + it does when using Decider('MD5'): + + + + + + + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % touch hello.c + % scons -Q hello + scons: `hello' is up to date. + % edit hello.c + [CHANGE THE CONTENTS OF hello.c] + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + However, the second call to &SCons; in the above output, + when the build is up-to-date, + will have been performed by simply looking at the + modification time of the &hello_c; file, + not by opening it and performing + an MD5 checksum calcuation on its contents. + This can significantly speed up many up-to-date builds. + + + + + + The only drawback to using + Decider('MD5-timestamp') + is that &SCons; will not + rebuild a target file if a source file was modified + within one second of the last time &SCons; built the file. + While most developers are programming, + this isn't a problem in practice, + since it's unlikely that someone will have built + and then thought quickly enough to make a substantive + change to a source file within one second. + Certain build scripts or + continuous integration tools may, however, + rely on the ability to apply changes to files + automatically and then rebuild as quickly as possible, + in which case use of + Decider('MD5-timestamp') + may not be appropriate. + + + +
+ +
+ Writing Your Own Custom &Decider; Function + + + + The different string values that we've passed to + the &Decider; function are essentially used by &SCons; + to pick one of several specific internal functions + that implement various ways of deciding if a dependency + (usually a source file) + has changed since a target file has been built. + As it turns out, + you can also supply your own function + to decide if a dependency has changed. + + + + + + For example, suppose we have an input file + that contains a lot of data, + in some specific regular format, + that is used to rebuild a lot of different target files, + but each target file really only depends on + one particular section of the input file. + We'd like to have each target file depend on + only its section of the input file. + However, since the input file may contain a lot of data, + we want to open the input file only if its timestamp has changed. + This could done with a custom + &Decider; function that might look something like this: + + + + + Program('hello.c') + def decide_if_changed(dependency, target, prev_ni): + if self.get_timestamp() != prev_ni.timestamp: + dep = str(dependency) + tgt = str(target) + if specific_part_of_file_has_changed(dep, tgt): + return True + return False + Decider(decide_if_changed) + + + + + Note that in the function definition, + the dependency + (input file) is the first argument, + and then the ⌖. + Both of these are passed to the functions as + SCons &Node; objects, + which we convert to strings using the Python + str(). + + + + + + The third argument, prev_ni, + is an object that holds the + signature or timestamp information + that was recorded about the dependency + the last time the target was built. + A prev_ni object can hold + different information, + depending on the type of thing that the + dependency argument represents. + For normal files, + the prev_ni object + has the following attributes: + + + + + + + .csig + + + + The content signature, + or MD5 checksum, of the contents of the + dependency + file the list time the ⌖ was built. + + + + + + + .size + + + + The size in bytes of the dependency + file the list time the target was built. + + + + + + + .timestamp + + + + The modification time of the dependency + file the list time the ⌖ was built. + + + + + + + + + + Note that ignoring some of the arguments + in your custom &Decider; function + is a perfectly normal thing to do, + if they don't impact the way you want to + decide if the dependency file has changed. + + + +
+ +
+ Mixing Different Ways of Deciding If a File Has Changed + + + + The previous examples have all demonstrated calling + the global &Decider; function + to configure all dependency decisions that &SCons; makes. + Sometimes, however, you want to be able to configure + different decision-making for different targets. + When that's necessary, you can use the + env.Decider + method to affect only the configuration + decisions for targets built with a + specific construction environment. + + + + + + For example, if we arbitrarily want to build + one program using MD5 checkums + and another using file modification times + from the same source + we might configure it this way: + + + + + env1 = Environment(CPPPATH = ['.']) + env2 = env1.Clone() + env2.Decider('timestamp-match') + env1.Program('prog-MD5', 'program1.c') + env2.Program('prog-timestamp', 'program2.c') + + + + + If both of the programs include the same + inc.h file, + then updating the modification time of + inc.h + (using the &touch; command) + will cause only prog-timestamp + to be rebuilt: + + + + + % scons -Q + cc -o program1.o -c -I. program1.c + cc -o prog-MD5 program1.o + cc -o program2.o -c -I. program2.c + cc -o prog-timestamp program2.o + % touch inc.h + % scons -Q + cc -o program2.o -c -I. program2.c + cc -o prog-timestamp program2.o + + +
+ +
+ +
+ Older Functions for Deciding When an Input File Has Changed + + + + &SCons; still supports two functions that used to be the + primary methods for configuring the + decision about whether or not an input file has changed. + Although they're not officially deprecated yet, + their use is discouraged, + mainly because they rely on a somewhat + confusing distinction between how + source files and target files are handled. + These functions are documented here mainly in case you + encounter them in existing &SConscript; files. + + + +
+ The &SourceSignatures; Function + + + + The &SourceSignatures; function is fairly straightforward, + and supports two different argument values + to configure whether source file changes should be decided + using MD5 signatures: + + + + + Program('hello.c') + SourceSignatures('MD5') + + + + + Or using time stamps: + + + + + Program('hello.c') + SourceSignatures('timestamp') + + + + + These are roughly equivalent to specifying + Decider('MD5') + or + Decider('timestamp-match'), + respectively, + although it only affects how SCons makes + decisions about dependencies on + source files--that is, + files that are not built from any other files. + + + +
+ +
+ The &TargetSignatures; Function + + + + The &TargetSignatures; function + specifies how &SCons; decides + when a target file has changed + when it is used as a + dependency of (input to) another target--that is, + the &TargetSignatures; function configures + how the signatures of "intermediate" target files + are used when deciding if a "downstream" target file + must be rebuilt. + + This easily-overlooked distinction between + how &SCons; decides if the target itself must be rebuilt + and how the target is then used to decide if a different + target must be rebuilt is one of the confusing + things that has led to the &TargetSignatures; + and &SourceSignatures; functions being + replaced by the simpler &Decider; function. + + + + + + + The &TargetSignatures; function supports the same + 'MD5' and 'timestamp' + argument values that are supported by the &SourceSignatures;, + with the same meanings, but applied to target files. + That is, in the example: + + + + + Program('hello.c') + TargetSignatures('MD5') + + + + + The MD5 checksum of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + And in the example: + + + + + Program('hello.c') + TargetSignatures('timestamp') + + + + + The modification time of the &hello_o; target file + will be used to decide if it has changed since the last + time the "downstream" &hello; target file was built. + + + + + + The &TargetSignatures; function supports + two additional argument values: + 'source' and 'build'. + The 'source' argument + specifies that decisions involving + whether target files have changed + since a previous build + should use the same behavior + for the decisions configured for source files + (using the &SourceSignatures; function). + So in the example: + + + + + Program('hello.c') + TargetSignatures('source') + SourceSignatures('timestamp') + + + + + All files, both targets and sources, + will use modification times + when deciding if an input file + has changed since the last + time a target was built. + + + + + + Lastly, the 'build' argument + specifies that &SCons; should examine + the build status of a target file + and always rebuild a "downstream" target + if the target file was itself rebuilt, + without re-examining the contents or timestamp + of the newly-built target file. + If the target file was not rebuilt during + this &scons; invocation, + then the target file will be examined + the same way as configured by + the &SourceSignature; call + to decide if it has changed. + + + + + + This mimics the behavior of + build signatures + in earlier versions of &SCons;. + A &buildsignature; re-combined + signatures of all the input files + that went into making the target file, + so that the target file itself + did not need to have its contents read + to compute an MD5 signature. + This can improve performance for some configurations, + but is generally not as effective as using + Decider('MD5-timestamp'). + + + +
+ +
+ +
+ Implicit Dependencies: The &cv-CPPPATH; Construction Variable + + + + Now suppose that our "Hello, World!" program + actually has an #include line + to include the &hello_h; file in the compilation: + + + + + #include <hello.h> + int + main() + { + printf("Hello, %s!\n", string); + } + + + + + And, for completeness, the &hello_h; file looks like this: + + + + + + #define string "world" + + + + + In this case, we want &SCons; to recognize that, + if the contents of the &hello_h; file change, + the &hello; program must be recompiled. + To do this, we need to modify the + &SConstruct; file like so: + + + + + + Program('hello.c', CPPPATH = '.') + + + + + The &cv-link-CPPPATH; value + tells &SCons; to look in the current directory + ('.') + for any files included by C source files + (.c or .h files). + With this assignment in the &SConstruct; file: + + + + + % scons -Q hello + cc -o hello.o -c -I. hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + % edit hello.h + [CHANGE THE CONTENTS OF hello.h] + % scons -Q hello + cc -o hello.o -c -I. hello.c + cc -o hello hello.o + + + + + First, notice that &SCons; + added the -I. argument + from the &cv-CPPPATH; variable + so that the compilation would find the + &hello_h; file in the local directory. + + + + + + Second, realize that &SCons; knows that the &hello; + program must be rebuilt + because it scans the contents of + the &hello_c; file + for the #include lines that indicate + another file is being included in the compilation. + &SCons; records these as + implicit dependencies + of the target file, + Consequently, + when the &hello_h; file changes, + &SCons; realizes that the &hello_c; file includes it, + and rebuilds the resulting &hello; program + that depends on both the &hello_c; and &hello_h; files. + + + + + + Like the &cv-link-LIBPATH; variable, + the &cv-CPPPATH; variable + may be a list of directories, + or a string separated by + the system-specific path separation character + (':' on POSIX/Linux, ';' on Windows). + Either way, &SCons; creates the + right command-line options + so that the following example: + + + + + Program('hello.c', CPPPATH = ['include', '/home/project/inc']) + + + + + Will look like this on POSIX or Linux: + + + + + % scons -Q hello + cc -o hello.o -c -Iinclude -I/home/project/inc hello.c + cc -o hello hello.o + + + + + And like this on Windows: + + + + + C:\>scons -Q hello.exe + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fohello.obj /c hello.c /nologo /Iinclude /I\home\project\inc + link /nologo /OUT:hello.exe hello.obj + + +
+ +
+ Caching Implicit Dependencies + + + + Scanning each file for #include lines + does take some extra processing time. + When you're doing a full build of a large system, + the scanning time is usually a very small percentage + of the overall time spent on the build. + You're most likely to notice the scanning time, + however, when you rebuild + all or part of a large system: + &SCons; will likely take some extra time to "think about" + what must be built before it issues the + first build command + (or decides that everything is up to date + and nothing must be rebuilt). + + + + + + + + In practice, having &SCons; scan files saves time + relative to the amount of potential time + lost to tracking down subtle problems + introduced by incorrect dependencies. + Nevertheless, the "waiting time" + while &SCons; scans files can annoy + individual developers waiting for their builds to finish. + Consequently, &SCons; lets you cache + the implicit dependencies + that its scanners find, + for use by later builds. + You can do this by specifying the + &implicit-cache; option on the command line: + + + + + % scons -Q --implicit-cache hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + + + + + If you don't want to specify &implicit-cache; + on the command line each time, + you can make it the default behavior for your build + by setting the &implicit_cache; option + in an &SConscript; file: + + + + + SetOption('implicit_cache', 1) + + + + + &SCons; does not cache implicit dependencies like this by default + because the &implicit-cache; causes &SCons; to simply use the implicit + dependencies stored during the last run, without any checking + for whether or not those dependencies are still correct. + Specifically, this means &implicit-cache; instructs &SCons; + to not rebuild "correctly" in the + following cases: + + + + + + + + + + When &implicit-cache; is used, &SCons; will ignore any changes that + may have been made to search paths + (like &cv-CPPPATH; or &cv-LIBPATH;,). + This can lead to &SCons; not rebuilding a file if a change to + &cv-CPPPATH; would normally cause a different, same-named file from + a different directory to be used. + + + + + + + + When &implicit-cache; is used, &SCons; will not detect if a + same-named file has been added to a directory that is earlier in + the search path than the directory in which the file was found + last time. + + + + + + +
+ The &implicit-deps-changed; Option + + + + When using cached implicit dependencies, + sometimes you want to "start fresh" + and have &SCons; re-scan the files + for which it previously cached the dependencies. + For example, + if you have recently installed a new version of + external code that you use for compilation, + the external header files will have changed + and the previously-cached implicit dependencies + will be out of date. + You can update them by + running &SCons; with the &implicit-deps-changed; option: + + + + + % scons -Q --implicit-deps-changed hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + + + + + In this case, &SCons; will re-scan all of the implicit dependencies + and cache updated copies of the information. + + + +
+ +
+ The &implicit-deps-unchanged; Option + + + + By default when caching dependencies, + &SCons; notices when a file has been modified + and re-scans the file for any updated + implicit dependency information. + Sometimes, however, you may want + to force &SCons; to use the cached implicit dependencies, + even if the source files changed. + This can speed up a build for example, + when you have changed your source files + but know that you haven't changed + any #include lines. + In this case, + you can use the &implicit-deps-unchanged; option: + + + + + % scons -Q --implicit-deps-unchanged hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + + + + + In this case, + &SCons; will assume that the cached implicit + dependencies are correct and + will not bother to re-scan changed files. + For typical builds after small, + incremental changes to source files, + the savings may not be very big, + but sometimes every bit of + improved performance counts. + + + +
+ + + +
+ +
+ Explicit Dependencies: the &Depends; Function + + + + Sometimes a file depends on another file + that is not detected by an &SCons; scanner. + For this situation, + &SCons; allows you to specific explicitly that one file + depends on another file, + and must be rebuilt whenever that file changes. + This is specified using the &Depends; method: + + + + + hello = Program('hello.c') + Depends(hello, 'other_file') + + + + + + % scons -Q hello + cc -c hello.c -o hello.o + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + % edit other_file + [CHANGE THE CONTENTS OF other_file] + % scons -Q hello + cc -c hello.c -o hello.o + cc -o hello hello.o + + + + + Note that the dependency + (the second argument to &Depends;) + may also be a list of Node objects + (for example, as returned by a call to a Builder): + + + + + hello = Program('hello.c') + goodbye = Program('goodbye.c') + Depends(hello, goodbye) + + + + + in which case the dependency or dependencies + will be built before the target(s): + + + + + % scons -Q hello + cc -c goodbye.c -o goodbye.o + cc -o goodbye goodbye.o + cc -c hello.c -o hello.o + cc -o hello hello.o + + +
+ +
+ Dependencies From External Files: the &ParseDepends; + Function + + + + &SCons; has built-in scanners for a number of languages. Sometimes + these scanners fail to extract certain implicit dependencies due + to limitations of the scanner implementation. + + + + + + The following example illustrates a case where the built-in C + scanner is unable to extract the implicit dependency on a header + file. + + + + + #define FOO_HEADER <foo.h> + #include FOO_HEADER + + int main() { + return FOO; + } + + + + % scons -Q + cc -o hello.o -c -I. hello.c + cc -o hello hello.o + % edit foo.h + [CHANGE CONTENTS OF foo.h] + % scons -Q + scons: `.' is up to date. + + + + + Apparently, the scanner does not know about the header dependency. + Being not a full-fledged C preprocessor, the scanner does not + expand the macro. + + + + + + In these cases, you may also use the compiler to extract the + implicit dependencies. &ParseDepends; can parse the contents of + the compiler output in the style of &Make;, and explicitly + establish all of the listed dependencies. + + + + + + The following example uses &ParseDepends; to process a compiler + generated dependency file which is generated as a side effect + during compilation of the object file: + + + + + + + obj = Object('hello.c', CCFLAGS='-MD -MF hello.d', CPPPATH='.') + SideEffect('hello.d', obj) + ParseDepends('hello.d') + Program('hello', obj) + + + + % scons -Q + cc -o hello.o -c -MD -MF hello.d -I. hello.c + cc -o hello hello.o + % edit foo.h + [CHANGE CONTENTS OF foo.h] + % scons -Q + cc -o hello.o -c -MD -MF hello.d -I. hello.c + + + + + Parsing dependencies from a compiler-generated + .d file has a chicken-and-egg problem, that + causes unnecessary rebuilds: + + + + + + + + + % scons -Q + cc -o hello.o -c -MD -MF hello.d -I. hello.c + cc -o hello hello.o + % scons -Q --debug=explain + scons: rebuilding `hello.o' because `foo.h' is a new dependency + cc -o hello.o -c -MD -MF hello.d -I. hello.c + % scons -Q + scons: `.' is up to date. + + + + + In the first pass, the dependency file is generated while the + object file is compiled. At that time, &SCons; does not know about + the dependency on foo.h. In the second pass, + the object file is regenerated because foo.h + is detected as a new dependency. + + + + + + &ParseDepends; immediately reads the specified file at invocation + time and just returns if the file does not exist. A dependency + file generated during the build process is not automatically + parsed again. Hence, the compiler-extracted dependencies are not + stored in the signature database during the same build pass. This + limitation of &ParseDepends; leads to unnecessary recompilations. + Therefore, &ParseDepends; should only be used if scanners are not + available for the employed language or not powerful enough for the + specific task. + + + +
+ +
+ Ignoring Dependencies: the &Ignore; Function + + + + Sometimes it makes sense + to not rebuild a program, + even if a dependency file changes. + In this case, + you would tell &SCons; specifically + to ignore a dependency as follows: + + + + + hello = Program('hello.c') + Ignore(hello, 'hello.h') + + + + + + + + % scons -Q hello + cc -c -o hello.o hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + % edit hello.h + [CHANGE THE CONTENTS OF hello.h] + % scons -Q hello + scons: `hello' is up to date. + + + + + Now, the above example is a little contrived, + because it's hard to imagine a real-world situation + where you wouldn't want to rebuild &hello; + if the &hello_h; file changed. + A more realistic example + might be if the &hello; + program is being built in a + directory that is shared between multiple systems + that have different copies of the + &stdio_h; include file. + In that case, + &SCons; would notice the differences between + the different systems' copies of &stdio_h; + and would rebuild &hello; + each time you change systems. + You could avoid these rebuilds as follows: + + + + + hello = Program('hello.c', CPPPATH=['/usr/include']) + Ignore(hello, '/usr/include/stdio.h') + + + + &Ignore; can also be used to prevent a generated file from being built + by default. This is due to the fact that directories depend on + their contents. So to ignore a generated file from the default build, + you specify that the directory should ignore the generated file. + Note that the file will still be built if the user specifically + requests the target on scons command line, or if the file is + a dependency of another file which is requested and/or is built + by default. + + + + hello_obj=Object('hello.c') + hello = Program(hello_obj) + Ignore('.',[hello,hello_obj]) + + + + % scons -Q + scons: `.' is up to date. + % scons -Q hello + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q hello + scons: `hello' is up to date. + +
+ +
+ Order-Only Dependencies: the &Requires; Function + + + + Occasionally, + it may be useful to specify that a certain + file or directory must, if necessary, + be built or created before some other target is built, + but that changes to that file or directory + do not + require that the target itself be rebuilt. + Such a relationship is called an + order-only dependency + because it only affects the order in which + things must be built--the dependency before the target--but + it is not a strict dependency relationship + because the target should not + change in response to changes in the dependent file. + + + + + + For example, suppose that you want to create a file + every time you run a build + that identifies the time the build was performed, + the version number, etc., + and which is included in every program that you build. + The version file's contents will change every build. + If you specify a normal dependency relationship, + then every program that depends on + that file would be rebuilt every time you ran &SCons;. + For example, we could use some Python code in + a &SConstruct; file to create a new version.c file + with a string containing the current date every time + we run &SCons;, + and then link a program with the resulting object file + by listing version.c in the sources: + + + + + import time + + version_c_text = """ + char *date = "%s"; + """ % time.ctime(time.time()) + open('version.c', 'w').write(version_c_text) + + hello = Program(['hello.c', 'version.c']) + + + + + If we list version.c as an actual source file, + though, then version.o + will get rebuilt every time we run &SCons; + (because the &SConstruct; file itself changes + the contents of version.c) + and the hello executable + will get re-linked every time + (because the version.o file changes): + + + + + + + % scons -Q + gcc -o hello.o -c hello.c + gcc -o version.o -c version.c + gcc -o hello hello.o version.o + % scons -Q + gcc -o version.o -c version.c + gcc -o hello hello.o version.o + % scons -Q + gcc -o version.o -c version.c + gcc -o hello hello.o version.o + + + + + One solution is to use the &Requires; function + to specify that the version.o + must be rebuilt before it is used by the link step, + but that changes to version.o + should not actually cause the hello + executable to be re-linked: + + + + + import time + + version_c_text = """ + char *date = "%s"; + """ % time.ctime(time.time()) + open('version.c', 'w').write(version_c_text) + + version_obj = Object('version.c') + + hello = Program('hello.c', + LINKFLAGS = str(version_obj[0])) + + Requires(hello, version_obj) + + + + + Notice that because we can no longer list version.c + as one of the sources for the hello program, + we have to find some other way to get it into the link command line. + For this example, we're cheating a bit and stuffing the + object file name (extracted from version_obj + list returned by the &b-Object; call) + into the &cv-link-LINKFLAGS; variable, + because &cv-LINKFLAGS; is already included + in the &cv-link-LINKCOM; command line. + + + + + + With these changes, + we get the desired behavior of + re-building the version.o file, + and therefore re-linking the hello executable, + only when the hello.c has changed: + + + + + % scons -Q + cc -o version.o -c version.c + cc -o hello.o -c hello.c + cc -o hello version.o hello.o + % scons -Q + scons: `.' is up to date. + % edit hello.c + [CHANGE THE CONTENTS OF hello.c] + % scons -Q + cc -o version.o -c version.c + cc -o hello.o -c hello.c + cc -o hello version.o hello.o + % scons -Q + scons: `.' is up to date. + + +
+ +
+ The &AlwaysBuild; Function + + + + How &SCons; handles dependencies can also be affected + by the &AlwaysBuild; method. + When a file is passed to the &AlwaysBuild; method, + like so: + + + + + hello = Program('hello.c') + AlwaysBuild(hello) + + + + + Then the specified target file (&hello; in our example) + will always be considered out-of-date and + rebuilt whenever that target file is evaluated + while walking the dependency graph: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q + cc -o hello hello.o + + + + + The &AlwaysBuild; function has a somewhat misleading name, + because it does not actually mean the target file will + be rebuilt every single time &SCons; is invoked. + Instead, it means that the target will, in fact, + be rebuilt whenever the target file is encountered + while evaluating the targets specified on + the command line (and their dependencies). + So specifying some other target on the command line, + a target that does not + itself depend on the &AlwaysBuild; target, + will still be rebuilt only if it's out-of-date + with respect to its dependencies: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q hello.o + scons: `hello.o' is up to date. + + + + +
+ + diff --git a/doc/user/environments.in b/doc/user/environments.in new file mode 100644 index 0000000..124aaaa --- /dev/null +++ b/doc/user/environments.in @@ -0,0 +1,1678 @@ + + + + + + + An environment + is a collection of values that + can affect how a program executes. + &SCons; distinguishes between three + different types of environments + that can affect the behavior of &SCons; itself + (subject to the configuration in the &SConscript; files), + as well as the compilers and other tools it executes: + + + + + + + External Environment + + + + + The external environment + is the set of variables in the user's environment + at the time the user runs &SCons. + These variables are available within the &SConscript; files + through the Python os.environ dictionary. + See , below. + + + + + + + &ConsEnv; + + + + + A &consenv; + is a distinct object creating within + a &SConscript; file and + and which contains values that + affect how &SCons; decides + what action to use to build a target, + and even to define which targets + should be built from which sources. + One of the most powerful features of &SCons; + is the ability to create multiple &consenvs;, + including the ability to clone a new, customized + &consenv; from an existing &consenv;. + See , below. + + + + + + + Execution Environment + + + + + An execution environment + is the values that &SCons; sets + when executing an external + command (such as a compiler or linker) + to build one or more targets. + Note that this is not the same as + the external environment + (see above). + See , below. + + + + + + + + + + Unlike &Make;, &SCons; does not automatically + copy or import values between different environments + (with the exception of explicit clones of &consenvs, + which inherit values from their parent). + This is a deliberate design choice + to make sure that builds are, + by default, repeatable regardless of + the values in the user's external environment. + This avoids a whole class of problems with builds + where a developer's local build works + because a custom variable setting + causes a different compiler or build option to be used, + but the checked-in change breaks the official build + because it uses different environment variable settings. + + + + + + Note that the &SConscript; writer can + easily arrange for variables to be + copied or imported between environments, + and this is often very useful + (or even downright necessary) + to make it easy for developers + to customize the build in appropriate ways. + The point is not + that copying variables between different environments + is evil and must always be avoided. + Instead, it should be up to the + implementer of the build system + to make conscious choices + about how and when to import + a variable from one environment to another, + making informed decisions about + striking the right balance + between making the build + repeatable on the one hand + and convenient to use on the other. + + + +
+ Using Values From the External Environment + + + + The external environment + variable settings that + the user has in force + when executing &SCons; + are available through the normal Python + os.environ + dictionary. + This means that you must add an + import os statement + to any &SConscript; file + in which you want to use + values from the user's external environment. + + + + + + import os + + + int main() { } + + + + + + More usefully, you can use the + os.environ + dictionary in your &SConscript; + files to initialize &consenvs; + with values from the user's external environment. + See the next section, + , + for information on how to do this. + + + +
+ +
+ Construction Environments + + + + It is rare that all of the software in a large, + complicated system needs to be built the same way. + For example, different source files may need different options + enabled on the command line, + or different executable programs need to be linked + with different libraries. + &SCons; accommodates these different build + requirements by allowing you to create and + configure multiple &consenvs; + that control how the software is built. + A &consenv; is an object + that has a number of associated + &consvars;, each with a name and a value. + (A construction environment also has an attached + set of &Builder; methods, + about which we'll learn more later.) + + + +
+ Creating a &ConsEnv;: the &Environment; Function + + + + A &consenv; is created by the &Environment; method: + + + + + env = Environment() + + + + + By default, &SCons; initializes every + new construction environment + with a set of &consvars; + based on the tools that it finds on your system, + plus the default set of builder methods + necessary for using those tools. + The construction variables + are initialized with values describing + the C compiler, + the Fortran compiler, + the linker, + etc., + as well as the command lines to invoke them. + + + + + + When you initialize a construction environment + you can set the values of the + environment's &consvars; + to control how a program is built. + For example: + + + + + + env = Environment(CC = 'gcc', + CCFLAGS = '-O2') + + env.Program('foo.c') + + + int main() { } + + + + + + The construction environment in this example + is still initialized with the same default + construction variable values, + except that the user has explicitly specified use of the + GNU C compiler &gcc;, + and further specifies that the -O2 + (optimization level two) + flag should be used when compiling the object file. + In other words, the explicit initializations of + &cv-link-CC; and &cv-link-CCFLAGS; + override the default values in the newly-created + construction environment. + So a run from this example would look like: + + + + + scons -Q + + +
+ +
+ Fetching Values From a &ConsEnv; + + + + You can fetch individual construction variables + using the normal syntax + for accessing individual named items in a Python dictionary: + + + + + + env = Environment() + print "CC is:", env['CC'] + + + + + + This example &SConstruct; file doesn't build anything, + but because it's actually a Python script, + it will print the value of &cv-link-CC; for us: + + + + + scons -Q + + + + + A construction environment, however, + is actually an object with associated methods, etc. + If you want to have direct access to only the + dictionary of construction variables, + you can fetch this using the &Dictionary; method: + + + + + + env = Environment(FOO = 'foo', BAR = 'bar') + dict = env.Dictionary() + for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']: + print "key = %s, value = %s" % (key, dict[key]) + + + + + + This &SConstruct; file + will print the specified dictionary items for us on POSIX + systems as follows: + + + + + scons -Q + + + + + And on Windows: + + + + + scons -Q + + + + + If you want to loop and print the values of + all of the construction variables in a construction environment, + the Python code to do that in sorted order might look something like: + + + + + env = Environment() + dict = env.Dictionary() + keys = dict.keys() + keys.sort() + for key in keys: + print "construction variable = '%s', value = '%s'" % (key, dict[key]) + + +
+ +
+ Expanding Values From a &ConsEnv;: the &subst; Method + + + + Another way to get information from + a construction environment. + is to use the &subst; method + on a string containing $ expansions + of construction variable names. + As a simple example, + the example from the previous + section that used + env['CC'] + to fetch the value of &cv-link-CC; + could also be written as: + + + + + env = Environment() + print "CC is:", env.subst('$CC') + + + + + One advantage of using + &subst; to expand strings is + that construction variables + in the result get re-expanded until + there are no expansions left in the string. + So a simple fetch of a value like + &cv-link-CCCOM;: + + + + + env = Environment(CCFLAGS = '-DFOO') + print "CCCOM is:", env['CCCOM'] + + + + + Will print the unexpanded value of &cv-CCCOM;, + showing us the construction + variables that still need to be expanded: + + + + + % scons -Q + CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES + scons: `.' is up to date. + + + + + Calling the &subst; method on $CCOM, + however: + + + + + env = Environment(CCFLAGS = '-DFOO') + print "CCCOM is:", env.subst('$CCCOM') + + + + + Will recursively expand all of + the construction variables prefixed + with $ (dollar signs), + showing us the final output: + + + + + % scons -Q + CCCOM is: gcc -DFOO -c -o + scons: `.' is up to date. + + + + + Note that because we're not expanding this + in the context of building something + there are no target or source files + for &cv-link-TARGET; and &cv-link-SOURCES; to expand. + + + +
+ +
+ Controlling the Default &ConsEnv;: the &DefaultEnvironment; Function + + + + All of the &Builder; functions that we've introduced so far, + like &Program; and &Library;, + actually use a default &consenv; + that contains settings + for the various compilers + and other tools that + &SCons; configures by default, + or otherwise knows about + and has discovered on your system. + The goal of the default construction environment + is to make many configurations to "just work" + to build software using + readily available tools + with a minimum of configuration changes. + + + + + + You can, however, control the settings + in the default contstruction environment + by using the &DefaultEnvironment; function + to initialize various settings: + + + + + + DefaultEnvironment(CC = '/usr/local/bin/gcc') + + + + + + When configured as above, + all calls to the &Program; + or &Object; Builder + will build object files with the + /usr/local/bin/gcc + compiler. + + + + + + Note that the &DefaultEnvironment; function + returns the initialized + default construction environment object, + which can then be manipulated like any + other construction environment. + So the following + would be equivalent to the + previous example, + setting the &cv-CC; + variable to /usr/local/bin/gcc + but as a separate step after + the default construction environment has been initialized: + + + + + + env = DefaultEnvironment() + env['CC'] = '/usr/local/bin/gcc' + + + + + + One very common use of the &DefaultEnvironment; function + is to speed up &SCons; initialization. + As part of trying to make most default + configurations "just work," + &SCons; will actually + search the local system for installed + compilers and other utilities. + This search can take time, + especially on systems with + slow or networked file systems. + If you know which compiler(s) and/or + other utilities you want to configure, + you can control the search + that &SCons; performs + by specifying some specific + tool modules with which to + initialize the default construction environment: + + + + + + env = DefaultEnvironment(tools = ['gcc', 'gnulink'], + CC = '/usr/local/bin/gcc') + + + + + + So the above example would tell &SCons; + to explicitly configure the default environment + to use its normal GNU Compiler and GNU Linker settings + (without having to search for them, + or any other utilities for that matter), + and specifically to use the compiler found at + /usr/local/bin/gcc. + + + +
+ +
+ Multiple &ConsEnvs; + + + + The real advantage of construction environments + is that you can create as many different construction + environments as you need, + each tailored to a different way to build + some piece of software or other file. + If, for example, we need to build + one program with the -O2 flag + and another with the -g (debug) flag, + we would do this like so: + + + + + + opt = Environment(CCFLAGS = '-O2') + dbg = Environment(CCFLAGS = '-g') + + opt.Program('foo', 'foo.c') + + dbg.Program('bar', 'bar.c') + + + int main() { } + + + int main() { } + + + + + scons -Q + + + + + We can even use multiple construction environments to build + multiple versions of a single program. + If you do this by simply trying to use the + &b-link-Program; builder with both environments, though, + like this: + + + + + + opt = Environment(CCFLAGS = '-O2') + dbg = Environment(CCFLAGS = '-g') + + opt.Program('foo', 'foo.c') + + dbg.Program('foo', 'foo.c') + + + int main() { } + + + + + + Then &SCons; generates the following error: + + + + + scons -Q + + + + + This is because the two &b-Program; calls have + each implicitly told &SCons; to generate an object file named + foo.o, + one with a &cv-link-CCFLAGS; value of + -O2 + and one with a &cv-link-CCFLAGS; value of + -g. + &SCons; can't just decide that one of them + should take precedence over the other, + so it generates the error. + To avoid this problem, + we must explicitly specify + that each environment compile + foo.c + to a separately-named object file + using the &b-link-Object; builder, like so: + + + + + + opt = Environment(CCFLAGS = '-O2') + dbg = Environment(CCFLAGS = '-g') + + o = opt.Object('foo-opt', 'foo.c') + opt.Program(o) + + d = dbg.Object('foo-dbg', 'foo.c') + dbg.Program(d) + + + int main() { } + + + + + + Notice that each call to the &b-Object; builder + returns a value, + an internal &SCons; object that + represents the object file that will be built. + We then use that object + as input to the &b-Program; builder. + This avoids having to specify explicitly + the object file name in multiple places, + and makes for a compact, readable + &SConstruct; file. + Our &SCons; output then looks like: + + + + + scons -Q + + +
+ +
+ Making Copies of &ConsEnvs;: the &Clone; Method + + + + Sometimes you want more than one construction environment + to share the same values for one or more variables. + Rather than always having to repeat all of the common + variables when you create each construction environment, + you can use the &Clone; method + to create a copy of a construction environment. + + + + + + Like the &Environment; call that creates a construction environment, + the &Clone; method takes &consvar; assignments, + which will override the values in the copied construction environment. + For example, suppose we want to use &gcc; + to create three versions of a program, + one optimized, one debug, and one with neither. + We could do this by creating a "base" construction environment + that sets &cv-link-CC; to &gcc;, + and then creating two copies, + one which sets &cv-link-CCFLAGS; for optimization + and the other which sets &cv-CCFLAGS; for debugging: + + + + + + env = Environment(CC = 'gcc') + opt = env.Clone(CCFLAGS = '-O2') + dbg = env.Clone(CCFLAGS = '-g') + + env.Program('foo', 'foo.c') + + o = opt.Object('foo-opt', 'foo.c') + opt.Program(o) + + d = dbg.Object('foo-dbg', 'foo.c') + dbg.Program(d) + + + int main() { } + + + + + + Then our output would look like: + + + + + scons -Q + + +
+ +
+ Replacing Values: the &Replace; Method + + + + You can replace existing construction variable values + using the &Replace; method: + + + + + + env = Environment(CCFLAGS = '-DDEFINE1') + env.Replace(CCFLAGS = '-DDEFINE2') + env.Program('foo.c') + + + int main() { } + + + + + + The replacing value + (-DDEFINE2 in the above example) + completely replaces the value in the + construction environment: + + + + + scons -Q + + + + + You can safely call &Replace; + for construction variables that + don't exist in the construction environment: + + + + + + env = Environment() + env.Replace(NEW_VARIABLE = 'xyzzy') + print "NEW_VARIABLE =", env['NEW_VARIABLE'] + + + + + + In this case, + the construction variable simply + gets added to the construction environment: + + + + + scons -Q + + + + + Because the variables + aren't expanded until the construction environment + is actually used to build the targets, + and because &SCons; function and method calls + are order-independent, + the last replacement "wins" + and is used to build all targets, + regardless of the order in which + the calls to Replace() are + interspersed with calls to + builder methods: + + + + + + env = Environment(CCFLAGS = '-DDEFINE1') + print "CCFLAGS =", env['CCFLAGS'] + env.Program('foo.c') + + env.Replace(CCFLAGS = '-DDEFINE2') + print "CCFLAGS =", env['CCFLAGS'] + env.Program('bar.c') + + + int main() { } + + + int main() { } + + + + + + The timing of when the replacement + actually occurs relative + to when the targets get built + becomes apparent + if we run &scons; without the -Q + option: + + + + + scons + + + + + Because the replacement occurs while + the &SConscript; files are being read, + the &cv-link-CCFLAGS; + variable has already been set to + -DDEFINE2 + by the time the &foo_o; target is built, + even though the call to the &Replace; + method does not occur until later in + the &SConscript; file. + + + +
+ +
+ Setting Values Only If They're Not Already Defined: the &SetDefault; Method + + + + Sometimes it's useful to be able to specify + that a construction variable should be + set to a value only if the construction environment + does not already have that variable defined + You can do this with the &SetDefault; method, + which behaves similarly to the set_default + method of Python dictionary objects: + + + + + env.SetDefault(SPECIAL_FLAG = '-extra-option') + + + + + This is especially useful + when writing your own Tool modules + to apply variables to construction environments. + + + + +
+ +
+ Appending to the End of Values: the &Append; Method + + + + You can append a value to + an existing construction variable + using the &Append; method: + + + + + + env = Environment(CCFLAGS = ['-DMY_VALUE']) + env.Append(CCFLAGS = ['-DLAST']) + env.Program('foo.c') + + + int main() { } + + + + + + &SCons; then supplies both the -DMY_VALUE and + -DLAST flags when compiling the object file: + + + + + scons -Q + + + + + If the construction variable doesn't already exist, + the &Append; method will create it: + + + + + + env = Environment() + env.Append(NEW_VARIABLE = 'added') + print "NEW_VARIABLE =", env['NEW_VARIABLE'] + + + + + + Which yields: + + + + + scons -Q + + + + + Note that the &Append; function tries to be "smart" + about how the new value is appended to the old value. + If both are strings, the previous and new strings + are simply concatenated. + Similarly, if both are lists, + the lists are concatenated. + If, however, one is a string and the other is a list, + the string is added as a new element to the list. + + + +
+ +
+ Appending Unique Values: the &AppendUnique; Method + + + + Some times it's useful to add a new value + only if the existing construction variable + doesn't already contain the value. + This can be done using the &AppendUnique; method: + + + + + env.AppendUnique(CCFLAGS=['-g']) + + + + + In the above example, + the -g would be added + only if the &cv-CCFLAGS; variable + does not already contain a -g value. + + + +
+ +
+ Appending to the Beginning of Values: the &Prepend; Method + + + + You can append a value to the beginning of + an existing construction variable + using the &Prepend; method: + + + + + + env = Environment(CCFLAGS = ['-DMY_VALUE']) + env.Prepend(CCFLAGS = ['-DFIRST']) + env.Program('foo.c') + + + int main() { } + + + + + + &SCons; then supplies both the -DFIRST and + -DMY_VALUE flags when compiling the object file: + + + + + scons -Q + + + + + If the construction variable doesn't already exist, + the &Prepend; method will create it: + + + + + + env = Environment() + env.Prepend(NEW_VARIABLE = 'added') + print "NEW_VARIABLE =", env['NEW_VARIABLE'] + + + + + + Which yields: + + + + + scons -Q + + + + + Like the &Append; function, + the &Prepend; function tries to be "smart" + about how the new value is appended to the old value. + If both are strings, the previous and new strings + are simply concatenated. + Similarly, if both are lists, + the lists are concatenated. + If, however, one is a string and the other is a list, + the string is added as a new element to the list. + + + +
+ +
+ Prepending Unique Values: the &PrependUnique; Method + + + + Some times it's useful to add a new value + to the beginning of a construction variable + only if the existing value + doesn't already contain the to-be-added value. + This can be done using the &PrependUnique; method: + + + + + env.PrependUnique(CCFLAGS=['-g']) + + + + + In the above example, + the -g would be added + only if the &cv-CCFLAGS; variable + does not already contain a -g value. + + + +
+ +
+ +
+ Controlling the Execution Environment for Issued Commands + + + + When &SCons; builds a target file, + it does not execute the commands with + the same external environment + that you used to execute &SCons;. + Instead, it uses the dictionary + stored in the &cv-link-ENV; construction variable + as the external environment + for executing commands. + + + + + + The most important ramification of this behavior + is that the &PATH; environment variable, + which controls where the operating system + will look for commands and utilities, + is not the same as in the external environment + from which you called &SCons;. + This means that &SCons; will not, by default, + necessarily find all of the tools + that you can execute from the command line. + + + + + + The default value of the &PATH; environment variable + on a POSIX system + is /usr/local/bin:/bin:/usr/bin. + The default value of the &PATH; environment variable + on a Windows system comes from the Windows registry + value for the command interpreter. + If you want to execute any commands--compilers, linkers, etc.--that + are not in these default locations, + you need to set the &PATH; value + in the &cv-ENV; dictionary + in your construction environment. + + + + + + The simplest way to do this is to initialize explicitly + the value when you create the construction environment; + this is one way to do that: + + + + + path = ['/usr/local/bin', '/bin', '/usr/bin'] + env = Environment(ENV = {'PATH' : path}) + + + + + Assign a dictionary to the &cv-ENV; + construction variable in this way + completely resets the external environment + so that the only variable that will be + set when external commands are executed + will be the &PATH; value. + If you want to use the rest of + the values in &cv-ENV; and only + set the value of &PATH;, + the most straightforward way is probably: + + + + + env['ENV']['PATH'] = ['/usr/local/bin', '/bin', '/usr/bin'] + + + + + Note that &SCons; does allow you to define + the directories in the &PATH; in a string, + separated by the pathname-separator character + for your system (':' on POSIX systems, ';' on Windows): + + + + + env['ENV']['PATH'] = '/usr/local/bin:/bin:/usr/bin' + + + + + But doing so makes your &SConscript; file less portable, + (although in this case that may not be a huge concern + since the directories you list are likley system-specific, anyway). + + + + + +
+ Propagating &PATH; From the External Environment + + + + You may want to propagate the external &PATH; + to the execution environment for commands. + You do this by initializing the &PATH; + variable with the &PATH; value from + the os.environ + dictionary, + which is Python's way of letting you + get at the external environment: + + + + + import os + env = Environment(ENV = {'PATH' : os.environ['PATH']}) + + + + + Alternatively, you may find it easier + to just propagate the entire external + environment to the execution environment + for commands. + This is simpler to code than explicity + selecting the &PATH; value: + + + + + import os + env = Environment(ENV = os.environ) + + + + + Either of these will guarantee that + &SCons; will be able to execute + any command that you can execute from the command line. + The drawback is that the build can behave + differently if it's run by people with + different &PATH; values in their environment--for example, + if both the /bin and + /usr/local/bin directories + have different &cc; commands, + then which one will be used to compile programs + will depend on which directory is listed + first in the user's &PATH; variable. + + + +
+ +
+ Adding to <varname>PATH</varname> Values in the Execution Environment + + + + One of the most common requirements + for manipulating a variable in the execution environment + is to add one or more custom directories to a search + like the $PATH variable on Linux or POSIX systems, + or the %PATH% variable on Windows, + so that a locally-installed compiler or other utility + can be found when &SCons; tries to execute it to update a target. + &SCons; provides &PrependENVPath; and &AppendENVPath; functions + to make adding things to execution variables convenient. + You call these functions by specifying the variable + to which you want the value added, + and then value itself. + So to add some /usr/local directories + to the $PATH and $LIB variables, + you might: + + + + + env = Environment(ENV = os.environ) + env.PrependENVPath('PATH', '/usr/local/bin') + env.AppendENVPath('LIB', '/usr/local/lib') + + + + + Note that the added values are strings, + and if you want to add multiple directories to + a variable like $PATH, + you must include the path separate character + (: on Linux or POSIX, + ; on Windows) + in the string. + + + +
+ +
diff --git a/doc/user/environments.xml b/doc/user/environments.xml new file mode 100644 index 0000000..b9a60b9 --- /dev/null +++ b/doc/user/environments.xml @@ -0,0 +1,1684 @@ + + + + + + + An environment + is a collection of values that + can affect how a program executes. + &SCons; distinguishes between three + different types of environments + that can affect the behavior of &SCons; itself + (subject to the configuration in the &SConscript; files), + as well as the compilers and other tools it executes: + + + + + + + External Environment + + + + + The external environment + is the set of variables in the user's environment + at the time the user runs &SCons;. + These variables are available within the &SConscript; files + through the Python os.environ dictionary. + See , below. + + + + + + + &ConsEnv; + + + + + A &consenv; + is a distinct object creating within + a &SConscript; file and + and which contains values that + affect how &SCons; decides + what action to use to build a target, + and even to define which targets + should be built from which sources. + One of the most powerful features of &SCons; + is the ability to create multiple &consenvs;, + including the ability to clone a new, customized + &consenv; from an existing &consenv;. + See , below. + + + + + + + Execution Environment + + + + + An execution environment + is the values that &SCons; sets + when executing an external + command (such as a compiler or linker) + to build one or more targets. + Note that this is not the same as + the external environment + (see above). + See , below. + + + + + + + + + + Unlike &Make;, &SCons; does not automatically + copy or import values between different environments + (with the exception of explicit clones of &consenvs;, + which inherit values from their parent). + This is a deliberate design choice + to make sure that builds are, + by default, repeatable regardless of + the values in the user's external environment. + This avoids a whole class of problems with builds + where a developer's local build works + because a custom variable setting + causes a different compiler or build option to be used, + but the checked-in change breaks the official build + because it uses different environment variable settings. + + + + + + Note that the &SConscript; writer can + easily arrange for variables to be + copied or imported between environments, + and this is often very useful + (or even downright necessary) + to make it easy for developers + to customize the build in appropriate ways. + The point is not + that copying variables between different environments + is evil and must always be avoided. + Instead, it should be up to the + implementer of the build system + to make conscious choices + about how and when to import + a variable from one environment to another, + making informed decisions about + striking the right balance + between making the build + repeatable on the one hand + and convenient to use on the other. + + + +
+ Using Values From the External Environment + + + + The external environment + variable settings that + the user has in force + when executing &SCons; + are available through the normal Python + os.environ + dictionary. + This means that you must add an + import os statement + to any &SConscript; file + in which you want to use + values from the user's external environment. + + + + + import os + + + + + More usefully, you can use the + os.environ + dictionary in your &SConscript; + files to initialize &consenvs; + with values from the user's external environment. + See the next section, + , + for information on how to do this. + + + +
+ +
+ Construction Environments + + + + It is rare that all of the software in a large, + complicated system needs to be built the same way. + For example, different source files may need different options + enabled on the command line, + or different executable programs need to be linked + with different libraries. + &SCons; accommodates these different build + requirements by allowing you to create and + configure multiple &consenvs; + that control how the software is built. + A &consenv; is an object + that has a number of associated + &consvars;, each with a name and a value. + (A construction environment also has an attached + set of &Builder; methods, + about which we'll learn more later.) + + + +
+ Creating a &ConsEnv;: the &Environment; Function + + + + A &consenv; is created by the &Environment; method: + + + + + env = Environment() + + + + + By default, &SCons; initializes every + new construction environment + with a set of &consvars; + based on the tools that it finds on your system, + plus the default set of builder methods + necessary for using those tools. + The construction variables + are initialized with values describing + the C compiler, + the Fortran compiler, + the linker, + etc., + as well as the command lines to invoke them. + + + + + + When you initialize a construction environment + you can set the values of the + environment's &consvars; + to control how a program is built. + For example: + + + + + import os + + env = Environment(CC = 'gcc', + CCFLAGS = '-O2') + + env.Program('foo.c') + + + + + The construction environment in this example + is still initialized with the same default + construction variable values, + except that the user has explicitly specified use of the + GNU C compiler &gcc;, + and further specifies that the -O2 + (optimization level two) + flag should be used when compiling the object file. + In other words, the explicit initializations of + &cv-link-CC; and &cv-link-CCFLAGS; + override the default values in the newly-created + construction environment. + So a run from this example would look like: + + + + + % scons -Q + gcc -o foo.o -c -O2 foo.c + gcc -o foo foo.o + + +
+ +
+ Fetching Values From a &ConsEnv; + + + + You can fetch individual construction variables + using the normal syntax + for accessing individual named items in a Python dictionary: + + + + + env = Environment() + print "CC is:", env['CC'] + + + + + This example &SConstruct; file doesn't build anything, + but because it's actually a Python script, + it will print the value of &cv-link-CC; for us: + + + + + % scons -Q + CC is: cc + scons: `.' is up to date. + + + + + A construction environment, however, + is actually an object with associated methods, etc. + If you want to have direct access to only the + dictionary of construction variables, + you can fetch this using the &Dictionary; method: + + + + + env = Environment(FOO = 'foo', BAR = 'bar') + dict = env.Dictionary() + for key in ['OBJSUFFIX', 'LIBSUFFIX', 'PROGSUFFIX']: + print "key = %s, value = %s" % (key, dict[key]) + + + + + This &SConstruct; file + will print the specified dictionary items for us on POSIX + systems as follows: + + + + + % scons -Q + key = OBJSUFFIX, value = .o + key = LIBSUFFIX, value = .a + key = PROGSUFFIX, value = + scons: `.' is up to date. + + + + + And on Windows: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + key = OBJSUFFIX, value = .obj + key = LIBSUFFIX, value = .lib + key = PROGSUFFIX, value = .exe + scons: `.' is up to date. + + + + + If you want to loop and print the values of + all of the construction variables in a construction environment, + the Python code to do that in sorted order might look something like: + + + + + env = Environment() + dict = env.Dictionary() + keys = dict.keys() + keys.sort() + for key in keys: + print "construction variable = '%s', value = '%s'" % (key, dict[key]) + + +
+ +
+ Expanding Values From a &ConsEnv;: the &subst; Method + + + + Another way to get information from + a construction environment. + is to use the &subst; method + on a string containing $ expansions + of construction variable names. + As a simple example, + the example from the previous + section that used + env['CC'] + to fetch the value of &cv-link-CC; + could also be written as: + + + + + env = Environment() + print "CC is:", env.subst('$CC') + + + + + One advantage of using + &subst; to expand strings is + that construction variables + in the result get re-expanded until + there are no expansions left in the string. + So a simple fetch of a value like + &cv-link-CCCOM;: + + + + + env = Environment(CCFLAGS = '-DFOO') + print "CCCOM is:", env['CCCOM'] + + + + + Will print the unexpanded value of &cv-CCCOM;, + showing us the construction + variables that still need to be expanded: + + + + + % scons -Q + CCCOM is: $CC $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES + scons: `.' is up to date. + + + + + Calling the &subst; method on $CCOM, + however: + + + + + env = Environment(CCFLAGS = '-DFOO') + print "CCCOM is:", env.subst('$CCCOM') + + + + + Will recursively expand all of + the construction variables prefixed + with $ (dollar signs), + showing us the final output: + + + + + % scons -Q + CCCOM is: gcc -DFOO -c -o + scons: `.' is up to date. + + + + + Note that because we're not expanding this + in the context of building something + there are no target or source files + for &cv-link-TARGET; and &cv-link-SOURCES; to expand. + + + +
+ +
+ Controlling the Default &ConsEnv;: the &DefaultEnvironment; Function + + + + All of the &Builder; functions that we've introduced so far, + like &Program; and &Library;, + actually use a default &consenv; + that contains settings + for the various compilers + and other tools that + &SCons; configures by default, + or otherwise knows about + and has discovered on your system. + The goal of the default construction environment + is to make many configurations to "just work" + to build software using + readily available tools + with a minimum of configuration changes. + + + + + + You can, however, control the settings + in the default contstruction environment + by using the &DefaultEnvironment; function + to initialize various settings: + + + + + + DefaultEnvironment(CC = '/usr/local/bin/gcc') + + + + + + When configured as above, + all calls to the &Program; + or &Object; Builder + will build object files with the + /usr/local/bin/gcc + compiler. + + + + + + Note that the &DefaultEnvironment; function + returns the initialized + default construction environment object, + which can then be manipulated like any + other construction environment. + So the following + would be equivalent to the + previous example, + setting the &cv-CC; + variable to /usr/local/bin/gcc + but as a separate step after + the default construction environment has been initialized: + + + + + + env = DefaultEnvironment() + env['CC'] = '/usr/local/bin/gcc' + + + + + + One very common use of the &DefaultEnvironment; function + is to speed up &SCons; initialization. + As part of trying to make most default + configurations "just work," + &SCons; will actually + search the local system for installed + compilers and other utilities. + This search can take time, + especially on systems with + slow or networked file systems. + If you know which compiler(s) and/or + other utilities you want to configure, + you can control the search + that &SCons; performs + by specifying some specific + tool modules with which to + initialize the default construction environment: + + + + + + env = DefaultEnvironment(tools = ['gcc', 'gnulink'], + CC = '/usr/local/bin/gcc') + + + + + + So the above example would tell &SCons; + to explicitly configure the default environment + to use its normal GNU Compiler and GNU Linker settings + (without having to search for them, + or any other utilities for that matter), + and specifically to use the compiler found at + /usr/local/bin/gcc. + + + +
+ +
+ Multiple &ConsEnvs; + + + + The real advantage of construction environments + is that you can create as many different construction + environments as you need, + each tailored to a different way to build + some piece of software or other file. + If, for example, we need to build + one program with the -O2 flag + and another with the -g (debug) flag, + we would do this like so: + + + + + opt = Environment(CCFLAGS = '-O2') + dbg = Environment(CCFLAGS = '-g') + + opt.Program('foo', 'foo.c') + + dbg.Program('bar', 'bar.c') + + + + % scons -Q + cc -o bar.o -c -g bar.c + cc -o bar bar.o + cc -o foo.o -c -O2 foo.c + cc -o foo foo.o + + + + + We can even use multiple construction environments to build + multiple versions of a single program. + If you do this by simply trying to use the + &b-link-Program; builder with both environments, though, + like this: + + + + + opt = Environment(CCFLAGS = '-O2') + dbg = Environment(CCFLAGS = '-g') + + opt.Program('foo', 'foo.c') + + dbg.Program('foo', 'foo.c') + + + + + Then &SCons; generates the following error: + + + + + % scons -Q + + scons: warning: Two different environments were specified for target foo.o, + but they appear to have the same action: CCCom(target, source, env) + File "/home/my/project/SConstruct", line 6, in ? + + scons: warning: Two different environments were specified for target foo, + but they appear to have the same action: Cat(target, source, env) + File "/home/my/project/SConstruct", line 6, in ? + cc -o foo.o -c -g foo.c + cc -o foo foo.o + + + + + This is because the two &b-Program; calls have + each implicitly told &SCons; to generate an object file named + foo.o, + one with a &cv-link-CCFLAGS; value of + -O2 + and one with a &cv-link-CCFLAGS; value of + -g. + &SCons; can't just decide that one of them + should take precedence over the other, + so it generates the error. + To avoid this problem, + we must explicitly specify + that each environment compile + foo.c + to a separately-named object file + using the &b-link-Object; builder, like so: + + + + + opt = Environment(CCFLAGS = '-O2') + dbg = Environment(CCFLAGS = '-g') + + o = opt.Object('foo-opt', 'foo.c') + opt.Program(o) + + d = dbg.Object('foo-dbg', 'foo.c') + dbg.Program(d) + + + + + Notice that each call to the &b-Object; builder + returns a value, + an internal &SCons; object that + represents the object file that will be built. + We then use that object + as input to the &b-Program; builder. + This avoids having to specify explicitly + the object file name in multiple places, + and makes for a compact, readable + &SConstruct; file. + Our &SCons; output then looks like: + + + + + % scons -Q + cc -o foo-dbg.o -c -g foo.c + cc -o foo-dbg foo-dbg.o + cc -o foo-opt.o -c -O2 foo.c + cc -o foo-opt foo-opt.o + + +
+ +
+ Making Copies of &ConsEnvs;: the &Clone; Method + + + + Sometimes you want more than one construction environment + to share the same values for one or more variables. + Rather than always having to repeat all of the common + variables when you create each construction environment, + you can use the &Clone; method + to create a copy of a construction environment. + + + + + + Like the &Environment; call that creates a construction environment, + the &Clone; method takes &consvar; assignments, + which will override the values in the copied construction environment. + For example, suppose we want to use &gcc; + to create three versions of a program, + one optimized, one debug, and one with neither. + We could do this by creating a "base" construction environment + that sets &cv-link-CC; to &gcc;, + and then creating two copies, + one which sets &cv-link-CCFLAGS; for optimization + and the other which sets &cv-CCFLAGS; for debugging: + + + + + env = Environment(CC = 'gcc') + opt = env.Clone(CCFLAGS = '-O2') + dbg = env.Clone(CCFLAGS = '-g') + + env.Program('foo', 'foo.c') + + o = opt.Object('foo-opt', 'foo.c') + opt.Program(o) + + d = dbg.Object('foo-dbg', 'foo.c') + dbg.Program(d) + + + + + Then our output would look like: + + + + + % scons -Q + gcc -o foo.o -c foo.c + gcc -o foo foo.o + gcc -o foo-dbg.o -c -g foo.c + gcc -o foo-dbg foo-dbg.o + gcc -o foo-opt.o -c -O2 foo.c + gcc -o foo-opt foo-opt.o + + +
+ +
+ Replacing Values: the &Replace; Method + + + + You can replace existing construction variable values + using the &Replace; method: + + + + + env = Environment(CCFLAGS = '-DDEFINE1') + env.Replace(CCFLAGS = '-DDEFINE2') + env.Program('foo.c') + + + + + The replacing value + (-DDEFINE2 in the above example) + completely replaces the value in the + construction environment: + + + + + % scons -Q + cc -o foo.o -c -DDEFINE2 foo.c + cc -o foo foo.o + + + + + You can safely call &Replace; + for construction variables that + don't exist in the construction environment: + + + + + env = Environment() + env.Replace(NEW_VARIABLE = 'xyzzy') + print "NEW_VARIABLE =", env['NEW_VARIABLE'] + + + + + In this case, + the construction variable simply + gets added to the construction environment: + + + + + % scons -Q + NEW_VARIABLE = xyzzy + scons: `.' is up to date. + + + + + Because the variables + aren't expanded until the construction environment + is actually used to build the targets, + and because &SCons; function and method calls + are order-independent, + the last replacement "wins" + and is used to build all targets, + regardless of the order in which + the calls to Replace() are + interspersed with calls to + builder methods: + + + + + env = Environment(CCFLAGS = '-DDEFINE1') + print "CCFLAGS =", env['CCFLAGS'] + env.Program('foo.c') + + env.Replace(CCFLAGS = '-DDEFINE2') + print "CCFLAGS =", env['CCFLAGS'] + env.Program('bar.c') + + + + + The timing of when the replacement + actually occurs relative + to when the targets get built + becomes apparent + if we run &scons; without the -Q + option: + + + + + % scons + scons: Reading SConscript files ... + CCFLAGS = -DDEFINE1 + CCFLAGS = -DDEFINE2 + scons: done reading SConscript files. + scons: Building targets ... + cc -o bar.o -c -DDEFINE2 bar.c + cc -o bar bar.o + cc -o foo.o -c -DDEFINE2 foo.c + cc -o foo foo.o + scons: done building targets. + + + + + Because the replacement occurs while + the &SConscript; files are being read, + the &cv-link-CCFLAGS; + variable has already been set to + -DDEFINE2 + by the time the &foo_o; target is built, + even though the call to the &Replace; + method does not occur until later in + the &SConscript; file. + + + +
+ +
+ Setting Values Only If They're Not Already Defined: the &SetDefault; Method + + + + Sometimes it's useful to be able to specify + that a construction variable should be + set to a value only if the construction environment + does not already have that variable defined + You can do this with the &SetDefault; method, + which behaves similarly to the set_default + method of Python dictionary objects: + + + + + env.SetDefault(SPECIAL_FLAG = '-extra-option') + + + + + This is especially useful + when writing your own Tool modules + to apply variables to construction environments. + + + + +
+ +
+ Appending to the End of Values: the &Append; Method + + + + You can append a value to + an existing construction variable + using the &Append; method: + + + + + env = Environment(CCFLAGS = ['-DMY_VALUE']) + env.Append(CCFLAGS = ['-DLAST']) + env.Program('foo.c') + + + + + &SCons; then supplies both the -DMY_VALUE and + -DLAST flags when compiling the object file: + + + + + % scons -Q + cc -o foo.o -c -DMY_VALUE -DLAST foo.c + cc -o foo foo.o + + + + + If the construction variable doesn't already exist, + the &Append; method will create it: + + + + + env = Environment() + env.Append(NEW_VARIABLE = 'added') + print "NEW_VARIABLE =", env['NEW_VARIABLE'] + + + + + Which yields: + + + + + % scons -Q + NEW_VARIABLE = added + scons: `.' is up to date. + + + + + Note that the &Append; function tries to be "smart" + about how the new value is appended to the old value. + If both are strings, the previous and new strings + are simply concatenated. + Similarly, if both are lists, + the lists are concatenated. + If, however, one is a string and the other is a list, + the string is added as a new element to the list. + + + +
+ +
+ Appending Unique Values: the &AppendUnique; Method + + + + Some times it's useful to add a new value + only if the existing construction variable + doesn't already contain the value. + This can be done using the &AppendUnique; method: + + + + + env.AppendUnique(CCFLAGS=['-g']) + + + + + In the above example, + the -g would be added + only if the &cv-CCFLAGS; variable + does not already contain a -g value. + + + +
+ +
+ Appending to the Beginning of Values: the &Prepend; Method + + + + You can append a value to the beginning of + an existing construction variable + using the &Prepend; method: + + + + + env = Environment(CCFLAGS = ['-DMY_VALUE']) + env.Prepend(CCFLAGS = ['-DFIRST']) + env.Program('foo.c') + + + + + &SCons; then supplies both the -DFIRST and + -DMY_VALUE flags when compiling the object file: + + + + + % scons -Q + cc -o foo.o -c -DFIRST -DMY_VALUE foo.c + cc -o foo foo.o + + + + + If the construction variable doesn't already exist, + the &Prepend; method will create it: + + + + + env = Environment() + env.Prepend(NEW_VARIABLE = 'added') + print "NEW_VARIABLE =", env['NEW_VARIABLE'] + + + + + Which yields: + + + + + % scons -Q + NEW_VARIABLE = added + scons: `.' is up to date. + + + + + Like the &Append; function, + the &Prepend; function tries to be "smart" + about how the new value is appended to the old value. + If both are strings, the previous and new strings + are simply concatenated. + Similarly, if both are lists, + the lists are concatenated. + If, however, one is a string and the other is a list, + the string is added as a new element to the list. + + + +
+ +
+ Prepending Unique Values: the &PrependUnique; Method + + + + Some times it's useful to add a new value + to the beginning of a construction variable + only if the existing value + doesn't already contain the to-be-added value. + This can be done using the &PrependUnique; method: + + + + + env.PrependUnique(CCFLAGS=['-g']) + + + + + In the above example, + the -g would be added + only if the &cv-CCFLAGS; variable + does not already contain a -g value. + + + +
+ +
+ +
+ Controlling the Execution Environment for Issued Commands + + + + When &SCons; builds a target file, + it does not execute the commands with + the same external environment + that you used to execute &SCons;. + Instead, it uses the dictionary + stored in the &cv-link-ENV; construction variable + as the external environment + for executing commands. + + + + + + The most important ramification of this behavior + is that the &PATH; environment variable, + which controls where the operating system + will look for commands and utilities, + is not the same as in the external environment + from which you called &SCons;. + This means that &SCons; will not, by default, + necessarily find all of the tools + that you can execute from the command line. + + + + + + The default value of the &PATH; environment variable + on a POSIX system + is /usr/local/bin:/bin:/usr/bin. + The default value of the &PATH; environment variable + on a Windows system comes from the Windows registry + value for the command interpreter. + If you want to execute any commands--compilers, linkers, etc.--that + are not in these default locations, + you need to set the &PATH; value + in the &cv-ENV; dictionary + in your construction environment. + + + + + + The simplest way to do this is to initialize explicitly + the value when you create the construction environment; + this is one way to do that: + + + + + path = ['/usr/local/bin', '/bin', '/usr/bin'] + env = Environment(ENV = {'PATH' : path}) + + + + + Assign a dictionary to the &cv-ENV; + construction variable in this way + completely resets the external environment + so that the only variable that will be + set when external commands are executed + will be the &PATH; value. + If you want to use the rest of + the values in &cv-ENV; and only + set the value of &PATH;, + the most straightforward way is probably: + + + + + env['ENV']['PATH'] = ['/usr/local/bin', '/bin', '/usr/bin'] + + + + + Note that &SCons; does allow you to define + the directories in the &PATH; in a string, + separated by the pathname-separator character + for your system (':' on POSIX systems, ';' on Windows): + + + + + env['ENV']['PATH'] = '/usr/local/bin:/bin:/usr/bin' + + + + + But doing so makes your &SConscript; file less portable, + (although in this case that may not be a huge concern + since the directories you list are likley system-specific, anyway). + + + + + +
+ Propagating &PATH; From the External Environment + + + + You may want to propagate the external &PATH; + to the execution environment for commands. + You do this by initializing the &PATH; + variable with the &PATH; value from + the os.environ + dictionary, + which is Python's way of letting you + get at the external environment: + + + + + import os + env = Environment(ENV = {'PATH' : os.environ['PATH']}) + + + + + Alternatively, you may find it easier + to just propagate the entire external + environment to the execution environment + for commands. + This is simpler to code than explicity + selecting the &PATH; value: + + + + + import os + env = Environment(ENV = os.environ) + + + + + Either of these will guarantee that + &SCons; will be able to execute + any command that you can execute from the command line. + The drawback is that the build can behave + differently if it's run by people with + different &PATH; values in their environment--for example, + if both the /bin and + /usr/local/bin directories + have different &cc; commands, + then which one will be used to compile programs + will depend on which directory is listed + first in the user's &PATH; variable. + + + +
+ +
+ Adding to <varname>PATH</varname> Values in the Execution Environment + + + + One of the most common requirements + for manipulating a variable in the execution environment + is to add one or more custom directories to a search + like the $PATH variable on Linux or POSIX systems, + or the %PATH% variable on Windows, + so that a locally-installed compiler or other utility + can be found when &SCons; tries to execute it to update a target. + &SCons; provides &PrependENVPath; and &AppendENVPath; functions + to make adding things to execution variables convenient. + You call these functions by specifying the variable + to which you want the value added, + and then value itself. + So to add some /usr/local directories + to the $PATH and $LIB variables, + you might: + + + + + env = Environment(ENV = os.environ) + env.PrependENVPath('PATH', '/usr/local/bin') + env.AppendENVPath('LIB', '/usr/local/lib') + + + + + Note that the added values are strings, + and if you want to add multiple directories to + a variable like $PATH, + you must include the path separate character + (: on Linux or POSIX, + ; on Windows) + in the string. + + + +
+ +
diff --git a/doc/user/errors.in b/doc/user/errors.in new file mode 100644 index 0000000..0051953 --- /dev/null +++ b/doc/user/errors.in @@ -0,0 +1,41 @@ + + + + + XXX + + + +
+ XXX + + + + XXX + + + +
diff --git a/doc/user/errors.xml b/doc/user/errors.xml new file mode 100644 index 0000000..0051953 --- /dev/null +++ b/doc/user/errors.xml @@ -0,0 +1,41 @@ + + + + + XXX + + + +
+ XXX + + + + XXX + + + +
diff --git a/doc/user/example.in b/doc/user/example.in new file mode 100644 index 0000000..0051953 --- /dev/null +++ b/doc/user/example.in @@ -0,0 +1,41 @@ + + + + + XXX + + + +
+ XXX + + + + XXX + + + +
diff --git a/doc/user/example.xml b/doc/user/example.xml new file mode 100644 index 0000000..0051953 --- /dev/null +++ b/doc/user/example.xml @@ -0,0 +1,41 @@ + + + + + XXX + + + +
+ XXX + + + + XXX + + + +
diff --git a/doc/user/factories.in b/doc/user/factories.in new file mode 100644 index 0000000..0cea6c4 --- /dev/null +++ b/doc/user/factories.in @@ -0,0 +1,507 @@ + + + + + &SCons; provides a number of platform-independent functions, + called factories, + that perform common file system manipulations + like copying, moving or deleting files and directories, + or making directories. + These functions are factories + because they don't perform the action + at the time they're called, + they each return an &Action; object + that can be executed at the appropriate time. + + + +
+ Copying Files or Directories: The &Copy; Factory + + + + Suppose you want to arrange to make a copy of a file, + and don't have a suitable pre-existing builder. + + + Unfortunately, in the early days of SCons design, + we used the name &Copy; for the function that + returns a copy of the environment, + otherwise that would be the logical choice for + a Builder that copies a file or directory tree + to a target location. + + + One way would be to use the &Copy; action factory + in conjunction with the &Command; builder: + + + + + + Command("file.out", "file.in", Copy("$TARGET", "$SOURCE")) + + file.in + + + + + Notice that the action returned by the &Copy; factory + will expand the &cv-link-TARGET; and &cv-link-SOURCE; strings + at the time &file_out; is built, + and that the order of the arguments + is the same as that of a builder itself--that is, + target first, followed by source: + + + + + scons -Q + + + + + You can, of course, name a file explicitly + instead of using &cv-TARGET; or &cv-SOURCE;: + + + + + + Command("file.out", [], Copy("$TARGET", "file.in")) + + file.in + + + + + Which executes as: + + + + + scons -Q + + + + + The usefulness of the &Copy; factory + becomes more apparent when + you use it in a list of actions + passed to the &Command; builder. + For example, suppose you needed to run a + file through a utility that only modifies files in-place, + and can't "pipe" input to output. + One solution is to copy the source file + to a temporary file name, + run the utility, + and then copy the modified temporary file to the target, + which the &Copy; factory makes extremely easy: + + + + + + Command("file.out", "file.in", + [ + Copy("tempfile", "$SOURCE"), + "modify tempfile", + Copy("$TARGET", "tempfile"), + ]) + + + env = DefaultEnvironment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + SConscript('S') + + file.in + + touch $* + + + + + + The output then looks like: + + + + + scons -Q + + +
+ +
+ Deleting Files or Directories: The &Delete; Factory + + + + If you need to delete a file, + then the &Delete; factory + can be used in much the same way as + the &Copy; factory. + For example, if we want to make sure that + the temporary file + in our last example doesn't exist before + we copy to it, + we could add &Delete; to the beginning + of the command list: + + + + + + Command("file.out", "file.in", + [ + Delete("tempfile"), + Copy("tempfile", "$SOURCE"), + "modify tempfile", + Copy("$TARGET", "tempfile"), + ]) + + + env = DefaultEnvironment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + SConscript('S') + + file.in + + touch $* + + + + + + Which then executes as follows: + + + + + scons -Q + + + + + Of course, like all of these &Action; factories, + the &Delete factory also expands + &cv-link-TARGET; and &cv-link-SOURCE; variables appropriately. + For example: + + + + + + Command("file.out", "file.in", + [ + Delete("$TARGET"), + Copy("$TARGET", "$SOURCE") + ]) + + file.in + + + + + Executes as: + + + + + scons -Q + + + + + Note, however, that you typically don't need to + call the &Delete; factory explicitly in this way; + by default, &SCons; deletes its target(s) + for you before executing any action. + + + + + + One word of caution about using the &Delete; factory: + it has the same variable expansions available + as any other factory, including the &cv-SOURCE; variable. + Specifying Delete("$SOURCE") + is not something you usually want to do! + + + +
+ +
+ Moving (Renaming) Files or Directories: The &Move; Factory + + + + The &Move; factory + allows you to rename a file or directory. + For example, if we don't want to copy the temporary file, + we could use: + + + + + + Command("file.out", "file.in", + [ + Copy("tempfile", "$SOURCE"), + "modify tempfile", + Move("$TARGET", "tempfile"), + ]) + + + env = DefaultEnvironment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + SConscript('S') + + file.in + + touch $* + + + + + + Which would execute as: + + + + + scons -Q + + +
+ +
+ Updating the Modification Time of a File: The &Touch; Factory + + + + If you just need to update the + recorded modification time for a file, + use the &Touch; factory: + + + + + + Command("file.out", "file.in", + [ + Copy("$TARGET", "$SOURCE"), + Touch("$TARGET"), + ]) + + + env = DefaultEnvironment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + SConscript('S') + + file.in + + + + + Which executes as: + + + + + scons -Q + + +
+ +
+ Creating a Directory: The &Mkdir; Factory + + + + If you need to create a directory, + use the &Mkdir; factory. + For example, if we need to process + a file in a temporary directory + in which the processing tool + will create other files that we don't care about, + you could use: + + + + + + Command("file.out", "file.in", + [ + Delete("tempdir"), + Mkdir("tempdir"), + Copy("tempdir/${SOURCE.file}", "$SOURCE"), + "process tempdir", + Move("$TARGET", "tempdir/output_file"), + Delete("tempdir"), + ]) + + + env = DefaultEnvironment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + SConscript('S') + + file.in + + touch $* + + + + + + Which executes as: + + + + + scons -Q + + +
+ +
+ Changing File or Directory Permissions: The &Chmod; Factory + + + + To change permissions on a file or directory, + use the &Chmod; factory. + The permission argument uses POSIX-style + permission bits and should typically + be expressed as an octal, + not decimal, number: + + + + + + Command("file.out", "file.in", + [ + Copy("$TARGET", "$SOURCE"), + Chmod("$TARGET", 0755), + ]) + + file.in + + + + + Which executes: + + + + + scons -Q + + +
+ +
+ Executing an action immediately: the &Execute; Function + + + + We've been showing you how to use &Action; factories + in the &Command; function. + You can also execute an &Action; returned by a factory + (or actually, any &Action;) + at the time the &SConscript; file is read + by using the &Execute; function. + For example, if we need to make sure that + a directory exists before we build any targets, + + + + + + Execute(Mkdir('__ROOT__/tmp/my_temp_directory')) + + + + + + Notice that this will + create the directory while + the &SConscript; file is being read: + + + + + scons + + + + + If you're familiar with Python, + you may wonder why you would want to use this + instead of just calling the native Python + os.mkdir() function. + The advantage here is that the &Mkdir; + action will behave appropriately if the user + specifies the &SCons; or + options--that is, + it will print the action but not actually + make the directory when is specified, + or make the directory but not print the action + when is specified. + + + + + + The &Execute; function returns the exit status + or return value of the underlying action being executed. + It will also print an error message if the action + fails and returns a non-zero value. + &SCons; will not, however, + actually stop the build if the action fails. + If you want the build to stop + in response to a failure in an action called by &Execute;, + you must do so by explicitly + checking the return value + and calling the &Exit; function + (or a Python equivalent): + + + + + if Execute(Mkdir('__ROOT__/tmp/my_temp_directory')): + # A problem occurred while making the temp directory. + Exit(1) + + +
diff --git a/doc/user/factories.xml b/doc/user/factories.xml new file mode 100644 index 0000000..17e52bd --- /dev/null +++ b/doc/user/factories.xml @@ -0,0 +1,466 @@ + + + + + &SCons; provides a number of platform-independent functions, + called factories, + that perform common file system manipulations + like copying, moving or deleting files and directories, + or making directories. + These functions are factories + because they don't perform the action + at the time they're called, + they each return an &Action; object + that can be executed at the appropriate time. + + + +
+ Copying Files or Directories: The &Copy; Factory + + + + Suppose you want to arrange to make a copy of a file, + and don't have a suitable pre-existing builder. + + + Unfortunately, in the early days of SCons design, + we used the name &Copy; for the function that + returns a copy of the environment, + otherwise that would be the logical choice for + a Builder that copies a file or directory tree + to a target location. + + + One way would be to use the &Copy; action factory + in conjunction with the &Command; builder: + + + + + Command("file.out", "file.in", Copy("$TARGET", "$SOURCE")) + + + + + Notice that the action returned by the &Copy; factory + will expand the &cv-link-TARGET; and &cv-link-SOURCE; strings + at the time &file_out; is built, + and that the order of the arguments + is the same as that of a builder itself--that is, + target first, followed by source: + + + + + % scons -Q + Copy("file.out", "file.in") + + + + + You can, of course, name a file explicitly + instead of using &cv-TARGET; or &cv-SOURCE;: + + + + + Command("file.out", [], Copy("$TARGET", "file.in")) + + + + + Which executes as: + + + + + % scons -Q + Copy("file.out", "file.in") + + + + + The usefulness of the &Copy; factory + becomes more apparent when + you use it in a list of actions + passed to the &Command; builder. + For example, suppose you needed to run a + file through a utility that only modifies files in-place, + and can't "pipe" input to output. + One solution is to copy the source file + to a temporary file name, + run the utility, + and then copy the modified temporary file to the target, + which the &Copy; factory makes extremely easy: + + + + + Command("file.out", "file.in", + [ + Copy("tempfile", "$SOURCE"), + "modify tempfile", + Copy("$TARGET", "tempfile"), + ]) + + + + + The output then looks like: + + + + + % scons -Q + Copy("tempfile", "file.in") + modify tempfile + Copy("file.out", "tempfile") + + +
+ +
+ Deleting Files or Directories: The &Delete; Factory + + + + If you need to delete a file, + then the &Delete; factory + can be used in much the same way as + the &Copy; factory. + For example, if we want to make sure that + the temporary file + in our last example doesn't exist before + we copy to it, + we could add &Delete; to the beginning + of the command list: + + + + + Command("file.out", "file.in", + [ + Delete("tempfile"), + Copy("tempfile", "$SOURCE"), + "modify tempfile", + Copy("$TARGET", "tempfile"), + ]) + + + + + Which then executes as follows: + + + + + % scons -Q + Delete("tempfile") + Copy("tempfile", "file.in") + modify tempfile + Copy("file.out", "tempfile") + + + + + Of course, like all of these &Action; factories, + the &Delete; factory also expands + &cv-link-TARGET; and &cv-link-SOURCE; variables appropriately. + For example: + + + + + Command("file.out", "file.in", + [ + Delete("$TARGET"), + Copy("$TARGET", "$SOURCE") + ]) + + + + + Executes as: + + + + + % scons -Q + Delete("file.out") + Copy("file.out", "file.in") + + + + + Note, however, that you typically don't need to + call the &Delete; factory explicitly in this way; + by default, &SCons; deletes its target(s) + for you before executing any action. + + + + + + One word of caution about using the &Delete; factory: + it has the same variable expansions available + as any other factory, including the &cv-SOURCE; variable. + Specifying Delete("$SOURCE") + is not something you usually want to do! + + + +
+ +
+ Moving (Renaming) Files or Directories: The &Move; Factory + + + + The &Move; factory + allows you to rename a file or directory. + For example, if we don't want to copy the temporary file, + we could use: + + + + + Command("file.out", "file.in", + [ + Copy("tempfile", "$SOURCE"), + "modify tempfile", + Move("$TARGET", "tempfile"), + ]) + + + + + Which would execute as: + + + + + % scons -Q + Copy("tempfile", "file.in") + modify tempfile + Move("file.out", "tempfile") + + +
+ +
+ Updating the Modification Time of a File: The &Touch; Factory + + + + If you just need to update the + recorded modification time for a file, + use the &Touch; factory: + + + + + Command("file.out", "file.in", + [ + Copy("$TARGET", "$SOURCE"), + Touch("$TARGET"), + ]) + + + + + Which executes as: + + + + + % scons -Q + Copy("file.out", "file.in") + Touch("file.out") + + +
+ +
+ Creating a Directory: The &Mkdir; Factory + + + + If you need to create a directory, + use the &Mkdir; factory. + For example, if we need to process + a file in a temporary directory + in which the processing tool + will create other files that we don't care about, + you could use: + + + + + Command("file.out", "file.in", + [ + Delete("tempdir"), + Mkdir("tempdir"), + Copy("tempdir/${SOURCE.file}", "$SOURCE"), + "process tempdir", + Move("$TARGET", "tempdir/output_file"), + Delete("tempdir"), + ]) + + + + + Which executes as: + + + + + % scons -Q + Delete("tempdir") + Mkdir("tempdir") + Copy("tempdir/file.in", "file.in") + process tempdir + Move("file.out", "tempdir/output_file") + scons: *** [file.out] tempdir/output_file: No such file or directory + + +
+ +
+ Changing File or Directory Permissions: The &Chmod; Factory + + + + To change permissions on a file or directory, + use the &Chmod; factory. + The permission argument uses POSIX-style + permission bits and should typically + be expressed as an octal, + not decimal, number: + + + + + Command("file.out", "file.in", + [ + Copy("$TARGET", "$SOURCE"), + Chmod("$TARGET", 0755), + ]) + + + + + Which executes: + + + + + % scons -Q + Copy("file.out", "file.in") + Chmod("file.out", 0755) + + +
+ +
+ Executing an action immediately: the &Execute; Function + + + + We've been showing you how to use &Action; factories + in the &Command; function. + You can also execute an &Action; returned by a factory + (or actually, any &Action;) + at the time the &SConscript; file is read + by using the &Execute; function. + For example, if we need to make sure that + a directory exists before we build any targets, + + + + + Execute(Mkdir('/tmp/my_temp_directory')) + + + + + Notice that this will + create the directory while + the &SConscript; file is being read: + + + + + % scons + scons: Reading SConscript files ... + Mkdir("/tmp/my_temp_directory") + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + + + + + If you're familiar with Python, + you may wonder why you would want to use this + instead of just calling the native Python + os.mkdir() function. + The advantage here is that the &Mkdir; + action will behave appropriately if the user + specifies the &SCons; or + options--that is, + it will print the action but not actually + make the directory when is specified, + or make the directory but not print the action + when is specified. + + + + + + The &Execute; function returns the exit status + or return value of the underlying action being executed. + It will also print an error message if the action + fails and returns a non-zero value. + &SCons; will not, however, + actually stop the build if the action fails. + If you want the build to stop + in response to a failure in an action called by &Execute;, + you must do so by explicitly + checking the return value + and calling the &Exit; function + (or a Python equivalent): + + + + + if Execute(Mkdir('/tmp/my_temp_directory')): + # A problem occurred while making the temp directory. + Exit(1) + + +
diff --git a/doc/user/file-removal.in b/doc/user/file-removal.in new file mode 100644 index 0000000..c26f020 --- /dev/null +++ b/doc/user/file-removal.in @@ -0,0 +1,223 @@ + + + + + There are two occasions when &SCons; will, + by default, remove target files. + The first is when &SCons; determines that + an target file needs to be rebuilt + and removes the existing version of the target + before executing + The second is when &SCons; is invoked with the + -c option to "clean" + a tree of its built targets. + + These behaviours can be suppressed with the + &Precious; and &NoClean; functions, respectively. + + + +
+ Preventing target removal during build: the &Precious; Function + + + + By default, &SCons; removes targets before building them. + Sometimes, however, this is not what you want. + For example, you may want to update a library incrementally, + not by having it deleted and then rebuilt from all + of the constituent object files. + In such cases, you can use the + &Precious; method to prevent + &SCons; from removing the target before it is built: + + + + + + env = Environment(RANLIBCOM='') + lib = env.Library('foo', ['f1.c', 'f2.c', 'f3.c']) + env.Precious(lib) + + + int f1() { } + + + int f2() { } + + + int f3() { } + + + + + + Although the output doesn't look any different, + &SCons; does not, in fact, + delete the target library before rebuilding it: + + + + + scons -Q + + + + + &SCons; will, however, still delete files marked as &Precious; + when the -c option is used. + + + +
+ +
+ Preventing target removal during clean: the &NoClean; Function + + + + By default, &SCons; removes all built targets when invoked + with the -c option to clean a source tree + of built targets. + Sometimes, however, this is not what you want. + For example, you may want to remove only intermediate generated files + (such as object files), + but leave the final targets + (the libraries) + untouched. + + In such cases, you can use the &NoClean; method to prevent &SCons; + from removing a target during a clean: + + + + + + env = Environment(RANLIBCOM='') + lib = env.Library('foo', ['f1.c', 'f2.c', 'f3.c']) + env.NoClean(lib) + + + int f1() { } + + + int f2() { } + + + int f3() { } + + + + + + Notice that the libfoo.a + is not listed as a removed file: + + + + + scons -Q + scons -c + + +
+ +
+ Removing additional files during clean: the &Clean; Function + + + + There may be additional files that you want removed + when the -c option is used, + but which &SCons; doesn't know about + because they're not normal target files. + For example, perhaps a command you invoke + creates a log file as + part of building the target file you want. + You would like the log file cleaned, + but you don't want to have to teach + SCons that the command + "builds" two files. + + + + + + You can use the &Clean; function to arrange for additional files + to be removed when the -c option is used. + Notice, however, that the &Clean; function takes two arguments, + and the second argument + is the name of the additional file you want cleaned + (foo.log in this example): + + + + + + t = Command('foo.out', 'foo.in', 'build -o $TARGET $SOURCE') + Clean(t, 'foo.log') + + + env = DefaultEnvironment() + import os + env['ENV']['PATH'] = env['ENV']['PATH'] + os.pathsep + os.getcwd() + SConscript('S') + + + foo.in + + + foo.log + + + cat $3 > $2 + + + + + + The first argument is the target with which you want + the cleaning of this additional file associated. + In the above example, + we've used the return value from the + &Command; function, + which represents the + foo.out + target. + Now whenever the + foo.out target is cleaned + by the -c option, + the foo.log file + will be removed as well: + + + + + scons -Q + scons -Q -c + + +
diff --git a/doc/user/file-removal.xml b/doc/user/file-removal.xml new file mode 100644 index 0000000..542fd38 --- /dev/null +++ b/doc/user/file-removal.xml @@ -0,0 +1,202 @@ + + + + + There are two occasions when &SCons; will, + by default, remove target files. + The first is when &SCons; determines that + an target file needs to be rebuilt + and removes the existing version of the target + before executing + The second is when &SCons; is invoked with the + -c option to "clean" + a tree of its built targets. + + These behaviours can be suppressed with the + &Precious; and &NoClean; functions, respectively. + + + +
+ Preventing target removal during build: the &Precious; Function + + + + By default, &SCons; removes targets before building them. + Sometimes, however, this is not what you want. + For example, you may want to update a library incrementally, + not by having it deleted and then rebuilt from all + of the constituent object files. + In such cases, you can use the + &Precious; method to prevent + &SCons; from removing the target before it is built: + + + + + env = Environment(RANLIBCOM='') + lib = env.Library('foo', ['f1.c', 'f2.c', 'f3.c']) + env.Precious(lib) + + + + + Although the output doesn't look any different, + &SCons; does not, in fact, + delete the target library before rebuilding it: + + + + + % scons -Q + cc -o f1.o -c f1.c + cc -o f2.o -c f2.c + cc -o f3.o -c f3.c + ar rc libfoo.a f1.o f2.o f3.o + + + + + &SCons; will, however, still delete files marked as &Precious; + when the -c option is used. + + + +
+ +
+ Preventing target removal during clean: the &NoClean; Function + + + + By default, &SCons; removes all built targets when invoked + with the -c option to clean a source tree + of built targets. + Sometimes, however, this is not what you want. + For example, you may want to remove only intermediate generated files + (such as object files), + but leave the final targets + (the libraries) + untouched. + + In such cases, you can use the &NoClean; method to prevent &SCons; + from removing a target during a clean: + + + + + env = Environment(RANLIBCOM='') + lib = env.Library('foo', ['f1.c', 'f2.c', 'f3.c']) + env.NoClean(lib) + + + + + Notice that the libfoo.a + is not listed as a removed file: + + + + + % scons -Q + cc -o f1.o -c f1.c + cc -o f2.o -c f2.c + cc -o f3.o -c f3.c + ar rc libfoo.a f1.o f2.o f3.o + % scons -c + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Cleaning targets ... + Removed f1.o + Removed f2.o + Removed f3.o + scons: done cleaning targets. + + +
+ +
+ Removing additional files during clean: the &Clean; Function + + + + There may be additional files that you want removed + when the -c option is used, + but which &SCons; doesn't know about + because they're not normal target files. + For example, perhaps a command you invoke + creates a log file as + part of building the target file you want. + You would like the log file cleaned, + but you don't want to have to teach + SCons that the command + "builds" two files. + + + + + + You can use the &Clean; function to arrange for additional files + to be removed when the -c option is used. + Notice, however, that the &Clean; function takes two arguments, + and the second argument + is the name of the additional file you want cleaned + (foo.log in this example): + + + + + t = Command('foo.out', 'foo.in', 'build -o $TARGET $SOURCE') + Clean(t, 'foo.log') + + + + + The first argument is the target with which you want + the cleaning of this additional file associated. + In the above example, + we've used the return value from the + &Command; function, + which represents the + foo.out + target. + Now whenever the + foo.out target is cleaned + by the -c option, + the foo.log file + will be removed as well: + + + + + % scons -Q + build -o foo.out foo.in + % scons -Q -c + Removed foo.out + Removed foo.log + + +
diff --git a/doc/user/hierarchy.in b/doc/user/hierarchy.in new file mode 100644 index 0000000..a917d10 --- /dev/null +++ b/doc/user/hierarchy.in @@ -0,0 +1,794 @@ + + + + + + + The source code for large software projects + rarely stays in a single directory, + but is nearly always divided into a + hierarchy of directories. + Organizing a large software build using &SCons; + involves creating a hierarchy of build scripts + using the &SConscript; function. + + + +
+ &SConscript; Files + + + + As we've already seen, + the build script at the top of the tree is called &SConstruct;. + The top-level &SConstruct; file can + use the &SConscript; function to + include other subsidiary scripts in the build. + These subsidiary scripts can, in turn, + use the &SConscript; function + to include still other scripts in the build. + By convention, these subsidiary scripts are usually + named &SConscript;. + For example, a top-level &SConstruct; file might + arrange for four subsidiary scripts to be included + in the build as follows: + + + + + SConscript(['drivers/display/SConscript', + 'drivers/mouse/SConscript', + 'parser/SConscript', + 'utilities/SConscript']) + + + + + In this case, the &SConstruct; file + lists all of the &SConscript; files in the build explicitly. + (Note, however, that not every directory in the tree + necessarily has an &SConscript; file.) + Alternatively, the drivers + subdirectory might contain an intermediate + &SConscript; file, + in which case the &SConscript; call in + the top-level &SConstruct; file + would look like: + + + + + SConscript(['drivers/SConscript', + 'parser/SConscript', + 'utilities/SConscript']) + + + + + And the subsidiary &SConscript; file in the + drivers subdirectory + would look like: + + + + + SConscript(['display/SConscript', + 'mouse/SConscript']) + + + + + Whether you list all of the &SConscript; files in the + top-level &SConstruct; file, + or place a subsidiary &SConscript; file in + intervening directories, + or use some mix of the two schemes, + is up to you and the needs of your software. + + + +
+ +
+ Path Names Are Relative to the &SConscript; Directory + + + + Subsidiary &SConscript; files make it easy to create a build + hierarchy because all of the file and directory names + in a subsidiary &SConscript; files are interpreted + relative to the directory in which the &SConscript; file lives. + Typically, this allows the &SConscript; file containing the + instructions to build a target file + to live in the same directory as the source files + from which the target will be built, + making it easy to update how the software is built + whenever files are added or deleted + (or other changes are made). + + + + + + For example, suppose we want to build two programs + &prog1; and &prog2; in two separate directories + with the same names as the programs. + One typical way to do this would be + with a top-level &SConstruct; file like this: + + + + + + SConscript(['prog1/SConscript', + 'prog2/SConscript']) + + + env = Environment() + env.Program('prog1', ['main.c', 'foo1.c', 'foo2.c']) + + + env = Environment() + env.Program('prog2', ['main.c', 'bar1.c', 'bar2.c']) + + + + x + + + x + + + x + + + + x + + + x + + + x + + + + + + And subsidiary &SConscript; files that look like this: + + + + + + + + + And this: + + + + + + + + + Then, when we run &SCons; in the top-level directory, + our build looks like: + + + + + scons -Q + + + + + Notice the following: + + First, you can have files with the same names + in multiple directories, like main.c in the above example. + + Second, unlike standard recursive use of &Make;, + &SCons; stays in the top-level directory + (where the &SConstruct; file lives) + and issues commands that use the path names + from the top-level directory to the + target and source files within the hierarchy. + + + +
+ +
+ Top-Level Path Names in Subsidiary &SConscript; Files + + + + If you need to use a file from another directory, + it's sometimes more convenient to specify + the path to a file in another directory + from the top-level &SConstruct; directory, + even when you're using that file in + a subsidiary &SConscript; file in a subdirectory. + You can tell &SCons; to interpret a path name + as relative to the top-level &SConstruct; directory, + not the local directory of the &SConscript; file, + by appending a &hash; (hash mark) + to the beginning of the path name: + + + + + + SConscript('src/prog/SConscript') + + + env = Environment() + env.Program('prog', ['main.c', '#lib/foo1.c', 'foo2.c']) + + + x + + + x + + + x + + + + + + In this example, + the lib directory is + directly underneath the top-level &SConstruct; directory. + If the above &SConscript; file is in a subdirectory + named src/prog, + the output would look like: + + + + + scons -Q + + + + + (Notice that the lib/foo1.o object file + is built in the same directory as its source file. + See , below, + for information about + how to build the object file in a different subdirectory.) + + + +
+ +
+ Absolute Path Names + + + + Of course, you can always specify + an absolute path name for a file--for example: + + + + + + SConscript('src/prog/SConscript') + + + env = Environment() + env.Program('prog', ['main.c', '__ROOT__/usr/joe/lib/foo1.c', 'foo2.c']) + + + x + + + x + + + x + + + + + + Which, when executed, would yield: + + + + + scons -Q + + + + + (As was the case with top-relative path names, + notice that the /usr/joe/lib/foo1.o object file + is built in the same directory as its source file. + See , below, + for information about + how to build the object file in a different subdirectory.) + + + +
+ +
+ Sharing Environments (and Other Variables) Between &SConscript; Files + + + + In the previous example, + each of the subsidiary &SConscript; files + created its own construction environment + by calling &Environment; separately. + This obviously works fine, + but if each program must be built + with the same construction variables, + it's cumbersome and error-prone to initialize + separate construction environments + in the same way over and over in each subsidiary + &SConscript; file. + + + + + + &SCons; supports the ability to export variables + from a parent &SConscript; file + to its subsidiary &SConscript; files, + which allows you to share common initialized + values throughout your build hierarchy. + + + +
+ Exporting Variables + + + + There are two ways to export a variable, + such as a construction environment, + from an &SConscript; file, + so that it may be used by other &SConscript; files. + First, you can call the &Export; + function with a list of variables, + or a string of white-space separated variable names. + Each call to &Export; adds one + or more variables to a global list + of variables that are available for import + by other &SConscript; files. + + + + + env = Environment() + Export('env') + + + + + You may export more than one variable name at a time: + + + + + env = Environment() + debug = ARGUMENTS['debug'] + Export('env', 'debug') + + + + + Because white space is not legal in Python variable names, + the &Export; function will even automatically split + a string into separate names for you: + + + + + Export('env debug') + + + + + Second, you can specify a list of + variables to export as a second argument + to the &SConscript; function call: + + + + + SConscript('src/SConscript', 'env') + + + + + Or as the &exports; keyword argument: + + + + + SConscript('src/SConscript', exports='env') + + + + + These calls export the specified variables + to only the listed &SConscript; files. + You may, however, specify more than one + &SConscript; file in a list: + + + + + SConscript(['src1/SConscript', + 'src2/SConscript'], exports='env') + + + + + This is functionally equivalent to + calling the &SConscript; function + multiple times with the same &exports; argument, + one per &SConscript; file. + + + +
+ +
+ Importing Variables + + + + Once a variable has been exported from a calling + &SConscript; file, + it may be used in other &SConscript; files + by calling the &Import; function: + + + + + Import('env') + env.Program('prog', ['prog.c']) + + + + + The &Import; call makes the env construction + environment available to the &SConscript; file, + after which the variable can be used to build + programs, libraries, etc. + + + + + + Like the &Export; function, + the &Import; function can be used + with multiple variable names: + + + + + Import('env', 'debug') + env = env.Clone(DEBUG = debug) + env.Program('prog', ['prog.c']) + + + + + And the &Import; function will similarly + split a string along white-space + into separate variable names: + + + + + Import('env debug') + env = env.Clone(DEBUG = debug) + env.Program('prog', ['prog.c']) + + + + + Lastly, as a special case, + you may import all of the variables that + have been exported by supplying an asterisk + to the &Import; function: + + + + + Import('*') + env = env.Clone(DEBUG = debug) + env.Program('prog', ['prog.c']) + + + + + If you're dealing with a lot of &SConscript; files, + this can be a lot simpler than keeping + arbitrary lists of imported variables in each file. + + + +
+ +
+ Returning Values From an &SConscript; File + + + + Sometimes, you would like to be able to + use information from a subsidiary + &SConscript file in some way. + For example, + suppose that you want to create one + library from source files + scattered throughout a number + of subsidiary &SConscript; files. + You can do this by using the &Return; + function to return values + from the subsidiary &SConscript; files + to the calling file. + + + + + + If, for example, we have two subdirectories + &foo; and &bar; + that should each contribute a source + file to a Library, + what we'd like to be able to do is + collect the object files + from the subsidiary &SConscript; calls + like this: + + + + + + env = Environment() + Export('env') + objs = [] + for subdir in ['foo', 'bar']: + o = SConscript('%s/SConscript' % subdir) + objs.append(o) + env.Library('prog', objs) + + + + + Import('env') + obj = env.Object('foo.c') + Return('obj') + + + Import('env') + obj = env.Object('bar.c') + Return('obj') + + + void foo(void) { printf("foo/foo.c\n"); } + + + void bar(void) { printf("bar/bar.c\n"); } + + + + + + We can do this by using the &Return; + function in the + foo/SConscript file like this: + + + + + + + + + (The corresponding + bar/SConscript + file should be pretty obvious.) + Then when we run &SCons;, + the object files from the subsidiary subdirectories + are all correctly archived in the desired library: + + + + + scons -Q + + + + +
+ +
+ + diff --git a/doc/user/hierarchy.xml b/doc/user/hierarchy.xml new file mode 100644 index 0000000..3495c5f --- /dev/null +++ b/doc/user/hierarchy.xml @@ -0,0 +1,746 @@ + + + + + + + The source code for large software projects + rarely stays in a single directory, + but is nearly always divided into a + hierarchy of directories. + Organizing a large software build using &SCons; + involves creating a hierarchy of build scripts + using the &SConscript; function. + + + +
+ &SConscript; Files + + + + As we've already seen, + the build script at the top of the tree is called &SConstruct;. + The top-level &SConstruct; file can + use the &SConscript; function to + include other subsidiary scripts in the build. + These subsidiary scripts can, in turn, + use the &SConscript; function + to include still other scripts in the build. + By convention, these subsidiary scripts are usually + named &SConscript;. + For example, a top-level &SConstruct; file might + arrange for four subsidiary scripts to be included + in the build as follows: + + + + + SConscript(['drivers/display/SConscript', + 'drivers/mouse/SConscript', + 'parser/SConscript', + 'utilities/SConscript']) + + + + + In this case, the &SConstruct; file + lists all of the &SConscript; files in the build explicitly. + (Note, however, that not every directory in the tree + necessarily has an &SConscript; file.) + Alternatively, the drivers + subdirectory might contain an intermediate + &SConscript; file, + in which case the &SConscript; call in + the top-level &SConstruct; file + would look like: + + + + + SConscript(['drivers/SConscript', + 'parser/SConscript', + 'utilities/SConscript']) + + + + + And the subsidiary &SConscript; file in the + drivers subdirectory + would look like: + + + + + SConscript(['display/SConscript', + 'mouse/SConscript']) + + + + + Whether you list all of the &SConscript; files in the + top-level &SConstruct; file, + or place a subsidiary &SConscript; file in + intervening directories, + or use some mix of the two schemes, + is up to you and the needs of your software. + + + +
+ +
+ Path Names Are Relative to the &SConscript; Directory + + + + Subsidiary &SConscript; files make it easy to create a build + hierarchy because all of the file and directory names + in a subsidiary &SConscript; files are interpreted + relative to the directory in which the &SConscript; file lives. + Typically, this allows the &SConscript; file containing the + instructions to build a target file + to live in the same directory as the source files + from which the target will be built, + making it easy to update how the software is built + whenever files are added or deleted + (or other changes are made). + + + + + + For example, suppose we want to build two programs + &prog1; and &prog2; in two separate directories + with the same names as the programs. + One typical way to do this would be + with a top-level &SConstruct; file like this: + + + + + SConscript(['prog1/SConscript', + 'prog2/SConscript']) + + + + + And subsidiary &SConscript; files that look like this: + + + + + + env = Environment() + env.Program('prog1', ['main.c', 'foo1.c', 'foo2.c']) + + + + + And this: + + + + + + env = Environment() + env.Program('prog2', ['main.c', 'bar1.c', 'bar2.c']) + + + + + Then, when we run &SCons; in the top-level directory, + our build looks like: + + + + + % scons -Q + cc -o prog1/foo1.o -c prog1/foo1.c + cc -o prog1/foo2.o -c prog1/foo2.c + cc -o prog1/main.o -c prog1/main.c + cc -o prog1/prog1 prog1/main.o prog1/foo1.o prog1/foo2.o + cc -o prog2/bar1.o -c prog2/bar1.c + cc -o prog2/bar2.o -c prog2/bar2.c + cc -o prog2/main.o -c prog2/main.c + cc -o prog2/prog2 prog2/main.o prog2/bar1.o prog2/bar2.o + + + + + Notice the following: + + First, you can have files with the same names + in multiple directories, like main.c in the above example. + + Second, unlike standard recursive use of &Make;, + &SCons; stays in the top-level directory + (where the &SConstruct; file lives) + and issues commands that use the path names + from the top-level directory to the + target and source files within the hierarchy. + + + +
+ +
+ Top-Level Path Names in Subsidiary &SConscript; Files + + + + If you need to use a file from another directory, + it's sometimes more convenient to specify + the path to a file in another directory + from the top-level &SConstruct; directory, + even when you're using that file in + a subsidiary &SConscript; file in a subdirectory. + You can tell &SCons; to interpret a path name + as relative to the top-level &SConstruct; directory, + not the local directory of the &SConscript; file, + by appending a &hash; (hash mark) + to the beginning of the path name: + + + + + env = Environment() + env.Program('prog', ['main.c', '#lib/foo1.c', 'foo2.c']) + + + + + In this example, + the lib directory is + directly underneath the top-level &SConstruct; directory. + If the above &SConscript; file is in a subdirectory + named src/prog, + the output would look like: + + + + + % scons -Q + cc -o lib/foo1.o -c lib/foo1.c + cc -o src/prog/foo2.o -c src/prog/foo2.c + cc -o src/prog/main.o -c src/prog/main.c + cc -o src/prog/prog src/prog/main.o lib/foo1.o src/prog/foo2.o + + + + + (Notice that the lib/foo1.o object file + is built in the same directory as its source file. + See , below, + for information about + how to build the object file in a different subdirectory.) + + + +
+ +
+ Absolute Path Names + + + + Of course, you can always specify + an absolute path name for a file--for example: + + + + + env = Environment() + env.Program('prog', ['main.c', '/usr/joe/lib/foo1.c', 'foo2.c']) + + + + + Which, when executed, would yield: + + + + + % scons -Q + cc -o src/prog/foo2.o -c src/prog/foo2.c + cc -o src/prog/main.o -c src/prog/main.c + cc -o /usr/joe/lib/foo1.o -c /usr/joe/lib/foo1.c + cc -o src/prog/prog src/prog/main.o /usr/joe/lib/foo1.o src/prog/foo2.o + + + + + (As was the case with top-relative path names, + notice that the /usr/joe/lib/foo1.o object file + is built in the same directory as its source file. + See , below, + for information about + how to build the object file in a different subdirectory.) + + + +
+ +
+ Sharing Environments (and Other Variables) Between &SConscript; Files + + + + In the previous example, + each of the subsidiary &SConscript; files + created its own construction environment + by calling &Environment; separately. + This obviously works fine, + but if each program must be built + with the same construction variables, + it's cumbersome and error-prone to initialize + separate construction environments + in the same way over and over in each subsidiary + &SConscript; file. + + + + + + &SCons; supports the ability to export variables + from a parent &SConscript; file + to its subsidiary &SConscript; files, + which allows you to share common initialized + values throughout your build hierarchy. + + + +
+ Exporting Variables + + + + There are two ways to export a variable, + such as a construction environment, + from an &SConscript; file, + so that it may be used by other &SConscript; files. + First, you can call the &Export; + function with a list of variables, + or a string of white-space separated variable names. + Each call to &Export; adds one + or more variables to a global list + of variables that are available for import + by other &SConscript; files. + + + + + env = Environment() + Export('env') + + + + + You may export more than one variable name at a time: + + + + + env = Environment() + debug = ARGUMENTS['debug'] + Export('env', 'debug') + + + + + Because white space is not legal in Python variable names, + the &Export; function will even automatically split + a string into separate names for you: + + + + + Export('env debug') + + + + + Second, you can specify a list of + variables to export as a second argument + to the &SConscript; function call: + + + + + SConscript('src/SConscript', 'env') + + + + + Or as the &exports; keyword argument: + + + + + SConscript('src/SConscript', exports='env') + + + + + These calls export the specified variables + to only the listed &SConscript; files. + You may, however, specify more than one + &SConscript; file in a list: + + + + + SConscript(['src1/SConscript', + 'src2/SConscript'], exports='env') + + + + + This is functionally equivalent to + calling the &SConscript; function + multiple times with the same &exports; argument, + one per &SConscript; file. + + + +
+ +
+ Importing Variables + + + + Once a variable has been exported from a calling + &SConscript; file, + it may be used in other &SConscript; files + by calling the &Import; function: + + + + + Import('env') + env.Program('prog', ['prog.c']) + + + + + The &Import; call makes the env construction + environment available to the &SConscript; file, + after which the variable can be used to build + programs, libraries, etc. + + + + + + Like the &Export; function, + the &Import; function can be used + with multiple variable names: + + + + + Import('env', 'debug') + env = env.Clone(DEBUG = debug) + env.Program('prog', ['prog.c']) + + + + + And the &Import; function will similarly + split a string along white-space + into separate variable names: + + + + + Import('env debug') + env = env.Clone(DEBUG = debug) + env.Program('prog', ['prog.c']) + + + + + Lastly, as a special case, + you may import all of the variables that + have been exported by supplying an asterisk + to the &Import; function: + + + + + Import('*') + env = env.Clone(DEBUG = debug) + env.Program('prog', ['prog.c']) + + + + + If you're dealing with a lot of &SConscript; files, + this can be a lot simpler than keeping + arbitrary lists of imported variables in each file. + + + +
+ +
+ Returning Values From an &SConscript; File + + + + Sometimes, you would like to be able to + use information from a subsidiary + &SConscript; file in some way. + For example, + suppose that you want to create one + library from source files + scattered throughout a number + of subsidiary &SConscript; files. + You can do this by using the &Return; + function to return values + from the subsidiary &SConscript; files + to the calling file. + + + + + + If, for example, we have two subdirectories + &foo; and &bar; + that should each contribute a source + file to a Library, + what we'd like to be able to do is + collect the object files + from the subsidiary &SConscript; calls + like this: + + + + + env = Environment() + Export('env') + objs = [] + for subdir in ['foo', 'bar']: + o = SConscript('%s/SConscript' % subdir) + objs.append(o) + env.Library('prog', objs) + + + + + We can do this by using the &Return; + function in the + foo/SConscript file like this: + + + + + + Import('env') + obj = env.Object('foo.c') + Return('obj') + + + + + (The corresponding + bar/SConscript + file should be pretty obvious.) + Then when we run &SCons;, + the object files from the subsidiary subdirectories + are all correctly archived in the desired library: + + + + + % scons -Q + cc -o bar/bar.o -c bar/bar.c + cc -o foo/foo.o -c foo/foo.c + ar rc libprog.a foo/foo.o bar/bar.o + ranlib libprog.a + + + + +
+ +
+ + diff --git a/doc/user/install.in b/doc/user/install.in new file mode 100644 index 0000000..cce2de3 --- /dev/null +++ b/doc/user/install.in @@ -0,0 +1,247 @@ + + + + + Once a program is built, + it is often appropriate to install it in another + directory for public use. + You use the &Install; method + to arrange for a program, or any other file, + to be copied into a destination directory: + + + + + + env = Environment() + hello = env.Program('hello.c') + env.Install('__ROOT__/usr/bin', hello) + + + int main() { printf("Hello, world!\n"); } + + + + + + Note, however, that installing a file is + still considered a type of file "build." + This is important when you remember that + the default behavior of &SCons; is + to build files in or below the current directory. + If, as in the example above, + you are installing files in a directory + outside of the top-level &SConstruct; file's directory tree, + you must specify that directory + (or a higher directory, such as /) + for it to install anything there: + + + + + scons -Q + scons -Q __ROOT__/usr/bin + + + + + It can, however, be cumbersome to remember + (and type) the specific destination directory + in which the program (or any other file) + should be installed. + This is an area where the &Alias; + function comes in handy, + allowing you, for example, + to create a pseudo-target named install + that can expand to the specified destination directory: + + + + + + env = Environment() + hello = env.Program('hello.c') + env.Install('__ROOT__/usr/bin', hello) + env.Alias('install', '__ROOT__/usr/bin') + + + int main() { printf("Hello, world!\n"); } + + + + + + This then yields the more natural + ability to install the program + in its destination as follows: + + + + + scons -Q + scons -Q install + + +
+ Installing Multiple Files in a Directory + + + + You can install multiple files into a directory + simply by calling the &Install; function multiple times: + + + + + + env = Environment() + hello = env.Program('hello.c') + goodbye = env.Program('goodbye.c') + env.Install('__ROOT__/usr/bin', hello) + env.Install('__ROOT__/usr/bin', goodbye) + env.Alias('install', '__ROOT__/usr/bin') + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Goodbye, world!\n"); } + + + + + + Or, more succinctly, listing the multiple input + files in a list + (just like you can do with any other builder): + + + + + env = Environment() + hello = env.Program('hello.c') + goodbye = env.Program('goodbye.c') + env.Install('__ROOT__/usr/bin', [hello, goodbye]) + env.Alias('install', '__ROOT__/usr/bin') + + + + + Either of these two examples yields: + + + + + scons -Q install + + +
+ +
+ Installing a File Under a Different Name + + + + The &Install; method preserves the name + of the file when it is copied into the + destination directory. + If you need to change the name of the file + when you copy it, use the &InstallAs; function: + + + + + + env = Environment() + hello = env.Program('hello.c') + env.InstallAs('__ROOT__/usr/bin/hello-new', hello) + env.Alias('install', '__ROOT__/usr/bin') + + + int main() { printf("Hello, world!\n"); } + + + + + + This installs the hello + program with the name hello-new + as follows: + + + + + scons -Q install + + +
+ +
+ Installing Multiple Files Under Different Names + + + + Lastly, if you have multiple files that all + need to be installed with different file names, + you can either call the &InstallAs; function + multiple times, or as a shorthand, + you can supply same-length lists + for both the target and source arguments: + + + + + + env = Environment() + hello = env.Program('hello.c') + goodbye = env.Program('goodbye.c') + env.InstallAs(['__ROOT__/usr/bin/hello-new', + '__ROOT__/usr/bin/goodbye-new'], + [hello, goodbye]) + env.Alias('install', '__ROOT__/usr/bin') + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Goodbye, world!\n"); } + + + + + + In this case, the &InstallAs; function + loops through both lists simultaneously, + and copies each source file into its corresponding + target file name: + + + + + scons -Q install + + +
diff --git a/doc/user/install.xml b/doc/user/install.xml new file mode 100644 index 0000000..e011986 --- /dev/null +++ b/doc/user/install.xml @@ -0,0 +1,237 @@ + + + + + Once a program is built, + it is often appropriate to install it in another + directory for public use. + You use the &Install; method + to arrange for a program, or any other file, + to be copied into a destination directory: + + + + + env = Environment() + hello = env.Program('hello.c') + env.Install('/usr/bin', hello) + + + + + Note, however, that installing a file is + still considered a type of file "build." + This is important when you remember that + the default behavior of &SCons; is + to build files in or below the current directory. + If, as in the example above, + you are installing files in a directory + outside of the top-level &SConstruct; file's directory tree, + you must specify that directory + (or a higher directory, such as /) + for it to install anything there: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q /usr/bin + Install file: "hello" as "/usr/bin/hello" + + + + + It can, however, be cumbersome to remember + (and type) the specific destination directory + in which the program (or any other file) + should be installed. + This is an area where the &Alias; + function comes in handy, + allowing you, for example, + to create a pseudo-target named install + that can expand to the specified destination directory: + + + + + env = Environment() + hello = env.Program('hello.c') + env.Install('/usr/bin', hello) + env.Alias('install', '/usr/bin') + + + + + This then yields the more natural + ability to install the program + in its destination as follows: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + % scons -Q install + Install file: "hello" as "/usr/bin/hello" + + +
+ Installing Multiple Files in a Directory + + + + You can install multiple files into a directory + simply by calling the &Install; function multiple times: + + + + + env = Environment() + hello = env.Program('hello.c') + goodbye = env.Program('goodbye.c') + env.Install('/usr/bin', hello) + env.Install('/usr/bin', goodbye) + env.Alias('install', '/usr/bin') + + + + + Or, more succinctly, listing the multiple input + files in a list + (just like you can do with any other builder): + + + + + env = Environment() + hello = env.Program('hello.c') + goodbye = env.Program('goodbye.c') + env.Install('/usr/bin', [hello, goodbye]) + env.Alias('install', '/usr/bin') + + + + + Either of these two examples yields: + + + + + % scons -Q install + cc -o goodbye.o -c goodbye.c + cc -o goodbye goodbye.o + Install file: "goodbye" as "/usr/bin/goodbye" + cc -o hello.o -c hello.c + cc -o hello hello.o + Install file: "hello" as "/usr/bin/hello" + + +
+ +
+ Installing a File Under a Different Name + + + + The &Install; method preserves the name + of the file when it is copied into the + destination directory. + If you need to change the name of the file + when you copy it, use the &InstallAs; function: + + + + + env = Environment() + hello = env.Program('hello.c') + env.InstallAs('/usr/bin/hello-new', hello) + env.Alias('install', '/usr/bin') + + + + + This installs the hello + program with the name hello-new + as follows: + + + + + % scons -Q install + cc -o hello.o -c hello.c + cc -o hello hello.o + Install file: "hello" as "/usr/bin/hello-new" + + +
+ +
+ Installing Multiple Files Under Different Names + + + + Lastly, if you have multiple files that all + need to be installed with different file names, + you can either call the &InstallAs; function + multiple times, or as a shorthand, + you can supply same-length lists + for both the target and source arguments: + + + + + env = Environment() + hello = env.Program('hello.c') + goodbye = env.Program('goodbye.c') + env.InstallAs(['/usr/bin/hello-new', + '/usr/bin/goodbye-new'], + [hello, goodbye]) + env.Alias('install', '/usr/bin') + + + + + In this case, the &InstallAs; function + loops through both lists simultaneously, + and copies each source file into its corresponding + target file name: + + + + + % scons -Q install + cc -o goodbye.o -c goodbye.c + cc -o goodbye goodbye.o + Install file: "goodbye" as "/usr/bin/goodbye-new" + cc -o hello.o -c hello.c + cc -o hello hello.o + Install file: "hello" as "/usr/bin/hello-new" + + +
diff --git a/doc/user/java.in b/doc/user/java.in new file mode 100644 index 0000000..358d79b --- /dev/null +++ b/doc/user/java.in @@ -0,0 +1,657 @@ + + + + + So far, we've been using examples of + building C and C++ programs + to demonstrate the features of &SCons;. + &SCons; also supports building Java programs, + but Java builds are handled slightly differently, + which reflects the ways in which + the Java compiler and tools + build programs differently than + other languages' tool chains. + + + +
+ Building Java Class Files: the &b-Java; Builder + + + + The basic activity when programming in Java, + of course, is to take one or more .java files + containing Java source code + and to call the Java compiler + to turn them into one or more + .class files. + In &SCons;, you do this + by giving the &b-link-Java; Builder + a target directory in which + to put the .class files, + and a source directory that contains + the .java files: + + + + + + Java('classes', 'src') + + + public class Example1 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example2 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example3 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + + + + If the src directory contains + three .java source files, + then running &SCons; might look like this: + + + + + scons -Q + + + + + &SCons; will actually search the src + directory tree for all of the .java files. + The Java compiler will then create the + necessary class files in the classes subdirectory, + based on the class names found in the .java files. + + + +
+ +
+ How &SCons; Handles Java Dependencies + + + + In addition to searching the source directory for + .java files, + &SCons; actually runs the .java files + through a stripped-down Java parser that figures out + what classes are defined. + In other words, &SCons; knows, + without you having to tell it, + what .class files + will be produced by the &javac; call. + So our one-liner example from the preceding section: + + + + + + Java('classes', 'src') + + + public class Example1 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + public class AdditionalClass1 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example2 + { + class Inner2 { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + } + + + public class Example3 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + public class AdditionalClass3 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + + + + Will not only tell you reliably + that the .class files + in the classes subdirectory + are up-to-date: + + + + + scons -Q + scons -Q classes + + + + + But it will also remove all of the generated + .class files, + even for inner classes, + without you having to specify them manually. + For example, if our + Example1.java + and + Example3.java + files both define additional classes, + and the class defined in Example2.java + has an inner class, + running scons -c + will clean up all of those .class files + as well: + + + + + scons -Q + scons -Q -c classes + + + + + To ensure correct handling of .class + dependencies in all cases, you need to tell &SCons; which Java + version is being used. This is needed because Java 1.5 changed + the .class file names for nested anonymous + inner classes. Use the JAVAVERSION construction + variable to specify the version in use. With Java 1.6, the + one-liner example can then be defined like this: + + + + + Java('classes', 'src', JAVAVERSION='1.6') + + + + See JAVAVERSION in the man page for more information. + + +
+ +
+ Building Java Archive (<filename>.jar</filename>) Files: the &b-Jar; Builder + + + + After building the class files, + it's common to collect them into + a Java archive (.jar) file, + which you do by calling the &b-link-Jar; Builder method. + If you want to just collect all of the + class files within a subdirectory, + you can just specify that subdirectory + as the &b-Jar; source: + + + + + + Java(target = 'classes', source = 'src') + Jar(target = 'test.jar', source = 'classes') + + + public class Example1 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example2 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example3 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + + + + &SCons; will then pass that directory + to the &jar; command, + which will collect all of the underlying + .class files: + + + + + scons -Q + + + + + If you want to keep all of the + .class files + for multiple programs in one location, + and only archive some of them in + each .jar file, + you can pass the &b-Jar; builder a + list of files as its source. + It's extremely simple to create multiple + .jar files this way, + using the lists of target class files created + by calls to the &b-link-Java; builder + as sources to the various &b-Jar; calls: + + + + + + prog1_class_files = Java(target = 'classes', source = 'prog1') + prog2_class_files = Java(target = 'classes', source = 'prog2') + Jar(target = 'prog1.jar', source = prog1_class_files) + Jar(target = 'prog2.jar', source = prog2_class_files) + + + public class Example1 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example2 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example3 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + public class Example4 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + + + + This will then create + prog1.jar + and prog2.jar + next to the subdirectories + that contain their .java files: + + + + + scons -Q + + +
+ +
+ Building C Header and Stub Files: the &b-JavaH; Builder + + + + You can generate C header and source files + for implementing native methods, + by using the &b-link-JavaH; Builder. + There are several ways of using the &JavaH Builder. + One typical invocation might look like: + + + + + + classes = Java(target = 'classes', source = 'src/pkg/sub') + JavaH(target = 'native', source = classes) + + + package pkg.sub; + public class Example1 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example2 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example3 + { + public static void main(String[] args) + { + } + } + + + + + + The source is a list of class files generated by the + call to the &b-link-Java; Builder, + and the target is the output directory in + which we want the C header files placed. + The target + gets converted into the + when &SCons; runs &javah;: + + + + + scons -Q + + + + + In this case, + the call to &javah; + will generate the header files + native/pkg_sub_Example1.h, + native/pkg_sub_Example2.h + and + native/pkg_sub_Example3.h. + Notice that &SCons; remembered that the class + files were generated with a target directory of + classes, + and that it then specified that target directory + as the option + to the call to &javah;. + + + + + + Although it's more convenient to use + the list of class files returned by + the &b-Java; Builder + as the source of a call to the &b-JavaH; Builder, + you can + specify the list of class files + by hand, if you prefer. + If you do, + you need to set the + &cv-link-JAVACLASSDIR; construction variable + when calling &b-JavaH;: + + + + + + Java(target = 'classes', source = 'src/pkg/sub') + class_file_list = ['classes/pkg/sub/Example1.class', + 'classes/pkg/sub/Example2.class', + 'classes/pkg/sub/Example3.class'] + JavaH(target = 'native', source = class_file_list, JAVACLASSDIR = 'classes') + + + package pkg.sub; + public class Example1 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example2 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example3 + { + public static void main(String[] args) + { + } + } + + + + + + The &cv-JAVACLASSDIR; value then + gets converted into the + when &SCons; runs &javah;: + + + + + scons -Q + + + + + Lastly, if you don't want a separate header file + generated for each source file, + you can specify an explicit File Node + as the target of the &b-JavaH; Builder: + + + + + + classes = Java(target = 'classes', source = 'src/pkg/sub') + JavaH(target = File('native.h'), source = classes) + + + package pkg.sub; + public class Example1 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example2 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example3 + { + public static void main(String[] args) + { + } + } + + + + + + Because &SCons; assumes by default + that the target of the &b-JavaH; builder is a directory, + you need to use the &File; function + to make sure that &SCons; doesn't + create a directory named native.h. + When a file is used, though, + &SCons; correctly converts the file name + into the &javah; option: + + + + + scons -Q + + +
+ +
+ Building RMI Stub and Skeleton Class Files: the &b-RMIC; Builder + + + + You can generate Remote Method Invocation stubs + by using the &b-link-RMIC; Builder. + The source is a list of directories, + typically returned by a call to the &b-link-Java; Builder, + and the target is an output directory + where the _Stub.class + and _Skel.class files will + be placed: + + + + + + classes = Java(target = 'classes', source = 'src/pkg/sub') + RMIC(target = 'outdir', source = classes) + + + package pkg.sub; + public class Example1 + { + public static void main(String[] args) + { + } + } + + + package pkg.sub; + public class Example2 + { + public static void main(String[] args) + { + } + } + + + + + + As it did with the &b-link-JavaH; Builder, + &SCons; remembers the class directory + and passes it as the option + to &rmic: + + + + + scons -Q + + + + + This example would generate the files + outdir/pkg/sub/Example1_Skel.class, + outdir/pkg/sub/Example1_Stub.class, + outdir/pkg/sub/Example2_Skel.class and + outdir/pkg/sub/Example2_Stub.class. + + + +
diff --git a/doc/user/java.xml b/doc/user/java.xml new file mode 100644 index 0000000..8198b65 --- /dev/null +++ b/doc/user/java.xml @@ -0,0 +1,433 @@ + + + + + So far, we've been using examples of + building C and C++ programs + to demonstrate the features of &SCons;. + &SCons; also supports building Java programs, + but Java builds are handled slightly differently, + which reflects the ways in which + the Java compiler and tools + build programs differently than + other languages' tool chains. + + + +
+ Building Java Class Files: the &b-Java; Builder + + + + The basic activity when programming in Java, + of course, is to take one or more .java files + containing Java source code + and to call the Java compiler + to turn them into one or more + .class files. + In &SCons;, you do this + by giving the &b-link-Java; Builder + a target directory in which + to put the .class files, + and a source directory that contains + the .java files: + + + + + Java('classes', 'src') + + + + + If the src directory contains + three .java source files, + then running &SCons; might look like this: + + + + + % scons -Q + javac -d classes -sourcepath src src/Example1.java src/Example2.java src/Example3.java + + + + + &SCons; will actually search the src + directory tree for all of the .java files. + The Java compiler will then create the + necessary class files in the classes subdirectory, + based on the class names found in the .java files. + + + +
+ +
+ How &SCons; Handles Java Dependencies + + + + In addition to searching the source directory for + .java files, + &SCons; actually runs the .java files + through a stripped-down Java parser that figures out + what classes are defined. + In other words, &SCons; knows, + without you having to tell it, + what .class files + will be produced by the &javac; call. + So our one-liner example from the preceding section: + + + + + Java('classes', 'src') + + + + + Will not only tell you reliably + that the .class files + in the classes subdirectory + are up-to-date: + + + + + % scons -Q + javac -d classes -sourcepath src src/Example1.java src/Example2.java src/Example3.java + % scons -Q classes + scons: `classes' is up to date. + + + + + But it will also remove all of the generated + .class files, + even for inner classes, + without you having to specify them manually. + For example, if our + Example1.java + and + Example3.java + files both define additional classes, + and the class defined in Example2.java + has an inner class, + running scons -c + will clean up all of those .class files + as well: + + + + + % scons -Q + javac -d classes -sourcepath src src/Example1.java src/Example2.java src/Example3.java + % scons -Q -c classes + Removed classes/Example1.class + Removed classes/AdditionalClass1.class + Removed classes/Example2$Inner2.class + Removed classes/Example2.class + Removed classes/Example3.class + Removed classes/AdditionalClass3.class + + + + + To ensure correct handling of .class + dependencies in all cases, you need to tell &SCons; which Java + version is being used. This is needed because Java 1.5 changed + the .class file names for nested anonymous + inner classes. Use the JAVAVERSION construction + variable to specify the version in use. With Java 1.6, the + one-liner example can then be defined like this: + + + + + Java('classes', 'src', JAVAVERSION='1.6') + + + + See JAVAVERSION in the man page for more information. + + +
+ +
+ Building Java Archive (<filename>.jar</filename>) Files: the &b-Jar; Builder + + + + After building the class files, + it's common to collect them into + a Java archive (.jar) file, + which you do by calling the &b-link-Jar; Builder method. + If you want to just collect all of the + class files within a subdirectory, + you can just specify that subdirectory + as the &b-Jar; source: + + + + + Java(target = 'classes', source = 'src') + Jar(target = 'test.jar', source = 'classes') + + + + + &SCons; will then pass that directory + to the &jar; command, + which will collect all of the underlying + .class files: + + + + + % scons -Q + javac -d classes -sourcepath src src/Example1.java src/Example2.java src/Example3.java + jar cf test.jar classes + + + + + If you want to keep all of the + .class files + for multiple programs in one location, + and only archive some of them in + each .jar file, + you can pass the &b-Jar; builder a + list of files as its source. + It's extremely simple to create multiple + .jar files this way, + using the lists of target class files created + by calls to the &b-link-Java; builder + as sources to the various &b-Jar; calls: + + + + + prog1_class_files = Java(target = 'classes', source = 'prog1') + prog2_class_files = Java(target = 'classes', source = 'prog2') + Jar(target = 'prog1.jar', source = prog1_class_files) + Jar(target = 'prog2.jar', source = prog2_class_files) + + + + + This will then create + prog1.jar + and prog2.jar + next to the subdirectories + that contain their .java files: + + + + + % scons -Q + javac -d classes -sourcepath prog1 prog1/Example1.java prog1/Example2.java + javac -d classes -sourcepath prog2 prog2/Example3.java prog2/Example4.java + jar cf prog1.jar -C classes Example1.class -C classes Example2.class + jar cf prog2.jar -C classes Example3.class -C classes Example4.class + + +
+ +
+ Building C Header and Stub Files: the &b-JavaH; Builder + + + + You can generate C header and source files + for implementing native methods, + by using the &b-link-JavaH; Builder. + There are several ways of using the &JavaH; Builder. + One typical invocation might look like: + + + + + classes = Java(target = 'classes', source = 'src/pkg/sub') + JavaH(target = 'native', source = classes) + + + + + The source is a list of class files generated by the + call to the &b-link-Java; Builder, + and the target is the output directory in + which we want the C header files placed. + The target + gets converted into the + when &SCons; runs &javah;: + + + + + % scons -Q + javac -d classes -sourcepath src/pkg/sub src/pkg/sub/Example1.java src/pkg/sub/Example2.java src/pkg/sub/Example3.java + javah -d native -classpath classes pkg.sub.Example1 pkg.sub.Example2 pkg.sub.Example3 + + + + + In this case, + the call to &javah; + will generate the header files + native/pkg_sub_Example1.h, + native/pkg_sub_Example2.h + and + native/pkg_sub_Example3.h. + Notice that &SCons; remembered that the class + files were generated with a target directory of + classes, + and that it then specified that target directory + as the option + to the call to &javah;. + + + + + + Although it's more convenient to use + the list of class files returned by + the &b-Java; Builder + as the source of a call to the &b-JavaH; Builder, + you can + specify the list of class files + by hand, if you prefer. + If you do, + you need to set the + &cv-link-JAVACLASSDIR; construction variable + when calling &b-JavaH;: + + + + + Java(target = 'classes', source = 'src/pkg/sub') + class_file_list = ['classes/pkg/sub/Example1.class', + 'classes/pkg/sub/Example2.class', + 'classes/pkg/sub/Example3.class'] + JavaH(target = 'native', source = class_file_list, JAVACLASSDIR = 'classes') + + + + + The &cv-JAVACLASSDIR; value then + gets converted into the + when &SCons; runs &javah;: + + + + + % scons -Q + javac -d classes -sourcepath src/pkg/sub src/pkg/sub/Example1.java src/pkg/sub/Example2.java src/pkg/sub/Example3.java + javah -d native -classpath classes pkg.sub.Example1 pkg.sub.Example2 pkg.sub.Example3 + + + + + Lastly, if you don't want a separate header file + generated for each source file, + you can specify an explicit File Node + as the target of the &b-JavaH; Builder: + + + + + classes = Java(target = 'classes', source = 'src/pkg/sub') + JavaH(target = File('native.h'), source = classes) + + + + + Because &SCons; assumes by default + that the target of the &b-JavaH; builder is a directory, + you need to use the &File; function + to make sure that &SCons; doesn't + create a directory named native.h. + When a file is used, though, + &SCons; correctly converts the file name + into the &javah; option: + + + + + % scons -Q + javac -d classes -sourcepath src/pkg/sub src/pkg/sub/Example1.java src/pkg/sub/Example2.java src/pkg/sub/Example3.java + javah -o native.h -classpath classes pkg.sub.Example1 pkg.sub.Example2 pkg.sub.Example3 + + +
+ +
+ Building RMI Stub and Skeleton Class Files: the &b-RMIC; Builder + + + + You can generate Remote Method Invocation stubs + by using the &b-link-RMIC; Builder. + The source is a list of directories, + typically returned by a call to the &b-link-Java; Builder, + and the target is an output directory + where the _Stub.class + and _Skel.class files will + be placed: + + + + + classes = Java(target = 'classes', source = 'src/pkg/sub') + RMIC(target = 'outdir', source = classes) + + + + + As it did with the &b-link-JavaH; Builder, + &SCons; remembers the class directory + and passes it as the option + to &rmic;: + + + + + % scons -Q + javac -d classes -sourcepath src/pkg/sub src/pkg/sub/Example1.java src/pkg/sub/Example2.java + rmic -d outdir -classpath classes pkg.sub.Example1 pkg.sub.Example2 + + + + + This example would generate the files + outdir/pkg/sub/Example1_Skel.class, + outdir/pkg/sub/Example1_Stub.class, + outdir/pkg/sub/Example2_Skel.class and + outdir/pkg/sub/Example2_Stub.class. + + + +
diff --git a/doc/user/less-simple.in b/doc/user/less-simple.in new file mode 100644 index 0000000..e19ba13 --- /dev/null +++ b/doc/user/less-simple.in @@ -0,0 +1,623 @@ + + + + + In this chapter, + you will see several examples of + very simple build configurations using &SCons;, + which will demonstrate how easy + it is to use &SCons; to + build programs from several different programming languages + on different types of systems. + + + +
+ Specifying the Name of the Target (Output) File + + + + You've seen that when you call the &b-link-Program; builder method, + it builds the resulting program with the same + base name as the source file. + That is, the following call to build an + executable program from the &hello_c; source file + will build an executable program named &hello; on POSIX systems, + and an executable program named &hello_exe; on Windows systems: + + + + + Program('hello.c') + + + + + If you want to build a program with + a different name than the base of the source file name, + you simply put the target file name + to the left of the source file name: + + + + + + Program('new_hello', 'hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + (&SCons; requires the target file name first, + followed by the source file name, + so that the order mimics that of an + assignment statement in most programming languages, + including Python: + "program = source files".) + + + + + + Now &SCons; will build an executable program + named &new_hello; when run on a POSIX system: + + + + + scons -Q + + + + + And &SCons; will build an executable program + named &new_hello_exe; when run on a Windows system: + + + + + scons -Q + + +
+ +
+ Compiling Multiple Source Files + + + + You've just seen how to configure &SCons; + to compile a program from a single source file. + It's more common, of course, + that you'll need to build a program from + many input source files, not just one. + To do this, you need to put the + source files in a Python list + (enclosed in square brackets), + like so: + + + + + + Program(['prog.c', 'file1.c', 'file2.c']) + + + int main() { printf("prog.c\n"); } + + + void file1() { printf("file1.c\n"); } + + + void file2() { printf("file2.c\n"); } + + + + + + A build of the above example would look like: + + + + + scons -Q + + + + + Notice that &SCons; + deduces the output program name + from the first source file specified + in the list--that is, + because the first source file was &prog_c;, + &SCons; will name the resulting program &prog; + (or &prog_exe; on a Windows system). + If you want to specify a different program name, + then (as we've seen in the previous section) + you slide the list of source files + over to the right + to make room for the output program file name. + (&SCons; puts the output file name to the left + of the source file names + so that the order mimics that of an + assignment statement: "program = source files".) + This makes our example: + + + + + + Program('program', ['prog.c', 'file1.c', 'file2.c']) + + + int main() { printf("prog.c\n"); } + + + void file1() { printf("file1.c\n"); } + + + void file2() { printf("file2.c\n"); } + + + + + + On Linux, a build of this example would look like: + + + + + scons -Q + + + + + Or on Windows: + + + + + scons -Q + + +
+ +
+ Making a list of files with &Glob; + + + + You can also use the &Glob; function to find all files matching a + certain template, using the standard shell pattern matching + characters *, ? + and [abc] to match any of + a, b or c. + [!abc] is also supported, + to match any character except + a, b or c. + This makes many multi-source-file builds quite easy: + + + + + Program('program', Glob('*.c')) + + + + + The SCons man page has more details on using &Glob; + with variant directories + (see , below) + and repositories + (see , below), + and returning strings rather than Nodes. + + + +
+ +
+ Specifying Single Files Vs. Lists of Files + + + + We've now shown you two ways to specify + the source for a program, + one with a list of files: + + + + + Program('hello', ['file1.c', 'file2.c']) + + + + + And one with a single file: + + + + + Program('hello', 'hello.c') + + + + + You could actually put a single file name in a list, too, + which you might prefer just for the sake of consistency: + + + + + Program('hello', ['hello.c']) + + + + + &SCons; functions will accept a single file name in either form. + In fact, internally, &SCons; treats all input as lists of files, + but allows you to omit the square brackets + to cut down a little on the typing + when there's only a single file name. + + + + + + + + Although &SCons; functions + are forgiving about whether or not you + use a string vs. a list for a single file name, + Python itself is more strict about + treating lists and strings differently. + So where &SCons; allows either + a string or list: + + + + + # The following two calls both work correctly: + Program('program1', 'program1.c') + Program('program2', ['program2.c']) + + + + + Trying to do "Python things" that mix strings and + lists will cause errors or lead to incorrect results: + + + + + common_sources = ['file1.c', 'file2.c'] + + # THE FOLLOWING IS INCORRECT AND GENERATES A PYTHON ERROR + # BECAUSE IT TRIES TO ADD A STRING TO A LIST: + Program('program1', common_sources + 'program1.c') + + # The following works correctly, because it's adding two + # lists together to make another list. + Program('program2', common_sources + ['program2.c']) + + + + +
+ +
+ Making Lists of Files Easier to Read + + + + One drawback to the use of a Python list + for source files is that + each file name must be enclosed in quotes + (either single quotes or double quotes). + This can get cumbersome and difficult to read + when the list of file names is long. + Fortunately, &SCons; and Python provide a number of ways + to make sure that + the &SConstruct; file stays easy to read. + + + + + + To make long lists of file names + easier to deal with, &SCons; provides a + &Split; function + that takes a quoted list of file names, + with the names separated by spaces or other white-space characters, + and turns it into a list of separate file names. + Using the &Split; function turns the + previous example into: + + + + + Program('program', Split('main.c file1.c file2.c')) + + + + + (If you're already familiar with Python, + you'll have realized that this is similar to the + split() method + in the Python standard string module. + Unlike the string.split() method, + however, the &Split; function + does not require a string as input + and will wrap up a single non-string object in a list, + or return its argument untouched if it's already a list. + This comes in handy as a way to make sure + arbitrary values can be passed to &SCons; functions + without having to check the type of the variable by hand.) + + + + + + Putting the call to the &Split; function + inside the &b-Program; call + can also be a little unwieldy. + A more readable alternative is to + assign the output from the &Split; call + to a variable name, + and then use the variable when calling the + &b-Program; function: + + + + + src_files = Split('main.c file1.c file2.c') + Program('program', src_files) + + + + + Lastly, the &Split; function + doesn't care how much white space separates + the file names in the quoted string. + This allows you to create lists of file + names that span multiple lines, + which often makes for easier editing: + + + + + src_files = Split("""main.c + file1.c + file2.c""") + Program('program', src_files) + + + + + (Note in this example that we used + the Python "triple-quote" syntax, + which allows a string to contain + multiple lines. + The three quotes can be either + single or double quotes.) + + + +
+ +
+ Keyword Arguments + + + + &SCons; also allows you to identify + the output file and input source files + using Python keyword arguments. + The output file is known as the + target, + and the source file(s) are known (logically enough) as the + source. + The Python syntax for this is: + + + + + src_files = Split('main.c file1.c file2.c') + Program(target = 'program', source = src_files) + + + + + Because the keywords explicitly identify + what each argument is, + you can actually reverse the order if you prefer: + + + + + src_files = Split('main.c file1.c file2.c') + Program(source = src_files, target = 'program') + + + + + Whether or not you choose to use keyword arguments + to identify the target and source files, + and the order in which you specify them + when using keywords, + are purely personal choices; + &SCons; functions the same regardless. + + + +
+ +
+ Compiling Multiple Programs + + + + In order to compile multiple programs + within the same &SConstruct; file, + simply call the &Program; method + multiple times, + once for each program you need to build: + + + + + + Program('foo.c') + Program('bar', ['bar1.c', 'bar2.c']) + + + int main() { printf("foo.c\n"); } + + + int main() { printf("bar1.c\n"); } + + + void bar2() { printf("bar2.c\n"); } + + + + + + &SCons; would then build the programs as follows: + + + + + scons -Q + + + + + Notice that &SCons; does not necessarily build the + programs in the same order in which you specify + them in the &SConstruct; file. + &SCons; does, however, recognize that + the individual object files must be built + before the resulting program can be built. + We'll discuss this in greater detail in + the "Dependencies" section, below. + + + +
+ +
+ Sharing Source Files Between Multiple Programs + + + + It's common to re-use code by sharing source files + between multiple programs. + One way to do this is to create a library + from the common source files, + which can then be linked into resulting programs. + (Creating libraries is discussed in + , below.) + + + + + + A more straightforward, but perhaps less convenient, + way to share source files between multiple programs + is simply to include the common files + in the lists of source files for each program: + + + + + + Program(Split('foo.c common1.c common2.c')) + Program('bar', Split('bar1.c bar2.c common1.c common2.c')) + + + int main() { printf("foo.c\n"); } + + + int main() { printf("bar1.c\n"); } + + + int bar2() { printf("bar2.c\n"); } + + + void common1() { printf("common1.c\n"); } + + + void common22() { printf("common2.c\n"); } + + + + + + &SCons; recognizes that the object files for + the &common1_c; and &common2_c; source files + each need to be built only once, + even though the resulting object files are + each linked in to both of the resulting executable programs: + + + + + scons -Q + + + + + If two or more programs + share a lot of common source files, + repeating the common files in the list for each program + can be a maintenance problem when you need to change the + list of common files. + You can simplify this by creating a separate Python list + to hold the common file names, + and concatenating it with other lists + using the Python + operator: + + + + + common = ['common1.c', 'common2.c'] + foo_files = ['foo.c'] + common + bar_files = ['bar1.c', 'bar2.c'] + common + Program('foo', foo_files) + Program('bar', bar_files) + + + + + This is functionally equivalent to the previous example. + + + +
diff --git a/doc/user/less-simple.xml b/doc/user/less-simple.xml new file mode 100644 index 0000000..57c61e2 --- /dev/null +++ b/doc/user/less-simple.xml @@ -0,0 +1,608 @@ + + + + + In this chapter, + you will see several examples of + very simple build configurations using &SCons;, + which will demonstrate how easy + it is to use &SCons; to + build programs from several different programming languages + on different types of systems. + + + +
+ Specifying the Name of the Target (Output) File + + + + You've seen that when you call the &b-link-Program; builder method, + it builds the resulting program with the same + base name as the source file. + That is, the following call to build an + executable program from the &hello_c; source file + will build an executable program named &hello; on POSIX systems, + and an executable program named &hello_exe; on Windows systems: + + + + + Program('hello.c') + + + + + If you want to build a program with + a different name than the base of the source file name, + you simply put the target file name + to the left of the source file name: + + + + + Program('new_hello', 'hello.c') + + + + + (&SCons; requires the target file name first, + followed by the source file name, + so that the order mimics that of an + assignment statement in most programming languages, + including Python: + "program = source files".) + + + + + + Now &SCons; will build an executable program + named &new_hello; when run on a POSIX system: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o new_hello hello.o + + + + + And &SCons; will build an executable program + named &new_hello_exe; when run on a Windows system: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:new_hello.exe hello.obj + + +
+ +
+ Compiling Multiple Source Files + + + + You've just seen how to configure &SCons; + to compile a program from a single source file. + It's more common, of course, + that you'll need to build a program from + many input source files, not just one. + To do this, you need to put the + source files in a Python list + (enclosed in square brackets), + like so: + + + + + Program(['prog.c', 'file1.c', 'file2.c']) + + + + + A build of the above example would look like: + + + + + % scons -Q + cc -o file1.o -c file1.c + cc -o file2.o -c file2.c + cc -o prog.o -c prog.c + cc -o prog prog.o file1.o file2.o + + + + + Notice that &SCons; + deduces the output program name + from the first source file specified + in the list--that is, + because the first source file was &prog_c;, + &SCons; will name the resulting program &prog; + (or &prog_exe; on a Windows system). + If you want to specify a different program name, + then (as we've seen in the previous section) + you slide the list of source files + over to the right + to make room for the output program file name. + (&SCons; puts the output file name to the left + of the source file names + so that the order mimics that of an + assignment statement: "program = source files".) + This makes our example: + + + + + Program('program', ['prog.c', 'file1.c', 'file2.c']) + + + + + On Linux, a build of this example would look like: + + + + + % scons -Q + cc -o file1.o -c file1.c + cc -o file2.o -c file2.c + cc -o prog.o -c prog.c + cc -o program prog.o file1.o file2.o + + + + + Or on Windows: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fofile1.obj /c file1.c /nologo + cl /Fofile2.obj /c file2.c /nologo + cl /Foprog.obj /c prog.c /nologo + link /nologo /OUT:program.exe prog.obj file1.obj file2.obj + + +
+ +
+ Making a list of files with &Glob; + + + + You can also use the &Glob; function to find all files matching a + certain template, using the standard shell pattern matching + characters *, ? + and [abc] to match any of + a, b or c. + [!abc] is also supported, + to match any character except + a, b or c. + This makes many multi-source-file builds quite easy: + + + + + Program('program', Glob('*.c')) + + + + + The SCons man page has more details on using &Glob; + with variant directories + (see , below) + and repositories + (see , below), + and returning strings rather than Nodes. + + + +
+ +
+ Specifying Single Files Vs. Lists of Files + + + + We've now shown you two ways to specify + the source for a program, + one with a list of files: + + + + + Program('hello', ['file1.c', 'file2.c']) + + + + + And one with a single file: + + + + + Program('hello', 'hello.c') + + + + + You could actually put a single file name in a list, too, + which you might prefer just for the sake of consistency: + + + + + Program('hello', ['hello.c']) + + + + + &SCons; functions will accept a single file name in either form. + In fact, internally, &SCons; treats all input as lists of files, + but allows you to omit the square brackets + to cut down a little on the typing + when there's only a single file name. + + + + + + + + Although &SCons; functions + are forgiving about whether or not you + use a string vs. a list for a single file name, + Python itself is more strict about + treating lists and strings differently. + So where &SCons; allows either + a string or list: + + + + + # The following two calls both work correctly: + Program('program1', 'program1.c') + Program('program2', ['program2.c']) + + + + + Trying to do "Python things" that mix strings and + lists will cause errors or lead to incorrect results: + + + + + common_sources = ['file1.c', 'file2.c'] + + # THE FOLLOWING IS INCORRECT AND GENERATES A PYTHON ERROR + # BECAUSE IT TRIES TO ADD A STRING TO A LIST: + Program('program1', common_sources + 'program1.c') + + # The following works correctly, because it's adding two + # lists together to make another list. + Program('program2', common_sources + ['program2.c']) + + + + +
+ +
+ Making Lists of Files Easier to Read + + + + One drawback to the use of a Python list + for source files is that + each file name must be enclosed in quotes + (either single quotes or double quotes). + This can get cumbersome and difficult to read + when the list of file names is long. + Fortunately, &SCons; and Python provide a number of ways + to make sure that + the &SConstruct; file stays easy to read. + + + + + + To make long lists of file names + easier to deal with, &SCons; provides a + &Split; function + that takes a quoted list of file names, + with the names separated by spaces or other white-space characters, + and turns it into a list of separate file names. + Using the &Split; function turns the + previous example into: + + + + + Program('program', Split('main.c file1.c file2.c')) + + + + + (If you're already familiar with Python, + you'll have realized that this is similar to the + split() method + in the Python standard string module. + Unlike the string.split() method, + however, the &Split; function + does not require a string as input + and will wrap up a single non-string object in a list, + or return its argument untouched if it's already a list. + This comes in handy as a way to make sure + arbitrary values can be passed to &SCons; functions + without having to check the type of the variable by hand.) + + + + + + Putting the call to the &Split; function + inside the &b-Program; call + can also be a little unwieldy. + A more readable alternative is to + assign the output from the &Split; call + to a variable name, + and then use the variable when calling the + &b-Program; function: + + + + + src_files = Split('main.c file1.c file2.c') + Program('program', src_files) + + + + + Lastly, the &Split; function + doesn't care how much white space separates + the file names in the quoted string. + This allows you to create lists of file + names that span multiple lines, + which often makes for easier editing: + + + + + src_files = Split("""main.c + file1.c + file2.c""") + Program('program', src_files) + + + + + (Note in this example that we used + the Python "triple-quote" syntax, + which allows a string to contain + multiple lines. + The three quotes can be either + single or double quotes.) + + + +
+ +
+ Keyword Arguments + + + + &SCons; also allows you to identify + the output file and input source files + using Python keyword arguments. + The output file is known as the + target, + and the source file(s) are known (logically enough) as the + source. + The Python syntax for this is: + + + + + src_files = Split('main.c file1.c file2.c') + Program(target = 'program', source = src_files) + + + + + Because the keywords explicitly identify + what each argument is, + you can actually reverse the order if you prefer: + + + + + src_files = Split('main.c file1.c file2.c') + Program(source = src_files, target = 'program') + + + + + Whether or not you choose to use keyword arguments + to identify the target and source files, + and the order in which you specify them + when using keywords, + are purely personal choices; + &SCons; functions the same regardless. + + + +
+ +
+ Compiling Multiple Programs + + + + In order to compile multiple programs + within the same &SConstruct; file, + simply call the &Program; method + multiple times, + once for each program you need to build: + + + + + Program('foo.c') + Program('bar', ['bar1.c', 'bar2.c']) + + + + + &SCons; would then build the programs as follows: + + + + + % scons -Q + cc -o bar1.o -c bar1.c + cc -o bar2.o -c bar2.c + cc -o bar bar1.o bar2.o + cc -o foo.o -c foo.c + cc -o foo foo.o + + + + + Notice that &SCons; does not necessarily build the + programs in the same order in which you specify + them in the &SConstruct; file. + &SCons; does, however, recognize that + the individual object files must be built + before the resulting program can be built. + We'll discuss this in greater detail in + the "Dependencies" section, below. + + + +
+ +
+ Sharing Source Files Between Multiple Programs + + + + It's common to re-use code by sharing source files + between multiple programs. + One way to do this is to create a library + from the common source files, + which can then be linked into resulting programs. + (Creating libraries is discussed in + , below.) + + + + + + A more straightforward, but perhaps less convenient, + way to share source files between multiple programs + is simply to include the common files + in the lists of source files for each program: + + + + + Program(Split('foo.c common1.c common2.c')) + Program('bar', Split('bar1.c bar2.c common1.c common2.c')) + + + + + &SCons; recognizes that the object files for + the &common1_c; and &common2_c; source files + each need to be built only once, + even though the resulting object files are + each linked in to both of the resulting executable programs: + + + + + % scons -Q + cc -o bar1.o -c bar1.c + cc -o bar2.o -c bar2.c + cc -o common1.o -c common1.c + cc -o common2.o -c common2.c + cc -o bar bar1.o bar2.o common1.o common2.o + cc -o foo.o -c foo.c + cc -o foo foo.o common1.o common2.o + + + + + If two or more programs + share a lot of common source files, + repeating the common files in the list for each program + can be a maintenance problem when you need to change the + list of common files. + You can simplify this by creating a separate Python list + to hold the common file names, + and concatenating it with other lists + using the Python + operator: + + + + + common = ['common1.c', 'common2.c'] + foo_files = ['foo.c'] + common + bar_files = ['bar1.c', 'bar2.c'] + common + Program('foo', foo_files) + Program('bar', bar_files) + + + + + This is functionally equivalent to the previous example. + + + +
diff --git a/doc/user/libraries.in b/doc/user/libraries.in new file mode 100644 index 0000000..4cce091 --- /dev/null +++ b/doc/user/libraries.in @@ -0,0 +1,445 @@ + + + + + It's often useful to organize large software projects + by collecting parts of the software into one or more libraries. + &SCons; makes it easy to create libraries + and to use them in the programs. + + + +
+ Building Libraries + + + + You build your own libraries by specifying &b-link-Library; + instead of &b-link-Program;: + + + + + + Library('foo', ['f1.c', 'f2.c', 'f3.c']) + + + void f1() { printf("f1.c\n"); } + + + void f2() { printf("f2.c\n"); } + + + void f3() { printf("f3.c\n"); } + + + + + + &SCons; uses the appropriate library prefix and suffix for your system. + So on POSIX or Linux systems, + the above example would build as follows + (although &ranlib; may not be called on all systems): + + + + + scons -Q + + + + + On a Windows system, + a build of the above example would look like: + + + + + scons -Q + + + + + The rules for the target name of the library + are similar to those for programs: + if you don't explicitly specify a target library name, + &SCons; will deduce one from the + name of the first source file specified, + and &SCons; will add an appropriate + file prefix and suffix if you leave them off. + + + +
+ Building Libraries From Source Code or Object Files + + + + The previous example shows building a library from a + list of source files. + You can, however, also give the &b-link-Library; call + object files, + and it will correctly realize + In fact, you can arbitrarily mix source code files + and object files in the source list: + + + + + + Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o']) + + + void f1() { printf("f1.c\n"); } + + + object file + + + void f3() { printf("f3.c\n"); } + + + object file + + + + + + And SCons realizes that only the source code files + must be compiled into object files + before creating the final library: + + + + + scons -Q + + + + + Of course, in this example, the object files + must already exist for the build to succeed. + See , below, + for information about how you can + build object files explicitly + and include the built files in a library. + + + +
+ +
+ Building Static Libraries Explicitly: the &b-StaticLibrary; Builder + + + + The &b-link-Library; function builds a traditional static library. + If you want to be explicit about the type of library being built, + you can use the synonym &b-link-StaticLibrary; function + instead of &b-Library;: + + + + + + StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c']) + + + + + + There is no functional difference between the + &b-link-StaticLibrary; and &b-Library; functions. + + + +
+ +
+ Building Shared (DLL) Libraries: the &b-SharedLibrary; Builder + + + + If you want to build a shared library (on POSIX systems) + or a DLL file (on Windows systems), + you use the &b-link-SharedLibrary; function: + + + + + + SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c']) + + + void f1() { printf("f1.c\n"); } + + + void f2() { printf("f2.c\n"); } + + + void f3() { printf("f3.c\n"); } + + + + + + The output on POSIX: + + + + + scons -Q + + + + + And the output on Windows: + + + + + scons -Q + + + + + Notice again that &SCons; takes care of + building the output file correctly, + adding the -shared option + for a POSIX compilation, + and the /dll option on Windows. + + + +
+ +
+ +
+ Linking with Libraries + + + + Usually, you build a library + because you want to link it with one or more programs. + You link libraries with a program by specifying + the libraries in the &cv-link-LIBS; construction variable, + and by specifying the directory in which + the library will be found in the + &cv-link-LIBPATH; construction variable: + + + + + + + + Library('foo', ['f1.c', 'f2.c', 'f3.c']) + Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.') + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Hello, world!\n"); } + + + + + + Notice, of course, that you don't need to specify a library + prefix (like lib) + or suffix (like .a or .lib). + &SCons; uses the correct prefix or suffix for the current system. + + + + + + On a POSIX or Linux system, + a build of the above example would look like: + + + + + scons -Q + + + + + On a Windows system, + a build of the above example would look like: + + + + + scons -Q + + + + + As usual, notice that &SCons; has taken care + of constructing the correct command lines + to link with the specified library on each system. + + + + + + Note also that, + if you only have a single library to link with, + you can specify the library name in single string, + instead of a Python list, + so that: + + + + + Program('prog.c', LIBS='foo', LIBPATH='.') + + + + + is equivalent to: + + + + + Program('prog.c', LIBS=['foo'], LIBPATH='.') + + + + + This is similar to the way that &SCons; + handles either a string or a list to + specify a single source file. + + + +
+ +
+ Finding Libraries: the &cv-LIBPATH; Construction Variable + + + + By default, the linker will only look in + certain system-defined directories for libraries. + &SCons; knows how to look for libraries + in directories that you specify with the + &cv-link-LIBPATH; construction variable. + &cv-LIBPATH; consists of a list of + directory names, like so: + + + + + + Program('prog.c', LIBS = 'm', + LIBPATH = ['/usr/lib', '/usr/local/lib']) + + + int main() { printf("prog.c\n"); } + + + + + + Using a Python list is preferred because it's portable + across systems. Alternatively, you could put all of + the directory names in a single string, separated by the + system-specific path separator character: + a colon on POSIX systems: + + + + + LIBPATH = '/usr/lib:/usr/local/lib' + + + + + or a semi-colon on Windows systems: + + + + + LIBPATH = 'C:\\lib;D:\\lib' + + + + + (Note that Python requires that the backslash + separators in a Windows path name + be escaped within strings.) + + + + + + When the linker is executed, + &SCons; will create appropriate flags + so that the linker will look for + libraries in the same directories as &SCons;. + So on a POSIX or Linux system, + a build of the above example would look like: + + + + + scons -Q + + + + + On a Windows system, + a build of the above example would look like: + + + + + scons -Q + + + + + + Note again that &SCons; has taken care of + the system-specific details of creating + the right command-line options. + + + +
diff --git a/doc/user/libraries.xml b/doc/user/libraries.xml new file mode 100644 index 0000000..e3821ce --- /dev/null +++ b/doc/user/libraries.xml @@ -0,0 +1,451 @@ + + + + + It's often useful to organize large software projects + by collecting parts of the software into one or more libraries. + &SCons; makes it easy to create libraries + and to use them in the programs. + + + +
+ Building Libraries + + + + You build your own libraries by specifying &b-link-Library; + instead of &b-link-Program;: + + + + + Library('foo', ['f1.c', 'f2.c', 'f3.c']) + + + + + &SCons; uses the appropriate library prefix and suffix for your system. + So on POSIX or Linux systems, + the above example would build as follows + (although &ranlib; may not be called on all systems): + + + + + % scons -Q + cc -o f1.o -c f1.c + cc -o f2.o -c f2.c + cc -o f3.o -c f3.c + ar rc libfoo.a f1.o f2.o f3.o + ranlib libfoo.a + + + + + On a Windows system, + a build of the above example would look like: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fof1.obj /c f1.c /nologo + cl /Fof2.obj /c f2.c /nologo + cl /Fof3.obj /c f3.c /nologo + lib /nologo /OUT:foo.lib f1.obj f2.obj f3.obj + + + + + The rules for the target name of the library + are similar to those for programs: + if you don't explicitly specify a target library name, + &SCons; will deduce one from the + name of the first source file specified, + and &SCons; will add an appropriate + file prefix and suffix if you leave them off. + + + +
+ Building Libraries From Source Code or Object Files + + + + The previous example shows building a library from a + list of source files. + You can, however, also give the &b-link-Library; call + object files, + and it will correctly realize + In fact, you can arbitrarily mix source code files + and object files in the source list: + + + + + Library('foo', ['f1.c', 'f2.o', 'f3.c', 'f4.o']) + + + + + And SCons realizes that only the source code files + must be compiled into object files + before creating the final library: + + + + + % scons -Q + cc -o f1.o -c f1.c + cc -o f3.o -c f3.c + ar rc libfoo.a f1.o f2.o f3.o f4.o + ranlib libfoo.a + + + + + Of course, in this example, the object files + must already exist for the build to succeed. + See , below, + for information about how you can + build object files explicitly + and include the built files in a library. + + + +
+ +
+ Building Static Libraries Explicitly: the &b-StaticLibrary; Builder + + + + The &b-link-Library; function builds a traditional static library. + If you want to be explicit about the type of library being built, + you can use the synonym &b-link-StaticLibrary; function + instead of &b-Library;: + + + + + StaticLibrary('foo', ['f1.c', 'f2.c', 'f3.c']) + + + + + There is no functional difference between the + &b-link-StaticLibrary; and &b-Library; functions. + + + +
+ +
+ Building Shared (DLL) Libraries: the &b-SharedLibrary; Builder + + + + If you want to build a shared library (on POSIX systems) + or a DLL file (on Windows systems), + you use the &b-link-SharedLibrary; function: + + + + + SharedLibrary('foo', ['f1.c', 'f2.c', 'f3.c']) + + + + + The output on POSIX: + + + + + % scons -Q + cc -o f1.os -c f1.c + cc -o f2.os -c f2.c + cc -o f3.os -c f3.c + cc -o libfoo.so -shared f1.os f2.os f3.os + + + + + And the output on Windows: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fof1.obj /c f1.c /nologo + cl /Fof2.obj /c f2.c /nologo + cl /Fof3.obj /c f3.c /nologo + link /nologo /dll /out:foo.dll /implib:foo.lib f1.obj f2.obj f3.obj + RegServerFunc(target, source, env) + + + + + Notice again that &SCons; takes care of + building the output file correctly, + adding the -shared option + for a POSIX compilation, + and the /dll option on Windows. + + + +
+ +
+ +
+ Linking with Libraries + + + + Usually, you build a library + because you want to link it with one or more programs. + You link libraries with a program by specifying + the libraries in the &cv-link-LIBS; construction variable, + and by specifying the directory in which + the library will be found in the + &cv-link-LIBPATH; construction variable: + + + + + + + Library('foo', ['f1.c', 'f2.c', 'f3.c']) + Program('prog.c', LIBS=['foo', 'bar'], LIBPATH='.') + + + + + Notice, of course, that you don't need to specify a library + prefix (like lib) + or suffix (like .a or .lib). + &SCons; uses the correct prefix or suffix for the current system. + + + + + + On a POSIX or Linux system, + a build of the above example would look like: + + + + + % scons -Q + cc -o f1.o -c f1.c + cc -o f2.o -c f2.c + cc -o f3.o -c f3.c + ar rc libfoo.a f1.o f2.o f3.o + ranlib libfoo.a + cc -o prog.o -c prog.c + cc -o prog prog.o -L. -lfoo -lbar + + + + + On a Windows system, + a build of the above example would look like: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fof1.obj /c f1.c /nologo + cl /Fof2.obj /c f2.c /nologo + cl /Fof3.obj /c f3.c /nologo + lib /nologo /OUT:foo.lib f1.obj f2.obj f3.obj + cl /Foprog.obj /c prog.c /nologo + link /nologo /OUT:prog.exe /LIBPATH:. foo.lib bar.lib prog.obj + + + + + As usual, notice that &SCons; has taken care + of constructing the correct command lines + to link with the specified library on each system. + + + + + + Note also that, + if you only have a single library to link with, + you can specify the library name in single string, + instead of a Python list, + so that: + + + + + Program('prog.c', LIBS='foo', LIBPATH='.') + + + + + is equivalent to: + + + + + Program('prog.c', LIBS=['foo'], LIBPATH='.') + + + + + This is similar to the way that &SCons; + handles either a string or a list to + specify a single source file. + + + +
+ +
+ Finding Libraries: the &cv-LIBPATH; Construction Variable + + + + By default, the linker will only look in + certain system-defined directories for libraries. + &SCons; knows how to look for libraries + in directories that you specify with the + &cv-link-LIBPATH; construction variable. + &cv-LIBPATH; consists of a list of + directory names, like so: + + + + + Program('prog.c', LIBS = 'm', + LIBPATH = ['/usr/lib', '/usr/local/lib']) + + + + + Using a Python list is preferred because it's portable + across systems. Alternatively, you could put all of + the directory names in a single string, separated by the + system-specific path separator character: + a colon on POSIX systems: + + + + + LIBPATH = '/usr/lib:/usr/local/lib' + + + + + or a semi-colon on Windows systems: + + + + + LIBPATH = 'C:\\lib;D:\\lib' + + + + + (Note that Python requires that the backslash + separators in a Windows path name + be escaped within strings.) + + + + + + When the linker is executed, + &SCons; will create appropriate flags + so that the linker will look for + libraries in the same directories as &SCons;. + So on a POSIX or Linux system, + a build of the above example would look like: + + + + + % scons -Q + cc -o prog.o -c prog.c + cc -o prog prog.o -L/usr/lib -L/usr/local/lib -lm + + + + + On a Windows system, + a build of the above example would look like: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Foprog.obj /c prog.c /nologo + link /nologo /OUT:prog.exe /LIBPATH:\usr\lib /LIBPATH:\usr\local\lib m.lib prog.obj + + + + + + Note again that &SCons; has taken care of + the system-specific details of creating + the right command-line options. + + + +
diff --git a/doc/user/main.in b/doc/user/main.in new file mode 100644 index 0000000..949a90a --- /dev/null +++ b/doc/user/main.in @@ -0,0 +1,385 @@ + + + + + %version; + + + %scons; + + + %builders-mod; + + + %tools-mod; + + + %variables-mod; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + SCons User Guide &buildversion; + + + Steven + Knight + + + Revision &buildrevision; (&builddate;) + + 2004, 2005, 2006, 2007, 2008 + + + 2004, 2005, 2006, 2007, 2008 + Steven Knight + + + + ©right; + + + version &buildversion; + + + + + Preface + &preface; + + + + Building and Installing &SCons; + &build-install; + + + + Simple Builds + &simple; + + + + Less Simple Things to Do With Builds + &less-simple; + + + + Building and Linking with Libraries + &libraries; + + + + Node Objects + &nodes; + + + + Dependencies + &depends; + + + + Environments + &environments; + + + + + Merging Options into the Environment: the &MergeFlags; Function + &mergeflags; + + + Separating Compile Arguments into their Variables: the &ParseFlags; Function + &parseflags; + + + Finding Installed Library Information: the &ParseConfig; Function + &parseconfig; + + + + Controlling Build Output + &output; + + + + Controlling a Build From the Command Line + &command-line; + + + + Installing Files in Other Directories: the &Install; Builder + &install; + + + + Platform-Independent File System Manipulation + &factories; + + + + Controlling Removal of Targets + &file-removal; + + + + Hierarchical Builds + &hierarchy; + + + + Separating Source and Build Directories + &separate; + + + + Variant Builds + &variants; + + + + + + Writing Your Own Builders + &builders-writing; + + + + Not Writing a Builder: the &Command; Builder + &builders-commands; + + + + Pseudo-Builders: the AddMethod function + &add-method; + + + + + + Writing Scanners + &scanners; + + + + Building From Code Repositories + &repositories; + + + + Multi-Platform Configuration (&Autoconf; Functionality) + &sconf; + + + + + + Caching Built Files + &caching; + + + + Alias Targets + &alias; + + + + Java Builds + &java; + + + + + + Miscellaneous Functionality + &misc; + + + + Troubleshooting + &troubleshoot; + + + + Construction Variables + &variables-xml; + + + + Builders + &builders; + + + + Tools + &tools; + + + + Handling Common Tasks + &tasks; + + + + + diff --git a/doc/user/main.xml b/doc/user/main.xml new file mode 100644 index 0000000..949a90a --- /dev/null +++ b/doc/user/main.xml @@ -0,0 +1,385 @@ + + + + + %version; + + + %scons; + + + %builders-mod; + + + %tools-mod; + + + %variables-mod; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + + + + + + SCons User Guide &buildversion; + + + Steven + Knight + + + Revision &buildrevision; (&builddate;) + + 2004, 2005, 2006, 2007, 2008 + + + 2004, 2005, 2006, 2007, 2008 + Steven Knight + + + + ©right; + + + version &buildversion; + + + + + Preface + &preface; + + + + Building and Installing &SCons; + &build-install; + + + + Simple Builds + &simple; + + + + Less Simple Things to Do With Builds + &less-simple; + + + + Building and Linking with Libraries + &libraries; + + + + Node Objects + &nodes; + + + + Dependencies + &depends; + + + + Environments + &environments; + + + + + Merging Options into the Environment: the &MergeFlags; Function + &mergeflags; + + + Separating Compile Arguments into their Variables: the &ParseFlags; Function + &parseflags; + + + Finding Installed Library Information: the &ParseConfig; Function + &parseconfig; + + + + Controlling Build Output + &output; + + + + Controlling a Build From the Command Line + &command-line; + + + + Installing Files in Other Directories: the &Install; Builder + &install; + + + + Platform-Independent File System Manipulation + &factories; + + + + Controlling Removal of Targets + &file-removal; + + + + Hierarchical Builds + &hierarchy; + + + + Separating Source and Build Directories + &separate; + + + + Variant Builds + &variants; + + + + + + Writing Your Own Builders + &builders-writing; + + + + Not Writing a Builder: the &Command; Builder + &builders-commands; + + + + Pseudo-Builders: the AddMethod function + &add-method; + + + + + + Writing Scanners + &scanners; + + + + Building From Code Repositories + &repositories; + + + + Multi-Platform Configuration (&Autoconf; Functionality) + &sconf; + + + + + + Caching Built Files + &caching; + + + + Alias Targets + &alias; + + + + Java Builds + &java; + + + + + + Miscellaneous Functionality + &misc; + + + + Troubleshooting + &troubleshoot; + + + + Construction Variables + &variables-xml; + + + + Builders + &builders; + + + + Tools + &tools; + + + + Handling Common Tasks + &tasks; + + + + + diff --git a/doc/user/make.in b/doc/user/make.in new file mode 100644 index 0000000..e778c61 --- /dev/null +++ b/doc/user/make.in @@ -0,0 +1,121 @@ + + + + + + + XXX + + + +
+ Differences Between &Make; and &SCons; + + + + XXX + + + +
+ +
+ Advantages of &SCons; Over &Make; + + + + XXX + + + +
diff --git a/doc/user/make.xml b/doc/user/make.xml new file mode 100644 index 0000000..e778c61 --- /dev/null +++ b/doc/user/make.xml @@ -0,0 +1,121 @@ + + + + + + + XXX + + + +
+ Differences Between &Make; and &SCons; + + + + XXX + + + +
+ +
+ Advantages of &SCons; Over &Make; + + + + XXX + + + +
diff --git a/doc/user/mergeflags.in b/doc/user/mergeflags.in new file mode 100644 index 0000000..b50992b --- /dev/null +++ b/doc/user/mergeflags.in @@ -0,0 +1,137 @@ + + + + + &SCons; construction environments have a &MergeFlags; method + that merges a dictionary of values into the construction environment. + &MergeFlags; treats each value in the dictionary + as a list of options such as one might pass to a command + (such as a compiler or linker). + &MergeFlags; will not duplicate an option + if it already exists in the construction environment variable. + + + + + + &MergeFlags; tries to be intelligent about merging options. + When merging options to any variable + whose name ends in PATH, + &MergeFlags; keeps the leftmost occurrence of the option, + because in typical lists of directory paths, + the first occurrence "wins." + When merging options to any other variable name, + &MergeFlags; keeps the rightmost occurrence of the option, + because in a list of typical command-line options, + the last occurrence "wins." + + + + + + env = Environment() + env.Append(CCFLAGS = '-option -O3 -O1') + flags = { 'CCFLAGS' : '-whatever -O3' } + env.MergeFlags(flags) + print env['CCFLAGS'] + + + + + scons -Q + + + + + Note that the default value for &cv-link-CCFLAGS; + + is an internal &SCons; object + which automatically converts + the options we specified as a string into a list. + + + + + + env = Environment() + env.Append(CPPPATH = ['/include', '/usr/local/include', '/usr/include']) + flags = { 'CPPPATH' : ['/usr/opt/include', '/usr/local/include'] } + env.MergeFlags(flags) + print env['CPPPATH'] + + + + + scons -Q + + + + + Note that the default value for &cv-link-CPPPATH; + + is a normal Python list, + so we must specify its values as a list + in the dictionary we pass to the &MergeFlags; function. + + + + + + If &MergeFlags; is passed anything other than a dictionary, + it calls the &ParseFlags; method to convert it into a dictionary. + + + + + + env = Environment() + env.Append(CCFLAGS = '-option -O3 -O1') + env.Append(CPPPATH = ['/include', '/usr/local/include', '/usr/include']) + env.MergeFlags('-whatever -I/usr/opt/include -O3 -I/usr/local/include') + print env['CCFLAGS'] + print env['CPPPATH'] + + + + + scons -Q + + + + + In the combined example above, + &ParseFlags; has sorted the options into their corresponding variables + and returned a dictionary for &MergeFlags; to apply + to the construction variables + in the specified construction environment. + + diff --git a/doc/user/mergeflags.xml b/doc/user/mergeflags.xml new file mode 100644 index 0000000..7edc986 --- /dev/null +++ b/doc/user/mergeflags.xml @@ -0,0 +1,138 @@ + + + + + &SCons; construction environments have a &MergeFlags; method + that merges a dictionary of values into the construction environment. + &MergeFlags; treats each value in the dictionary + as a list of options such as one might pass to a command + (such as a compiler or linker). + &MergeFlags; will not duplicate an option + if it already exists in the construction environment variable. + + + + + + &MergeFlags; tries to be intelligent about merging options. + When merging options to any variable + whose name ends in PATH, + &MergeFlags; keeps the leftmost occurrence of the option, + because in typical lists of directory paths, + the first occurrence "wins." + When merging options to any other variable name, + &MergeFlags; keeps the rightmost occurrence of the option, + because in a list of typical command-line options, + the last occurrence "wins." + + + + + env = Environment() + env.Append(CCFLAGS = '-option -O3 -O1') + flags = { 'CCFLAGS' : '-whatever -O3' } + env.MergeFlags(flags) + print env['CCFLAGS'] + + + + % scons -Q + ['-option', '-O1', '-whatever', '-O3'] + scons: `.' is up to date. + + + + + Note that the default value for &cv-link-CCFLAGS; + + is an internal &SCons; object + which automatically converts + the options we specified as a string into a list. + + + + + env = Environment() + env.Append(CPPPATH = ['/include', '/usr/local/include', '/usr/include']) + flags = { 'CPPPATH' : ['/usr/opt/include', '/usr/local/include'] } + env.MergeFlags(flags) + print env['CPPPATH'] + + + + % scons -Q + ['/include', '/usr/local/include', '/usr/include', '/usr/opt/include'] + scons: `.' is up to date. + + + + + Note that the default value for &cv-link-CPPPATH; + + is a normal Python list, + so we must specify its values as a list + in the dictionary we pass to the &MergeFlags; function. + + + + + + If &MergeFlags; is passed anything other than a dictionary, + it calls the &ParseFlags; method to convert it into a dictionary. + + + + + env = Environment() + env.Append(CCFLAGS = '-option -O3 -O1') + env.Append(CPPPATH = ['/include', '/usr/local/include', '/usr/include']) + env.MergeFlags('-whatever -I/usr/opt/include -O3 -I/usr/local/include') + print env['CCFLAGS'] + print env['CPPPATH'] + + + + % scons -Q + ['-option', '-O1', '-whatever', '-O3'] + ['/include', '/usr/local/include', '/usr/include', '/usr/opt/include'] + scons: `.' is up to date. + + + + + In the combined example above, + &ParseFlags; has sorted the options into their corresponding variables + and returned a dictionary for &MergeFlags; to apply + to the construction variables + in the specified construction environment. + + diff --git a/doc/user/misc.in b/doc/user/misc.in new file mode 100644 index 0000000..6679134 --- /dev/null +++ b/doc/user/misc.in @@ -0,0 +1,606 @@ + + + + + &SCons; supports a lot of additional functionality + that doesn't readily fit into the other chapters. + + + +
+ Verifying the Python Version: the &EnsurePythonVersion; Function + + + + Although the &SCons; code itself will run + on any Python version 1.5.2 or later, + you are perfectly free to make use of + Python syntax and modules from more modern versions + (for example, Python 2.4 or 2.5) + when writing your &SConscript; files + or your own local modules. + If you do this, it's usually helpful to + configure &SCons; to exit gracefully with an error message + if it's being run with a version of Python + that simply won't work with your code. + This is especially true if you're going to use &SCons; + to build source code that you plan to distribute publicly, + where you can't be sure of the Python version + that an anonymous remote user might use + to try to build your software. + + + + + + &SCons; provides an &EnsurePythonVersion; function for this. + You simply pass it the major and minor versions + numbers of the version of Python you require: + + + + + + + EnsurePythonVersion(2, 5) + + + + + And then &SCons will exit with the following error + message when a user runs it with an unsupported + earlier version of Python: + + + + + + + % scons -Q + Python 2.5 or greater required, but you have Python 2.3.6 + + +
+ +
+ Verifying the SCons Version: the &EnsureSConsVersion; Function + + + + You may, of course, write your &SConscript; files + to use features that were only added in + recent versions of &SCons;. + When you publicly distribute software that is built using &SCons;, + it's helpful to have &SCons; + verify the version being used and + exit gracefully with an error message + if the user's version of &SCons; won't work + with your &SConscript; files. + &SCons; provides an &EnsureSConsVersion; function + that verifies the version of &SCons; + in the same + the &EnsurePythonVersion; function + verifies the version of Python, + by passing in the major and minor versions + numbers of the version of SCons you require: + + + + + + + EnsureSConsVersion(1, 0) + + + + + And then &SCons will exit with the following error + message when a user runs it with an unsupported + earlier version of &SCons;: + + + + + + + % scons -Q + SCons 1.0 or greater required, but you have SCons 0.98.5 + + +
+ +
+ Explicitly Terminating &SCons; While Reading &SConscript; Files: the &Exit; Function + + + + &SCons; supports an &Exit; function + which can be used to terminate &SCons; + while reading the &SConscript; files, + usually because you've detected a condition + under which it doesn't make sense to proceed: + + + + + + if ARGUMENTS.get('FUTURE'): + print "The FUTURE option is not supported yet!" + Exit(2) + env = Environment() + env.Program('hello.c') + + + hello.c + + + + + scons -Q FUTURE=1 + scons -Q + + + + + The &Exit; function takes as an argument + the (numeric) exit status that you want &SCons; to exit with. + If you don't specify a value, + the default is to exit with 0, + which indicates successful execution. + + + + + + Note that the &Exit; function + is equivalent to calling the Python + sys.exit function + (which the it actually calls), + but because &Exit; is a &SCons; function, + you don't have to import the Python + sys module to use it. + + + +
+ +
+ Searching for Files: the &FindFile; Function + + + + The &FindFile; function searches for a file in a list of directories. + If there is only one directory, it can be given as a simple string. + The function returns a File node if a matching file exists, + or None if no file is found. + (See the documentation for the &Glob; function for an alternative way + of searching for entries in a directory.) + + + + + + # one directory + print FindFile('missing', '.') + t = FindFile('exists', '.') + print t.__class__, t + + + exists + + + + + scons -Q + + + + + # several directories + includes = [ '.', 'include', 'src/include'] + headers = [ 'nonesuch.h', 'config.h', 'private.h', 'dist.h'] + for hdr in headers: + print '%-12s' % ('%s:' % hdr), FindFile(hdr, includes) + + + exists + + + + + + exists + + + + exists + + + + scons -Q + + + + + + + If the file exists in more than one directory, + only the first occurrence is returned. + + + + + + print FindFile('multiple', ['sub1', 'sub2', 'sub3']) + print FindFile('multiple', ['sub2', 'sub3', 'sub1']) + print FindFile('multiple', ['sub3', 'sub1', 'sub2']) + + + + exists + + + + exists + + + + exists + + + + + scons -Q + + + + + + + In addition to existing files, &FindFile; will also find derived files + (that is, non-leaf files) that haven't been built yet. + (Leaf files should already exist, or the build will fail!) + + + + + + # Neither file exists, so build will fail + Command('derived', 'leaf', 'cat >$TARGET $SOURCE') + print FindFile('leaf', '.') + print FindFile('derived', '.') + + + + + scons -Q + + + + + # Only 'leaf' exists + Command('derived', 'leaf', 'cat >$TARGET $SOURCE') + print FindFile('leaf', '.') + print FindFile('derived', '.') + + + leaf + + + + + scons -Q + + + + + If a source file exists, &FindFile; will correctly return the name + in the build directory. + + + + + + # Only 'src/leaf' exists + VariantDir('build', 'src') + print FindFile('leaf', 'build') + + + + leaf + + + + + scons -Q + + +
+ +
+ Handling Nested Lists: the &Flatten; Function + + + + &SCons; supports a &Flatten; function + which takes an input Python sequence + (list or tuple) + and returns a flattened list + containing just the individual elements of + the sequence. + This can be handy when trying to examine + a list composed of the lists + returned by calls to various Builders. + For example, you might collect + object files built in different ways + into one call to the &Program; Builder + by just enclosing them in a list, as follows: + + + + + + objects = [ + Object('prog1.c'), + Object('prog2.c', CCFLAGS='-DFOO'), + ] + Program(objects) + + + prog1.c + + + prog2.c + + + + + + Because the Builder calls in &SCons; + flatten their input lists, + this works just fine to build the program: + + + + + scons -Q + + + + + But if you were debugging your build + and wanted to print the absolute path + of each object file in the + objects list, + you might try the following simple approach, + trying to print each Node's + abspath + attribute: + + + + + + objects = [ + Object('prog1.c'), + Object('prog2.c', CCFLAGS='-DFOO'), + ] + Program(objects) + + for object_file in objects: + print object_file.abspath + + + prog1.c + + + prog2.c + + + + + + This does not work as expected + because each call to str + is operating an embedded list returned by + each &Object; call, + not on the underlying Nodes within those lists: + + + + + scons -Q + + + + + The solution is to use the &Flatten; function + so that you can pass each Node to + the str separately: + + + + + + objects = [ + Object('prog1.c'), + Object('prog2.c', CCFLAGS='-DFOO'), + ] + Program(objects) + + for object_file in Flatten(objects): + print object_file.abspath + + + prog1.c + + + prog2.c + + + + + + + % scons -Q + /home/me/project/prog1.o + /home/me/project/prog2.o + cc -o prog1.o -c prog1.c + cc -o prog2.o -c -DFOO prog2.c + cc -o prog1 prog1.o prog2.o + + +
+ +
+ Finding the Invocation Directory: the &GetLaunchDir; Function + + + + If you need to find the directory from + which the user invoked the &scons; command, + you can use the &GetLaunchDir; function: + + + + + env = Environment( + LAUNCHDIR = GetLaunchDir(), + ) + env.Command('directory_build_info', + '$LAUNCHDIR/build_info' + Copy('$TARGET', '$SOURCE')) + + + + + Because &SCons; is usually invoked from the top-level + directory in which the &SConstruct; file lives, + the Python os.getcwd() + is often equivalent. + However, the &SCons; + -u, + -U + and + -D + command-line options, + when invoked from a subdirectory, + will cause &SCons; to change to the directory + in which the &SConstruct; file is found. + When those options are used, + &GetLaunchDir; will still return the path to the + user's invoking subdirectory, + allowing the &SConscript; configuration + to still get at configuration (or other) files + from the originating directory. + + + +
diff --git a/doc/user/misc.xml b/doc/user/misc.xml new file mode 100644 index 0000000..a2305ee --- /dev/null +++ b/doc/user/misc.xml @@ -0,0 +1,565 @@ + + + + + &SCons; supports a lot of additional functionality + that doesn't readily fit into the other chapters. + + + +
+ Verifying the Python Version: the &EnsurePythonVersion; Function + + + + Although the &SCons; code itself will run + on any Python version 1.5.2 or later, + you are perfectly free to make use of + Python syntax and modules from more modern versions + (for example, Python 2.4 or 2.5) + when writing your &SConscript; files + or your own local modules. + If you do this, it's usually helpful to + configure &SCons; to exit gracefully with an error message + if it's being run with a version of Python + that simply won't work with your code. + This is especially true if you're going to use &SCons; + to build source code that you plan to distribute publicly, + where you can't be sure of the Python version + that an anonymous remote user might use + to try to build your software. + + + + + + &SCons; provides an &EnsurePythonVersion; function for this. + You simply pass it the major and minor versions + numbers of the version of Python you require: + + + + + + + EnsurePythonVersion(2, 5) + + + + + And then &SCons; will exit with the following error + message when a user runs it with an unsupported + earlier version of Python: + + + + + + + % scons -Q + Python 2.5 or greater required, but you have Python 2.3.6 + + +
+ +
+ Verifying the SCons Version: the &EnsureSConsVersion; Function + + + + You may, of course, write your &SConscript; files + to use features that were only added in + recent versions of &SCons;. + When you publicly distribute software that is built using &SCons;, + it's helpful to have &SCons; + verify the version being used and + exit gracefully with an error message + if the user's version of &SCons; won't work + with your &SConscript; files. + &SCons; provides an &EnsureSConsVersion; function + that verifies the version of &SCons; + in the same + the &EnsurePythonVersion; function + verifies the version of Python, + by passing in the major and minor versions + numbers of the version of SCons you require: + + + + + + + EnsureSConsVersion(1, 0) + + + + + And then &SCons; will exit with the following error + message when a user runs it with an unsupported + earlier version of &SCons;: + + + + + + + % scons -Q + SCons 1.0 or greater required, but you have SCons 0.98.5 + + +
+ +
+ Explicitly Terminating &SCons; While Reading &SConscript; Files: the &Exit; Function + + + + &SCons; supports an &Exit; function + which can be used to terminate &SCons; + while reading the &SConscript; files, + usually because you've detected a condition + under which it doesn't make sense to proceed: + + + + + if ARGUMENTS.get('FUTURE'): + print "The FUTURE option is not supported yet!" + Exit(2) + env = Environment() + env.Program('hello.c') + + + + % scons -Q FUTURE=1 + The FUTURE option is not supported yet! + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + The &Exit; function takes as an argument + the (numeric) exit status that you want &SCons; to exit with. + If you don't specify a value, + the default is to exit with 0, + which indicates successful execution. + + + + + + Note that the &Exit; function + is equivalent to calling the Python + sys.exit function + (which the it actually calls), + but because &Exit; is a &SCons; function, + you don't have to import the Python + sys module to use it. + + + +
+ +
+ Searching for Files: the &FindFile; Function + + + + The &FindFile; function searches for a file in a list of directories. + If there is only one directory, it can be given as a simple string. + The function returns a File node if a matching file exists, + or None if no file is found. + (See the documentation for the &Glob; function for an alternative way + of searching for entries in a directory.) + + + + + # one directory + print FindFile('missing', '.') + t = FindFile('exists', '.') + print t.__class__, t + + + + % scons -Q + None + SCons.Node.FS.File exists + scons: `.' is up to date. + + + + # several directories + includes = [ '.', 'include', 'src/include'] + headers = [ 'nonesuch.h', 'config.h', 'private.h', 'dist.h'] + for hdr in headers: + print '%-12s' % ('%s:' % hdr), FindFile(hdr, includes) + + + + % scons -Q + nonesuch.h: None + config.h: config.h + private.h: src/include/private.h + dist.h: include/dist.h + scons: `.' is up to date. + + + + + + + If the file exists in more than one directory, + only the first occurrence is returned. + + + + + print FindFile('multiple', ['sub1', 'sub2', 'sub3']) + print FindFile('multiple', ['sub2', 'sub3', 'sub1']) + print FindFile('multiple', ['sub3', 'sub1', 'sub2']) + + + + % scons -Q + sub1/multiple + sub2/multiple + sub3/multiple + scons: `.' is up to date. + + + + + + + In addition to existing files, &FindFile; will also find derived files + (that is, non-leaf files) that haven't been built yet. + (Leaf files should already exist, or the build will fail!) + + + + + # Neither file exists, so build will fail + Command('derived', 'leaf', 'cat >$TARGET $SOURCE') + print FindFile('leaf', '.') + print FindFile('derived', '.') + + + + % scons -Q + None + derived + scons: *** [derived] Source `leaf' not found, needed by target `derived'. + + + + # Neither file exists, so build will fail + Command('derived', 'leaf', 'cat >$TARGET $SOURCE') + print FindFile('leaf', '.') + print FindFile('derived', '.') + + # Only 'leaf' exists + Command('derived', 'leaf', 'cat >$TARGET $SOURCE') + print FindFile('leaf', '.') + print FindFile('derived', '.') + + + + % scons -Q + leaf + derived + cat > derived leaf + + + + + If a source file exists, &FindFile; will correctly return the name + in the build directory. + + + + + # Only 'src/leaf' exists + VariantDir('build', 'src') + print FindFile('leaf', 'build') + + + + % scons -Q + build/leaf + scons: `.' is up to date. + + +
+ +
+ Handling Nested Lists: the &Flatten; Function + + + + &SCons; supports a &Flatten; function + which takes an input Python sequence + (list or tuple) + and returns a flattened list + containing just the individual elements of + the sequence. + This can be handy when trying to examine + a list composed of the lists + returned by calls to various Builders. + For example, you might collect + object files built in different ways + into one call to the &Program; Builder + by just enclosing them in a list, as follows: + + + + + objects = [ + Object('prog1.c'), + Object('prog2.c', CCFLAGS='-DFOO'), + ] + Program(objects) + + + + + Because the Builder calls in &SCons; + flatten their input lists, + this works just fine to build the program: + + + + + % scons -Q + cc -o prog1.o -c prog1.c + cc -o prog2.o -c -DFOO prog2.c + cc -o prog1 prog1.o prog2.o + + + + + But if you were debugging your build + and wanted to print the absolute path + of each object file in the + objects list, + you might try the following simple approach, + trying to print each Node's + abspath + attribute: + + + + + objects = [ + Object('prog1.c'), + Object('prog2.c', CCFLAGS='-DFOO'), + ] + Program(objects) + + for object_file in objects: + print object_file.abspath + + + + + This does not work as expected + because each call to str + is operating an embedded list returned by + each &Object; call, + not on the underlying Nodes within those lists: + + + + + % scons -Q + AttributeError: NodeList instance has no attribute 'abspath': + File "/home/my/project/SConstruct", line 8: + print object_file.abspath + + + + + The solution is to use the &Flatten; function + so that you can pass each Node to + the str separately: + + + + + objects = [ + Object('prog1.c'), + Object('prog2.c', CCFLAGS='-DFOO'), + ] + Program(objects) + + for object_file in Flatten(objects): + print object_file.abspath + + + + + + % scons -Q + /home/me/project/prog1.o + /home/me/project/prog2.o + cc -o prog1.o -c prog1.c + cc -o prog2.o -c -DFOO prog2.c + cc -o prog1 prog1.o prog2.o + + +
+ +
+ Finding the Invocation Directory: the &GetLaunchDir; Function + + + + If you need to find the directory from + which the user invoked the &scons; command, + you can use the &GetLaunchDir; function: + + + + + env = Environment( + LAUNCHDIR = GetLaunchDir(), + ) + env.Command('directory_build_info', + '$LAUNCHDIR/build_info' + Copy('$TARGET', '$SOURCE')) + + + + + Because &SCons; is usually invoked from the top-level + directory in which the &SConstruct; file lives, + the Python os.getcwd() + is often equivalent. + However, the &SCons; + -u, + -U + and + -D + command-line options, + when invoked from a subdirectory, + will cause &SCons; to change to the directory + in which the &SConstruct; file is found. + When those options are used, + &GetLaunchDir; will still return the path to the + user's invoking subdirectory, + allowing the &SConscript; configuration + to still get at configuration (or other) files + from the originating directory. + + + +
diff --git a/doc/user/nodes.in b/doc/user/nodes.in new file mode 100644 index 0000000..c914ce5 --- /dev/null +++ b/doc/user/nodes.in @@ -0,0 +1,386 @@ + + + + + Internally, &SCons; represents all of the files + and directories it knows about as &Nodes;. + These internal objects + (not object files) + can be used in a variety of ways + to make your &SConscript; + files portable and easy to read. + + + +
+ Builder Methods Return Lists of Target Nodes + + + + All builder methods return a list of + &Node; objects that identify the + target file or files that will be built. + These returned &Nodes; can be passed + as arguments to other builder methods. + + + + + + For example, suppose that we want to build + the two object files that make up a program with different options. + This would mean calling the &b-link-Object; + builder once for each object file, + specifying the desired options: + + + + + Object('hello.c', CCFLAGS='-DHELLO') + Object('goodbye.c', CCFLAGS='-DGOODBYE') + + + + + One way to combine these object files + into the resulting program + would be to call the &b-link-Program; + builder with the names of the object files + listed as sources: + + + + + Object('hello.c', CCFLAGS='-DHELLO') + Object('goodbye.c', CCFLAGS='-DGOODBYE') + Program(['hello.o', 'goodbye.o']) + + + + + The problem with specifying the names as strings + is that our &SConstruct; file is no longer portable + across operating systems. + It won't, for example, work on Windows + because the object files there would be + named &hello_obj; and &goodbye_obj;, + not &hello_o; and &goodbye_o;. + + + + + + A better solution is to assign the lists of targets + returned by the calls to the &b-Object; builder to variables, + which we can then concatenate in our + call to the &b-Program; builder: + + + + + + hello_list = Object('hello.c', CCFLAGS='-DHELLO') + goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE') + Program(hello_list + goodbye_list) + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Goodbye, world!\n"); } + + + + + + This makes our &SConstruct; file portable again, + the build output on Linux looking like: + + + + + scons -Q + + + + + And on Windows: + + + + + scons -Q + + + + + We'll see examples of using the list of nodes + returned by builder methods throughout + the rest of this guide. + + + +
+ +
+ Explicitly Creating File and Directory Nodes + + + + It's worth mentioning here that + &SCons; maintains a clear distinction + between Nodes that represent files + and Nodes that represent directories. + &SCons; supports &File; and &Dir; + functions that, respectively, + return a file or directory Node: + + + + + + hello_c = File('hello.c') + Program(hello_c) + + classes = Dir('classes') + Java(classes, 'src') + + + + + + Normally, you don't need to call + &File; or &Dir; directly, + because calling a builder method automatically + treats strings as the names of files or directories, + and translates them into + the Node objects for you. + The &File; and &Dir; functions can come in handy + in situations where you need to explicitly + instruct &SCons; about the type of Node being + passed to a builder or other function, + or unambiguously refer to a specific + file in a directory tree. + + + + + + + There are also times when you may need to + refer to an entry in a file system + without knowing in advance + whether it's a file or a directory. + For those situations, + &SCons; also supports an &Entry; function, + which returns a Node + that can represent either a file or a directory. + + + + + xyzzy = Entry('xyzzy') + + + + + The returned xyzzy Node + will be turned into a file or directory Node + the first time it is used by a builder method + or other function that + requires one vs. the other. + + + +
+ +
+ Printing &Node; File Names + + + + One of the most common things you can do + with a Node is use it to print the + file name that the node represents. + Keep in mind, though, that because the object + returned by a builder call + is a list of Nodes, + you must use Python subscripts + to fetch individual Nodes from the list. + For example, the following &SConstruct; file: + + + + + + object_list = Object('hello.c') + program_list = Program(object_list) + print "The object file is:", object_list[0] + print "The program file is:", program_list[0] + + + int main() { printf("Hello, world!\n"); } + + + + + + Would print the following file names on a POSIX system: + + + + + scons -Q + + + + + And the following file names on a Windows system: + + + + + scons -Q + + + + + Note that in the above example, + the object_list[0] + extracts an actual Node object + from the list, + and the Python print statement + converts the object to a string for printing. + + + +
+ +
+ Using a &Node;'s File Name as a String + + + + Printing a &Node;'s name + as described in the previous section + works because the string representation of a &Node; object + is the name of the file. + If you want to do something other than + print the name of the file, + you can fetch it by using the builtin Python + &str; function. + For example, if you want to use the Python + os.path.exists + to figure out whether a file + exists while the &SConstruct; file + is being read and executed, + you can fetch the string as follows: + + + + + + import os.path + program_list = Program('hello.c') + program_name = str(program_list[0]) + if not os.path.exists(program_name): + print program_name, "does not exist!" + + + int main() { printf("Hello, world!\n"); } + + + + + + Which executes as follows on a POSIX system: + + + + + scons -Q + + +
+ + + + diff --git a/doc/user/nodes.xml b/doc/user/nodes.xml new file mode 100644 index 0000000..46215c4 --- /dev/null +++ b/doc/user/nodes.xml @@ -0,0 +1,401 @@ + + + + + Internally, &SCons; represents all of the files + and directories it knows about as &Nodes;. + These internal objects + (not object files) + can be used in a variety of ways + to make your &SConscript; + files portable and easy to read. + + + +
+ Builder Methods Return Lists of Target Nodes + + + + All builder methods return a list of + &Node; objects that identify the + target file or files that will be built. + These returned &Nodes; can be passed + as arguments to other builder methods. + + + + + + For example, suppose that we want to build + the two object files that make up a program with different options. + This would mean calling the &b-link-Object; + builder once for each object file, + specifying the desired options: + + + + + Object('hello.c', CCFLAGS='-DHELLO') + Object('goodbye.c', CCFLAGS='-DGOODBYE') + + + + + One way to combine these object files + into the resulting program + would be to call the &b-link-Program; + builder with the names of the object files + listed as sources: + + + + + Object('hello.c', CCFLAGS='-DHELLO') + Object('goodbye.c', CCFLAGS='-DGOODBYE') + Program(['hello.o', 'goodbye.o']) + + + + + The problem with specifying the names as strings + is that our &SConstruct; file is no longer portable + across operating systems. + It won't, for example, work on Windows + because the object files there would be + named &hello_obj; and &goodbye_obj;, + not &hello_o; and &goodbye_o;. + + + + + + A better solution is to assign the lists of targets + returned by the calls to the &b-Object; builder to variables, + which we can then concatenate in our + call to the &b-Program; builder: + + + + + hello_list = Object('hello.c', CCFLAGS='-DHELLO') + goodbye_list = Object('goodbye.c', CCFLAGS='-DGOODBYE') + Program(hello_list + goodbye_list) + + + + + This makes our &SConstruct; file portable again, + the build output on Linux looking like: + + + + + % scons -Q + cc -o goodbye.o -c -DGOODBYE goodbye.c + cc -o hello.o -c -DHELLO hello.c + cc -o hello hello.o goodbye.o + + + + + And on Windows: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fogoodbye.obj /c goodbye.c -DGOODBYE + cl /Fohello.obj /c hello.c -DHELLO + link /nologo /OUT:hello.exe hello.obj goodbye.obj + + + + + We'll see examples of using the list of nodes + returned by builder methods throughout + the rest of this guide. + + + +
+ +
+ Explicitly Creating File and Directory Nodes + + + + It's worth mentioning here that + &SCons; maintains a clear distinction + between Nodes that represent files + and Nodes that represent directories. + &SCons; supports &File; and &Dir; + functions that, respectively, + return a file or directory Node: + + + + + hello_c = File('hello.c') + Program(hello_c) + + classes = Dir('classes') + Java(classes, 'src') + + + + + Normally, you don't need to call + &File; or &Dir; directly, + because calling a builder method automatically + treats strings as the names of files or directories, + and translates them into + the Node objects for you. + The &File; and &Dir; functions can come in handy + in situations where you need to explicitly + instruct &SCons; about the type of Node being + passed to a builder or other function, + or unambiguously refer to a specific + file in a directory tree. + + + + + + + There are also times when you may need to + refer to an entry in a file system + without knowing in advance + whether it's a file or a directory. + For those situations, + &SCons; also supports an &Entry; function, + which returns a Node + that can represent either a file or a directory. + + + + + xyzzy = Entry('xyzzy') + + + + + The returned xyzzy Node + will be turned into a file or directory Node + the first time it is used by a builder method + or other function that + requires one vs. the other. + + + +
+ +
+ Printing &Node; File Names + + + + One of the most common things you can do + with a Node is use it to print the + file name that the node represents. + Keep in mind, though, that because the object + returned by a builder call + is a list of Nodes, + you must use Python subscripts + to fetch individual Nodes from the list. + For example, the following &SConstruct; file: + + + + + hello_c = File('hello.c') + Program(hello_c) + + classes = Dir('classes') + Java(classes, 'src') + + object_list = Object('hello.c') + program_list = Program(object_list) + print "The object file is:", object_list[0] + print "The program file is:", program_list[0] + + + + + Would print the following file names on a POSIX system: + + + + + % scons -Q + The object file is: hello.o + The program file is: hello + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + And the following file names on a Windows system: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + The object file is: hello.obj + The program file is: hello.exe + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:hello.exe hello.obj + + + + + Note that in the above example, + the object_list[0] + extracts an actual Node object + from the list, + and the Python print statement + converts the object to a string for printing. + + + +
+ +
+ Using a &Node;'s File Name as a String + + + + Printing a &Node;'s name + as described in the previous section + works because the string representation of a &Node; object + is the name of the file. + If you want to do something other than + print the name of the file, + you can fetch it by using the builtin Python + &str; function. + For example, if you want to use the Python + os.path.exists + to figure out whether a file + exists while the &SConstruct; file + is being read and executed, + you can fetch the string as follows: + + + + + import os.path + program_list = Program('hello.c') + program_name = str(program_list[0]) + if not os.path.exists(program_name): + print program_name, "does not exist!" + + + + + Which executes as follows on a POSIX system: + + + + + % scons -Q + hello does not exist! + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ + + + diff --git a/doc/user/output.in b/doc/user/output.in new file mode 100644 index 0000000..1f600f3 --- /dev/null +++ b/doc/user/output.in @@ -0,0 +1,681 @@ + + + + + A key aspect of creating a usable build configuration + is providing good output from the build + so its users can readily understand + what the build is doing + and get information about how to control the build. + &SCons; provides several ways of + controlling output from the build configuration + to help make the build + more useful and understandable. + + + +
+ Providing Build Help: the &Help; Function + + + + It's often very useful to be able to give + users some help that describes the + specific targets, build options, etc., + that can be used for your build. + &SCons; provides the &Help; function + to allow you to specify this help text: + + + + + + Help(""" + Type: 'scons program' to build the production program, + 'scons debug' to build the debug version. + """) + + + + + + (Note the above use of the Python triple-quote syntax, + which comes in very handy for + specifying multi-line strings like help text.) + + + + + + When the &SConstruct; or &SConscript; files + contain such a call to the &Help; function, + the specified help text will be displayed in response to + the &SCons; -h option: + + + + + scons -h + + + + + The &SConscript; files may contain + multiple calls to the &Help; function, + in which case the specified text(s) + will be concatenated when displayed. + This allows you to split up the + help text across multiple &SConscript; files. + In this situation, the order in + which the &SConscript; files are called + will determine the order in which the &Help; functions are called, + which will determine the order in which + the various bits of text will get concatenated. + + + + + + Another use would be to make the help text conditional + on some variable. + For example, suppose you only want to display + a line about building a Windows-only + version of a program when actually + run on Windows. + The following &SConstruct; file: + + + + + + env = Environment() + + Help("\nType: 'scons program' to build the production program.\n") + + if env['PLATFORM'] == 'win32': + Help("\nType: 'scons windebug' to build the Windows debug version.\n") + + + + + + Will display the complete help text on Windows: + + + + + scons -h + + + + + But only show the relevant option on a Linux or UNIX system: + + + + + scons -h + + + + + If there is no &Help; text in the &SConstruct; or + &SConscript; files, + &SCons; will revert to displaying its + standard list that describes the &SCons; command-line + options. + This list is also always displayed whenever + the -H option is used. + + + +
+ +
+ Controlling How &SCons; Prints Build Commands: the <envar>$*COMSTR</envar> Variables + + + + Sometimes the commands executed + to compile object files or link programs + (or build other targets) + can get very long, + long enough to make it difficult for users + to distinguish error messages or + other important build output + from the commands themselves. + All of the default $*COM variables + that specify the command lines + used to build various types of target files + have a corresponding $*COMSTR variable + that can be set to an alternative + string that will be displayed + when the target is built. + + + + + + For example, suppose you want to + have &SCons; display a + "Compiling" + message whenever it's compiling an object file, + and a + "Linking" + when it's linking an executable. + You could write a &SConstruct; file + that looks like: + + + + + + env = Environment(CCCOMSTR = "Compiling $TARGET", + LINKCOMSTR = "Linking $TARGET") + env.Program('foo.c') + + + foo.c + + + + + + Which would then yield the output: + + + + + + + % scons -Q + Compiling foo.o + Linking foo + + + + + &SCons; performs complete variable substitution + on $*COMSTR variables, + so they have access to all of the + standard variables like &cv-TARGET; &cv-SOURCES;, etc., + as well as any construction variables + that happen to be configured in + the construction environment + used to build a specific target. + + + + + + Of course, sometimes it's still important to + be able to see the exact command + that &SCons; will execute to build a target. + For example, you may simply need to verify + that &SCons; is configured to supply + the right options to the compiler, + or a developer may want to + cut-and-paste a compile command + to add a few options + for a custom test. + + + + + + One common way to give users + control over whether or not + &SCons; should print the actual command line + or a short, configured summary + is to add support for a + VERBOSE + command-line variable to your &SConstruct; file. + A simple configuration for this might look like: + + + + + + env = Environment() + if ARGUMENTS.get('VERBOSE') != "1': + env['CCCOMSTR'] = "Compiling $TARGET" + env['LINKCOMSTR'] = "Linking $TARGET" + env.Program('foo.c') + + + foo.c + + + + + + + By only setting the appropriate + $*COMSTR variables + if the user specifies + VERBOSE=1 + on the command line, + the user has control + over how &SCons; + displays these particular command lines: + + + + + + + % scons -Q + Compiling foo.o + Linking foo + % scons -Q -c + Removed foo.o + Removed foo + % scons -Q VERBOSE=1 + cc -o foo.o -c foo.c + cc -o foo foo.o + + +
+ +
+ Providing Build Progress Output: the &Progress; Function + + + + Another aspect of providing good build output + is to give the user feedback + about what &SCons; is doing + even when nothing is being built at the moment. + This can be especially true for large builds + when most of the targets are already up-to-date. + Because &SCons; can take a long time + making absolutely sure that every + target is, in fact, up-to-date + with respect to a lot of dependency files, + it can be easy for users to mistakenly + conclude that &SCons; is hung + or that there is some other problem with the build. + + + + + + One way to deal with this perception + is to configure &SCons; to print something to + let the user know what it's "thinking about." + The &Progress; function + allows you to specify a string + that will be printed for every file + that &SCons; is "considering" + while it is traversing the dependency graph + to decide what targets are or are not up-to-date. + + + + + + Progress('Evaluating $TARGET\n') + Program('f1.c') + Program('f2.c') + + + f1.c + + + f2.c + + + + + + Note that the &Progress; function does not + arrange for a newline to be printed automatically + at the end of the string (as does the Python + print statement), + and we must specify the + \n + that we want printed at the end of the configured string. + This configuration, then, + will have &SCons; + print that it is Evaluating + each file that it encounters + in turn as it traverses the dependency graph: + + + + + scons -Q + + + + + Of course, normally you don't want to add + all of these additional lines to your build output, + as that can make it difficult for the user + to find errors or other important messages. + A more useful way to display + this progress might be + to have the file names printed + directly to the user's screen, + not to the same standard output + stream where build output is printed, + and to use a carriage return character + (\r) + so that each file name gets re-printed on the same line. + Such a configuration would look like: + + + + + Progress('$TARGET\r', + file=open('/dev/tty', 'w'), + overwrite=True) + Program('f1.c') + Program('f2.c') + + + + + Note that we also specified the + overwrite=True argument + to the &Progress; function, + which causes &SCons; to + "wipe out" the previous string with space characters + before printing the next &Progress; string. + Without the + overwrite=True argument, + a shorter file name would not overwrite + all of the charactes in a longer file name that + precedes it, + making it difficult to tell what the + actual file name is on the output. + Also note that we opened up the + /dev/tty file + for direct access (on POSIX) to + the user's screen. + On Windows, the equivalent would be to open + the con: file name. + + + + + + Also, it's important to know that although you can use + $TARGET to substitute the name of + the node in the string, + the &Progress; function does not + perform general variable substitution + (because there's not necessarily a construction + environment involved in evaluating a node + like a source file, for example). + + + + + + You can also specify a list of strings + to the &Progress; function, + in which case &SCons; will + display each string in turn. + This can be used to implement a "spinner" + by having &SCons; cycle through a + sequence of strings: + + + + + Progress(['-\r', '\\\r', '|\r', '/\r'], interval=5) + Program('f1.c') + Program('f2.c') + + + + + Note that here we have also used the + interval= + keyword argument to have &SCons; + only print a new "spinner" string + once every five evaluated nodes. + Using an interval= count, + even with strings that use $TARGET like + our examples above, + can be a good way to lessen the + work that &SCons; expends printing &Progress; strings, + while still giving the user feedback + that indicates &SCons; is still + working on evaluating the build. + + + + + + Lastly, you can have direct control + over how to print each evaluated node + by passing a Python function + (or other Python callable) + to the &Progress function. + Your function will be called + for each evaluated node, + allowing you to + implement more sophisticated logic + like adding a counter: + + + + + + screen = open('/dev/tty', 'w') + count = 0 + def progress_function(node) + count += 1 + screen.write('Node %4d: %s\r' % (count, node)) + + Progress(progress_function) + + + + + + Of course, if you choose, + you could completely ignore the + node argument to the function, + and just print a count, + or anything else you wish. + + + + + + (Note that there's an obvious follow-on question here: + how would you find the total number of nodes + that will be + evaluated so you can tell the user how + close the build is to finishing? + Unfortunately, in the general case, + there isn't a good way to do that, + short of having &SCons; evaluate its + dependency graph twice, + first to count the total and + the second time to actually build the targets. + This would be necessary because + you can't know in advance which + target(s) the user actually requested + to be built. + The entire build may consist of thousands of Nodes, + for example, + but maybe the user specifically requested + that only a single object file be built.) + + + +
+ +
+ Printing Detailed Build Status: the &GetBuildFailures; Function + + + + SCons, like most build tools, returns zero status to + the shell on success and nonzero status on failure. + Sometimes it's useful to give more information about + the build status at the end of the run, for instance + to print an informative message, send an email, or + page the poor slob who broke the build. + + + + + + SCons provides a &GetBuildFailures; method that + you can use in a python atexit function + to get a list of objects describing the actions that failed + while attempting to build targets. There can be more + than one if you're using -j. Here's a + simple example: + + + + + + import atexit + + def print_build_failures(): + from SCons.Script import GetBuildFailures + for bf in GetBuildFailures(): + print "%s failed: %s" % (bf.node, bf.errstr) + atexit.register(print_build_failures) + + + + + + The atexit.register call + registers print_build_failures + as an atexit callback, to be called + before &SCons; exits. When that function is called, + it calls &GetBuildFailures; to fetch the list of failed objects. + See the man page + for the detailed contents of the returned objects; + some of the more useful attributes are + .node, + .errstr, + .filename, and + .command. + The filename is not necessarily + the same file as the node; the + node is the target that was + being built when the error occurred, while the + filenameis the file or dir that + actually caused the error. + Note: only call &GetBuildFailures; at the end of the + build; calling it at any other time is undefined. + + + + + + Here is a more complete example showing how to + turn each element of &GetBuildFailures; into a string: + + + + + + # Make the build fail if we pass fail=1 on the command line + if ARGUMENTS.get('fail', 0): + Command('target', 'source', ['/bin/false']) + + def bf_to_str(bf): + """Convert an element of GetBuildFailures() to a string + in a useful way.""" + import SCons.Errors + if bf is None: # unknown targets product None in list + return '(unknown tgt)' + elif isinstance(bf, SCons.Errors.StopError): + return str(bf) + elif bf.node: + return str(bf.node) + ': ' + bf.errstr + elif bf.filename: + return bf.filename + ': ' + bf.errstr + return 'unknown failure: ' + bf.errstr + import atexit + + def build_status(): + """Convert the build status to a 2-tuple, (status, msg).""" + from SCons.Script import GetBuildFailures + bf = GetBuildFailures() + if bf: + # bf is normally a list of build failures; if an element is None, + # it's because of a target that scons doesn't know anything about. + status = 'failed' + failures_message = "\n".join(["Failed building %s" % bf_to_str(x) + for x in bf if x is not None]) + else: + # if bf is None, the build completed successfully. + status = 'ok' + failures_message = '' + return (status, failures_message) + + def display_build_status(): + """Display the build status. Called by atexit. + Here you could do all kinds of complicated things.""" + status, failures_message = build_status() + if status == 'failed': + print "FAILED!!!!" # could display alert, ring bell, etc. + elif status == 'ok': + print "Build succeeded." + print failures_message + + atexit.register(display_build_status) + + + + + + When this runs, you'll see the appropriate output: + + + + + scons -Q + scons -Q fail=1 + + +
diff --git a/doc/user/output.xml b/doc/user/output.xml new file mode 100644 index 0000000..89b443b --- /dev/null +++ b/doc/user/output.xml @@ -0,0 +1,703 @@ + + + + + A key aspect of creating a usable build configuration + is providing good output from the build + so its users can readily understand + what the build is doing + and get information about how to control the build. + &SCons; provides several ways of + controlling output from the build configuration + to help make the build + more useful and understandable. + + + +
+ Providing Build Help: the &Help; Function + + + + It's often very useful to be able to give + users some help that describes the + specific targets, build options, etc., + that can be used for your build. + &SCons; provides the &Help; function + to allow you to specify this help text: + + + + + Help(""" + Type: 'scons program' to build the production program, + 'scons debug' to build the debug version. + """) + + + + + (Note the above use of the Python triple-quote syntax, + which comes in very handy for + specifying multi-line strings like help text.) + + + + + + When the &SConstruct; or &SConscript; files + contain such a call to the &Help; function, + the specified help text will be displayed in response to + the &SCons; -h option: + + + + + % scons -h + scons: Reading SConscript files ... + scons: done reading SConscript files. + + Type: 'scons program' to build the production program, + 'scons debug' to build the debug version. + + Use scons -H for help about command-line options. + + + + + The &SConscript; files may contain + multiple calls to the &Help; function, + in which case the specified text(s) + will be concatenated when displayed. + This allows you to split up the + help text across multiple &SConscript; files. + In this situation, the order in + which the &SConscript; files are called + will determine the order in which the &Help; functions are called, + which will determine the order in which + the various bits of text will get concatenated. + + + + + + Another use would be to make the help text conditional + on some variable. + For example, suppose you only want to display + a line about building a Windows-only + version of a program when actually + run on Windows. + The following &SConstruct; file: + + + + + env = Environment() + + Help("\nType: 'scons program' to build the production program.\n") + + if env['PLATFORM'] == 'win32': + Help("\nType: 'scons windebug' to build the Windows debug version.\n") + + + + + Will display the complete help text on Windows: + + + + + C:\>scons -h + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + scons: done reading SConscript files. + + Type: 'scons program' to build the production program. + + Type: 'scons windebug' to build the Windows debug version. + + Use scons -H for help about command-line options. + + + + + But only show the relevant option on a Linux or UNIX system: + + + + + % scons -h + scons: Reading SConscript files ... + scons: done reading SConscript files. + + Type: 'scons program' to build the production program. + + Use scons -H for help about command-line options. + + + + + If there is no &Help; text in the &SConstruct; or + &SConscript; files, + &SCons; will revert to displaying its + standard list that describes the &SCons; command-line + options. + This list is also always displayed whenever + the -H option is used. + + + +
+ +
+ Controlling How &SCons; Prints Build Commands: the <envar>$*COMSTR</envar> Variables + + + + Sometimes the commands executed + to compile object files or link programs + (or build other targets) + can get very long, + long enough to make it difficult for users + to distinguish error messages or + other important build output + from the commands themselves. + All of the default $*COM variables + that specify the command lines + used to build various types of target files + have a corresponding $*COMSTR variable + that can be set to an alternative + string that will be displayed + when the target is built. + + + + + + For example, suppose you want to + have &SCons; display a + "Compiling" + message whenever it's compiling an object file, + and a + "Linking" + when it's linking an executable. + You could write a &SConstruct; file + that looks like: + + + + + env = Environment(CCCOMSTR = "Compiling $TARGET", + LINKCOMSTR = "Linking $TARGET") + env.Program('foo.c') + + + + + Which would then yield the output: + + + + + + + % scons -Q + Compiling foo.o + Linking foo + + + + + &SCons; performs complete variable substitution + on $*COMSTR variables, + so they have access to all of the + standard variables like &cv-TARGET; &cv-SOURCES;, etc., + as well as any construction variables + that happen to be configured in + the construction environment + used to build a specific target. + + + + + + Of course, sometimes it's still important to + be able to see the exact command + that &SCons; will execute to build a target. + For example, you may simply need to verify + that &SCons; is configured to supply + the right options to the compiler, + or a developer may want to + cut-and-paste a compile command + to add a few options + for a custom test. + + + + + + One common way to give users + control over whether or not + &SCons; should print the actual command line + or a short, configured summary + is to add support for a + VERBOSE + command-line variable to your &SConstruct; file. + A simple configuration for this might look like: + + + + + env = Environment() + if ARGUMENTS.get('VERBOSE') != "1': + env['CCCOMSTR'] = "Compiling $TARGET" + env['LINKCOMSTR'] = "Linking $TARGET" + env.Program('foo.c') + + + + + + By only setting the appropriate + $*COMSTR variables + if the user specifies + VERBOSE=1 + on the command line, + the user has control + over how &SCons; + displays these particular command lines: + + + + + + + % scons -Q + Compiling foo.o + Linking foo + % scons -Q -c + Removed foo.o + Removed foo + % scons -Q VERBOSE=1 + cc -o foo.o -c foo.c + cc -o foo foo.o + + +
+ +
+ Providing Build Progress Output: the &Progress; Function + + + + Another aspect of providing good build output + is to give the user feedback + about what &SCons; is doing + even when nothing is being built at the moment. + This can be especially true for large builds + when most of the targets are already up-to-date. + Because &SCons; can take a long time + making absolutely sure that every + target is, in fact, up-to-date + with respect to a lot of dependency files, + it can be easy for users to mistakenly + conclude that &SCons; is hung + or that there is some other problem with the build. + + + + + + One way to deal with this perception + is to configure &SCons; to print something to + let the user know what it's "thinking about." + The &Progress; function + allows you to specify a string + that will be printed for every file + that &SCons; is "considering" + while it is traversing the dependency graph + to decide what targets are or are not up-to-date. + + + + + Progress('Evaluating $TARGET\n') + Program('f1.c') + Program('f2.c') + + + + + Note that the &Progress; function does not + arrange for a newline to be printed automatically + at the end of the string (as does the Python + print statement), + and we must specify the + \n + that we want printed at the end of the configured string. + This configuration, then, + will have &SCons; + print that it is Evaluating + each file that it encounters + in turn as it traverses the dependency graph: + + + + + % scons -Q + Evaluating SConstruct + Evaluating f1.c + Evaluating f1.o + cc -o f1.o -c f1.c + Evaluating f1 + cc -o f1 f1.o + Evaluating f2.c + Evaluating f2.o + cc -o f2.o -c f2.c + Evaluating f2 + cc -o f2 f2.o + Evaluating . + + + + + Of course, normally you don't want to add + all of these additional lines to your build output, + as that can make it difficult for the user + to find errors or other important messages. + A more useful way to display + this progress might be + to have the file names printed + directly to the user's screen, + not to the same standard output + stream where build output is printed, + and to use a carriage return character + (\r) + so that each file name gets re-printed on the same line. + Such a configuration would look like: + + + + + Progress('$TARGET\r', + file=open('/dev/tty', 'w'), + overwrite=True) + Program('f1.c') + Program('f2.c') + + + + + Note that we also specified the + overwrite=True argument + to the &Progress; function, + which causes &SCons; to + "wipe out" the previous string with space characters + before printing the next &Progress; string. + Without the + overwrite=True argument, + a shorter file name would not overwrite + all of the charactes in a longer file name that + precedes it, + making it difficult to tell what the + actual file name is on the output. + Also note that we opened up the + /dev/tty file + for direct access (on POSIX) to + the user's screen. + On Windows, the equivalent would be to open + the con: file name. + + + + + + Also, it's important to know that although you can use + $TARGET to substitute the name of + the node in the string, + the &Progress; function does not + perform general variable substitution + (because there's not necessarily a construction + environment involved in evaluating a node + like a source file, for example). + + + + + + You can also specify a list of strings + to the &Progress; function, + in which case &SCons; will + display each string in turn. + This can be used to implement a "spinner" + by having &SCons; cycle through a + sequence of strings: + + + + + Progress(['-\r', '\\\r', '|\r', '/\r'], interval=5) + Program('f1.c') + Program('f2.c') + + + + + Note that here we have also used the + interval= + keyword argument to have &SCons; + only print a new "spinner" string + once every five evaluated nodes. + Using an interval= count, + even with strings that use $TARGET like + our examples above, + can be a good way to lessen the + work that &SCons; expends printing &Progress; strings, + while still giving the user feedback + that indicates &SCons; is still + working on evaluating the build. + + + + + + Lastly, you can have direct control + over how to print each evaluated node + by passing a Python function + (or other Python callable) + to the &Progress; function. + Your function will be called + for each evaluated node, + allowing you to + implement more sophisticated logic + like adding a counter: + + + + + screen = open('/dev/tty', 'w') + count = 0 + def progress_function(node) + count += 1 + screen.write('Node %4d: %s\r' % (count, node)) + + Progress(progress_function) + + + + + Of course, if you choose, + you could completely ignore the + node argument to the function, + and just print a count, + or anything else you wish. + + + + + + (Note that there's an obvious follow-on question here: + how would you find the total number of nodes + that will be + evaluated so you can tell the user how + close the build is to finishing? + Unfortunately, in the general case, + there isn't a good way to do that, + short of having &SCons; evaluate its + dependency graph twice, + first to count the total and + the second time to actually build the targets. + This would be necessary because + you can't know in advance which + target(s) the user actually requested + to be built. + The entire build may consist of thousands of Nodes, + for example, + but maybe the user specifically requested + that only a single object file be built.) + + + +
+ +
+ Printing Detailed Build Status: the &GetBuildFailures; Function + + + + SCons, like most build tools, returns zero status to + the shell on success and nonzero status on failure. + Sometimes it's useful to give more information about + the build status at the end of the run, for instance + to print an informative message, send an email, or + page the poor slob who broke the build. + + + + + + SCons provides a &GetBuildFailures; method that + you can use in a python atexit function + to get a list of objects describing the actions that failed + while attempting to build targets. There can be more + than one if you're using -j. Here's a + simple example: + + + + + import atexit + + def print_build_failures(): + from SCons.Script import GetBuildFailures + for bf in GetBuildFailures(): + print "%s failed: %s" % (bf.node, bf.errstr) + atexit.register(print_build_failures) + + + + + The atexit.register call + registers print_build_failures + as an atexit callback, to be called + before &SCons; exits. When that function is called, + it calls &GetBuildFailures; to fetch the list of failed objects. + See the man page + for the detailed contents of the returned objects; + some of the more useful attributes are + .node, + .errstr, + .filename, and + .command. + The filename is not necessarily + the same file as the node; the + node is the target that was + being built when the error occurred, while the + filenameis the file or dir that + actually caused the error. + Note: only call &GetBuildFailures; at the end of the + build; calling it at any other time is undefined. + + + + + + Here is a more complete example showing how to + turn each element of &GetBuildFailures; into a string: + + + + + # Make the build fail if we pass fail=1 on the command line + if ARGUMENTS.get('fail', 0): + Command('target', 'source', ['/bin/false']) + + def bf_to_str(bf): + """Convert an element of GetBuildFailures() to a string + in a useful way.""" + import SCons.Errors + if bf is None: # unknown targets product None in list + return '(unknown tgt)' + elif isinstance(bf, SCons.Errors.StopError): + return str(bf) + elif bf.node: + return str(bf.node) + ': ' + bf.errstr + elif bf.filename: + return bf.filename + ': ' + bf.errstr + return 'unknown failure: ' + bf.errstr + import atexit + + def build_status(): + """Convert the build status to a 2-tuple, (status, msg).""" + from SCons.Script import GetBuildFailures + bf = GetBuildFailures() + if bf: + # bf is normally a list of build failures; if an element is None, + # it's because of a target that scons doesn't know anything about. + status = 'failed' + failures_message = "\n".join(["Failed building %s" % bf_to_str(x) + for x in bf if x is not None]) + else: + # if bf is None, the build completed successfully. + status = 'ok' + failures_message = '' + return (status, failures_message) + + def display_build_status(): + """Display the build status. Called by atexit. + Here you could do all kinds of complicated things.""" + status, failures_message = build_status() + if status == 'failed': + print "FAILED!!!!" # could display alert, ring bell, etc. + elif status == 'ok': + print "Build succeeded." + print failures_message + + atexit.register(display_build_status) + + + + + When this runs, you'll see the appropriate output: + + + + + % scons -Q + scons: `.' is up to date. + Build succeeded. + % scons -Q fail=1 + scons: *** [target] Source `source' not found, needed by target `target'. + FAILED!!!! + Failed building target: Source `source' not found, needed by target `target'. + + +
diff --git a/doc/user/parseconfig.in b/doc/user/parseconfig.in new file mode 100644 index 0000000..db97c35 --- /dev/null +++ b/doc/user/parseconfig.in @@ -0,0 +1,114 @@ + + + + + Configuring the right options to build programs to work with + libraries--especially shared libraries--that are available + on POSIX systems can be very complicated. + To help this situation, + various utilies with names that end in config + return the command-line options for the GNU Compiler Collection (GCC) + that are needed to use these libraries; + for example, the command-line options + to use a library named lib + would be found by calling a utility named lib-config. + + + + + + A more recent convention is that these options + are available from the generic pkg-config program, + which has common framework, error handling, and the like, + so that all the package creator has to do is provide the set of strings + for his particular package. + + + + + + &SCons; construction environments have a &ParseConfig; method + that executes a *config utility + (either pkg-config or a + more specific utility) + and configures the appropriate construction variables + in the environment + based on the command-line options + returned by the specified command. + + + + + + env = Environment() + env['CPPPATH'] = ['/lib/compat'] + env.ParseConfig("pkg-config x11 --cflags --libs") + print env['CPPPATH'] + + + + + + &SCons; will execute the specified command string, + parse the resultant flags, + and add the flags to the appropriate environment variables. + + + + + scons -Q + + + + + In the example above, &SCons; has added the include directory to + CPPPATH. + (Depending upon what other flags are emitted by the + pkg-config command, + other variables may have been extended as well.) + + + + + + Note that the options are merged with existing options using + the &MergeFlags; method, + so that each option only occurs once in the construction variable: + + + + + + env = Environment() + env.ParseConfig("pkg-config x11 --cflags --libs") + env.ParseConfig("pkg-config x11 --cflags --libs") + print env['CPPPATH'] + + + + + scons -Q + diff --git a/doc/user/parseconfig.xml b/doc/user/parseconfig.xml new file mode 100644 index 0000000..1f85ad0 --- /dev/null +++ b/doc/user/parseconfig.xml @@ -0,0 +1,132 @@ + + + + + Configuring the right options to build programs to work with + libraries--especially shared libraries--that are available + on POSIX systems can be very complicated. + To help this situation, + various utilies with names that end in config + return the command-line options for the GNU Compiler Collection (GCC) + that are needed to use these libraries; + for example, the command-line options + to use a library named lib + would be found by calling a utility named lib-config. + + + + + + A more recent convention is that these options + are available from the generic pkg-config program, + which has common framework, error handling, and the like, + so that all the package creator has to do is provide the set of strings + for his particular package. + + + + + + &SCons; construction environments have a &ParseConfig; method + that executes a *config utility + (either pkg-config or a + more specific utility) + and configures the appropriate construction variables + in the environment + based on the command-line options + returned by the specified command. + + + + + env = Environment() + env['CPPPATH'] = ['/lib/compat'] + env.ParseConfig("pkg-config x11 --cflags --libs") + print env['CPPPATH'] + + + + + &SCons; will execute the specified command string, + parse the resultant flags, + and add the flags to the appropriate environment variables. + + + + + % scons -Q + Package x11 was not found in the pkg-config search path. + Perhaps you should add the directory containing `x11.pc' + to the PKG_CONFIG_PATH environment variable + No package 'x11' found + OSError: 'pkg-config x11 --cflags --libs' exited 1: + File "/home/my/project/SConstruct", line 3: + env.ParseConfig("pkg-config x11 --cflags --libs") + File "bootstrap/src/engine/SCons/Environment.py", line 1474: + None + File "bootstrap/src/engine/SCons/Environment.py", line 593: + None + + + + + In the example above, &SCons; has added the include directory to + CPPPATH. + (Depending upon what other flags are emitted by the + pkg-config command, + other variables may have been extended as well.) + + + + + + Note that the options are merged with existing options using + the &MergeFlags; method, + so that each option only occurs once in the construction variable: + + + + + env = Environment() + env.ParseConfig("pkg-config x11 --cflags --libs") + env.ParseConfig("pkg-config x11 --cflags --libs") + print env['CPPPATH'] + + + + % scons -Q + Package x11 was not found in the pkg-config search path. + Perhaps you should add the directory containing `x11.pc' + to the PKG_CONFIG_PATH environment variable + No package 'x11' found + OSError: 'pkg-config x11 --cflags --libs' exited 1: + File "/home/my/project/SConstruct", line 2: + env.ParseConfig("pkg-config x11 --cflags --libs") + File "bootstrap/src/engine/SCons/Environment.py", line 1474: + None + File "bootstrap/src/engine/SCons/Environment.py", line 593: + None + diff --git a/doc/user/parseflags.in b/doc/user/parseflags.in new file mode 100644 index 0000000..b21df4f --- /dev/null +++ b/doc/user/parseflags.in @@ -0,0 +1,184 @@ + + + + + &SCons; has a bewildering array of construction variables + for different types of options when building programs. + Sometimes you may not know exactly which variable + should be used for a particular option. + + + + + + &SCons; construction environments have a &ParseFlags; method + that takes a set of typical command-line options + and distrbutes them into the appropriate construction variables. + Historically, it was created to support the &ParseConfig; method, + so it focuses on options used by the GNU Compiler Collection (GCC) + for the C and C++ toolchains. + + + + + + &ParseFlags; returns a dictionary containing the options + distributed into their respective construction variables. + Normally, this dictionary would be passed to &MergeFlags; + to merge the options into a &consenv;, + but the dictionary can be edited if desired to provide + additional functionality. + (Note that if the flags are not going to be edited, + calling &MergeFlags; with the options directly + will avoid an additional step.) + + + + + + env = Environment() + d = env.ParseFlags("-I/opt/include -L/opt/lib -lfoo") + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + int main() { return 0; } + + + + + scons -Q + + + + + Note that if the options are limited to generic types + like those above, + they will be correctly translated for other platform types: + + + + + scons -Q + + + + + Since the assumption is that the flags are used for the GCC toolchain, + unrecognized flags are placed in &cv-link-CCFLAGS; + so they will be used for both C and C++ compiles: + + + + + + env = Environment() + d = env.ParseFlags("-whatever") + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + int main() { return 0; } + + + + + scons -Q + + + + + &ParseFlags; will also accept a (recursive) list of strings as input; + the list is flattened before the strings are processed: + + + + + + env = Environment() + d = env.ParseFlags(["-I/opt/include", ["-L/opt/lib", "-lfoo"]]) + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + int main() { return 0; } + + + + + scons -Q + + + + + If a string begins with a "!" (an exclamation mark, often called a bang), + the string is passed to the shell for execution. + The output of the command is then parsed: + + + + + + env = Environment() + d = env.ParseFlags(["!echo -I/opt/include", "!echo -L/opt/lib", "-lfoo"]) + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + int main() { return 0; } + + + + + scons -Q + + + + + &ParseFlags; is regularly updated for new options; + consult the man page for details about those currently recognized. + + diff --git a/doc/user/parseflags.xml b/doc/user/parseflags.xml new file mode 100644 index 0000000..6900291 --- /dev/null +++ b/doc/user/parseflags.xml @@ -0,0 +1,199 @@ + + + + + &SCons; has a bewildering array of construction variables + for different types of options when building programs. + Sometimes you may not know exactly which variable + should be used for a particular option. + + + + + + &SCons; construction environments have a &ParseFlags; method + that takes a set of typical command-line options + and distrbutes them into the appropriate construction variables. + Historically, it was created to support the &ParseConfig; method, + so it focuses on options used by the GNU Compiler Collection (GCC) + for the C and C++ toolchains. + + + + + + &ParseFlags; returns a dictionary containing the options + distributed into their respective construction variables. + Normally, this dictionary would be passed to &MergeFlags; + to merge the options into a &consenv;, + but the dictionary can be edited if desired to provide + additional functionality. + (Note that if the flags are not going to be edited, + calling &MergeFlags; with the options directly + will avoid an additional step.) + + + + + env = Environment() + d = env.ParseFlags("-I/opt/include -L/opt/lib -lfoo") + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + + % scons -Q + CPPPATH ['/opt/include'] + LIBPATH ['/opt/lib'] + LIBS ['foo'] + cc -o f1.o -c -I/opt/include f1.c + cc -o f1 f1.o -L/opt/lib -lfoo + + + + + Note that if the options are limited to generic types + like those above, + they will be correctly translated for other platform types: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + CPPPATH ['/opt/include'] + LIBPATH ['/opt/lib'] + LIBS ['foo'] + cl /Fof1.obj /c f1.c /nologo /I\opt\include + link /nologo /OUT:f1.exe /LIBPATH:\opt\lib foo.lib f1.obj + + + + + Since the assumption is that the flags are used for the GCC toolchain, + unrecognized flags are placed in &cv-link-CCFLAGS; + so they will be used for both C and C++ compiles: + + + + + env = Environment() + d = env.ParseFlags("-whatever") + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + + % scons -Q + CCFLAGS -whatever + cc -o f1.o -c -whatever f1.c + cc -o f1 f1.o + + + + + &ParseFlags; will also accept a (recursive) list of strings as input; + the list is flattened before the strings are processed: + + + + + env = Environment() + d = env.ParseFlags(["-I/opt/include", ["-L/opt/lib", "-lfoo"]]) + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + + % scons -Q + CPPPATH ['/opt/include'] + LIBPATH ['/opt/lib'] + LIBS ['foo'] + cc -o f1.o -c -I/opt/include f1.c + cc -o f1 f1.o -L/opt/lib -lfoo + + + + + If a string begins with a "!" (an exclamation mark, often called a bang), + the string is passed to the shell for execution. + The output of the command is then parsed: + + + + + env = Environment() + d = env.ParseFlags(["!echo -I/opt/include", "!echo -L/opt/lib", "-lfoo"]) + l = d.items() + l.sort() + for k,v in l: + if v: + print k, v + env.MergeFlags(d) + env.Program('f1.c') + + + + % scons -Q + CPPPATH ['/opt/include'] + LIBPATH ['/opt/lib'] + LIBS ['foo'] + cc -o f1.o -c -I/opt/include f1.c + cc -o f1 f1.o -L/opt/lib -lfoo + + + + + &ParseFlags; is regularly updated for new options; + consult the man page for details about those currently recognized. + + diff --git a/doc/user/preface.in b/doc/user/preface.in new file mode 100644 index 0000000..de4cb43 --- /dev/null +++ b/doc/user/preface.in @@ -0,0 +1,426 @@ + + + + + Thank you for taking the time to read about &SCons;. + &SCons; is a next-generation + software construction tool, + or make tool--that is, a software utility + for building software (or other files) + and keeping built software up-to-date + whenever the underlying input files change. + + + + + + The most distinctive thing about &SCons; + is that its configuration files are + actually scripts, + written in the &Python; programming language. + This is in contrast to most alternative build tools, + which typically invent a new language to + configure the build. + &SCons; still has a learning curve, of course, + because you have to know what functions to call + to set up your build properly, + but the underlying syntax used should be familiar + to anyone who has ever looked at a Python script. + + + + + + Paradoxically, + using Python as the configuration file format + makes &SCons; + easier + for non-programmers to learn + than the cryptic languages of other build tools, + which are usually invented by programmers for other programmers. + This is in no small part due to the + consistency and readability that are hallmarks of Python. + It just so happens that making a real, live + scripting language the basis for the + configuration files + makes it a snap for more accomplished programmers + to do more complicated things with builds, + as necessary. + + + + + +
+ &SCons; Principles + + + + There are a few overriding principles + we try to live up to in designing and implementing &SCons: + + + + + + + Correctness + + + + + First and foremost, + by default, &SCons; guarantees a correct build + even if it means sacrificing performance a little. + We strive to guarantee the build is correct + regardless of how the software being built is structured, + how it may have been written, + or how unusual the tools are that build it. + + + + + + + Performance + + + + + Given that the build is correct, + we try to make &SCons; build software + as quickly as possible. + In particular, wherever we may have needed to slow + down the default &SCons; behavior to guarantee a correct build, + we also try to make it easy to speed up &SCons; + through optimization options that let you trade off + guaranteed correctness in all end cases for + a speedier build in the usual cases. + + + + + + + Convenience + + + + + &SCons; tries to do as much for you out of the box as reasonable, + including detecting the right tools on your system + and using them correctly to build the software. + + + + + + + + + + In a nutshell, we try hard to make &SCons; just + "do the right thing" and build software correctly, + with a minimum of hassles. + + + +
+ + + + + +
+ A Caveat About This Guide's Completeness + + + + One word of warning as you read through this Guide: + Like too much Open Source software out there, + the &SCons; documentation isn't always + kept up-to-date with the available features. + In other words, + there's a lot that &SCons; can do that + isn't yet covered in this User's Guide. + (Come to think of it, + that also describes a lot of proprietary software, doesn't it?) + + + + + + Although this User's Guide isn't as complete as we'd like it to be, + our development process does emphasize + making sure that the &SCons; man page is kept up-to-date + with new features. + So if you're trying to figure out how to do something + that &SCons; supports + but can't find enough (or any) information here, + it would be worth your while to look + at the man page to see if the information is covered there. + And if you do, + maybe you'd even consider contributing + a section to the User's Guide + so the next person looking for + that information won't have to + go through the same thing...? + + + +
+ +
+ Acknowledgements + + + + &SCons; would not exist without a lot of help + from a lot of people, + many of whom may not even be aware + that they helped or served as inspiration. + So in no particular order, + and at the risk of leaving out someone: + + + + + + First and foremost, + &SCons; owes a tremendous debt to Bob Sidebotham, + the original author of the classic Perl-based &Cons; tool + which Bob first released to the world back around 1996. + Bob's work on Cons classic provided the underlying architecture + and model of specifying a build configuration + using a real scripting language. + My real-world experience working on Cons + informed many of the design decisions in SCons, + including the improved parallel build support, + making Builder objects easily definable by users, + and separating the build engine from the wrapping interface. + + + + + + Greg Wilson was instrumental in getting + &SCons; started as a real project + when he initiated the Software Carpentry design + competition in February 2000. + Without that nudge, + marrying the advantages of the Cons classic + architecture with the readability of Python + might have just stayed no more than a nice idea. + + + + + + The entire &SCons; team have been + absolutely wonderful to work with, + and &SCons; would be nowhere near as useful a + tool without the energy, enthusiasm + and time people have contributed over the past few years. + The "core team" + of Chad Austin, Anthony Roach, + Bill Deegan, Charles Crain, Steve Leblanc, Greg Noel, + Gary Oberbrunner, Greg Spencer and Christoph Wiedemann + have been great about reviewing my (and other) changes + and catching problems before they get in the code base. + Of particular technical note: + Anthony's outstanding and innovative work on the tasking engine + has given &SCons; a vastly superior parallel build model; + Charles has been the master of the crucial Node infrastructure; + Christoph's work on the Configure infrastructure + has added crucial Autoconf-like functionality; + and Greg has provided excellent support + for Microsoft Visual Studio. + + + + + + Special thanks to David Snopek for contributing + his underlying "Autoscons" code that formed + the basis of Christoph's work with the Configure functionality. + David was extremely generous in making + this code available to &SCons;, + given that he initially released it under the GPL + and &SCons; is released under a less-restrictive MIT-style license. + + + + + + + + Thanks to Peter Miller + for his splendid change management system, &Aegis;, + which has provided the &SCons; project + with a robust development methodology from day one, + and which showed me how you could + integrate incremental regression tests into + a practical development cycle + (years before eXtreme Programming arrived on the scene). + + + + + + And last, thanks to Guido van Rossum + for his elegant scripting language, + which is the basis not only for the &SCons; implementation, + but for the interface itself. + + + +
+ +
+ Contact + + + + The best way to contact people involved with SCons, + including the author, + is through the SCons mailing lists. + + + + + + If you want to ask general questions about how to use &SCons; + send email to &scons-users;. + + + + + + If you want to contact the &SCons; development community directly, + send email to &scons-devel;. + + + + + + If you want to receive announcements about &SCons, + join the low-volume &scons-announce; mailing list. + + + +
diff --git a/doc/user/preface.xml b/doc/user/preface.xml new file mode 100644 index 0000000..2c6d8f4 --- /dev/null +++ b/doc/user/preface.xml @@ -0,0 +1,426 @@ + + + + + Thank you for taking the time to read about &SCons;. + &SCons; is a next-generation + software construction tool, + or make tool--that is, a software utility + for building software (or other files) + and keeping built software up-to-date + whenever the underlying input files change. + + + + + + The most distinctive thing about &SCons; + is that its configuration files are + actually scripts, + written in the &Python; programming language. + This is in contrast to most alternative build tools, + which typically invent a new language to + configure the build. + &SCons; still has a learning curve, of course, + because you have to know what functions to call + to set up your build properly, + but the underlying syntax used should be familiar + to anyone who has ever looked at a Python script. + + + + + + Paradoxically, + using Python as the configuration file format + makes &SCons; + easier + for non-programmers to learn + than the cryptic languages of other build tools, + which are usually invented by programmers for other programmers. + This is in no small part due to the + consistency and readability that are hallmarks of Python. + It just so happens that making a real, live + scripting language the basis for the + configuration files + makes it a snap for more accomplished programmers + to do more complicated things with builds, + as necessary. + + + + + +
+ &SCons; Principles + + + + There are a few overriding principles + we try to live up to in designing and implementing &SCons;: + + + + + + + Correctness + + + + + First and foremost, + by default, &SCons; guarantees a correct build + even if it means sacrificing performance a little. + We strive to guarantee the build is correct + regardless of how the software being built is structured, + how it may have been written, + or how unusual the tools are that build it. + + + + + + + Performance + + + + + Given that the build is correct, + we try to make &SCons; build software + as quickly as possible. + In particular, wherever we may have needed to slow + down the default &SCons; behavior to guarantee a correct build, + we also try to make it easy to speed up &SCons; + through optimization options that let you trade off + guaranteed correctness in all end cases for + a speedier build in the usual cases. + + + + + + + Convenience + + + + + &SCons; tries to do as much for you out of the box as reasonable, + including detecting the right tools on your system + and using them correctly to build the software. + + + + + + + + + + In a nutshell, we try hard to make &SCons; just + "do the right thing" and build software correctly, + with a minimum of hassles. + + + +
+ + + + + +
+ A Caveat About This Guide's Completeness + + + + One word of warning as you read through this Guide: + Like too much Open Source software out there, + the &SCons; documentation isn't always + kept up-to-date with the available features. + In other words, + there's a lot that &SCons; can do that + isn't yet covered in this User's Guide. + (Come to think of it, + that also describes a lot of proprietary software, doesn't it?) + + + + + + Although this User's Guide isn't as complete as we'd like it to be, + our development process does emphasize + making sure that the &SCons; man page is kept up-to-date + with new features. + So if you're trying to figure out how to do something + that &SCons; supports + but can't find enough (or any) information here, + it would be worth your while to look + at the man page to see if the information is covered there. + And if you do, + maybe you'd even consider contributing + a section to the User's Guide + so the next person looking for + that information won't have to + go through the same thing...? + + + +
+ +
+ Acknowledgements + + + + &SCons; would not exist without a lot of help + from a lot of people, + many of whom may not even be aware + that they helped or served as inspiration. + So in no particular order, + and at the risk of leaving out someone: + + + + + + First and foremost, + &SCons; owes a tremendous debt to Bob Sidebotham, + the original author of the classic Perl-based &Cons; tool + which Bob first released to the world back around 1996. + Bob's work on Cons classic provided the underlying architecture + and model of specifying a build configuration + using a real scripting language. + My real-world experience working on Cons + informed many of the design decisions in SCons, + including the improved parallel build support, + making Builder objects easily definable by users, + and separating the build engine from the wrapping interface. + + + + + + Greg Wilson was instrumental in getting + &SCons; started as a real project + when he initiated the Software Carpentry design + competition in February 2000. + Without that nudge, + marrying the advantages of the Cons classic + architecture with the readability of Python + might have just stayed no more than a nice idea. + + + + + + The entire &SCons; team have been + absolutely wonderful to work with, + and &SCons; would be nowhere near as useful a + tool without the energy, enthusiasm + and time people have contributed over the past few years. + The "core team" + of Chad Austin, Anthony Roach, + Bill Deegan, Charles Crain, Steve Leblanc, Greg Noel, + Gary Oberbrunner, Greg Spencer and Christoph Wiedemann + have been great about reviewing my (and other) changes + and catching problems before they get in the code base. + Of particular technical note: + Anthony's outstanding and innovative work on the tasking engine + has given &SCons; a vastly superior parallel build model; + Charles has been the master of the crucial Node infrastructure; + Christoph's work on the Configure infrastructure + has added crucial Autoconf-like functionality; + and Greg has provided excellent support + for Microsoft Visual Studio. + + + + + + Special thanks to David Snopek for contributing + his underlying "Autoscons" code that formed + the basis of Christoph's work with the Configure functionality. + David was extremely generous in making + this code available to &SCons;, + given that he initially released it under the GPL + and &SCons; is released under a less-restrictive MIT-style license. + + + + + + + + Thanks to Peter Miller + for his splendid change management system, &Aegis;, + which has provided the &SCons; project + with a robust development methodology from day one, + and which showed me how you could + integrate incremental regression tests into + a practical development cycle + (years before eXtreme Programming arrived on the scene). + + + + + + And last, thanks to Guido van Rossum + for his elegant scripting language, + which is the basis not only for the &SCons; implementation, + but for the interface itself. + + + +
+ +
+ Contact + + + + The best way to contact people involved with SCons, + including the author, + is through the SCons mailing lists. + + + + + + If you want to ask general questions about how to use &SCons; + send email to &scons-users;. + + + + + + If you want to contact the &SCons; development community directly, + send email to &scons-devel;. + + + + + + If you want to receive announcements about &SCons;, + join the low-volume &scons-announce; mailing list. + + + +
diff --git a/doc/user/python.in b/doc/user/python.in new file mode 100644 index 0000000..aa49030 --- /dev/null +++ b/doc/user/python.in @@ -0,0 +1,154 @@ + + + diff --git a/doc/user/python.xml b/doc/user/python.xml new file mode 100644 index 0000000..aa49030 --- /dev/null +++ b/doc/user/python.xml @@ -0,0 +1,154 @@ + + + diff --git a/doc/user/repositories.in b/doc/user/repositories.in new file mode 100644 index 0000000..8c97b02 --- /dev/null +++ b/doc/user/repositories.in @@ -0,0 +1,641 @@ + + + + + Often, a software project will have + one or more central repositories, + directory trees that contain + source code, or derived files, or both. + You can eliminate additional unnecessary + rebuilds of files by having &SCons; + use files from one or more code repositories + to build files in your local build tree. + + + +
+ The &Repository; Method + + + + + + It's often useful to allow multiple programmers working + on a project to build software from + source files and/or derived files that + are stored in a centrally-accessible repository, + a directory copy of the source code tree. + (Note that this is not the sort of repository + maintained by a source code management system + like BitKeeper, CVS, or Subversion.) + + You use the &Repository; method + to tell &SCons; to search one or more + central code repositories (in order) + for any source files and derived files + that are not present in the local build tree: + + + + + + env = Environment() + env.Program('hello.c') + Repository('__ROOT__/usr/repository1', '__ROOT__/usr/repository2') + + + int main() { printf("Hello, world!\n"); } + + + + + + Multiple calls to the &Repository; method + will simply add repositories to the global list + that &SCons; maintains, + with the exception that &SCons; will automatically eliminate + the current directory and any non-existent + directories from the list. + + + +
+ +
+ Finding source files in repositories + + + + The above example + specifies that &SCons; + will first search for files under + the /usr/repository1 tree + and next under the /usr/repository2 tree. + &SCons; expects that any files it searches + for will be found in the same position + relative to the top-level directory. + In the above example, if the &hello_c; file is not + found in the local build tree, + &SCons; will search first for + a /usr/repository1/hello.c file + and then for a /usr/repository2/hello.c file + to use in its place. + + + + + + So given the &SConstruct; file above, + if the &hello_c; file exists in the local + build directory, + &SCons; will rebuild the &hello; program + as normal: + + + + + scons -Q + + + + + If, however, there is no local &hello_c; file, + but one exists in /usr/repository1, + &SCons; will recompile the &hello; program + from the source file it finds in the repository: + + + + + + env = Environment() + env.Program('hello.c') + Repository('__ROOT__/usr/repository1', '__ROOT__/usr/repository2') + + + int main() { printf("Hello, world!\n"); } + + + + + scons -Q + + + + + And similarly, if there is no local &hello_c; file + and no /usr/repository1/hello.c, + but one exists in /usr/repository2: + + + + + + env = Environment() + env.Program('hello.c') + Repository('__ROOT__/usr/repository1', '__ROOT__/usr/repository2') + + + int main() { printf("Hello, world!\n"); } + + + + + scons -Q + + + + + + +
+ +
+ Finding <literal>#include</literal> files in repositories + + + + We've already seen that SCons will scan the contents of + a source file for #include file names + and realize that targets built from that source file + also depend on the #include file(s). + For each directory in the &cv-link-CPPPATH; list, + &SCons; will actually search the corresponding directories + in any repository trees and establish the + correct dependencies on any + #include files that it finds + in repository directory. + + + + + + Unless the C compiler also knows about these directories + in the repository trees, though, + it will be unable to find the #include files. + If, for example, the &hello_c; file in + our previous example includes the &hello.h; + in its current directory, + and the &hello.h; only exists in the repository: + + + + + % scons -Q + cc -o hello.o -c hello.c + hello.c:1: hello.h: No such file or directory + + + + + In order to inform the C compiler about the repositories, + &SCons; will add appropriate + -I flags to the compilation commands + for each directory in the &cv-CPPPATH; list. + So if we add the current directory to the + construction environment &cv-CPPPATH; like so: + + + + + + env = Environment(CPPPATH = ['.']) + env.Program('hello.c') + Repository('__ROOT__/usr/repository1') + + + int main() { printf("Hello, world!\n"); } + + + + + + Then re-executing &SCons; yields: + + + + + scons -Q + + + + + The order of the -I options replicates, + for the C preprocessor, + the same repository-directory search path + that &SCons; uses for its own dependency analysis. + If there are multiple repositories and multiple &cv-CPPPATH; + directories, &SCons; will add the repository directories + to the beginning of each &cv-CPPPATH; directory, + rapidly multiplying the number of -I flags. + If, for example, the &cv-CPPPATH; contains three directories + (and shorter repository path names!): + + + + + + env = Environment(CPPPATH = ['dir1', 'dir2', 'dir3']) + env.Program('hello.c') + Repository('__ROOT__/r1', '__ROOT__/r2') + + + int main() { printf("Hello, world!\n"); } + + + + + + Then we'll end up with nine -I options + on the command line, + three (for each of the &cv-CPPPATH; directories) + times three (for the local directory plus the two repositories): + + + + + scons -Q + + + + +
+ Limitations on <literal>#include</literal> files in repositories + + + + &SCons; relies on the C compiler's + -I options to control the order in which + the preprocessor will search the repository directories + for #include files. + This causes a problem, however, with how the C preprocessor + handles #include lines with + the file name included in double-quotes. + + + + + + As we've seen, + &SCons; will compile the &hello_c; file from + the repository if it doesn't exist in + the local directory. + If, however, the &hello_c; file in the repository contains + a #include line with the file name in + double quotes: + + + + + #include "hello.h" + int + main(int argc, char *argv[]) + { + printf(HELLO_MESSAGE); + return (0); + } + + + + + Then the C preprocessor will always + use a &hello_h; file from the repository directory first, + even if there is a &hello_h; file in the local directory, + despite the fact that the command line specifies + -I as the first option: + + + + + + env = Environment(CPPPATH = ['.']) + env.Program('hello.c') + Repository('__ROOT__/usr/repository1') + + + int main() { printf("Hello, world!\n"); } + + + + + scons -Q + + + + + This behavior of the C preprocessor--always search + for a #include file in double-quotes + first in the same directory as the source file, + and only then search the -I--can + not, in general, be changed. + In other words, it's a limitation + that must be lived with if you want to use + code repositories in this way. + There are three ways you can possibly + work around this C preprocessor behavior: + + + + + + + + + Some modern versions of C compilers do have an option + to disable or control this behavior. + If so, add that option to &cv-link-CFLAGS; + (or &cv-link-CXXFLAGS; or both) in your construction environment(s). + Make sure the option is used for all construction + environments that use C preprocessing! + + + + + + + + Change all occurrences of #include "file.h" + to #include &lt;file.h&gt;. + Use of #include with angle brackets + does not have the same behavior--the -I + directories are searched first + for #include files--which + gives &SCons; direct control over the list of + directories the C preprocessor will search. + + + + + + + + Require that everyone working with compilation from + repositories check out and work on entire directories of files, + not individual files. + (If you use local wrapper scripts around + your source code control system's command, + you could add logic to enforce this restriction there. + + + + + + +
+ +
+ +
+ Finding the &SConstruct; file in repositories + + + + &SCons; will also search in repositories + for the &SConstruct; file and any specified &SConscript; files. + This poses a problem, though: how can &SCons; search a + repository tree for an &SConstruct; file + if the &SConstruct; file itself contains the information + about the pathname of the repository? + To solve this problem, &SCons; allows you + to specify repository directories + on the command line using the -Y option: + + + + + % scons -Q -Y /usr/repository1 -Y /usr/repository2 + + + + + When looking for source or derived files, + &SCons; will first search the repositories + specified on the command line, + and then search the repositories + specified in the &SConstruct; or &SConscript; files. + + + +
+ +
+ Finding derived files in repositories + + + + If a repository contains not only source files, + but also derived files (such as object files, + libraries, or executables), &SCons; will perform + its normal MD5 signature calculation to + decide if a derived file in a repository is up-to-date, + or the derived file must be rebuilt in the local build directory. + For the &SCons; signature calculation to work correctly, + a repository tree must contain the &sconsign; files + that &SCons; uses to keep track of signature information. + + + + + + Usually, this would be done by a build integrator + who would run &SCons; in the repository + to create all of its derived files and &sconsign; files, + or who would run &SCons; in a separate build directory + and copy the resulting tree to the desired repository: + + + + + + env = Environment() + env.Program(['hello.c', 'file1.c', 'file2.c']) + Repository('/usr/repository1', '/usr/repository2') + + + int main() { printf("Hello, world!\n"); } + + + int f1() { printf("file1\n"); } + + + int f2() { printf("file2.c\n"); } + + + + + cd /usr/repository1 + scons -Q + + + + + (Note that this is safe even if the &SConstruct; file + lists /usr/repository1 as a repository, + because &SCons; will remove the current build directory + from its repository list for that invocation.) + + + + + + Now, with the repository populated, + we only need to create the one local source file + we're interested in working with at the moment, + and use the -Y option to + tell &SCons; to fetch any other files it needs + from the repository: + + + + + + % cd $HOME/build + % edit hello.c + % scons -Q -Y /usr/repository1 + cc -c -o hello.o hello.c + cc -o hello hello.o /usr/repository1/file1.o /usr/repository1/file2.o + + + + + Notice that &SCons; realizes that it does not need to + rebuild local copies file1.o and file2.o files, + but instead uses the already-compiled files + from the repository. + + + +
+ +
+ Guaranteeing local copies of files + + + + If the repository tree contains the complete results of a build, + and we try to build from the repository + without any files in our local tree, + something moderately surprising happens: + + + + + % mkdir $HOME/build2 + % cd $HOME/build2 + % scons -Q -Y /usr/all/repository hello + scons: `hello' is up-to-date. + + + + + Why does &SCons; say that the &hello; program + is up-to-date when there is no &hello; program + in the local build directory? + Because the repository (not the local directory) + contains the up-to-date &hello; program, + and &SCons; correctly determines that nothing + needs to be done to rebuild that + up-to-date copy of the file. + + + + + + There are, however, many times when you want to ensure that a + local copy of a file always exists. + A packaging or testing script, for example, + may assume that certain generated files exist locally. + To tell &SCons; to make a copy of any up-to-date repository + file in the local build directory, + use the &Local; function: + + + + + + env = Environment() + hello = env.Program('hello.c') + Local(hello) + + + int main() { printf("Hello, world!\n"); } + + + + + + If we then run the same command, + &SCons; will make a local copy of the program + from the repository copy, + and tell you that it is doing so: + + + + + % scons -Y /usr/all/repository hello + Local copy of hello from /usr/all/repository/hello + scons: `hello' is up-to-date. + + + + + (Notice that, because the act of making the local copy + is not considered a "build" of the &hello; file, + &SCons; still reports that it is up-to-date.) + + + +
diff --git a/doc/user/repositories.xml b/doc/user/repositories.xml new file mode 100644 index 0000000..7d955c3 --- /dev/null +++ b/doc/user/repositories.xml @@ -0,0 +1,595 @@ + + + + + Often, a software project will have + one or more central repositories, + directory trees that contain + source code, or derived files, or both. + You can eliminate additional unnecessary + rebuilds of files by having &SCons; + use files from one or more code repositories + to build files in your local build tree. + + + +
+ The &Repository; Method + + + + + + It's often useful to allow multiple programmers working + on a project to build software from + source files and/or derived files that + are stored in a centrally-accessible repository, + a directory copy of the source code tree. + (Note that this is not the sort of repository + maintained by a source code management system + like BitKeeper, CVS, or Subversion.) + + You use the &Repository; method + to tell &SCons; to search one or more + central code repositories (in order) + for any source files and derived files + that are not present in the local build tree: + + + + + env = Environment() + env.Program('hello.c') + Repository('/usr/repository1', '/usr/repository2') + + + + + Multiple calls to the &Repository; method + will simply add repositories to the global list + that &SCons; maintains, + with the exception that &SCons; will automatically eliminate + the current directory and any non-existent + directories from the list. + + + +
+ +
+ Finding source files in repositories + + + + The above example + specifies that &SCons; + will first search for files under + the /usr/repository1 tree + and next under the /usr/repository2 tree. + &SCons; expects that any files it searches + for will be found in the same position + relative to the top-level directory. + In the above example, if the &hello_c; file is not + found in the local build tree, + &SCons; will search first for + a /usr/repository1/hello.c file + and then for a /usr/repository2/hello.c file + to use in its place. + + + + + + So given the &SConstruct; file above, + if the &hello_c; file exists in the local + build directory, + &SCons; will rebuild the &hello; program + as normal: + + + + + % scons -Q + cc -o hello.o -c hello.c + cc -o hello hello.o + + + + + If, however, there is no local &hello_c; file, + but one exists in /usr/repository1, + &SCons; will recompile the &hello; program + from the source file it finds in the repository: + + + + + + + % scons -Q + cc -o hello.o -c /usr/repository1/hello.c + cc -o hello hello.o + + + + + And similarly, if there is no local &hello_c; file + and no /usr/repository1/hello.c, + but one exists in /usr/repository2: + + + + + + + % scons -Q + cc -o hello.o -c /usr/repository2/hello.c + cc -o hello hello.o + + + + + + +
+ +
+ Finding <literal>#include</literal> files in repositories + + + + We've already seen that SCons will scan the contents of + a source file for #include file names + and realize that targets built from that source file + also depend on the #include file(s). + For each directory in the &cv-link-CPPPATH; list, + &SCons; will actually search the corresponding directories + in any repository trees and establish the + correct dependencies on any + #include files that it finds + in repository directory. + + + + + + Unless the C compiler also knows about these directories + in the repository trees, though, + it will be unable to find the #include files. + If, for example, the &hello_c; file in + our previous example includes the &hello;.h; + in its current directory, + and the &hello;.h; only exists in the repository: + + + + + % scons -Q + cc -o hello.o -c hello.c + hello.c:1: hello.h: No such file or directory + + + + + In order to inform the C compiler about the repositories, + &SCons; will add appropriate + -I flags to the compilation commands + for each directory in the &cv-CPPPATH; list. + So if we add the current directory to the + construction environment &cv-CPPPATH; like so: + + + + + env = Environment(CPPPATH = ['.']) + env.Program('hello.c') + Repository('/usr/repository1') + + + + + Then re-executing &SCons; yields: + + + + + % scons -Q + cc -o hello.o -c -I. -I/usr/repository1 hello.c + cc -o hello hello.o + + + + + The order of the -I options replicates, + for the C preprocessor, + the same repository-directory search path + that &SCons; uses for its own dependency analysis. + If there are multiple repositories and multiple &cv-CPPPATH; + directories, &SCons; will add the repository directories + to the beginning of each &cv-CPPPATH; directory, + rapidly multiplying the number of -I flags. + If, for example, the &cv-CPPPATH; contains three directories + (and shorter repository path names!): + + + + + env = Environment(CPPPATH = ['dir1', 'dir2', 'dir3']) + env.Program('hello.c') + Repository('/r1', '/r2') + + + + + Then we'll end up with nine -I options + on the command line, + three (for each of the &cv-CPPPATH; directories) + times three (for the local directory plus the two repositories): + + + + + % scons -Q + cc -o hello.o -c -Idir1 -I/r1/dir1 -I/r2/dir1 -Idir2 -I/r1/dir2 -I/r2/dir2 -Idir3 -I/r1/dir3 -I/r2/dir3 hello.c + cc -o hello hello.o + + + + +
+ Limitations on <literal>#include</literal> files in repositories + + + + &SCons; relies on the C compiler's + -I options to control the order in which + the preprocessor will search the repository directories + for #include files. + This causes a problem, however, with how the C preprocessor + handles #include lines with + the file name included in double-quotes. + + + + + + As we've seen, + &SCons; will compile the &hello_c; file from + the repository if it doesn't exist in + the local directory. + If, however, the &hello_c; file in the repository contains + a #include line with the file name in + double quotes: + + + + + #include "hello.h" + int + main(int argc, char *argv[]) + { + printf(HELLO_MESSAGE); + return (0); + } + + + + + Then the C preprocessor will always + use a &hello_h; file from the repository directory first, + even if there is a &hello_h; file in the local directory, + despite the fact that the command line specifies + -I as the first option: + + + + + + + % scons -Q + cc -o hello.o -c -I. -I/usr/repository1 /usr/repository1/hello.c + cc -o hello hello.o + + + + + This behavior of the C preprocessor--always search + for a #include file in double-quotes + first in the same directory as the source file, + and only then search the -I--can + not, in general, be changed. + In other words, it's a limitation + that must be lived with if you want to use + code repositories in this way. + There are three ways you can possibly + work around this C preprocessor behavior: + + + + + + + + + Some modern versions of C compilers do have an option + to disable or control this behavior. + If so, add that option to &cv-link-CFLAGS; + (or &cv-link-CXXFLAGS; or both) in your construction environment(s). + Make sure the option is used for all construction + environments that use C preprocessing! + + + + + + + + Change all occurrences of #include "file.h" + to #include <file.h>. + Use of #include with angle brackets + does not have the same behavior--the -I + directories are searched first + for #include files--which + gives &SCons; direct control over the list of + directories the C preprocessor will search. + + + + + + + + Require that everyone working with compilation from + repositories check out and work on entire directories of files, + not individual files. + (If you use local wrapper scripts around + your source code control system's command, + you could add logic to enforce this restriction there. + + + + + + +
+ +
+ +
+ Finding the &SConstruct; file in repositories + + + + &SCons; will also search in repositories + for the &SConstruct; file and any specified &SConscript; files. + This poses a problem, though: how can &SCons; search a + repository tree for an &SConstruct; file + if the &SConstruct; file itself contains the information + about the pathname of the repository? + To solve this problem, &SCons; allows you + to specify repository directories + on the command line using the -Y option: + + + + + % scons -Q -Y /usr/repository1 -Y /usr/repository2 + + + + + When looking for source or derived files, + &SCons; will first search the repositories + specified on the command line, + and then search the repositories + specified in the &SConstruct; or &SConscript; files. + + + +
+ +
+ Finding derived files in repositories + + + + If a repository contains not only source files, + but also derived files (such as object files, + libraries, or executables), &SCons; will perform + its normal MD5 signature calculation to + decide if a derived file in a repository is up-to-date, + or the derived file must be rebuilt in the local build directory. + For the &SCons; signature calculation to work correctly, + a repository tree must contain the &sconsign; files + that &SCons; uses to keep track of signature information. + + + + + + Usually, this would be done by a build integrator + who would run &SCons; in the repository + to create all of its derived files and &sconsign; files, + or who would run &SCons; in a separate build directory + and copy the resulting tree to the desired repository: + + + + + + + % cd /usr/repository1 + % scons -Q + cc -o file1.o -c file1.c + cc -o file2.o -c file2.c + cc -o hello.o -c hello.c + cc -o hello hello.o file1.o file2.o + + + + + (Note that this is safe even if the &SConstruct; file + lists /usr/repository1 as a repository, + because &SCons; will remove the current build directory + from its repository list for that invocation.) + + + + + + Now, with the repository populated, + we only need to create the one local source file + we're interested in working with at the moment, + and use the -Y option to + tell &SCons; to fetch any other files it needs + from the repository: + + + + + + % cd $HOME/build + % edit hello.c + % scons -Q -Y /usr/repository1 + cc -c -o hello.o hello.c + cc -o hello hello.o /usr/repository1/file1.o /usr/repository1/file2.o + + + + + Notice that &SCons; realizes that it does not need to + rebuild local copies file1.o and file2.o files, + but instead uses the already-compiled files + from the repository. + + + +
+ +
+ Guaranteeing local copies of files + + + + If the repository tree contains the complete results of a build, + and we try to build from the repository + without any files in our local tree, + something moderately surprising happens: + + + + + % mkdir $HOME/build2 + % cd $HOME/build2 + % scons -Q -Y /usr/all/repository hello + scons: `hello' is up-to-date. + + + + + Why does &SCons; say that the &hello; program + is up-to-date when there is no &hello; program + in the local build directory? + Because the repository (not the local directory) + contains the up-to-date &hello; program, + and &SCons; correctly determines that nothing + needs to be done to rebuild that + up-to-date copy of the file. + + + + + + There are, however, many times when you want to ensure that a + local copy of a file always exists. + A packaging or testing script, for example, + may assume that certain generated files exist locally. + To tell &SCons; to make a copy of any up-to-date repository + file in the local build directory, + use the &Local; function: + + + + + env = Environment() + hello = env.Program('hello.c') + Local(hello) + + + + + If we then run the same command, + &SCons; will make a local copy of the program + from the repository copy, + and tell you that it is doing so: + + + + + % scons -Y /usr/all/repository hello + Local copy of hello from /usr/all/repository/hello + scons: `hello' is up-to-date. + + + + + (Notice that, because the act of making the local copy + is not considered a "build" of the &hello; file, + &SCons; still reports that it is up-to-date.) + + + +
diff --git a/doc/user/run.in b/doc/user/run.in new file mode 100644 index 0000000..946a7ed --- /dev/null +++ b/doc/user/run.in @@ -0,0 +1,375 @@ + + + + + + + XXX + + + +
+ Command-Line Options + + + + XXX + + + +
+ +
+ Getting at Command-Line Arguments + + + + XXX + + + +
+ +
+ Selective Builds + + + + XXX + + + +
+ + + +
+ Overriding Construction Variables + + + + XXX + + + +
diff --git a/doc/user/run.xml b/doc/user/run.xml new file mode 100644 index 0000000..946a7ed --- /dev/null +++ b/doc/user/run.xml @@ -0,0 +1,375 @@ + + + + + + + XXX + + + +
+ Command-Line Options + + + + XXX + + + +
+ +
+ Getting at Command-Line Arguments + + + + XXX + + + +
+ +
+ Selective Builds + + + + XXX + + + +
+ + + +
+ Overriding Construction Variables + + + + XXX + + + +
diff --git a/doc/user/scanners.in b/doc/user/scanners.in new file mode 100644 index 0000000..8fa36d7 --- /dev/null +++ b/doc/user/scanners.in @@ -0,0 +1,331 @@ + + + + + + + &SCons; has built-in scanners that know how to look in + C, Fortran and IDL source files for information about + other files that targets built from those files depend on--for example, + in the case of files that use the C preprocessor, + the .h files that are specified + using #include lines in the source. + You can use the same mechanisms that &SCons; uses to create + its built-in scanners to write scanners of your own for file types + that &SCons; does not know how to scan "out of the box." + + + +
+ A Simple Scanner Example + + + + Suppose, for example, that we want to create a simple scanner + for .foo files. + A .foo file contains some text that + will be processed, + and can include other files on lines that begin + with include + followed by a file name: + + + + + include filename.foo + + + + + Scanning a file will be handled by a Python function + that you must supply. + Here is a function that will use the Python + re module + to scan for the include lines in our example: + + + + + import re + + include_re = re.compile(r'^include\s+(\S+)$', re.M) + + def kfile_scan(node, env, path, arg): + contents = node.get_text_contents() + return include_re.findall(contents) + + + + + The scanner function must + accept the four specified arguments + and return a list of implicit dependencies. + Presumably, these would be dependencies found + from examining the contents of the file, + although the function can perform any + manipulation at all to generate the list of + dependencies. + + + + + + + node + + + + + An &SCons; node object representing the file being scanned. + The path name to the file can be + used by converting the node to a string + using the str() function, + or an internal &SCons; get_text_contents() + object method can be used to fetch the contents. + + + + + + + env + + + + + The construction environment in effect for this scan. + The scanner function may choose to use construction + variables from this environment to affect its behavior. + + + + + + + path + + + + + A list of directories that form the search path for included files + for this scanner. + This is how &SCons; handles the &cv-link-CPPPATH; and &cv-link-LIBPATH; + variables. + + + + + + + arg + + + + + An optional argument that you can choose to + have passed to this scanner function by + various scanner instances. + + + + + + + + + + A Scanner object is created using the &Scanner; function, + which typically takes an skeys argument + to associate the type of file suffix with this scanner. + The Scanner object must then be associated with the + &cv-link-SCANNERS; construction variable of a construction environment, + typically by using the &Append; method: + + + + + kscan = Scanner(function = kfile_scan, + skeys = ['.k']) + env.Append(SCANNERS = kscan) + + + + + When we put it all together, it looks like: + + + + + + import re + + include_re = re.compile(r'^include\s+(\S+)$', re.M) + + def kfile_scan(node, env, path): + contents = node.get_text_contents() + includes = include_re.findall(contents) + return includes + + kscan = Scanner(function = kfile_scan, + skeys = ['.k']) + + env = Environment(ENV = {'PATH' : '__ROOT__/usr/local/bin'}) + env.Append(SCANNERS = kscan) + + env.Command('foo', 'foo.k', 'kprocess < $SOURCES > $TARGET') + + + include other_file + + + other_file + + + + + + cat + + + + + +
diff --git a/doc/user/scanners.xml b/doc/user/scanners.xml new file mode 100644 index 0000000..d34b222 --- /dev/null +++ b/doc/user/scanners.xml @@ -0,0 +1,317 @@ + + + + + + + &SCons; has built-in scanners that know how to look in + C, Fortran and IDL source files for information about + other files that targets built from those files depend on--for example, + in the case of files that use the C preprocessor, + the .h files that are specified + using #include lines in the source. + You can use the same mechanisms that &SCons; uses to create + its built-in scanners to write scanners of your own for file types + that &SCons; does not know how to scan "out of the box." + + + +
+ A Simple Scanner Example + + + + Suppose, for example, that we want to create a simple scanner + for .foo files. + A .foo file contains some text that + will be processed, + and can include other files on lines that begin + with include + followed by a file name: + + + + + include filename.foo + + + + + Scanning a file will be handled by a Python function + that you must supply. + Here is a function that will use the Python + re module + to scan for the include lines in our example: + + + + + import re + + include_re = re.compile(r'^include\s+(\S+)$', re.M) + + def kfile_scan(node, env, path, arg): + contents = node.get_text_contents() + return include_re.findall(contents) + + + + + The scanner function must + accept the four specified arguments + and return a list of implicit dependencies. + Presumably, these would be dependencies found + from examining the contents of the file, + although the function can perform any + manipulation at all to generate the list of + dependencies. + + + + + + + node + + + + + An &SCons; node object representing the file being scanned. + The path name to the file can be + used by converting the node to a string + using the str() function, + or an internal &SCons; get_text_contents() + object method can be used to fetch the contents. + + + + + + + env + + + + + The construction environment in effect for this scan. + The scanner function may choose to use construction + variables from this environment to affect its behavior. + + + + + + + path + + + + + A list of directories that form the search path for included files + for this scanner. + This is how &SCons; handles the &cv-link-CPPPATH; and &cv-link-LIBPATH; + variables. + + + + + + + arg + + + + + An optional argument that you can choose to + have passed to this scanner function by + various scanner instances. + + + + + + + + + + A Scanner object is created using the &Scanner; function, + which typically takes an skeys argument + to associate the type of file suffix with this scanner. + The Scanner object must then be associated with the + &cv-link-SCANNERS; construction variable of a construction environment, + typically by using the &Append; method: + + + + + kscan = Scanner(function = kfile_scan, + skeys = ['.k']) + env.Append(SCANNERS = kscan) + + + + + When we put it all together, it looks like: + + + + + import re + + include_re = re.compile(r'^include\s+(\S+)$', re.M) + + def kfile_scan(node, env, path): + contents = node.get_text_contents() + includes = include_re.findall(contents) + return includes + + kscan = Scanner(function = kfile_scan, + skeys = ['.k']) + + env = Environment(ENV = {'PATH' : '/usr/local/bin'}) + env.Append(SCANNERS = kscan) + + env.Command('foo', 'foo.k', 'kprocess < $SOURCES > $TARGET') + + + + +
diff --git a/doc/user/sconf.in b/doc/user/sconf.in new file mode 100644 index 0000000..c4092c9 --- /dev/null +++ b/doc/user/sconf.in @@ -0,0 +1,486 @@ + + + + + &SCons; has integrated support for multi-platform build configuration + similar to that offered by GNU &Autoconf;, + such as + figuring out what libraries or header files + are available on the local system. + This section describes how to use + this &SCons feature. + + + + + + This chapter is still under development, + so not everything is explained as well as it should be. + See the &SCons; man page for additional information. + + + +
+ &Configure_Contexts; + + + + The basic framework for multi-platform build configuration + in &SCons; is to attach a &configure_context; to a + construction environment by calling the &Configure; function, + perform a number of checks for + libraries, functions, header files, etc., + and to then call the configure context's &Finish; method + to finish off the configuration: + + + + + env = Environment() + conf = Configure(env) + # Checks for libraries, header files, etc. go here! + env = conf.Finish() + + + + + &SCons; provides a number of basic checks, + as well as a mechanism for adding your own custom checks. + + + + + + Note that &SCons; uses its own dependency + mechanism to determine when a check + needs to be run--that is, + &SCons; does not run the checks + every time it is invoked, + but caches the values returned by previous checks + and uses the cached values unless something has changed. + This saves a tremendous amount + of developer time while working on + cross-platform build issues. + + + + + + The next sections describe + the basic checks that &SCons; supports, + as well as how to add your own custom checks. + + + +
+ +
+ Checking for the Existence of Header Files + + + + Testing the existence of a header file + requires knowing what language the header file is. + A configure context has a &CheckCHeader; method + that checks for the existence of a C header file: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckCHeader('math.h'): + print 'Math.h must be installed!' + Exit(1) + if conf.CheckCHeader('foo.h'): + conf.env.Append('-DHAS_FOO_H') + env = conf.Finish() + + + + + Note that you can choose to terminate + the build if a given header file doesn't exist, + or you can modify the construction environment + based on the existence of a header file. + + + + + + If you need to check for the existence + a C++ header file, + use the &CheckCXXHeader; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckCXXHeader('vector.h'): + print 'vector.h must be installed!' + Exit(1) + env = conf.Finish() + + +
+ +
+ Checking for the Availability of a Function + + + + Check for the availability of a specific function + using the &CheckFunc; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckFunc('strcpy'): + print 'Did not find strcpy(), using local version' + conf.env.Append(CPPDEFINES = '-Dstrcpy=my_local_strcpy') + env = conf.Finish() + + +
+ +
+ Checking for the Availability of a Library + + + + Check for the availability of a library + using the &CheckLib; method. + You only specify the basename of the library, + you don't need to add a lib + prefix or a .a or .lib suffix: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckLib('m'): + print 'Did not find libm.a or m.lib, exiting!' + Exit(1) + env = conf.Finish() + + + + + Because the ability to use a library successfully + often depends on having access to a header file + that describes the library's interface, + you can check for a library + and a header file + at the same time by using the + &CheckLibWithHeader; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckLibWithHeader('m', 'math.h', 'c'): + print 'Did not find libm.a or m.lib, exiting!' + Exit(1) + env = conf.Finish() + + + + + This is essentially shorthand for + separate calls to the &CheckHeader; and &CheckLib; + functions. + + + +
+ +
+ Checking for the Availability of a &typedef; + + + + Check for the availability of a &typedef; + by using the &CheckType; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckType('off_t'): + print 'Did not find off_t typedef, assuming int' + conf.env.Append(CCFLAGS = '-Doff_t=int') + env = conf.Finish() + + + + + You can also add a string that will be + placed at the beginning of the test file + that will be used to check for the &typedef;. + This provide a way to specify + files that must be included to find the &typedef;: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckType('off_t', '#include &lt;sys/types.h&gt;\n'): + print 'Did not find off_t typedef, assuming int' + conf.env.Append(CCFLAGS = '-Doff_t=int') + env = conf.Finish() + + +
+ +
+ Adding Your Own Custom Checks + + + + A custom check is a Python function + that checks for a certain condition to exist + on the running system, + usually using methods that &SCons; + supplies to take care of the details + of checking whether a compilation succeeds, + a link succeeds, + a program is runnable, + etc. + A simple custom check for the existence of + a specific library might look as follows: + + + + + mylib_test_source_file = """ + #include &lt;mylib.h&gt; + int main(int argc, char **argv) + { + MyLibrary mylib(argc, argv); + return 0; + } + """ + + def CheckMyLibrary(context): + context.Message('Checking for MyLibrary...') + result = context.TryLink(mylib_test_source_file, '.c') + context.Result(result) + return result + + + + + The &Message; and &Result; methods + should typically begin and end a custom check to + let the user know what's going on: + the &Message; call prints the + specified message (with no trailing newline) + and the &Result; call prints + yes if the check succeeds and + no if it doesn't. + The &TryLink; method + actually tests for whether the + specified program text + will successfully link. + + + + + + (Note that a custom check can modify + its check based on any arguments you + choose to pass it, + or by using or modifying the configure context environment + in the context.env attribute.) + + + + + + This custom check function is + then attached to the &configure_context; + by passing a dictionary + to the &Configure; call + that maps a name of the check + to the underlying function: + + + + + env = Environment() + conf = Configure(env, custom_tests = {'CheckMyLibrary' : CheckMyLibrary}) + + + + + You'll typically want to make + the check and the function name the same, + as we've done here, + to avoid potential confusion. + + + + + + We can then put these pieces together + and actually call the CheckMyLibrary check + as follows: + + + + + mylib_test_source_file = """ + #include &lt;mylib.h&gt; + int main(int argc, char **argv) + { + MyLibrary mylib(argc, argv); + return 0; + } + """ + + def CheckMyLibrary(context): + context.Message('Checking for MyLibrary... ') + result = context.TryLink(mylib_test_source_file, '.c') + context.Result(result) + return result + + env = Environment() + conf = Configure(env, custom_tests = {'CheckMyLibrary' : CheckMyLibrary}) + if not conf.CheckMyLibrary(): + print 'MyLibrary is not installed!' + Exit(1) + env = conf.Finish() + + # We would then add actual calls like Program() to build + # something using the "env" construction environment. + + + + + If MyLibrary is not installed on the system, + the output will look like: + + + + + % scons + scons: Reading SConscript file ... + Checking for MyLibrary... failed + MyLibrary is not installed! + + + + + If MyLibrary is installed, + the output will look like: + + + + + % scons + scons: Reading SConscript file ... + Checking for MyLibrary... failed + scons: done reading SConscript + scons: Building targets ... + . + . + . + + +
+ +
+ Not Configuring When Cleaning Targets + + + + Using multi-platform configuration + as described in the previous sections + will run the configuration commands + even when invoking + scons -c + to clean targets: + + + + + % scons -Q -c + Checking for MyLibrary... yes + Removed foo.o + Removed foo + + + + + Although running the platform checks + when removing targets doesn't hurt anything, + it's usually unnecessary. + You can avoid this by using the + &GetOption(); method to + check whether the (clean) + option has been invoked on the command line: + + + + + env = Environment() + if not env.GetOption('clean'): + conf = Configure(env, custom_tests = {'CheckMyLibrary' : CheckMyLibrary}) + if not conf.CheckMyLibrary(): + print 'MyLibrary is not installed!' + Exit(1) + env = conf.Finish() + + + + % scons -Q -c + Removed foo.o + Removed foo + + +
+ + diff --git a/doc/user/sconf.xml b/doc/user/sconf.xml new file mode 100644 index 0000000..16ba534 --- /dev/null +++ b/doc/user/sconf.xml @@ -0,0 +1,486 @@ + + + + + &SCons; has integrated support for multi-platform build configuration + similar to that offered by GNU &Autoconf;, + such as + figuring out what libraries or header files + are available on the local system. + This section describes how to use + this &SCons; feature. + + + + + + This chapter is still under development, + so not everything is explained as well as it should be. + See the &SCons; man page for additional information. + + + +
+ &Configure_Contexts; + + + + The basic framework for multi-platform build configuration + in &SCons; is to attach a &configure_context; to a + construction environment by calling the &Configure; function, + perform a number of checks for + libraries, functions, header files, etc., + and to then call the configure context's &Finish; method + to finish off the configuration: + + + + + env = Environment() + conf = Configure(env) + # Checks for libraries, header files, etc. go here! + env = conf.Finish() + + + + + &SCons; provides a number of basic checks, + as well as a mechanism for adding your own custom checks. + + + + + + Note that &SCons; uses its own dependency + mechanism to determine when a check + needs to be run--that is, + &SCons; does not run the checks + every time it is invoked, + but caches the values returned by previous checks + and uses the cached values unless something has changed. + This saves a tremendous amount + of developer time while working on + cross-platform build issues. + + + + + + The next sections describe + the basic checks that &SCons; supports, + as well as how to add your own custom checks. + + + +
+ +
+ Checking for the Existence of Header Files + + + + Testing the existence of a header file + requires knowing what language the header file is. + A configure context has a &CheckCHeader; method + that checks for the existence of a C header file: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckCHeader('math.h'): + print 'Math.h must be installed!' + Exit(1) + if conf.CheckCHeader('foo.h'): + conf.env.Append('-DHAS_FOO_H') + env = conf.Finish() + + + + + Note that you can choose to terminate + the build if a given header file doesn't exist, + or you can modify the construction environment + based on the existence of a header file. + + + + + + If you need to check for the existence + a C++ header file, + use the &CheckCXXHeader; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckCXXHeader('vector.h'): + print 'vector.h must be installed!' + Exit(1) + env = conf.Finish() + + +
+ +
+ Checking for the Availability of a Function + + + + Check for the availability of a specific function + using the &CheckFunc; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckFunc('strcpy'): + print 'Did not find strcpy(), using local version' + conf.env.Append(CPPDEFINES = '-Dstrcpy=my_local_strcpy') + env = conf.Finish() + + +
+ +
+ Checking for the Availability of a Library + + + + Check for the availability of a library + using the &CheckLib; method. + You only specify the basename of the library, + you don't need to add a lib + prefix or a .a or .lib suffix: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckLib('m'): + print 'Did not find libm.a or m.lib, exiting!' + Exit(1) + env = conf.Finish() + + + + + Because the ability to use a library successfully + often depends on having access to a header file + that describes the library's interface, + you can check for a library + and a header file + at the same time by using the + &CheckLibWithHeader; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckLibWithHeader('m', 'math.h', 'c'): + print 'Did not find libm.a or m.lib, exiting!' + Exit(1) + env = conf.Finish() + + + + + This is essentially shorthand for + separate calls to the &CheckHeader; and &CheckLib; + functions. + + + +
+ +
+ Checking for the Availability of a &typedef; + + + + Check for the availability of a &typedef; + by using the &CheckType; method: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckType('off_t'): + print 'Did not find off_t typedef, assuming int' + conf.env.Append(CCFLAGS = '-Doff_t=int') + env = conf.Finish() + + + + + You can also add a string that will be + placed at the beginning of the test file + that will be used to check for the &typedef;. + This provide a way to specify + files that must be included to find the &typedef;: + + + + + env = Environment() + conf = Configure(env) + if not conf.CheckType('off_t', '#include <sys/types.h>\n'): + print 'Did not find off_t typedef, assuming int' + conf.env.Append(CCFLAGS = '-Doff_t=int') + env = conf.Finish() + + +
+ +
+ Adding Your Own Custom Checks + + + + A custom check is a Python function + that checks for a certain condition to exist + on the running system, + usually using methods that &SCons; + supplies to take care of the details + of checking whether a compilation succeeds, + a link succeeds, + a program is runnable, + etc. + A simple custom check for the existence of + a specific library might look as follows: + + + + + mylib_test_source_file = """ + #include <mylib.h> + int main(int argc, char **argv) + { + MyLibrary mylib(argc, argv); + return 0; + } + """ + + def CheckMyLibrary(context): + context.Message('Checking for MyLibrary...') + result = context.TryLink(mylib_test_source_file, '.c') + context.Result(result) + return result + + + + + The &Message; and &Result; methods + should typically begin and end a custom check to + let the user know what's going on: + the &Message; call prints the + specified message (with no trailing newline) + and the &Result; call prints + yes if the check succeeds and + no if it doesn't. + The &TryLink; method + actually tests for whether the + specified program text + will successfully link. + + + + + + (Note that a custom check can modify + its check based on any arguments you + choose to pass it, + or by using or modifying the configure context environment + in the context.env attribute.) + + + + + + This custom check function is + then attached to the &configure_context; + by passing a dictionary + to the &Configure; call + that maps a name of the check + to the underlying function: + + + + + env = Environment() + conf = Configure(env, custom_tests = {'CheckMyLibrary' : CheckMyLibrary}) + + + + + You'll typically want to make + the check and the function name the same, + as we've done here, + to avoid potential confusion. + + + + + + We can then put these pieces together + and actually call the CheckMyLibrary check + as follows: + + + + + mylib_test_source_file = """ + #include <mylib.h> + int main(int argc, char **argv) + { + MyLibrary mylib(argc, argv); + return 0; + } + """ + + def CheckMyLibrary(context): + context.Message('Checking for MyLibrary... ') + result = context.TryLink(mylib_test_source_file, '.c') + context.Result(result) + return result + + env = Environment() + conf = Configure(env, custom_tests = {'CheckMyLibrary' : CheckMyLibrary}) + if not conf.CheckMyLibrary(): + print 'MyLibrary is not installed!' + Exit(1) + env = conf.Finish() + + # We would then add actual calls like Program() to build + # something using the "env" construction environment. + + + + + If MyLibrary is not installed on the system, + the output will look like: + + + + + % scons + scons: Reading SConscript file ... + Checking for MyLibrary... failed + MyLibrary is not installed! + + + + + If MyLibrary is installed, + the output will look like: + + + + + % scons + scons: Reading SConscript file ... + Checking for MyLibrary... failed + scons: done reading SConscript + scons: Building targets ... + . + . + . + + +
+ +
+ Not Configuring When Cleaning Targets + + + + Using multi-platform configuration + as described in the previous sections + will run the configuration commands + even when invoking + scons -c + to clean targets: + + + + + % scons -Q -c + Checking for MyLibrary... yes + Removed foo.o + Removed foo + + + + + Although running the platform checks + when removing targets doesn't hurt anything, + it's usually unnecessary. + You can avoid this by using the + &GetOption;(); method to + check whether the (clean) + option has been invoked on the command line: + + + + + env = Environment() + if not env.GetOption('clean'): + conf = Configure(env, custom_tests = {'CheckMyLibrary' : CheckMyLibrary}) + if not conf.CheckMyLibrary(): + print 'MyLibrary is not installed!' + Exit(1) + env = conf.Finish() + + + + % scons -Q -c + Removed foo.o + Removed foo + + +
+ + diff --git a/doc/user/separate.in b/doc/user/separate.in new file mode 100644 index 0000000..edc6da8 --- /dev/null +++ b/doc/user/separate.in @@ -0,0 +1,540 @@ + + + + + + + It's often useful to keep any built files completely + separate from the source files. + In &SCons;, this is usually done by creating one or more separate + variant directory trees + that are used to hold the built objects files, libraries, + and executable programs, etc. + for a specific flavor, or variant, of build. + &SCons; provides two ways to do this, + one through the &SConscript; function that we've already seen, + and the second through a more flexible &VariantDir; function. + + + + + + One historical note: the &VariantDir; function + used to be called &BuildDir;. + That name is still supported + but has been deprecated + because the &SCons; functionality + differs from the model of a "build directory" + implemented by other build systems like the GNU Autotools. + + + +
+ Specifying a Variant Directory Tree as Part of an &SConscript; Call + + + + The most straightforward way to establish a variant directory tree + uses the fact that the usual way to + set up a build hierarchy is to have an + &SConscript; file in the source subdirectory. + If you then pass a &variant_dir; argument to the + &SConscript; function call: + + + + + + SConscript('src/SConscript', variant_dir='build') + + + env = Environment() + env.Program('hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + &SCons; will then build all of the files in + the &build; subdirectory: + + + + + ls src + scons -Q + ls build + + + + + But wait a minute--what's going on here? + &SCons; created the object file + build/hello.o + in the &build; subdirectory, + as expected. + But even though our &hello_c; file lives in the &src; subdirectory, + &SCons; has actually compiled a + build/hello.c file + to create the object file. + + + + + + What's happened is that &SCons; has duplicated + the &hello_c; file from the &src; subdirectory + to the &build; subdirectory, + and built the program from there. + The next section explains why &SCons; does this. + + + +
+ +
+ Why &SCons; Duplicates Source Files in a Variant Directory Tree + + + + &SCons; duplicates source files in variant directory trees + because it's the most straightforward way to guarantee a correct build + regardless of include-file directory paths, + relative references between files, + or tool support for putting files in different locations, + and the &SCons; philosophy is to, by default, + guarantee a correct build in all cases. + + + + + + The most direct reason to duplicate source files + in variant directories + is simply that some tools (mostly older versions) + are written to only build their output files + in the same directory as the source files. + In this case, the choices are either + to build the output file in the source directory + and move it to the variant directory, + or to duplicate the source files in the variant directory. + + + + + + Additionally, + relative references between files + can cause problems if we don't + just duplicate the hierarchy of source files + in the variant directory. + You can see this at work in + use of the C preprocessor #include + mechanism with double quotes, not angle brackets: + + + + + #include "file.h" + + + + + The de facto standard behavior + for most C compilers in this case + is to first look in the same directory + as the source file that contains the #include line, + then to look in the directories in the preprocessor search path. + Add to this that the &SCons; implementation of + support for code repositories + (described below) + means not all of the files + will be found in the same directory hierarchy, + and the simplest way to make sure + that the right include file is found + is to duplicate the source files into the variant directory, + which provides a correct build + regardless of the original location(s) of the source files. + + + + + + Although source-file duplication guarantees a correct build + even in these end-cases, + it can usually be safely disabled. + The next section describes + how you can disable the duplication of source files + in the variant directory. + + + +
+ +
+ Telling &SCons; to Not Duplicate Source Files in the Variant Directory Tree + + + + In most cases and with most tool sets, + &SCons; can place its target files in a build subdirectory + without + duplicating the source files + and everything will work just fine. + You can disable the default &SCons; behavior + by specifying duplicate=0 + when you call the &SConscript; function: + + + + + SConscript('src/SConscript', variant_dir='build', duplicate=0) + + + + + When this flag is specified, + &SCons; uses the variant directory + like most people expect--that is, + the output files are placed in the variant directory + while the source files stay in the source directory: + + + + + % ls src + SConscript + hello.c + % scons -Q + cc -c src/hello.c -o build/hello.o + cc -o build/hello build/hello.o + % ls build + hello + hello.o + + +
+ +
+ The &VariantDir; Function + + + + Use the &VariantDir; function to establish that target + files should be built in a separate directory + from the source files: + + + + + + VariantDir('build', 'src') + env = Environment() + env.Program('build/hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + Note that when you're not using + an &SConscript; file in the &src; subdirectory, + you must actually specify that + the program must be built from + the build/hello.c + file that &SCons; will duplicate in the + &build; subdirectory. + + + + + + When using the &VariantDir; function directly, + &SCons; still duplicates the source files + in the variant directory by default: + + + + + ls src + scons -Q + ls build + + + + + You can specify the same duplicate=0 argument + that you can specify for an &SConscript; call: + + + + + + VariantDir('build', 'src', duplicate=0) + env = Environment() + env.Program('build/hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + In which case &SCons; + will disable duplication of the source files: + + + + + ls src + scons -Q + ls build + + +
+ +
+ Using &VariantDir; With an &SConscript; File + + + + Even when using the &VariantDir; function, + it's much more natural to use it with + a subsidiary &SConscript; file. + For example, if the + src/SConscript + looks like this: + + + + + + VariantDir('build', 'src') + SConscript('build/SConscript') + + + env = Environment() + env.Program('hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + Then our &SConstruct; file could look like: + + + + + + + + + Yielding the following output: + + + + + ls src + scons -Q + ls build + + + + + Notice that this is completely equivalent + to the use of &SConscript; that we + learned about in the previous section. + + + +
+ +
+ Using &Glob; with &VariantDir; + + + + The &Glob; file name pattern matching function + works just as usual when using &VariantDir;. + For example, if the + src/SConscript + looks like this: + + + + + + VariantDir('build', 'src') + SConscript('build/SConscript') + + + env = Environment() + env.Program('hello', Glob('*.c')) + + + #include "f2.h" + int main() { printf(f2()); } + + + const char * f2() { return("Hello, world!\n"); } + + + const char * f2(); + + + + + + Then with the same &SConstruct; file as in the previous section, + and source files f1.c + and f2.c in src, + we would see the following output: + + + + + ls src + scons -Q + ls build + + + + + The &Glob; function returns Nodes in the + build/ tree, as you'd expect. + + + +
+ + diff --git a/doc/user/separate.xml b/doc/user/separate.xml new file mode 100644 index 0000000..fd5bdf0 --- /dev/null +++ b/doc/user/separate.xml @@ -0,0 +1,520 @@ + + + + + + + It's often useful to keep any built files completely + separate from the source files. + In &SCons;, this is usually done by creating one or more separate + variant directory trees + that are used to hold the built objects files, libraries, + and executable programs, etc. + for a specific flavor, or variant, of build. + &SCons; provides two ways to do this, + one through the &SConscript; function that we've already seen, + and the second through a more flexible &VariantDir; function. + + + + + + One historical note: the &VariantDir; function + used to be called &BuildDir;. + That name is still supported + but has been deprecated + because the &SCons; functionality + differs from the model of a "build directory" + implemented by other build systems like the GNU Autotools. + + + +
+ Specifying a Variant Directory Tree as Part of an &SConscript; Call + + + + The most straightforward way to establish a variant directory tree + uses the fact that the usual way to + set up a build hierarchy is to have an + &SConscript; file in the source subdirectory. + If you then pass a &variant_dir; argument to the + &SConscript; function call: + + + + + SConscript('src/SConscript', variant_dir='build') + + + + + &SCons; will then build all of the files in + the &build; subdirectory: + + + + + % ls src + SConscript hello.c + % scons -Q + cc -o build/hello.o -c build/hello.c + cc -o build/hello build/hello.o + % ls build + SConscript hello hello.c hello.o + + + + + But wait a minute--what's going on here? + &SCons; created the object file + build/hello.o + in the &build; subdirectory, + as expected. + But even though our &hello_c; file lives in the &src; subdirectory, + &SCons; has actually compiled a + build/hello.c file + to create the object file. + + + + + + What's happened is that &SCons; has duplicated + the &hello_c; file from the &src; subdirectory + to the &build; subdirectory, + and built the program from there. + The next section explains why &SCons; does this. + + + +
+ +
+ Why &SCons; Duplicates Source Files in a Variant Directory Tree + + + + &SCons; duplicates source files in variant directory trees + because it's the most straightforward way to guarantee a correct build + regardless of include-file directory paths, + relative references between files, + or tool support for putting files in different locations, + and the &SCons; philosophy is to, by default, + guarantee a correct build in all cases. + + + + + + The most direct reason to duplicate source files + in variant directories + is simply that some tools (mostly older versions) + are written to only build their output files + in the same directory as the source files. + In this case, the choices are either + to build the output file in the source directory + and move it to the variant directory, + or to duplicate the source files in the variant directory. + + + + + + Additionally, + relative references between files + can cause problems if we don't + just duplicate the hierarchy of source files + in the variant directory. + You can see this at work in + use of the C preprocessor #include + mechanism with double quotes, not angle brackets: + + + + + #include "file.h" + + + + + The de facto standard behavior + for most C compilers in this case + is to first look in the same directory + as the source file that contains the #include line, + then to look in the directories in the preprocessor search path. + Add to this that the &SCons; implementation of + support for code repositories + (described below) + means not all of the files + will be found in the same directory hierarchy, + and the simplest way to make sure + that the right include file is found + is to duplicate the source files into the variant directory, + which provides a correct build + regardless of the original location(s) of the source files. + + + + + + Although source-file duplication guarantees a correct build + even in these end-cases, + it can usually be safely disabled. + The next section describes + how you can disable the duplication of source files + in the variant directory. + + + +
+ +
+ Telling &SCons; to Not Duplicate Source Files in the Variant Directory Tree + + + + In most cases and with most tool sets, + &SCons; can place its target files in a build subdirectory + without + duplicating the source files + and everything will work just fine. + You can disable the default &SCons; behavior + by specifying duplicate=0 + when you call the &SConscript; function: + + + + + SConscript('src/SConscript', variant_dir='build', duplicate=0) + + + + + When this flag is specified, + &SCons; uses the variant directory + like most people expect--that is, + the output files are placed in the variant directory + while the source files stay in the source directory: + + + + + % ls src + SConscript + hello.c + % scons -Q + cc -c src/hello.c -o build/hello.o + cc -o build/hello build/hello.o + % ls build + hello + hello.o + + +
+ +
+ The &VariantDir; Function + + + + Use the &VariantDir; function to establish that target + files should be built in a separate directory + from the source files: + + + + + VariantDir('build', 'src') + env = Environment() + env.Program('build/hello.c') + + + + + Note that when you're not using + an &SConscript; file in the &src; subdirectory, + you must actually specify that + the program must be built from + the build/hello.c + file that &SCons; will duplicate in the + &build; subdirectory. + + + + + + When using the &VariantDir; function directly, + &SCons; still duplicates the source files + in the variant directory by default: + + + + + % ls src + hello.c + % scons -Q + cc -o build/hello.o -c build/hello.c + cc -o build/hello build/hello.o + % ls build + hello hello.c hello.o + + + + + You can specify the same duplicate=0 argument + that you can specify for an &SConscript; call: + + + + + VariantDir('build', 'src', duplicate=0) + env = Environment() + env.Program('build/hello.c') + + + + + In which case &SCons; + will disable duplication of the source files: + + + + + % ls src + hello.c + % scons -Q + cc -o build/hello.o -c src/hello.c + cc -o build/hello build/hello.o + % ls build + hello hello.o + + +
+ +
+ Using &VariantDir; With an &SConscript; File + + + + Even when using the &VariantDir; function, + it's much more natural to use it with + a subsidiary &SConscript; file. + For example, if the + src/SConscript + looks like this: + + + + + env = Environment() + env.Program('hello.c') + + + + + Then our &SConstruct; file could look like: + + + + + + VariantDir('build', 'src') + SConscript('build/SConscript') + + + + + Yielding the following output: + + + + + % ls src + SConscript hello.c + % scons -Q + cc -o build/hello.o -c build/hello.c + cc -o build/hello build/hello.o + % ls build + SConscript hello hello.c hello.o + + + + + Notice that this is completely equivalent + to the use of &SConscript; that we + learned about in the previous section. + + + +
+ +
+ Using &Glob; with &VariantDir; + + + + The &Glob; file name pattern matching function + works just as usual when using &VariantDir;. + For example, if the + src/SConscript + looks like this: + + + + + env = Environment() + env.Program('hello', Glob('*.c')) + + + + + Then with the same &SConstruct; file as in the previous section, + and source files f1.c + and f2.c in src, + we would see the following output: + + + + + % ls src + SConscript f1.c f2.c f2.h + % scons -Q + cc -o build/f1.o -c build/f1.c + cc -o build/f2.o -c build/f2.c + cc -o build/hello build/f1.o build/f2.o + % ls build + SConscript f1.c f1.o f2.c f2.h f2.o hello + + + + + The &Glob; function returns Nodes in the + build/ tree, as you'd expect. + + + +
+ + diff --git a/doc/user/sideeffect.in b/doc/user/sideeffect.in new file mode 100644 index 0000000..58c7306 --- /dev/null +++ b/doc/user/sideeffect.in @@ -0,0 +1,216 @@ + + + + + + + Sometimes a program the you need to call + to build a target file + will also update another file, + such as a log file describing what the program + does while building the target. + For example, we the folowing configuration + would have &SCons; invoke a hypothetical + script named build + (in the local directory) + with command-line arguments that write + log information to a common + logfile.txt file: + + + + + env = Environment() + env.Command('file1.out', 'file.in', + './build --log logfile.txt $SOURCE $TARGET') + env.Command('file2.out', 'file.in', + './build --log logfile.txt $SOURCE $TARGET') + + + + + This can cause problems when running + the build in parallel if + &SCons; decides to update both targets + by running both program invocations at the same time. + The multiple program invocations + may interfere with each other + writing to the common log file, + leading at best to intermixed output in the log file, + and at worst to an actual failed build + (on a system like Windows, for example, + where only one process at a time can open the log file for writing). + + + + + + We can make sure that &SCons; does not + run these build + commands at the same time + by using the &SideEffect; function + to specify that updating + the logfile.txt file + is a side effect of building the specified + file1 + and + file2 + target files: + + + + + + env = Environment() + f1 = env.Command('file1.out', 'file1.in', + './build --log logfile.txt $SOURCE $TARGET') + f2 = env.Command('file2.out', 'file2.in', + './build --log logfile.txt $SOURCE $TARGET') + env.SideEffect('logfile.txt', f1 + f2) + + file1.in + file2.in + + cat + + + + + + + + + + This makes sure the the two + ./build steps are run sequentially, + even withthe --jobs=2 in the command line: + + + + + scons -Q --jobs=2 + + + + + The &SideEffect; function can be called multiple + times for the same side-effect file. + Additionally, the name used as a &SideEffect; does not + even need to actually exist as a file on disk. + &SCons; will still make sure + that the relevant targets + will be executed sequentially, not in parallel: + + + + + + env = Environment() + f1 = env.Command('file1.out', [], 'echo >$TARGET data1') + env.SideEffect('not_really_updated', f1) + f2 = env.Command('file2.out', [], 'echo >$TARGET data2') + env.SideEffect('not_really_updated', f2) + + + + + scons -Q --jobs=2 + + + + + Note that it might be tempting to + use &SideEffect; for additional target files + that a command produces. + For example, versions the Microsoft Visual C/C++ compiler + produce a foo.ilk + alongside compiling foo.obj file. + Specifying foo.ilk as a + side-effect of foo.obj + is not a recommended use of &SideEffect;, + because &SCons; handle side-effect files + slightly differently in its analysis of the dependency graph. + When a command produces multiple output files, + they should be specified as multiple targets of + the call to the relevant builder function, + and the &SideEffect; function itself should really only be used + when it's important to ensure that commands are not executed in parallel, + such as when a "peripheral" file (such as a log file) + may actually updated by more than one command invocation. + + diff --git a/doc/user/sideeffect.xml b/doc/user/sideeffect.xml new file mode 100644 index 0000000..df62eca --- /dev/null +++ b/doc/user/sideeffect.xml @@ -0,0 +1,211 @@ + + + + + + + Sometimes a program the you need to call + to build a target file + will also update another file, + such as a log file describing what the program + does while building the target. + For example, we the folowing configuration + would have &SCons; invoke a hypothetical + script named build + (in the local directory) + with command-line arguments that write + log information to a common + logfile.txt file: + + + + + env = Environment() + env.Command('file1.out', 'file.in', + './build --log logfile.txt $SOURCE $TARGET') + env.Command('file2.out', 'file.in', + './build --log logfile.txt $SOURCE $TARGET') + + + + + This can cause problems when running + the build in parallel if + &SCons; decides to update both targets + by running both program invocations at the same time. + The multiple program invocations + may interfere with each other + writing to the common log file, + leading at best to intermixed output in the log file, + and at worst to an actual failed build + (on a system like Windows, for example, + where only one process at a time can open the log file for writing). + + + + + + We can make sure that &SCons; does not + run these build + commands at the same time + by using the &SideEffect; function + to specify that updating + the logfile.txt file + is a side effect of building the specified + file1 + and + file2 + target files: + + + + + env = Environment() + f1 = env.Command('file1.out', 'file1.in', + './build --log logfile.txt $SOURCE $TARGET') + f2 = env.Command('file2.out', 'file2.in', + './build --log logfile.txt $SOURCE $TARGET') + env.SideEffect('logfile.txt', f1 + f2) + + + + + + + + + This makes sure the the two + ./build steps are run sequentially, + even withthe --jobs=2 in the command line: + + + + + % scons -Q --jobs=2 + ./build --log logfile.txt file1.in file1.out + ./build --log logfile.txt file2.in file2.out + + + + + The &SideEffect; function can be called multiple + times for the same side-effect file. + Additionally, the name used as a &SideEffect; does not + even need to actually exist as a file on disk. + &SCons; will still make sure + that the relevant targets + will be executed sequentially, not in parallel: + + + + + env = Environment() + f1 = env.Command('file1.out', [], 'echo >$TARGET data1') + env.SideEffect('not_really_updated', f1) + f2 = env.Command('file2.out', [], 'echo >$TARGET data2') + env.SideEffect('not_really_updated', f2) + + + + % scons -Q --jobs=2 + echo > file1.out data1 + echo > file2.out data2 + + + + + Note that it might be tempting to + use &SideEffect; for additional target files + that a command produces. + For example, versions the Microsoft Visual C/C++ compiler + produce a foo.ilk + alongside compiling foo.obj file. + Specifying foo.ilk as a + side-effect of foo.obj + is not a recommended use of &SideEffect;, + because &SCons; handle side-effect files + slightly differently in its analysis of the dependency graph. + When a command produces multiple output files, + they should be specified as multiple targets of + the call to the relevant builder function, + and the &SideEffect; function itself should really only be used + when it's important to ensure that commands are not executed in parallel, + such as when a "peripheral" file (such as a log file) + may actually updated by more than one command invocation. + + diff --git a/doc/user/simple.in b/doc/user/simple.in new file mode 100644 index 0000000..4d42934 --- /dev/null +++ b/doc/user/simple.in @@ -0,0 +1,517 @@ + + + + + In this chapter, + you will see several examples of + very simple build configurations using &SCons;, + which will demonstrate how easy + it is to use &SCons; to + build programs from several different programming languages + on different types of systems. + + + +
+ Building Simple C / C++ Programs + + + + Here's the famous "Hello, World!" program in C: + + + + + int + main() + { + printf("Hello, world!\n"); + } + + + + + And here's how to build it using &SCons;. + Enter the following into a file named &SConstruct;: + + + + + + Program('hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + This minimal configuration file gives + &SCons; two pieces of information: + what you want to build + (an executable program), + and the input file from + which you want it built + (the hello.c file). + &b-link-Program; is a builder_method, + a Python call that tells &SCons; that you want to build an + executable program. + + + + + + That's it. Now run the &scons; command to build the program. + On a POSIX-compliant system like Linux or UNIX, + you'll see something like: + + + + + scons + + + + + On a Windows system with the Microsoft Visual C++ compiler, + you'll see something like: + + + + + scons + + + + + First, notice that you only need + to specify the name of the source file, + and that &SCons; correctly deduces the names of + the object and executable files to be built + from the base of the source file name. + + + + + + Second, notice that the same input &SConstruct; file, + without any changes, + generates the correct output file names on both systems: + hello.o and hello + on POSIX systems, + hello.obj and hello.exe + on Windows systems. + This is a simple example of how &SCons; + makes it extremely easy to + write portable software builds. + + + + + + (Note that we won't provide duplicate side-by-side + POSIX and Windows output for all of the examples in this guide; + just keep in mind that, unless otherwise specified, + any of the examples should work equally well on both types of systems.) + + + +
+ +
+ Building Object Files + + + + The &b-link-Program; builder method is only one of + many builder methods that &SCons; provides + to build different types of files. + Another is the &b-link-Object; builder method, + which tells &SCons; to build an object file + from the specified source file: + + + + + + Object('hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + + Now when you run the &scons; command to build the program, + it will build just the &hello_o; object file on a POSIX system: + + + + + scons + + + + + And just the &hello_obj; object file + on a Windows system (with the Microsoft Visual C++ compiler): + + + + + scons + + +
+ +
+ Simple Java Builds + + + + &SCons; also makes building with Java extremely easy. + Unlike the &b-link-Program; and &b-link-Object; builder methods, + however, the &b-link-Java; builder method + requires that you specify + the name of a destination directory in which + you want the class files placed, + followed by the source directory + in which the .java files live: + + + + + + Java('classes', 'src') + + + public class Example1 + { + public static void main(String[] args) + { + System.out.println("Hello Java world!\n"); + } + } + + + + + + If the src directory + contains a single hello.java file, + then the output from running the &scons; command + would look something like this + (on a POSIX system): + + + + + scons + + + + + We'll cover Java builds in more detail, + including building Java archive (.jar) + and other types of file, + in . + + + +
+ +
+ Cleaning Up After a Build + + + + When using &SCons;, it is unnecessary to add special + commands or target names to clean up after a build. + Instead, you simply use the + -c or --clean + option when you invoke &SCons;, + and &SCons; removes the appropriate built files. + So if we build our example above + and then invoke scons -c + afterwards, the output on POSIX looks like: + + + + + + Program('hello.c') + + + int main() { printf("Hello, world!\n"); } + + + + + scons + scons -c + + + + + And the output on Windows looks like: + + + + + scons + scons -c + + + + + Notice that &SCons; changes its output to tell you that it + is Cleaning targets ... and + done cleaning targets. + + + +
+ +
+ The &SConstruct; File + + + + If you're used to build systems like &Make; + you've already figured out that the &SConstruct; file + is the &SCons; equivalent of a &Makefile;. + That is, the &SConstruct; file is the input file + that &SCons; reads to control the build. + + + +
+ &SConstruct; Files Are Python Scripts + + + + There is, however, an important difference between + an &SConstruct; file and a &Makefile;: + the &SConstruct; file is actually a Python script. + If you're not already familiar with Python, don't worry. + This User's Guide will introduce you step-by-step + to the relatively small amount of Python you'll + need to know to be able to use &SCons; effectively. + And Python is very easy to learn. + + + + + + One aspect of using Python as the + scripting language is that you can put comments + in your &SConstruct; file using Python's commenting convention; + that is, everything between a '#' and the end of the line + will be ignored: + + + + + # Arrange to build the "hello" program. + Program('hello.c') # "hello.c" is the source file. + + + + + You'll see throughout the remainder of this Guide + that being able to use the power of a + real scripting language + can greatly simplify the solutions + to complex requirements of real-world builds. + + + +
+ +
+ &SCons; Functions Are Order-Independent + + + + One important way in which the &SConstruct; + file is not exactly like a normal Python script, + and is more like a &Makefile, + is that the order in which + the &SCons; functions are called in + the &SConstruct; file + does not + affect the order in which &SCons; + actually builds the programs and object files + you want it to build. + In programming parlance, + the &SConstruct; file is + declarative, + meaning you tell &SCons; what you want done + and let it figure out the order in which to do it, + rather than strictly imperative, + where you specify explicitly the order in + which to do things. + + + In other words, when you call the &b-link-Program; builder + (or any other builder method), + you're not telling &SCons; to build + the program at the instant the builder method is called. + Instead, you're telling &SCons; to build the program + that you want, for example, + a program built from a file named &hello_c;, + and it's up to &SCons; to build that program + (and any other files) whenever it's necessary. + (We'll learn more about how + &SCons; decides when building or rebuilding a file + is necessary in , below.) + + + + + + &SCons; reflects this distinction between + calling a builder method like &b-Program; + and actually building the program + by printing the status messages that indicate + when it's "just reading" the &SConstruct; file, + and when it's actually building the target files. + This is to make it clear when &SCons; is + executing the Python statements that make up the &SConstruct; file, + and when &SCons; is actually executing the + commands or other actions to + build the necessary files. + + + + + + Let's clarify this with an example. + Python has a print statement that + prints a string of characters to the screen. + If we put print statements around + our calls to the &b-Program; builder method: + + + + + + print "Calling Program('hello.c')" + Program('hello.c') + print "Calling Program('goodbye.c')" + Program('goodbye.c') + print "Finished calling Program()" + + + int main() { printf("Hello, world!\n"); } + + + int main() { printf("Goodbye, world!\n"); } + + + + + + Then when we execute &SCons;, + we see the output from the print + statements in between the messages about + reading the &SConscript; files, + indicating that that is when the + Python statements are being executed: + + + + + scons + + + + + Notice also that &SCons; built the &goodbye; program first, + even though the "reading &SConscript" output + shows that we called Program('hello.c') + first in the &SConstruct; file. + + + +
+ +
+ +
+ Making the &SCons; Output Less Verbose + + + + You've already seen how &SCons; prints + some messages about what it's doing, + surrounding the actual commands used to build the software: + + + + + scons + + + + + These messages emphasize the + order in which &SCons; does its work: + all of the configuration files + (generically referred to as &SConscript; files) + are read and executed first, + and only then are the target files built. + Among other benefits, these messages help to distinguish between + errors that occur while the configuration files are read, + and errors that occur while targets are being built. + + + + + + One drawback, of course, is that these messages clutter the output. + Fortunately, they're easily disabled by using + the &Q; option when invoking &SCons;: + + + + + scons -Q + + + + + Because we want this User's Guide to focus + on what &SCons; is actually doing, + we're going to use the &Q; option + to remove these messages from the + output of all the remaining examples in this Guide. + + + +
diff --git a/doc/user/simple.xml b/doc/user/simple.xml new file mode 100644 index 0000000..598d44b --- /dev/null +++ b/doc/user/simple.xml @@ -0,0 +1,587 @@ + + + + + In this chapter, + you will see several examples of + very simple build configurations using &SCons;, + which will demonstrate how easy + it is to use &SCons; to + build programs from several different programming languages + on different types of systems. + + + +
+ Building Simple C / C++ Programs + + + + Here's the famous "Hello, World!" program in C: + + + + + int + main() + { + printf("Hello, world!\n"); + } + + + + + And here's how to build it using &SCons;. + Enter the following into a file named &SConstruct;: + + + + + Program('hello.c') + + + + + This minimal configuration file gives + &SCons; two pieces of information: + what you want to build + (an executable program), + and the input file from + which you want it built + (the hello.c file). + &b-link-Program; is a builder_method, + a Python call that tells &SCons; that you want to build an + executable program. + + + + + + That's it. Now run the &scons; command to build the program. + On a POSIX-compliant system like Linux or UNIX, + you'll see something like: + + + + + % scons + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Building targets ... + cc -o hello.o -c hello.c + cc -o hello hello.o + scons: done building targets. + + + + + On a Windows system with the Microsoft Visual C++ compiler, + you'll see something like: + + + + + C:\>scons + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + scons: done reading SConscript files. + scons: Building targets ... + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:hello.exe hello.obj + scons: done building targets. + + + + + First, notice that you only need + to specify the name of the source file, + and that &SCons; correctly deduces the names of + the object and executable files to be built + from the base of the source file name. + + + + + + Second, notice that the same input &SConstruct; file, + without any changes, + generates the correct output file names on both systems: + hello.o and hello + on POSIX systems, + hello.obj and hello.exe + on Windows systems. + This is a simple example of how &SCons; + makes it extremely easy to + write portable software builds. + + + + + + (Note that we won't provide duplicate side-by-side + POSIX and Windows output for all of the examples in this guide; + just keep in mind that, unless otherwise specified, + any of the examples should work equally well on both types of systems.) + + + +
+ +
+ Building Object Files + + + + The &b-link-Program; builder method is only one of + many builder methods that &SCons; provides + to build different types of files. + Another is the &b-link-Object; builder method, + which tells &SCons; to build an object file + from the specified source file: + + + + + Object('hello.c') + + + + + Now when you run the &scons; command to build the program, + it will build just the &hello_o; object file on a POSIX system: + + + + + % scons + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Building targets ... + cc -o hello.o -c hello.c + scons: done building targets. + + + + + And just the &hello_obj; object file + on a Windows system (with the Microsoft Visual C++ compiler): + + + + + C:\>scons + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + scons: done reading SConscript files. + scons: Building targets ... + cl /Fohello.obj /c hello.c /nologo + scons: done building targets. + + +
+ +
+ Simple Java Builds + + + + &SCons; also makes building with Java extremely easy. + Unlike the &b-link-Program; and &b-link-Object; builder methods, + however, the &b-link-Java; builder method + requires that you specify + the name of a destination directory in which + you want the class files placed, + followed by the source directory + in which the .java files live: + + + + + Java('classes', 'src') + + + + + If the src directory + contains a single hello.java file, + then the output from running the &scons; command + would look something like this + (on a POSIX system): + + + + + % scons + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Building targets ... + javac -d classes -sourcepath src src/hello.java + scons: done building targets. + + + + + We'll cover Java builds in more detail, + including building Java archive (.jar) + and other types of file, + in . + + + +
+ +
+ Cleaning Up After a Build + + + + When using &SCons;, it is unnecessary to add special + commands or target names to clean up after a build. + Instead, you simply use the + -c or --clean + option when you invoke &SCons;, + and &SCons; removes the appropriate built files. + So if we build our example above + and then invoke scons -c + afterwards, the output on POSIX looks like: + + + + + + + % scons + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Building targets ... + cc -o hello.o -c hello.c + cc -o hello hello.o + scons: done building targets. + % scons -c + scons: Reading SConscript files ... + scons: done reading SConscript files. + scons: Cleaning targets ... + Removed hello.o + Removed hello + scons: done cleaning targets. + + + + + And the output on Windows looks like: + + + + + C:\>scons + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + scons: done reading SConscript files. + scons: Building targets ... + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:hello.exe hello.obj + scons: done building targets. + C:\>scons -c + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + scons: done reading SConscript files. + scons: Cleaning targets ... + Removed hello.obj + Removed hello.exe + scons: done cleaning targets. + + + + + Notice that &SCons; changes its output to tell you that it + is Cleaning targets ... and + done cleaning targets. + + + +
+ +
+ The &SConstruct; File + + + + If you're used to build systems like &Make; + you've already figured out that the &SConstruct; file + is the &SCons; equivalent of a &Makefile;. + That is, the &SConstruct; file is the input file + that &SCons; reads to control the build. + + + +
+ &SConstruct; Files Are Python Scripts + + + + There is, however, an important difference between + an &SConstruct; file and a &Makefile;: + the &SConstruct; file is actually a Python script. + If you're not already familiar with Python, don't worry. + This User's Guide will introduce you step-by-step + to the relatively small amount of Python you'll + need to know to be able to use &SCons; effectively. + And Python is very easy to learn. + + + + + + One aspect of using Python as the + scripting language is that you can put comments + in your &SConstruct; file using Python's commenting convention; + that is, everything between a '#' and the end of the line + will be ignored: + + + + + # Arrange to build the "hello" program. + Program('hello.c') # "hello.c" is the source file. + + + + + You'll see throughout the remainder of this Guide + that being able to use the power of a + real scripting language + can greatly simplify the solutions + to complex requirements of real-world builds. + + + +
+ +
+ &SCons; Functions Are Order-Independent + + + + One important way in which the &SConstruct; + file is not exactly like a normal Python script, + and is more like a &Makefile;, + is that the order in which + the &SCons; functions are called in + the &SConstruct; file + does not + affect the order in which &SCons; + actually builds the programs and object files + you want it to build. + In programming parlance, + the &SConstruct; file is + declarative, + meaning you tell &SCons; what you want done + and let it figure out the order in which to do it, + rather than strictly imperative, + where you specify explicitly the order in + which to do things. + + + In other words, when you call the &b-link-Program; builder + (or any other builder method), + you're not telling &SCons; to build + the program at the instant the builder method is called. + Instead, you're telling &SCons; to build the program + that you want, for example, + a program built from a file named &hello_c;, + and it's up to &SCons; to build that program + (and any other files) whenever it's necessary. + (We'll learn more about how + &SCons; decides when building or rebuilding a file + is necessary in , below.) + + + + + + &SCons; reflects this distinction between + calling a builder method like &b-Program; + and actually building the program + by printing the status messages that indicate + when it's "just reading" the &SConstruct; file, + and when it's actually building the target files. + This is to make it clear when &SCons; is + executing the Python statements that make up the &SConstruct; file, + and when &SCons; is actually executing the + commands or other actions to + build the necessary files. + + + + + + Let's clarify this with an example. + Python has a print statement that + prints a string of characters to the screen. + If we put print statements around + our calls to the &b-Program; builder method: + + + + + print "Calling Program('hello.c')" + Program('hello.c') + print "Calling Program('goodbye.c')" + Program('goodbye.c') + print "Finished calling Program()" + + + + + Then when we execute &SCons;, + we see the output from the print + statements in between the messages about + reading the &SConscript; files, + indicating that that is when the + Python statements are being executed: + + + + + % scons + scons: Reading SConscript files ... + Calling Program('hello.c') + Calling Program('goodbye.c') + Finished calling Program() + scons: done reading SConscript files. + scons: Building targets ... + cc -o goodbye.o -c goodbye.c + cc -o goodbye goodbye.o + cc -o hello.o -c hello.c + cc -o hello hello.o + scons: done building targets. + + + + + Notice also that &SCons; built the &goodbye; program first, + even though the "reading &SConscript;" output + shows that we called Program('hello.c') + first in the &SConstruct; file. + + + +
+ +
+ +
+ Making the &SCons; Output Less Verbose + + + + You've already seen how &SCons; prints + some messages about what it's doing, + surrounding the actual commands used to build the software: + + + + + C:\>scons + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + scons: done reading SConscript files. + scons: Building targets ... + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:hello.exe hello.obj + scons: done building targets. + + + + + These messages emphasize the + order in which &SCons; does its work: + all of the configuration files + (generically referred to as &SConscript; files) + are read and executed first, + and only then are the target files built. + Among other benefits, these messages help to distinguish between + errors that occur while the configuration files are read, + and errors that occur while targets are being built. + + + + + + One drawback, of course, is that these messages clutter the output. + Fortunately, they're easily disabled by using + the &Q; option when invoking &SCons;: + + + + + C:\>scons -Q + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + cl /Fohello.obj /c hello.c /nologo + link /nologo /OUT:hello.exe hello.obj + + + + + Because we want this User's Guide to focus + on what &SCons; is actually doing, + we're going to use the &Q; option + to remove these messages from the + output of all the remaining examples in this Guide. + + + +
diff --git a/doc/user/sourcecode.in b/doc/user/sourcecode.in new file mode 100644 index 0000000..5cbbe1a --- /dev/null +++ b/doc/user/sourcecode.in @@ -0,0 +1,162 @@ + + + + + + + XXX + + + +
+ Fetching Source Code From BitKeeper + + + + XXX + + + + + + env = Environment() + env.SourceCode('.', env.BitKeeper()) + env.Program('hello.c') + + + s.hello.c + + + + + scons -Q + + +
+ +
+ Fetching Source Code From CVS + + + + XXX + + + + + + env = Environment() + env.SourceCode('.', env.CVS('/usr/local/CVS')) + env.Program('hello.c') + + + + + scons -Q + + +
+ +
+ Fetching Source Code From RCS + + + + XXX + + + + + + env = Environment() + env.SourceCode('.', env.RCS()) + env.Program('hello.c') + + + hello.c,v + + + + + scons -Q + + +
+ +
+ Fetching Source Code From SCCS + + + + XXX + + + + + + env = Environment() + env.SourceCode('.', env.SCCS()) + env.Program('hello.c') + + + s.hello.c + + + + + scons -Q + + +
+ + diff --git a/doc/user/sourcecode.xml b/doc/user/sourcecode.xml new file mode 100644 index 0000000..aebcd20 --- /dev/null +++ b/doc/user/sourcecode.xml @@ -0,0 +1,157 @@ + + + + + + + XXX + + + +
+ Fetching Source Code From BitKeeper + + + + XXX + + + + + env = Environment() + env.SourceCode('.', env.BitKeeper()) + env.Program('hello.c') + + + + % scons -Q + bk get hello.c + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ +
+ Fetching Source Code From CVS + + + + XXX + + + + + env = Environment() + env.SourceCode('.', env.CVS('/usr/local/CVS')) + env.Program('hello.c') + + + + % scons -Q + cvs -d /usr/local/CVS co hello.c + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ +
+ Fetching Source Code From RCS + + + + XXX + + + + + env = Environment() + env.SourceCode('.', env.RCS()) + env.Program('hello.c') + + + + % scons -Q + co hello.c + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ +
+ Fetching Source Code From SCCS + + + + XXX + + + + + env = Environment() + env.SourceCode('.', env.SCCS()) + env.Program('hello.c') + + + + % scons -Q + sccs get hello.c + cc -o hello.o -c hello.c + cc -o hello hello.o + + +
+ + diff --git a/doc/user/tasks.in b/doc/user/tasks.in new file mode 100644 index 0000000..14775c8 --- /dev/null +++ b/doc/user/tasks.in @@ -0,0 +1,108 @@ + + + +There is a common set of simple tasks that many build configurations rely +on as they become more complex. Most build tools have special +purpose constructs for performing these tasks, but since &SConscript; +files are &Python; scripts, you can use more flexible built-in &Python; +services to perform these tasks. This appendix lists a number of these +tasks and how to implement them in &Python;. + + + +Wildcard globbing to create a list of filenames + +files = Glob(wildcard) + + + + +Filename extension substitution + +import os.path +filename = os.path.splitext(filename)[0]+extension + + + + +Appending a path prefix to a list of filenames + +import os.path +filenames = [os.path.join(prefix, x) for x in filenames] + + +or in Python 1.5.2: + + +import os.path +new_filenames = [] +for x in filenames: + new_filenames.append(os.path.join(prefix, x)) + + + + +Substituting a path prefix with another one + +if filename.find(old_prefix) == 0: + filename = filename.replace(old_prefix, new_prefix) + + +or in Python 1.5.2: + + +import string +if string.find(filename, old_prefix) == 0: + filename = string.replace(filename, old_prefix, new_prefix) + + + + +Filtering a filename list to exclude/retain only a specific set +of extensions + +import os.path +filenames = [x for x in filenames if os.path.splitext(x)[1] in extensions] + + +or in Python 1.5.2: + + +import os.path +new_filenames = [] +for x in filenames: + if os.path.splitext(x)[1] in extensions: + new_filenames.append(x) + + + + +The "backtick function": run a shell command and capture the +output +import os +output = os.popen(command).read() + + diff --git a/doc/user/tasks.xml b/doc/user/tasks.xml new file mode 100644 index 0000000..14775c8 --- /dev/null +++ b/doc/user/tasks.xml @@ -0,0 +1,108 @@ + + + +There is a common set of simple tasks that many build configurations rely +on as they become more complex. Most build tools have special +purpose constructs for performing these tasks, but since &SConscript; +files are &Python; scripts, you can use more flexible built-in &Python; +services to perform these tasks. This appendix lists a number of these +tasks and how to implement them in &Python;. + + + +Wildcard globbing to create a list of filenames + +files = Glob(wildcard) + + + + +Filename extension substitution + +import os.path +filename = os.path.splitext(filename)[0]+extension + + + + +Appending a path prefix to a list of filenames + +import os.path +filenames = [os.path.join(prefix, x) for x in filenames] + + +or in Python 1.5.2: + + +import os.path +new_filenames = [] +for x in filenames: + new_filenames.append(os.path.join(prefix, x)) + + + + +Substituting a path prefix with another one + +if filename.find(old_prefix) == 0: + filename = filename.replace(old_prefix, new_prefix) + + +or in Python 1.5.2: + + +import string +if string.find(filename, old_prefix) == 0: + filename = string.replace(filename, old_prefix, new_prefix) + + + + +Filtering a filename list to exclude/retain only a specific set +of extensions + +import os.path +filenames = [x for x in filenames if os.path.splitext(x)[1] in extensions] + + +or in Python 1.5.2: + + +import os.path +new_filenames = [] +for x in filenames: + if os.path.splitext(x)[1] in extensions: + new_filenames.append(x) + + + + +The "backtick function": run a shell command and capture the +output +import os +output = os.popen(command).read() + + diff --git a/doc/user/tools.in b/doc/user/tools.in new file mode 100644 index 0000000..cc0628e --- /dev/null +++ b/doc/user/tools.in @@ -0,0 +1,38 @@ + + + + +This appendix contains descriptions of all of the +Tools modules that are +available "out of the box" in this version of SCons. + + + + + +&tools-gen; + + diff --git a/doc/user/tools.xml b/doc/user/tools.xml new file mode 100644 index 0000000..cc0628e --- /dev/null +++ b/doc/user/tools.xml @@ -0,0 +1,38 @@ + + + + +This appendix contains descriptions of all of the +Tools modules that are +available "out of the box" in this version of SCons. + + + + + +&tools-gen; + + diff --git a/doc/user/troubleshoot.in b/doc/user/troubleshoot.in new file mode 100644 index 0000000..c729149 --- /dev/null +++ b/doc/user/troubleshoot.in @@ -0,0 +1,875 @@ + + + + + The experience of configuring any + software build tool to build a large code base + usually, at some point, + involves trying to figure out why + the tool is behaving a certain way, + and how to get it to behave the way you want. + &SCons; is no different. + This appendix contains a number of + different ways in which you can + get some additional insight into &SCons' behavior. + + + + + + Note that we're always interested in trying to + improve how you can troubleshoot configuration problems. + If you run into a problem that has + you scratching your head, + and which there just doesn't seem to be a good way to debug, + odds are pretty good that someone else will run into + the same problem, too. + If so, please let the SCons development team know + (preferably by filing a bug report + or feature request at our project pages at tigris.org) + so that we can use your feedback + to try to come up with a better way to help you, + and others, get the necessary insight into &SCons; behavior + to help identify and fix configuration issues. + + + +
+ Why is That Target Being Rebuilt? the &debug-explain; Option + + + + Let's look at a simple example of + a misconfigured build + that causes a target to be rebuilt + every time &SCons; is run: + + + + + + # Intentionally misspell the output file name in the + # command used to create the file: + Command('file.out', 'file.in', 'cp $SOURCE file.oout') + + + file.in + + + + + + (Note to Windows users: The POSIX &cp; command + copies the first file named on the command line + to the second file. + In our example, it copies the &file_in; file + to the &file_out; file.) + + + + + + Now if we run &SCons; multiple times on this example, + we see that it re-runs the &cp; + command every time: + + + + + scons -Q + scons -Q + scons -Q + + + + + In this example, + the underlying cause is obvious: + we've intentionally misspelled the output file name + in the &cp; command, + so the command doesn't actually + build the &file_out; file that we've told &SCons; to expect. + But if the problem weren't obvious, + it would be helpful + to specify the &debug-explain; option + on the command line + to have &SCons; tell us very specifically + why it's decided to rebuild the target: + + + + + scons -Q --debug=explain + + + + + If this had been a more complicated example + involving a lot of build output, + having &SCons; tell us that + it's trying to rebuild the target file + because it doesn't exist + would be an important clue + that something was wrong with + the command that we invoked to build it. + + + + + + The &debug-explain; option also comes in handy + to help figure out what input file changed. + Given a simple configuration that builds + a program from three source files, + changing one of the source files + and rebuilding with the &debug-explain; + option shows very specifically + why &SCons; rebuilds the files that it does: + + + + + + Program('prog', ['file1.c', 'file2.c', 'file3.c']) + + + file1.c + + + file2.c + + + file3.c + + + + + scons -Q + edit file2.c + scons -Q --debug=explain + + + + + This becomes even more helpful + in identifying when a file is rebuilt + due to a change in an implicit dependency, + such as an incuded .h file. + If the file1.c + and file3.c files + in our example + both included a &hello_h; file, + then changing that included file + and re-running &SCons; with the &debug-explain; option + will pinpoint that it's the change to the included file + that starts the chain of rebuilds: + + + + + + Program('prog', ['file1.c', 'file2.c', 'file3.c'], CPPPATH='.') + + + #include <hello.h> + file1.c + + + file2.c + + + #include <hello.h> + file3.c + + + #define string "world" + + + + + scons -Q + edit hello.h + scons -Q --debug=explain + + + + + (Note that the &debug-explain; option will only tell you + why &SCons; decided to rebuild necessary targets. + It does not tell you what files it examined + when deciding not + to rebuild a target file, + which is often a more valuable question to answer.) + + + +
+ +
+ What's in That Construction Environment? the &Dump; Method + + + + When you create a construction environment, + &SCons; populates it + with construction variables that are set up + for various compilers, linkers and utilities + that it finds on your system. + Although this is usually helpful and what you want, + it might be frustrating if &SCons; + doesn't set certain variables that you + expect to be set. + In situations like this, + it's sometimes helpful to use the + construction environment &Dump; method + to print all or some of + the construction variables. + Note that the &Dump; method + returns + the representation of the variables + in the environment + for you to print (or otherwise manipulate): + + + + + + env = Environment() + print env.Dump() + + + + + + On a POSIX system with gcc installed, + this might generate: + + + + + scons + + + + + On a Windows system with Visual C++ + the output might look like: + + + + + scons + + + + + The construction environments in these examples have + actually been restricted to just gcc and Visual C++, + respectively. + In a real-life situation, + the construction environments will + likely contain a great many more variables. + Also note that we've massaged the example output above + to make the memory address of all objects a constant 0x700000. + In reality, you would see a different hexadecimal + number for each object. + + + + + + To make it easier to see just what you're + interested in, + the &Dump; method allows you to + specify a specific constrcution variable + that you want to disply. + For example, + it's not unusual to want to verify + the external environment used to execute build commands, + to make sure that the PATH and other + environment variables are set up the way they should be. + You can do this as follows: + + + + + + env = Environment() + print env.Dump('ENV') + + + + + + Which might display the following when executed on a POSIX system: + + + + + scons + + + + + And the following when executed on a Windows system: + + + + + scons + + +
+ +
+ + What Dependencies Does &SCons; Know About? the &tree; Option + + + + Sometimes the best way to try to figure out what + &SCons; is doing is simply to take a look at the + dependency graph that it constructs + based on your &SConscript; files. + The --tree option + will display all or part of the + &SCons; dependency graph in an + "ASCII art" graphical format + that shows the dependency hierarchy. + + + + + + For example, given the following input &SConstruct; file: + + + + + + env = Environment(CPPPATH = ['.']) + env.Program('prog', ['f1.c', 'f2.c', 'f3.c']) + + + #include "inc.h" + + + #include "inc.h" + + + #include "inc.h" + + + inc.h + + + + + + Running &SCons; with the --tree=all + option yields: + + + + + scons -Q --tree=all + + + + + The tree will also be printed when the + -n (no execute) option is used, + which allows you to examine the dependency graph + for a configuration without actually + rebuilding anything in the tree. + + + + + + The --tree option only prints + the dependency graph for the specified targets + (or the default target(s) if none are specified on the command line). + So if you specify a target like f2.o + on the command line, + the --tree option will only + print the dependency graph for that file: + + + + + scons -Q --tree=all f2.o + + + + + This is, of course, useful for + restricting the output from a very large + build configuration to just a + portion in which you're interested. + Multiple targets are fine, + in which case a tree will be printed + for each specified target: + + + + + scons -Q --tree=all f1.o f3.o + + + + + The status argument may be used + to tell &SCons; to print status information about + each file in the dependency graph: + + + + + scons -Q --tree=status + + + + + Note that --tree=all,status is equivalent; + the all + is assumed if only status is present. + As an alternative to all, + you can specify --tree=derived + to have &SCons; only print derived targets + in the tree output, + skipping source files + (like .c and .h files): + + + + + scons -Q --tree=derived + + + + + You can use the status + modifier with derived as well: + + + + + scons -Q --tree=derived,status + + + + + Note that the order of the --tree= + arguments doesn't matter; + --tree=status,derived is + completely equivalent. + + + + + + The default behavior of the --tree option + is to repeat all of the dependencies each time the library dependency + (or any other dependency file) is encountered in the tree. + If certain target files share other target files, + such as two programs that use the same library: + + + + + + env = Environment(CPPPATH = ['.'], + LIBS = ['foo'], + LIBPATH = ['.']) + env.Library('foo', ['f1.c', 'f2.c', 'f3.c']) + env.Program('prog1.c') + env.Program('prog2.c') + + + #include "inc.h" + + + #include "inc.h" + + + #include "inc.h" + + + #include "inc.h" + + + #include "inc.h" + + + inc.h + + + + + + Then there can be a lot of repetition in the + --tree= output: + + + + + scons -Q --tree=all + + + + + In a large configuration with many internal libraries + and include files, + this can very quickly lead to huge output trees. + To help make this more manageable, + a prune modifier may + be added to the option list, + in which case &SCons; + will print the name of a target that has + already been visited during the tree-printing + in [square brackets] + as an indication that the dependencies + of the target file may be found + by looking farther up the tree: + + + + + scons -Q --tree=prune + + + + + Like the status keyword, + the prune argument by itself + is equivalent to --tree=all,prune. + + + +
+ +
+ + How is &SCons; Constructing the Command Lines It Executes? the &debug-presub; Option + + + + Sometimes it's useful to look at the + pre-substitution string + that &SCons; uses to generate + the command lines it executes. + This can be done with the &debug-presub; option: + + + + + + env = Environment(CPPPATH = ['.']) + env.Program('prog', 'prog.c') + + + prog.c + + + + + + + % scons -Q --debug=presub + Building prog.o with action: + $CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCOMCOM $SOURCES + cc -o prog.o -c -I. prog.c + Building prog with action: + $SMART_LINKCOM + cc -o prog prog.o + + +
+ +
+ + Where is &SCons; Searching for Libraries? the &debug-findlibs; Option + + + + To get some insight into what library names + &SCons; is searching for, + and in which directories it is searching, + Use the --debug=findlibs option. + Given the following input &SConstruct; file: + + + + + + env = Environment(LIBPATH = ['libs1', 'libs2']) + env.Program('prog.c', LIBS=['foo', 'bar']) + + + prog.c + + + libs1/libfoo.a + + + libs2/libbar.a + + + + + + And the libraries libfoo.a + and libbar.a + in libs1 and libs2, + respectively, + use of the --debug=findlibs option yields: + + + + + scons -Q --debug=findlibs + + +
+ + + +
+ + Where is &SCons; Blowing Up? the &debug-stacktrace; Option + + + + In general, &SCons; tries to keep its error + messages short and informative. + That means we usually try to avoid showing + the stack traces that are familiar + to experienced Python programmers, + since they usually contain much more + information than is useful to most people. + + + + + + For example, the following &SConstruct file: + + + + + + Program('prog.c') + + + + + + Generates the following error if the + prog.c file + does not exist: + + + + + scons -Q + + + + + In this case, + the error is pretty obvious. + But if it weren't, + and you wanted to try to get more information + about the error, + the &debug-stacktrace; option + would show you exactly where in the &SCons; source code + the problem occurs: + + + + + scons -Q --debug=stacktrace + + + + + Of course, if you do need to dive into the &SCons; source code, + we'd like to know if, or how, + the error messages or troubleshooting options + could have been improved to avoid that. + Not everyone has the necessary time or + Python skill to dive into the source code, + and we'd like to improve &SCons; + for those people as well... + + + +
+ +
+ + How is &SCons; Making Its Decisions? the &taskmastertrace; Option + + + + The internal &SCons; subsystem that handles walking + the dependency graph + and controls the decision-making about what to rebuild + is the Taskmaster. + &SCons; supports a --taskmastertrace + option that tells the Taskmaster to print + information about the children (dependencies) + of the various Nodes on its walk down the graph, + which specific dependent Nodes are being evaluated, + and in what order. + + + + + + The --taskmastertrace option + takes as an argument the name of a file in + which to put the trace output, + with - (a single hyphen) + indicating that the trace messages + should be printed to the standard output: + + + + + + env = Environment(CPPPATH = ['.']) + env.Program('prog.c') + + + #include "inc.h" + prog.c + + + #define STRING "one" + + + + + scons -Q --taskmastertrace=- prog + + + + + The --taskmastertrace option + doesn't provide information about the actual + calculations involved in deciding if a file is up-to-date, + but it does show all of the dependencies + it knows about for each Node, + and the order in which those dependencies are evaluated. + This can be useful as an alternate way to determine + whether or not your &SCons; configuration, + or the implicit dependency scan, + has actually identified all the correct dependencies + you want it to. + + + +
+ + + + diff --git a/doc/user/troubleshoot.xml b/doc/user/troubleshoot.xml new file mode 100644 index 0000000..6bd53f9 --- /dev/null +++ b/doc/user/troubleshoot.xml @@ -0,0 +1,1313 @@ + + + + + The experience of configuring any + software build tool to build a large code base + usually, at some point, + involves trying to figure out why + the tool is behaving a certain way, + and how to get it to behave the way you want. + &SCons; is no different. + This appendix contains a number of + different ways in which you can + get some additional insight into &SCons;' behavior. + + + + + + Note that we're always interested in trying to + improve how you can troubleshoot configuration problems. + If you run into a problem that has + you scratching your head, + and which there just doesn't seem to be a good way to debug, + odds are pretty good that someone else will run into + the same problem, too. + If so, please let the SCons development team know + (preferably by filing a bug report + or feature request at our project pages at tigris.org) + so that we can use your feedback + to try to come up with a better way to help you, + and others, get the necessary insight into &SCons; behavior + to help identify and fix configuration issues. + + + +
+ Why is That Target Being Rebuilt? the &debug-explain; Option + + + + Let's look at a simple example of + a misconfigured build + that causes a target to be rebuilt + every time &SCons; is run: + + + + + # Intentionally misspell the output file name in the + # command used to create the file: + Command('file.out', 'file.in', 'cp $SOURCE file.oout') + + + + + (Note to Windows users: The POSIX &cp; command + copies the first file named on the command line + to the second file. + In our example, it copies the &file_in; file + to the &file_out; file.) + + + + + + Now if we run &SCons; multiple times on this example, + we see that it re-runs the &cp; + command every time: + + + + + % scons -Q + cp file.in file.oout + % scons -Q + cp file.in file.oout + % scons -Q + cp file.in file.oout + + + + + In this example, + the underlying cause is obvious: + we've intentionally misspelled the output file name + in the &cp; command, + so the command doesn't actually + build the &file_out; file that we've told &SCons; to expect. + But if the problem weren't obvious, + it would be helpful + to specify the &debug-explain; option + on the command line + to have &SCons; tell us very specifically + why it's decided to rebuild the target: + + + + + % scons -Q --debug=explain + scons: building `file.out' because it doesn't exist + cp file.in file.oout + + + + + If this had been a more complicated example + involving a lot of build output, + having &SCons; tell us that + it's trying to rebuild the target file + because it doesn't exist + would be an important clue + that something was wrong with + the command that we invoked to build it. + + + + + + The &debug-explain; option also comes in handy + to help figure out what input file changed. + Given a simple configuration that builds + a program from three source files, + changing one of the source files + and rebuilding with the &debug-explain; + option shows very specifically + why &SCons; rebuilds the files that it does: + + + + + + + % scons -Q + cc -o file1.o -c file1.c + cc -o file2.o -c file2.c + cc -o file3.o -c file3.c + cc -o prog file1.o file2.o file3.o + % edit file2.c + [CHANGE THE CONTENTS OF file2.c] + % scons -Q --debug=explain + scons: rebuilding `file2.o' because `file2.c' changed + cc -o file2.o -c file2.c + scons: rebuilding `prog' because `file2.o' changed + cc -o prog file1.o file2.o file3.o + + + + + This becomes even more helpful + in identifying when a file is rebuilt + due to a change in an implicit dependency, + such as an incuded .h file. + If the file1.c + and file3.c files + in our example + both included a &hello_h; file, + then changing that included file + and re-running &SCons; with the &debug-explain; option + will pinpoint that it's the change to the included file + that starts the chain of rebuilds: + + + + + + + % scons -Q + cc -o file1.o -c -I. file1.c + cc -o file2.o -c -I. file2.c + cc -o file3.o -c -I. file3.c + cc -o prog file1.o file2.o file3.o + % edit hello.h + [CHANGE THE CONTENTS OF hello.h] + % scons -Q --debug=explain + scons: rebuilding `file1.o' because `hello.h' changed + cc -o file1.o -c -I. file1.c + scons: rebuilding `file3.o' because `hello.h' changed + cc -o file3.o -c -I. file3.c + scons: rebuilding `prog' because: + `file1.o' changed + `file3.o' changed + cc -o prog file1.o file2.o file3.o + + + + + (Note that the &debug-explain; option will only tell you + why &SCons; decided to rebuild necessary targets. + It does not tell you what files it examined + when deciding not + to rebuild a target file, + which is often a more valuable question to answer.) + + + +
+ +
+ What's in That Construction Environment? the &Dump; Method + + + + When you create a construction environment, + &SCons; populates it + with construction variables that are set up + for various compilers, linkers and utilities + that it finds on your system. + Although this is usually helpful and what you want, + it might be frustrating if &SCons; + doesn't set certain variables that you + expect to be set. + In situations like this, + it's sometimes helpful to use the + construction environment &Dump; method + to print all or some of + the construction variables. + Note that the &Dump; method + returns + the representation of the variables + in the environment + for you to print (or otherwise manipulate): + + + + + env = Environment() + print env.Dump() + + + + + On a POSIX system with gcc installed, + this might generate: + + + + + % scons + scons: Reading SConscript files ... + { 'BUILDERS': {'_InternalInstall': <function InstallBuilderWrapper at 0x700000>, '_InternalInstallAs': <function InstallAsBuilderWrapper at 0x700000>}, + 'CONFIGUREDIR': '#/.sconf_temp', + 'CONFIGURELOG': '#/config.log', + 'CPPSUFFIXES': [ '.c', + '.C', + '.cxx', + '.cpp', + '.c++', + '.cc', + '.h', + '.H', + '.hxx', + '.hpp', + '.hh', + '.F', + '.fpp', + '.FPP', + '.m', + '.mm', + '.S', + '.spp', + '.SPP'], + 'DSUFFIXES': ['.d'], + 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'ENV': {'PATH': '/usr/local/bin:/opt/bin:/bin:/usr/bin'}, + 'ESCAPE': <function escape at 0x700000>, + 'File': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'HOST_ARCH': None, + 'HOST_OS': None, + 'IDLSUFFIXES': ['.idl', '.IDL'], + 'INSTALL': <function copyFunc at 0x700000>, + 'LIBPREFIX': 'lib', + 'LIBPREFIXES': ['$LIBPREFIX'], + 'LIBSUFFIX': '.a', + 'LIBSUFFIXES': ['$LIBSUFFIX', '$SHLIBSUFFIX'], + 'MAXLINELENGTH': 128072, + 'OBJPREFIX': '', + 'OBJSUFFIX': '.o', + 'PLATFORM': 'posix', + 'PROGPREFIX': '', + 'PROGSUFFIX': '', + 'PSPAWN': <function piped_env_spawn at 0x700000>, + 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'SCANNERS': [], + 'SHELL': 'sh', + 'SHLIBPREFIX': '$LIBPREFIX', + 'SHLIBSUFFIX': '.so', + 'SHOBJPREFIX': '$OBJPREFIX', + 'SHOBJSUFFIX': '$OBJSUFFIX', + 'SPAWN': <function spawnvpe_spawn at 0x700000>, + 'TARGET_ARCH': None, + 'TARGET_OS': None, + 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0x700000>, + 'TEMPFILEPREFIX': '@', + 'TOOLS': ['install', 'install'], + '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}', + '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', + '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', + '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', + '__RPATH': '$_RPATH', + '_concat': <function _concat at 0x700000>, + '_defines': <function _defines at 0x700000>, + '_stripixes': <function _stripixes at 0x700000>} + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + + + + + On a Windows system with Visual C++ + the output might look like: + + + + + C:\>scons + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + { 'BUILDERS': {'_InternalInstall': <function InstallBuilderWrapper at 0x700000>, 'Object': <SCons.Builder.CompositeBuilder instance at 0x700000>, 'PCH': <SCons.Builder.BuilderBase instance at 0x700000>, 'RES': <SCons.Builder.BuilderBase instance at 0x700000>, 'SharedObject': <SCons.Builder.CompositeBuilder instance at 0x700000>, 'StaticObject': <SCons.Builder.CompositeBuilder instance at 0x700000>, '_InternalInstallAs': <function InstallAsBuilderWrapper at 0x700000>}, + 'CC': 'cl', + 'CCCOM': <SCons.Action.FunctionAction instance at 0x700000>, + 'CCFLAGS': ['/nologo'], + 'CCPCHFLAGS': ['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}'], + 'CCPDBFLAGS': ['${(PDB and "/Z7") or ""}'], + 'CFILESUFFIX': '.c', + 'CFLAGS': [], + 'CONFIGUREDIR': '#/.sconf_temp', + 'CONFIGURELOG': '#/config.log', + 'CPPDEFPREFIX': '/D', + 'CPPDEFSUFFIX': '', + 'CPPSUFFIXES': [ '.c', + '.C', + '.cxx', + '.cpp', + '.c++', + '.cc', + '.h', + '.H', + '.hxx', + '.hpp', + '.hh', + '.F', + '.fpp', + '.FPP', + '.m', + '.mm', + '.S', + '.spp', + '.SPP'], + 'CXX': '$CC', + 'CXXCOM': '$CXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CXXFLAGS $CCFLAGS $_CCCOMCOM', + 'CXXFILESUFFIX': '.cc', + 'CXXFLAGS': ['$(', '/TP', '$)'], + 'DSUFFIXES': ['.d'], + 'Dir': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'Dirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'ENV': { 'PATH': 'C:/WINDOWS\\System32', + 'PATHEXT': '.COM;.EXE;.BAT;.CMD', + 'SystemRoot': 'C:/WINDOWS'}, + 'ESCAPE': <function escape at 0x700000>, + 'File': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'HOST_ARCH': '', + 'HOST_OS': 'win32', + 'IDLSUFFIXES': ['.idl', '.IDL'], + 'INCPREFIX': '/I', + 'INCSUFFIX': '', + 'INSTALL': <function copyFunc at 0x700000>, + 'LIBPREFIX': '', + 'LIBPREFIXES': ['$LIBPREFIX'], + 'LIBSUFFIX': '.lib', + 'LIBSUFFIXES': ['$LIBSUFFIX'], + 'MAXLINELENGTH': 2048, + 'MSVC_SETUP_RUN': True, + 'OBJPREFIX': '', + 'OBJSUFFIX': '.obj', + 'PCHCOM': '$CXX /Fo${TARGETS[1]} $CXXFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS $PCHPDBFLAGS', + 'PCHPDBFLAGS': ['${(PDB and "/Yd") or ""}'], + 'PLATFORM': 'win32', + 'PROGPREFIX': '', + 'PROGSUFFIX': '.exe', + 'PSPAWN': <function piped_spawn at 0x700000>, + 'RC': 'rc', + 'RCCOM': <SCons.Action.FunctionAction instance at 0x700000>, + 'RCFLAGS': [], + 'RCSUFFIXES': ['.rc', '.rc2'], + 'RDirs': <SCons.Defaults.Variable_Method_Caller instance at 0x700000>, + 'SCANNERS': [], + 'SHCC': '$CC', + 'SHCCCOM': <SCons.Action.FunctionAction instance at 0x700000>, + 'SHCCFLAGS': ['$CCFLAGS'], + 'SHCFLAGS': ['$CFLAGS'], + 'SHCXX': '$CXX', + 'SHCXXCOM': '$SHCXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM', + 'SHCXXFLAGS': ['$CXXFLAGS'], + 'SHELL': None, + 'SHLIBPREFIX': '', + 'SHLIBSUFFIX': '.dll', + 'SHOBJPREFIX': '$OBJPREFIX', + 'SHOBJSUFFIX': '$OBJSUFFIX', + 'SPAWN': <function spawn at 0x700000>, + 'STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME': 1, + 'TARGET_ARCH': '', + 'TARGET_OS': 'win32', + 'TEMPFILE': <class SCons.Platform.TempFileMunge at 0x700000>, + 'TEMPFILEPREFIX': '@', + 'TOOLS': ['msvc', 'install', 'install'], + '_CCCOMCOM': '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $CCPCHFLAGS $CCPDBFLAGS', + '_CPPDEFFLAGS': '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}', + '_CPPINCFLAGS': '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', + '_LIBDIRFLAGS': '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', + '_LIBFLAGS': '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', + '_MSVC_OUTPUT_FLAG': <function msvc_output_flag at 0x700000>, + '_concat': <function _concat at 0x700000>, + '_defines': <function _defines at 0x700000>, + '_stripixes': <function _stripixes at 0x700000>} + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + + + + + The construction environments in these examples have + actually been restricted to just gcc and Visual C++, + respectively. + In a real-life situation, + the construction environments will + likely contain a great many more variables. + Also note that we've massaged the example output above + to make the memory address of all objects a constant 0x700000. + In reality, you would see a different hexadecimal + number for each object. + + + + + + To make it easier to see just what you're + interested in, + the &Dump; method allows you to + specify a specific constrcution variable + that you want to disply. + For example, + it's not unusual to want to verify + the external environment used to execute build commands, + to make sure that the PATH and other + environment variables are set up the way they should be. + You can do this as follows: + + + + + env = Environment() + print env.Dump('ENV') + + + + + Which might display the following when executed on a POSIX system: + + + + + % scons + scons: Reading SConscript files ... + {'PATH': '/usr/local/bin:/opt/bin:/bin:/usr/bin'} + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + + + + + And the following when executed on a Windows system: + + + + + C:\>scons + scons: Reading SConscript files ... + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + { 'PATH': 'C:/WINDOWS\\System32', + 'PATHEXT': '.COM;.EXE;.BAT;.CMD', + 'SystemRoot': 'C:/WINDOWS'} + scons: done reading SConscript files. + scons: Building targets ... + scons: `.' is up to date. + scons: done building targets. + + +
+ +
+ + What Dependencies Does &SCons; Know About? the &tree; Option + + + + Sometimes the best way to try to figure out what + &SCons; is doing is simply to take a look at the + dependency graph that it constructs + based on your &SConscript; files. + The --tree option + will display all or part of the + &SCons; dependency graph in an + "ASCII art" graphical format + that shows the dependency hierarchy. + + + + + + For example, given the following input &SConstruct; file: + + + + + env = Environment(CPPPATH = ['.']) + env.Program('prog', ['f1.c', 'f2.c', 'f3.c']) + + + + + Running &SCons; with the --tree=all + option yields: + + + + + % scons -Q --tree=all + cc -o f1.o -c -I. f1.c + cc -o f2.o -c -I. f2.c + cc -o f3.o -c -I. f3.c + cc -o prog f1.o f2.o f3.o + +-. + +-SConstruct + +-f1.c + +-f1.o + | +-f1.c + | +-inc.h + +-f2.c + +-f2.o + | +-f2.c + | +-inc.h + +-f3.c + +-f3.o + | +-f3.c + | +-inc.h + +-inc.h + +-prog + +-f1.o + | +-f1.c + | +-inc.h + +-f2.o + | +-f2.c + | +-inc.h + +-f3.o + +-f3.c + +-inc.h + + + + + The tree will also be printed when the + -n (no execute) option is used, + which allows you to examine the dependency graph + for a configuration without actually + rebuilding anything in the tree. + + + + + + The --tree option only prints + the dependency graph for the specified targets + (or the default target(s) if none are specified on the command line). + So if you specify a target like f2.o + on the command line, + the --tree option will only + print the dependency graph for that file: + + + + + % scons -Q --tree=all f2.o + cc -o f2.o -c -I. f2.c + +-f2.o + +-f2.c + +-inc.h + + + + + This is, of course, useful for + restricting the output from a very large + build configuration to just a + portion in which you're interested. + Multiple targets are fine, + in which case a tree will be printed + for each specified target: + + + + + % scons -Q --tree=all f1.o f3.o + cc -o f1.o -c -I. f1.c + +-f1.o + +-f1.c + +-inc.h + cc -o f3.o -c -I. f3.c + +-f3.o + +-f3.c + +-inc.h + + + + + The status argument may be used + to tell &SCons; to print status information about + each file in the dependency graph: + + + + + % scons -Q --tree=status + cc -o f1.o -c -I. f1.c + cc -o f2.o -c -I. f2.c + cc -o f3.o -c -I. f3.c + cc -o prog f1.o f2.o f3.o + E = exists + R = exists in repository only + b = implicit builder + B = explicit builder + S = side effect + P = precious + A = always build + C = current + N = no clean + H = no cache + + [E b ]+-. + [E C ] +-SConstruct + [E C ] +-f1.c + [E B C ] +-f1.o + [E C ] | +-f1.c + [E C ] | +-inc.h + [E C ] +-f2.c + [E B C ] +-f2.o + [E C ] | +-f2.c + [E C ] | +-inc.h + [E C ] +-f3.c + [E B C ] +-f3.o + [E C ] | +-f3.c + [E C ] | +-inc.h + [E C ] +-inc.h + [E B C ] +-prog + [E B C ] +-f1.o + [E C ] | +-f1.c + [E C ] | +-inc.h + [E B C ] +-f2.o + [E C ] | +-f2.c + [E C ] | +-inc.h + [E B C ] +-f3.o + [E C ] +-f3.c + [E C ] +-inc.h + + + + + Note that --tree=all,status is equivalent; + the all + is assumed if only status is present. + As an alternative to all, + you can specify --tree=derived + to have &SCons; only print derived targets + in the tree output, + skipping source files + (like .c and .h files): + + + + + % scons -Q --tree=derived + cc -o f1.o -c -I. f1.c + cc -o f2.o -c -I. f2.c + cc -o f3.o -c -I. f3.c + cc -o prog f1.o f2.o f3.o + +-. + +-f1.o + +-f2.o + +-f3.o + +-prog + +-f1.o + +-f2.o + +-f3.o + + + + + You can use the status + modifier with derived as well: + + + + + % scons -Q --tree=derived,status + cc -o f1.o -c -I. f1.c + cc -o f2.o -c -I. f2.c + cc -o f3.o -c -I. f3.c + cc -o prog f1.o f2.o f3.o + E = exists + R = exists in repository only + b = implicit builder + B = explicit builder + S = side effect + P = precious + A = always build + C = current + N = no clean + H = no cache + + [E b ]+-. + [E B C ] +-f1.o + [E B C ] +-f2.o + [E B C ] +-f3.o + [E B C ] +-prog + [E B C ] +-f1.o + [E B C ] +-f2.o + [E B C ] +-f3.o + + + + + Note that the order of the --tree= + arguments doesn't matter; + --tree=status,derived is + completely equivalent. + + + + + + The default behavior of the --tree option + is to repeat all of the dependencies each time the library dependency + (or any other dependency file) is encountered in the tree. + If certain target files share other target files, + such as two programs that use the same library: + + + + + env = Environment(CPPPATH = ['.'], + LIBS = ['foo'], + LIBPATH = ['.']) + env.Library('foo', ['f1.c', 'f2.c', 'f3.c']) + env.Program('prog1.c') + env.Program('prog2.c') + + + + + Then there can be a lot of repetition in the + --tree= output: + + + + + % scons -Q --tree=all + cc -o f1.o -c -I. f1.c + cc -o f2.o -c -I. f2.c + cc -o f3.o -c -I. f3.c + ar rc libfoo.a f1.o f2.o f3.o + ranlib libfoo.a + cc -o prog1.o -c -I. prog1.c + cc -o prog1 prog1.o -L. -lfoo + cc -o prog2.o -c -I. prog2.c + cc -o prog2 prog2.o -L. -lfoo + +-. + +-SConstruct + +-f1.c + +-f1.o + | +-f1.c + | +-inc.h + +-f2.c + +-f2.o + | +-f2.c + | +-inc.h + +-f3.c + +-f3.o + | +-f3.c + | +-inc.h + +-inc.h + +-libfoo.a + | +-f1.o + | | +-f1.c + | | +-inc.h + | +-f2.o + | | +-f2.c + | | +-inc.h + | +-f3.o + | +-f3.c + | +-inc.h + +-prog1 + | +-prog1.o + | | +-prog1.c + | | +-inc.h + | +-libfoo.a + | +-f1.o + | | +-f1.c + | | +-inc.h + | +-f2.o + | | +-f2.c + | | +-inc.h + | +-f3.o + | +-f3.c + | +-inc.h + +-prog1.c + +-prog1.o + | +-prog1.c + | +-inc.h + +-prog2 + | +-prog2.o + | | +-prog2.c + | | +-inc.h + | +-libfoo.a + | +-f1.o + | | +-f1.c + | | +-inc.h + | +-f2.o + | | +-f2.c + | | +-inc.h + | +-f3.o + | +-f3.c + | +-inc.h + +-prog2.c + +-prog2.o + +-prog2.c + +-inc.h + + + + + In a large configuration with many internal libraries + and include files, + this can very quickly lead to huge output trees. + To help make this more manageable, + a prune modifier may + be added to the option list, + in which case &SCons; + will print the name of a target that has + already been visited during the tree-printing + in [square brackets] + as an indication that the dependencies + of the target file may be found + by looking farther up the tree: + + + + + % scons -Q --tree=prune + cc -o f1.o -c -I. f1.c + cc -o f2.o -c -I. f2.c + cc -o f3.o -c -I. f3.c + ar rc libfoo.a f1.o f2.o f3.o + ranlib libfoo.a + cc -o prog1.o -c -I. prog1.c + cc -o prog1 prog1.o -L. -lfoo + cc -o prog2.o -c -I. prog2.c + cc -o prog2 prog2.o -L. -lfoo + +-. + +-SConstruct + +-f1.c + +-f1.o + | +-f1.c + | +-inc.h + +-f2.c + +-f2.o + | +-f2.c + | +-inc.h + +-f3.c + +-f3.o + | +-f3.c + | +-inc.h + +-inc.h + +-libfoo.a + | +-[f1.o] + | +-[f2.o] + | +-[f3.o] + +-prog1 + | +-prog1.o + | | +-prog1.c + | | +-inc.h + | +-[libfoo.a] + +-prog1.c + +-[prog1.o] + +-prog2 + | +-prog2.o + | | +-prog2.c + | | +-inc.h + | +-[libfoo.a] + +-prog2.c + +-[prog2.o] + + + + + Like the status keyword, + the prune argument by itself + is equivalent to --tree=all,prune. + + + +
+ +
+ + How is &SCons; Constructing the Command Lines It Executes? the &debug-presub; Option + + + + Sometimes it's useful to look at the + pre-substitution string + that &SCons; uses to generate + the command lines it executes. + This can be done with the &debug-presub; option: + + + + + + + + + % scons -Q --debug=presub + Building prog.o with action: + $CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCOMCOM $SOURCES + cc -o prog.o -c -I. prog.c + Building prog with action: + $SMART_LINKCOM + cc -o prog prog.o + + +
+ +
+ + Where is &SCons; Searching for Libraries? the &debug-findlibs; Option + + + + To get some insight into what library names + &SCons; is searching for, + and in which directories it is searching, + Use the --debug=findlibs option. + Given the following input &SConstruct; file: + + + + + env = Environment(LIBPATH = ['libs1', 'libs2']) + env.Program('prog.c', LIBS=['foo', 'bar']) + + + + + And the libraries libfoo.a + and libbar.a + in libs1 and libs2, + respectively, + use of the --debug=findlibs option yields: + + + + + % scons -Q --debug=findlibs + findlibs: looking for 'libfoo.a' in 'libs1' ... + findlibs: ... FOUND 'libfoo.a' in 'libs1' + findlibs: looking for 'libfoo.so' in 'libs1' ... + findlibs: looking for 'libfoo.so' in 'libs2' ... + findlibs: looking for 'libbar.a' in 'libs1' ... + findlibs: looking for 'libbar.a' in 'libs2' ... + findlibs: ... FOUND 'libbar.a' in 'libs2' + findlibs: looking for 'libbar.so' in 'libs1' ... + findlibs: looking for 'libbar.so' in 'libs2' ... + cc -o prog.o -c prog.c + cc -o prog prog.o -Llibs1 -Llibs2 -lfoo -lbar + + +
+ + + +
+ + Where is &SCons; Blowing Up? the &debug-stacktrace; Option + + + + In general, &SCons; tries to keep its error + messages short and informative. + That means we usually try to avoid showing + the stack traces that are familiar + to experienced Python programmers, + since they usually contain much more + information than is useful to most people. + + + + + + For example, the following &SConstruct; file: + + + + + Program('prog.c') + + + + + Generates the following error if the + prog.c file + does not exist: + + + + + % scons -Q + scons: *** [prog.o] Source `prog.c' not found, needed by target `prog.o'. + + + + + In this case, + the error is pretty obvious. + But if it weren't, + and you wanted to try to get more information + about the error, + the &debug-stacktrace; option + would show you exactly where in the &SCons; source code + the problem occurs: + + + + + % scons -Q --debug=stacktrace + scons: *** [prog.o] Source `prog.c' not found, needed by target `prog.o'. + scons: internal stack trace: + File "bootstrap/src/engine/SCons/Job.py", line 197, in start + File "bootstrap/src/engine/SCons/Script/Main.py", line 167, in prepare + File "bootstrap/src/engine/SCons/Taskmaster.py", line 190, in prepare + File "bootstrap/src/engine/SCons/Executor.py", line 397, in prepare + + + + + Of course, if you do need to dive into the &SCons; source code, + we'd like to know if, or how, + the error messages or troubleshooting options + could have been improved to avoid that. + Not everyone has the necessary time or + Python skill to dive into the source code, + and we'd like to improve &SCons; + for those people as well... + + + +
+ +
+ + How is &SCons; Making Its Decisions? the &taskmastertrace; Option + + + + The internal &SCons; subsystem that handles walking + the dependency graph + and controls the decision-making about what to rebuild + is the Taskmaster. + &SCons; supports a --taskmastertrace + option that tells the Taskmaster to print + information about the children (dependencies) + of the various Nodes on its walk down the graph, + which specific dependent Nodes are being evaluated, + and in what order. + + + + + + The --taskmastertrace option + takes as an argument the name of a file in + which to put the trace output, + with - (a single hyphen) + indicating that the trace messages + should be printed to the standard output: + + + + + env = Environment(CPPPATH = ['.']) + env.Program('prog.c') + + + + % scons -Q --taskmastertrace=- prog + + Taskmaster: Looking for a node to evaluate + Taskmaster: Considering node <no_state 0 'prog'> and its children: + Taskmaster: <no_state 0 'prog.o'> + Taskmaster: adjusted ref count: <pending 1 'prog'>, child 'prog.o' + Taskmaster: Considering node <no_state 0 'prog.o'> and its children: + Taskmaster: <no_state 0 'prog.c'> + Taskmaster: <no_state 0 'inc.h'> + Taskmaster: adjusted ref count: <pending 1 'prog.o'>, child 'prog.c' + Taskmaster: adjusted ref count: <pending 2 'prog.o'>, child 'inc.h' + Taskmaster: Considering node <no_state 0 'prog.c'> and its children: + Taskmaster: Evaluating <pending 0 'prog.c'> + + Task.make_ready_current(): node <pending 0 'prog.c'> + Task.prepare(): node <up_to_date 0 'prog.c'> + Task.executed_with_callbacks(): node <up_to_date 0 'prog.c'> + Task.postprocess(): node <up_to_date 0 'prog.c'> + Task.postprocess(): removing <up_to_date 0 'prog.c'> + Task.postprocess(): adjusted parent ref count <pending 1 'prog.o'> + + Taskmaster: Looking for a node to evaluate + Taskmaster: Considering node <no_state 0 'inc.h'> and its children: + Taskmaster: Evaluating <pending 0 'inc.h'> + + Task.make_ready_current(): node <pending 0 'inc.h'> + Task.prepare(): node <up_to_date 0 'inc.h'> + Task.executed_with_callbacks(): node <up_to_date 0 'inc.h'> + Task.postprocess(): node <up_to_date 0 'inc.h'> + Task.postprocess(): removing <up_to_date 0 'inc.h'> + Task.postprocess(): adjusted parent ref count <pending 0 'prog.o'> + + Taskmaster: Looking for a node to evaluate + Taskmaster: Considering node <pending 0 'prog.o'> and its children: + Taskmaster: <up_to_date 0 'prog.c'> + Taskmaster: <up_to_date 0 'inc.h'> + Taskmaster: Evaluating <pending 0 'prog.o'> + + Task.make_ready_current(): node <pending 0 'prog.o'> + Task.prepare(): node <executing 0 'prog.o'> + Task.execute(): node <executing 0 'prog.o'> + cc -o prog.o -c -I. prog.c + Task.executed_with_callbacks(): node <executing 0 'prog.o'> + Task.postprocess(): node <executed 0 'prog.o'> + Task.postprocess(): removing <executed 0 'prog.o'> + Task.postprocess(): adjusted parent ref count <pending 0 'prog'> + + Taskmaster: Looking for a node to evaluate + Taskmaster: Considering node <pending 0 'prog'> and its children: + Taskmaster: <executed 0 'prog.o'> + Taskmaster: Evaluating <pending 0 'prog'> + + Task.make_ready_current(): node <pending 0 'prog'> + Task.prepare(): node <executing 0 'prog'> + Task.execute(): node <executing 0 'prog'> + cc -o prog prog.o + Task.executed_with_callbacks(): node <executing 0 'prog'> + Task.postprocess(): node <executed 0 'prog'> + + Taskmaster: Looking for a node to evaluate + Taskmaster: No candidate anymore. + + + + + The --taskmastertrace option + doesn't provide information about the actual + calculations involved in deciding if a file is up-to-date, + but it does show all of the dependencies + it knows about for each Node, + and the order in which those dependencies are evaluated. + This can be useful as an alternate way to determine + whether or not your &SCons; configuration, + or the implicit dependency scan, + has actually identified all the correct dependencies + you want it to. + + + +
+ + + + diff --git a/doc/user/variables.in b/doc/user/variables.in new file mode 100644 index 0000000..85fb9cd --- /dev/null +++ b/doc/user/variables.in @@ -0,0 +1,56 @@ + + + + +This appendix contains descriptions of all of the +construction variables that are potentially +available "out of the box" in this version of SCons. +Whether or not setting a construction variable +in a construction environment +will actually have an effect depends on +whether any of the Tools and/or Builders +that use the variable have been +included in the construction environment. + + + + + +In this appendix, we have +appended the initial $ +(dollar sign) to the beginning of each +variable name when it appears in the text, +but left off the dollar sign +in the left-hand column +where the name appears for each entry. + + + + + +&variables-gen; + + diff --git a/doc/user/variables.xml b/doc/user/variables.xml new file mode 100644 index 0000000..85fb9cd --- /dev/null +++ b/doc/user/variables.xml @@ -0,0 +1,56 @@ + + + + +This appendix contains descriptions of all of the +construction variables that are potentially +available "out of the box" in this version of SCons. +Whether or not setting a construction variable +in a construction environment +will actually have an effect depends on +whether any of the Tools and/or Builders +that use the variable have been +included in the construction environment. + + + + + +In this appendix, we have +appended the initial $ +(dollar sign) to the beginning of each +variable name when it appears in the text, +but left off the dollar sign +in the left-hand column +where the name appears for each entry. + + + + + +&variables-gen; + + diff --git a/doc/user/variants.in b/doc/user/variants.in new file mode 100644 index 0000000..696e174 --- /dev/null +++ b/doc/user/variants.in @@ -0,0 +1,151 @@ + + + + + + + The &variant_dir; keyword argument of + the &SConscript; function provides everything + we need to show how easy it is to create + variant builds using &SCons;. + Suppose, for example, that we want to + build a program for both Windows and Linux platforms, + but that we want to build it in a shared directory + with separate side-by-side build directories + for the Windows and Linux versions of the program. + + + + + + platform = ARGUMENTS.get('OS', Platform()) + + include = "#export/$PLATFORM/include" + lib = "#export/$PLATFORM/lib" + bin = "#export/$PLATFORM/bin" + + env = Environment(PLATFORM = platform, + BINDIR = bin, + INCDIR = include, + LIBDIR = lib, + CPPPATH = [include], + LIBPATH = [lib], + LIBS = 'world') + + Export('env') + + env.SConscript('src/SConscript', variant_dir='build/$PLATFORM') + + + + + + Import('env') + SConscript('hello/SConscript') + SConscript('world/SConscript') + + + Import('env') + hello = env.Program('hello.c') + env.Install('$BINDIR', hello) + + + #include "world.h" + int main(int argc, char *argv[]) { printf "hello.c\n"; world(); } + + + Import('env') + world = env.Library('world.c') + env.Install('$LIBDIR', world) + env.Install('$INCDIR', 'world.h') + + + #define STRING "world.h" + extern int world(); + + + int world() { printf "world.c\n"; } + + + + + + This SConstruct file, + when run on a Linux system, yields: + + + + + scons -Q OS=linux + + + + + The same SConstruct file on Windows would build: + + + + + scons -Q OS=windows + + + diff --git a/doc/user/variants.xml b/doc/user/variants.xml new file mode 100644 index 0000000..016202a --- /dev/null +++ b/doc/user/variants.xml @@ -0,0 +1,146 @@ + + + + + + + The &variant_dir; keyword argument of + the &SConscript; function provides everything + we need to show how easy it is to create + variant builds using &SCons;. + Suppose, for example, that we want to + build a program for both Windows and Linux platforms, + but that we want to build it in a shared directory + with separate side-by-side build directories + for the Windows and Linux versions of the program. + + + + + platform = ARGUMENTS.get('OS', Platform()) + + include = "#export/$PLATFORM/include" + lib = "#export/$PLATFORM/lib" + bin = "#export/$PLATFORM/bin" + + env = Environment(PLATFORM = platform, + BINDIR = bin, + INCDIR = include, + LIBDIR = lib, + CPPPATH = [include], + LIBPATH = [lib], + LIBS = 'world') + + Export('env') + + env.SConscript('src/SConscript', variant_dir='build/$PLATFORM') + + + + + This SConstruct file, + when run on a Linux system, yields: + + + + + % scons -Q OS=linux + Install file: "build/linux/world/world.h" as "export/linux/include/world.h" + cc -o build/linux/hello/hello.o -c -Iexport/linux/include build/linux/hello/hello.c + cc -o build/linux/world/world.o -c -Iexport/linux/include build/linux/world/world.c + ar rc build/linux/world/libworld.a build/linux/world/world.o + ranlib build/linux/world/libworld.a + Install file: "build/linux/world/libworld.a" as "export/linux/lib/libworld.a" + cc -o build/linux/hello/hello build/linux/hello/hello.o -Lexport/linux/lib -lworld + Install file: "build/linux/hello/hello" as "export/linux/bin/hello" + + + + + The same SConstruct file on Windows would build: + + + + + C:\>scons -Q OS=windows + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + + scons: warning: No installed VCs + File "<stdin>", line 67, in __call__ + + scons: warning: No version of Visual Studio compiler found - C/C++ compilers most likely not set correctly + File "<stdin>", line 67, in __call__ + Install file: "build/windows/world/world.h" as "export/windows/include/world.h" + cl /Fobuild\windows\hello\hello.obj /c build\windows\hello\hello.c /nologo /Iexport\windows\include + cl /Fobuild\windows\world\world.obj /c build\windows\world\world.c /nologo /Iexport\windows\include + lib /nologo /OUT:build\windows\world\world.lib build\windows\world\world.obj + Install file: "build/windows/world/world.lib" as "export/windows/lib/world.lib" + link /nologo /OUT:build\windows\hello\hello.exe /LIBPATH:export\windows\lib world.lib build\windows\hello\hello.obj + Install file: "build/windows/hello/hello.exe" as "export/windows/bin/hello.exe" + + + diff --git a/src/CHANGES.txt b/src/CHANGES.txt new file mode 100644 index 0000000..eb2a5ad --- /dev/null +++ b/src/CHANGES.txt @@ -0,0 +1,5176 @@ +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# src/CHANGES.txt 4577 2009/12/27 19:44:43 scons + + + SCons - a software construction tool + + Change Log + + + +RELEASE 1.2.0.d20091224 - Thu, 24 Dec 2009 17:20:55 -08000 + + From Jim Randall: + - Fixed temp filename race condition on Windows with long cmd lines. + + From David Cournapeau: + - Fixed tryRun when sconf directory is in a variant dir. + - Do not add -fPIC for ifort tool on non-posix platforms (darwin and + windows). + - Fix bug 2294 (spurious CheckCC failures). + - Fix scons bootstrap process on windows 64 (wrong wininst name) + + From William Deegan: + - Final merge from vs_revamp branch to main + + - Added definition and usage of HOST_OS, HOST_ARCH, TARGET_OS, + TARGET_ARCH, currently only defined/used by Visual Studio + Compilers. This will be rolled out to other platforms/tools + in the future. + + - Add check for python >= 3.0.0 and exit gracefully. + For 1.3 python >= 1.5.2 and < 3.0.0 are supported + + - Fix bug 1944 - Handle non-existent .i file in swig emitter, previously + it would crash with an IOError exception. Now it will try to make an + educated guess on the module name based on the filename. + + From Lukas Erlinghagen: + + - Have AddOption() remove variables from the list of + seen-but-unknown variables (which are reported later). + + - An option name and aliases can now be specified as a tuple. + + From Hartmut Goebel: + + - Textfile builder. + + From Jared Grubb: + + - use "is/is not" in comparisons with None instead of "==" or "!=". + + From Jim Hunziker: + + - Avoid adding -gphobos to a command line multiple times + when initializing use of the DMD compiler. + + From Jason Kenney: + + - Sugguested HOST/TARGET OS/ARCH separation. + + From Steven Knight: + + - Fix the -n option when used with VariantDir(duplicate=1) + and the variant directory doesn't already exist. + + - Fix scanning of Unicode files for both UTF-16 endian flavors. + + - Fix a TypeError on #include of file names with Unicode characters. + + - Fix an exception if a null command-line argument is passed in. + + - Evaluate Requires() prerequisites before a Node's direct children + (sources and dependencies). + + From Greg Noel: + + - Remove redundant __metaclass__ initializations in Environment.py. + + - Correct the documentation of text returned by sconf.Result(). + + - Document that filenames with '.' as the first character are + ignored by Glob() by default (matching UNIX glob semantics). + + - Fix SWIG testing infrastructure to work on Mac OS X. + + - Restructure a test that occasionally hung so that the test would + detect when it was stuck and fail instead. + + - Substfile builder. + + From Gary Oberbrunner: + + - When reporting a target that SCons doesn't know how to make, + specify whether it's a File, Dir, etc. + + From Ben Webb: + + - Fix use of $SWIGOUTDIR when generating Python wrappers. + + - Add $SWIGDIRECTORSUFFIX and $SWIGVERSION construction variables. + + From Rob Managan: + + - Add -recorder flag to Latex commands and updated internals to + use the output to find files TeX creates. This allows the MiKTeX + installations to find the created files + + - Notify user of Latex errors that would get buried in the + Latex output + + - Remove LATEXSUFFIXES from environments that don't initialize Tex. + + - Add support for the glosaaries package for glossaries and acronyms + + - Fix problem that pdftex, latex, and pdflatex tools by themselves did + not create the actions for bibtex, makeindex,... by creating them + and other environment settings in one routine called by all four + tex tools. + + - Fix problem with filenames of sideeffects when the user changes + the name of the output file from the latex default + + - Add scanning of files included in Latex by means of \lstinputlisting{} + Patch from Stefan Hepp. + + - Change command line for epstopdf to use --outfile= instead of -o + since this works on all platforms. + Patch from Stefan Hepp. + +RELEASE 1.2.0.d20090223 - Mon, 23 Feb 2009 08:41:06 -0800 + + From Stanislav Baranov: + + - Make suffix-matching for scanners case-insensitive on Windows. + + From David Cournapeau: + + - Change the way SCons finds versions of Visual C/C++ and Visual + Studio to find and use the Microsoft v*vars.bat files. + + From Robert P. J. Day: + + - User's Guide updates. + + From Dan Eaton: + + - Fix generation of Visual Studio 8 project files on x64 platforms. + + From Allan Erskine: + + - Set IncludeSearchPath and PreprocessorDefinitions in generated + Visual Studio 8 project files, to help IntelliSense work. + + From Mateusz Gruca: + + - Fix deletion of broken symlinks by the --clean option. + + From Steven Knight: + + - Fix the error message when use of a non-existent drive on Windows + is detected. + + - Add sources for files whose targets don't exist in $CHANGED_SOURCES. + + - Detect implicit dependencies on commands even when the command is + quoted. + + - Fix interaction of $CHANGED_SOURCES with the --config=force option. + + - Fix finding #include files when the string contains escaped + backslashes like "C:\\some\\include.h". + + - Pass $CCFLAGS to Visual C/C++ precompiled header compilation. + + - Remove unnecessary nested $( $) around $_LIBDIRFLAGS on link lines + for the Microsoft linker, the OS/2 ilink linker and the Phar Lap + linkloc linker. + + - Spell the Windows environment variables consistently "SystemDrive" + and "SystemRoot" instead of "SYSTEMDRIVE" and "SYSTEMROOT". + + + +RELEASE 1.2.0.d20090113 - Tue, 13 Jan 2009 02:50:30 -0800 + + From Stanislav Baranov, Ted Johnson and Steven Knight: + + - Add support for batch compilation of Visual Studio C/C++ source + files, controlled by a new $MSVC_BATCH construction variable. + + From Steven Knight: + + - Print the message, "scons: Build interrupted." on error output, + not standard output. + + - Add a --warn=future-deprecated option for advance warnings about + deprecated features that still have warnings hidden by default. + + - Fix use of $SOURCE and $SOURCES attributes when there are no + sources specified in the Builder call. + + - Add support for new $CHANGED_SOURCES, $CHANGED_TARGETS, + $UNCHANGED_SOURCES and $UNCHANGED_TARGETS variables. + + - Add general support for batch builds through new batch_key= and + targets= keywords to Action object creation. + + From Arve Knudsen: + + - Make linker tools differentiate properly between SharedLibrary + and LoadableModule. + + - Document TestCommon.shobj_prefix variable. + + - Support $SWIGOUTDIR values with spaces. + + From Rob Managan: + + - Don't automatically try to build .pdf graphics files for + .eps files in \includegraphics{} calls in TeX/LaTeX files + when building with the PDF builder (and thus using pdflatex). + + From Gary Oberbrunner: + + - Allow AppendENVPath() and PrependENVPath() to interpret '#' + for paths relative to the top-level SConstruct directory. + + - Use the Borland ilink -e option to specify the output file name. + + - Document that the msvc Tool module uses $PCH, $PCHSTOP and $PDB. + + - Allow WINDOWS_INSERT_DEF=0 to disable --output-def when linking + under MinGW. + + From Zia Sobhani: + + - Fix typos in the User's Guide. + + From Greg Spencer: + + - Support implicit dependency scanning of files encoded in utf-8 + and utf-16. + + From Roberto de Vecchi: + + - Remove $CCFLAGS from the the default definitions of $CXXFLAGS for + Visual C/C++ and MIPSpro C++ on SGI so, they match other tools + and avoid flag duplication on C++ command lines. + + From Ben Webb: + + - Handle quoted module names in SWIG source files. + + - Emit *_wrap.h when SWIG generates header file for directors + + From Matthew Wesley: + + - Copy file attributes so we identify, and can link a shared library + from, shared object files in a Repository. + + + +RELEASE 1.2.0 - Sat, 20 Dec 2008 22:47:29 -0800 + + From Steven Knight: + + - Don't fail if can't import a _subprocess module on Windows. + + - Add warnings for use of the deprecated Options object. + + + +RELEASE 1.1.0.d20081207 - Sun, 07 Dec 2008 19:17:23 -0800 + + From Benoit Belley: + + - Improve the robustness of GetBuildFailures() by refactoring + SCons exception handling (especially BuildError exceptions). + + - Have the --taskmastertrace= option print information about + individual Task methods, not just the Taskmaster control flow. + + - Eliminate some spurious dependency cycles by being more aggressive + about pruning pending children from the Taskmaster walk. + + - Suppress mistaken reports of a dependency cycle when a child + left on the pending list is a single Node in EXECUTED state. + + From David Cournapeau: + + - Fix $FORTRANMODDIRPREFIX for the ifort (Intel Fortran) tool. + + From Brad Fitzpatrick: + + - Don't pre-generate an exception message (which will likely be + ignored anyway) when an EntryProxy re-raises an AttributeError. + + From Jared Grubb: + + - Clean up coding style and white space in Node/FS.py. + + - Fix a typo in the documentation for $_CPPDEFFLAGS. + + - Issue 2401: Fix usage of comparisons with None. + + From Ludwig Hähne: + + - Handle Java inner classes declared within a method. + + From Steven Knight: + + - Fix label placement by the "scons-time.py func" subcommand + when a profile value was close to (or equal to) 0.0. + + - Fix env.Append() and env.Prepend()'s ability to add a string to + list-like variables like $CCFLAGS under Python 2.6. + + - Other Python2.6 portability: don't use "as" (a Python 2.6 keyword). + Don't use the deprecated Exception.message attribute. + + - Support using the -f option to search for a different top-level + file name when walking up with the -D, -U or -u options. + + - Fix use of VariantDir when the -n option is used and doesn't, + therefore, actually create the variant directory. + + - Fix a stack trace from the --debug=includes option when passed a + static or shared library as an argument. + + - Speed up the internal find_file() function (used for searching + CPPPATH, LIBPATH, etc.). + + - Add support for using the Python "in" keyword on construction + environments (for example, if "CPPPATH" in env: ...). + + - Fix use of Glob() when a repository or source directory contains + an in-memory Node without a corresponding on-disk file or directory. + + - Add a warning about future reservation of $CHANGED_SOURCES, + $CHANGED_TARGETS, $UNCHANGED_SOURCES and $UNCHANGED_TARGETS. + + - Enable by default the existing warnings about setting the resource + $SOURCE, $SOURCES, $TARGET and $TARGETS variable. + + From Rob Managan: + + - Scan for TeX files in the paths specified in the $TEXINPUTS + construction variable and the $TEXINPUTS environment variable. + + - Configure the PDF() and PostScript() Builders as single_source so + they know each source file generates a separate target file. + + - Add $EPSTOPDF, $EPSTOPDFFLAGS and $EPSTOPDFCOM + + - Add .tex as a valid extension for the PDF() builder. + + - Add regular expressions to find \input, \include and + \includegraphics. + + - Support generating a .pdf file from a .eps source. + + - Recursive scan included input TeX files. + + - Handle requiring searched-for TeX input graphics files to have + extensions (to avoid trying to build a .eps from itself, e.g.). + + From Greg Noel: + + - Make the Action() function handle positional parameters consistently. + + - Clarify use of Configure.CheckType(). + + - Make the File.{Dir,Entry,File}() methods create their entries + relative to the calling File's directory, not the SConscript + directory. + + - Use the Python os.devnull variable to discard error output when + looking for the $CC or $CXX version. + + - Mention LoadableModule() in the SharedLibrary() documentation. + + From Gary Oberbrunner: + + - Update the User's Guide to clarify use of the site_scons/ + directory and the site_init.py module. + + - Make env.AppendUnique() and env.PrependUnique remove duplicates + within a passed-in list being added, too. + + From Randall Spangler: + + - Fix Glob() so an on-disk file or directory beginning with '#' + doesn't throw an exception. + + + +RELEASE 1.1.0 - Thu, 09 Oct 2008 08:33:47 -0700 + + From Chris AtLee + + - Use the specified environment when checking for the GCC compiler + version. + + From Ian P. Cardenas: + + - Fix Glob() polluting LIBPATH by returning copy of list + + From David Cournapeau: + + - Add CheckCC, CheckCXX, CheckSHCC and CheckSHCXX tests to + configuration contexts. + + - Have the --profile= argument use the much faster cProfile module + (if it's available in the running Python version). + + - Reorder MSVC compilation arguments so the /Fo is first. + + From Bill Deegan: + + - Add scanning Windows resource (.rc) files for implicit dependencies. + + From John Gozde: + + - When scanning for a #include file, don't use a directory that + has the same name as the file. + + From Ralf W. Grosse-Kunstleve + + - Suppress error output when checking for the GCC compiler version. + + From Jared Grubb: + + - Fix VariantDir duplication of #included files in subdirectories. + + From Ludwig Hähne: + + - Reduce memory usage when a directory is used as a dependency of + another Node (such as an Alias) by returning a concatenation + of the children's signatures + names, not the children's contents, + as the directory contents. + + - Raise AttributeError, not KeyError, when a Builder can't be found. + + - Invalidate cached Node information (such as the contenst returned + by the get_contents() method) when calling actions with Execute(). + + - Avoid object reference cycles from frame objects. + + - Reduce memory usage from Null Executor objects. + + - Compute MD5 checksums of large files without reading the entire + file contents into memory. Add a new --md5-chunksize option to + control the size of each chunk read into memory. + + From Steven Knight: + + - Fix the ability of the add_src_builder() method to add a new + source builder to any other builder. + + - Avoid an infinite loop on non-Windows systems trying to find the + SCons library directory if the Python library directory does not + begin with the string "python". + + - Search for the SCons library directory in "scons-local" (with + no version number) after "scons-local-{VERSION}". + + From Rob Managan: + + - Fix the user's ability to interrupt the TeX build chain. + + - Fix the TeX builder's allowing the user to specify the target name, + instead of always using its default output name based on the source. + + - Iterate building TeX output files until all warning are gone + and the auxiliary files stop changing, or until we reach the + (configurable) maximum number of retries. + + - Add TeX scanner support for: glossaries, nomenclatures, lists of + figures, lists of tables, hyperref and beamer. + + - Use the $BIBINPUTS, $BSTINPUTS, $TEXINPUTS and $TEXPICTS construction + variables as search paths for the relevant types of input file. + + - Fix building TeX with VariantDir(duplicate=0) in effect. + + - Fix the LaTeX scanner to search for graphics on the TEXINPUTS path. + + - Have the PDFLaTeX scanner search for .gif files as well. + + From Greg Noel: + + - Fix typos and format bugs in the man page. + + - Add a first draft of a wrapper module for Python's subprocess + module. + + - Refactor use of the SCons.compat module so other modules don't + have to import it individually. + + - Add .sx as a suffix for assembly language files that use the + C preprocessor. + + From Gary Oberbrunner: + + - Make Glob() sort the returned list of Files or Nodes + to prevent spurious rebuilds. + + - Add a delete_existing keyword argument to the AppendENVPath() + and PrependENVPath() Environment methods. + + - Add ability to use "$SOURCE" when specifying a target to a builder + + From Damyan Pepper: + + - Add a test case to verify that SConsignFile() files can be + created in previously non-existent subdirectories. + + From Jim Randall: + + - Make the subdirectory in which the SConsignFile() file will + live, if the subdirectory doesn't already exist. + + From Ali Tofigh: + + - Add a test to verify duplication of files in VariantDir subdirectories. + + + +RELEASE 1.0.1 - Sat, 06 Sep 2008 07:29:34 -0700 + + From Greg Noel: + + - Add a FindFile() section to the User's Guide. + + - Fix the FindFile() documentation in the man page. + + - Fix formatting errors in the Package() description in the man page. + + - Escape parentheses that appear within variable names when spawning + command lines using os.system(). + + + +RELEASE 1.0.0.d20080826 - Tue, 26 Aug 2008 09:12:40 -0700 + + From Gary Oberbrunner: + + - Make Glob() sort the returned list of Files or Nodes + to prevent spurious rebuilds. + + From Ian P. Cardenas: + + - Fix Glob() polluting LIBPATH by returning copy of list + + + +RELEASE 1.0.0 - XXX + + From Jared Grubb: + + - Clear the Node state when turning a generic Entry into a Dir. + + From Ludwig Hähne: + + - Fix sporadic output-order failures in test/GetBuildFailures/parallel.py. + + - Document the ParseDepends() function in the User's Guide. + + From khomenko: + + - Create a separate description and long_description for RPM packages. + + From Steven Knight: + + - Document the GetLaunchDir() function in the User's Guide. + + - Have the env.Execute() method print an error message if the + executed command fails. + + - Add a script for creating a standard SCons development system on + Ubuntu Hardy. Rewrite subsidiary scripts for install Python and + SCons versions in Python (from shell). + + From Greg Noel: + + - Handle yacc/bison on newer Mac OS X versions creating file.hpp, + not file.cpp.h. + + - In RPCGEN tests, ignore stderr messages from older versions of + rpcgen on some versions of Mac OS X. + + - Fix typos in man page descriptions of Tag() and Package(), and in + the scons-time man page. + + - Fix documentation of SConf.CheckLibWithHeader and other SConf methods. + + - Update documentation of SConscript(variant_dir) usage. + + - Fix SWIG tests for (some versions of) Mac OS X. + + From Jonas Olsson: + + - Print the warning about -j on Windows being potentially unreliable if + the pywin32 extensions are unavailable or lack file handle operations. + + From Jim Randall: + + - Fix the env.WhereIs() method to expand construction variables. + + From Rogier Schouten: + + - Enable building of shared libraries with the Bordand ilink32 linker. + + + +RELEASE 1.0.0 - Sat, 09 Aug 2008 12:19:44 -0700 + + From Luca Falavigna: + + - Fix SCons man page indentation under Debian's man page macros. + + From Steven Knight: + + - Clarify the man page description of the SConscript(src_dir) argument. + + - User's Guide updates: + + - Document the BUILD_TARGETS, COMMAND_LINE_TARGETS and + DEFAULT_TARGETS variables. + + - Document the AddOption(), GetOption() and SetOption() functions. + + - Document the Requires() function; convert to the Variables + object, its UnknownOptions() method, and its associated + BoolVariable(), EnumVariable(), ListVariable(), PackageVariable() + and PathVariable() functions. + + - Document the Progress() function. + + - Reorganize the chapter and sections describing the different + types of environments and how they interact. Document the + SetDefault() method. Document the PrependENVPath() and + AppendENVPath() functions. + + - Reorganize the command-line arguments chapter. Document the + ARGLIST variable. + + - Collect some miscellaneous sections into a chapter about + configuring build output. + + - Man page updates: + + - Document suggested use of the Visual C/C++ /FC option to fix + the ability to double-click on file names in compilation error + messages. + + - Document the need to use Clean() for any SideEffect() files that + must be explicitly removed when their targets are removed. + + - Explicitly document use of Node lists as input to Dependency(). + + From Greg Noel: + + - Document MergeFlags(), ParseConfig(), ParseFlags() and SideEffect() + in the User's Guide. + + From Gary Oberbrunner: + + - Document use of the GetBuildFailures() function in the User's Guide. + + From Adam Simpkins: + + - Add man page text clarifying the behavior of AddPreAction() and + AddPostAction() when called with multiple targets. + + From Alexey Zezukin: + + - Fix incorrectly swapped man page descriptions of the --warn= options + for duplicate-environment and missing-sconscript. + + + +RELEASE 0.98.5 - Sat, 07 Jun 2008 08:20:35 -0700 + + From Benoit Belley: + + - Fix the Intel C++ compiler ABI specification for EMT64 processors. + + From David Cournapeau: + + - Issue a (suppressable) warning, not an error, when trying to link + C++ and Fortran object files into the same executable. + + From Steven Knight: + + - Update the scons.bat file so that it returns the real exit status + from SCons, even though it uses setlocal + endlocal. + + - Fix the --interactive post-build messages so it doesn't get stuck + mistakenly reporting failures after any individual build fails. + + - Fix calling File() as a File object method in some circumstances. + + - Fix setup.py installation on Mac OS X so SCons gets installed + under /usr/lcoal by default, not in the Mac OS X Python framework. + + + +RELEASE 0.98.4 - Sat, 17 May 2008 22:14:46 -0700 + + From Benoit Belley: + + - Fix calculation of signatures for Python function actions with + closures in Python versions before 2.5. + + From David Cournapeau: + + - Fix the initialization of $SHF77FLAGS so it includes $F77FLAGS. + + From Jonas Olsson: + + - Fix a syntax error in the Intel C compiler support on Windows. + + From Steven Knight: + + - Change how we represent Python Value Nodes when printing and when + stored in .sconsign files (to avoid blowing out memory by storing + huge strings in .sconsign files after multiple runs using Configure + contexts cause the Value strings to be re-escaped each time). + + - Fix a regression in not executing configuration checks after failure + of any configuration check that used the same compiler or other tool. + + - Handle multiple destinations in Visual Studio 8 settings for the + analogues to the INCLUDE, LIBRARY and PATH variables. + + From Greg Noel: + + - Update man page text for VariantDir(). + + + +RELEASE 0.98.3 - Tue, 29 Apr 2008 22:40:12 -0700 + + From Greg Noel: + + - Fix use of $CXXFLAGS when building C++ shared object files. + + From Steven Knight: + + - Fix a regression when a Builder's source_scanner doesn't select + a more specific scanner for the suffix of a specified source file. + + - Fix the Options object backwards compatibility so people can still + "import SCons.Options.{Bool,Enum,List,Package,Path}Option" submodules. + + - Fix searching for implicit dependencies when an Entry Node shows up + in the search path list. + + From Stefano: + + - Fix expansion of $FORTRANMODDIR in the default Fortran command line(s) + when it's set to something like ${TARGET.dir}. + + + +RELEASE 0.98.2 - Sun, 20 Apr 2008 23:38:56 -0700 + + From Steven Knight: + + - Fix a bug in Fortran suffix computation that would cause SCons to + run out of memory on Windows systems. + + - Fix being able to specify --interactive mode command lines with + \ (backslash) path name separators on Windows. + + From Gary Oberbrunner: + + - Document Glob() in the User's Guide. + + + +RELEASE 0.98.1 - Fri, 18 Apr 2008 19:11:58 -0700 + + From Benoit Belley: + + - Speed up the SCons.Util.to_string*() functions. + + - Optimize various Node intialization and calculations. + + - Optimize Executor scanning code. + + - Optimize Taskmaster execution, including dependency-cycle checking. + + - Fix the --debug=stree option so it prints its tree once, not twice. + + From Johan BoulÃ: + + - Fix the ability to use LoadableModule() under MinGW. + + From David Cournapeau: + + - Various missing Fortran-related construction variables have been added. + + - SCons now uses the program specified in the $FORTRAN construction + variable to link Fortran object files. + + - Fortran compilers on Linux (Intel, g77 and gfortran) now add the -fPIC + option by default when compilling shared objects. + + - New 'sunf77', 'sunf90' and 'sunf95' Tool modules have been added to + support Sun Fortran compilers. On Solaris, the Sun Fortran compilers + are used in preference to other compilers by default. + + - Fortran support now uses gfortran in preference to g77. + + - Fortran file suffixes are now configurable through the + $F77FILESUFFIXES, $F90FILESUFFIXES, $F95FILESUFFIXES and + $FORTRANFILESUFFIXES variables. + + From Steven Knight: + + - Make the -d, -e, -w and --no-print-directory options "Ignored for + compatibility." (We're not going to implement them.) + + - Fix a serious inefficiency in how SCons checks for whether any source + files are missing when a Builder call creates many targets from many + input source files. + + - In Java projects, make the target .class files depend only on the + specific source .java files where the individual classes are defined. + + - Don't store duplicate source file entries in the .sconsign file so + we don't endlessly rebuild the target(s) for no reason. + + - Add a Variables object as the first step towards deprecating the + Options object name. Similarly, add BoolVariable(), EnumVariable(), + ListVariable(), PackageVariable() and PathVariable() functions + as first steps towards replacing BoolOption(), EnumOption(), + ListOption(), PackageOption() and PathOption(). + + - Change the options= keyword argument to the Environment() function + to variables=, to avoid confusion with SCons command-line options. + Continue supporting the options= keyword for backwards compatibility. + + - When $SWIGFLAGS contains the -python flag, expect the generated .py + file to be in the same (sub)directory as the target. + + - When compiling C++ files, allow $CCFLAGS settings to show up on the + command line even when $CXXFLAGS has been redefined. + + - Fix --interactive with -u/-U/-D when a VariantDir() is used. + + From Anatoly Techtonik: + + - Have the scons.bat file add the script execution directory to its + local %PATH% on Windows, so the Python executable can be found. + + From Mike Wake: + + - Fix passing variable names as a list to the Return() function. + + From Matthew Wesley: + + - Add support for the GDC 'D' language compiler. + + + +RELEASE 0.98 - Sun, 30 Mar 2008 23:33:05 -0700 + + From Benoit Belley: + + - Fix the --keep-going flag so it builds all possible targets even when + a later top-level target depends on a child that failed its build. + + - Fix being able to use $PDB and $WINDWOWS_INSERT_MANIFEST together. + + - Don't crash if un-installing the Intel C compiler leaves left-over, + dangling entries in the Windows registry. + + - Improve support for non-standard library prefixes and suffixes by + stripping all prefixes/suffixes from file name string as appropriate. + + - Reduce the default stack size for -j worker threads to 256 Kbytes. + Provide user control over this value by adding --stack-size and + --warn=stack-size options, and a SetOption('stack_size') function. + + - Fix a crash on Linux systems when trying to use the Intel C compiler + and no /opt/intel_cc_* directories are found. + + - Improve using Python functions as actions by incorporating into + a FunctionAction's signature: + - literal values referenced by the byte code. + - values of default arguments + - code of nested functions + - values of variables captured by closures + - names of referenced global variables and functions + + - Fix the closing message when --clean and --keep-going are both + used and no errors occur. + + - Add support for the Intel C compiler on Mac OS X. + + - Speed up reading SConscript files by about 20% (for some + configurations) by: 1) optimizing the SCons.Util.is_*() and + SCons.Util.flatten() functions; 2) avoiding unnecessary os.stat() + calls by using a File's .suffix attribute directly instead of + stringifying it. + + From Jérôme Berger: + + - Have the D language scanner search for .di files as well as .d files. + + - Add a find_include_names() method to the Scanner.Classic class to + abstract out how included names can be generated by subclasses. + + - Allow the D language scanner to detect multiple modules imported by + a single statement. + + From Konstantin Bozhikov: + + - Support expansion of construction variables that contain or refer + to lists of other variables or Nodes within expansions like $CPPPATH. + + - Change variable substitution (the env.subst() method) so that an + input sequence (list or tuple) is preserved as a list in the output. + + From David Cournapeau: + + - Add a CheckDeclaration() call to configure contexts. + + - Improve the CheckTypeSize() code. + + - Add a Define() call to configure contexts, to add arbitrary #define + lines to a generated configure header file. + + - Add a "gfortran" Tool module for the GNU F95/F2003 compiler. + + - Avoid use of -rpath with the Mac OS X linker. + + - Add comment lines to the generated config.h file to describe what + the various #define/#undef lines are doing. + + From Steven Knight: + + - Support the ability to subclass the new-style "str" class as input + to Builders. + + - Improve the performance of our type-checking by using isinstance() + with new-style classes. + + - Fix #include (and other $*PATH variables searches) of files with + absolute path names. Don't die if they don't exist (due to being + #ifdef'ed out or the like). + + - Fix --interactive mode when Default(None) is used. + + - Fix --debug=memoizer to work around a bug in base Python 2.2 metaclass + initialization (by just not allowing Memoization in Python versions + that have the bug). + + - Have the "scons-time time" subcommand handle empty log files, and + log files that contain no results specified by the --which option. + + - Fix the max Y of vertical bars drawn by "scons-time --fmt=gnuplot". + + - On Mac OS X, account for the fact that the header file generated + from a C++ file will be named (e.g.) file.cpp.h, not file.hpp. + + - Fix floating-point numbers confusing the Java parser about + generated .class file names in some configurations. + + - Document (nearly) all the values you can now fetch with GetOption(). + + - Fix use of file names containing strings of multiple spaces when + using ActionFactory instances like the Copy() or Move() function. + + - Fix a 0.97 regression when using a variable expansion (like + $OBJSUFFIX) in a source file name to a builder with attached source + builders that match suffix (like Program()+Object()). + + - Have the Java parser recognize generics (surrounded by angle brackets) + so they don't interfere with identifying anonymous inner classes. + + - Avoid an infinite loop when trying to use saved copies of the + env.Install() or env.InstallAs() after replacing the method + attributes. + + - Improve the performance of setting construction variables. + + - When cloning a construction environment, avoid over-writing an + attribute for an added method if the user explicitly replaced it. + + - Add a warning about deprecated support for Python 1.5, 2.0 and 2.1. + + - Fix being able to SetOption('warn', ...) in SConscript files. + + - Add a warning about env.Copy() being deprecated. + + - Add warnings about the --debug={dtree,stree,tree} options + being deprecated. + + - Add VariantDir() as the first step towards deprecating BuildDir(). + Add the keyword argument "variant_dir" as the replacement for + "build_dir". + + - Add warnings about the {Target,Source}Signatures() methods and + functions being deprecated. + + From Rob Managan: + + - Enhance TeX and LaTeX support to work with BuildDir(duplicate=0). + + - Re-run LaTeX when it issues a package warning that it must be re-run. + + From Leanid Nazdrynau: + + - Have the Copy() action factory preserve file modes and times + when copying individual files. + + From Jan Nijtmans: + + - If $JARCHDIR isn't set explicitly, use the .java_classdir attribute + that was set when the Java() Builder built the .class files. + + From Greg Noel: + + - Document the Dir(), File() and Entry() methods of Dir and File Nodes. + + - Add the parse_flags option when creating Environments + + From Gary Oberbrunner: + + - Make File(), Dir() and Entry() return a list of Nodes when passed + a list of names, instead of trying to make a string from the name + list and making a Node from that string. + + - Fix the ability to build an Alias in --interactive mode. + + - Fix the ability to hash the contents of actions for nested Python + functions on Python versions where the inability to pickle them + returns a TypeError (instead of the documented PicklingError). + + From Jonas Olsson: + + - Fix use of the Intel C compiler when the top compiler directory, + but not the compiler version, is specified. + + - Handle Intel C compiler network license files (port@system). + + From Jim Randall: + + - Fix how Python Value Nodes are printed in --debug=explain output. + + From Adam Simpkins: + + - Add a --interactive option that starts a session for building (or + cleaning) targets without re-reading the SConscript files every time. + + - Fix use of readline command-line editing in --interactive mode. + + - Have the --interactive mode "build" command with no arguments + build the specified Default() targets. + + - Fix the Chmod(), Delete(), Mkdir() and Touch() Action factories to + take a list (of Nodes or strings) as arguments. + + From Vaclav Smilauer: + + - Fix saving and restoring an Options value of 'all' on Python + versions where all() is a builtin function. + + From Daniel Svensson: + + - Code correction in SCons.Util.is_List(). + + From Ben Webb: + + - Support the SWIG %module statement with following modifiers in + parenthese (e.g., '%module(directors="1")'). + + + +RELEASE 0.97.0d20071212 - Wed, 12 Dec 2007 09:29:32 -0600 + + From Benoit Belley: + + - Fix occasional spurious rebuilds and inefficiency when using + --implicit-cache and Builders that produce multiple targets. + + - Allow SCons to not have to know about the builders of generated + files when BuildDir(duplicate=0) is used, potentially allowing some + SConscript files to be ignored for smaller builds. + + From David Cournapeau: + + - Add a CheckTypeSize() call to configure contexts. + + From Ken Deeter: + + - Make the "contents" of Alias Nodes a concatenation of the children's + content signatures (MD5 checksums), not a concatenation of the + children's contents, to avoid using large amounts of memory during + signature calculation. + + From Malte Helmert: + + - Fix a lot of typos in the man page and User's Guide. + + From Geoffrey Irving: + + - Speed up conversion of paths in .sconsign files to File or Dir Nodes. + + From Steven Knight: + + - Add an Options.UnknownOptions() method that returns any settings + (from the command line, or whatever dictionary was passed in) + that aren't known to the Options object. + + - Add a Glob() function. + + - When removing targets with the -c option, use the absolute path (to + avoid problems interpreting BuildDir() when the top-level directory + is the source directory). + + - Fix problems with Install() and InstallAs() when called through a + clone (of a clone, ...) of a cloned construction environment. + + - When executing a file containing Options() settings, add the file's + directory to sys.path (so modules can be imported from there) and + explicity set __name__ to the name of the file so the statement's + in the file can deduce the location if they need to. + + - Fix an O(n^2) performance problem when adding sources to a target + through calls to a multi Builder (including Aliases). + + - Redefine the $WINDOWSPROGMANIFESTSUFFIX and + $WINDOWSSHLIBMANIFESTSUFFIX variables so they pick up changes to + the underlying $SHLIBSUFFIX and $PROGSUFFIX variables. + + - Add a GetBuildFailures() function that can be called from functions + registered with the Python atexit module to print summary information + about any failures encountered while building. + + - Return a NodeList object, not a Python list, when a single_source + Builder like Object() is called with more than one file. + + - When searching for implicit dependency files in the directories + in a $*PATH list, don't create Dir Nodes for directories that + don't actually exist on-disk. + + - Add a Requires() function to allow the specification of order-only + prerequisites, which will be updated before specified "downstream" + targets but which don't actually cause the target to be rebuilt. + + - Restore the FS.{Dir,File,Entry}.rel_path() method. + + - Make the default behavior of {Source,Target}Signatures('timestamp') + be equivalent to 'timestamp-match', not 'timestamp-newer'. + + - Fix use of CacheDir with Decider('timestamp-newer') by updating + the modification time when copying files from the cache. + + - Fix random issues with parallel (-j) builds on Windows when Python + holds open file handles (especially for SCons temporary files, + or targets built by Python function actions) across process creation. + + From Maxim Kartashev: + + - Fix test scripts when run on Solaris. + + From Gary Oberbrunner: + + - Fix Glob() when a pattern is in an explicitly-named subdirectory. + + From Philipp Scholl: + + - Fix setting up targets if multiple Package builders are specified + at once. + + + +RELEASE 0.97.0d20070918 - Tue, 18 Sep 2007 10:51:27 -0500 + + From Steven Knight: + + - Fix the wix Tool module to handle null entries in $PATH variables. + + - Move the documentation of Install() and InstallAs() from the list + of functions to the list of Builders (now that they're implemented + as such). + + - Allow env.CacheDir() to be set per construction environment. The + global CacheDir() function now sets an overridable global default. + + - Add an env.Decider() method and a Node.Decider() method that allow + flexible specification of an arbitrary function to decide if a given + dependency has changed since the last time a target was built. + + - Don't execute Configure actions (while reading SConscript files) + when cleaning (-c) or getting help (-h or -H). + + - Add to each target an implicit dependency on the external command(s) + used to build the target, as found by searching env['ENV']['PATH'] + for the first argument on each executed command line. + + - Add support for a $IMPLICIT_COMMAND_DEPENDENCIES construction + variabe that can be used to disable the automatic implicit + dependency on executed commands. + + - Add an "ensure_suffix" keyword to Builder() definitions that, when + true, will add the configured suffix to the targets even if it looks + like they already have a different suffix. + + - Add a Progress() function that allows for calling a function or string + (or list of strings) to display progress while walking the DAG. + + - Allow ParseConfig(), MergeFlags() and ParseFlags() to handle output + from a *config command with quoted path names that contain spaces. + + - Make the Return() function stop processing the SConscript file and + return immediately. Add a "stop=" keyword argument that can be set + to False to preserve the old behavior. + + - Fix use of exitstatfunc on an Action. + + - Introduce all man page function examples with "Example:" or "Examples:". + + - When a file gets added to a directory, make sure the directory gets + re-scanned for the new implicit dependency. + + - Fix handling a file that's specified multiple times in a target + list so that it doesn't cause dependent Nodes to "disappear" from + the dependency graph walk. + + From Carsten Koch: + + - Avoid race conditions with same-named files and directory creation + when pushing copies of files to CacheDir(). + + From Tzvetan Mikov: + + - Handle $ in Java class names. + + From Gary Oberbrunner: + + - Add support for the Intel C compiler on Windows64. + + - On SGI IRIX, have $SHCXX use $CXX by default (like other platforms). + + From Sohail Somani: + + - When Cloning a construction environment, set any variables before + applying tools (so the tool module can access the configured settings) + and re-set them after (so they end up matching what the user set). + + From Matthias Troffaes: + + - Make sure extra auxiliary files generated by some LaTeX packages + and not ending in .aux also get deleted by scons -c. + + From Greg Ward: + + - Add a $JAVABOOTCLASSPATH variable for directories to be passed to the + javac -bootclasspath option. + + From Christoph Wiedemann: + + - Add implicit dependencies on the commands used to build a target. + + + + +RELEASE 0.97.0d20070809 - Fri, 10 Aug 2007 10:51:27 -0500 + + From Lars Albertsson: + + - Don't error if a #include line happens to match a directory + somewhere on a path (like $CPPPATH, $FORTRANPATH, etc.). + + From Mark Bertoglio: + + - Fix listing multiple projects in Visual Studio 7.[01] solution files, + including generating individual project GUIDs instead of re-using + the solution GUID. + + From Jean Brouwers: + + - Add /opt/SUNWspro/bin to the default execution PATH on Solaris. + + From Allan Erskine: + + - Only expect the Microsoft IDL compiler to emit *_p.c and *_data.c + files if the /proxy and /dlldata switches are used (respectively). + + From Steven Knight: + + - Have --debug=explain report if a target is being rebuilt because + AlwaysBuild() is specified (instead of "unknown reasons"). + + - Support {Get,Set}Option('help') to make it easier for SConscript + files to tell if a help option (-h, --help, etc.) has been specified. + + - Support {Get,Set}Option('random') so random-dependency interaction + with CacheDir() is controllable from SConscript files. + + - Add a new AddOption() function to support user-defined command- + line flags (like --prefix=, --force, etc.). + + - Push and retrieve built symlinks to/from a CacheDir() as actual + symlinks, not by copying the file contents. + + - Fix how the Action module handles stringifying the shared library + generator in the Tool/mingw.py module. + + - When generating a config.h file, print "#define HAVE_{FEATURE} 1" + instad of just "#define HAVE_{FEATURE}", for more compatibility + with Autoconf-style projects. + + - Fix expansion of $TARGET, $TARGETS, $SOURCE and $SOURCES keywords in + Visual C/C++ PDB file names. + + - Fix locating Visual C/C++ PDB files in build directories. + + - Support an env.AddMethod() method and an AddMethod() global function + for adding a new method, respectively, to a construction environment + or an arbitrary object (such as a class). + + - Fix the --debug=time option when the -j option is specified and all + files are up to date. + + - Add a $SWIGOUTDIR variable to allow setting the swig -outdir option, + and use it to identify files created by the swig -java option. + + - Add a $SWIGPATH variable that specifies the path to be searched + for included SWIG files, Also add related $SWIGINCPREFIX and + $SWIGINCSUFFIX variables that specify the prefix and suffix to + be be added to each $SWIGPATH directory when expanded on the SWIG + command line. + + - More efficient copying of construction environments (mostly borrowed + from copy.deepcopy() in the standard Python library). + + - When printing --tree=prune output, don't print [brackets] around + source files, only do so for built targets with children. + + - Fix interpretation of Builder source arguments when the Builder has + a src_suffix *and* a source_builder and the argument has no suffix. + + - Fix use of expansions like ${TARGET.dir} or ${SOURCE.dir} in the + following construction variables: $FORTRANMODDIR, $JARCHDIR, + $JARFLAGS, $LEXFLAGS, $SWIGFLAGS, $SWIGOUTDIR and $YACCFLAGS. + + - Fix dependencies on Java files generated by SWIG so they can be + detected and built in one pass. + + - Fix SWIG when used with a BuildDir(). + + From Leanid Nazdrynau: + + - When applying Tool modules after a construction environment has + already been created, don't overwrite existing $CFILESUFFIX and + $CXXFILESUFFIX value. + + - Support passing the Java() builder a list of explicit .java files + (not only a list of directories to be scanned for .java files). + + - Support passing .java files to the Jar() and JavaH() builders, which + then use the builder underlying the Java() builder to turn them into + .class files. (That is, the Jar()-Java() chain of builders become + multi-step, like the Program()-Object()-CFile() builders.) + + - Support passing SWIG .i files to the Java builders (Java(), + Jar(), JavaH()), to cause intermediate .java files to be created + automatically. + + - Add $JAVACLASSPATH and $JAVASOURCEPATH variables, that get added to + the javac "-classpath" and "-sourcepath" options. (Note that SCons + does *not* currently search these paths for implicit dependencies.) + + - Commonize initialization of Java-related builders. + + From Jan Nijtmans: + + - Find Java anonymous classes when the next token after the name is + an open parenthesis. + + From Gary Oberbrunner: + + - Fix a code example in the man page. + + From Tilo Prutz: + + - Add support for the file names that Java 1.5 (and 1.6) generates for + nested anonymous inner classes, which are different from Java 1.4. + + From Adam Simpkins: + + - Allow worker threads to terminate gracefully when all jobs are + finished. + + From Sohail Somani: + + - Add LaTeX scanner support for finding dependencies specified with + the \usepackage{} directive. + + + +RELEASE 0.97 - Thu, 17 May 2007 08:59:41 -0500 + + From Steven Knight: + + - Fix a bug that would make parallel builds stop in their tracks if + Nodes that depended on lists that contained some Nodes built together + caused the reference count to drop below 0 if the Nodes were visited + and commands finished in the wrong order. + + - Make sure the DirEntryScanner doesn't choke if it's handed something + that's not a directory (Node.FS.Dir) Node. + + + +RELEASE 0.96.96 - Thu, 12 Apr 2007 12:36:25 -0500 + + NOTE: This is (Yet) a(nother) pre-release of 0.97 for testing purposes. + + From Joe Bloggs: + + - Man page fix: remove cut-and-paste sentence in NoCache() description. + + From Dmitry Grigorenko and Gary Oberbrunner: + + - Use the Intel C++ compiler, not $CC, to link C++ source. + + From Helmut Grohne: + + - Fix the man page example of propagating a user's external environment. + + From Steven Knight: + + - Back out (most of) the Windows registry installer patch, which + seems to not work on some versions of Windows. + + - Don't treat Java ".class" attributes as defining an inner class. + + - Fix detecting an erroneous Java anonymous class when the first + non-skipped token after a "new" keyword is a closing brace. + + - Fix a regression when a CPPDEFINES list contains a tuple, the second + item of which (the option value) is a construction variable expansion + (e.g. $VALUE) and the value of the variable isn't a string. + + - Improve the error message if an IOError (like trying to read a + directory as a file) occurs while deciding if a node is up-to-date. + + - Fix "maximum recursion" / "unhashable type" errors in $CPPPATH + PathList expansion if a subsidiary expansion yields a stringable, + non-Node object. + + - Generate API documentation from the docstrings (using epydoc). + + - Fix use of --debug=presub with Actions for out-of-the-box Builders. + + - Fix handling nested lists within $CPPPATH, $LIBPATH, etc. + + - Fix a "builders_used" AttributeError that real-world Qt initialization + triggered in the refactored suffix handling for Builders. + + - Make the reported --debug=time timings meaningful when used with -j. + Better documentation of what the times mean. + + - User Guide updates: --random, AlwaysBuild(), --tree=, + --debug=findlibs, --debug=presub, --debug=stacktrace, + --taskmastertrace. + + - Document (in both man page and User's Guide) that --implicit-cache + ignores changes in $CPPPATH, $LIBPATH, etc. + + From Jean-Baptiste Lab: + + - Remove hard-coded dependency on Python 2.2 from Debian packaging files. + + From Jeff Mahovsky: + + - Handle spaces in the build target name in Visual Studio project files. + + From Rob Managan: + + - Re-run LaTeX after BibTeX has been re-run in response to a changed + .bib file. + + From Joel B. Mohler: + + - Make additional TeX auxiliary files (.toc, .idx and .bbl files) + Precious so their removal doesn't affect whether the necessary + sections are included in output PDF or PostScript files. + + From Gary Oberbrunner: + + - Fix the ability to import modules in the site_scons directory from + a subdirectory. + + From Adam Simpkins: + + - Make sure parallel (-j) builds all targets even if they show up + multiple times in the child list (as a source and a dependency). + + From Matthias Troffaes: + + - Don't re-run TeX if the triggering strings (\makeindex, \bibliography + \tableofcontents) are commented out. + + From Richard Viney: + + - Fix use of custom include and lib paths with Visual Studio 8. + + - Select the default .NET Framework SDK Dir based on the version of + Visual Studio being used. + + + +RELEASE 0.96.95 - Mon, 12 Feb 2007 20:25:16 -0600 + + From Anatoly: + + - Add the scons.org URL and a package description to the setup.py + arguments. + + - Have the Windows installer add a registry entry for scons.bat in the + "App Paths" key, so scons.bat can be executed without adding the + directory to the %PATH%. (Python itself works this way.) + + From Anonymous: + + - Fix looking for default paths in Visual Studio 8.0 (and later). + + - Add -lm to the list of default D libraries for linking. + + From Matt Doar: + + - Provide a more complete write-your-own-Scanner example in the man page. + + From Ralf W. Grosse-Kunstleve: + + - Contributed upstream Python change to our copied subprocess.py module + for more efficient standard input processing. + + From Steven Knight: + + - Fix the Node.FS.Base.rel_path() method when the two nodes are on + different drive letters. (This caused an infinite loop when + trying to write .sconsign files.) + + - Fully support Scanners that use a dictionary to map file suffixes + to other scanners. + + - Support delayed evaluation of the $SPAWN variable to allow selection + of a function via ${} string expansions. + + - Add --srcdir as a synonym for -Y/--repository. + + - Document limitations of #include "file.h" with Repository(). + + - Fix use of a toolpath under the source directory of a BuildDir(). + + - Fix env.Install() with a file name portion that begins with '#'. + + - Fix ParseConfig()'s handling of multiple options in a string that's + replaced a *FLAGS construction variable. + + - Have the C++ tools initialize common C compilation variables ($CCFLAGS, + $SHCCFLAGS and $_CCCOMCOM) even if the 'cc' Tool isn't loaded. + + From Leanid Nazdrynau: + + - Fix detection of Java anonymous classes if a newline precedes the + opening brace. + + From Gary Oberbrunner: + + - Document use of ${} to execute arbitrary Python code. + + - Add support for: + 1) automatically adding a site_scons subdirectory (in the top-level + SConstruct directory) to sys.path (PYTHONPATH); + 2) automatically importing site_scons/site_init.py; + 3) automatically adding site_scons/site_tools to the toolpath. + + From John Pye: + + - Change ParseConfig() to preserve white space in arguments passed in + as a list. + + From a smith: + + - Fix adding explicitly-named Java inner class files (and any + other file names that may contain a '$') to Jar files. + + From David Vitek: + + - Add a NoCache() function to mark targets as unsuitable for propagating + to (or retrieving from) a CacheDir(). + + From Ben Webb: + + - If the swig -noproxy option is used, it won't generate a .py file, + so don't emit it as a target that we expect to be built. + + + +RELEASE 0.96.94 - Sun, 07 Jan 2007 18:36:20 -0600 + + NOTE: This is a pre-release of 0.97 for testing purposes. + + From Anonymous: + + - Allow arbitrary white space after a SWIG %module declaration. + + From Paul: + + - When compiling resources under MinGW, make sure there's a space + between the --include-dir option and its argument. + + From Jay Kint: + + - Alleviate long command line issues on Windows by executing command + lines directly via os.spawnv() if the command line doesn't need + shell interpretation (has no pipes, redirection, etc.). + + From Walter Franzini: + + - Exclude additional Debian packaging files from the copyright check. + + From Fawad Halim: + + - Handle the conflict between the impending Python 2.6 'as' keyword + and our Tool/as.py module name. + + From Steven Knight: + + - Speed up the Node.FS.Dir.rel_path() method used to generate path names + that get put into the .sconsign* file(s). + + - Optimize Node.FS.Base.get_suffix() by computing the suffix once, up + front, when we set the Node's name. (Duh...) + + - Reduce the Memoizer's responsibilities to simply counting hits and + misses when the --debug=memoizer option is used, not to actually + handling the key calculation and memoization itself. This speeds + up some configurations significantly, and should cause no functional + differences. + + - Add a new scons-time script with subcommands for generating + consistent timing output from SCons configurations, extracting + various information from those timings, and displaying them in + different formats. + + - Reduce some unnecessary stat() calls from on-disk entry type checks. + + - Fix SideEffect() when used with -j, which was badly broken in 0.96.93. + + - Propagate TypeError exceptions when evaluating construction variable + expansions up the stack, so users can see what's going on. + + - When disambiguating a Node.FS.Entry into a Dir or File, don't look + in the on-disk source directory until we've confirmed there's no + on-disk entry locally and there *is* one in the srcdir. This avoids + creating a phantom Node that can interfere with dependencies on + directory contents. + + - Add an AllowSubstExceptions() function that gives the SConscript + files control over what exceptions cause a string to expand to '' + vs. terminating processing with an error. + + - Allow the f90.py and f95.py Tool modules to compile earlier source + source files of earlier Fortran version. + + - Fix storing signatures of files retrieved from CacheDir() so they're + correctly identified as up-to-date next invocation. + + - Make sure lists of computed source suffixes cached by Builder objects + don't persist across changes to the list of source Builders (so the + addition of suffixes like .ui by the qt.py Tool module take effect). + + - Enhance the bootstrap.py script to allow it to be used to execute + SCons more easily from a checked-out source tree. + + From Ben Leslie: + + - Fix post-Memoizer value caching misspellings in Node.FS._doLookup(). + + From Rob Managan, Dmitry Mikhin and Joel B. Mohler: + + - Handle TeX/LaTeX files in subdirectories by changing directory + before invoking TeX/LaTeX. + + - Scan LaTeX files for \bibliography lines. + + - Support multiple file names in a "\bibliography{file1,file2}" string. + + - Handle TeX warnings about undefined citations. + + - Support re-running LaTeX if necessary due to a Table of Contents. + + From Dmitry Mikhin: + + - Return LaTeX if "Rerun to get citations correct" shows up on the next + line after the "Warning:" string. + + From Gary Oberbrunner: + + - Add #include lines to fix portability issues in two tests. + + - Eliminate some unnecessary os.path.normpath() calls. + + - Add a $CFLAGS variable for C-specific options, leaving $CCFLAGS + for options common to C and C++. + + From Tom Parker: + + - Have the error message print the missing file that Qt can't find. + + From John Pye: + + - Fix env.MergeFlags() appending to construction variable value of None. + + From Steve Robbins: + + - Fix the "sconsign" script when the .sconsign.dblite file is explicitly + specified on the command line (and not intuited from the old way of + calling it with just ".sconsign"). + + From Jose Pablo Ezequiel "Pupeno" Fernandez Silva: + + - Give the 'lex' tool knowledge of the additional target files produced + by the flex "--header-file=" and "--tables-file=" options. + + - Give the 'yacc' tool knowledge of the additional target files produced + by the bison "-g", "--defines=" and "--graph=" options. + + - Generate intermediate files with Objective C file suffixes (.m) when + the lex and yacc source files have appropriate suffixes (.lm and .ym). + + From Sohail Somain: + + - Have the mslink.py Tool only look for a 'link' executable on Windows + systems. + + From Vaclav Smilauer: + + - Add support for a "srcdir" keyword argument when calling a Builder, + which will add a srcdir prefix to all non-relative string sources. + + From Jonathan Ultis: + + - Allow Options converters to take the construction environment as + an optional argument. + + + +RELEASE 0.96.93 - Mon, 06 Nov 2006 00:44:11 -0600 + + NOTE: This is a pre-release of 0.97 for testing purposes. + + From Anonymous: + + - Allow Python Value Nodes to be Builder targets. + + From Matthias: + + - Only filter Visual Studio common filename prefixes on complete + directory names. + + From Chad Austin: + + - Fix the build of the SCons documentation on systems that don't + have "python" in the $PATH. + + From Ken Boortz: + + - Enhance ParseConfig() to recognize options that begin with '+'. + + From John Calcote, Elliot Murphy: + + - Document ways to override the CCPDBFLAGS variable to use the + Microsoft linker's /Zi option instead of the default /Z7. + + From Christopher Drexler: + + - Make SCons aware bibtex must be called if any \include files + cause creation of a bibliography. + + - Make SCons aware that "\bilbiography" in TeX source files means + that related .bbl and .blg bibliography files will be created. + (NOTE: This still needs to search for the string in \include files.) + + From David Gruener: + + - Fix inconsistent handling of Action strfunction arguments. + + - Preserve white space in display Action strfunction strings. + + From James Y. Knight and Gerard Patel: + + - Support creation of shared object files from assembly language. + + From Steven Knight: + + - Speed up the Taskmaster significantly by avoiding unnecessary + re-scans of Nodes to find out if there's work to be done, having it + track the currently-executed top-level target directly and not + through its presence on the target list, and eliminating some other + minor list(s), method(s) and manipulation. + + - Fix the expansion of $TARGET and $SOURCE in the expansion of + $INSTALLSTR displayed for non-environment calls to InstallAs(). + + - Fix the ability to have an Alias() call refer to a directory + name that's not identified as a directory until later. + + - Enhance runtest.py with an option to use QMTest as the harness. + This will become the default behavior as we add more functionality + to the QMTest side. + + - Let linking on mingw use the default function that chooses $CC (gcc) + or $CXX (g++) depending on whether there are any C++ source files. + + - Work around a bug in early versions of the Python 2.4 profile module + that caused the --profile= option to fail. + + - Only call Options validators and converters once when initializing a + construction environment. + + - Fix the ability of env.Append() and env.Prepend(), in all known Python + versions, to handle different input value types when the construction + variable being updated is a dictionary. + + - Add a --cache-debug option for information about what files it's + looking for in a CacheDir(). + + - Document the difference in construction variable expansion between + {Action,Builder}() and env.{Action,Builder}(). + + - Change the name of env.Copy() to env.Clone(), keeping the old name + around for backwards compatibility (with the intention of eventually + phasing it out to avoid confusion with the Copy() Action factory). + + From Arve Knudsen: + + - Support cleaning and scanning SWIG-generated files. + + From Carsten Koch: + + - Allow selection of Visual Studio version by setting $MSVS_VERSION + after construction environment initialization. + + From Jean-Baptiste Lab: + + - Try using zipimport if we can't import Tool or Platform modules + using the normal "imp" module. This allows SCons to be packaged + using py2exe's all-in-one-zip-file approach. + + From Ben Liblit: + + - Do not re-scan files if the scanner returns no implicit dependencies. + + From Sanjoy Mahajan: + + - Change use of $SOURCES to $SOURCE in all TeX-related Tool modules. + + From Joel B. Mohler: + + - Make SCons aware that "\makeindex" in TeX source files means that + related .ilg, .ind and .idx index files will be created. + (NOTE: This still needs to search for the string in \include files.) + + - Prevent scanning the TeX .aux file for additional files from + trying to remove it twice when the -c option is used. + + From Leanid Nazdrynau: + + - Give the MSVC RES (resource) Builder a src_builder list and a .rc + src_suffix so other builders can generate .rc files. + + From Matthew A. Nicholson: + + - Enhance Install() and InstallAs() to handle directory trees as sources. + + From Jan Nijtmans: + + - Don't use the -fPIC flag when using gcc on Windows (e.g. MinGW). + + From Greg Noel: + + - Add an env.ParseFlags() method that provides separate logic for + parsing GNU tool chain flags into a dictionary. + + - Add an env.MergeFlags() method to apply an arbitrary dictionary + of flags to a construction environment's variables. + + From Gary Oberbrunner: + + - Fix parsing tripartite Intel C compiler version numbers on Linux. + + - Extend the ParseConfig() function to recognize -arch and + -isysroot options. + + - Have the error message list the known suffixes when a Builder call + can't build a source file with an unknown suffix. + + From Karol Pietrzak: + + - Avoid recursive calls to main() in the program snippet used by the + SConf subsystem to test linking against libraries. This changes the + default behavior of CheckLib() and CheckLibWithHeader() to print + "Checking for C library foo..." instead of "Checking for main() + in C library foo...". + + From John Pye: + + - Throw an exception if a command called by ParseConfig() or + ParseFlags() returns an error. + + From Stefan Seefeld: + + - Initial infrastructure for running SCons tests under QMTest. + + From Sohail Somani: + + - Fix tests that fail due to gcc warnings. + + From Dobes Vandermeer: + + - In stack traces, print the full paths of SConscript files. + + From Atul Varma: + + - Fix detection of Visual C++ Express Edition. + + From Dobes Vandermeer: + + - Let the src_dir option to the SConscript() function affect all the + the source file paths, instead of treating all source files paths + as relative to the SConscript directory itself. + + From Nicolas Vigier: + + - Fix finding Fortran modules in build directories. + + - Fix use of BuildDir() when the source file in the source directory + is a symlink with a relative path. + + From Edward Wang: + + - Fix the Memoizer when the SCons Python modules are executed from + .pyo files at different locations from where they were compiled. + + From Johan Zander: + + - Fix missing os.path.join() when constructing the $FRAMEWORKSDKDIR/bin. + + + +RELEASE 0.96.92 - Mon, 10 Apr 2006 21:08:22 -0400 + + NOTE: This was a pre-release of 0.97 for testing purposes. + + From Anonymous: + + - Fix the intelc.py Tool module to not throw an exception if the + only installed version is something other than ia32. + + - Set $CCVERSION when using gcc. + + From Matthias: + + - Support generating project and solution files for Microsoft + Visual Studio version 8. + + - Support generating more than one project file for a Microsoft + Visual Studio solution file. + + - Add support for a support "runfile" parameter to Microsoft + Visual Studio project file creation. + + - Put the project GUID, not the solution GUID, in the right spot + in the solution file. + + From Erling Andersen: + + - Fix interpretation of Node.FS objects wrapped in Proxy instances, + allowing expansion of things like ${File(TARGET)} in command lines. + + From Stanislav Baranov: + + - Add a separate MSVSSolution() Builder, with support for the + following new construction variables: $MSVSBUILDCOM, $MSVSCLEANCOM, + $MSVSENCODING, $MSVSREBUILDCOM, $MSVSSCONS, $MSVSSCONSCOM, + $MSVSSCONSFLAGS, $MSVSSCONSCRIPT and $MSVSSOLUTIONCOM. + + From Ralph W. Grosse-Kunstleve and Patrick Mezard: + + - Remove unneceesary (and incorrect) SCons.Util strings on some function + calls in SCons.Util. + + From Bob Halley: + + - Fix C/C++ compiler selection on AIX to not always use the external $CC + environment variable. + + From August Hörandl: + + - Add a scanner for \include and \import files, with support for + searching a directory list in $TEXINPUTS (imported from the external + environment). + + - Support $MAKEINDEX, $MAKEINDEXCOM, $MAKEINDEXCOMSTR and + $MAKEINDEXFLAGS for generating indices from .idx files. + + From Steven Johnson: + + - Add a NoClean() Environment method and function to override removal + of targets during a -c clean, including documentation and tests. + + From Steven Knight: + + - Check for whether files exist on disk by listing the directory + contents, not calling os.path.exists() file by file. This is + somewhat more efficient in general, and may be significantly + more efficient on Windows. + + - Minor speedups in the internal is_Dict(), is_List() and is_String() + functions. + + - Fix a signature refactoring bug that caused Qt header files to + get re-generated every time. + + - Don't fail when writing signatures if the .sconsign.dblite file is + owned by a different user (e.g. root) from a previous run. + + - When deleting variables from stacked OverrideEnvironments, don't + throw a KeyError if we were able to delte the variable from any + Environment in the stack. + + - Get rid of the last indentation tabs in the SCons source files and + add -tt to the Python invocations in the packaging build and the + tests so they don't creep back in. + + - In Visual Studio project files, put quotes around the -C directory + so everything works even if the path has spaces in it. + + - The Intel Fortran compiler uses -object:$TARGET, not "-o $TARGET", + when building object files on Windows. Have the the ifort Tool + modify the default command lines appropriately. + + - Document the --debug=explain option in the man page. (How did we + miss this?) + + - Add a $LATEXRETRIES variable to allow configuration of the number of + times LaTex can be re-called to try to resolve undefined references. + + - Change the order of the arguments to Configure.Checklib() to match + the documentation. + + - Handle signature calculation properly when the Python function used + for a FunctionAction is an object method. + + - On Windows, assume that absolute path names without a drive letter + refer to the drive on which the SConstruct file lives. + + - Add /usr/ccs/bin to the end of the the default external execution + PATH on Solaris. + + - Add $PKGCHK and $PKGINFO variables for use on Solaris when searching + for the SunPRO C++ compiler. Make the default value for $PKGCHK + be /usr/sbin/pgkchk (since /usr/sbin isn't usually on the external + execution $PATH). + + - Fix a man page example of overriding variables when calling + SharedLibrary() to also set the $LIBSUFFIXES variable. + + - Add a --taskmastertrace=FILE option to give some insight on how + the taskmaster decides what Node to build next. + + - Changed the names of the old $WIN32DEFPREFIX, $WIN32DEFSUFFIX, + $WIN32DLLPREFIX and $WIN32IMPLIBPREFIX construction variables to + new $WINDOWSDEFPREFIX, $WINDOWSDEFSUFFIX, $WINDOWSDLLPREFIX and + $WINDOWSIMPLIBPREFIX construction variables. The old names are now + deprecated, but preserved for backwards compatibility. + + - Fix (?) a runtest.py hang on Windows when the --xml option is used. + + - Change the message when an error occurs trying to interact with the + file system to report the target(s) in square brackets (as before) and + the actual file or directory that encountered the error afterwards. + + From Chen Lee: + + - Add x64 support for Microsoft Visual Studio 8. + + From Baptiste Lepilleur: + + - Support the --debug=memory option on Windows when the Python version + has the win32process and win32api modules. + + - Add support for Visual Studio 2005 Pro. + + - Fix portability issues in various tests: test/Case.py, + Test/Java/{JAR,JARCHDIR,JARFLAGS,JAVAC,JAVACFLAGS,JAVAH,RMIC}.py, + test/MSVS/vs-{6.0,7.0,7.1,8.0}-exec.py, + test/Repository/{Java,JavaH,RMIC}.py, + test/QT/{generated-ui,installed,up-to-date,warnings}.py, + test/ZIP/ZIP.py. + + - Ignore pkgchk errors on Solaris when searching for the C++ compiler. + + - Speed up the SCons/EnvironmentTests.py unit tests. + + - Add a --verbose= option to runtest.py to print executed commands + and their output at various levels. + + From Christian Maaser: + + - Add support for Visual Studio Express Editions. + + - Add support for Visual Studio 8 *.manifest files, includng + new $WINDOWS_INSERT_MANIFEST, $WINDOWSPROGMANIFESTSUFFIX, + $WINDOWSPROGMANIFESTPREFIX, $WINDOWSPROGMANIFESTSUFFIX, + $WINDOWSSHLIBMANIFESTPREFIX and $WINDOWSSHLIBMANIFESTSUFFIX + construction variables. + + From Adam MacBeth: + + - Fix detection of additional Java inner classes following use of a + "new" keyword inside an inner class. + + From Sanjoy Mahajan: + + - Correct TeX-related command lines to just $SOURCE, not $SOURCES + + From Patrick Mezard: + + - Execute build commands for a command-line target if any of the + files built along with the target is out of date or non-existent, + not just if the command-line target itself is out of date. + + - Fix the -n option when used with -c to print all of the targets + that will be removed for a multi-target Builder call. + + - If there's no file in the source directory, make sure there isn't + one in the build directory, too, to avoid dangling files left + over from previous runs when a source file is removed. + + - Allow AppendUnique() and PrependUnique() to append strings (and + other atomic objects) to lists. + + From Joel B. Mohler: + + - Extend latex.py, pdflatex.py, pdftex.py and tex.py so that building + from both TeX and LaTeX files uses the same logic to call $BIBTEX + when it's necessary, to call $MAKEINDEX when it's necessary, and to + call $TEX or $LATEX multiple times to handle undefined references. + + - Add an emitter to the various TeX builders so that the generated + .aux and .log files also get deleted by the -c option. + + From Leanid Nazdrynau: + + - Fix the Qt UIC scanner to work with generated .ui files (by using + the FindFile() function instead of checking by-hand for the file). + + From Jan Nieuwenhuizen: + + - Fix a problem with interpreting quoted argument lists on command lines. + + From Greg Noel: + + - Add /sw/bin to the default execution PATH on Mac OS X. + + From Kian Win Ong: + + - When building a .jar file and there is a $JARCHDIR, put the -C + in front of each .class file on the command line. + + - Recognize the Java 1.5 enum keyword. + + From Asfand Yar Qazi: + + - Add /opt/bin to the default execution PATH on all POSIX platforms + (between /usr/local/bin and /bin). + + From Jon Rafkind: + + - Fix the use of Configure() contexts from nested subsidiary + SConscript files. + + From Christoph Schulz: + + - Add support for $CONFIGUREDIR and $CONFIGURELOG variables to control + the directory and logs for configuration tests. + + - Add support for a $INSTALLSTR variable. + + - Add support for $RANLIBCOM and $RANLIBCOMSTR variables (which fixes + a bug when setting $ARCOMSTR). + + From Amir Szekely: + + - Add use of $CPPDEFINES to $RCCOM (resource file compilation) on MinGW. + + From Erick Tryzelaar: + + - Fix the error message when trying to report that a given option is + not gettable/settable from an SConscript file. + + From Dobes Vandermeer: + + - Add support for SCC and other settings in Microsoft Visual + Studio project and solution files: $MSVS_PROJECT_BASE_PATH, + $MSVS_PROJECT_GUID, $MSVS_SCC_AUX_PATH, $MSVS_SCC_LOCAL_PATH, + $MSVS_SCC_PROJECT_NAME, $MSVS_SCC_PROVIDER, + + - Add support for using a $SCONS_HOME variable (imported from the + external environment, or settable internally) to put a shortened + SCons execution line in the Visual Studio project file. + + From David J. Van Maren: + + - Only filter common prefixes from source files names in Visual Studio + project files if the prefix is a complete (sub)directory name. + + From Thad Ward: + + - If $MSVSVERSIONS is already set, don't overwrite it with + information from the registry. + + + +RELEASE 0.96.91 - Thu, 08 Sep 2005 07:18:23 -0400 + + NOTE: This was a pre-release of 0.97 for testing purposes. + + From Chad Austin: + + - Have the environment store the toolpath and re-use it to find Tools + modules during later Copy() or Tool() calls (unless overridden). + + - Normalize the directory path names in SConsignFile() database + files so the same signature file can interoperate on Windows and + non-Windows systems. + + - Make --debug=stacktrace print a stacktrace when a UserError is thrown. + + - Remove an old, erroneous cut-and-paste comment in Scanner/Dir.py. + + From Stanislav Baranov: + + - Make it possible to support with custom Alias (sub-)classes. + + - Allow Builders to take empty source lists when called. + + - Allow access to both TARGET and SOURCE in $*PATH expansions. + + - Allow SConscript files to modify BUILD_TARGETS. + + From Timothee Besset: + + - Add support for Objective C/C++ .m and .mm file suffixes (for + Mac OS X). + + From Charles Crain + + - Fix the PharLap linkloc.py module to use target+source arguments + when calling env.subst(). + + From Bjorn Eriksson: + + - Fix an incorrect Command() keyword argument in the man page. + + - Add a $TEMPFILEPREFIX variable to control the prefix or flag used + to pass a long-command-line-execution tempfile to a command. + + From Steven Knight: + + - Enhanced the SCons setup.py script to install man pages on + UNIX/Linux systems. + + - Add support for an Options.FormatOptionHelpText() method that can + be overridden to customize the format of Options help text. + + - Add a global name for the Entry class (which had already been + documented). + + - Fix re-scanning of generated source files for implicit dependencies + when the -j option is used. + + - Fix a dependency problem that caused $LIBS scans to not be added + to all of the targets in a multiple-target builder call, which + could cause out-of-order builds when the -j option is used. + + - Store the paths of source files and dependencies in the .sconsign* + file(s) relative to the target's directory, not relative to the + top-level SConstruct directory. This starts to make it possible to + subdivide the dependency tree arbitrarily by putting an SConstruct + file in every directory and using content signatures. + + - Add support for $YACCHFILESUFFIX and $YACCHXXFILESUFFIX variables + that accomodate parser generators that write header files to a + different suffix than the hard-coded .hpp when the -d option is used. + + - The default behavior is now to store signature information in a + single .sconsign.dblite file in the top-level SConstruct directory. + The old behavior of a separate .sconsign file in each directory can + be specified by calling SConsignFile(None). + + - Remove line number byte codes within the signature calculation + of Python function actions, so that changing the location of an + otherwise unmodified Python function doesn't cause rebuilds. + + - Fix AddPreAction() and AddPostAction() when an action has more than + one target file: attach the actions to the Executor, not the Node. + + - Allow the source directory of a BuildDir / build_dir to be outside + of the top-level SConstruct directory tree. + + - Add a --debug=nomemoizer option that disables the Memoizer for clearer + looks at the counts and profiles of the underlying function calls, + not the Memoizer wrappers. + + - Print various --debug= stats even if we exit early (e.g. using -h). + + - Really only use the cached content signature value if the file + is older than --max-drift, not just if --max-drift is set. + + - Remove support for conversion from old (pre 0.96) .sconsign formats. + + - Add support for a --diskcheck option to enable or disable various + on-disk checks: that File and Dir nodes match on-disk entries; + whether an RCS file exists for a missing source file; whether an + SCCS file exists for a missing source file. + + - Add a --raw argument to the sconsign script, so it can print a + raw representation of each entry's NodeInfo dictionary. + + - Add the 'f90' and 'f95' tools to the list of Fortran compilers + searched for by default. + + - Add the +Z option by default when compiling shared objects on + HP-UX. + + From Chen Lee: + + - Handle Visual Studio project and solution files in Unicode. + + From Sanjoy Mahajan: + + - Fix a bad use of Copy() in an example in the man page, and a + bad regular expression example in the man page and User's Guide. + + From Shannon Mann: + + - Have the Visual Studio project file(s) echo "Starting SCons" before + executing SCons, mainly to work around a quote-stripping bug in + (some versions of?) the Windows cmd command executor. + + From Georg Mischler: + + - Remove the space after the -o option when invoking the Borland + BCC compiler; some versions apparently require that the file name + argument be concatenated with the option. + + From Leanid Nazdrynau: + + - Fix the Java parser's handling of backslashes in strings. + + From Greg Noel: + + - Add construction variables to support frameworks on Mac OS X: + $FRAMEWORKS, $FRAMEWORKPREFIX, $FRAMEWORKPATH, $FRAMEWORKPATHPREFIX. + + - Re-order link lines so the -o option always comes right after the + command name. + + From Gary Oberbrunner: + + - Add support for Intel C++ beta 9.0 (both 32 and 64 bit versions). + + - Document the new $FRAMEWORK* variables for Mac OS X. + + From Karol Pietrzak: + + - Add $RPATH (-R) support to the Sun linker Tool (sunlink). + + - Add a description of env.subst() to the man page. + + From Chris Prince: + + - Look in the right directory, not always the local directory, for a + same-named file or directory conflict on disk. + + - On Windows, preserve the external environment's %SYSTEMDRIVE% + variable, too. + + From Craig Scott: + + - Have the Fortran module emitter look for Fortan modules to be created + relative to $FORTRANMODDIR, not the top-level directory. + + - When saving Options to a file, run default values through the + converter before comparing them with the set values. This correctly + suppresses Boolean Option values from getting written to the saved + file when they're one of the many synonyms for a default True or + False value. + + - Fix the Fortran Scanner's ability to handle a module being used + in the same file in which it is defined. + + From Steve-o: + + - Add the -KPIC option by default when compiling shared objects on + Solaris. + + - Change the default suffix for Solaris objects to .o, to conform to + Sun WorkShop's expectations. Change the profix to so_ so they can + still be differentiated from static objects in the same directory. + + From Amir Szekely: + + - When calling the resource compiler on MinGW, add --include-dir and + the source directory so it finds the source file. + + - Update EnsureSConsVersion() to support revision numbers. + + From Greg Ward: + + - Fix a misplaced line in the man page. + + + +RELEASE 0.96.90 - Tue, 15 Feb 2005 21:21:12 +0000 + + NOTE: This was a pre-release of 0.97 for testing purposes. + + From Anonymous: + + - Fix Java parsing to avoid erroneously identifying a new array + of class instances as an anonymous inner class. + + - Fix a typo in the man page description of PathIsDirCreate. + + From Chad Austin: + + - Allow Help() to be called multiple times, appending to the help + text each call. + + - Allow Tools found on a toolpath to import Python modules from + their local directory. + + From Steve Christensen: + + - Handle exceptions from Python functions as build actions. + + - Add a set of canned PathOption validators: PathExists (the default), + PathIsFile, PathIsDir and PathIsDirCreate. + + From Matthew Doar: + + - Add support for .lex and .yacc file suffixes for Lex and Yacc files. + + From Eric Frias: + + - Huge performance improvement: wrap the tuples representing an + include path in an object, so that the time it takes to hash the + path doesn't grow porportionally to the length of the path. + + From Gottfried Ganssauge: + + - Fix SCons on SuSE/AMD-64 Linux by having the wrapper script also + check for the build engine in the parent directory of the Python + library directory (/usr/lib64 instead of /usr/lib). + + From Stephen Kennedy: + + - Speed up writing the .sconsign file at the end of a run by only + calling sync() once at the end, not after every entry. + + From Steven Knight: + + - When compiling with Microsoft Visual Studio, don't include the ATL and + MFC directories in the default INCLUDE and LIB environment variables. + + - Remove the following deprecated features: the ParseConfig() + global function (deprecated in 0.93); the misspelled "validater" + keyword to the Options.Add() method (deprecated in 0.91); the + SetBuildSignatureType(), SetContentSignatureType(), SetJobs() and + GetJobs() global functions (deprecated in 0.14). + + - Fix problems with corrupting the .sconsign.dblite file when + interrupting builds by writing to a temporary file and renaming, + not writing the file directly. + + - Fix a 0.96 regression where when running with -k, targets built from + walking dependencies later on the command line would not realize + that a dependency had failed an earlier build attempt, and would + try to rebuild the dependent targets. + + - Change the final messages when using -k and errors occur from + "{building,cleaning} terminated because of errors" to "done + {building,cleaning} targets (errors occurred during {build,clean})." + + - Allow Configure.CheckFunc() to take an optional header argument + (already supported by Conftest.py) to specify text at the top of + the compiled test file. + + - Fix the --debug=explain output when a Python function action changed + so it prints a meaningful string, not the binary representation of + the function contents. + + - Allow a ListOption's default value(s) to be a Python list of specified + values, not just a string containing a comma-separated list of names. + + - Add a ParseDepends() function that will parse up a list of explicit + dependencies from a "make depend" style file. + + - Support the ability to change directory when executing an Action + through "chdir" keyword arguments to Action and Builder creation + and calls. + + - Fix handling of Action ojects (and other callables that don't match + our calling arguments) in construction variable expansions. + + - On Win32, install scons.bat in the Python directory when installing + from setup.py. (The bdist_wininst installer was already doing this.) + + - Fix env.SConscript() when called with a list of SConscipt files. + (The SConscript() global function already worked properly.) + + - Add a missing newline to the end of the --debug=explain "unknown + reasons" message. + + - Enhance ParseConfig() to work properly for spaces in between the -I, + -L and -l options and their arguments. + + - Packaging build fix: Rebuild the files that are use to report the + --version of SCons whenever the development version number changes. + + - Fix the ability to specify a target_factory of Dir() to a Builder, + which the default create-a-directory Builder was interfering with. + + - Mark a directory as built if it's created as part of the preparation + for another target, to avoid trying to build it again when it comes + up in the target list. + + - Allow a function with the right calling signature to be put directly + in an Environment's BUILDERS dictionary, making for easier creation + and use of wrappers (pseudo-Builders) that call other Builders. + + - On Python 2.x, wrap lists of Nodes returned by Builders in a UserList + object that adds a method that makes str() object return a string + with all of the Nodes expanded to their path names. (Builders under + Python 1.5.2 still return lists to avoid TypeErrors when trying + to extend() list, so Python 1.5.2 doesn't get pretty-printing of Node + lists, but everything should still function.) + + - Allow Aliases to have actions that will be executed whenever + any of the expanded Alias targets are out of date. + + - Fix expansion of env.Command() overrides within target and + source file names. + + - Support easier customization of what's displayed by various default + actions by adding lots of new construction variables: $ARCOMSTR, + $ASCOMSTR, $ASPPCOMSTR, $BIBTEXCOMSTR, $BITKEEPERCOMSTR, $CCCOMSTR, + $CVSCOMSTR, $CXXCOMSTR, $DCOMSTR, $DVIPDFCOMSTR, $F77COMSTR, + $F90COMSTR, $F95COMSTR, $FORTRANCOMSTR, $GSCOMSTR, $JARCOMSTR, + $JAVACCOMSTR, $JAVAHCOMSTR, $LATEXCOMSTR, $LEXCOMSTR, $LINKCOMSTR, + $M4COMSTR, $MIDLCOMSTR, $P4COMSTR, $PCHCOMSTR, $PDFLATEXCOMSTR, + $PDFTEXCOMSTR, $PSCOMSTR, $QT_MOCFROMCXXCOMSTR, $QT_MOCFROMHCOMSTR, + $QT_UICCOMSTR, $RCCOMSTR, $REGSVRCOMSTR, $RCS_COCOMSTR, $RMICCOMSTR, + $SCCSCOMSTR, $SHCCCOMSTR, $SHCXXCOMSTR, $SHF77COMSTR, $SHF90COMSTR, + $SHF95COMSTR, $SHFORTRANCOMSTR, $SHLINKCOMSTR, $SWIGCOMSTR, + $TARCOMSTR, $TEXCOMSTR, $YACCCOMSTR and $ZIPCOMSTR. + + - Add an optional "map" keyword argument to ListOption() that takes a + dictionary to map user-specified values to legal values from the list + (like EnumOption() already doee). + + - Add specific exceptions to try:-except: blocks without any listed, + so that they won't catch and mask keyboard interrupts. + + - Make --debug={tree,dtree,stree} print something even when there's + a build failure. + + - Fix how Scanners sort the found dependencies so that it doesn't + matter whether the dependency file is in a Repository or not. + This may cause recompilations upon upgrade to this version. + + - Make AlwaysBuild() work with Alias and Python value Nodes (making + it much simpler to support aliases like "clean" that just invoke + an arbitrary action). + + - Have env.ParseConfig() use AppendUnique() by default to suppress + duplicate entries from multiple calls. Add a "unique" keyword + argument to allow the old behavior to be specified. + + - Allow the library modules imported by an SConscript file to get at + all of the normally-available global functions and variables by saying + "from SCons.Script import *". + + - Add a --debug=memoizer option to print Memoizer hit/mass statistics. + + - Allow more than one --debug= option to be set at a time. + + - Change --debug=count to report object counts before and after + reading SConscript files and before and after building targets. + + - Change --debug=memory output to line up the numbers and to better + match (more or less) the headers on the --debug=count columns. + + - Speed things up when there are lists of targets and/or sources by + getting rid of some N^2 walks of the lists involved. + + - Cache evaluation of LazyActions so we don't create a new object + for each invocation. + + - When scanning, don't create Nodes for include files that don't + actually exist on disk. + + - Make supported global variables CScanner, DScanner, ProgramScanner and + SourceFileScanner. Make SourceFileScanner.add_scanner() a supported + part of the public interface. Keep the old SCons.Defaults.*Scan names + around for a while longer since some people were already using them. + + - By default, don't scan directories for on-disk files. Add a + DirScanner global scanner that can be used in Builders or Command() + calls that want source directory trees scanned for on-disk changes. + Have the Tar() and Zip() Builders use the new DirScanner to preserve + the behavior of rebuilding a .tar or .zip file if any file or + directory under a source tree changes. Add Command() support for + a source_scanner keyword argument to Command() that can be set to + DirScanner to get this behavior. + + - Documentation changes: Explain that $CXXFLAGS contains $CCFLAGS + by default. Fix a bad target_factory example in the man page. + Add appendices to the User's Guide to cover the available Tools, + Builders and construction variables. Comment out the build of + the old Python 10 paper, which doesn't build on all systems and + is old enough at this point that it probably isn't worth the + effort to make it do so. + + From Wayne Lee: + + - Avoid "maximum recursion limit" errors when removing $(-$) pairs + from long command lines. + + From Clive Levinson: + + - Make ParseConfig() recognize and add -mno-cygwin to $LINKFLAGS and + $CCFLAGS, and -mwindows to $LINKFLAGS. + + From Michael McCracken: + + - Add a new "applelink" tool to handle the things like Frameworks and + bundles that Apple has added to gcc for linking. + + - Use more appropriate default search lists of linkers, compilers and + and other tools for the 'darwin' platform. + + - Add a LoadableModule Builder that builds a bundle on Mac OS X (Darwin) + and a shared library on other systems. + + - Improve SWIG tests for use on Mac OS X (Darwin). + + From Elliot Murphy: + + - Enhance the tests to guarantee persistence of ListOption + values in saved options files. + + - Supply the help text when -h is used with the -u, -U or -D options. + + From Christian Neeb: + + - Fix the Java parser's handling of string definitions to avoid ignoring + subsequent code. + + From Han-Wen Nienhuys: + + - Optimize variable expansion by: using the re.sub() method (when + possible); not using "eval" for variables for which we can fetch the + value directory; avoiding slowing substitution logic when there's no + '$' in the string. + + From Gary Oberbrunner: + + - Add an Environment.Dump() method to print the contents of a + construction environment. + + - Allow $LIBS (and similar variables) to contain explicit File Nodes. + + - Change ParseConfig to add the found library names directly to the + $LIBS variable, instead of returning them. + + - Add ParseConfig() support for the -framework GNU linker option. + + - Add a PRINT_CMD_LINE_FUNC construction variable to allow people + to filter (or log) command-line output. + + - Print an internal Python stack trace in response to an otherwise + unexplained error when --debug=stacktrace is specified. + + - Add a --debug=findlibs option to print what's happening when + the scanner is searching for libraries. + + - Allow Tool specifications to be passed a dictionary of keyword + arguments. + + - Support an Options default value of None, in which case the variable + will not be added to the construction environment unless it's set + explicitly by the user or from an Options file. + + - Avoid copying __builtin__ values into a construction environment's + dictionary when evaluating construction variables. + + - Add a new cross-platform intelc.py Tool that can detect and + configure the Intel C++ v8 compiler on both Windows, where it's + named icl, and Linux, where it's named icc. It also checks that + the directory specified in the Windows registry exists, and sets a + new $INTEL_C_COMPILER_VERSION construction variable to identify the + version being used. (Niall Douglas contributed an early prototype + of parts of this module.) + + - Fix the private Conftest._Have() function so it doesn't change + non-alphanumeric characters to underscores. + + - Supply a better error message when a construction variable expansion + has an unknown attribute. + + - Documentation changes: Update the man page to describe use of + filenames or Nodes in $LIBS. + + From Chris Pawling: + + - Have the linkloc tool use $MSVS_VERSION to select the Microsoft + Visual Studio version to use. + + From Kevin Quick: + + - Fix the Builder name returned from ListBuilders and other instances + of subclasses of the BuilderBase class. + + - Add Builders and construction variables to support rpcgen: + RPCGenClient(), RPCGenHeader(), RPCGenService(), RPCGenXDR(), + $RPCGEN, $RPCGENFLAGS, $RPCGENCLIENTFLAGS, $RPCGENHEADERFLAGS, + $RPCGENSERVICEFLAGS, $RPCGENXDRFLAGS. + + - Update the man page to document that prefix and suffix Builder + keyword arguments can be strings, callables or dictionaries. + + - Provide more info in the error message when a user tries to build + a target multiple ways. + + - Fix Delete() when a file doesn't exist and must_exist=1. (We were + unintentionally dependent on a bug in versions of the Python shutil.py + module prior to Python 2.3, which would generate an exception for + a nonexistent file even when ignore_errors was set.) + + - Only replace a Node's builder with a non-null source builder. + + - Fix a stack trace when a suffix selection dictionary is passed + an empty source file list. + + - Allow optional names to be attached to Builders, for default + Builders that don't get attached to construction environments. + + - Fix problems with Parallel Task Exception handling. + + - Build targets in an associated BuildDir even if there are targets + or subdirectories locally in the source directory. + + - If a FunctionAction has a callable class as its underlying Python + function, use its strfunction() method (if any) to display the + action. + + - Fix handling when BuildDir() exists but is unwriteable. Add + "Stop." to those error messages for consistency. + + - Catch incidents of bad builder creation (without an action) and + supply meaningful error messages. + + - Fix handling of src_suffix values that aren't extensions (don't + begin with a '.'). + + - Don't retrieve files from a CacheDir, but report what would happen, + when the -n option is used. + + - Use the source_scanner from the target Node, not the source node + itself. + + - Internal Scanners fixes: Make sure Scanners are only passed Nodes. + Fix how a Scanner.Selector called its base class initialization. + Make comparisons of Scanner objects more robust. Add a name to + an internal default ObjSourceScanner. + + - Add a deprecated warning for use of the old "scanner" keyword argument + to Builder creation. + + - Improve the --debug=explain message when the build action changes. + + - Test enhancements in SourceCode.py, option-n.py, midl.py. Better + Command() and Scanner test coverage. Improved test infrastructure + for -c output. + + - Refactor the interface between Action and Executor objects to treat + Actions atomically. + + - The --debug=presub option will now report the pre-substitution + each action seprately, instead of reporting the entire list before + executing the actions one by one. + + - The --debug=explain option explaining a changed action will now + (more correctly) show pre-substitution action strings, instead of + the commands with substituted file names. + + - A Node (file) will now be rebuilt if its PreAction or PostAction + actions change. + + - Python Function actions now have their calling signature (target, + source, env) reported correctly when displayed. + + - Fix BuildDir()/build_dir handling when the build_dir is underneath + the source directory and trying to use entries from the build_dir + as sources for other targets in the build-dir. + + - Fix hard-coding of JDK path names in various Java tests. + + - Handle Python stack traces consistently (stop at the SConscript stack + frame, by default) even if the Python source code isn't available. + + - Improve the performance of the --debug={tree,dtree} options. + + - Add --debug=objects logging of creation of OverrideWarner, + EnvironmentCopy and EnvironmentOverride objects. + + - Fix command-line expansion of Python Value Nodes. + + - Internal cleanups: Remove an unnecessary scan argument. Associate + Scanners only with Builders, not nodes. Apply overrides once when + a Builder is called, not in multiple places. Cache results from the + Node.FS.get_suffix() and Node.get_build_env() methods. Use the Python + md5 modules' hexdigest() method, if there is one. Have Taskmaster + call get_stat() once for each Node and re-use the value instead of + calling it each time it needs the value. Have Node.depends_on() + re-use the list from the children() method instead of calling it + multiple times. + + - Use the correct scanner if the same source file is used for targets in + two different environments with the same path but different scanners. + + - Collect logic for caching values in memory in a Memoizer class, + which cleans up a lot of special-case code in various methods and + caches additional values to speed up most configurations. + + - Add a PathAccept validator to the list of new canned PathOption + validators. + + From Jeff Squyres: + + - Documentation changes: Use $CPPDEFINES instead of $CCFLAGS in man + page examples. + + From Levi Stephen: + + - Allow $JARCHDIR to be expanded to other construction variables. + + From Christoph Wiedemann: + + - Add an Environment.SetDefault() method that only sets values if + they aren't already set. + + - Have the qt.py Tool not override variables already set by the user. + + - Add separate $QT_BINPATH, $QT_CPPPATH and $QT_LIBPATH variables + so these can be set individually, instead of being hard-wired + relative to $QTDIR. + + - The %TEMP% and %TMP% external environment variables are now propagated + automatically to the command execution environment on Windows systems. + + - A new --config= command-line option allows explicit control of + of when the Configure() tests are run: --config=force forces all + checks to be run, --config=cache uses all previously cached values, + --config=auto (the default) runs tests only when dependency analysis + determines it's necessary. + + - The Configure() subsystem can now write a config.h file with values + like HAVE_STDIO_H, HAVE_LIBM, etc. + + - The Configure() subsystem now executes its checks silently when the + -Q option is specified. + + - The Configure() subsystem now reports if a test result is being + taken from cache, and prints the standard output and error output + of tests even when cached. + + - Configure() test results are now reported as "yes" or "no" instead of + "ok" or "failed." + + - Fixed traceback printing when calling the env.Configure() method + instead of the Configure() global function. + + - The Configure() subsystem now caches build failures in a .sconsign + file in the subdirectory, not a .cache file. This may cause + tests to be re-executed the first time after you install 0.97. + + - Additional significant internal cleanups in the Configure() subsystem + and its tests. + + - Have the Qt Builder make uic-generated files dependent on the .ui.h + file, if one exists. + + - Add a test to make sure that SCons source code does not contain + try:-except: blocks that catch all errors, which potentially catch + and mask keyboard interrupts. + + - Fix us of TargetSignatures('content') with the SConf subsystem. + + From Russell Yanofsky: + + - Add support for the Metrowerks Codewarrior compiler and linker + (mwcc and mwld). + + + +RELEASE 0.96.1 - Mon, 23 Aug 2004 12:55:50 +0000 + + From Craig Bachelor: + + - Handle white space in the executable Python path name within in MSVS + project files by quoting the path. + + - Correct the format of a GUID string in a solution (.dsw) file so + MSVS can correctly "build enable" a project. + + From Steven Knight: + + - Add a must_exist flag to Delete() to let the user control whether + it's an error if the specified entry doesn't exist. The default + behavior is now to silently do nothing if it doesn't exist. + + - Package up the new Platform/darwin.py, mistakenly left out of 0.96. + + - Make the scons.bat REM statements into @REM so they aren't printed. + + - Make the SCons packaging SConscript files platform independent. + + From Anthony Roach: + + - Fix scanning of pre-compiled header (.pch) files for #includes, + broken in 0.96. + + + +RELEASE 0.96 - Wed, 18 Aug 2004 13:36:40 +0000 + + From Chad Austin: + + - Make the CacheDir() directory if it doesn't already exist. + + - Allow construction variable substitutions in $LIBS specifications. + + - Allow the emitter argument to a Builder() to be or expand to a list + of emitter functions, which will be called in sequence. + + - Suppress null values in construction variables like $LIBS that use + the internal _concat() function. + + - Remove .dll files from the construction variables searched for + libraries that can be fed to Win32 compilers. + + From Chad Austin and Christoph Wiedemann: + + - Add support for a $RPATH variable to supply a list of directories + to search for shared libraries when linking a program. Used by + the GNU and IRIX linkers (gnulink and sgilink). + + From Charles Crain: + + - Restore the ability to do construction variable substitutions in all + kinds of *PATH variables, even when the substitution returns a Node + or other object. + + From Tom Epperly: + + - Allow the Java() Builder to take more than one source directory. + + From Ralf W. Grosse-Kunstleve: + + - Have SConsignFile() use, by default, a custom "dblite.py" that we can + control and guarantee to work on all Python versions (or nearly so). + + From Jonathan Gurley: + + - Add support for the newer "ifort" versions of the Intel Fortran + Compiler for Linux. + + From Bob Halley: + + - Make the new *FLAGS variable type work with copied Environments. + + From Chris Hoeppler: + + - Initialize the name of a Scanner.Classic scanner correctly. + + From James Juhasz: + + - Add support for the .dylib shared library suffix and the -dynamiclib + linker option on Mac OS X. + + From Steven Knight: + + - Add an Execute() method for executing actions directly. + + - Support passing environment override keyword arguments to Command(). + + - Fix use of $MSVS_IGNORE_IDE_PATHS, which was broken when we added + support for $MSVS_USE_MFC_DIRS last release. + + - Make env.Append() and env.Prepend() act like the underlying Python + behavior when the variable being appended to is a UserList object. + + - Fix a regression that prevented the Command() global function in + 0.95 from working with command-line strings as actions. + + - Fix checking out a file from a source code management system when + the env.SourceCode() method was called with an individual file name + or node, not a directory name or node. + + - Enhance the Task.make_ready() method to create a list of the + out-of-date Nodes for the task for use by the wrapping interface. + + - Allow Scanners to pull the list of suffixes from the construction + environment when the "skeys" keyword argument is a string containing + a construction variable to be expanded. + + - Support new $CPPSUFFIXES, $DSUFFIXES $FORTRANSUFFIXES, and + $IDLSUFFIXES. construction variables that contain the default list + of suffixes to be scanned by a given type of scanner, allowing these + suffix lists to be easily added to or overridden. + + - Speed up Node creation when calling a Builder by comparing whether two + Environments are the same object, not if their underlying dictionaries + are equivalent. + + - Add a --debug=explain option that reports the reason(s) why SCons + thinks it must rebuild something. + + - Add support for functions that return platform-independent Actions + to Chmod(), Copy(), Delete(), Mkdir(), Move() and Touch() files + and/or directories. Like any other Actions, the returned Action + object may be executed directly using the Execute() global function + or env.Execute() environment method, or may be used as a Builder + action or in an env.Command() action list. + + - Add support for the strfunction argument to all types of Actions: + CommandAction, ListAction, and CommandGeneratorAction. + + - Speed up turning file system Nodes into strings by caching the + values after we're finished reading the SConscript files. + + - Have ParseConfig() recognize and supporting adding the -Wa, -Wl, + and -Wp, flags to ASFLAGS, LINKFLAGS and CPPFLAGS, respectively. + + - Change the .sconsign format and the checks for whether a Node is + up-to-date to make dependency checks more efficient and correct. + + - Add wrapper Actions to SCons.Defaults for $ASCOM, $ASPPCOM, $LINKCOM, + $SHLINKCOM, $ARCOM, $LEXCOM and $YACCCOM. This makes it possible + to replace the default print behavior with a custom strfunction() + for each of these. + + - When a Node has been built, don't walk the whole tree back to delete + the parents's implicit dependencies, let returning up the normal + Taskmaster descent take care of it for us. + + - Add documented support for separate target_scanner and source_scanner + arguments to Builder creation, which allows different scanners to + be applied to source files + + - Don't re-install or (re-generate) .h files when a subsidiary #included + .h file changes. This eliminates incorrect circular dependencies + with .h files generated from other source files. + + - Slim down the internal Sig.Calculator class by eliminating methods + whose functionality is now covered by Node methods. + + - Document use of the target_factory and source_factory keyword + arguments when creating Builder objects. Enhance Dir Nodes so that + they can be created with user-specified Builder objects. + + - Don't blow up with stack trace when the external $PATH environment + variable isn't set. + + - Make Builder calls return lists all the time, even if there's only + one target. This keeps things consistent and easier to program to + across platforms. + + - Add a Flatten() function to make it easier to deal with the Builders + all returning lists of targets, not individual targets. + + - Performance optimizations in Node.FS.__doLookup(). + + - Man page fixes: formatting typos, misspellings, bad example. + + - User's Guide fixes: Fix the signatures of the various example + *Options() calls. Triple-quote properly a multi-line Split example. + + - User's Guide additions: Chapter describing File and Directory + Nodes. Section describing declarative nature of SCons functions in + SConscript files. Better organization and clarification of points + raised by Robert P. J. Day. Chapter describing SConf (Autoconf-like) + functionality. Chapter describing how to install Python and + SCons. Chapter describing Java builds. + + From Chris Murray: + + - Add a .win32 attribute to force file names to expand with + Windows backslash path separators. + + - Fix escaping file names on command lines when the expansion is + concatenated with another string. + + - Add support for Fortran 90 and Fortran 95. This adds $FORTRAN* + variables that specify a default compiler, command-line, flags, + etc. for all Fortran versions, plus separate $F90* and $F95* + variables for when different compilers/flags/etc. must be specified + for different Fortran versions. + + - Have individual tools that create libraries override the default + $LIBPREFIX and $LIBSUFFIX values set by the platform. This makes + it easier to use Microsoft Visual Studio tools on a CygWin platform. + + From Gary Oberbrunner: + + - Add a --debug=presub option to print actions prior to substitution. + + - Add a warning upon use of the override keywords "targets" and + "sources" when calling Builders. These are usually mistakes which + are otherwise silently (and confusingly) turned into construction + variable overrides. + + - Try to find the ICL license file path name in the external environment + and the registry before resorting to the hard-coded path name. + + - Add support for fetching command-line keyword=value arguments in + order from an ARGLIST list. + + - Avoid stack traces when trying to read dangling symlinks. + + - Treat file "extensions" that only contain digits as part of the + file basename. This supports version numbers as part of shared + library names, for example. + + - Avoid problems when there are null entries (None or '') in tool + lists or CPPPATH. + + - Add an example and explanation of how to use "tools = ['default', ..." + when creating a construction environment. + + - Add a section describing File and Directory Nodes and some of their + attributes and methods. + + - Have ParseConfig() add a returned -pthread flag to both $CCFLAGS + and $LINKFLAGS. + + - Fix some test portability issues on Mac OS X (darwin). + + From Simon Perkins: + + - Fix a bug introduced in building shared libraries under MinGW. + + From Kevin Quick: + + - Handling SCons exceptions according to Pythonic standards. + + - Fix test/chained-build.py on systems that execute within one second. + + - Fix tests on systems where 'ar' warns about archive creation. + + From Anthony Roach: + + - Fix use of the --implicit-cache option with timestamp signatures. + + - If Visual Studio is installed, assume the C/C++ compiler, the linker + and the MIDL compiler that comes with it are available, too. + + - Better error messages when evaluating a construction variable + expansion yields a Python syntax error. + + - Change the generation of PDB files when using Visual Studio from + compile time to link time. + + From sam th: + + - Allow SConf.CheckLib() to search a list of libraries, like the + Autoconf AC_SEARCH_LIBS macro. + + - Allow the env.WhereIs() method to take a "reject" argument to + let it weed out specific path names. + + From Christoph Wiedemann: + + - Add new Moc() and Uic() Builders for more explicit control over + Qt builds, plus new construction variables to control them: + $QT_AUTOSCAN, $QT_DEBUG, $QT_MOCCXXPREFIX, $QT_MOCCXXSUFFIX, + $QT_MOCHPREFIX, $QT_MOCHSUFFIX, $QT_UICDECLPREFIX, $QT_UICDECLSUFFIX, + $QT_UICIMPLPREFIX, $QT_UICIMPLSUFFIX and $QT_UISUFFIX. + + - Add a new single_source keyword argument for Builders that enforces + a single source file on calls to the Builder. + + + +RELEASE 0.95 - Mon, 08 Mar 2004 06:43:20 -0600 + + From Chad Austin: + + - Replace print statements with calls to sys.stdout.write() so output + lines stay together when -j is used. + + - Add portability fixes for a number of tests. + + - Accomodate the fact that Cygwin's os.path.normcase() lies about + the underlying system being case-sensitive. + + - Fix an incorrect _concat() call in the $RCINCFLAGS definition for + the mingw Tool. + + - Fix a problem with the msvc tool with Python versions prior to 2.3. + + - Add support for a "toolpath" Tool() and Environment keyword that + allows Tool modules to be found in specified local directories. + + - Work around Cygwin Python's silly fiction that it's using a + case-sensitive file system. + + - More robust handling of data in VCComponents.dat. + + - If the "env" command is available, spawn commands with the more + general "env -" instead of "env -i". + + From Kerim Borchaev: + + - Fix a typo in a msvc.py's registry lookup: "VCComponents.dat", not + "VSComponents.dat". + + From Chris Burghart: + + - Fix the ability to save/restore a PackageOption to a file. + + From Steve Christensen: + + - Update the MSVS .NET and MSVC 6.0/7.0 path detection. + + From David M. Cooke: + + - Make the Fortran scanner case-insensitive for the INCLUDE string. + + From Charles Crain: + + - If no version of MSVC is detected but the tool is specified, + use the MSVC 6.0 paths by default. + + - Ignore any "6.1" version of MSVC found in the registry; this is a + phony version number (created by later service packs?) and would + throw off the logic if the user had any non-default paths configure. + + - Correctly detect if the user has independently configured the MSVC + "include," "lib" or "path" in the registry and use the appropriate + values. Previously, SCons would only use the values if all three + were set in the registry. + + - Make sure side-effect nodes are prepare()d before building their + corresponding target. + + - Preserve the ability to call BuildDir() multiple times with the + same target and source directory arguments. + + From Andy Friesen: + + - Add support for the Digital Mars "D" programming language. + + From Scott Lystig Fritchie: + + - Fix the ability to use a custom _concat() function in the + construction environment when calling _stripixes(). + + - Make the message about ignoring a missing SConscript file into a + suppressable Warning, not a hard-coded sys.stderr.write(). + + - If a builder can be called multiple times for a target (because + the sources and overrides are identical, or it's a builder with the + "multi" flag set), allow the builder to be called through multiple + environments so long as the builders have the same signature for + the environments in questions (that is, they're the same action). + + From Bob Halley: + + - When multiple targets are built by a single action, retrieve all + of them from cache, not just the first target, and exec the build + command if any of the targets isn't present in the cache. + + From Zephaniah Hull: + + - Fix command-line ARGUMENTS with multiple = in them. + + From Steven Knight: + + - Fix EnsureSConsVersion() so it checks against the SCons version, + not the Python version, on Pythons with sys.version_info. + + - Don't swallow the AttributeError when someone uses an expansion like + $TARGET.bak, so we can supply a more informative error message. + + - Fix an odd double-quote escape sequence in the man page. + + - Fix looking up a naked drive letter as a directory (Dir('C:')). + + - Support using File nodes in the LIBS construction variable. + + - Allow the LIBS construction variable to be a single string or File + node, not a list, when only one library is needed. + + - Fix typos in the man page: JAVACHDIR => JARCHDIR; add "for_signature" + to the __call__() example in the "Variable Substitution" section. + + - Correct error message spellings of "non-existant" to "non-existent." + + - When scanning for libraries to link with, don't append $LIBPREFIXES + or $LIBSUFFIXES values to the $LIBS values if they're already present. + + - Add a ZIPCOMPRESSION construction variable to control whether the + internal Python action for the Zip Builder compresses the file or + not. The default value is zipfile.ZIP_DEFLATED, which generates + a compressed file. + + - Refactor construction variable expansion to support recursive + expansion of variables (e.g. CCFLAGS = "$CCFLAGS -g") without going + into an infinite loop. Support this in all construction variable + overrides, as well as when copying Environments. + + - Fix calling Configure() from more than one subsidiary SConscript file. + + - Fix the env.Action() method so it returns the correct type of + Action for its argument(s). + + - Fix specifying .class files as input to JavaH with the .class suffix + when they weren't generated using the Java Builder. + + - Make the check for whether all of the objects going into a + SharedLibrary() are shared work even if the object was built in a + previous run. + + - Supply meaningful error messages, not stack traces, if we try to add + a non-Node as a source, dependency, or ignored dependency of a Node. + + - Generate MSVS Project files that re-invoke SCons properly regardless + of whether the file was built via scons.bat or scons.py. + (Thanks to Niall Douglas for contributing code and testing.) + + - Fix TestCmd.py, runtest.py and specific tests to accomodate being + run from directories whose paths include white space. + + - Provide a more useful error message if a construction variable + expansion contains a syntax error during evaluation. + + - Fix transparent checkout of implicit dependency files from SCCS + and RCS. + + - Added new --debug=count, --debug=memory and --debug=objects options. + --debug=count and --debug=objects only print anything when run + under Python 2.1 or later. + + - Deprecate the "overrides" keyword argument to Builder() creation + in favor of using keyword argument values directly (like we do + for builder execution and the like). + + - Always use the Builder overrides in substitutions, not just if + there isn't a target-specific environment. + + - Add new "rsrcpath" and "rsrcdir" and attributes to $TARGET/$SOURCE, + so Builder command lines can find things in Repository source + directories when using BuildDir. + + - Fix the M4 Builder so that it chdirs to the Repository directory + when the input file is in the source directory of a BuildDir. + + - Save memory at build time by allowing Nodes to delete their build + environments after they've been built. + + - Add AppendUnique() and PrependUnique() Environment methods, which + add values to construction variables like Append() and Prepend() + do, but suppress any duplicate elements in the list. + + - Allow the 'qt' tool to still be used successfully from a copied + Environment. The include and library directories previously ended up + having the same string re-appended to the end, yielding an incorrect + path name. + + - Supply a more descriptive error message when the source for a target + can't be found. + + - Initialize all *FLAGS variables with objects do the right thing with + appending flags as strings or lists. + + - Make things like ${TARGET.dir} work in *PATH construction variables. + + - Allow a $MSVS_USE_MFC_DIRS construction variable to control whether + ATL and MFC directories are included in the default INCLUDE and + LIB paths. + + - Document the dbm_module argument to the SConsignFile() function. + + From Vincent Risi: + + - Add support for the bcc32, ilink32 and tlib Borland tools. + + From Anthony Roach: + + - Supply an error message if the user tries to configure a BuildDir + for a directory that already has one. + + - Remove documentation of the still-unimplemented -e option. + + - Add -H help text listing the legal --debug values. + + - Don't choke if a construction variable is a non-string value. + + - Build Type Libraries in the target directory, not the source + directory. + + - Add an appendix to the User's Guide showing how to accomplish + various common tasks in Python. + + From Greg Spencer: + + - Add support for Microsoft Visual Studio 2003 (version 7.1). + + - Evaluate $MSVSPROJECTSUFFIX and $MSVSSOLUTIONSUFFIX when the Builder + is invoked, not when the tool is initialized. + + From Christoph Wiedemann: + + - When compiling Qt, make sure the moc_*.cc files are compiled using + the flags from the environment used to specify the target, not + the environment that first has the Qt Builders attached. + + + +RELEASE 0.94 - Fri, 07 Nov 2003 05:29:48 -0600 + + From Hartmut Goebel: + + - Add several new types of canned functions to help create options: + BoolOption(), EnumOption(), ListOption(), PackageOption(), + PathOption(). + + From Steven Knight: + + - Fix use of CPPDEFINES with C++ source files. + + - Fix env.Append() when the operand is an object with a __cmp__() + method (like a Scanner instance). + + - Fix subclassing the Environment and Scanner classes. + + - Add BUILD_TARGETS, COMMAND_LINE_TARGETS and DEFAULT_TARGETS variables. + + From Steve Leblanc: + + - SGI fixes: Fix C++ compilation, add a separate Tool/sgic++.py module. + + From Gary Oberbrunner: + + - Fix how the man page un-indents after examples in some browsers. + + From Vincent Risi: + + - Fix the C and C++ tool specifications for AIX. + + + +RELEASE 0.93 - Thu, 23 Oct 2003 07:26:55 -0500 + + From J.T. Conklin: + + - On POSIX, execute commands with the more modern os.spawnvpe() + function, if it's available. + + - Scan .S, .spp and .SPP files for C preprocessor dependencies. + + - Refactor the Job.Parallel() class to use a thread pool without a + condition variable. This improves parallel build performance and + handles keyboard interrupts properly when -j is used. + + From Charles Crain: + + - Add support for a JARCHDIR variable to control changing to a + directory using the jar -C option. + + - Add support for detecting Java manifest files when using jar, + and specifying them using the jar m flag. + + - Fix some Python 2.2 specific things in various tool modules. + + - Support directories as build sources, so that a rebuild of a target + can be triggered if anything underneath the directory changes. + + - Have the scons.bat and scons.py files look for the SCons modules + in site-packages as well. + + From Christian Engel: + + - Support more flexible inclusion of separate C and C++ compilers. + + - Use package management tools on AIX and Solaris to find where + the comilers are installed, and what version they are. + + - Add support for CCVERSION and CXXVERSION variables for a number + of C and C++ compilers. + + From Sergey Fogel: + + - Add test cases for the new capabilities to run bibtex and to rerun + latex as needed. + + From Ralf W. Grosse-Kunstleve: + + - Accomodate anydbm modules that don't have a sync() method. + + - Allow SConsignFile() to take an argument specifying the DBM + module to be used. + + From Stephen Kennedy: + + - Add support for a configurable global .sconsign.dbm file which + can be used to avoid cluttering each directory with an individual + .sconsign file. + + From John Johnson: + + - Fix (re-)scanning of dependencies in generated or installed + header files. + + From Steven Knight: + + - The -Q option suppressed too many messages; fix it so that it only + suppresses the Reading/Building messages. + + - Support #include when there's no space before the opening quote + or angle bracket. + + - Accomodate alphanumeric version strings in EnsurePythonVersion(). + + - Support arbitrary expansion of construction variables within + file and directory arguments to Builder calls and Environment methods. + + - Add Environment-method versions of the following global functions: + Action(), AddPostAction(), AddPreAction(), Alias(), Builder(), + BuildDir(), CacheDir(), Clean(), Configure(), Default(), + EnsurePythonVersion(), EnsureSConsVersion(), Environment(), + Exit(), Export(), FindFile(), GetBuildPath(), GetOption(), Help(), + Import(), Literal(), Local(), Platform(), Repository(), Scanner(), + SConscriptChdir(), SConsignFile(), SetOption(), SourceSignatures(), + Split(), TargetSignatures(), Tool(), Value(). + + - Add the following global functions that correspond to the same-named + Environment methods: AlwaysBuild(), Command(), Depends(), Ignore(), + Install(), InstallAs(), Precious(), SideEffect() and SourceCode(). + + - Add the following global functions that correspond to the default + Builder methods supported by SCons: CFile(), CXXFile(), DVI(), Jar(), + Java(), JavaH(), Library(), M4(), MSVSProject(), Object(), PCH(), + PDF(), PostScript(), Program(), RES(), RMIC(), SharedLibrary(), + SharedObject(), StaticLibrary(), StaticObject(), Tar(), TypeLibrary() + and Zip(). + + - Rearrange the man page to show construction environment methods and + global functions in the same list, and to explain the difference. + + - Alphabetize the explanations of the builder methods in the man page. + + - Rename the Environment.Environment class to Enviroment.Base. + Allow the wrapping interface to extend an Environment by using its own + subclass of Environment.Base and setting a new Environment.Environment + variable as the calling entry point. + + - Deprecate the ParseConfig() global function in favor of a same-named + construction environment method. + + - Allow the Environment.WhereIs() method to take explicit path and + pathext arguments (like the underlying SCons.Util.WhereIs() function). + + - Remove the long-obsolete {Get,Set}CommandHandler() functions. + + - Enhance env.Append() to suppress null values when appropriate. + + - Fix ParseConfig() so it works regardless of initial construction + variable values. + + Extend CheckHeader(), CheckCHeader(), CheckCXXHeader() and + CheckLibWithHeader() to accept a list of header files that will be + #included in the test. The last one in the list is assumed to be + the one being checked for. (Prototype code contributed by Gerard + Patel and Niall Douglas). + + - Supply a warning when -j is used and threading isn't built in to + the current version of Python. + + - First release of the User's Guide (finally, and despite a lot + of things still missing from it...). + + From Clark McGrew: + + - Generalize the action for .tex files so that it will decide whether + a file is TeX or LaTeX, check the .aux output to decide if it should + run bibtex, and check the .log output to re-run LaTeX if needed. + + From Bram Moolenaar: + + - Split the non-SCons-specific functionality from SConf.py to a new, + re-usable Conftest.py module. + + From Gary Oberbrunner: + + - Allow a directory to be the target or source or dependency of a + Depends(), Ignore(), Precious() or SideEffect() call. + + From Gerard Patel: + + - Use the %{_mandir} macro when building our RPM package. + + From Marko Rauhamaa: + + - Have the closing message say "...terminated because of errors" if + there were any. + + From Anthony Roach: + + - On Win32 systems, only use "rm" to delete files if Cygwin is being + used. ("rm" doesn't understand Win32-format path names.) + + From Christoph Wiedemann: + + - Fix test/SWIG.py to find the Python include directory in all cases. + + - Fix a bug in detection of Qt installed on the local system. + + - Support returning Python 2.3 BooleanType values from Configure checks. + + - Provide an error message if someone mistakenly tries to call a + Configure check from within a Builder function. + + - Support calling a Builder when a Configure context is still open. + + - Handle interrupts better by eliminating all try:-except: blocks + which caught any and all exceptions, including KeyboardInterrupt. + + - Add a --duplicate= option to control how files are duplicated. + + + +RELEASE 0.92 - Wed, 20 Aug 2003 03:45:28 -0500 + + From Charles Crain and Gary Oberbrunner: + + - Fix Tool import problems with the Intel and PharLap linkers. + + From Steven Knight + + - Refactor the DictCmdGenerator class to be a Selector subclass. + + - Allow the DefaultEnvironment() function to take arguments and pass + them to instantiation of the default construction environment. + + - Update the Debian package so it uses Python 2.2 and more closely + resembles the currently official Debian packaging info. + + From Gerard Patel + + - When the yacc -d flag is used, take the .h file base name from the + target .c file, not the source (matching what yacc does). + + + +RELEASE 0.91 - Thu, 14 Aug 2003 13:00:44 -0500 + + From Chad Austin: + + - Support specifying a list of tools when calling Environment.Copy(). + + - Give a Value Nodes a timestamp of the system time when they're + created, so they'll work when using timestamp-based signatures. + + - Add a DefaultEnvironment() function that only creates a default + environment on-demand (for fetching source files, e.g.). + + - Portability fix for test/M4.py. + + From Steven Knight: + + - Tighten up the scons -H help output. + + - When the input yacc file ends in .yy and the -d flag is specified, + recognize that a .hpp file (not a .h file) will be created. + + - Make builder prefixes work correctly when deducing a target + from a source file name in another directory. + + - Documentation fixes: typo in the man page; explain up-front about + not propagating the external environment. + + - Use "cvs co -d" instead of "cvs co -p >" when checking out something + from CVS with a specified module name. This avoids zero-length + files when there is a checkout error. + + - Add an "sconsign" script to print the contents of .sconsign files. + + - Speed up maintaining the various lists of Node children by using + dictionaries to avoid "x in list" searches. + + - Cache the computed list of Node children minus those being Ignored + so it's only calculated once. + + - Fix use of the --cache-show option when building a Program() + (or using any other arbitrary action) by making sure all Action + instances have strfunction() methods. + + - Allow the source of Command() to be a directory. + + - Better error handling of things like raw TypeErrors in SConscripts. + + - When installing using "setup.py install --prefix=", suppress the + distutils warning message about adding the (incorrect) library + directory to your search path. + + - Correct the spelling of the "validater" option to "validator." + Add a DeprecatedWarning when the old spelling is used. + + - Allow a Builder's emitter to be a dictionary that maps source file + suffixes to emitter functions, using the suffix of the first file + in the source list to pick the right one. + + - Refactor the creation of the Program, *Object and *Library Builders + so that they're moved out of SCons.Defaults and created on demand. + + - Don't split SConscript file names on white space. + + - Document the SConscript function's "dirs" and "name" keywords. + + - Remove the internal (and superfluous) SCons.Util.argmunge() function. + + - Add /TP to the default CXXFLAGS for msvc, so it can compile all + of the suffixes we use as C++ files. + + - Allow the "prefix" and "suffix" attributes of a Builder to be + callable objects that return generated strings, or dictionaries + that map a source file suffix to the right prefix/suffix. + + - Support a MAXLINELINELENGTH construction variable on Win32 systems + to control when a temporary file is used for long command lines. + + - Make how we build .rpm packages not depend on the installation + locations from the distutils being used. + + - When deducing a target Node, create it directly from the first + source Node, not by trying to create the right string to pass to + arg2nodes(). + + - Add support for SWIG. + + From Bram Moolenaar: + + - Test portability fixes for FreeBSD. + + From Gary Oberbrunner: + + - Report the target being built in error messages when building + multiple sources from different extensions, or when the target file + extension can't be deduced, or when we don't have an action for a + file suffix. + + - Provide helpful error messages when the arguments to env.Install() + are incorrect. + + - Fix the value returned by the Node.prevsiginfo() method to conform + to a previous change when checking whether a node is current. + + - Supply a stack trace if the Taskmaster catches an exception. + + - When using a temporary file for a long link line on Win32 systems, + (also) print the command line that is being executed through the + temporary file. + + - Initialize the LIB environment variable when using the Intel + compiler (icl). + + - Documentation fixes: better explain the AlwaysBuild() function. + + From Laurent Pelecq: + + - When the -debug=pdb option is specified, use pdb.Pdb().runcall() to + call pdb directly, don't call Python recursively. + + From Ben Scott: + + - Add support for a platform-independent CPPDEFINES variable. + + From Christoph Wiedemann: + + - Have the g++ Tool actually use g++ in preference to c++. + + - Have the gcc Tool actually use gcc in preference to cc. + + - Add a gnutools.py test of the GNU tool chain. + + - Be smarter about linking: use $CC by default and $CXX only if we're + linking with any C++ objects. + + - Avoid SCons hanging when a piped command has a lot of output to read. + + - Add QT support for preprocessing .ui files into .c files. + + + +RELEASE 0.90 - Wed, 25 Jun 2003 14:24:52 -0500 + + From Chad Austin: + + - Fix the _concat() documentation, and add a test for it. + + - Portability fixes for non-GNU versions of lex and yacc. + + From Matt Balvin: + + - Fix handling of library prefixes when the subdirectory matches + the prefix. + + From Timothee Bessett: + + - Add an M4 Builder. + + From Charles Crain: + + - Use '.lnk' as the suffix on the temporary file for linking long + command lines (necessary for the Phar Lap linkloc linker). + + - Save non-string Options values as their actual type. + + - Save Options string values that contain a single quote correctly. + + - Save any Options values that are changed from the default + Environment values, not just ones changed on the command line or in + an Options file. + + - Make closing the Options file descriptor exception-safe. + + From Steven Knight: + + - SCons now enforces (with an error) that construction variables + must have the same form as valid Python identifiers. + + - Fix man page bugs: remove duplicate AddPostAction() description; + document no_import_lib; mention that CPPFLAGS does not contain + $_CPPINCFLAGS; mention that F77FLAGS does not contain $_F77INCFLAGS; + mention that LINKFLAGS and SHLINKFLAGS contains neither $_LIBFLAGS + nor $_LIBDIRFLAGS. + + - Eliminate a dependency on the distutils.fancy_getopt module by + copying and pasting its wrap_text() function directly. + + - Make the Script.Options() subclass match the underlying base class + implementation. + + - When reporting a target is up to date, quote the target like make + (backquote-quote) instead of with double quotes. + + - Fix handling of ../* targets when using -U, -D or -u. + + From Steve Leblanc: + + - Don't update the .sconsign files when run with -n. + + From Gary Oberbrunner: + + - Add support for the Intel C Compiler (icl.exe). + + From Anthony Roach + + - Fix Import('*'). + + From David Snopek + + - Fix use of SConf in paths with white space in them. + + - Add CheckFunc and CheckType functionality to SConf. + + - Fix use of SConf with Builders that return a list of nodes. + + From David Snopek and Christoph Wiedemann + + - Fix use of the SConf subsystem with SConscriptChdir(). + + From Greg Spencer + + - Check for the existence of MS Visual Studio on disk before using it, + to avoid getting fooled by leftover junk in the registry. + + - Add support for MSVC++ .NET. + + - Add support for MS Visual Studio project files (DSP, DSW, + SLN and VCPROJ files). + + From Christoph Wiedemann + + - SConf now works correctly when the -n and -q options are used. + + + +RELEASE 0.14 - Wed, 21 May 2003 05:16:32 -0500 + + From Chad Austin: + + - Use .dll (not .so) for shared libraries on Cygwin; use -fPIC + when compiling them. + + - Use 'rm' to remove files under Cygwin. + + - Add a PLATFORM variable to construction environments. + + - Remove the "platform" argument from tool specifications. + + - Propogate PYTHONPATH when running the regression tests so distutils + can be found in non-standard locations. + + - Using MSVC long command-line linking when running Cygwin. + + - Portability fixes for a lot of tests. + + - Add a Value Node class for dependencies on in-core Python values. + + From Allen Bierbaum: + + - Pass an Environment to the Options validator method, and + add an Options.Save() method. + + From Steve Christensen: + + - Add an optional sort function argument to the GenerateHelpText() + Options function. + + - Evaluate the "varlist" variables when computing the signature of a + function action. + + From Charles Crain: + + - Parse the source .java files for class names (including inner class + names) to figure out the target .class files that will be created. + + - Make Java support work with Repositories and SConscriptChdir(0). + + - Pass Nodes, not strings, to Builder emitter functions. + + - Refactor command-line interpolation and signature calculation + so we can use real Node attributes. + + From Steven Knight: + + - Add Java support (javac, javah, jar and rmic). + + - Propagate the external SYSTEMROOT environment variable into ENV on + Win32 systems, so external commands that use sockets will work. + + - Add a .posix attribute to PathList expansions. + + - Check out CVS source files using POSIX path names (forward slashes + as separators) even on Win32. + + - Add Node.clear() and Node.FS.Entry.clear() methods to wipe out a + Node's state, allowing it to be re-evaluated by continuous + integration build interfaces. + + - Change the name of the Set{Build,Content}SignatureType() functions + to {Target,Source}Signatures(). Deprecate the old names but support + them for backwards compatibility. + + - Add internal SCons.Node.FS.{Dir,File}.Entry() methods. + + - Interpolate the null string if an out-of-range subscript is used + for a construction variable. + + - Fix the internal Link function so that it properly links or copies + files in subsidiary BuildDir directories. + + - Refactor the internal representation of a single execution instance + of an action to eliminate redundant signature calculations. + + - Eliminate redundant signature calculations for Nodes. + + - Optimize out calling hasattr() before accessing attributes. + + - Say "Cleaning targets" (not "Building...") when the -c option is + used. + + From Damyan Pepper: + + - Quote the "Entering directory" message like Make. + + From Stefan Reichor: + + - Add support for using Ghostscript to convert Postscript to PDF files. + + From Anthony Roach: + + - Add a standalone "Alias" function (separate from an Environment). + + - Make Export() work for local variables. + + - Support passing a dictionary to Export(). + + - Support Import('*') to import everything that's been Export()ed. + + - Fix an undefined exitvalmap on Win32 systems. + + - Support new SetOption() and GetOption() functions for setting + various command-line options from with an SConscript file. + + - Deprecate the old SetJobs() and GetJobs() functions in favor of + using the new generic {Set,Get}Option() functions. + + - Fix a number of tests that searched for a Fortran compiler using the + external PATH instead of what SCons would use. + + - Fix the interaction of SideEffect() and BuildDir() so that (for + example) PDB files get put correctly in a BuildDir(). + + From David Snopek: + + - Contribute the "Autoscons" code for Autoconf-like checking for + the existence of libraries, header files and the like. + + - Have the Tool() function add the tool name to the $TOOLS + construction variable. + + From Greg Spencer: + + - Support the C preprocessor #import statement. + + - Allow the SharedLibrary() Builder on Win32 systems to be able to + register a newly-built dll using regsvr32. + + - Add a Builder for Windows type library (.tlb) files from IDL files. + + - Add an IDL scanner. + + - Refactor the Fortran, C and IDL scanners to share common logic. + + - Add .srcpath and .srcdir attributes to $TARGET and $SOURCE. + + From Christoph Wiedemann: + + - Integrate David Snopek's "Autoscons" code as the new SConf + configuration subsystem, including caching of values between + runs (using normal SCons dependency mechanisms), tests, and + documentation. + + + +RELEASE 0.13 - Mon, 31 Mar 2003 20:22:00 -0600 + + From Charles Crain: + + - Fix a bug when BuildDir(duplicate=0) is used and SConscript + files are called from within other SConscript files. + + - Support (older) versions of Perforce which don't set the Windows + registry. + + + +RELEASE 0.12 - Thu, 27 Mar 2003 23:52:09 -0600 + + From Charles Crain: + + - Added support for the Perforce source code management system. + + - Fix str(Node.FS) so that it returns a path relative to the calling + SConscript file's directory, not the top-level directory. + + - Added support for a separate src_dir argument to SConscript() + that allows explicit specification of where the source files + for an SConscript file can be found. + + - Support more easily re-usable flavors of command generators by + calling callable variables when strings are expanded. + + From Steven Knight: + + - Added an INSTALL construction variable that can be set to a function + to control how the Install() and InstallAs() Builders install files. + The default INSTALL function now copies, not links, files. + + - Remove deprecated features: the "name" argument to Builder objects, + and the Environment.Update() method. + + - Add an Environment.SourceCode() method to support fetching files + from source code systems. Add factory methods that create Builders + to support BitKeeper, CVS, RCS, and SCCS. Add support for fetching + files from RCS or SCCS transparently (like GNU Make). + + - Make the internal to_String() function more efficient. + + - Make the error message the same as other build errors when there's a + problem unlinking a target file in preparation for it being built. + + - Make TARGET, TARGETS, SOURCE and SOURCES reserved variable names and + warn if the user tries to set them in a construction environment. + + - Add support for Tar and Zip files. + + - Better documentation of the different ways to export variables to a + subsidiary SConscript file. Fix documentation bugs in a tools + example, places that still assumed SCons split strings on white + space, and typos. + + - Support fetching arbitrary files from the TARGETS or SOURCES lists + (e.g. ${SOURCES[2]}) when calculating the build signature of a + command. + + - Don't silently swallow exceptions thrown by Scanners (or other + exceptions while finding a node's dependent children). + + - Push files to CacheDir() before calling the superclass built() + method (which may clear the build signature as part of clearing + cached implicit dependencies, if the file has a source scanner). + (Bug reported by Jeff Petkau.) + + - Raise an internal error if we attempt to push a file to CacheDir() + with a build signature of None. + + - Add an explicit Exit() function for terminating early. + + - Change the documentation to correctly describe that the -f option + doesn't change to the directory in which the specified file lives. + + - Support changing directories locally with SConscript directory + path names relative to any SConstruct file specified with -f. + This allows you to build in another directory by simply changing + there and pointing at the SConstruct file in another directory. + + - Change the default SConscriptChdir() behavior to change to the + SConscript directory while it's being read. + + - Fix an exception thrown when the -U option was used with no + Default() target specified. + + - Fix -u so that it builds things in corresponding build directories + when used in a source directory. + + From Lachlan O'Dea: + + - Add SharedObject() support to the masm tool. + + - Fix WhereIs() to return normalized paths. + + From Jeff Petkau: + + - Don't copy a built file to a CacheDir() if it's already there. + + - Avoid partial copies of built files in a CacheDir() by copying + to a temporary file and renaming. + + From Anthony Roach: + + - Fix incorrect dependency-cycle errors when an Aliased source doesn't + exist. + + + +RELEASE 0.11 - Tue, 11 Feb 2003 05:24:33 -0600 + + From Chad Austin: + + - Add support for IRIX and the SGI MIPSPro tool chain. + + - Support using the MSVC tool chain when running Cygwin Python. + + From Michael Cook: + + - Avoid losing signal bits in the exit status from a command, + helping terminate builds on interrupt (CTRL+C). + + From Charles Crain: + + - Added new AddPreAction() and AddPostAction() functions that support + taking additional actions before or after building specific targets. + + - Add support for the PharLap ETS tool chain. + + From Steven Knight: + + - Allow Python function Actions to specify a list of construction + variables that should be included in the Action's signature. + + - Allow libraries in the LIBS variable to explicitly include the prefix + and suffix, even when using the GNU linker. + (Bug reported by Neal Becker.) + + - Use DOS-standard CR-LF line endings in the scons.bat file. + (Bug reported by Gary Ruben.) + + - Doc changes: Eliminate description of deprecated "name" keyword + argument from Builder definition (reported by Gary Ruben). + + - Support using env.Append() on BUILDERS (and other dictionaries). + (Bug reported by Bj=F6rn Bylander.) + + - Setting the BUILDERS construction variable now properly clears + the previous Builder attributes from the construction Environment. + (Bug reported by Bj=F6rn Bylander.) + + - Fix adding a prefix to a file when the target isn't specified. + (Bug reported by Esa Ilari Vuokko.) + + - Clean up error messages from problems duplicating into read-only + BuildDir directories or into read-only files. + + - Add a CommandAction.strfunction() method, and add an "env" argument + to the FunctionAction.strfunction() method, so that all Action + objects have strfunction() methods, and the functions for building + and returning a string both take the same arguments. + + - Add support for new CacheDir() functionality to share derived files + between builds, with related options --cache-disable, --cache-force, + and --cache-show. + + - Change the default behavior when no targets are specified to build + everything in the current directory and below (like Make). This + can be disabled by specifying Default(None) in an SConscript. + + - Revamp SCons installation to fix a case-sensitive installation + on Win32 systems, and to add SCons-specific --standard-lib, + --standalone-lib, and --version-lib options for easier user + control of where the libraries get installed. + + - Fix the ability to directly import and use Platform and Tool modules + that have been implicitly imported into an Environment(). + + - Add support for allowing an embedding interface to annotate a node + when it's created. + + - Extend the SConscript() function to accept build_dir and duplicate + keyword arguments that function like a BuildDir() call. + + From Steve Leblanc: + + - Fix the output of -c -n when directories are involved, so it + matches -c. + + From Anthony Roach: + + - Use a different shared object suffix (.os) when using gcc so shared + and static objects can exist side-by-side in the same directory. + + - Allow the same object files on Win32 to be linked into either + shared or static libraries. + + - Cache implicit cache values when using --implicit-cache. + + + +RELEASE 0.10 - Thu, 16 Jan 2003 04:11:46 -0600 + + From Derrick 'dman' Hudson: + + - Support Repositories on other file systems by symlinking or + copying files when hard linking won't work. + + From Steven Knight: + + - Remove Python bytecode (*.pyc) files from the scons-local packages. + + - Have FunctionActions print a description of what they're doing + (a representation of the Python call). + + - Fix the Install() method so that, like other actions, it prints + what would have happened when the -n option is used. + + - Don't create duplicate source files in a BuildDir when the -n + option is used. + + - Refactor the Scanner interface to eliminate unnecessary Scanner + calls and make it easier to write efficient scanners. + + - Added a "recursive" flag to Scanner creation that specifies the + Scanner should be invoked recursively on dependency files returned + by the scanner. + + - Significant performance improvement from using a more efficient + check, throughout the code, for whether a Node has a Builder. + + - Fix specifying only the source file to MultiStepBuilders such as + the Program Builder. (Bug reported by Dean Bair.) + + - Fix an exception when building from a file with the same basename as + the subdirectory in which it lives. (Bug reported by Gerard Patel.) + + - Fix automatic deduction of a target file name when there are + multiple source files specified; the target is now deduced from just + the first source file in the list. + + - Documentation fixes: better initial explanation of SConscript files; + fix a misformatted "table" in the StaticObject explanation. + + From Steven Knight and Steve Leblanc: + + - Fix the -c option so it will remove symlinks. + + From Steve Leblanc: + + - Add a Clean() method to support removing user-specified targets + when using the -c option. + + - Add a development script for running SCons through PyChecker. + + - Clean up things found by PyChecker (mostly unnecessary imports). + + - Add a script to use HappyDoc to create HTML class documentation. + + From Lachlan O'Dea: + + - Make the Environment.get() method return None by default. + + From Anthony Roach: + + - Add SetJobs() and GetJobs() methods to allow configuration of the + number of default jobs (still overridden by -j). + + - Convert the .sconsign file format from ASCII to a pickled Python + data structure. + + - Error message cleanups: Made consistent the format of error + messages (now all start with "scons: ***") and warning messages (now + all start with "scons: warning:"). Caught more cases with the "Do + not know how to build" error message. + + - Added support for the MinGW tool chain. + + - Added a --debug=includes option. + + + +RELEASE 0.09 - Thu, 5 Dec 2002 04:48:25 -0600 + + From Chad Austin: + + - Add a Prepend() method to Environments, to append values to + the beginning of construction variables. + + From Matt Balvin: + + - Add long command-line support to the "lib" Tool (Microsoft library + archiver), too. + + From Charles Crain: + + - Allow $$ in a string to be passed through as $. + + - Support file names with odd characters in them. + + - Add support for construction variable substition on scanner + directories (in CPPPATH, F77PATH, LIBPATH, etc.). + + From Charles Crain and Steven Knight: + + - Add Repository() functionality, including the -Y option. + + From Steven Knight: + + - Fix auto-deduction of target names so that deduced targets end + up in the same subdirectory as the source. + + - Don't remove source files specified on the command line! + + - Suport the Intel Fortran Compiler (ifl.exe). + + - Supply an error message if there are no command-line or + Default() targets specified. + + - Fix the ASPPCOM values for the GNU assembler. + (Bug reported by Brett Polivka.) + + - Fix an exception thrown when a Default() directory was specified + when using the -U option. + + - Issue a warning when -c can't remove a target. + + - Eliminate unnecessary Scanner calls by checking for the + existence of a file before scanning it. (This adds a generic + hook to check an arbitrary condition before scanning.) + + - Add explicit messages to tell when we're "Reading SConscript files + ...," "done reading SConscript files," "Building targets," and + "done building targets." Add a -Q option to supress these. + + - Add separate $SHOBJPREFIX and $SHOBJSUFFIX construction variables + (by default, the same as $OBJPREFIX and $OBJSUFFIX). + + - Add Make-like error messages when asked to build a source file, + and before trying to build a file that doesn't have all its source + files (including when an invalid drive letter is used on WIN32). + + - Add an scons-local-{version} package (in both .tar.gz and .zip + flavors) to help people who want to ship SCons as a stand-alone + build tool in their software packages. + + - Prevent SCons from unlinking files in certain situations when + the -n option is used. + + - Change the name of Tool/lib.py to Tool/mslib.py. + + From Steven Knight and Anthony Roach: + + - Man page: document the fact that Builder calls return Node objects. + + From Steve LeBlanc: + + - Refactor option processing to use our own version of Greg Ward's + Optik module, modified to run under Python 1.5.2. + + - Add a ParseConfig() command to modify an environment based on + parsing output from a *-config command. + + From Jeff Petkau: + + - Fix interpretation of '#/../foo' on Win32 systems. + + From Anthony Roach: + + - Fixed use of command lines with spaces in their arguments, + and use of Nodes with spaces in their string representation. + + - Make access and modification times of files in a BuildDir match + the source file, even when hard linking isn't available. + + - Make -U be case insensitive on Win32 systems. + + - Issue a warning and continue when finding a corrupt .sconsign file. + + - Fix using an alias as a dependency of a target so that if one of the + alias' dependencies gets rebuilt, the resulting target will, too. + + - Fix differently ordered targets causing unnecessary rebuilds + on case insensitive systems. + + - Use os.system() to execute external commands whenever the "env" + utility is available, which is much faster than fork()/exec(), + and fixes the -j option on several platforms. + + - Fix use of -j with multiple targets. + + - Add an Options() object for friendlier accomodation of command- + line arguments. + + - Add support for Microsoft VC++ precompiled header (.pch) files, + debugger (.pdb) files, and resource (.rc) files. + + - Don't compute the $_CPPINCFLAGS, $_F77INCFLAGS, $_LIBFLAGS and + $_LIBDIRFLAGS variables each time a command is executed, define + them so they're computed only as needed. Add a new _concat + function to the Environment that allows people to define their + own similar variables. + + - Fix dependency scans when $LIBS is overridden. + + - Add EnsurePythonVersion() and EnsureSConsVersion() functions. + + - Fix the overly-verbose stack trace on ListBuilder build errors. + + - Add a SetContentSignatureType() function, allowing use of file + timestamps instead of MD5 signatures. + + - Make -U and Default('source') fail gracefully. + + - Allow the File() and Dir() methods to take a path-name string as + the starting directory, in addition to a Dir object. + + - Allow the command handler to be selected via the SPAWN, SHELL + and ESCAPE construction variables. + + - Allow construction variables to be overridden when a Builder + is called. + + From sam th: + + - Dynamically check for the existence of utilities with which to + initialize Environments by default. + + + +RELEASE 0.08 - Mon, 15 Jul 2002 12:08:51 -0500 + + From Charles Crain: + + - Fixed a bug with relative CPPPATH dirs when using BuildDir(). + (Bug reported by Bob Summerwill.) + + - Added a warnings framework and a --warn option to enable or + disable warnings. + + - Make the C scanner warn users if files referenced by #include + directives cannot be found and --warn=dependency is specified. + + - The BUILDERS construction variable should now be a dictionary + that maps builder names to actions. Existing uses of lists, + and the Builder name= keyword argument, generate warnings + about use of deprecated features. + + - Removed the "shared" keyword argument from the Object and + Library builders. + + - Added separated StaticObject, SharedObject, StaticLibrary and + SharedLibrary builders. Made Object and Library synonyms for + StaticObject and StaticLibrary, respectively. + + - Add LIBS and LIBPATH dependencies for shared libraries. + + - Removed support for the prefix, suffix and src_suffix arguments + to Builder() to be callable functions. + + - Fix handling file names with multiple dots. + + - Allow a build directory to be outside of the SConstruct tree. + + - Add a FindFile() function that searches for a file node with a + specified name. + + - Add $CPPFLAGS to the shared-object command lines for g++ and gcc. + + From Charles Crain and Steven Knight: + + - Add a "tools=" keyword argument to Environment instantiation, + and a separate Tools() method, for more flexible specification + of tool-specific environment changes. + + From Steven Knight: + + - Add a "platform=" keyword argument to Environment instantiation, + and a separate Platform() method, for more flexible specification + of platform-specific environment changes. + + - Updated README instructions and setup.py code to catch an + installation failure from not having distutils installed. + + - Add descriptions to the -H help text for -D, -u and -U so + people can tell them apart. + + - Remove the old feature of automatically splitting strings + of file names on white space. + + - Add a dependency Scanner for native Fortran "include" statements, + using a new "F77PATH" construction variable. + + - Fix C #include scanning to detect file names with characters like + '-' in them. + + - Add more specific version / build output to the -v option. + + - Add support for the GNU as, Microsoft masm, and nasm assemblers. + + - Allow the "target" argument to a Builder call to be omitted, in + which case the target(s) are deduced from the source file(s) and the + Builder's specified suffix. + + - Add a tar archive builder. + + - Add preliminary support for the OS/2 Platform, including the icc + and ilink Tools. + + From Jeff Petkau: + + - Fix --implicit-cache if the scanner returns an empty list. + + From Anthony Roach: + + - Add a "multi" keyword argument to Builder creation that specifies + it's okay to call the builder multiple times for a target. + + - Set a "multi" on Aliases so multiple calls will append to an Alias. + + - Fix emitter functions' use of path names when using BuildDir or + in subdirectories. + + - Fix --implicit-cache causing redundant rebuilds when the header + file list changed. + + - Fix --implicit-cache when a file has no implicit dependencies and + its source is generated. + + - Make the drive letters on Windows always be the same case, so that + changes in the case of drive letters don't cause a rebuild. + + - Fall back to importing the SCons.TimeStamp module if the SCons.MD5 + module can't be imported. + + - Fix interrupt handling to guarantee that a single interrupt will + halt SCons both when using -j and not. + + - Fix .sconsign signature storage so that output files of one build + can be safely used as input files to another build. + + - Added a --debug=time option to print SCons execution times. + + - Print an error message if a file can't be unlinked before being + built, rather than just silently terminating the build. + + - Add a SideEffect() method that can be used to tell the build + engine that a given file is created as a side effect of building + a target. A file can be specified as a side effect of more than + one build comand, in which case the commands will not be executed + simultaneously. + + - Significant performance gains from not using our own version of + the inefficient stock os.path.splitext() method, caching source + suffix computation, code cleanup in MultiStepBuilder.__call__(), + and replicating some logic in scons_subst(). + + - Add --implicit-deps-changed and --implicit-deps-unchanged options. + + - Add a GetLaunchDir() function. + + - Add a SetBuildSignatureType() function. + + From Zed Shaw: + + - Add an Append() method to Environments, to append values to + construction variables. + + - Change the name of Update() to Replace(). Keep Update() as a + deprecated synonym, at least for now. + + From Terrel Shumway: + + - Use a $PYTHON construction variable, initialized to sys.executable, + when using Python to build parts of the SCons packages. + + - Use sys.prefix, not sys.exec_prefix, to find pdb.py. + + + +RELEASE 0.07 - Thu, 2 May 2002 13:37:16 -0500 + + From Chad Austin: + + - Changes to build SCons packages on IRIX (and other *NIces). + + - Don't create a directory Node when a file already exists there, + and vice versa. + + - Add 'dirs' and 'names' keyword arguments to SConscript for + easier specification of subsidiary SConscript files. + + From Charles Crain: + + - Internal cleanup of environment passing to function Actions. + + - Builders can now take arbitrary keyword arguments to create + attributes to be passed to: command generator functions, + FunctionAction functions, Builder emitter functions (below), + and prefix/suffix generator functions (below). + + - Command generator functions can now return ANYTHING that can be + converted into an Action (a function, a string, a CommandGenerator + instance, even an ActionBase instance). + + - Actions now call get_contents() with the actual target and source + nodes used for the build. + + - A new DictCmdGenerator class replaces CompositeBuilder to support + more flexible Builder behavior internally. + + - Builders can now take an emitter= keyword argument. An emitter + is a function that takes target, source, and env argument, then + return a 2-tuple of (new sources, new targets). The emitter is + called when the Builder is __call__'ed, allowing a user to modify + source and target lists. + + - The prefix, suffix and src_suffix Builder arguments now take a + callable as well a string. The callable is passed the Environment + and any extra Builder keyword arguments and is expected to return + the appropriate prefix or suffix. + + - CommandActions can now be a string, a list of command + argument + strings, or a list of commands (strings or lists). + + - Added shared library support. The Object and Library Builders now + take a "shared=1" keyword argument to specify that a shared object + or shared library should be built. It is an error to try to build + static objects into a shared library or vice versa. + + - Win32 support for .def files has been added. Added the Win32-specific + construction variables $WIN32DEFPREFIX, $WIN32DEFSUFFIX, + $WIN32DLLPREFIX and $WIN32IMPLIBPREFIX. When building a .dll, + the new construction variable $WIN32_INSERT_DEF, controls whether + the appropriately-named .def file is inserted into the target + list (if not already present). A .lib file is always added to + a Library build if not present in the list of targets. + + - ListBuilder now passes all targets to the action, not just the first. + + - Fix so that -c now deletes generated yacc .h files. + + - Builder actions and emitter functions can now be initialized, through + construction variables, to things other than strings. + + - Make top-relative '#/dir' lookups work like '#dir'. + + - Fix for relative CPPPATH directories in subsidiary SConscript files + (broken in 0.06). + + - Add a for_signature argument to command generators, so that + generators that need to can return distinct values for the + command signature and for executing the command. + + From Alex Jacques: + + - Create a better scons.bat file from a py2bat.py script on the Python + mailing list two years ago (modeled after pl2bat.pl). + + From Steven Knight: + + - Fix so that -c -n does *not* remove the targets! + + - Man page: Add a hierarchical libraries + Program example. + + - Support long MSVC linker command lines through a builder action + that writes to a temporary file and uses the magic MSVC "link @file" + argument syntax if the line is longer than 2K characters. + + - Fix F77 command-line options on Win32 (use /Fo instead of -o). + + - Use the same action to build from .c (lower case) and .C (upper + case) files on case-insensitive systems like Win32. + + - Support building a PDF file directly from a TeX or LaTeX file + using pdftex or pdflatex. + + - Add a -x option to runtest.py to specify the script being tested. + A -X option indicates it's an executable, not a script to feed + to the Python interpreter. + + - Add a Split() function (identical to SCons.Util.argmunge()) for use + in the next release, when Builders will no longer automatically split + strings on white space. + + From Steve Leblanc: + + - Add the SConscriptChdir() method. + + From Anthony Roach: + + - Fix --debug=tree when used with directory targets. + + - Significant internal restructuring of Scanners and Taskmaster. + + - Added new --debug=dtree option. + + - Fixes for --profile option. + + - Performance improvement in construction variable substitution. + + - Implemented caching of content signatures, plus added --max-drift + option to control caching. + + - Implemented caching of dependency signatures, enabled by new + --implicit-cache option. + + - Added abspath construction variable modifier. + + - Added $SOURCE variable as a synonym for $SOURCES[0]. + + - Write out .sconsign files on error or interrupt so intermediate + build results are saved. + + - Change the -U option to -D. Make a new -U that builds just the + targets from the local SConscript file. + + - Fixed use of sys.path so Python modules can be imported from + the SConscript directory. + + - Fix for using Aliases with the -u, -U and -D options. + + - Fix so that Nodes can be passed to SConscript files. + + From Moshe Zadka: + + - Changes for official Debian packaging. + + + +RELEASE 0.06 - Thu, 28 Mar 2002 01:24:29 -0600 + + From Charles Crain: + + - Fix command generators to expand construction variables. + + - Make FunctionAction arguments be Nodes, not strings. + + From Stephen Kennedy: + + - Performance: Use a dictionary, not a list, for a Node's parents. + + From Steven Knight: + + - Add .zip files to the packages we build. + + - Man page: document LIBS, fix a typo, document ARGUMENTS. + + - Added RANLIB and RANLIBFLAGS construction variables. Only use them + in ARCOM if there's a "ranlib" program on the system. + + - Add a configurable CFILESUFFIX for the Builder of .l and .y files + into C files. + + - Add a CXXFile Builder that turns .ll and .yy files into .cc files + (configurable via a CXXFILESUFFIX construction variable). + + - Use the POSIX-standard lex -t flag, not the GNU-specific -o flag. + (Bug reported by Russell Christensen.) + + - Fixed an exception when CPPPATH or LIBPATH is a null string. + (Bug reported by Richard Kiss.) + + - Add a --profile=FILE option to make profiling SCons easier. + + - Modify the new DVI builder to create .dvi files from LaTeX (.ltx + and .latex) files. + + - Add support for Aliases (phony targets). + + - Add a WhereIs() method for searching for path names to executables. + + - Add PDF and PostScript document builders. + + - Add support for compiling Fortran programs from a variety of + suffixes (a la GNU Make): .f, .F, .for, .FOR, .fpp and .FPP + + - Support a CPPFLAGS variable on all default commands that use the + C preprocessor. + + From Steve Leblanc: + + - Add support for the -U option. + + - Allow CPPPATH, LIBPATH and LIBS to be specified as white-space + separated strings. + + - Add a document builder to create .dvi files from TeX (.tex) files. + + From Anthony Roach: + + - Fix: Construction variables with values of 0 were incorrectly + interpolated as ''. + + - Support env['VAR'] to fetch construction variable values. + + - Man page: document Precious(). + + + +RELEASE 0.05 - Thu, 21 Feb 2002 16:50:03 -0600 + + From Chad Austin: + + - Set PROGSUFFIX to .exe under Cygwin. + + From Charles Crain: + + - Allow a library to specified as a command-line source file, not just + in the LIBS construction variable. + + - Compensate for a bug in os.path.normpath() that returns '' for './' + on WIN32. + + - More performance optimizations: cache #include lines from files, + eliminate unnecessary calls. + + - If a prefix or suffix contains white space, treat the resulting + concatenation as separate arguments. + + - Fix irregularities in the way we fetch DevStudio information from + the Windows registry, and in our registry error handling. + + From Steven Knight: + + - Flush stdout after print so it intermixes correctly with stderr + when redirected. + + - Allow Scanners to return a list of strings, and document how to + write your own Scanners. + + - Look up implicit (scanned) dependencies relative to the directory + of file being scanned. + + - Make writing .sconsign files more robust by first trying to write + to a temp file that gets renamed. + + - Create all of the directories for a list of targets before trying + to build any of the targets. + + - WIN32 portability fixes in tests. + + - Allow the list of variables exported to an SConscript file to be + a UserList, too. + + - Document the overlooked LIBPATH construction variable. + (Bug reported by Eicke Godehardt.) + + - Fix so that Ignore() ignores indirect, implicit dependencies + (included files), not just direct dependencies. + + - Put the man page in the Debian distribution. + + - Run HTML docs through tidy to clean up the HTML (for Konqueror). + + - Add preliminary support for Unicode strings. + + - Efficiency: don't scan dependencies more than once during the + walk of a tree. + + - Fix the -c option so it doesn't stop removing targets if one doesn't + already exist. + (Bug reported by Paul Connell.) + + - Fix the --debug=pdb option when run on Windows NT. + (Bug reported by Paul Connell.) + + - Add support for the -q option. + + From Steve Leblanc: + + - Add support for the -u option. + + - Add .cc and .hh file suffixes to the C Scanner. + + From Anthony Roach: + + - Make the scons script return an error code on failures. + + - Add support for using code to generate a command to build a target. + + + +RELEASE 0.04 - Wed, 30 Jan 2002 11:09:42 -0600 + + From Charles Crain: + + - Significant performance improvements in the Node.FS and + Scanner subsystems. + + - Fix signatures of binary files on Win32 systems. + + - Allow LIBS and LIBPATH to be strings, not just arrays. + + - Print a traceback if a Python-function builder throws an exception. + + From Steven Knight: + + - Fix using a directory as a Default(), and allow Default() to + support white space in file names for strings in arrays. + + - Man page updates: corrected some mistakes, documented various + missing Environment methods, alphabetized the construction + variables and other functions, defined begin and end macros for + the example sections, regularized white space separation, fixed + the use of Export() in the Multiple Variants example. + + - Function action fixes: None is now a successful return value. + Exceptions are now reported. Document function actions. + + - Add 'Action' and 'Scanner' to the global keywords so SConscript + files can use them too. + + - Removed the Wrapper class between Nodes and Walkers. + + - Add examples using Library, LIBS, and LIBPATH. + + - The C Scanner now always returns a sorted list of dependencies + so order changes don't cause unnecessary rebuilds. + + - Strip $(-$) bracketed text from command lines. Use this to + surround $_INCDIRS and $_LIBDIRS so we don't rebuild in response + to changes to -I or -L options. + + - Add the Ignore() method to ignore dependencies. + + - Provide an error message when a nonexistent target is specified + on the command line. + + - Remove targets before building them, and add an Environment + Precious() method to override that. + + - Eliminate redundant calls to the same builder when the target is a + list of targets: Add a ListBuilder class that wraps Builders to + handle lists atomically. Extend the Task class to support building + and updating multiple targets in a single Task. Simplify the + interface between Task and Taskmaster. + + - Add a --debug=pdb option to re-run SCons under the Python debugger. + + - Only compute a build signature once for each node. + + - Changes to our sys.path[] manipulation to support installation into + an arbitrary --prefix value. + + From Steve Leblanc: + + - Add var=value command-line arguments. + + + +RELEASE 0.03 - Fri, 11 Jan 2002 01:09:30 -0600 + + From Charles Crain: + + - Performance improvements in the Node.FS and Sig.Calculator classes. + + - Add the InstallAs() method. + + - Execute commands through an external interpreter (sh, cmd.exe, or + command.com) to handle redirection metacharacters. + + - Allow the user to supply a command handler. + + From Steven Knight: + + - Search both /usr/lib and /usr/local/lib for scons directories by + adding them both to sys.path, with whichever is in sys.prefix first. + + - Fix interpreting strings of multiple white-space separated file names + as separate file names, allowing prefixes and suffixes to be appended + to each individually. + + - Refactor to move CompositeBuilder initialization logic from the + factory wrapper to the __init__() method, and allow a Builder to + have both an action and a src_builder (or array of them). + + - Refactor BuilderBase.__call__() to separate Node creation/lookup + from initialization of the Node's builder information. + + - Add a CFile Builder object that supports turning lex (.l) and + yacc (.y) files into .c files. + + - Document: variable interpretation attributes; how to propogate + the user's environment variables to executed commands; how to + build variants in multiple BuildDirs. + + - Collect String, Dict, and List type-checking in common utility + routines so we can accept User{String,Dict,List}s all over. + + - Put the Action factory and classes into their own module. + + - Use one CPlusPlusAction in the Object Builder's action dictionary, + instead of letting it create multiple identical instances. + + - Document the Install() and InstallAs() methods. + + From Steve Leblanc: + + - Require that a Builder be given a name argument, supplying a + useful error message when it isn't. + + From Anthony Roach: + + - Add a "duplicate" keyword argument to BuildDir() that can be set + to prevent linking/copying source files into build directories. + + - Add a "--debug=tree" option to print an ASCII dependency tree. + + - Fetch the location of the Microsoft Visual C++ compiler(s) from + the Registry, instead of hard-coding the location. + + - Made Scanner objects take Nodes, not path names. + + - Have the C Scanner cache the #include file names instead of + (re-)scanning the file each time it's called. + + - Created a separate class for parent "nodes" of file system roots, + eliminating the need for separate is-parent-null checks everywhere. + + - Removed defined __hash__() and __cmp() methods from FS.Entry, in + favor of Python's more efficient built-in identity comparisons. + + + +RELEASE 0.02 - Sun, 23 Dec 2001 19:05:09 -0600 + + From Charles Crain: + + - Added the Install(), BuildDir(), and Export() methods. + + - Fix the -C option by delaying setting the top of the FS tree. + + - Avoid putting the directory path on the libraries in the LIBS + construction variable. + + - Added a GetBuildPath() method to return the full path to the + Node for a specified string. + + - Fixed variable substitution in CPPPATH and LIBPATH. + + From Steven Knight: + + - Fixed the version comment in the scons.bat (the UNIX geek used + # instead of @rem). + + - Fix to setup.py so it doesn't require a sys.argv[1] argument. + + - Provide make-like warning message for "command not found" and + similar errors. + + - Added an EXAMPLES section to the man page. + + - Make Default() targets properly relative to their SConscript + file's subdirectory. + + From Anthony Roach: + + - Documented CXXFLAGS, CXXCOM, and CPPPATH. + + - Fixed SCONS_LIB_DIR to work as documented. + + - Made Default() accept Nodes as arguments. + + - Changed Export() to make it easier to use. + + - Added the Import() and Return() methods. + + + +RELEASE 0.01 - Thu Dec 13 19:25:23 CST 2001 + +A brief overview of important functionality available in release 0.01: + + - C and C++ compilation on POSIX and Windows NT. + + - Automatic scanning of C/C++ source files for #include dependencies. + + - Support for building libraries; setting construction variables + allows creation of shared libraries. + + - Library and C preprocessor search paths. + + - File changes detected using MD5 signatures. + + - User-definable Builder objects for building files. + + - User-definable Scanner objects for scanning for dependencies. + + - Parallel build (-j) support. + + - Dependency cycles detected. + + - Linux packages available in RPM and Debian format. + + - Windows installer available. diff --git a/src/LICENSE.txt b/src/LICENSE.txt new file mode 100644 index 0000000..67f1906 --- /dev/null +++ b/src/LICENSE.txt @@ -0,0 +1,20 @@ +Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/MANIFEST.in b/src/MANIFEST.in new file mode 100644 index 0000000..e69de29 diff --git a/src/README.txt b/src/README.txt new file mode 100644 index 0000000..c347136 --- /dev/null +++ b/src/README.txt @@ -0,0 +1,273 @@ +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# src/README.txt 4577 2009/12/27 19:44:43 scons + + + SCons - a software construction tool + + Version 1.2.0.d20091224 + + +This is SCons, a tool for building software (and other files). SCons is +implemented in Python, and its "configuration files" are actually Python +scripts, allowing you to use the full power of a real scripting language +to solve build problems. You do not, however, need to know Python to +use SCons effectively. + +See the RELEASE.txt file for notes about this specific release, +including known problems. See the CHANGES.txt file for a list of +changes since the previous release. + + +LATEST VERSION +============== + +Before going further, you can check that this package you have is +the latest version by checking the SCons download page at: + + http://www.scons.org/download.html + + +EXECUTION REQUIREMENTS +====================== + +Running SCons requires Python version 1.5.2 or later. There should be +no other dependencies or requirements to run SCons. (There is, however, +an additional requirement to *install* SCons from this particular +package; see the next section.) + +By default, SCons knows how to search for available programming tools +on various systems--see the SCons man page for details. You may, +of course, override the default SCons choices made by appropriate +configuration of Environment construction variables. + + +INSTALLATION REQUIREMENTS +========================= + +Installing SCons from this package requires the Python distutils +package. The distutils package was not shipped as a standard part of +Python until Python version 1.6, so if your system is running Python +1.5.2, you may not have distutils installed. If you are running +Python version 1.6 or later, you should be fine. + +NOTE TO RED HAT USERS: Red Hat shipped Python 1.5.2 as the default all +the way up to Red Hat Linux 7.3, so you probably do *not* have distutils +installed, unless you have already done so manually or are running Red +Hat 8.0 or later. + +In this case, your options are: + + -- (Recommended.) Install from a pre-packaged SCons package that + does not require distutils: + + Red Hat Linux scons-1.2.0.d20091224-1.noarch.rpm + + Debian GNU/Linux scons_1.2.0.d20091224-1_all.deb + (or use apt-get) + + Windows scons-1.2.0.d20091224.win32.exe + + -- (Optional.) Download the latest distutils package from the + following URL: + + http://www.python.org/sigs/distutils-sig/download.html + + Install the distutils according to the instructions on the page. + You can then proceed to the next section to install SCons from + this package. + + +INSTALLATION +============ + +Assuming your system satisfies the installation requirements in the +previous section, install SCons from this package simply by running the +provided Python-standard setup script as follows: + + # python setup.py install + +By default, the above command will do the following: + + -- Install the version-numbered "scons-1.2.0.d20091224" and "sconsign-1.2.0.d20091224" + scripts in the default system script directory (/usr/bin or + C:\Python*\Scripts, for example). This can be disabled by + specifying the "--no-version-script" option on the command + line. + + -- Install scripts named "scons" and "sconsign" scripts in the + default system script directory (/usr/bin or C:\Python*\Scripts, + for example). This can be disabled by specifying the + "--no-scons-script" option on the command line, which is useful + if you want to install and experiment with a new version before + making it the default on your system. + + On UNIX or Linux systems, you can have the "scons" and "sconsign" + scripts be hard links or symbolic links to the "scons-1.2.0.d20091224" and + "sconsign-1.2.0.d20091224" scripts by specifying the "--hardlink-scons" + or "--symlink-scons" options on the command line. + + -- Install "scons-1.2.0.d20091224.bat" and "scons.bat" wrapper scripts in the + Python prefix directory on Windows (C:\Python*, for example). + This can be disabled by specifying the "--no-install-bat" option + on the command line. + + On UNIX or Linux systems, the "--install-bat" option may be + specified to have "scons-1.2.0.d20091224.bat" and "scons.bat" files + installed in the default system script directory, which is useful + if you want to install SCons in a shared file system directory + that can be used to execute SCons from both UNIX/Linux and + Windows systems. + + -- Install the SCons build engine (a Python module) in an + appropriate version-numbered SCons library directory + (/usr/lib/scons-1.2.0.d20091224 or C:\Python*\scons-1.2.0.d20091224, for example). + See below for more options related to installing the build + engine library. + + -- Install the troff-format man pages in an appropriate directory + on UNIX or Linux systems (/usr/share/man/man1 or /usr/man/man1, + for example). This can be disabled by specifying the + "--no-install-man" option on the command line. The man pages + can be installed on Windows systems by specifying the + "--install-man" option on the command line. + +Note that, by default, SCons does not install its build engine library +in the standard Python library directories. If you want to be able to +use the SCons library modules (the build engine) in other Python +scripts, specify the "--standard-lib" option on the command line, as +follows: + + # python setup.py install --standard-lib + +This will install the build engine in the standard Python library +directory (/usr/lib/python*/site-packages or +C:\Python*\Lib\site-packages). + +Alternatively, you can have SCons install its build engine library in a +hard-coded standalone library directory, instead of the default +version-numbered directory, by specifying the "--standalone-lib" option +on the command line, as follows: + + # python setup.py install --standalone-lib + +This is usually not recommended, however. + +Note that, to install SCons in any of the above system directories, +you should have system installation privileges (that is, "root" or +"Administrator") when running the setup.py script. If you don't have +system installation privileges, you can use the --prefix option to +specify an alternate installation location, such as your home directory: + + $ python setup.py install --prefix=$HOME + +This will install SCons in the appropriate locations relative to +$HOME--that is, the scons script itself $HOME/bin and the associated +library in $HOME/lib/scons, for example. + + +DOCUMENTATION +============= + +See the RELEASE.txt file for notes about this specific release, +including known problems. See the CHANGES.txt file for a list of +changes since the previous release. + +The scons.1 man page is included in this package, and contains a section +of small examples for getting started using SCons. + +Additional documentation for SCons is available at: + + http://www.scons.org/doc.html + + +LICENSING +========= + +SCons is distributed under the MIT license, a full copy of which is +available in the LICENSE.txt file. The MIT license is an approved Open +Source license, which means: + + This software is OSI Certified Open Source Software. OSI + Certified is a certification mark of the Open Source Initiative. + +More information about OSI certifications and Open Source software is +available at: + + http://www.opensource.org/ + + +REPORTING BUGS +============== + +Please report bugs by following the detailed instructions on our Bug +Submission page: + + http://scons.tigris.org/bug-submission.html + +You can also send mail to the SCons developers' mailing list: + + dev@scons.tigris.org + +But even if you send email to the mailing list please make sure that you +ALSO submit a bug report to the project page bug tracker, because bug +reports in email often get overlooked in the general flood of messages. + + +MAILING LISTS +============= + +An active mailing list for users of SCons is available. You may send +questions or comments to the list at: + + users@scons.tigris.org + +You may subscribe to the mailing list by sending email to: + + users-subscribe@scons.tigris.org + +There is also a low-volume mailing list available for announcements +about SCons. Subscribe by sending email to: + + announce-subscribe@scons.tigris.org + +There are other mailing lists available for SCons developers, for +notification of SCons code changes, and for notification of updated +bug reports and project documents. Please see our mailing lists page +for details. + + +DONATIONS +========= + +If you find SCons helpful, please consider making a donation (of cash, +software, or hardware) to support continued work on the project. +Information is available at: + + http://www.scons.org/donate.html + + +FOR MORE INFORMATION +==================== + +Check the SCons web site at: + + http://www.scons.org/ + + +AUTHOR INFO +=========== + +Steven Knight +knight at baldmt dot com +http://www.baldmt.com/~knight/ + +With plenty of help from the SCons Development team: + Chad Austin + Charles Crain + Steve Leblanc + Greg Noel + Gary Oberbrunner + Anthony Roach + Greg Spencer + Christoph Wiedemann + diff --git a/src/RELEASE.txt b/src/RELEASE.txt new file mode 100644 index 0000000..94c7f26 --- /dev/null +++ b/src/RELEASE.txt @@ -0,0 +1,1016 @@ +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# src/RELEASE.txt 4577 2009/12/27 19:44:43 scons + + + SCons - a software construction tool + + Release Notes + + +This is SCons, a tool for building software (and other files). SCons is +implemented in Python, and its "configuration files" are actually Python +scripts, allowing you to use the full power of a real scripting language +to solve build problems. You do not, however, need to know Python to +use SCons effectively. + +So that everyone using SCons can help each other learn how to use it +more effectively, please sign up for the scons-users mailing list at: + + http://lists.sourceforge.net/lists/listinfo/scons-users + + + +RELEASE 1.2.0.d20091224 - Thu, 24 Dec 2009 17:20:55 -0800 + + Please consult the CHANGES.txt file for a list of specific changes + since last release. + + Please note the following important changes scheduled for the next + minor release (1.3.0): + + -- DEPRECATED FEATURES WILL GENERATE MANDATORY WARNINGS IN 1.3.0 + + In keeping with our deprecation cycle, the following deprecated + features will still be supported in 1.3.0 but will generate + mandatory, non-disableable warnings: + + -- Support for Python versions 1.5, 1.6, 2.0, 2.1, 2.2, and 2.3. + -- The overrides= keyword argument to the Builder() call. + -- The scanner= keyword argument to the Builder() call. + -- The BuildDir() function and env.BuildDir() method. + -- The env.Copy() method. + -- The SourceSignatures() function and + env.SourceSignatures() method. + -- The TargetSignatures() function and + env.TargetSignatures() method. + -- The Sig module (now an unnused stub). + -- The --debug=dtree, --debug=stree and --debug=tree options. + -- The --debug=nomemoizer option. + -- The Options object and the related BoolOption(), + EnumOption(), ListOption(), PackageOption() and + PathOption() functions. + + The mandatory warnings will be issued in order to make sure + users of 1.3.0 notice *prior* to the release of SCons 2.0.0, that + these features will be removed. In SCons 2.0.0 these features + will no longer work at all, and will instead generate specific + fatal errors when anyone tries to use them. + + Please note the following important changes since release 1.2.0: + -- Visual Studio/Visual C++ support has been change significantly + + Support for HOST_OS,HOST_ARCH,TARGET_OS, TARGET_ARCH has been + added to allow specifying different target arch than the host + system. This is only supported for Visual Studio/Visual C++ + at this time. + + -- Support for Latex glosseries and acronyms has been added + + -- VISUAL C/C++ PRECOMPILED HEADERS WILL BE REBUILT + + Precompiled header files built with Visual C/C++ will be + rebuilt after upgrading from 1.2.0 to a later release. + + This rebuild is normal and will occur because the command line + defined by the $PCHCOM construction variable has had the $CCFLAGS + variable added, and has been rearranged to put the "/Fo" output + flag towards the beginning of the line, consistent with the + related command lines for $CCCOM, $CXXCOM, etc. + + -- CHANGES TO SOME LINKER COMMAND LINES WILL CAUSE RELINKING + + Changes to the command line definitions for the Microsoft link.exe + linker, the OS/2 ilink linker and the Phar Lap linkloc linker + will cause targets built with those tools be to be rebuilt after + upgrading from 1.2.0 to a later release. + + This relink is normal and will occur because the command lines for + these tools have been redefined to remove unnecessary nested $( + and $) character strings. + + Please note the following important changes since release 1.1.0: + + -- THE $CHANGED_SOURCES, $CHANGED_TARGETS, $UNCHANGED_SOURCES + AND $UNCHANGED_TARGETS VARIABLES WILL BECOME RESERVED + + A future release (probably 1.3.0) will make the construction + variable names $CHANGED_SOURCES, $CHANGED_TARGETS, + $UNCHANGED_SOURCES and $UNCHANGED_TARGETS into reserved + construction variable names controlled by SCons itself (like + the current $SOURCE, $TARGETS, etc.). + + Setting these variable names in the current release will generate + a warning but still set the variables. When they become reserved + variable names, they will generate a different warning message + and attempts to set these variables will be ignored. + + SCons configurations that happen to use these variable names + should be changed to use different variable names, in order + to ensure that the configuration continues to work with future + versions of SCons. + + -- THE Options OBJECT AND RELATED FUNCTIONS NOW GENERATE WARNINGS + + Use of the Options object, and related functions BoolOption(), + EnumOption(), ListOption(), PackageOption() and PathOption() + were announced as deprecated in release 0.98.1. Since then, + however, no warning messages were ever implemented for the + use of these deprecated functions. + + By default, release 1.2.0 prints warning messages when these + deprecated features are used. Warnings about all deprecated + features may be suppressed by using the --warn=no-deprecated + command-line option: + + $ scons --warn=no-deprecated + + Or by using the appropriate SetOption() call in any SConscript + file: + + SetOption('warn', 'no-deprecated') + + You may optionally disable just warnings about the deprecation + of the Options object and its related functions as follows: + + SetOption('warn', 'no-deprecated-options') + + The current plan is for these warnings to become mandatory + (non-suppressible) in release 1.3.0, and for the use of Options + and its related functions to generate errors in release 2.0. + + Please note the following important changes since release 0.98.4: + + -- scons.bat NOW RETURNS THE REAL SCONS EXIT STATUS + + The scons.bat script shipped with SCons used to exit with + a status of 1 when it detected any failed (non-zero) exit + status from the underlying Python execution of SCons itself. + The scons.bat script now exits with the actual status + returned by SCons. + + -- SCONS NOW WARNS WHEN TRYING TO LINK C++ AND FORTRAN OBJECT FILES + + Some C++ toolchains do not understand Fortran runtimes and create + unpredictable executables when linking C++ and Fortran object + files together. SCons now issues a warning if you try to link + C++ and Fortran object files into the same executable: + + scons: warning: Using $CXX to link Fortran and C++ code together. + This may generate a buggy executable if the '/usr/bin/gcc' + compiler does not know how to deal with Fortran runtimes. + + The warning may be suppressed with either the --warning=no-link + or --warning=no-fortran-cxx-mix command line options, or by + adding either of the following lines to a SConscript file: + + SetOption('warn', 'no-link') + SetOption('warn', 'no-fortran-cxx-mix') + + Please note the following important changes since release 0.98: + + -- SCONS NO LONGER SETS THE GNU TOOLCHAIN -fPIC FLAG IN $SHCXXFLAGS + + The GNU toolchain support in previous versions of SCons would + add the -fPIC flag to the $SHCXXFLAGS construction variable. + The -fPIC flag has been now been removed from the default + $SHCXXFLAGS setting. Instead, the $SHCXXCOM construction variable + (the default SCons command line for compiling shared objects + from C++ source files) has been changed to add the $SHCCFLAGS + variable, which contains the -fPIC flag. + + This change was made in order to make the behavior of the default + C++ compilation line including $SHCCFLAGS consistent with the + default C compilation line including $CCFLAGS. + + This change should have no impact on configurations that use + the default $SHCXXCOM command line. It may have an impact on + configurations that were using the default $SHCXXFLAGS value + *without* the $SHCCFLAGS variable to get the -fPIC flag into a + custom command line. You can fix these by adding the $SHCCFLAGS + to the custom command line. + + Adding $SHCCFLAGS is backwards compatible with older SCons + releases, although it might cause the -fPIC flag to be repeated + on the command line if you execute it on an older version of + SCons that sets -fPIC in both the $SHCCLAFGS and $SHCXXFLAGS + variables. Duplicating the -fPIC flag on the g++ command line + will not cause any compilation problems, but the change to the + command line may cause SCons to rebuild object files. + + -- FORTRAN NOW COMPILES .f FILES WITH gfortran BY DEFAULT + + The Fortran Tool modules have had a major overhaul with the intent + of making them work as-is for most configurations. In general, + most configurations that use default settings should not see + any noticeable difference. + + One configuration that has changed is if you have both a gfortran + and g77 compiler installed. In this case, previous versions of + SCons would, by default, use g77 by default to compile files with + a .f suffix, while SCons 0.98.1 will use the gfortran compiler + by default. The old behavior may be preserved by explicitly + initializing construction environments with the 'g77' Tool module: + + env = Environment(tools = ['g77', 'default']) + + The above code is backwards compatible to older versions of SCons. + + If you notice any other changes in the behavior of default + Fortran support, please let us know so we can document them in + these release notes for other users. + + Please note the following important changes since release 0.97.0d20071212: + + -- SUPPORT FOR PYTHON VERSIONS BEFORE 2.2 IS NOW DEPRECATED + + SCons now prints the following warning when it is run by any + Python 1.5, 2.0 or 2.1 release or sub-release: + + scons: warning: Support for pre-2.2 Python (VERSION) is deprecated. + If this will cause hardship, contact dev@scons.tigris.org. + + You may disable all warnings about deprecated features by adding + the option "--warn=no-deprecated" to the command line or to the + $SCONSFLAGS environment variable: + + $ scons --warn=no-deprecated + + Using '--warn=no-deprecated' is compatible with earlier versions + of SCons. + + You may also, as of this version of SCons, disable all warnings + about deprecated features by adding the following to any + SConscript file: + + SetOption('warn', 'no-deprecated') + + You may disable only the specific warning about running under + a deprecated Python version by adding the following to any + SConscript file: + + SetOption('warn', 'no-python-version') + + The warning may also be suppressed on the command line: + + $ scons --warn=no-python-version + + Or by specifying the --warn=no-python-version option in the + $SCONSFLAGS environment variable. + + Using SetOption('warn', ...), and the 'no-python-version' + command-line option for suppressing this specific warning, + are *not* backwards-compatible to earlier versions of SCons. + + -- THE env.Copy() METHOD IS NOW OFFICIALLY DEPRECATED + + The env.Copy() method is now officially deprecated and will + be removed in a future release. Using the env.Copy() method + now generates the following message: + + scons: warning: The env.Copy() method is deprecated; use the env.Clone() method instead. + + You may disable all warnings about deprecated features by adding + the option "--warn=no-deprecated" to the command line or to the + $SCONSFLAGS environment variable: + + $ scons --warn=no-deprecated + + Using '--warn=no-deprecated' is compatible with earlier versions + of SCons. + + You may also, as of this version of SCons, disable all warnings + about deprecated features by adding the following to any + SConscript file: + + SetOption('warn', 'no-deprecated') + + You may disable only the specific warning about the deprecated + env.Copy() method by adding the following to any SConscript + file: + + SetOption('warn', 'no-deprecated-copy') + + The warning may also be suppressed on the command line: + + $ scons --warn=no-deprecated-copy + + Or by specifying the --warn=no-deprecated-copy option in the + $SCONSFLAGS environment variable. + + Using SetOption('warn', ...), and the 'no-deprecated-copy' + command-line option for suppressing this specific warning, + are *not* backwards-compatible to earlier versions of SCons. + + -- THE --debug=dtree, --debug=stree AND --debug=tree OPTIONS ARE DEPRECATED + + The --debug=dtree, --debug=stree and --debug=tree methods + are now officially deprecated and will be removed in a + future release. Using these options now generate a warning + message recommending use of the --tree=derived, --tree=all,status + and --tree=all options, respectively. + + You may disable these warnings, and all warnings about + deprecated features, by adding the option "--warn=no-deprecated" + to the command line or to the $SCONSFLAGS environment + variable: + + $ scons --warn=no-deprecated + + Using '--warn=no-deprecated' is compatible with earlier versions + of SCons. + + -- THE TargetSignatures() AND SourceSignatures() FUNCTIONS ARE DEPRECATED + + The TargetSignatures() and SourceSignatures() functions, + and their corresponding env.TargetSignatures() and + env.SourceSignatures() methods, are now officially deprecated + and will be be removed in a future release. Using ahy of + these functions or methods now generates a message + similar to the following: + + scons: warning: The env.TargetSignatures() method is deprecated; + convert your build to use the env.Decider() method instead. + + You may disable all warnings about deprecated features by adding + the option "--warn=no-deprecated" to the command line or to the + $SCONSFLAGS environment variable: + + $ scons --warn=no-deprecated + + Using '--warn=no-deprecated' is compatible with earlier versions + of SCons. + + You may also, as of this version of SCons, disable all warnings + about deprecated features by adding the following to any + SConscript file: + + SetOption('warn', 'no-deprecated') + + You may disable only the specific warning about the use of + TargetSignatures() or SourceSignatures() by adding the + following to any SConscript file: + + SetOption('warn', 'no-deprecated-target-signatures') + SetOption('warn', 'no-deprecated-source-signatures') + + The warnings may also be suppressed on the command line: + + $ scons --warn=no-deprecated-target-signatures --warn=no-deprecated-source-signatures + + Or by specifying these options in the $SCONSFLAGS environment + variable. + + Using SetOption('warn', ...), or the command-line options + for suppressing these warnings, is *not* backwards-compatible + to earlier versions of SCons. + + -- File(), Dir() and Entry() NOW RETURN A LIST WHEN THE INPUT IS A SEQUENCE + + Previously, if these methods were passed a list, the list was + substituted and stringified, then passed as a single string to + create a File/Dir/Entry Node. This rarely if ever worked with + more than one element in the list. They now return a list of + Nodes when passed a list. + + One case that works differently now is a passing in a + single-element sequence; that formerly was stringified + (returning its only element) and then a single Node would be + returned. Now a single-element list containing the Node will + be returned, for consistency. + + -- THE env.subst() METHOD NOW RETURNS A LIST WHEN THE INPUT IS A SEQUENCE + + The env.subst() method now returns a list with the elements + expanded when given a list as input. Previously, the env.subst() + method would always turn its result into a string. + + This behavior was changed because it interfered with being able + to include things like lists within the expansion of variables + like $CPPPATH and then have SCons understand that the elements + of the "internal" lists still needed to be treated separately. + This would cause a $CPPPATH list like ['subdir1', 'subdir'] + to show up in a command line as "-Isubdir1 subdir". + + -- THE Jar() BUILDER NOW USES THE Java() BUILDER CLASSDIR BY DEFAULT + + By default, the Jar() Builder will now use the class directory + specified when the Java() builder is called. So the following + input: + + classes = env.Java('classes', 'src') + env.Jar('out.jar', classes) + + Will cause "-C classes" to be passed the "jar" command invocation, + and the Java classes in the "out.jar" file will not be prefixed + "classes/". + + Explicitly setting the $JARCHDIR variable overrides this default + behavior. The old behavior of not passing any -C option to the + "jar" command can be preserved by explicitly setting $JARCHDIR + to None: + + env = Environment(JARCHDIR = None) + + The above setting is compatible with older versions of SCons. + + Please note the following important changes since release 0.97.0d20070918: + + -- SCons REDEFINES PYTHON open() AND file() ON Windows TO NOT PASS + ON OPEN FILE HANDLES TO CREATED PROCESSES + + On Windows systems, SCons now redefines the Python open() + and file() functions so that, if the Python Win32 extensions + are available, the file handles for any opened files will *not* + be inherited by subprocesses, such as the spawned compilers and + other tools invoked to build the software. + + This prevents certain race conditions where a file handle for + a file opened by Python (either in a Python function action, + or directly in a SConscript file) could be inherited and help + open by a subprocess, interfering with the ability of other + processes to create or modify the file. + + In general, this should not cause problems for the vast majority + of configurations. The only time this would be a problem would be + in the unlikely event that a process spawned by SCons specifically + *expected* to use an inherited file handle opened by SCons. + + If the Python Win32 extensions are not installed or are an + earlier version that does not have the ability to disable file + handle inheritance, SCons will print a warning message when the + -j option is used. The warning message may be suppressed by + specifying --warn=no-parallel-support. + + Please note the following important changes since release 0.97.0d20070809: + + -- "content" SIGNATURES ARE NOW THE DEFAULT BEHAVIOR + + The default behavior of SCons is now to use the MD5 checksum of + all file contents to decide if any files have changed and should + cause rebuilds of their source files. This means that SCons may + decide not to rebuild "downstream" targets if a a given input + file is rebuilt to the exact same contents as the last time. + The old behavior may preserved by explicity specifying: + + TargetSignatures("build") + + In any of your SConscript files. + + -- TARGETS NOW IMPLICITLY DEPEND ON THE COMMAND THAT BUILDS THEM + + For all targets built by calling external commands (such as a + compiler or other utility), SCons now adds an implicit dependency + on the command(s) used to build the target. + + This will cause rebuilds of all targets built by external commands + when running SCons in a tree built by previous version of SCons, + in order to update the recorded signatures. + + The old behavior of not having targets depend on the external + commands that build them can be preserved by setting a new + $IMPLICIT_COMMAND_DEPENDENCIES construction variable to a + non-True value: + + env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0) + + or by adding Ignore() calls for any targets where the behavior + is desired: + + Ignore('/usr/bin/gcc', 'foo.o') + + Both of these settings are compatible with older versions + of SCons. + + -- CHANGING SourceSignature() MAY CAUSE "UNECESSARY" REBUILDS + + If you change the SourceSignature() value from 'timestamp' to + 'MD5', SCons will now rebuild targets that were already up-to-date + with respect to their source files. + + This will happen because SCons did not record the content + signatures of the input source files when the target was last + built--it only recorded the timestamps--and it must record them + to make sure the signature information is correct. However, + the content of source files may have changed since the last + timestamp build was performed, and SCons would not have any way to + verify that. (It would have had to open up the file and record + a content signature, which is one of the things you're trying to + avoid by specifying use of timestamps....) So in order to make + sure the built targets reflect the contents of the source files, + the targets must be rebuilt. + + Change the SourceSignature() value from 'MD5' to 'timestamp' + should correctly not rebuild target files, because the timestamp + of the files is always recorded. + + In previous versions of SCons, changing the SourceSignature() + value would lead to unpredictable behavior, usually including + rebuilding targets. + + -- THE Return() FUNCTION NOW ACTUALLY RETURNS IMMEDIATELY + + The Return() function now immediately stops processing the + SConscript file in which it appears and returns the values of the + variables named in its arguments. It used to continue processing + the rest of the SConscript file, and then return the values of the + specified variables at the point the Return() function was called. + + The old behavior may be requested by adding a "stop=False" + keyword argument to the Return() call: + + Return('value', stop=False) + + The "stop=" keyword argument is *not* compatible with SCons + versions 0.97.0d20070809 or earlier. + + Please note the following important changes since release 0.97: + + -- env.CacheDir() NOW ONLY AFFECTS CONSTRUCTION ENVIRONMENT TARGETS + + The env.CacheDir() method now only causes derived files to be + retrieved from the specified cache directory for targets built + with the specified specified construction environment ("env"). + + Previously, any call to env.CacheDir() or CacheDir() would modify + a global setting and cause all built targets to be retrieved + from the specified cache directory. This behavior was changed so + that env.CacheDir() would be consistent with other construction + environment methods, which only affect targets built with the + specified construction environment. + + The old behavior of changing the global behavior may be preserved + by changing any env.CacheDir() calls to: + + CacheDir('/path/to/cache/directory') + + The above change is backwards-compatible and works in all earlier + versions of SCons that support CacheDir(). + + -- INTERPRETATION OF SUFFIX-LESS SOURCE ARGUMENTS HAS CHANGED + + The interpretation of source arguments (files) without suffixes + has changed in one specific configuration. + + Previously, if a Builder had a src_suffix specified (indicating + that source files without suffixes should have that suffix + appended), the suffix would only be applied to suffix-less source + arguments if the Builder did *not* have one or more attached + source Builders (that is, the Builder was not a "multi-stage" + Builder). So in the following configuration: + + build_foo = Builder(src_suffix = '.foo') + build_bar = Builder(src_suffix = '.bar', + src_builder = build_bar) + + env = Environment(BUILDERS = { + 'Foo' : build_foo, + 'Boo' : build_bar, + }) + + env.Foo('tgt1', 'src1') + env.Bar('tgt2', 'src2') + + SCons would have expected to find a source file 'src1.foo' for the + env.Foo() call, but a source file 'src2' for the env.Bar() call. + + This behavior has now been made consistent, so that the two + above calls would expect source files named 'src1.foo' and + 'src2.bar', respectively. + + Note that, if genuinely desired, the old behavior of building + from a source file without a suffix at all (when the Builder has + a src_suffix *and* a src_builder) can be specified explicity by + turning the string into a File Node directly: + + env.Bar('tgt2', File('src2')) + + The above use of File() is backwards-compatible and will work + on earlier versions of SCons. + + -- THE DEFAULT EXECUTION PATH FOR Solaris HAS CHANGED + + On Solaris systems, SCons now adds the "/opt/SUNWspro/bin" + directory to the default execution $PATH variable before the + "/usr/ccs/bin" directory. This was done to reflect the fact + that /opt/SUNWspro/ is the default for SUN tools, but it may + cause a different compiler to be used if you have compilers + installed in both directories. + + -- GENERATED config.h FILES NOW SAY "#define HAVE_{FEATURE} 1" + + When generating a "config.h" file, SCons now defines values that + record the existence of a feature with a "1" value: + + #define HAVE_FEATURE 1 + + Instead of printing the line without a "1", as it used to: + + #define HAVE_FEATURE + + This should not cause any problems in the normal use of "#ifdef + HAVE_{FEATURE}" statements interpreted by a C preprocessor, but + might cause a compatibility issue if a script or other utility + looks for an exact match of the previous text. + + Please note the following planned, future changes: + + -- THE Options OBJECT AND RELATED FUNCTIONS WILL BE DEPRECATED + + The Options object is being replaced by a new Variables + object, which uses a new Variables.AddVariable() method + where the previous interface used Options.AddOptions(). + + Similarly, the following utility functions are being replaced + by the following similarly-named functions: + + BoolOption() BoolVariable() + EnumOption() EnumVariable() + ListOption() ListVariable() + PackageOption() PackageVariable() + PathOption() PathVariable() + + And also related, the options= keyword argument when creating + construction environments with the Environment() functions is + being replaced with a variables= keyword argument. + + In some future release a deprecation warning will be added to + existing uses of the Options object, its methods, the above + utility functions, and the options= keyword argument of the + Environment() function. At some point after the deprecation + warning is added, the Options object, related functions and + options= keyword argument will be removed entirely. + + You can prepare for this by changing all your uses of the Options + object and related functions to the Variables object and the new + function names, and changing any uses of the options= keyword + argument to variables=. + + NOTE: CONVERTING TO USING THE NEW Variables OBJECT OR THE + RELATED *Variable() FUNCTIONS, OR USING THE NEW variable= + KEYWORD ARGUMENT, IS NOT BACKWARDS COMPATIBLE TO VERSIONS OF + SCons BEFORE 0.98. YOUR SConscript FILES WILL NOT WORK ON + EARLIER VERSIONS OF SCons AFTER MAKING THIS CHANGE. + + If you change SConscript files in software that you make available + for download or otherwise distribute, other users may try to + build your software with an earlier version of SCons that does + not have the Variables object or related *Variable() functions. + We recommend preparing for this in one of two ways: + + -- Make your SConscript files backwards-compatible by + modifying your calls with Python try:-except: blocks + as follows: + + try: + vars = Variables('custom.py', ARGUMENTS) + vars.AddVariables( + BoolVariable('WARNINGS', 'cmopile with -Wall', 1), + EnumVariable('DEBUG', 'debug version', 'no' + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), + ListVariable('SHAREDLIBS', + 'libraries to build shared', + 'all', + names = list_of_libs), + PackageVariable('X11', + 'use X11 from here', + '/usr/bin/X11'), + PathVariable('QTDIR', 'root of Qt', qtdir), + ) + except NameError: + vars = Options('custom.py', ARGUMENTS) + vars.AddOptions( + BoolOption('WARNINGS', 'cmopile with -Wall', 1), + EnumOption('DEBUG', 'debug version', 'no' + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=0), + ListOption('SHAREDLIBS', + 'libraries to build shared', + 'all', + names = list_of_libs), + PackageOption('X11', + 'use X11 from here', + '/usr/bin/X11'), + PathOption('QTDIR', 'root of Qt', qtdir), + ) + + Additionally, you can check for availability of the new + variables= keyword argument as follows: + + try: + env = Environment(variables=vars) + except TypeError: + env = Environment(options=vars) + + (Note that we plan to maintain the existing Options object + name for some time, to ensure backwards compatibility, + so in practice it may be easier to just continue to use + the old name until you're reasonably sure you won't have + people trying to build your software with versions of + SCons earlier than 0.98.1.) + + -- Use the EnsureSConsVersion() function to provide a + descriptive error message if your SConscript files + are executed by an earlier version of SCons: + + EnsureSConsVersion(0, 98, 1) + + -- THE BuildDir() METHOD AND FUNCTION WILL BE DEPRECATED + + The env.BuildDir() method and BuildDir() function are being + replaced by the new env.VariantDir() method and VariantDir() + function. + + In some future release a deprecation warning will be added + to existing uses of the env.BuildDir() method and BuildDir() + function. At some point after the deprecation warning, the + env.Builder() method and BuildDir() function will either + be removed entirely or have their behavior changed. + + You can prepare for this by changing all your uses of the + env.BuildDir() method to env.VariantDir() and uses of the + global BuildDir() function to VariantDir(). If you use a + named keyword argument of "build_dir" when calling + env.BuildDir() or BuildDir(): + + env.BuildDir(build_dir='opt', src_dir='src') + + The keyword must be changed to "variant_dir": + + env.VariantDir(variant_dir='opt', src_dir='src') + + NOTE: CHANGING USES OF env.BuildDir() AND BuildDir() to + env.VariantDir() AND VariantDir() IS NOT BACKWARDS COMPATIBLE + TO VERSIONS OF SCons BEFORE 0.98. YOUR SConscript FILES + WILL NOT WORK ON EARLIER VERSIONS OF SCons AFTER MAKING + THIS CHANGE. + + If you change SConscript files in software that you make + available for download or otherwise distribute, other users + may try to build your software with an earlier version of + SCons that does not have the env.VariantDir() method or + VariantDir() fnction. We recommend preparing for this in + one of two ways: + + -- Make your SConscript files backwards-compatible by + including the following code near the beginning of your + top-level SConstruct file: + + import SCons.Environment + try: + SCons.Environment.Environment.VariantDir + except AttributeError: + SCons.Environment.Environment.VariantDir = \ + SCons.Environment.Environment.BuildDir + + -- Use the EnsureSConsVersion() function to provide a + descriptive error message if your SConscript files + are executed by an earlier version of SCons: + + EnsureSConsVersion(0, 98) + + -- THE SConscript() "build_dir" KEYWORD ARGUMENT WILL BE DEPRECATED + + The "build_dir" keyword argument of the SConscript function + and env.SConscript() method are being replaced by a new + "variant_dir" keyword argument. + + In some future release a deprecation warning will be added + to existing uses of the SConscript()/env.SConscript() + "build_dir" keyword argument. At some point after the + deprecation warning, support for this keyword argument will + be removed entirely. + + You can prepare for this by changing all your uses of the + SConscript()/env.SConscript() 'build_dir" keyword argument: + + SConscript('src/SConscript', build_dir='opt') + + To use the new "variant_dir" keyword argument: + + SConscript('src/SConscript', variant_dir='opt') + + NOTE: USING THE NEW "variant_dir" KEYWORD IS NOT BACKWARDS + COMPATIBLE TO VERSIONS OF SCons BEFORE 0.98. YOUR SConscript + FILES WILL NOT WORK ON EARLIER VERSIONS OF SCons AFTER + MAKING THIS CHANGE. + + If you change SConscript files in software that you make + available for download or otherwise distribute, other users + may try to build your software with an earlier version of + SCons that does not support the "variant_dir" keyword. + + If you can insist that users use a recent version of SCons + that supports "variant_dir", we recommend using the + EnsureSConsVersion() function to provide a descriptive error + message if your SConscript files are executed by an earlier + version of SCons: + + EnsureSConsVersion(0, 98) + + If you want to make sure that your SConscript files will + still work with earlier versions of SCons, then your best + bet is to continue to use the "build_dir" keyword until the + support is removed (which, in all likelihood, won't happen + for quite some time). + + -- SCANNER NAMES HAVE BEEN DEPRECATED AND WILL BE REMOVED + + Several internal variable names in SCons.Defaults for various + pre-made default Scanner objects have been deprecated and will + be removed in a future revision. In their place are several new + global variable names that are now part of the publicly-supported + interface: + + NEW NAME DEPRECATED NAME + -------- ---------------------------- + CScanner SCons.Defaults.CScan + DSCanner SCons.Defaults.DScan + SourceFileScanner SCons.Defaults.ObjSourceScan + ProgramScanner SCons.Defaults.ProgScan + + Of these, only ObjSourceScan was probably used at all, to add + new mappings of file suffixes to other scanners for use by the + Object() Builder. This should now be done as follows: + + SourceFileScanner.add_scanner('.x', XScanner) + + -- THE env.Copy() METHOD WILL CHANGE OR GO AWAY ENTIRELY + + The env.Copy() method (to make a copy of a construction + environment) is being replaced by the env.Clone() method. + + As of SCons 0.98, a deprecation warning has been added to + current uses of the env.Copy() method. At some point in + the future, the env.Copy() method will either be removed + entirely or have its behavior changed. + + You can prepare for this by changing all your uses of env.Copy() + to env.Clone(), which has the exact same calling arguments. + + NOTE: CHANGING USES OF env.Copy() TO env.Clone() WILL MAKE + YOUR SConscript FILES NOT WORK ON VERSIONS OF SCons BEFORE + 0.96.93. + + If you change SConscript files in software that you make + available for download or otherwise distribute, other users + may try to build your software with an earlier version of + SCons that does not have the env.Clone() method. We recommend + preparing for this in one of two ways: + + -- Make your SConscript files backwards-compatible by + including the following code near the beginning of your + top-level SConstruct file: + + import SCons.Environment + try: + SCons.Environment.Environment.Clone + except AttributeError: + SCons.Environment.Environment.Clone = \ + SCons.Environment.Environment.Copy + + -- Use the EnsureSConsVersion() function to provide a + descriptive error message if your SConscript files + are executed by an earlier version of SCons: + + EnsureSConsVersion(0, 96, 93) + + -- THE CheckLib Configure TEST WILL CHANGE BEHAVIOR + + The CheckLib() Configure test appends the lib(s) to the + Environment's LIBS list in 1.3 and earlier. In 1.3 there is a + new CheckLib argument, append, which defaults to True to + preserve the old behavior. In a future release, append will + be changed to default to False, to conform with autoconf and + user expectations, since it is usually used to build up + library lists in a right-to-left way. + + + + SCons is developed with an extensive regression test suite, and a + rigorous development methodology for continually improving that suite. + Because of this, SCons is of sufficient quality that you can use it + for real work. + + The interfaces in release 1.0 will *not* be knowingly changed in + any new, future 1.x release. If an interface change should ever + become necessary due to extraordinary circumstances, the change + and an appropriate transition strategy will be documented in these + RELEASE notes. + + As you use SCons, please heed the following: + + - Please report any bugs or other problems that you find to our bug + tracker at our SourceForge project page: + + http://sourceforge.net/tracker/?func=add&group_id=30337&atid=398971 + + We have a reliable bug-fixing methodology already in place and + strive to respond to problems relatively quickly. + + - Documentation is spottier than we'd like. You may need to dive + into the source code to figure out how to do something. Asking + questions on the scons-users mailing list is also welcome. We + will be addressing the documentation in upcoming releases, but + would be more than glad to have your assistance in correcting this + problem... :-) + + - The "SCons Design" documentation on the SCons web site is very + out of date, as we made significant changes to portions of the + interface as we figured out what worked and what didn't during the + extensive beta implementation. The "SCons Design" document should + be used only for historical purposes, or for just an extremely + general understanding of SCons' architectural goals. + + - There may be performance issues. Improving SCons performance + is an ongoing priority. If you still find the performance + unacceptable, we would very much like to hear from you and learn + more about your configuration so we can optimize the right things. + + - Error messages don't always exist where they'd be helpful. + Please let us know about any errors you ran into that would + have benefitted from a (more) descriptive message. + + KNOWN PROBLEMS IN THIS RELEASE: + + For a complete list of known problems, consult the SCons Issue Tracker + at tigris.org: + + http://scons.tigris.org/project_issues.html + + - Support for parallel builds (-j) does not work on WIN32 systems + prior to *official* Python release 2.2 (not 2.2 pre-releases). + + Prior to Python 2.2, there is a bug in Python's Win32 + implementation such that when a thread spawns an external command, + it blocks all threads from running. This breaks the SCons + multithreading architecture used to support -j builds. + + We have included a patch file, os_spawnv_fix.diff, that you can + use if you you want to fix your version of Python to support + parallel builds in SCons. + + - Again, the "SCons Design" documentation on the SCons web site is + out of date. Take what you read there with a grain of salt. + + - On Win32 systems, you must put a space between the redirection + characters < and >, and the specified files (or construction + variable expansions): + + command < $SOURCE > $TARGET + + If you don't supply a space (for example, "<$SOURCE"), SCons will + not recognize the redirection. + + - MSVC .res files are not rebuilt when icons change. + + - The -c option does not clean up .sconsign files or directories + created as part of the build, and also does not clean up + SideEffect files (for example, Visual Studio .pdb files). + + - When using multiple Repositories, changing the name of an include + file can cause an old version of the file to be used. + + - There is currently no way to force use of a relative path (../*) + for directories outside the top-level SConstruct file. + + - The Jar() Builder will, on its second or subsequent invocation, + package up the .sconsign files that SCons uses to track signatures. + You can work around this by using the SConsignFile() function + to collect all of the .sconsign information into a single file + outside of the directory being packaged by Jar(). + + - SCons does not currently have a way to detect that an intermediate + file has been corrupted from outside and should be rebuilt. + + - Unicode characters in path names do not work in all circumstances. + + - SCons does not currently automatically check out SConstruct or + SConscript files from SCCS, RCS or BitKeeper. + + - No support yet for the following planned command-line options: + + -d -e -l --list-actions --list-derived --list-where + -o --override -p -r -R -w --write-filenames + -W --warn-undefined-variables + + + +Thank you for your interest, and please let us know how we can help +improve SCons for your needs. + +Steven Knight +knight at baldmt dot com +http://www.baldmt.com/~knight/ + +With plenty of help from the SCons Development team: + Chad Austin + Charles Crain + Bill Deegan + Steve Leblanc + Greg Noel + Gary Oberbrunner + Anthony Roach + Greg Spencer + Christoph Wiedemann + diff --git a/src/engine/MANIFEST-xml.in b/src/engine/MANIFEST-xml.in new file mode 100644 index 0000000..f6ce08a --- /dev/null +++ b/src/engine/MANIFEST-xml.in @@ -0,0 +1,99 @@ +SCons/Action.xml +SCons/Defaults.xml +SCons/Environment.xml +SCons/Platform/__init__.xml +SCons/Platform/posix.xml +SCons/Platform/sunos.xml +SCons/Platform/win32.xml +SCons/Tool/386asm.xml +SCons/Tool/BitKeeper.xml +SCons/Tool/CVS.xml +SCons/Tool/Perforce.xml +SCons/Tool/RCS.xml +SCons/Tool/SCCS.xml +SCons/Tool/Subversion.xml +SCons/Tool/__init__.xml +SCons/Tool/aixc++.xml +SCons/Tool/aixcc.xml +SCons/Tool/aixf77.xml +SCons/Tool/aixlink.xml +SCons/Tool/applelink.xml +SCons/Tool/ar.xml +SCons/Tool/as.xml +SCons/Tool/bcc32.xml +SCons/Tool/c++.xml +SCons/Tool/c++.xml +SCons/Tool/cc.xml +SCons/Tool/cvf.xml +SCons/Tool/default.xml +SCons/Tool/dmd.xml +SCons/Tool/dvi.xml +SCons/Tool/dvipdf.xml +SCons/Tool/dvips.xml +SCons/Tool/f77.xml +SCons/Tool/f90.xml +SCons/Tool/f95.xml +SCons/Tool/fortran.xml +SCons/Tool/g++.xml +SCons/Tool/g77.xml +SCons/Tool/gas.xml +SCons/Tool/gcc.xml +SCons/Tool/gfortran.xml +SCons/Tool/gnulink.xml +SCons/Tool/gs.xml +SCons/Tool/hpc++.xml +SCons/Tool/hpcc.xml +SCons/Tool/hplink.xml +SCons/Tool/icc.xml +SCons/Tool/icl.xml +SCons/Tool/ifl.xml +SCons/Tool/ifort.xml +SCons/Tool/ilink.xml +SCons/Tool/ilink32.xml +SCons/Tool/install.xml +SCons/Tool/intelc.xml +SCons/Tool/jar.xml +SCons/Tool/javac.xml +SCons/Tool/javah.xml +SCons/Tool/latex.xml +SCons/Tool/lex.xml +SCons/Tool/link.xml +SCons/Tool/linkloc.xml +SCons/Tool/m4.xml +SCons/Tool/masm.xml +SCons/Tool/midl.xml +SCons/Tool/mingw.xml +SCons/Tool/mslib.xml +SCons/Tool/mslink.xml +SCons/Tool/mssdk.xml +SCons/Tool/msvc.xml +SCons/Tool/msvs.xml +SCons/Tool/mwcc.xml +SCons/Tool/mwld.xml +SCons/Tool/nasm.xml +SCons/Tool/packaging.xml +SCons/Tool/packaging/__init__.xml +SCons/Tool/pdf.xml +SCons/Tool/pdflatex.xml +SCons/Tool/pdftex.xml +SCons/Tool/qt.xml +SCons/Tool/rmic.xml +SCons/Tool/rpcgen.xml +SCons/Tool/sgiar.xml +SCons/Tool/sgic++.xml +SCons/Tool/sgicc.xml +SCons/Tool/sgilink.xml +SCons/Tool/sunar.xml +SCons/Tool/sunc++.xml +SCons/Tool/suncc.xml +SCons/Tool/sunf77.xml +SCons/Tool/sunf90.xml +SCons/Tool/sunf95.xml +SCons/Tool/sunlink.xml +SCons/Tool/swig.xml +SCons/Tool/tar.xml +SCons/Tool/tex.xml +SCons/Tool/textfile.xml +SCons/Tool/tlib.xml +SCons/Tool/yacc.xml +SCons/Tool/zip.xml diff --git a/src/engine/MANIFEST.in b/src/engine/MANIFEST.in new file mode 100644 index 0000000..2185d30 --- /dev/null +++ b/src/engine/MANIFEST.in @@ -0,0 +1,187 @@ +SCons/__init__.py +SCons/Action.py +SCons/Builder.py +SCons/compat/__init__.py +SCons/compat/_scons_hashlib.py +SCons/compat/_scons_itertools.py +SCons/compat/_scons_optparse.py +SCons/compat/_scons_sets.py +SCons/compat/_scons_sets15.py +SCons/compat/_scons_shlex.py +SCons/compat/_scons_subprocess.py +SCons/compat/_scons_textwrap.py +SCons/compat/_scons_UserString.py +SCons/compat/builtins.py +SCons/CacheDir.py +SCons/Conftest.py +SCons/cpp.py +SCons/dblite.py +SCons/Debug.py +SCons/Defaults.py +SCons/Environment.py +SCons/Errors.py +SCons/Executor.py +SCons/Job.py +SCons/exitfuncs.py +SCons/Memoize.py +SCons/Node/__init__.py +SCons/Node/Alias.py +SCons/Node/FS.py +SCons/Node/Python.py +SCons/Options/__init__.py +SCons/Options/BoolOption.py +SCons/Options/EnumOption.py +SCons/Options/ListOption.py +SCons/Options/PackageOption.py +SCons/Options/PathOption.py +SCons/PathList.py +SCons/Platform/__init__.py +SCons/Platform/aix.py +SCons/Platform/cygwin.py +SCons/Platform/darwin.py +SCons/Platform/hpux.py +SCons/Platform/irix.py +SCons/Platform/os2.py +SCons/Platform/posix.py +SCons/Platform/sunos.py +SCons/Platform/win32.py +SCons/Scanner/__init__.py +SCons/Scanner/C.py +SCons/Scanner/D.py +SCons/Scanner/Dir.py +SCons/Scanner/Fortran.py +SCons/Scanner/IDL.py +SCons/Scanner/LaTeX.py +SCons/Scanner/Prog.py +SCons/Scanner/RC.py +SCons/SConf.py +SCons/SConsign.py +SCons/Script/__init__.py +SCons/Script/Interactive.py +SCons/Script/Main.py +SCons/Script/SConscript.py +SCons/Script/SConsOptions.py +SCons/Sig.py +SCons/Subst.py +SCons/Taskmaster.py +SCons/Tool/__init__.py +SCons/Tool/386asm.py +SCons/Tool/aixc++.py +SCons/Tool/aixcc.py +SCons/Tool/aixf77.py +SCons/Tool/aixlink.py +SCons/Tool/applelink.py +SCons/Tool/ar.py +SCons/Tool/as.py +SCons/Tool/bcc32.py +SCons/Tool/BitKeeper.py +SCons/Tool/c++.py +SCons/Tool/cc.py +SCons/Tool/cvf.py +SCons/Tool/CVS.py +SCons/Tool/default.py +SCons/Tool/dmd.py +SCons/Tool/dvi.py +SCons/Tool/dvipdf.py +SCons/Tool/dvips.py +SCons/Tool/f77.py +SCons/Tool/f90.py +SCons/Tool/f95.py +SCons/Tool/filesystem.py +SCons/Tool/fortran.py +SCons/Tool/FortranCommon.py +SCons/Tool/g++.py +SCons/Tool/g77.py +SCons/Tool/gas.py +SCons/Tool/gcc.py +SCons/Tool/gfortran.py +SCons/Tool/gnulink.py +SCons/Tool/gs.py +SCons/Tool/hpc++.py +SCons/Tool/hpcc.py +SCons/Tool/hplink.py +SCons/Tool/icc.py +SCons/Tool/icl.py +SCons/Tool/ifl.py +SCons/Tool/ifort.py +SCons/Tool/ilink.py +SCons/Tool/ilink32.py +SCons/Tool/install.py +SCons/Tool/intelc.py +SCons/Tool/ipkg.py +SCons/Tool/jar.py +SCons/Tool/JavaCommon.py +SCons/Tool/javac.py +SCons/Tool/javah.py +SCons/Tool/latex.py +SCons/Tool/lex.py +SCons/Tool/link.py +SCons/Tool/linkloc.py +SCons/Tool/MSCommon/__init__.py +SCons/Tool/MSCommon/arch.py +SCons/Tool/MSCommon/common.py +SCons/Tool/MSCommon/netframework.py +SCons/Tool/MSCommon/sdk.py +SCons/Tool/MSCommon/vs.py +SCons/Tool/MSCommon/vc.py +SCons/Tool/m4.py +SCons/Tool/masm.py +SCons/Tool/midl.py +SCons/Tool/mingw.py +SCons/Tool/mslib.py +SCons/Tool/mslink.py +SCons/Tool/mssdk.py +SCons/Tool/msvc.py +SCons/Tool/msvs.py +SCons/Tool/mwcc.py +SCons/Tool/mwld.py +SCons/Tool/nasm.py +SCons/Tool/packaging/__init__.py +SCons/Tool/packaging/ipk.py +SCons/Tool/packaging/msi.py +SCons/Tool/packaging/rpm.py +SCons/Tool/packaging/src_tarbz2.py +SCons/Tool/packaging/src_targz.py +SCons/Tool/packaging/src_zip.py +SCons/Tool/packaging/tarbz2.py +SCons/Tool/packaging/targz.py +SCons/Tool/packaging/zip.py +SCons/Tool/pdf.py +SCons/Tool/pdflatex.py +SCons/Tool/pdftex.py +SCons/Tool/Perforce.py +SCons/Tool/PharLapCommon.py +SCons/Tool/qt.py +SCons/Tool/RCS.py +SCons/Tool/rmic.py +SCons/Tool/rpcgen.py +SCons/Tool/rpm.py +SCons/Tool/SCCS.py +SCons/Tool/sgiar.py +SCons/Tool/sgic++.py +SCons/Tool/sgicc.py +SCons/Tool/sgilink.py +SCons/Tool/Subversion.py +SCons/Tool/sunar.py +SCons/Tool/sunc++.py +SCons/Tool/suncc.py +SCons/Tool/sunf77.py +SCons/Tool/sunf90.py +SCons/Tool/sunf95.py +SCons/Tool/sunlink.py +SCons/Tool/swig.py +SCons/Tool/tar.py +SCons/Tool/tex.py +SCons/Tool/textfile.py +SCons/Tool/tlib.py +SCons/Tool/wix.py +SCons/Tool/yacc.py +SCons/Tool/zip.py +SCons/Util.py +SCons/Variables/__init__.py +SCons/Variables/BoolVariable.py +SCons/Variables/EnumVariable.py +SCons/Variables/ListVariable.py +SCons/Variables/PackageVariable.py +SCons/Variables/PathVariable.py +SCons/Warnings.py diff --git a/src/engine/README.txt b/src/engine/README.txt new file mode 100644 index 0000000..3d6281f --- /dev/null +++ b/src/engine/README.txt @@ -0,0 +1,167 @@ +### +### THIS FILE IS NO LONGER USED. THIS IS THE README FILE FOR THE +### SEPARATE BUILD ENGINE PACKAGE FROM THE ORIGINAL (DRAFT) PACKAGING +### SCHEME. WE'RE SAVING THIS IN CASE WE NEED OR WANT TO RESURRECT +### A SEPARATE BUILD ENGINE PACKAGE IN THE FUTURE. +### +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# src/engine/README.txt 4577 2009/12/27 19:44:43 scons + + + SCons - a software construction tool + + Version 1.2.0.d20091224 + + +This is an alpha release of the SCons build engine, a Python extension +module for building software (and other files). The SCons build engine +manages dependencies and executes commands or Python functions to update +out-of-date files (or other objects). + + +LATEST VERSION +============== + +Before going further, you can check that this package you have is +the latest version by checking the SCons download page at: + + http://www.scons.org/download.html + + +ABOUT SCONS PACKAGES +==================== + +The complete SCons system is comprised of three separate packages: + + scons + The scons script itself, plus the SCons build engine + installed into an SCons-specific library directory. + + python-scons [THIS PACKAGE] + The SCons build engine, installed into the standard + Python library directory. + + scons-script + Only the scons script itself. + +Depending on what you want to do with SCons, you may need to install +additional (or other) packages: + + If you just want to use scons (the script) to build software: + + Do not install this package. Install the scons package instead, + which contains a copy of both the script and the build engine. + You will not need to install any other packages. + + If you do NOT want to use the scons script, but you want to use the + SCons build engine in other Python software: + + Install this package. You do not need to install any other + packages. + + If you want to use the scons script AND you want to use the SCons + build engine in other Python software: + + Install this package AND the scons package. + + Note that this installs two separate copies of the build engine, + one (in an SCons-specific library directory) used by the scons + script itself and one (in the standard Python library) used by + other software. This allows you the flexibility to upgrade + one build engine without affecting the other. + + If you want the scons script and other Python software to use the + same version of the build engine: + + Install this package AND the scons-script package. + + +INSTALLATION +============ + +To install this package, simply run the provided Python-standard setup +script as follows: + + # python setup.py + +You should have system installation privileges (that is, "root" or +"Administrator") when running the setup.py script. + + +DOCUMENTATION +============= + +Documentation for SCons is available at: + + http://www.scons.org/doc.html + + +LICENSING +========= + +SCons is distributed under the MIT license, a full copy of which is +available in the LICENSE.txt file. The MIT license is an approved Open +Source license, which means: + + This software is OSI Certified Open Source Software. OSI + Certified is a certification mark of the Open Source Initiative. + +More information about OSI certifications and Open Source software is +available at: + + http://www.opensource.org/ + + +REPORTING BUGS +============== + +You can report bugs either by following the "Tracker - Bugs" link +on the SCons project page: + + http://sourceforge.net/projects/scons/ + +or by sending mail to the SCons developers mailing list: + + scons-devel@lists.sourceforge.net + + +MAILING LISTS +============= + +A mailing list for users of SCons is available. You may send +questions or comments to the list at: + + scons-users@lists.sourceforge.net + +You may subscribe to the mailing list at: + + http://lists.sourceforge.net/lists/listinfo/scons-users + +There is also a low-volume mailing list available for announcements +about SCons. Subscribe at: + + http://lists.sourceforge.net/lists/listinfo/scons-announce + + +FOR MORE INFORMATION +==================== + +Check the SCons web site at: + + http://www.scons.org/ + + +AUTHOR INFO +=========== + +Steven Knight +knight at baldmt dot com +http://www.baldmt.com/~knight/ + +With more than a little help from: + Chad Austin + Charles Crain + Steve Leblanc + Anthony Roach + Steven Shaw + diff --git a/src/engine/SCons/Action.py b/src/engine/SCons/Action.py new file mode 100644 index 0000000..a0a35b7 --- /dev/null +++ b/src/engine/SCons/Action.py @@ -0,0 +1,1240 @@ +"""SCons.Action + +This encapsulates information about executing any sort of action that +can build one or more target Nodes (typically files) from one or more +source Nodes (also typically files) given a specific Environment. + +The base class here is ActionBase. The base class supplies just a few +OO utility methods and some generic methods for displaying information +about an Action in response to the various commands that control printing. + +A second-level base class is _ActionAction. This extends ActionBase +by providing the methods that can be used to show and perform an +action. True Action objects will subclass _ActionAction; Action +factory class objects will subclass ActionBase. + +The heavy lifting is handled by subclasses for the different types of +actions we might execute: + + CommandAction + CommandGeneratorAction + FunctionAction + ListAction + +The subclasses supply the following public interface methods used by +other modules: + + __call__() + THE public interface, "calling" an Action object executes the + command or Python function. This also takes care of printing + a pre-substitution command for debugging purposes. + + get_contents() + Fetches the "contents" of an Action for signature calculation + plus the varlist. This is what gets MD5 checksummed to decide + if a target needs to be rebuilt because its action changed. + + genstring() + Returns a string representation of the Action *without* + command substitution, but allows a CommandGeneratorAction to + generate the right action based on the specified target, + source and env. This is used by the Signature subsystem + (through the Executor) to obtain an (imprecise) representation + of the Action operation for informative purposes. + + +Subclasses also supply the following methods for internal use within +this module: + + __str__() + Returns a string approximation of the Action; no variable + substitution is performed. + + execute() + The internal method that really, truly, actually handles the + execution of a command or Python function. This is used so + that the __call__() methods can take care of displaying any + pre-substitution representations, and *then* execute an action + without worrying about the specific Actions involved. + + get_presig() + Fetches the "contents" of a subclass for signature calculation. + The varlist is added to this to produce the Action's contents. + + strfunction() + Returns a substituted string representation of the Action. + This is used by the _ActionAction.show() command to display the + command/function that will be executed to generate the target(s). + +There is a related independent ActionCaller class that looks like a +regular Action, and which serves as a wrapper for arbitrary functions +that we want to let the user specify the arguments to now, but actually +execute later (when an out-of-date check determines that it's needed to +be executed, for example). Objects of this class are returned by an +ActionFactory class that provides a __call__() method as a convenient +way for wrapping up the functions. + +""" + +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +__revision__ = "src/engine/SCons/Action.py 4577 2009/12/27 19:44:43 scons" + +import cPickle +import dis +import os +import re +import string +import sys +import subprocess + +from SCons.Debug import logInstanceCreation +import SCons.Errors +import SCons.Executor +import SCons.Util +import SCons.Subst + +# we use these a lot, so try to optimize them +is_String = SCons.Util.is_String +is_List = SCons.Util.is_List + +class _null: + pass + +print_actions = 1 +execute_actions = 1 +print_actions_presub = 0 + +def rfile(n): + try: + return n.rfile() + except AttributeError: + return n + +def default_exitstatfunc(s): + return s + +try: + SET_LINENO = dis.SET_LINENO + HAVE_ARGUMENT = dis.HAVE_ARGUMENT +except AttributeError: + remove_set_lineno_codes = lambda x: x +else: + def remove_set_lineno_codes(code): + result = [] + n = len(code) + i = 0 + while i < n: + c = code[i] + op = ord(c) + if op >= HAVE_ARGUMENT: + if op != SET_LINENO: + result.append(code[i:i+3]) + i = i+3 + else: + result.append(c) + i = i+1 + return string.join(result, '') + +strip_quotes = re.compile('^[\'"](.*)[\'"]$') + + +def _callable_contents(obj): + """Return the signature contents of a callable Python object. + """ + try: + # Test if obj is a method. + return _function_contents(obj.im_func) + + except AttributeError: + try: + # Test if obj is a callable object. + return _function_contents(obj.__call__.im_func) + + except AttributeError: + try: + # Test if obj is a code object. + return _code_contents(obj) + + except AttributeError: + # Test if obj is a function object. + return _function_contents(obj) + + +def _object_contents(obj): + """Return the signature contents of any Python object. + + We have to handle the case where object contains a code object + since it can be pickled directly. + """ + try: + # Test if obj is a method. + return _function_contents(obj.im_func) + + except AttributeError: + try: + # Test if obj is a callable object. + return _function_contents(obj.__call__.im_func) + + except AttributeError: + try: + # Test if obj is a code object. + return _code_contents(obj) + + except AttributeError: + try: + # Test if obj is a function object. + return _function_contents(obj) + + except AttributeError: + # Should be a pickable Python object. + try: + return cPickle.dumps(obj) + except (cPickle.PicklingError, TypeError): + # This is weird, but it seems that nested classes + # are unpickable. The Python docs say it should + # always be a PicklingError, but some Python + # versions seem to return TypeError. Just do + # the best we can. + return str(obj) + + +def _code_contents(code): + """Return the signature contents of a code object. + + By providing direct access to the code object of the + function, Python makes this extremely easy. Hooray! + + Unfortunately, older versions of Python include line + number indications in the compiled byte code. Boo! + So we remove the line number byte codes to prevent + recompilations from moving a Python function. + """ + + contents = [] + + # The code contents depends on the number of local variables + # but not their actual names. + contents.append("%s,%s" % (code.co_argcount, len(code.co_varnames))) + try: + contents.append(",%s,%s" % (len(code.co_cellvars), len(code.co_freevars))) + except AttributeError: + # Older versions of Python do not support closures. + contents.append(",0,0") + + # The code contents depends on any constants accessed by the + # function. Note that we have to call _object_contents on each + # constants because the code object of nested functions can + # show-up among the constants. + # + # Note that we also always ignore the first entry of co_consts + # which contains the function doc string. We assume that the + # function does not access its doc string. + contents.append(',(' + string.join(map(_object_contents,code.co_consts[1:]),',') + ')') + + # The code contents depends on the variable names used to + # accessed global variable, as changing the variable name changes + # the variable actually accessed and therefore changes the + # function result. + contents.append(',(' + string.join(map(_object_contents,code.co_names),',') + ')') + + + # The code contents depends on its actual code!!! + contents.append(',(' + str(remove_set_lineno_codes(code.co_code)) + ')') + + return string.join(contents, '') + + +def _function_contents(func): + """Return the signature contents of a function.""" + + contents = [_code_contents(func.func_code)] + + # The function contents depends on the value of defaults arguments + if func.func_defaults: + contents.append(',(' + string.join(map(_object_contents,func.func_defaults),',') + ')') + else: + contents.append(',()') + + # The function contents depends on the closure captured cell values. + try: + closure = func.func_closure or [] + except AttributeError: + # Older versions of Python do not support closures. + closure = [] + + #xxx = [_object_contents(x.cell_contents) for x in closure] + try: + xxx = map(lambda x: _object_contents(x.cell_contents), closure) + except AttributeError: + xxx = [] + contents.append(',(' + string.join(xxx, ',') + ')') + + return string.join(contents, '') + + +def _actionAppend(act1, act2): + # This function knows how to slap two actions together. + # Mainly, it handles ListActions by concatenating into + # a single ListAction. + a1 = Action(act1) + a2 = Action(act2) + if a1 is None or a2 is None: + raise TypeError, "Cannot append %s to %s" % (type(act1), type(act2)) + if isinstance(a1, ListAction): + if isinstance(a2, ListAction): + return ListAction(a1.list + a2.list) + else: + return ListAction(a1.list + [ a2 ]) + else: + if isinstance(a2, ListAction): + return ListAction([ a1 ] + a2.list) + else: + return ListAction([ a1, a2 ]) + +def _do_create_keywords(args, kw): + """This converts any arguments after the action argument into + their equivalent keywords and adds them to the kw argument. + """ + v = kw.get('varlist', ()) + # prevent varlist="FOO" from being interpreted as ['F', 'O', 'O'] + if is_String(v): v = (v,) + kw['varlist'] = tuple(v) + if args: + # turn positional args into equivalent keywords + cmdstrfunc = args[0] + if cmdstrfunc is None or is_String(cmdstrfunc): + kw['cmdstr'] = cmdstrfunc + elif callable(cmdstrfunc): + kw['strfunction'] = cmdstrfunc + else: + raise SCons.Errors.UserError( + 'Invalid command display variable type. ' + 'You must either pass a string or a callback which ' + 'accepts (target, source, env) as parameters.') + if len(args) > 1: + kw['varlist'] = args[1:] + kw['varlist'] + if kw.get('strfunction', _null) is not _null \ + and kw.get('cmdstr', _null) is not _null: + raise SCons.Errors.UserError( + 'Cannot have both strfunction and cmdstr args to Action()') + +def _do_create_action(act, kw): + """This is the actual "implementation" for the + Action factory method, below. This handles the + fact that passing lists to Action() itself has + different semantics than passing lists as elements + of lists. + + The former will create a ListAction, the latter + will create a CommandAction by converting the inner + list elements to strings.""" + + if isinstance(act, ActionBase): + return act + + if is_List(act): + #TODO(1.5) return CommandAction(act, **kw) + return apply(CommandAction, (act,), kw) + + if callable(act): + try: + gen = kw['generator'] + del kw['generator'] + except KeyError: + gen = 0 + if gen: + action_type = CommandGeneratorAction + else: + action_type = FunctionAction + return action_type(act, kw) + + if is_String(act): + var=SCons.Util.get_environment_var(act) + if var: + # This looks like a string that is purely an Environment + # variable reference, like "$FOO" or "${FOO}". We do + # something special here...we lazily evaluate the contents + # of that Environment variable, so a user could put something + # like a function or a CommandGenerator in that variable + # instead of a string. + return LazyAction(var, kw) + commands = string.split(str(act), '\n') + if len(commands) == 1: + #TODO(1.5) return CommandAction(commands[0], **kw) + return apply(CommandAction, (commands[0],), kw) + # The list of string commands may include a LazyAction, so we + # reprocess them via _do_create_list_action. + return _do_create_list_action(commands, kw) + return None + +def _do_create_list_action(act, kw): + """A factory for list actions. Convert the input list into Actions + and then wrap them in a ListAction.""" + acts = [] + for a in act: + aa = _do_create_action(a, kw) + if aa is not None: acts.append(aa) + if not acts: + return ListAction([]) + elif len(acts) == 1: + return acts[0] + else: + return ListAction(acts) + +def Action(act, *args, **kw): + """A factory for action objects.""" + # Really simple: the _do_create_* routines do the heavy lifting. + _do_create_keywords(args, kw) + if is_List(act): + return _do_create_list_action(act, kw) + return _do_create_action(act, kw) + +class ActionBase: + """Base class for all types of action objects that can be held by + other objects (Builders, Executors, etc.) This provides the + common methods for manipulating and combining those actions.""" + + def __cmp__(self, other): + return cmp(self.__dict__, other) + + def no_batch_key(self, env, target, source): + return None + + batch_key = no_batch_key + + def genstring(self, target, source, env): + return str(self) + + def get_contents(self, target, source, env): + result = [ self.get_presig(target, source, env) ] + # This should never happen, as the Action() factory should wrap + # the varlist, but just in case an action is created directly, + # we duplicate this check here. + vl = self.varlist + if is_String(vl): vl = (vl,) + for v in vl: + result.append(env.subst('${'+v+'}')) + return string.join(result, '') + + def __add__(self, other): + return _actionAppend(self, other) + + def __radd__(self, other): + return _actionAppend(other, self) + + def presub_lines(self, env): + # CommandGeneratorAction needs a real environment + # in order to return the proper string here, since + # it may call LazyAction, which looks up a key + # in that env. So we temporarily remember the env here, + # and CommandGeneratorAction will use this env + # when it calls its _generate method. + self.presub_env = env + lines = string.split(str(self), '\n') + self.presub_env = None # don't need this any more + return lines + + def get_targets(self, env, executor): + """ + Returns the type of targets ($TARGETS, $CHANGED_TARGETS) used + by this action. + """ + return self.targets + +class _ActionAction(ActionBase): + """Base class for actions that create output objects.""" + def __init__(self, cmdstr=_null, strfunction=_null, varlist=(), + presub=_null, chdir=None, exitstatfunc=None, + batch_key=None, targets='$TARGETS', + **kw): + self.cmdstr = cmdstr + if strfunction is not _null: + if strfunction is None: + self.cmdstr = None + else: + self.strfunction = strfunction + self.varlist = varlist + self.presub = presub + self.chdir = chdir + if not exitstatfunc: + exitstatfunc = default_exitstatfunc + self.exitstatfunc = exitstatfunc + + self.targets = targets + + if batch_key: + if not callable(batch_key): + # They have set batch_key, but not to their own + # callable. The default behavior here will batch + # *all* targets+sources using this action, separated + # for each construction environment. + def default_batch_key(self, env, target, source): + return (id(self), id(env)) + batch_key = default_batch_key + SCons.Util.AddMethod(self, batch_key, 'batch_key') + + def print_cmd_line(self, s, target, source, env): + sys.stdout.write(s + "\n") + + def __call__(self, target, source, env, + exitstatfunc=_null, + presub=_null, + show=_null, + execute=_null, + chdir=_null, + executor=None): + if not is_List(target): + target = [target] + if not is_List(source): + source = [source] + + if presub is _null: + presub = self.presub + if presub is _null: + presub = print_actions_presub + if exitstatfunc is _null: exitstatfunc = self.exitstatfunc + if show is _null: show = print_actions + if execute is _null: execute = execute_actions + if chdir is _null: chdir = self.chdir + save_cwd = None + if chdir: + save_cwd = os.getcwd() + try: + chdir = str(chdir.abspath) + except AttributeError: + if not is_String(chdir): + if executor: + chdir = str(executor.batches[0].targets[0].dir) + else: + chdir = str(target[0].dir) + if presub: + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + t = string.join(map(str, target), ' and ') + l = string.join(self.presub_lines(env), '\n ') + out = "Building %s with action:\n %s\n" % (t, l) + sys.stdout.write(out) + cmd = None + if show and self.strfunction: + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + try: + cmd = self.strfunction(target, source, env, executor) + except TypeError: + cmd = self.strfunction(target, source, env) + if cmd: + if chdir: + cmd = ('os.chdir(%s)\n' % repr(chdir)) + cmd + try: + get = env.get + except AttributeError: + print_func = self.print_cmd_line + else: + print_func = get('PRINT_CMD_LINE_FUNC') + if not print_func: + print_func = self.print_cmd_line + print_func(cmd, target, source, env) + stat = 0 + if execute: + if chdir: + os.chdir(chdir) + try: + stat = self.execute(target, source, env, executor=executor) + if isinstance(stat, SCons.Errors.BuildError): + s = exitstatfunc(stat.status) + if s: + stat.status = s + else: + stat = s + else: + stat = exitstatfunc(stat) + finally: + if save_cwd: + os.chdir(save_cwd) + if cmd and save_cwd: + print_func('os.chdir(%s)' % repr(save_cwd), target, source, env) + + return stat + + +def _string_from_cmd_list(cmd_list): + """Takes a list of command line arguments and returns a pretty + representation for printing.""" + cl = [] + for arg in map(str, cmd_list): + if ' ' in arg or '\t' in arg: + arg = '"' + arg + '"' + cl.append(arg) + return string.join(cl) + +# A fiddlin' little function that has an 'import SCons.Environment' which +# can't be moved to the top level without creating an import loop. Since +# this import creates a local variable named 'SCons', it blocks access to +# the global variable, so we move it here to prevent complaints about local +# variables being used uninitialized. +default_ENV = None +def get_default_ENV(env): + global default_ENV + try: + return env['ENV'] + except KeyError: + if not default_ENV: + import SCons.Environment + # This is a hideously expensive way to get a default shell + # environment. What it really should do is run the platform + # setup to get the default ENV. Fortunately, it's incredibly + # rare for an Environment not to have a shell environment, so + # we're not going to worry about it overmuch. + default_ENV = SCons.Environment.Environment()['ENV'] + return default_ENV + +# This function is still in draft mode. We're going to need something like +# it in the long run as more and more places use subprocess, but I'm sure +# it'll have to be tweaked to get the full desired functionality. +# one special arg (so far?), 'error', to tell what to do with exceptions. +def _subproc(env, cmd, error = 'ignore', **kw): + """Do common setup for a subprocess.Popen() call""" + # allow std{in,out,err} to be "'devnull'" + io = kw.get('stdin') + if is_String(io) and io == 'devnull': + kw['stdin'] = open(os.devnull) + io = kw.get('stdout') + if is_String(io) and io == 'devnull': + kw['stdout'] = open(os.devnull, 'w') + io = kw.get('stderr') + if is_String(io) and io == 'devnull': + kw['stderr'] = open(os.devnull, 'w') + + # Figure out what shell environment to use + ENV = kw.get('env', None) + if ENV is None: ENV = get_default_ENV(env) + + # Ensure that the ENV values are all strings: + new_env = {} + for key, value in ENV.items(): + if is_List(value): + # If the value is a list, then we assume it is a path list, + # because that's a pretty common list-like value to stick + # in an environment variable: + value = SCons.Util.flatten_sequence(value) + new_env[key] = string.join(map(str, value), os.pathsep) + else: + # It's either a string or something else. If it's a string, + # we still want to call str() because it might be a *Unicode* + # string, which makes subprocess.Popen() gag. If it isn't a + # string or a list, then we just coerce it to a string, which + # is the proper way to handle Dir and File instances and will + # produce something reasonable for just about everything else: + new_env[key] = str(value) + kw['env'] = new_env + + try: + #FUTURE return subprocess.Popen(cmd, **kw) + return apply(subprocess.Popen, (cmd,), kw) + except EnvironmentError, e: + if error == 'raise': raise + # return a dummy Popen instance that only returns error + class dummyPopen: + def __init__(self, e): self.exception = e + def communicate(self): return ('','') + def wait(self): return -self.exception.errno + stdin = None + class f: + def read(self): return '' + def readline(self): return '' + stdout = stderr = f() + return dummyPopen(e) + +class CommandAction(_ActionAction): + """Class for command-execution actions.""" + def __init__(self, cmd, **kw): + # Cmd can actually be a list or a single item; if it's a + # single item it should be the command string to execute; if a + # list then it should be the words of the command string to + # execute. Only a single command should be executed by this + # object; lists of commands should be handled by embedding + # these objects in a ListAction object (which the Action() + # factory above does). cmd will be passed to + # Environment.subst_list() for substituting environment + # variables. + if __debug__: logInstanceCreation(self, 'Action.CommandAction') + + #TODO(1.5) _ActionAction.__init__(self, **kw) + apply(_ActionAction.__init__, (self,), kw) + if is_List(cmd): + if filter(is_List, cmd): + raise TypeError, "CommandAction should be given only " \ + "a single command" + self.cmd_list = cmd + + def __str__(self): + if is_List(self.cmd_list): + return string.join(map(str, self.cmd_list), ' ') + return str(self.cmd_list) + + def process(self, target, source, env, executor=None): + if executor: + result = env.subst_list(self.cmd_list, 0, executor=executor) + else: + result = env.subst_list(self.cmd_list, 0, target, source) + silent = None + ignore = None + while 1: + try: c = result[0][0][0] + except IndexError: c = None + if c == '@': silent = 1 + elif c == '-': ignore = 1 + else: break + result[0][0] = result[0][0][1:] + try: + if not result[0][0]: + result[0] = result[0][1:] + except IndexError: + pass + return result, ignore, silent + + def strfunction(self, target, source, env, executor=None): + if self.cmdstr is None: + return None + if self.cmdstr is not _null: + from SCons.Subst import SUBST_RAW + if executor: + c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) + else: + c = env.subst(self.cmdstr, SUBST_RAW, target, source) + if c: + return c + cmd_list, ignore, silent = self.process(target, source, env, executor) + if silent: + return '' + return _string_from_cmd_list(cmd_list[0]) + + def execute(self, target, source, env, executor=None): + """Execute a command action. + + This will handle lists of commands as well as individual commands, + because construction variable substitution may turn a single + "command" into a list. This means that this class can actually + handle lists of commands, even though that's not how we use it + externally. + """ + escape_list = SCons.Subst.escape_list + flatten_sequence = SCons.Util.flatten_sequence + + try: + shell = env['SHELL'] + except KeyError: + raise SCons.Errors.UserError('Missing SHELL construction variable.') + + try: + spawn = env['SPAWN'] + except KeyError: + raise SCons.Errors.UserError('Missing SPAWN construction variable.') + else: + if is_String(spawn): + spawn = env.subst(spawn, raw=1, conv=lambda x: x) + + escape = env.get('ESCAPE', lambda x: x) + + ENV = get_default_ENV(env) + + # Ensure that the ENV values are all strings: + for key, value in ENV.items(): + if not is_String(value): + if is_List(value): + # If the value is a list, then we assume it is a + # path list, because that's a pretty common list-like + # value to stick in an environment variable: + value = flatten_sequence(value) + ENV[key] = string.join(map(str, value), os.pathsep) + else: + # If it isn't a string or a list, then we just coerce + # it to a string, which is the proper way to handle + # Dir and File instances and will produce something + # reasonable for just about everything else: + ENV[key] = str(value) + + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + cmd_list, ignore, silent = self.process(target, map(rfile, source), env, executor) + + # Use len() to filter out any "command" that's zero-length. + for cmd_line in filter(len, cmd_list): + # Escape the command line for the interpreter we are using. + cmd_line = escape_list(cmd_line, escape) + result = spawn(shell, escape, cmd_line[0], cmd_line, ENV) + if not ignore and result: + msg = "Error %s" % result + return SCons.Errors.BuildError(errstr=msg, + status=result, + action=self, + command=cmd_line) + return 0 + + def get_presig(self, target, source, env, executor=None): + """Return the signature contents of this action's command line. + + This strips $(-$) and everything in between the string, + since those parts don't affect signatures. + """ + from SCons.Subst import SUBST_SIG + cmd = self.cmd_list + if is_List(cmd): + cmd = string.join(map(str, cmd)) + else: + cmd = str(cmd) + if executor: + return env.subst_target_source(cmd, SUBST_SIG, executor=executor) + else: + return env.subst_target_source(cmd, SUBST_SIG, target, source) + + def get_implicit_deps(self, target, source, env, executor=None): + icd = env.get('IMPLICIT_COMMAND_DEPENDENCIES', True) + if is_String(icd) and icd[:1] == '$': + icd = env.subst(icd) + if not icd or icd in ('0', 'None'): + return [] + from SCons.Subst import SUBST_SIG + if executor: + cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, executor=executor) + else: + cmd_list = env.subst_list(self.cmd_list, SUBST_SIG, target, source) + res = [] + for cmd_line in cmd_list: + if cmd_line: + d = str(cmd_line[0]) + m = strip_quotes.match(d) + if m: + d = m.group(1) + d = env.WhereIs(d) + if d: + res.append(env.fs.File(d)) + return res + +class CommandGeneratorAction(ActionBase): + """Class for command-generator actions.""" + def __init__(self, generator, kw): + if __debug__: logInstanceCreation(self, 'Action.CommandGeneratorAction') + self.generator = generator + self.gen_kw = kw + self.varlist = kw.get('varlist', ()) + self.targets = kw.get('targets', '$TARGETS') + + def _generate(self, target, source, env, for_signature, executor=None): + # ensure that target is a list, to make it easier to write + # generator functions: + if not is_List(target): + target = [target] + + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + ret = self.generator(target=target, + source=source, + env=env, + for_signature=for_signature) + #TODO(1.5) gen_cmd = Action(ret, **self.gen_kw) + gen_cmd = apply(Action, (ret,), self.gen_kw) + if not gen_cmd: + raise SCons.Errors.UserError("Object returned from command generator: %s cannot be used to create an Action." % repr(ret)) + return gen_cmd + + def __str__(self): + try: + env = self.presub_env + except AttributeError: + env = None + if env is None: + env = SCons.Defaults.DefaultEnvironment() + act = self._generate([], [], env, 1) + return str(act) + + def batch_key(self, env, target, source): + return self._generate(target, source, env, 1).batch_key(env, target, source) + + def genstring(self, target, source, env, executor=None): + return self._generate(target, source, env, 1, executor).genstring(target, source, env) + + def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, + show=_null, execute=_null, chdir=_null, executor=None): + act = self._generate(target, source, env, 0, executor) + if act is None: + raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source)))) + return act(target, source, env, exitstatfunc, presub, + show, execute, chdir, executor) + + def get_presig(self, target, source, env, executor=None): + """Return the signature contents of this action's command line. + + This strips $(-$) and everything in between the string, + since those parts don't affect signatures. + """ + return self._generate(target, source, env, 1, executor).get_presig(target, source, env) + + def get_implicit_deps(self, target, source, env, executor=None): + return self._generate(target, source, env, 1, executor).get_implicit_deps(target, source, env) + + def get_targets(self, env, executor): + return self._generate(None, None, env, 1, executor).get_targets(env, executor) + + + +# A LazyAction is a kind of hybrid generator and command action for +# strings of the form "$VAR". These strings normally expand to other +# strings (think "$CCCOM" to "$CC -c -o $TARGET $SOURCE"), but we also +# want to be able to replace them with functions in the construction +# environment. Consequently, we want lazy evaluation and creation of +# an Action in the case of the function, but that's overkill in the more +# normal case of expansion to other strings. +# +# So we do this with a subclass that's both a generator *and* +# a command action. The overridden methods all do a quick check +# of the construction variable, and if it's a string we just call +# the corresponding CommandAction method to do the heavy lifting. +# If not, then we call the same-named CommandGeneratorAction method. +# The CommandGeneratorAction methods work by using the overridden +# _generate() method, that is, our own way of handling "generation" of +# an action based on what's in the construction variable. + +class LazyAction(CommandGeneratorAction, CommandAction): + + def __init__(self, var, kw): + if __debug__: logInstanceCreation(self, 'Action.LazyAction') + #FUTURE CommandAction.__init__(self, '${'+var+'}', **kw) + apply(CommandAction.__init__, (self, '${'+var+'}'), kw) + self.var = SCons.Util.to_String(var) + self.gen_kw = kw + + def get_parent_class(self, env): + c = env.get(self.var) + if is_String(c) and not '\n' in c: + return CommandAction + return CommandGeneratorAction + + def _generate_cache(self, env): + if env: + c = env.get(self.var, '') + else: + c = '' + #TODO(1.5) gen_cmd = Action(c, **self.gen_kw) + gen_cmd = apply(Action, (c,), self.gen_kw) + if not gen_cmd: + raise SCons.Errors.UserError("$%s value %s cannot be used to create an Action." % (self.var, repr(c))) + return gen_cmd + + def _generate(self, target, source, env, for_signature, executor=None): + return self._generate_cache(env) + + def __call__(self, target, source, env, *args, **kw): + args = (self, target, source, env) + args + c = self.get_parent_class(env) + #TODO(1.5) return c.__call__(*args, **kw) + return apply(c.__call__, args, kw) + + def get_presig(self, target, source, env): + c = self.get_parent_class(env) + return c.get_presig(self, target, source, env) + + + +class FunctionAction(_ActionAction): + """Class for Python function actions.""" + + def __init__(self, execfunction, kw): + if __debug__: logInstanceCreation(self, 'Action.FunctionAction') + + self.execfunction = execfunction + try: + self.funccontents = _callable_contents(execfunction) + except AttributeError: + try: + # See if execfunction will do the heavy lifting for us. + self.gc = execfunction.get_contents + except AttributeError: + # This is weird, just do the best we can. + self.funccontents = _object_contents(execfunction) + + #TODO(1.5) _ActionAction.__init__(self, **kw) + apply(_ActionAction.__init__, (self,), kw) + + def function_name(self): + try: + return self.execfunction.__name__ + except AttributeError: + try: + return self.execfunction.__class__.__name__ + except AttributeError: + return "unknown_python_function" + + def strfunction(self, target, source, env, executor=None): + if self.cmdstr is None: + return None + if self.cmdstr is not _null: + from SCons.Subst import SUBST_RAW + if executor: + c = env.subst(self.cmdstr, SUBST_RAW, executor=executor) + else: + c = env.subst(self.cmdstr, SUBST_RAW, target, source) + if c: + return c + def array(a): + def quote(s): + try: + str_for_display = s.str_for_display + except AttributeError: + s = repr(s) + else: + s = str_for_display() + return s + return '[' + string.join(map(quote, a), ", ") + ']' + try: + strfunc = self.execfunction.strfunction + except AttributeError: + pass + else: + if strfunc is None: + return None + if callable(strfunc): + return strfunc(target, source, env) + name = self.function_name() + tstr = array(target) + sstr = array(source) + return "%s(%s, %s)" % (name, tstr, sstr) + + def __str__(self): + name = self.function_name() + if name == 'ActionCaller': + return str(self.execfunction) + return "%s(target, source, env)" % name + + def execute(self, target, source, env, executor=None): + exc_info = (None,None,None) + try: + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + rsources = map(rfile, source) + try: + result = self.execfunction(target=target, source=rsources, env=env) + except KeyboardInterrupt, e: + raise + except SystemExit, e: + raise + except Exception, e: + result = e + exc_info = sys.exc_info() + + if result: + result = SCons.Errors.convert_to_BuildError(result, exc_info) + result.node=target + result.action=self + try: + result.command=self.strfunction(target, source, env, executor) + except TypeError: + result.command=self.strfunction(target, source, env) + + # FIXME: This maintains backward compatibility with respect to + # which type of exceptions were returned by raising an + # exception and which ones were returned by value. It would + # probably be best to always return them by value here, but + # some codes do not check the return value of Actions and I do + # not have the time to modify them at this point. + if (exc_info[1] and + not isinstance(exc_info[1],EnvironmentError)): + raise result + + return result + finally: + # Break the cycle between the traceback object and this + # function stack frame. See the sys.exc_info() doc info for + # more information about this issue. + del exc_info + + + def get_presig(self, target, source, env): + """Return the signature contents of this callable action.""" + try: + return self.gc(target, source, env) + except AttributeError: + return self.funccontents + + def get_implicit_deps(self, target, source, env): + return [] + +class ListAction(ActionBase): + """Class for lists of other actions.""" + def __init__(self, list): + if __debug__: logInstanceCreation(self, 'Action.ListAction') + def list_of_actions(x): + if isinstance(x, ActionBase): + return x + return Action(x) + self.list = map(list_of_actions, list) + # our children will have had any varlist + # applied; we don't need to do it again + self.varlist = () + self.targets = '$TARGETS' + + def genstring(self, target, source, env): + return string.join(map(lambda a, t=target, s=source, e=env: + a.genstring(t, s, e), + self.list), + '\n') + + def __str__(self): + return string.join(map(str, self.list), '\n') + + def presub_lines(self, env): + return SCons.Util.flatten_sequence( + map(lambda a, env=env: a.presub_lines(env), self.list)) + + def get_presig(self, target, source, env): + """Return the signature contents of this action list. + + Simple concatenation of the signatures of the elements. + """ + return string.join(map(lambda x, t=target, s=source, e=env: + x.get_contents(t, s, e), + self.list), + "") + + def __call__(self, target, source, env, exitstatfunc=_null, presub=_null, + show=_null, execute=_null, chdir=_null, executor=None): + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + for act in self.list: + stat = act(target, source, env, exitstatfunc, presub, + show, execute, chdir, executor) + if stat: + return stat + return 0 + + def get_implicit_deps(self, target, source, env): + result = [] + for act in self.list: + result.extend(act.get_implicit_deps(target, source, env)) + return result + +class ActionCaller: + """A class for delaying calling an Action function with specific + (positional and keyword) arguments until the Action is actually + executed. + + This class looks to the rest of the world like a normal Action object, + but what it's really doing is hanging on to the arguments until we + have a target, source and env to use for the expansion. + """ + def __init__(self, parent, args, kw): + self.parent = parent + self.args = args + self.kw = kw + + def get_contents(self, target, source, env): + actfunc = self.parent.actfunc + try: + # "self.actfunc" is a function. + contents = str(actfunc.func_code.co_code) + except AttributeError: + # "self.actfunc" is a callable object. + try: + contents = str(actfunc.__call__.im_func.func_code.co_code) + except AttributeError: + # No __call__() method, so it might be a builtin + # or something like that. Do the best we can. + contents = str(actfunc) + contents = remove_set_lineno_codes(contents) + return contents + + def subst(self, s, target, source, env): + # If s is a list, recursively apply subst() + # to every element in the list + if is_List(s): + result = [] + for elem in s: + result.append(self.subst(elem, target, source, env)) + return self.parent.convert(result) + + # Special-case hack: Let a custom function wrapped in an + # ActionCaller get at the environment through which the action + # was called by using this hard-coded value as a special return. + if s == '$__env__': + return env + elif is_String(s): + return env.subst(s, 1, target, source) + return self.parent.convert(s) + + def subst_args(self, target, source, env): + return map(lambda x, self=self, t=target, s=source, e=env: + self.subst(x, t, s, e), + self.args) + + def subst_kw(self, target, source, env): + kw = {} + for key in self.kw.keys(): + kw[key] = self.subst(self.kw[key], target, source, env) + return kw + + def __call__(self, target, source, env, executor=None): + args = self.subst_args(target, source, env) + kw = self.subst_kw(target, source, env) + #TODO(1.5) return self.parent.actfunc(*args, **kw) + return apply(self.parent.actfunc, args, kw) + + def strfunction(self, target, source, env): + args = self.subst_args(target, source, env) + kw = self.subst_kw(target, source, env) + #TODO(1.5) return self.parent.strfunc(*args, **kw) + return apply(self.parent.strfunc, args, kw) + + def __str__(self): + #TODO(1.5) return self.parent.strfunc(*self.args, **self.kw) + return apply(self.parent.strfunc, self.args, self.kw) + +class ActionFactory: + """A factory class that will wrap up an arbitrary function + as an SCons-executable Action object. + + The real heavy lifting here is done by the ActionCaller class. + We just collect the (positional and keyword) arguments that we're + called with and give them to the ActionCaller object we create, + so it can hang onto them until it needs them. + """ + def __init__(self, actfunc, strfunc, convert=lambda x: x): + self.actfunc = actfunc + self.strfunc = strfunc + self.convert = convert + + def __call__(self, *args, **kw): + ac = ActionCaller(self, args, kw) + action = Action(ac, strfunction=ac.strfunction) + return action + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Action.xml b/src/engine/SCons/Action.xml new file mode 100644 index 0000000..a4eb12d --- /dev/null +++ b/src/engine/SCons/Action.xml @@ -0,0 +1,107 @@ + + + +Controls whether or not SCons will +add implicit dependencies for the commands +executed to build targets. + +By default, SCons will add +to each target +an implicit dependency on the command +represented by the first argument on any +command line it executes. +The specific file for the dependency is +found by searching the +PATH +variable in the +ENV +environment used to execute the command. + +If the construction variable +&cv-IMPLICIT_COMMAND_DEPENDENCIES; +is set to a false value +(None, +False, +0, +etc.), +then the implicit dependency will +not be added to the targets +built with that construction environment. + + +env = Environment(IMPLICIT_COMMAND_DEPENDENCIES = 0) + + + + + + +A Python function used to print the command lines as they are executed +(assuming command printing is not disabled by the + +or + +options or their equivalents). +The function should take four arguments: +s, +the command being executed (a string), +target, +the target being built (file node, list, or string name(s)), +source, +the source(s) used (file node, list, or string name(s)), and +env, +the environment being used. + +The function must do the printing itself. The default implementation, +used if this variable is not set or is None, is: + +def print_cmd_line(s, target, source, env): + sys.stdout.write(s + "\n") + + +Here's an example of a more interesting function: + + +def print_cmd_line(s, target, source, env): + sys.stdout.write("Building %s -> %s...\n" % + (' and '.join([str(x) for x in source]), + ' and '.join([str(x) for x in target]))) +env=Environment(PRINT_CMD_LINE_FUNC=print_cmd_line) +env.Program('foo', 'foo.c') + + +This just prints "Building targetname from sourcename..." instead +of the actual commands. +Such a function could also log the actual commands to a log file, +for example. + + + + + +A command interpreter function that will be called to execute command line +strings. The function must expect the following arguments: + + +def spawn(shell, escape, cmd, args, env): + + +sh +is a string naming the shell program to use. +escape +is a function that can be called to escape shell special characters in +the command line. +cmd +is the path to the command to be executed. +args +is the arguments to the command. +env +is a dictionary of the environment variables +in which the command should be executed. + + diff --git a/src/engine/SCons/ActionTests.py b/src/engine/SCons/ActionTests.py new file mode 100644 index 0000000..6d4863e --- /dev/null +++ b/src/engine/SCons/ActionTests.py @@ -0,0 +1,2013 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/ActionTests.py 4577 2009/12/27 19:44:43 scons" + +# Define a null function and a null class for use as builder actions. +# Where these are defined in the file seems to affect their byte-code +# contents, so try to minimize changes by defining them here, before we +# even import anything. +def GlobalFunc(): + pass + +class GlobalActFunc: + def __call__(self): + pass + +import os +import re +import StringIO +import string +import sys +import types +import unittest +import UserDict + +import SCons.Action +import SCons.Environment +import SCons.Errors + +import TestCmd + +# Initial setup of the common environment for all tests, +# a temporary working directory containing a +# script for writing arguments to an output file. +# +# We don't do this as a setUp() method because it's +# unnecessary to create a separate directory and script +# for each test, they can just use the one. +test = TestCmd.TestCmd(workdir = '') + +test.write('act.py', """\ +import os, string, sys +f = open(sys.argv[1], 'w') +f.write("act.py: '" + string.join(sys.argv[2:], "' '") + "'\\n") +try: + if sys.argv[3]: + f.write("act.py: '" + os.environ[sys.argv[3]] + "'\\n") +except: + pass +f.close() +if os.environ.has_key( 'ACTPY_PIPE' ): + if os.environ.has_key( 'PIPE_STDOUT_FILE' ): + stdout_msg = open(os.environ['PIPE_STDOUT_FILE'], 'r').read() + else: + stdout_msg = "act.py: stdout: executed act.py %s\\n" % string.join(sys.argv[1:]) + sys.stdout.write( stdout_msg ) + if os.environ.has_key( 'PIPE_STDERR_FILE' ): + stderr_msg = open(os.environ['PIPE_STDERR_FILE'], 'r').read() + else: + stderr_msg = "act.py: stderr: executed act.py %s\\n" % string.join(sys.argv[1:]) + sys.stderr.write( stderr_msg ) +sys.exit(0) +""") + +test.write('exit.py', """\ +import sys +sys.exit(int(sys.argv[1])) +""") + +act_py = test.workpath('act.py') +exit_py = test.workpath('exit.py') + +outfile = test.workpath('outfile') +outfile2 = test.workpath('outfile2') +pipe_file = test.workpath('pipe.out') + +scons_env = SCons.Environment.Environment() + +# Capture all the stuff the Actions will print, +# so it doesn't clutter the output. +sys.stdout = StringIO.StringIO() + +class CmdStringHolder: + def __init__(self, cmd, literal=None): + self.data = str(cmd) + self.literal = literal + + def is_literal(self): + return self.literal + + def escape(self, escape_func): + """Escape the string with the supplied function. The + function is expected to take an arbitrary string, then + return it with all special characters escaped and ready + for passing to the command interpreter. + + After calling this function, the next call to str() will + return the escaped string. + """ + + if self.is_literal(): + return escape_func(self.data) + elif ' ' in self.data or '\t' in self.data: + return '"%s"' % self.data + else: + return self.data + +class Environment: + def __init__(self, **kw): + self.d = {} + self.d['SHELL'] = scons_env['SHELL'] + self.d['SPAWN'] = scons_env['SPAWN'] + self.d['PSPAWN'] = scons_env['PSPAWN'] + self.d['ESCAPE'] = scons_env['ESCAPE'] + for k, v in kw.items(): + self.d[k] = v + # Just use the underlying scons_subst*() utility methods. + def subst(self, strSubst, raw=0, target=[], source=[], conv=None): + return SCons.Subst.scons_subst(strSubst, self, raw, + target, source, self.d, conv=conv) + subst_target_source = subst + def subst_list(self, strSubst, raw=0, target=[], source=[], conv=None): + return SCons.Subst.scons_subst_list(strSubst, self, raw, + target, source, self.d, conv=conv) + def __getitem__(self, item): + return self.d[item] + def __setitem__(self, item, value): + self.d[item] = value + def has_key(self, item): + return self.d.has_key(item) + def get(self, key, value=None): + return self.d.get(key, value) + def items(self): + return self.d.items() + def Dictionary(self): + return self.d + def Clone(self, **kw): + res = Environment() + res.d = SCons.Util.semi_deepcopy(self.d) + for k, v in kw.items(): + res.d[k] = v + return res + def sig_dict(self): + d = {} + for k,v in self.items(): d[k] = v + d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__'] + d['TARGET'] = d['TARGETS'][0] + d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__'] + d['SOURCE'] = d['SOURCES'][0] + return d + +class DummyNode: + def __init__(self, name): + self.name = name + def str_for_display(self): + return '"' + self.name + '"' + def __str__(self): + return self.name + def rfile(self): + return self + def get_subst_proxy(self): + return self + +if os.name == 'java': + python = os.path.join(sys.prefix, 'jython') +else: + python = sys.executable + +_python_ = '"' + python + '"' + +_null = SCons.Action._null + +def test_varlist(pos_call, str_call, cmd, cmdstrfunc, **kw): + def call_action(a, pos_call=pos_call, str_call=str_call, kw=kw): + #FUTURE a = SCons.Action.Action(*a, **kw) + a = apply(SCons.Action.Action, a, kw) + # returned object must provide these entry points + assert hasattr(a, '__call__') + assert hasattr(a, 'get_contents') + assert hasattr(a, 'genstring') + pos_call(a) + str_call(a) + return a + + a = call_action((cmd, cmdstrfunc)) + assert a.varlist == (), a.varlist + + a = call_action((cmd, cmdstrfunc, 'foo')) + assert a.varlist == ('foo',), a.varlist + + a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c')) + assert a.varlist == ('a', 'b', 'c'), a.varlist + + kw['varlist'] = 'foo' + a = call_action((cmd, cmdstrfunc)) + assert a.varlist == ('foo',), a.varlist + + kw['varlist'] = ['x', 'y', 'z'] + a = call_action((cmd, cmdstrfunc)) + assert a.varlist == ('x', 'y', 'z'), a.varlist + + a = call_action((cmd, cmdstrfunc, 'foo')) + assert a.varlist == ('foo', 'x', 'y', 'z'), a.varlist + + a = call_action((cmd, cmdstrfunc, 'a', 'b', 'c')) + assert a.varlist == ('a', 'b', 'c', 'x', 'y', 'z'), a.varlist + +def test_positional_args(pos_callback, cmd, **kw): + """Test that Action() returns the expected type and that positional args work. + """ + #FUTURE act = SCons.Action.Action(cmd, **kw) + act = apply(SCons.Action.Action, (cmd,), kw) + pos_callback(act) + assert act.varlist is (), act.varlist + + if not isinstance(act, SCons.Action._ActionAction): + # only valid cmdstrfunc is None + def none(a): pass + #FUTURE test_varlist(pos_callback, none, cmd, None, **kw) + apply(test_varlist, (pos_callback, none, cmd, None), kw) + else: + # _ActionAction should have set these + assert hasattr(act, 'strfunction') + assert act.cmdstr is _null, act.cmdstr + assert act.presub is _null, act.presub + assert act.chdir is None, act.chdir + assert act.exitstatfunc is SCons.Action.default_exitstatfunc, \ + act.exitstatfunc + + def cmdstr(a): + assert hasattr(a, 'strfunction') + assert a.cmdstr == 'cmdstr', a.cmdstr + #FUTURE test_varlist(pos_callback, cmdstr, cmd, 'cmdstr', **kw) + apply(test_varlist, (pos_callback, cmdstr, cmd, 'cmdstr'), kw) + + def fun(): pass + def strfun(a, fun=fun): + assert a.strfunction is fun, a.strfunction + assert a.cmdstr == _null, a.cmdstr + #FUTURE test_varlist(pos_callback, strfun, cmd, fun, **kw) + apply(test_varlist, (pos_callback, strfun, cmd, fun), kw) + + def none(a): + assert hasattr(a, 'strfunction') + assert a.cmdstr is None, a.cmdstr + #FUTURE test_varlist(pos_callback, none, cmd, None, **kw) + apply(test_varlist, (pos_callback, none, cmd, None), kw) + + """Test handling of bad cmdstrfunc arguments """ + try: + #FUTURE a = SCons.Action.Action(cmd, [], **kw) + a = apply(SCons.Action.Action, (cmd, []), kw) + except SCons.Errors.UserError, e: + s = str(e) + m = 'Invalid command display variable' + assert string.find(s, m) != -1, 'Unexpected string: %s' % s + else: + raise Exception, "did not catch expected UserError" + + return act + +class ActionTestCase(unittest.TestCase): + """Test the Action() factory function""" + + def test_FunctionAction(self): + """Test the Action() factory's creation of FunctionAction objects + """ + def foo(): + pass + + def func_action(a, foo=foo): + assert isinstance(a, SCons.Action.FunctionAction), a + assert a.execfunction == foo, a.execfunction + test_positional_args(func_action, foo) + # a singleton list returns the contained action + test_positional_args(func_action, [foo]) + + def test_CommandAction(self): + """Test the Action() factory's creation of CommandAction objects + """ + def cmd_action(a): + assert isinstance(a, SCons.Action.CommandAction), a + assert a.cmd_list == "string", a.cmd_list + test_positional_args(cmd_action, "string") + # a singleton list returns the contained action + test_positional_args(cmd_action, ["string"]) + + if hasattr(types, 'UnicodeType'): + a2 = eval("SCons.Action.Action(u'string')") + assert isinstance(a2, SCons.Action.CommandAction), a2 + + def line_action(a): + assert isinstance(a, SCons.Action.CommandAction), a + assert a.cmd_list == [ "explicit", "command", "line" ], a.cmd_list + test_positional_args(line_action, [[ "explicit", "command", "line" ]]) + + def test_ListAction(self): + """Test the Action() factory's creation of ListAction objects + """ + a1 = SCons.Action.Action(["x", "y", "z", [ "a", "b", "c"]]) + assert isinstance(a1, SCons.Action.ListAction), a1 + assert a1.varlist is (), a1.varlist + assert isinstance(a1.list[0], SCons.Action.CommandAction), a1.list[0] + assert a1.list[0].cmd_list == "x", a1.list[0].cmd_list + assert isinstance(a1.list[1], SCons.Action.CommandAction), a1.list[1] + assert a1.list[1].cmd_list == "y", a1.list[1].cmd_list + assert isinstance(a1.list[2], SCons.Action.CommandAction), a1.list[2] + assert a1.list[2].cmd_list == "z", a1.list[2].cmd_list + assert isinstance(a1.list[3], SCons.Action.CommandAction), a1.list[3] + assert a1.list[3].cmd_list == [ "a", "b", "c" ], a1.list[3].cmd_list + + a2 = SCons.Action.Action("x\ny\nz") + assert isinstance(a2, SCons.Action.ListAction), a2 + assert a2.varlist is (), a2.varlist + assert isinstance(a2.list[0], SCons.Action.CommandAction), a2.list[0] + assert a2.list[0].cmd_list == "x", a2.list[0].cmd_list + assert isinstance(a2.list[1], SCons.Action.CommandAction), a2.list[1] + assert a2.list[1].cmd_list == "y", a2.list[1].cmd_list + assert isinstance(a2.list[2], SCons.Action.CommandAction), a2.list[2] + assert a2.list[2].cmd_list == "z", a2.list[2].cmd_list + + def foo(): + pass + + a3 = SCons.Action.Action(["x", foo, "z"]) + assert isinstance(a3, SCons.Action.ListAction), a3 + assert a3.varlist is (), a3.varlist + assert isinstance(a3.list[0], SCons.Action.CommandAction), a3.list[0] + assert a3.list[0].cmd_list == "x", a3.list[0].cmd_list + assert isinstance(a3.list[1], SCons.Action.FunctionAction), a3.list[1] + assert a3.list[1].execfunction == foo, a3.list[1].execfunction + assert isinstance(a3.list[2], SCons.Action.CommandAction), a3.list[2] + assert a3.list[2].cmd_list == "z", a3.list[2].cmd_list + + a4 = SCons.Action.Action(["x", "y"], strfunction=foo) + assert isinstance(a4, SCons.Action.ListAction), a4 + assert a4.varlist is (), a4.varlist + assert isinstance(a4.list[0], SCons.Action.CommandAction), a4.list[0] + assert a4.list[0].cmd_list == "x", a4.list[0].cmd_list + assert a4.list[0].strfunction == foo, a4.list[0].strfunction + assert isinstance(a4.list[1], SCons.Action.CommandAction), a4.list[1] + assert a4.list[1].cmd_list == "y", a4.list[1].cmd_list + assert a4.list[1].strfunction == foo, a4.list[1].strfunction + + a5 = SCons.Action.Action("x\ny", strfunction=foo) + assert isinstance(a5, SCons.Action.ListAction), a5 + assert a5.varlist is (), a5.varlist + assert isinstance(a5.list[0], SCons.Action.CommandAction), a5.list[0] + assert a5.list[0].cmd_list == "x", a5.list[0].cmd_list + assert a5.list[0].strfunction == foo, a5.list[0].strfunction + assert isinstance(a5.list[1], SCons.Action.CommandAction), a5.list[1] + assert a5.list[1].cmd_list == "y", a5.list[1].cmd_list + assert a5.list[1].strfunction == foo, a5.list[1].strfunction + + def test_CommandGeneratorAction(self): + """Test the Action() factory's creation of CommandGeneratorAction objects + """ + def foo(): pass + + def gen_action(a, foo=foo): + assert isinstance(a, SCons.Action.CommandGeneratorAction), a + assert a.generator is foo, a.generator + test_positional_args(gen_action, foo, generator=1) + + def test_LazyCmdGeneratorAction(self): + """Test the Action() factory's creation of lazy CommandGeneratorAction objects + """ + def lazy_action(a): + assert isinstance(a, SCons.Action.LazyAction), a + assert a.var == "FOO", a.var + assert a.cmd_list == "${FOO}", a.cmd_list + test_positional_args(lazy_action, "$FOO") + test_positional_args(lazy_action, "${FOO}") + + def test_no_action(self): + """Test when the Action() factory can't create an action object + """ + a5 = SCons.Action.Action(1) + assert a5 is None, a5 + + def test_reentrance(self): + """Test the Action() factory when the action is already an Action object + """ + a1 = SCons.Action.Action("foo") + a2 = SCons.Action.Action(a1) + assert a2 is a1, a2 + +class _ActionActionTestCase(unittest.TestCase): + + def test__init__(self): + """Test creation of _ActionAction objects + """ + + def func1(): + pass + + def func2(): + pass + + def func3(): + pass + + a = SCons.Action._ActionAction() + assert not hasattr(a, 'strfunction') + assert a.cmdstr is _null, a.cmdstr + assert a.varlist == (), a.varlist + assert a.presub is _null, a.presub + assert a.chdir is None, a.chdir + assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc + + assert SCons.Action._ActionAction(kwarg = 1) + assert not hasattr(a, 'kwarg') + assert not hasattr(a, 'strfunction') + assert a.cmdstr is _null, a.cmdstr + assert a.varlist == (), a.varlist + assert a.presub is _null, a.presub + assert a.chdir is None, a.chdir + assert a.exitstatfunc is SCons.Action.default_exitstatfunc, a.exitstatfunc + + a = SCons.Action._ActionAction(strfunction=func1) + assert a.strfunction is func1, a.strfunction + + a = SCons.Action._ActionAction(strfunction=None) + assert not hasattr(a, 'strfunction') + assert a.cmdstr is None, a.cmdstr + + a = SCons.Action._ActionAction(cmdstr='cmdstr') + assert not hasattr(a, 'strfunction') + assert a.cmdstr is 'cmdstr', a.cmdstr + + a = SCons.Action._ActionAction(cmdstr=None) + assert not hasattr(a, 'strfunction') + assert a.cmdstr is None, a.cmdstr + + t = ('a','b','c') + a = SCons.Action._ActionAction(varlist=t) + assert a.varlist == t, a.varlist + + a = SCons.Action._ActionAction(presub=func1) + assert a.presub is func1, a.presub + + a = SCons.Action._ActionAction(chdir=1) + assert a.chdir is 1, a.chdir + + a = SCons.Action._ActionAction(exitstatfunc=func1) + assert a.exitstatfunc is func1, a.exitstatfunc + + a = SCons.Action._ActionAction( + # alphabetical order ... + chdir='x', + cmdstr='cmdstr', + exitstatfunc=func3, + presub=func2, + strfunction=func1, + varlist=t, + ) + assert a.chdir is 'x', a.chdir + assert a.cmdstr is 'cmdstr', a.cmdstr + assert a.exitstatfunc is func3, a.exitstatfunc + assert a.presub is func2, a.presub + assert a.strfunction is func1, a.strfunction + assert a.varlist is t, a.varlist + + def test_dup_keywords(self): + """Test handling of both cmdstr and strfunction arguments + """ + def func(): pass + try: + a = SCons.Action.Action('foo', cmdstr='string', strfunction=func) + except SCons.Errors.UserError, e: + s = str(e) + m = 'Cannot have both strfunction and cmdstr args to Action()' + assert string.find(s, m) != -1, 'Unexpected string: %s' % s + else: + raise Exception, "did not catch expected UserError" + + def test___cmp__(self): + """Test Action comparison + """ + a1 = SCons.Action.Action("x") + a2 = SCons.Action.Action("x") + assert a1 == a2 + a3 = SCons.Action.Action("y") + assert a1 != a3 + assert a2 != a3 + + def test_print_cmd_lines(self): + """Test the print_cmd_lines() method + """ + save_stdout = sys.stdout + + try: + def execfunc(target, source, env): + pass + a = SCons.Action.Action(execfunc) + + sio = StringIO.StringIO() + sys.stdout = sio + a.print_cmd_line("foo bar", None, None, None) + s = sio.getvalue() + assert s == "foo bar\n", s + + finally: + sys.stdout = save_stdout + + def test___call__(self): + """Test calling an Action + """ + save_stdout = sys.stdout + + save_print_actions = SCons.Action.print_actions + save_print_actions_presub = SCons.Action.print_actions_presub + save_execute_actions = SCons.Action.execute_actions + #SCons.Action.print_actions = 0 + + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub', 'xyz') + os.chdir(test.workpath()) + + try: + env = Environment() + + def execfunc(target, source, env): + assert type(target) is type([]), type(target) + assert type(source) is type([]), type(source) + return 7 + a = SCons.Action.Action(execfunc) + + def firstfunc(target, source, env): + assert type(target) is type([]), type(target) + assert type(source) is type([]), type(source) + return 0 + def lastfunc(target, source, env): + assert type(target) is type([]), type(target) + assert type(source) is type([]), type(source) + return 9 + b = SCons.Action.Action([firstfunc, execfunc, lastfunc]) + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env) + assert result.status == 7, result + s = sio.getvalue() + assert s == "execfunc(['out'], ['in'])\n", s + + a.chdir = 'xyz' + expect = "os.chdir(%s)\nexecfunc(['out'], ['in'])\nos.chdir(%s)\n" + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == expect % (repr('xyz'), repr(test.workpath())), s + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env, chdir='sub') + assert result.status == 7, result.status + s = sio.getvalue() + assert s == expect % (repr('sub'), repr(test.workpath())), s + + a.chdir = None + + sio = StringIO.StringIO() + sys.stdout = sio + result = b("out", "in", env) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\n", s + + SCons.Action.execute_actions = 0 + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env) + assert result == 0, result + s = sio.getvalue() + assert s == "execfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = b("out", "in", env) + assert result == 0, result + s = sio.getvalue() + assert s == "firstfunc(['out'], ['in'])\nexecfunc(['out'], ['in'])\nlastfunc(['out'], ['in'])\n", s + + SCons.Action.print_actions_presub = 1 + SCons.Action.execute_actions = 1 + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env, presub=0) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "execfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env, presub=1) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = b(["out"], "in", env, presub=1) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "Building out with action:\n firstfunc(target, source, env)\nfirstfunc(['out'], ['in'])\nBuilding out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = b(["out", "list"], "in", env, presub=1) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "Building out and list with action:\n firstfunc(target, source, env)\nfirstfunc(['out', 'list'], ['in'])\nBuilding out and list with action:\n execfunc(target, source, env)\nexecfunc(['out', 'list'], ['in'])\n", s + + a2 = SCons.Action.Action(execfunc) + + sio = StringIO.StringIO() + sys.stdout = sio + result = a2("out", "in", env) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "Building out with action:\n execfunc(target, source, env)\nexecfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = a2("out", "in", env, presub=0) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == "execfunc(['out'], ['in'])\n", s + + SCons.Action.execute_actions = 0 + + sio = StringIO.StringIO() + sys.stdout = sio + result = a2("out", "in", env, presub=0) + assert result == 0, result + s = sio.getvalue() + assert s == "execfunc(['out'], ['in'])\n", s + + sio = StringIO.StringIO() + sys.stdout = sio + result = a("out", "in", env, presub=0, execute=1, show=0) + assert result.status == 7, result.status + s = sio.getvalue() + assert s == '', s + + sys.stdout = save_stdout + exitstatfunc_result = [] + + def exitstatfunc(stat, result=exitstatfunc_result): + result.append(stat) + return stat + + result = a("out", "in", env, exitstatfunc=exitstatfunc) + assert result == 0, result + assert exitstatfunc_result == [], exitstatfunc_result + + result = a("out", "in", env, execute=1, exitstatfunc=exitstatfunc) + assert result.status == 7, result.status + assert exitstatfunc_result == [7], exitstatfunc_result + + SCons.Action.execute_actions = 1 + + result = [] + def my_print_cmd_line(s, target, source, env, result=result): + result.append(s) + env['PRINT_CMD_LINE_FUNC'] = my_print_cmd_line + a("output", "input", env) + assert result == ["execfunc(['output'], ['input'])"], result + + + finally: + sys.stdout = save_stdout + SCons.Action.print_actions = save_print_actions + SCons.Action.print_actions_presub = save_print_actions_presub + SCons.Action.execute_actions = save_execute_actions + + def test_presub_lines(self): + """Test the presub_lines() method + """ + env = Environment() + a = SCons.Action.Action("x") + s = a.presub_lines(env) + assert s == ['x'], s + + a = SCons.Action.Action(["y", "z"]) + s = a.presub_lines(env) + assert s == ['y', 'z'], s + + def func(): + pass + a = SCons.Action.Action(func) + s = a.presub_lines(env) + assert s == ["func(target, source, env)"], s + + def gen(target, source, env, for_signature): + return 'generat' + env.get('GEN', 'or') + a = SCons.Action.Action(gen, generator=1) + s = a.presub_lines(env) + assert s == ["generator"], s + s = a.presub_lines(Environment(GEN = 'ed')) + assert s == ["generated"], s + + a = SCons.Action.Action("$ACT") + s = a.presub_lines(env) + assert s == [''], s + s = a.presub_lines(Environment(ACT = 'expanded action')) + assert s == ['expanded action'], s + + def test_add(self): + """Test adding Actions to stuff.""" + # Adding actions to other Actions or to stuff that can + # be converted into an Action should produce a ListAction + # containing all the Actions. + def bar(): + return None + baz = SCons.Action.Action(bar, generator=1) + act1 = SCons.Action.Action('foo bar') + act2 = SCons.Action.Action([ 'foo', bar ]) + + sum = act1 + act2 + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 3, len(sum.list) + assert map(lambda x: isinstance(x, SCons.Action.ActionBase), + sum.list) == [ 1, 1, 1 ] + + sum = act1 + act1 + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 2, len(sum.list) + + sum = act2 + act2 + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 4, len(sum.list) + + # Should also be able to add command generators to each other + # or to actions + sum = baz + baz + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 2, len(sum.list) + + sum = baz + act1 + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 2, len(sum.list) + + sum = act2 + baz + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 3, len(sum.list) + + # Also should be able to add Actions to anything that can + # be converted into an action. + sum = act1 + bar + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 2, len(sum.list) + assert isinstance(sum.list[1], SCons.Action.FunctionAction) + + sum = 'foo bar' + act2 + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 3, len(sum.list) + assert isinstance(sum.list[0], SCons.Action.CommandAction) + + sum = [ 'foo', 'bar' ] + act1 + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 3, sum.list + assert isinstance(sum.list[0], SCons.Action.CommandAction) + assert isinstance(sum.list[1], SCons.Action.CommandAction) + + sum = act2 + [ baz, bar ] + assert isinstance(sum, SCons.Action.ListAction), str(sum) + assert len(sum.list) == 4, len(sum.list) + assert isinstance(sum.list[2], SCons.Action.CommandGeneratorAction) + assert isinstance(sum.list[3], SCons.Action.FunctionAction) + + try: + sum = act2 + 1 + except TypeError: + pass + else: + assert 0, "Should have thrown a TypeError adding to an int." + + try: + sum = 1 + act2 + except TypeError: + pass + else: + assert 0, "Should have thrown a TypeError adding to an int." + +class CommandActionTestCase(unittest.TestCase): + + def test___init__(self): + """Test creation of a command Action + """ + a = SCons.Action.CommandAction(["xyzzy"]) + assert a.cmd_list == [ "xyzzy" ], a.cmd_list + assert a.cmdstr is _null, a.cmdstr + + a = SCons.Action.CommandAction(["abra"], cmdstr="cadabra") + assert a.cmd_list == [ "abra" ], a.cmd_list + assert a.cmdstr == "cadabra", a.cmdstr + + def test___str__(self): + """Test fetching the pre-substitution string for command Actions + """ + env = Environment() + act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE') + s = str(act) + assert s == 'xyzzy $TARGET $SOURCE', s + + act = SCons.Action.CommandAction(['xyzzy', + '$TARGET', '$SOURCE', + '$TARGETS', '$SOURCES']) + s = str(act) + assert s == "xyzzy $TARGET $SOURCE $TARGETS $SOURCES", s + + def test_genstring(self): + """Test the genstring() method for command Actions + """ + + env = Environment() + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE') + expect = 'xyzzy $TARGET $SOURCE' + s = act.genstring([], [], env) + assert s == expect, s + s = act.genstring([t1], [s1], env) + assert s == expect, s + s = act.genstring([t1, t2], [s1, s2], env) + assert s == expect, s + + act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES') + expect = 'xyzzy $TARGETS $SOURCES' + s = act.genstring([], [], env) + assert s == expect, s + s = act.genstring([t1], [s1], env) + assert s == expect, s + s = act.genstring([t1, t2], [s1, s2], env) + assert s == expect, s + + act = SCons.Action.CommandAction(['xyzzy', + '$TARGET', '$SOURCE', + '$TARGETS', '$SOURCES']) + expect = "xyzzy $TARGET $SOURCE $TARGETS $SOURCES" + s = act.genstring([], [], env) + assert s == expect, s + s = act.genstring([t1], [s1], env) + assert s == expect, s + s = act.genstring([t1, t2], [s1, s2], env) + assert s == expect, s + + def test_strfunction(self): + """Test fetching the string representation of command Actions + """ + + env = Environment() + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE') + s = act.strfunction([], [], env) + assert s == 'xyzzy', s + s = act.strfunction([t1], [s1], env) + assert s == 'xyzzy t1 s1', s + s = act.strfunction([t1, t2], [s1, s2], env) + assert s == 'xyzzy t1 s1', s + + act = SCons.Action.CommandAction('xyzzy $TARGET $SOURCE', + cmdstr='cmdstr - $SOURCE - $TARGET -') + s = act.strfunction([], [], env) + assert s == 'cmdstr - - -', s + s = act.strfunction([t1], [s1], env) + assert s == 'cmdstr - s1 - t1 -', s + s = act.strfunction([t1, t2], [s1, s2], env) + assert s == 'cmdstr - s1 - t1 -', s + + act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES') + s = act.strfunction([], [], env) + assert s == 'xyzzy', s + s = act.strfunction([t1], [s1], env) + assert s == 'xyzzy t1 s1', s + s = act.strfunction([t1, t2], [s1, s2], env) + assert s == 'xyzzy t1 t2 s1 s2', s + + act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES', + cmdstr='cmdstr = $SOURCES = $TARGETS =') + s = act.strfunction([], [], env) + assert s == 'cmdstr = = =', s + s = act.strfunction([t1], [s1], env) + assert s == 'cmdstr = s1 = t1 =', s + s = act.strfunction([t1, t2], [s1, s2], env) + assert s == 'cmdstr = s1 s2 = t1 t2 =', s + + act = SCons.Action.CommandAction(['xyzzy', + '$TARGET', '$SOURCE', + '$TARGETS', '$SOURCES']) + s = act.strfunction([], [], env) + assert s == 'xyzzy', s + s = act.strfunction([t1], [s1], env) + assert s == 'xyzzy t1 s1 t1 s1', s + s = act.strfunction([t1, t2], [s1, s2], env) + assert s == 'xyzzy t1 s1 t1 t2 s1 s2', s + + act = SCons.Action.CommandAction('xyzzy $TARGETS $SOURCES', + cmdstr='cmdstr\t$TARGETS\n$SOURCES ') + + s = act.strfunction([], [], env) + assert s == 'cmdstr\t\n ', s + s = act.strfunction([t1], [s1], env) + assert s == 'cmdstr\tt1\ns1 ', s + s = act.strfunction([t1, t2], [s1, s2], env) + assert s == 'cmdstr\tt1 t2\ns1 s2 ', s + + def sf(target, source, env): + return "sf was called" + act = SCons.Action.CommandAction('foo', strfunction=sf) + s = act.strfunction([], [], env) + assert s == "sf was called", s + + class actclass1: + def __init__(self, targets, sources, env): + pass + def __call__(self): + return 1 + class actclass2: + def __init__(self, targets, sources, env): + self.strfunction = 5 + def __call__(self): + return 2 + class actclass3: + def __init__(self, targets, sources, env): + pass + def __call__(self): + return 3 + def strfunction(self, targets, sources, env): + return 'actclass3 on %s to get %s'%(str(sources[0]), + str(targets[0])) + class actclass4: + def __init__(self, targets, sources, env): + pass + def __call__(self): + return 4 + strfunction = None + + act1 = SCons.Action.Action(actclass1([t1], [s1], env)) + s = act1.strfunction([t1], [s1], env) + assert s == 'actclass1(["t1"], ["s1"])', s + + act2 = SCons.Action.Action(actclass2([t1], [s1], env)) + s = act2.strfunction([t1], [s1], env) + assert s == 'actclass2(["t1"], ["s1"])', s + + act3 = SCons.Action.Action(actclass3([t1], [s1], env)) + s = act3.strfunction([t1], [s1], env) + assert s == 'actclass3 on s1 to get t1', s + + act4 = SCons.Action.Action(actclass4([t1], [s1], env)) + s = act4.strfunction([t1], [s1], env) + assert s is None, s + + act = SCons.Action.CommandAction("@foo bar") + s = act.strfunction([], [], env) + assert s == "", s + + act = SCons.Action.CommandAction("@-foo bar") + s = act.strfunction([], [], env) + assert s == "", s + + act = SCons.Action.CommandAction("-@foo bar") + s = act.strfunction([], [], env) + assert s == "", s + + act = SCons.Action.CommandAction("-foo bar") + s = act.strfunction([], [], env) + assert s == "foo bar", s + + act = SCons.Action.CommandAction("@ foo bar") + s = act.strfunction([], [], env) + assert s == "", s + + act = SCons.Action.CommandAction("@- foo bar") + s = act.strfunction([], [], env) + assert s == "", s + + act = SCons.Action.CommandAction("-@ foo bar") + s = act.strfunction([], [], env) + assert s == "", s + + act = SCons.Action.CommandAction("- foo bar") + s = act.strfunction([], [], env) + assert s == "foo bar", s + + def test_execute(self): + """Test execution of command Actions + + """ + try: + env = self.env + except AttributeError: + env = Environment() + + cmd1 = r'%s %s %s xyzzy' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd1) + r = act([], [], env.Clone()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'xyzzy'\n", c + + cmd2 = r'%s %s %s $TARGET' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd2) + r = act(DummyNode('foo'), [], env.Clone()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'foo'\n", c + + cmd3 = r'%s %s %s ${TARGETS}' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd3) + r = act(map(DummyNode, ['aaa', 'bbb']), [], env.Clone()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'aaa' 'bbb'\n", c + + cmd4 = r'%s %s %s $SOURCES' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd4) + r = act([], [DummyNode('one'), DummyNode('two')], env.Clone()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'one' 'two'\n", c + + cmd4 = r'%s %s %s ${SOURCES[:2]}' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd4) + sources = [DummyNode('three'), DummyNode('four'), DummyNode('five')] + env2 = env.Clone() + r = act([], source = sources, env = env2) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'three' 'four'\n", c + + cmd5 = r'%s %s %s $TARGET XYZZY' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd5) + env5 = Environment() + if scons_env.has_key('ENV'): + env5['ENV'] = scons_env['ENV'] + PATH = scons_env['ENV'].get('PATH', '') + else: + env5['ENV'] = {} + PATH = '' + + env5['ENV']['XYZZY'] = 'xyzzy' + r = act(target = DummyNode('out5'), source = [], env = env5) + + act = SCons.Action.CommandAction(cmd5) + r = act(target = DummyNode('out5'), + source = [], + env = env.Clone(ENV = {'XYZZY' : 'xyzzy5', + 'PATH' : PATH})) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: 'out5' 'XYZZY'\nact.py: 'xyzzy5'\n", c + + class Obj: + def __init__(self, str): + self._str = str + def __str__(self): + return self._str + def rfile(self): + return self + def get_subst_proxy(self): + return self + + cmd6 = r'%s %s %s ${TARGETS[1]} $TARGET ${SOURCES[:2]}' % (_python_, act_py, outfile) + + act = SCons.Action.CommandAction(cmd6) + r = act(target = [Obj('111'), Obj('222')], + source = [Obj('333'), Obj('444'), Obj('555')], + env = env.Clone()) + assert r == 0 + c = test.read(outfile, 'r') + assert c == "act.py: '222' '111' '333' '444'\n", c + + if os.name == 'nt': + # NT treats execs of directories and non-executable files + # as "file not found" errors + expect_nonexistent = 1 + expect_nonexecutable_file = 1 + expect_nonexecutable_dir = 1 + elif sys.platform == 'cygwin': + expect_nonexistent = 127 + # Newer cygwin seems to return 126 for following + expect_nonexecutable_file = 126 + expect_nonexecutable_dir = 127 + else: + expect_nonexistent = 127 + expect_nonexecutable_file = 126 + expect_nonexecutable_dir = 126 + + # Test that a nonexistent command returns 127 + act = SCons.Action.CommandAction(python + "_no_such_command_") + r = act([], [], env.Clone(out = outfile)) + assert r.status == expect_nonexistent, r.status + + # Test that trying to execute a directory returns 126 + dir, tail = os.path.split(python) + act = SCons.Action.CommandAction(dir) + r = act([], [], env.Clone(out = outfile)) + assert r.status == expect_nonexecutable_file, r.status + + # Test that trying to execute a non-executable file returns 126 + act = SCons.Action.CommandAction(outfile) + r = act([], [], env.Clone(out = outfile)) + assert r.status == expect_nonexecutable_dir, r.status + + act = SCons.Action.CommandAction('%s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r.status == 1, r.status + + act = SCons.Action.CommandAction('@%s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r.status == 1, r.status + + act = SCons.Action.CommandAction('@-%s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r == 0, r + + act = SCons.Action.CommandAction('-%s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r == 0, r + + act = SCons.Action.CommandAction('@ %s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r.status == 1, r.status + + act = SCons.Action.CommandAction('@- %s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r == 0, r + + act = SCons.Action.CommandAction('- %s %s 1' % (_python_, exit_py)) + r = act([], [], env) + assert r == 0, r + + def _DO_NOT_EXECUTE_test_pipe_execute(self): + """Test capturing piped output from an action + + We used to have PIPE_BUILD support built right into + Action.execute() for the benefit of the SConf subsystem, but we've + moved that logic back into SConf itself. We'll leave this code + here, just in case we ever want to resurrect this functionality + in the future, but change the name of the test so it doesn't + get executed as part of the normal test suite. + """ + pipe = open( pipe_file, "w" ) + self.env = Environment(ENV = {'ACTPY_PIPE' : '1'}, PIPE_BUILD = 1, + PSTDOUT = pipe, PSTDERR = pipe) + # everything should also work when piping output + self.test_execute() + self.env['PSTDOUT'].close() + pipe_out = test.read( pipe_file ) + + act_out = "act.py: stdout: executed act.py" + act_err = "act.py: stderr: executed act.py" + + # Since we are now using select(), stdout and stderr can be + # intermixed, so count the lines separately. + outlines = re.findall(act_out, pipe_out) + errlines = re.findall(act_err, pipe_out) + assert len(outlines) == 6, pipe_out + repr(outlines) + assert len(errlines) == 6, pipe_out + repr(errlines) + + # test redirection operators + def test_redirect(self, redir, stdout_msg, stderr_msg): + cmd = r'%s %s %s xyzzy %s' % (_python_, act_py, outfile, redir) + # Write the output and error messages to files because + # Windows can't handle strings that are too big in its + # external environment (os.spawnve() returns EINVAL, + # "Invalid argument"). + stdout_file = test.workpath('stdout_msg') + stderr_file = test.workpath('stderr_msg') + open(stdout_file, 'w').write(stdout_msg) + open(stderr_file, 'w').write(stderr_msg) + pipe = open( pipe_file, "w" ) + act = SCons.Action.CommandAction(cmd) + env = Environment( ENV = {'ACTPY_PIPE' : '1', + 'PIPE_STDOUT_FILE' : stdout_file, + 'PIPE_STDERR_FILE' : stderr_file}, + PIPE_BUILD = 1, + PSTDOUT = pipe, PSTDERR = pipe ) + r = act([], [], env) + pipe.close() + assert r == 0 + return (test.read(outfile2, 'r'), test.read(pipe_file, 'r')) + + (redirected, pipe_out) = test_redirect(self,'> %s' % outfile2, + act_out, act_err) + assert redirected == act_out + assert pipe_out == act_err + + (redirected, pipe_out) = test_redirect(self,'2> %s' % outfile2, + act_out, act_err) + assert redirected == act_err + assert pipe_out == act_out + + (redirected, pipe_out) = test_redirect(self,'> %s 2>&1' % outfile2, + act_out, act_err) + assert (redirected == act_out + act_err or + redirected == act_err + act_out) + assert pipe_out == "" + + act_err = "Long Command Output\n"*3000 + # the size of the string should exceed the system's default block size + act_out = "" + (redirected, pipe_out) = test_redirect(self,'> %s' % outfile2, + act_out, act_err) + assert (redirected == act_out) + assert (pipe_out == act_err) + + def test_set_handler(self): + """Test setting the command handler... + """ + class Test: + def __init__(self): + self.executed = 0 + t=Test() + def func(sh, escape, cmd, args, env, test=t): + test.executed = args + test.shell = sh + return 0 + def escape_func(cmd): + return '**' + cmd + '**' + + class LiteralStr: + def __init__(self, x): + self.data = x + def __str__(self): + return self.data + def escape(self, escape_func): + return escape_func(self.data) + def is_literal(self): + return 1 + + a = SCons.Action.CommandAction(["xyzzy"]) + e = Environment(SPAWN = func) + a([], [], e) + assert t.executed == [ 'xyzzy' ], t.executed + + a = SCons.Action.CommandAction(["xyzzy"]) + e = Environment(SPAWN = '$FUNC', FUNC = func) + a([], [], e) + assert t.executed == [ 'xyzzy' ], t.executed + + a = SCons.Action.CommandAction(["xyzzy"]) + e = Environment(SPAWN = func, SHELL = 'fake shell') + a([], [], e) + assert t.executed == [ 'xyzzy' ], t.executed + assert t.shell == 'fake shell', t.shell + + a = SCons.Action.CommandAction([ LiteralStr("xyzzy") ]) + e = Environment(SPAWN = func, ESCAPE = escape_func) + a([], [], e) + assert t.executed == [ '**xyzzy**' ], t.executed + + def test_get_contents(self): + """Test fetching the contents of a command Action + """ + def CmdGen(target, source, env, for_signature): + assert for_signature + return "%s %s" % \ + (env["foo"], env["bar"]) + + # The number 1 is there to make sure all args get converted to strings. + a = SCons.Action.CommandAction(["|", "$(", "$foo", "|", "$bar", + "$)", "|", "$baz", 1]) + c = a.get_contents(target=[], source=[], + env=Environment(foo = 'FFF', bar = 'BBB', + baz = CmdGen)) + assert c == "| | FFF BBB 1", c + + # Make sure that CommandActions use an Environment's + # subst_target_source() method for substitution. + class SpecialEnvironment(Environment): + def subst_target_source(self, strSubst, raw=0, target=[], source=[]): + return 'subst_target_source: ' + strSubst + + c = a.get_contents(target=DummyNode('ttt'), source = DummyNode('sss'), + env=SpecialEnvironment(foo = 'GGG', bar = 'CCC', + baz = 'ZZZ')) + assert c == 'subst_target_source: | $( $foo | $bar $) | $baz 1', c + + # We've discussed using the real target and source names in a + # CommandAction's signature contents. This would have have the + # advantage of recompiling when a file's name changes (keeping + # debug info current), but it would currently break repository + # logic that will change the file name based on whether the + # files come from a repository or locally. If we ever move to + # that scheme, then all of the '__t1__' and '__s6__' file names + # in the asserts below would change to 't1' and 's6' and the + # like. + t = map(DummyNode, ['t1', 't2', 't3', 't4', 't5', 't6']) + s = map(DummyNode, ['s1', 's2', 's3', 's4', 's5', 's6']) + env = Environment() + + a = SCons.Action.CommandAction(["$TARGET"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "t1", c + + a = SCons.Action.CommandAction(["$TARGETS"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "t1 t2 t3 t4 t5 t6", c + + a = SCons.Action.CommandAction(["${TARGETS[2]}"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "t3", c + + a = SCons.Action.CommandAction(["${TARGETS[3:5]}"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "t4 t5", c + + a = SCons.Action.CommandAction(["$SOURCE"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "s1", c + + a = SCons.Action.CommandAction(["$SOURCES"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "s1 s2 s3 s4 s5 s6", c + + a = SCons.Action.CommandAction(["${SOURCES[2]}"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "s3", c + + a = SCons.Action.CommandAction(["${SOURCES[3:5]}"]) + c = a.get_contents(target=t, source=s, env=env) + assert c == "s4 s5", c + +class CommandGeneratorActionTestCase(unittest.TestCase): + + def factory(self, act, **kw): + """Pass any keywords as a dict""" + return SCons.Action.CommandGeneratorAction(act, kw) + + def test___init__(self): + """Test creation of a command generator Action + """ + def f(target, source, env): + pass + a = self.factory(f) + assert a.generator == f + + def test___str__(self): + """Test the pre-substitution strings for command generator Actions + """ + def f(target, source, env, for_signature, self=self): + + # See if "env" is really a construction environment (or + # looks like one) by accessing the FindIxes attribute. + # (The Tool/mingw.py module has a generator that uses this, + # and the __str__() method used to cause problems by passing + # us a regular dictionary as a fallback.) + + env.FindIxes + return "FOO" + a = self.factory(f) + s = str(a) + assert s == 'FOO', s + + def test_genstring(self): + """Test the command generator Action genstring() method + """ + def f(target, source, env, for_signature, self=self): + dummy = env['dummy'] + self.dummy = dummy + return "$FOO $TARGET $SOURCE $TARGETS $SOURCES" + a = self.factory(f) + self.dummy = 0 + s = a.genstring([], [], env=Environment(FOO='xyzzy', dummy=1)) + assert self.dummy == 1, self.dummy + assert s == "$FOO $TARGET $SOURCE $TARGETS $SOURCES", s + + def test_execute(self): + """Test executing a command generator Action + """ + + def f(target, source, env, for_signature, self=self): + dummy = env['dummy'] + self.dummy = dummy + s = env.subst("$FOO") + assert s == 'foo baz\nbar ack', s + return "$FOO" + def func_action(target, source, env, self=self): + dummy=env['dummy'] + s = env.subst('$foo') + assert s == 'bar', s + self.dummy=dummy + def f2(target, source, env, for_signature, f=func_action): + return f + def ch(sh, escape, cmd, args, env, self=self): + self.cmd.append(cmd) + self.args.append(args) + + a = self.factory(f) + self.dummy = 0 + self.cmd = [] + self.args = [] + a([], [], env=Environment(FOO = 'foo baz\nbar ack', + dummy = 1, + SPAWN = ch)) + assert self.dummy == 1, self.dummy + assert self.cmd == ['foo', 'bar'], self.cmd + assert self.args == [[ 'foo', 'baz' ], [ 'bar', 'ack' ]], self.args + + b = self.factory(f2) + self.dummy = 0 + b(target=[], source=[], env=Environment(foo = 'bar', + dummy = 2 )) + assert self.dummy==2, self.dummy + del self.dummy + + class DummyFile: + def __init__(self, t): + self.t = t + def rfile(self): + self.t.rfile_called = 1 + return self + def get_subst_proxy(self): + return self + def f3(target, source, env, for_signature): + return '' + c = self.factory(f3) + c(target=[], source=DummyFile(self), env=Environment()) + assert self.rfile_called + + def test_get_contents(self): + """Test fetching the contents of a command generator Action + """ + def f(target, source, env, for_signature): + foo = env['foo'] + bar = env['bar'] + assert for_signature, for_signature + return [["guux", foo, "$(", "$ignore", "$)", bar, + '${test("$( foo $bar $)")}' ]] + + def test(mystr): + assert mystr == "$( foo $bar $)", mystr + return "test" + + env = Environment(foo = 'FFF', bar = 'BBB', + ignore = 'foo', test=test) + a = self.factory(f) + c = a.get_contents(target=[], source=[], env=env) + assert c == "guux FFF BBB test", c + + +class FunctionActionTestCase(unittest.TestCase): + + def test___init__(self): + """Test creation of a function Action + """ + def func1(): + pass + def func2(): + pass + def func3(): + pass + def func4(): + pass + + a = SCons.Action.FunctionAction(func1, {}) + assert a.execfunction == func1, a.execfunction + assert isinstance(a.strfunction, types.MethodType), type(a.strfunction) + + a = SCons.Action.FunctionAction(func2, { 'strfunction' : func3 }) + assert a.execfunction == func2, a.execfunction + assert a.strfunction == func3, a.strfunction + + def test___str__(self): + """Test the __str__() method for function Actions + """ + def func1(): + pass + a = SCons.Action.FunctionAction(func1, {}) + s = str(a) + assert s == "func1(target, source, env)", s + + class class1: + def __call__(self): + pass + a = SCons.Action.FunctionAction(class1(), {}) + s = str(a) + assert s == "class1(target, source, env)", s + + def test_execute(self): + """Test executing a function Action + """ + self.inc = 0 + def f(target, source, env): + s = env['s'] + s.inc = s.inc + 1 + s.target = target + s.source=source + assert env.subst("$BAR") == 'foo bar', env.subst("$BAR") + return 0 + a = SCons.Action.FunctionAction(f, {}) + a(target=1, source=2, env=Environment(BAR = 'foo bar', + s = self)) + assert self.inc == 1, self.inc + assert self.source == [2], self.source + assert self.target == [1], self.target + + global count + count = 0 + def function1(target, source, env): + global count + count = count + 1 + for t in target: + open(t, 'w').write("function1\n") + return 1 + + act = SCons.Action.FunctionAction(function1, {}) + r = act(target = [outfile, outfile2], source=[], env=Environment()) + assert r.status == 1, r.status + + assert count == 1, count + c = test.read(outfile, 'r') + assert c == "function1\n", c + c = test.read(outfile2, 'r') + assert c == "function1\n", c + + class class1a: + def __init__(self, target, source, env): + open(env['out'], 'w').write("class1a\n") + + act = SCons.Action.FunctionAction(class1a, {}) + r = act([], [], Environment(out = outfile)) + assert isinstance(r.status, class1a), r.status + c = test.read(outfile, 'r') + assert c == "class1a\n", c + + class class1b: + def __call__(self, target, source, env): + open(env['out'], 'w').write("class1b\n") + return 2 + + act = SCons.Action.FunctionAction(class1b(), {}) + r = act([], [], Environment(out = outfile)) + assert r.status == 2, r.status + c = test.read(outfile, 'r') + assert c == "class1b\n", c + + def build_it(target, source, env, executor=None, self=self): + self.build_it = 1 + return 0 + def string_it(target, source, env, executor=None, self=self): + self.string_it = 1 + return None + act = SCons.Action.FunctionAction(build_it, + { 'strfunction' : string_it }) + r = act([], [], Environment()) + assert r == 0, r + assert self.build_it + assert self.string_it + + def test_get_contents(self): + """Test fetching the contents of a function Action + """ + + def LocalFunc(): + pass + + func_matches = [ + "0,0,0,0,(),(),(d\000\000S),(),()", + "0,0,0,0,(),(),(d\x00\x00S),(),()", + ] + + meth_matches = [ + "1,1,0,0,(),(),(d\000\000S),(),()", + "1,1,0,0,(),(),(d\x00\x00S),(),()", + ] + + def factory(act, **kw): + return SCons.Action.FunctionAction(act, kw) + + a = factory(GlobalFunc) + c = a.get_contents(target=[], source=[], env=Environment()) + assert c in func_matches, repr(c) + + a = factory(LocalFunc) + c = a.get_contents(target=[], source=[], env=Environment()) + assert c in func_matches, repr(c) + + matches_foo = map(lambda x: x + "foo", func_matches) + + a = factory(GlobalFunc, varlist=['XYZ']) + c = a.get_contents(target=[], source=[], env=Environment()) + assert c in func_matches, repr(c) + c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo')) + assert c in matches_foo, repr(c) + + ##TODO: is this set of tests still needed? + # Make sure a bare string varlist works + a = factory(GlobalFunc, varlist='XYZ') + c = a.get_contents(target=[], source=[], env=Environment()) + assert c in func_matches, repr(c) + c = a.get_contents(target=[], source=[], env=Environment(XYZ='foo')) + assert c in matches_foo, repr(c) + + class Foo: + def get_contents(self, target, source, env): + return 'xyzzy' + a = factory(Foo()) + c = a.get_contents(target=[], source=[], env=Environment()) + assert c == 'xyzzy', repr(c) + + class LocalClass: + def LocalMethod(self): + pass + lc = LocalClass() + a = factory(lc.LocalMethod) + c = a.get_contents(target=[], source=[], env=Environment()) + assert c in meth_matches, repr(c) + + def test_strfunction(self): + """Test the FunctionAction.strfunction() method + """ + def func(): + pass + + def factory(act, **kw): + return SCons.Action.FunctionAction(act, kw) + + a = factory(func) + s = a.strfunction(target=[], source=[], env=Environment()) + assert s == 'func([], [])', s + + a = factory(func, strfunction=None) + s = a.strfunction(target=[], source=[], env=Environment()) + assert s is None, s + + a = factory(func, cmdstr='function') + s = a.strfunction(target=[], source=[], env=Environment()) + assert s == 'function', s + +class ListActionTestCase(unittest.TestCase): + + def test___init__(self): + """Test creation of a list of subsidiary Actions + """ + def func(): + pass + a = SCons.Action.ListAction(["x", func, ["y", "z"]]) + assert isinstance(a.list[0], SCons.Action.CommandAction) + assert isinstance(a.list[1], SCons.Action.FunctionAction) + assert isinstance(a.list[2], SCons.Action.ListAction) + assert a.list[2].list[0].cmd_list == 'y' + + def test___str__(self): + """Test the __str__() method for a list of subsidiary Actions + """ + def f(target,source,env): + pass + def g(target,source,env): + pass + a = SCons.Action.ListAction([f, g, "XXX", f]) + s = str(a) + assert s == "f(target, source, env)\ng(target, source, env)\nXXX\nf(target, source, env)", s + + def test_genstring(self): + """Test the genstring() method for a list of subsidiary Actions + """ + def f(target,source,env): + pass + def g(target,source,env,for_signature): + return 'generated %s %s' % (target[0], source[0]) + g = SCons.Action.Action(g, generator=1) + a = SCons.Action.ListAction([f, g, "XXX", f]) + s = a.genstring(['foo.x'], ['bar.y'], Environment()) + assert s == "f(target, source, env)\ngenerated foo.x bar.y\nXXX\nf(target, source, env)", s + + def test_execute(self): + """Test executing a list of subsidiary Actions + """ + self.inc = 0 + def f(target,source,env): + s = env['s'] + s.inc = s.inc + 1 + a = SCons.Action.ListAction([f, f, f]) + a([], [], Environment(s = self)) + assert self.inc == 3, self.inc + + cmd2 = r'%s %s %s syzygy' % (_python_, act_py, outfile) + + def function2(target, source, env): + open(env['out'], 'a').write("function2\n") + return 0 + + class class2a: + def __call__(self, target, source, env): + open(env['out'], 'a').write("class2a\n") + return 0 + + class class2b: + def __init__(self, target, source, env): + open(env['out'], 'a').write("class2b\n") + act = SCons.Action.ListAction([cmd2, function2, class2a(), class2b]) + r = act([], [], Environment(out = outfile)) + assert isinstance(r.status, class2b), r.status + c = test.read(outfile, 'r') + assert c == "act.py: 'syzygy'\nfunction2\nclass2a\nclass2b\n", c + + def test_get_contents(self): + """Test fetching the contents of a list of subsidiary Actions + """ + self.foo=0 + def gen(target, source, env, for_signature): + s = env['s'] + s.foo=1 + return "y" + a = SCons.Action.ListAction(["x", + SCons.Action.Action(gen, generator=1), + "z"]) + c = a.get_contents(target=[], source=[], env=Environment(s = self)) + assert self.foo==1, self.foo + assert c == "xyz", c + +class LazyActionTestCase(unittest.TestCase): + def test___init__(self): + """Test creation of a lazy-evaluation Action + """ + # Environment variable references should create a special type + # of LazyAction that lazily evaluates the variable for whether + # it's a string or something else before doing anything. + a9 = SCons.Action.Action('$FOO') + assert isinstance(a9, SCons.Action.LazyAction), a9 + assert a9.var == 'FOO', a9.var + + a10 = SCons.Action.Action('${FOO}') + assert isinstance(a10, SCons.Action.LazyAction), a10 + assert a10.var == 'FOO', a10.var + + def test_genstring(self): + """Test the lazy-evaluation Action genstring() method + """ + def f(target, source, env): + pass + a = SCons.Action.Action('$BAR') + env1 = Environment(BAR=f, s=self) + env2 = Environment(BAR='xxx', s=self) + s = a.genstring([], [], env=env1) + assert s == "f(target, source, env)", s + s = a.genstring([], [], env=env2) + assert s == 'xxx', s + + def test_execute(self): + """Test executing a lazy-evaluation Action + """ + def f(target, source, env): + s = env['s'] + s.test=1 + return 0 + a = SCons.Action.Action('$BAR') + a([], [], env=Environment(BAR = f, s = self)) + assert self.test == 1, self.test + cmd = r'%s %s %s lazy' % (_python_, act_py, outfile) + a([], [], env=Environment(BAR = cmd, s = self)) + c = test.read(outfile, 'r') + assert c == "act.py: 'lazy'\n", c + + def test_get_contents(self): + """Test fetching the contents of a lazy-evaluation Action + """ + a = SCons.Action.Action("${FOO}") + env = Environment(FOO = [["This", "is", "a", "test"]]) + c = a.get_contents(target=[], source=[], env=env) + assert c == "This is a test", c + +class ActionCallerTestCase(unittest.TestCase): + def test___init__(self): + """Test creation of an ActionCaller""" + ac = SCons.Action.ActionCaller(1, [2, 3], {'FOO' : 4, 'BAR' : 5}) + assert ac.parent == 1, ac.parent + assert ac.args == [2, 3], ac.args + assert ac.kw == {'FOO' : 4, 'BAR' : 5}, ac.kw + + def test_get_contents(self): + """Test fetching the contents of an ActionCaller""" + def strfunc(): + pass + + def LocalFunc(): + pass + + matches = [ + "d\000\000S", + "d\x00\x00S" + ] + + af = SCons.Action.ActionFactory(GlobalFunc, strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c in matches, repr(c) + + af = SCons.Action.ActionFactory(LocalFunc, strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c in matches, repr(c) + + matches = [ + 'd\000\000S', + "d\x00\x00S" + ] + + class LocalActFunc: + def __call__(self): + pass + + af = SCons.Action.ActionFactory(GlobalActFunc(), strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c in matches, repr(c) + + af = SCons.Action.ActionFactory(LocalActFunc(), strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c in matches, repr(c) + + matches = [ + "", + "", + ] + + af = SCons.Action.ActionFactory(str, strfunc) + ac = SCons.Action.ActionCaller(af, [], {}) + c = ac.get_contents([], [], Environment()) + assert c == "" or \ + c == "", repr(c) + + def test___call__(self): + """Test calling an ActionCaller""" + actfunc_args = [] + def actfunc(a1, a2, a3, args=actfunc_args): + args.extend([a1, a2, a3]) + def strfunc(a1, a2, a3): + pass + + e = Environment(FOO = 2, BAR = 5) + + af = SCons.Action.ActionFactory(actfunc, strfunc) + ac = SCons.Action.ActionCaller(af, ['$__env__', '$FOO', 3], {}) + ac([], [], e) + assert actfunc_args[0] is e, actfunc_args + assert actfunc_args[1] == '2', actfunc_args + assert actfunc_args[2] == 3, actfunc_args + del actfunc_args[:] + + ac = SCons.Action.ActionCaller(af, [], {'a3' : '$__env__', 'a2' : '$BAR', 'a1' : 4}) + ac([], [], e) + assert actfunc_args[0] == 4, actfunc_args + assert actfunc_args[1] == '5', actfunc_args + assert actfunc_args[2] is e, actfunc_args + del actfunc_args[:] + + def test_strfunction(self): + """Test calling the ActionCaller strfunction() method""" + strfunc_args = [] + def actfunc(a1, a2, a3, a4): + pass + def strfunc(a1, a2, a3, a4, args=strfunc_args): + args.extend([a1, a2, a3, a4]) + + af = SCons.Action.ActionFactory(actfunc, strfunc) + ac = SCons.Action.ActionCaller(af, [1, '$FOO', 3, '$WS'], {}) + ac.strfunction([], [], Environment(FOO = 2, WS='white space')) + assert strfunc_args == [1, '2', 3, 'white space'], strfunc_args + + del strfunc_args[:] + d = {'a3' : 6, 'a2' : '$BAR', 'a1' : 4, 'a4' : '$WS'} + ac = SCons.Action.ActionCaller(af, [], d) + ac.strfunction([], [], Environment(BAR = 5, WS='w s')) + assert strfunc_args == [4, '5', 6, 'w s'], strfunc_args + +class ActionFactoryTestCase(unittest.TestCase): + def test___init__(self): + """Test creation of an ActionFactory""" + def actfunc(): + pass + def strfunc(): + pass + ac = SCons.Action.ActionFactory(actfunc, strfunc) + assert ac.actfunc is actfunc, ac.actfunc + assert ac.strfunc is strfunc, ac.strfunc + + def test___call__(self): + """Test calling whatever's returned from an ActionFactory""" + actfunc_args = [] + strfunc_args = [] + def actfunc(a1, a2, a3, args=actfunc_args): + args.extend([a1, a2, a3]) + def strfunc(a1, a2, a3, args=strfunc_args): + args.extend([a1, a2, a3]) + af = SCons.Action.ActionFactory(actfunc, strfunc) + af(3, 6, 9)([], [], Environment()) + assert actfunc_args == [3, 6, 9], actfunc_args + assert strfunc_args == [3, 6, 9], strfunc_args + + +class ActionCompareTestCase(unittest.TestCase): + + def test_1_solo_name(self): + """Test Lazy Cmd Generator Action get_name alone. + + Basically ensures we can locate the builder, comparing it to + itself along the way.""" + bar = SCons.Builder.Builder(action = {}) + env = Environment( BUILDERS = {'BAR' : bar} ) + name = bar.get_name(env) + assert name == 'BAR', name + + def test_2_multi_name(self): + """Test LazyCmdGenerator Action get_name multi builders. + + Ensure that we can compare builders (and thereby actions) to + each other safely.""" + foo = SCons.Builder.Builder(action = '$FOO', suffix = '.foo') + bar = SCons.Builder.Builder(action = {}) + assert foo != bar + assert foo.action != bar.action + env = Environment( BUILDERS = {'FOO' : foo, + 'BAR' : bar} ) + name = foo.get_name(env) + assert name == 'FOO', name + name = bar.get_name(env) + assert name == 'BAR', name + + def test_3_dict_names(self): + """Test Action/Suffix dicts with get_name. + + Verifies that Action/Suffix dictionaries work correctly, + especially two builders that can generate the same suffix, + where one of the builders has a suffix dictionary with a None + key.""" + + foo = SCons.Builder.Builder(action = '$FOO', suffix = '.foo') + bar = SCons.Builder.Builder(action = {}, suffix={None:'.bar'}) + bar.add_action('.cow', "$MOO") + dog = SCons.Builder.Builder(suffix = '.bar') + + env = Environment( BUILDERS = {'FOO' : foo, + 'BAR' : bar, + 'DOG' : dog} ) + + assert foo.get_name(env) == 'FOO', foo.get_name(env) + assert bar.get_name(env) == 'BAR', bar.get_name(env) + assert dog.get_name(env) == 'DOG', dog.get_name(env) + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ _ActionActionTestCase, + ActionTestCase, + CommandActionTestCase, + CommandGeneratorActionTestCase, + FunctionActionTestCase, + ListActionTestCase, + LazyActionTestCase, + ActionCallerTestCase, + ActionFactoryTestCase, + ActionCompareTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Builder.py b/src/engine/SCons/Builder.py new file mode 100644 index 0000000..0d7e065 --- /dev/null +++ b/src/engine/SCons/Builder.py @@ -0,0 +1,868 @@ +"""SCons.Builder + +Builder object subsystem. + +A Builder object is a callable that encapsulates information about how +to execute actions to create a target Node (file) from source Nodes +(files), and how to create those dependencies for tracking. + +The main entry point here is the Builder() factory method. This provides +a procedural interface that creates the right underlying Builder object +based on the keyword arguments supplied and the types of the arguments. + +The goal is for this external interface to be simple enough that the +vast majority of users can create new Builders as necessary to support +building new types of files in their configurations, without having to +dive any deeper into this subsystem. + +The base class here is BuilderBase. This is a concrete base class which +does, in fact, represent the Builder objects that we (or users) create. + +There is also a proxy that looks like a Builder: + + CompositeBuilder + + This proxies for a Builder with an action that is actually a + dictionary that knows how to map file suffixes to a specific + action. This is so that we can invoke different actions + (compilers, compile options) for different flavors of source + files. + +Builders and their proxies have the following public interface methods +used by other modules: + + __call__() + THE public interface. Calling a Builder object (with the + use of internal helper methods) sets up the target and source + dependencies, appropriate mapping to a specific action, and the + environment manipulation necessary for overridden construction + variable. This also takes care of warning about possible mistakes + in keyword arguments. + + add_emitter() + Adds an emitter for a specific file suffix, used by some Tool + modules to specify that (for example) a yacc invocation on a .y + can create a .h *and* a .c file. + + add_action() + Adds an action for a specific file suffix, heavily used by + Tool modules to add their specific action(s) for turning + a source file into an object file to the global static + and shared object file Builders. + +There are the following methods for internal use within this module: + + _execute() + The internal method that handles the heavily lifting when a + Builder is called. This is used so that the __call__() methods + can set up warning about possible mistakes in keyword-argument + overrides, and *then* execute all of the steps necessary so that + the warnings only occur once. + + get_name() + Returns the Builder's name within a specific Environment, + primarily used to try to return helpful information in error + messages. + + adjust_suffix() + get_prefix() + get_suffix() + get_src_suffix() + set_src_suffix() + Miscellaneous stuff for handling the prefix and suffix + manipulation we use in turning source file names into target + file names. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Builder.py 4577 2009/12/27 19:44:43 scons" + +import UserDict +import UserList + +import SCons.Action +from SCons.Debug import logInstanceCreation +from SCons.Errors import InternalError, UserError +import SCons.Executor +import SCons.Memoize +import SCons.Node +import SCons.Node.FS +import SCons.Util +import SCons.Warnings + +class _Null: + pass + +_null = _Null + +def match_splitext(path, suffixes = []): + if suffixes: + matchsuf = filter(lambda S,path=path: path[-len(S):] == S, + suffixes) + if matchsuf: + suf = max(map(None, map(len, matchsuf), matchsuf))[1] + return [path[:-len(suf)], path[-len(suf):]] + return SCons.Util.splitext(path) + +class DictCmdGenerator(SCons.Util.Selector): + """This is a callable class that can be used as a + command generator function. It holds on to a dictionary + mapping file suffixes to Actions. It uses that dictionary + to return the proper action based on the file suffix of + the source file.""" + + def __init__(self, dict=None, source_ext_match=1): + SCons.Util.Selector.__init__(self, dict) + self.source_ext_match = source_ext_match + + def src_suffixes(self): + return self.keys() + + def add_action(self, suffix, action): + """Add a suffix-action pair to the mapping. + """ + self[suffix] = action + + def __call__(self, target, source, env, for_signature): + if not source: + return [] + + if self.source_ext_match: + suffixes = self.src_suffixes() + ext = None + for src in map(str, source): + my_ext = match_splitext(src, suffixes)[1] + if ext and my_ext != ext: + raise UserError("While building `%s' from `%s': Cannot build multiple sources with different extensions: %s, %s" % (repr(map(str, target)), src, ext, my_ext)) + ext = my_ext + else: + ext = match_splitext(str(source[0]), self.src_suffixes())[1] + + if not ext: + #return ext + raise UserError("While building `%s': Cannot deduce file extension from source files: %s" % (repr(map(str, target)), repr(map(str, source)))) + + try: + ret = SCons.Util.Selector.__call__(self, env, source, ext) + except KeyError, e: + raise UserError("Ambiguous suffixes after environment substitution: %s == %s == %s" % (e[0], e[1], e[2])) + if ret is None: + raise UserError("While building `%s' from `%s': Don't know how to build from a source file with suffix `%s'. Expected a suffix in this list: %s." % \ + (repr(map(str, target)), repr(map(str, source)), ext, repr(self.keys()))) + return ret + +class CallableSelector(SCons.Util.Selector): + """A callable dictionary that will, in turn, call the value it + finds if it can.""" + def __call__(self, env, source): + value = SCons.Util.Selector.__call__(self, env, source) + if callable(value): + value = value(env, source) + return value + +class DictEmitter(SCons.Util.Selector): + """A callable dictionary that maps file suffixes to emitters. + When called, it finds the right emitter in its dictionary for the + suffix of the first source file, and calls that emitter to get the + right lists of targets and sources to return. If there's no emitter + for the suffix in its dictionary, the original target and source are + returned. + """ + def __call__(self, target, source, env): + emitter = SCons.Util.Selector.__call__(self, env, source) + if emitter: + target, source = emitter(target, source, env) + return (target, source) + +class ListEmitter(UserList.UserList): + """A callable list of emitters that calls each in sequence, + returning the result. + """ + def __call__(self, target, source, env): + for e in self.data: + target, source = e(target, source, env) + return (target, source) + +# These are a common errors when calling a Builder; +# they are similar to the 'target' and 'source' keyword args to builders, +# so we issue warnings when we see them. The warnings can, of course, +# be disabled. +misleading_keywords = { + 'targets' : 'target', + 'sources' : 'source', +} + +class OverrideWarner(UserDict.UserDict): + """A class for warning about keyword arguments that we use as + overrides in a Builder call. + + This class exists to handle the fact that a single Builder call + can actually invoke multiple builders. This class only emits the + warnings once, no matter how many Builders are invoked. + """ + def __init__(self, dict): + UserDict.UserDict.__init__(self, dict) + if __debug__: logInstanceCreation(self, 'Builder.OverrideWarner') + self.already_warned = None + def warn(self): + if self.already_warned: + return + for k in self.keys(): + if misleading_keywords.has_key(k): + alt = misleading_keywords[k] + msg = "Did you mean to use `%s' instead of `%s'?" % (alt, k) + SCons.Warnings.warn(SCons.Warnings.MisleadingKeywordsWarning, msg) + self.already_warned = 1 + +def Builder(**kw): + """A factory for builder objects.""" + composite = None + if kw.has_key('generator'): + if kw.has_key('action'): + raise UserError, "You must not specify both an action and a generator." + kw['action'] = SCons.Action.CommandGeneratorAction(kw['generator'], {}) + del kw['generator'] + elif kw.has_key('action'): + source_ext_match = kw.get('source_ext_match', 1) + if kw.has_key('source_ext_match'): + del kw['source_ext_match'] + if SCons.Util.is_Dict(kw['action']): + composite = DictCmdGenerator(kw['action'], source_ext_match) + kw['action'] = SCons.Action.CommandGeneratorAction(composite, {}) + kw['src_suffix'] = composite.src_suffixes() + else: + kw['action'] = SCons.Action.Action(kw['action']) + + if kw.has_key('emitter'): + emitter = kw['emitter'] + if SCons.Util.is_String(emitter): + # This allows users to pass in an Environment + # variable reference (like "$FOO") as an emitter. + # We will look in that Environment variable for + # a callable to use as the actual emitter. + var = SCons.Util.get_environment_var(emitter) + if not var: + raise UserError, "Supplied emitter '%s' does not appear to refer to an Environment variable" % emitter + kw['emitter'] = EmitterProxy(var) + elif SCons.Util.is_Dict(emitter): + kw['emitter'] = DictEmitter(emitter) + elif SCons.Util.is_List(emitter): + kw['emitter'] = ListEmitter(emitter) + + result = apply(BuilderBase, (), kw) + + if not composite is None: + result = CompositeBuilder(result, composite) + + return result + +def _node_errors(builder, env, tlist, slist): + """Validate that the lists of target and source nodes are + legal for this builder and environment. Raise errors or + issue warnings as appropriate. + """ + + # First, figure out if there are any errors in the way the targets + # were specified. + for t in tlist: + if t.side_effect: + raise UserError, "Multiple ways to build the same target were specified for: %s" % t + if t.has_explicit_builder(): + if not t.env is None and not t.env is env: + action = t.builder.action + t_contents = action.get_contents(tlist, slist, t.env) + contents = action.get_contents(tlist, slist, env) + + if t_contents == contents: + msg = "Two different environments were specified for target %s,\n\tbut they appear to have the same action: %s" % (t, action.genstring(tlist, slist, t.env)) + SCons.Warnings.warn(SCons.Warnings.DuplicateEnvironmentWarning, msg) + else: + msg = "Two environments with different actions were specified for the same target: %s" % t + raise UserError, msg + if builder.multi: + if t.builder != builder: + msg = "Two different builders (%s and %s) were specified for the same target: %s" % (t.builder.get_name(env), builder.get_name(env), t) + raise UserError, msg + # TODO(batch): list constructed each time! + if t.get_executor().get_all_targets() != tlist: + msg = "Two different target lists have a target in common: %s (from %s and from %s)" % (t, map(str, t.get_executor().get_all_targets()), map(str, tlist)) + raise UserError, msg + elif t.sources != slist: + msg = "Multiple ways to build the same target were specified for: %s (from %s and from %s)" % (t, map(str, t.sources), map(str, slist)) + raise UserError, msg + + if builder.single_source: + if len(slist) > 1: + raise UserError, "More than one source given for single-source builder: targets=%s sources=%s" % (map(str,tlist), map(str,slist)) + +class EmitterProxy: + """This is a callable class that can act as a + Builder emitter. It holds on to a string that + is a key into an Environment dictionary, and will + look there at actual build time to see if it holds + a callable. If so, we will call that as the actual + emitter.""" + def __init__(self, var): + self.var = SCons.Util.to_String(var) + + def __call__(self, target, source, env): + emitter = self.var + + # Recursively substitute the variable. + # We can't use env.subst() because it deals only + # in strings. Maybe we should change that? + while SCons.Util.is_String(emitter) and env.has_key(emitter): + emitter = env[emitter] + if callable(emitter): + target, source = emitter(target, source, env) + elif SCons.Util.is_List(emitter): + for e in emitter: + target, source = e(target, source, env) + + return (target, source) + + + def __cmp__(self, other): + return cmp(self.var, other.var) + +class BuilderBase: + """Base class for Builders, objects that create output + nodes (files) from input nodes (files). + """ + + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + memoizer_counters = [] + + def __init__(self, action = None, + prefix = '', + suffix = '', + src_suffix = '', + target_factory = None, + source_factory = None, + target_scanner = None, + source_scanner = None, + emitter = None, + multi = 0, + env = None, + single_source = 0, + name = None, + chdir = _null, + is_explicit = 1, + src_builder = None, + ensure_suffix = False, + **overrides): + if __debug__: logInstanceCreation(self, 'Builder.BuilderBase') + self._memo = {} + self.action = action + self.multi = multi + if SCons.Util.is_Dict(prefix): + prefix = CallableSelector(prefix) + self.prefix = prefix + if SCons.Util.is_Dict(suffix): + suffix = CallableSelector(suffix) + self.env = env + self.single_source = single_source + if overrides.has_key('overrides'): + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "The \"overrides\" keyword to Builder() creation has been deprecated;\n" +\ + "\tspecify the items as keyword arguments to the Builder() call instead.") + overrides.update(overrides['overrides']) + del overrides['overrides'] + if overrides.has_key('scanner'): + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "The \"scanner\" keyword to Builder() creation has been deprecated;\n" + "\tuse: source_scanner or target_scanner as appropriate.") + del overrides['scanner'] + self.overrides = overrides + + self.set_suffix(suffix) + self.set_src_suffix(src_suffix) + self.ensure_suffix = ensure_suffix + + self.target_factory = target_factory + self.source_factory = source_factory + self.target_scanner = target_scanner + self.source_scanner = source_scanner + + self.emitter = emitter + + # Optional Builder name should only be used for Builders + # that don't get attached to construction environments. + if name: + self.name = name + self.executor_kw = {} + if not chdir is _null: + self.executor_kw['chdir'] = chdir + self.is_explicit = is_explicit + + if src_builder is None: + src_builder = [] + elif not SCons.Util.is_List(src_builder): + src_builder = [ src_builder ] + self.src_builder = src_builder + + def __nonzero__(self): + raise InternalError, "Do not test for the Node.builder attribute directly; use Node.has_builder() instead" + + def get_name(self, env): + """Attempts to get the name of the Builder. + + Look at the BUILDERS variable of env, expecting it to be a + dictionary containing this Builder, and return the key of the + dictionary. If there's no key, then return a directly-configured + name (if there is one) or the name of the class (by default).""" + + try: + index = env['BUILDERS'].values().index(self) + return env['BUILDERS'].keys()[index] + except (AttributeError, KeyError, TypeError, ValueError): + try: + return self.name + except AttributeError: + return str(self.__class__) + + def __cmp__(self, other): + return cmp(self.__dict__, other.__dict__) + + def splitext(self, path, env=None): + if not env: + env = self.env + if env: + suffixes = self.src_suffixes(env) + else: + suffixes = [] + return match_splitext(path, suffixes) + + def _adjustixes(self, files, pre, suf, ensure_suffix=False): + if not files: + return [] + result = [] + if not SCons.Util.is_List(files): + files = [files] + + for f in files: + if SCons.Util.is_String(f): + f = SCons.Util.adjustixes(f, pre, suf, ensure_suffix) + result.append(f) + return result + + def _create_nodes(self, env, target = None, source = None): + """Create and return lists of target and source nodes. + """ + src_suf = self.get_src_suffix(env) + + target_factory = env.get_factory(self.target_factory) + source_factory = env.get_factory(self.source_factory) + + source = self._adjustixes(source, None, src_suf) + slist = env.arg2nodes(source, source_factory) + + pre = self.get_prefix(env, slist) + suf = self.get_suffix(env, slist) + + if target is None: + try: + t_from_s = slist[0].target_from_source + except AttributeError: + raise UserError("Do not know how to create a target from source `%s'" % slist[0]) + except IndexError: + tlist = [] + else: + splitext = lambda S,self=self,env=env: self.splitext(S,env) + tlist = [ t_from_s(pre, suf, splitext) ] + else: + target = self._adjustixes(target, pre, suf, self.ensure_suffix) + tlist = env.arg2nodes(target, target_factory, target=target, source=source) + + if self.emitter: + # The emitter is going to do str(node), but because we're + # being called *from* a builder invocation, the new targets + # don't yet have a builder set on them and will look like + # source files. Fool the emitter's str() calls by setting + # up a temporary builder on the new targets. + new_targets = [] + for t in tlist: + if not t.is_derived(): + t.builder_set(self) + new_targets.append(t) + + orig_tlist = tlist[:] + orig_slist = slist[:] + + target, source = self.emitter(target=tlist, source=slist, env=env) + + # Now delete the temporary builders that we attached to any + # new targets, so that _node_errors() doesn't do weird stuff + # to them because it thinks they already have builders. + for t in new_targets: + if t.builder is self: + # Only delete the temporary builder if the emitter + # didn't change it on us. + t.builder_set(None) + + # Have to call arg2nodes yet again, since it is legal for + # emitters to spit out strings as well as Node instances. + tlist = env.arg2nodes(target, target_factory, + target=orig_tlist, source=orig_slist) + slist = env.arg2nodes(source, source_factory, + target=orig_tlist, source=orig_slist) + + return tlist, slist + + def _execute(self, env, target, source, overwarn={}, executor_kw={}): + # We now assume that target and source are lists or None. + if self.src_builder: + source = self.src_builder_sources(env, source, overwarn) + + if self.single_source and len(source) > 1 and target is None: + result = [] + if target is None: target = [None]*len(source) + for tgt, src in zip(target, source): + if not tgt is None: tgt = [tgt] + if not src is None: src = [src] + result.extend(self._execute(env, tgt, src, overwarn)) + return SCons.Node.NodeList(result) + + overwarn.warn() + + tlist, slist = self._create_nodes(env, target, source) + + # Check for errors with the specified target/source lists. + _node_errors(self, env, tlist, slist) + + # The targets are fine, so find or make the appropriate Executor to + # build this particular list of targets from this particular list of + # sources. + + executor = None + key = None + + if self.multi: + try: + executor = tlist[0].get_executor(create = 0) + except (AttributeError, IndexError): + pass + else: + executor.add_sources(slist) + + if executor is None: + if not self.action: + fmt = "Builder %s must have an action to build %s." + raise UserError, fmt % (self.get_name(env or self.env), + map(str,tlist)) + key = self.action.batch_key(env or self.env, tlist, slist) + if key: + try: + executor = SCons.Executor.GetBatchExecutor(key) + except KeyError: + pass + else: + executor.add_batch(tlist, slist) + + if executor is None: + executor = SCons.Executor.Executor(self.action, env, [], + tlist, slist, executor_kw) + if key: + SCons.Executor.AddBatchExecutor(key, executor) + + # Now set up the relevant information in the target Nodes themselves. + for t in tlist: + t.cwd = env.fs.getcwd() + t.builder_set(self) + t.env_set(env) + t.add_source(slist) + t.set_executor(executor) + t.set_explicit(self.is_explicit) + + return SCons.Node.NodeList(tlist) + + def __call__(self, env, target=None, source=None, chdir=_null, **kw): + # We now assume that target and source are lists or None. + # The caller (typically Environment.BuilderWrapper) is + # responsible for converting any scalar values to lists. + if chdir is _null: + ekw = self.executor_kw + else: + ekw = self.executor_kw.copy() + ekw['chdir'] = chdir + if kw: + if kw.has_key('srcdir'): + def prependDirIfRelative(f, srcdir=kw['srcdir']): + import os.path + if SCons.Util.is_String(f) and not os.path.isabs(f): + f = os.path.join(srcdir, f) + return f + if not SCons.Util.is_List(source): + source = [source] + source = map(prependDirIfRelative, source) + del kw['srcdir'] + if self.overrides: + env_kw = self.overrides.copy() + env_kw.update(kw) + else: + env_kw = kw + else: + env_kw = self.overrides + env = env.Override(env_kw) + return self._execute(env, target, source, OverrideWarner(kw), ekw) + + def adjust_suffix(self, suff): + if suff and not suff[0] in [ '.', '_', '$' ]: + return '.' + suff + return suff + + def get_prefix(self, env, sources=[]): + prefix = self.prefix + if callable(prefix): + prefix = prefix(env, sources) + return env.subst(prefix) + + def set_suffix(self, suffix): + if not callable(suffix): + suffix = self.adjust_suffix(suffix) + self.suffix = suffix + + def get_suffix(self, env, sources=[]): + suffix = self.suffix + if callable(suffix): + suffix = suffix(env, sources) + return env.subst(suffix) + + def set_src_suffix(self, src_suffix): + if not src_suffix: + src_suffix = [] + elif not SCons.Util.is_List(src_suffix): + src_suffix = [ src_suffix ] + adjust = lambda suf, s=self: \ + callable(suf) and suf or s.adjust_suffix(suf) + self.src_suffix = map(adjust, src_suffix) + + def get_src_suffix(self, env): + """Get the first src_suffix in the list of src_suffixes.""" + ret = self.src_suffixes(env) + if not ret: + return '' + return ret[0] + + def add_emitter(self, suffix, emitter): + """Add a suffix-emitter mapping to this Builder. + + This assumes that emitter has been initialized with an + appropriate dictionary type, and will throw a TypeError if + not, so the caller is responsible for knowing that this is an + appropriate method to call for the Builder in question. + """ + self.emitter[suffix] = emitter + + def add_src_builder(self, builder): + """ + Add a new Builder to the list of src_builders. + + This requires wiping out cached values so that the computed + lists of source suffixes get re-calculated. + """ + self._memo = {} + self.src_builder.append(builder) + + def _get_sdict(self, env): + """ + Returns a dictionary mapping all of the source suffixes of all + src_builders of this Builder to the underlying Builder that + should be called first. + + This dictionary is used for each target specified, so we save a + lot of extra computation by memoizing it for each construction + environment. + + Note that this is re-computed each time, not cached, because there + might be changes to one of our source Builders (or one of their + source Builders, and so on, and so on...) that we can't "see." + + The underlying methods we call cache their computed values, + though, so we hope repeatedly aggregating them into a dictionary + like this won't be too big a hit. We may need to look for a + better way to do this if performance data show this has turned + into a significant bottleneck. + """ + sdict = {} + for bld in self.get_src_builders(env): + for suf in bld.src_suffixes(env): + sdict[suf] = bld + return sdict + + def src_builder_sources(self, env, source, overwarn={}): + sdict = self._get_sdict(env) + + src_suffixes = self.src_suffixes(env) + + lengths = list(set(map(len, src_suffixes))) + + def match_src_suffix(name, src_suffixes=src_suffixes, lengths=lengths): + node_suffixes = map(lambda l, n=name: n[-l:], lengths) + for suf in src_suffixes: + if suf in node_suffixes: + return suf + return None + + result = [] + for s in SCons.Util.flatten(source): + if SCons.Util.is_String(s): + match_suffix = match_src_suffix(env.subst(s)) + if not match_suffix and not '.' in s: + src_suf = self.get_src_suffix(env) + s = self._adjustixes(s, None, src_suf)[0] + else: + match_suffix = match_src_suffix(s.name) + if match_suffix: + try: + bld = sdict[match_suffix] + except KeyError: + result.append(s) + else: + tlist = bld._execute(env, None, [s], overwarn) + # If the subsidiary Builder returned more than one + # target, then filter out any sources that this + # Builder isn't capable of building. + if len(tlist) > 1: + mss = lambda t, m=match_src_suffix: m(t.name) + tlist = filter(mss, tlist) + result.extend(tlist) + else: + result.append(s) + + source_factory = env.get_factory(self.source_factory) + + return env.arg2nodes(result, source_factory) + + def _get_src_builders_key(self, env): + return id(env) + + memoizer_counters.append(SCons.Memoize.CountDict('get_src_builders', _get_src_builders_key)) + + def get_src_builders(self, env): + """ + Returns the list of source Builders for this Builder. + + This exists mainly to look up Builders referenced as + strings in the 'BUILDER' variable of the construction + environment and cache the result. + """ + memo_key = id(env) + try: + memo_dict = self._memo['get_src_builders'] + except KeyError: + memo_dict = {} + self._memo['get_src_builders'] = memo_dict + else: + try: + return memo_dict[memo_key] + except KeyError: + pass + + builders = [] + for bld in self.src_builder: + if SCons.Util.is_String(bld): + try: + bld = env['BUILDERS'][bld] + except KeyError: + continue + builders.append(bld) + + memo_dict[memo_key] = builders + return builders + + def _subst_src_suffixes_key(self, env): + return id(env) + + memoizer_counters.append(SCons.Memoize.CountDict('subst_src_suffixes', _subst_src_suffixes_key)) + + def subst_src_suffixes(self, env): + """ + The suffix list may contain construction variable expansions, + so we have to evaluate the individual strings. To avoid doing + this over and over, we memoize the results for each construction + environment. + """ + memo_key = id(env) + try: + memo_dict = self._memo['subst_src_suffixes'] + except KeyError: + memo_dict = {} + self._memo['subst_src_suffixes'] = memo_dict + else: + try: + return memo_dict[memo_key] + except KeyError: + pass + suffixes = map(lambda x, s=self, e=env: e.subst(x), self.src_suffix) + memo_dict[memo_key] = suffixes + return suffixes + + def src_suffixes(self, env): + """ + Returns the list of source suffixes for all src_builders of this + Builder. + + This is essentially a recursive descent of the src_builder "tree." + (This value isn't cached because there may be changes in a + src_builder many levels deep that we can't see.) + """ + sdict = {} + suffixes = self.subst_src_suffixes(env) + for s in suffixes: + sdict[s] = 1 + for builder in self.get_src_builders(env): + for s in builder.src_suffixes(env): + if not sdict.has_key(s): + sdict[s] = 1 + suffixes.append(s) + return suffixes + +class CompositeBuilder(SCons.Util.Proxy): + """A Builder Proxy whose main purpose is to always have + a DictCmdGenerator as its action, and to provide access + to the DictCmdGenerator's add_action() method. + """ + + def __init__(self, builder, cmdgen): + if __debug__: logInstanceCreation(self, 'Builder.CompositeBuilder') + SCons.Util.Proxy.__init__(self, builder) + + # cmdgen should always be an instance of DictCmdGenerator. + self.cmdgen = cmdgen + self.builder = builder + + def add_action(self, suffix, action): + self.cmdgen.add_action(suffix, action) + self.set_src_suffix(self.cmdgen.src_suffixes()) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/BuilderTests.py b/src/engine/SCons/BuilderTests.py new file mode 100644 index 0000000..2faaa2d --- /dev/null +++ b/src/engine/SCons/BuilderTests.py @@ -0,0 +1,1650 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/BuilderTests.py 4577 2009/12/27 19:44:43 scons" + +# Define a null function for use as a builder action. +# Where this is defined in the file seems to affect its +# byte-code contents, so try to minimize changes by +# defining it here, before we even import anything. +def Func(): + pass + +import os.path +import re +import sys +import types +import StringIO +import unittest +import UserList + +import TestCmd + +import SCons.Action +import SCons.Builder +import SCons.Environment +import SCons.Errors +import SCons.Subst +import SCons.Util + +sys.stdout = StringIO.StringIO() + +# Initial setup of the common environment for all tests, +# a temporary working directory containing a +# script for writing arguments to an output file. +# +# We don't do this as a setUp() method because it's +# unnecessary to create a separate directory and script +# for each test, they can just use the one. +test = TestCmd.TestCmd(workdir = '') + +outfile = test.workpath('outfile') +outfile2 = test.workpath('outfile2') + +infile = test.workpath('infile') +test.write(infile, "infile\n") + +show_string = None + +scons_env = SCons.Environment.Environment() + +env_arg2nodes_called = None + +class Environment: + def __init__(self, **kw): + self.d = {} + self.d['SHELL'] = scons_env['SHELL'] + self.d['SPAWN'] = scons_env['SPAWN'] + self.d['ESCAPE'] = scons_env['ESCAPE'] + for k, v in kw.items(): + self.d[k] = v + global env_arg2nodes_called + env_arg2nodes_called = None + self.scanner = None + self.fs = SCons.Node.FS.FS() + def subst(self, s): + if not SCons.Util.is_String(s): + return s + def substitute(m, d=self.d): + return d.get(m.group(1), '') + return re.sub(r'\$(\w+)', substitute, s) + def subst_target_source(self, string, raw=0, target=None, + source=None, dict=None, conv=None): + return SCons.Subst.scons_subst(string, self, raw, target, + source, dict, conv) + def subst_list(self, string, raw=0, target=None, source=None, conv=None): + return SCons.Subst.scons_subst_list(string, self, raw, target, + source, {}, {}, conv) + def arg2nodes(self, args, factory, **kw): + global env_arg2nodes_called + env_arg2nodes_called = 1 + if not SCons.Util.is_List(args): + args = [args] + list = [] + for a in args: + if SCons.Util.is_String(a): + a = factory(self.subst(a)) + list.append(a) + return list + def get_factory(self, factory): + return factory or self.fs.File + def get_scanner(self, ext): + return self.scanner + def Dictionary(self): + return {} + def autogenerate(self, dir=''): + return {} + def __setitem__(self, item, var): + self.d[item] = var + def __getitem__(self, item): + return self.d[item] + def has_key(self, item): + return self.d.has_key(item) + def keys(self): + return self.d.keys() + def get(self, key, value=None): + return self.d.get(key, value) + def Override(self, overrides): + env = apply(Environment, (), self.d) + env.d.update(overrides) + env.scanner = self.scanner + return env + def _update(self, dict): + self.d.update(dict) + def items(self): + return self.d.items() + def sig_dict(self): + d = {} + for k,v in self.items(): d[k] = v + d['TARGETS'] = ['__t1__', '__t2__', '__t3__', '__t4__', '__t5__', '__t6__'] + d['TARGET'] = d['TARGETS'][0] + d['SOURCES'] = ['__s1__', '__s2__', '__s3__', '__s4__', '__s5__', '__s6__'] + d['SOURCE'] = d['SOURCES'][0] + return d + def __cmp__(self, other): + return cmp(self.scanner, other.scanner) or cmp(self.d, other.d) + +class MyAction: + def __init__(self, action): + self.action = action + def __call__(self, *args, **kw): + pass + def get_executor(self, env, overrides, tlist, slist, executor_kw): + return ['executor'] + [self.action] + +class MyNode_without_target_from_source: + def __init__(self, name): + self.name = name + self.sources = [] + self.builder = None + self.is_explicit = None + self.side_effect = 0 + self.suffix = os.path.splitext(name)[1] + def disambiguate(self): + return self + def __str__(self): + return self.name + def builder_set(self, builder): + self.builder = builder + def has_builder(self): + return not self.builder is None + def set_explicit(self, is_explicit): + self.is_explicit = is_explicit + def has_explicit_builder(self): + return self.is_explicit + def env_set(self, env, safe=0): + self.env = env + def add_source(self, source): + self.sources.extend(source) + def scanner_key(self): + return self.name + def is_derived(self): + return self.has_builder() + def generate_build_env(self, env): + return env + def get_build_env(self): + return self.executor.get_build_env() + def set_executor(self, executor): + self.executor = executor + def get_executor(self, create=1): + return self.executor + +class MyNode(MyNode_without_target_from_source): + def target_from_source(self, prefix, suffix, stripext): + return MyNode(prefix + stripext(str(self))[0] + suffix) + +class BuilderTestCase(unittest.TestCase): + + def test__init__(self): + """Test simple Builder creation + """ + builder = SCons.Builder.Builder(action="foo") + assert not builder is None, builder + builder = SCons.Builder.Builder(action="foo", OVERRIDE='x') + x = builder.overrides['OVERRIDE'] + assert x == 'x', x + + def test__nonzero__(self): + """Test a builder raising an exception when __nonzero__ is called + """ + builder = SCons.Builder.Builder(action="foo") + exc_caught = None + try: + builder.__nonzero__() + except SCons.Errors.InternalError: + exc_caught = 1 + assert exc_caught, "did not catch expected InternalError exception" + + class Node: + pass + + n = Node() + n.builder = builder + exc_caught = None + try: + if n.builder: + pass + except SCons.Errors.InternalError: + exc_caught = 1 + assert exc_caught, "did not catch expected InternalError exception" + + def test__call__(self): + """Test calling a builder to establish source dependencies + """ + env = Environment() + builder = SCons.Builder.Builder(action="foo", + target_factory=MyNode, + source_factory=MyNode) + + tgt = builder(env, source=[]) + assert tgt == [], tgt + + n1 = MyNode("n1") + n2 = MyNode("n2") + builder(env, target = n1, source = n2) + assert env_arg2nodes_called + assert n1.env == env, n1.env + assert n1.builder == builder, n1.builder + assert n1.sources == [n2], n1.sources + assert n1.executor, "no executor found" + assert not hasattr(n2, 'env') + + l = [1] + ul = UserList.UserList([2]) + try: + l.extend(ul) + except TypeError: + def mystr(l): + return str(map(str, l)) + else: + mystr = str + + nnn1 = MyNode("nnn1") + nnn2 = MyNode("nnn2") + tlist = builder(env, target = [nnn1, nnn2], source = []) + s = mystr(tlist) + assert s == "['nnn1', 'nnn2']", s + l = map(str, tlist) + assert l == ['nnn1', 'nnn2'], l + + tlist = builder(env, target = 'n3', source = 'n4') + s = mystr(tlist) + assert s == "['n3']", s + target = tlist[0] + l = map(str, tlist) + assert l == ['n3'], l + assert target.name == 'n3' + assert target.sources[0].name == 'n4' + + tlist = builder(env, target = 'n4 n5', source = ['n6 n7']) + s = mystr(tlist) + assert s == "['n4 n5']", s + l = map(str, tlist) + assert l == ['n4 n5'], l + target = tlist[0] + assert target.name == 'n4 n5' + assert target.sources[0].name == 'n6 n7' + + tlist = builder(env, target = ['n8 n9'], source = 'n10 n11') + s = mystr(tlist) + assert s == "['n8 n9']", s + l = map(str, tlist) + assert l == ['n8 n9'], l + target = tlist[0] + assert target.name == 'n8 n9' + assert target.sources[0].name == 'n10 n11' + + # A test to be uncommented when we freeze the environment + # as part of calling the builder. + #env1 = Environment(VAR='foo') + #target = builder(env1, target = 'n12', source = 'n13') + #env1['VAR'] = 'bar' + #be = target.get_build_env() + #assert be['VAR'] == 'foo', be['VAR'] + + if not hasattr(types, 'UnicodeType'): + uni = str + else: + uni = unicode + + target = builder(env, target = uni('n12 n13'), + source = [uni('n14 n15')])[0] + assert target.name == uni('n12 n13') + assert target.sources[0].name == uni('n14 n15') + + target = builder(env, target = [uni('n16 n17')], + source = uni('n18 n19'))[0] + assert target.name == uni('n16 n17') + assert target.sources[0].name == uni('n18 n19') + + n20 = MyNode_without_target_from_source('n20') + flag = 0 + try: + target = builder(env, None, source=n20) + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown if a source node can't create a target." + + builder = SCons.Builder.Builder(action="foo", + target_factory=MyNode, + source_factory=MyNode, + prefix='p-', + suffix='.s') + target = builder(env, None, source='n21')[0] + assert target.name == 'p-n21.s', target + + builder = SCons.Builder.Builder(misspelled_action="foo", + suffix = '.s') + try: + builder(env, target = 'n22', source = 'n22') + except SCons.Errors.UserError, e: + pass + else: + raise "Did not catch expected UserError." + + builder = SCons.Builder.Builder(action="foo") + target = builder(env, None, source='n22', srcdir='src_dir')[0] + p = target.sources[0].path + assert p == os.path.join('src_dir', 'n22'), p + + def test_mistaken_variables(self): + """Test keyword arguments that are often mistakes + """ + import SCons.Warnings + env = Environment() + builder = SCons.Builder.Builder(action="foo") + + save_warn = SCons.Warnings.warn + warned = [] + def my_warn(exception, warning, warned=warned): + warned.append(warning) + SCons.Warnings.warn = my_warn + + try: + target = builder(env, 'mistaken1', sources='mistaken1.c') + assert warned == ["Did you mean to use `source' instead of `sources'?"], warned + del warned[:] + + target = builder(env, 'mistaken2', targets='mistaken2.c') + assert warned == ["Did you mean to use `target' instead of `targets'?"], warned + del warned[:] + + target = builder(env, 'mistaken3', targets='mistaken3', sources='mistaken3.c') + assert "Did you mean to use `source' instead of `sources'?" in warned, warned + assert "Did you mean to use `target' instead of `targets'?" in warned, warned + del warned[:] + finally: + SCons.Warnings.warn = save_warn + + def test_action(self): + """Test Builder creation + + Verify that we can retrieve the supplied action attribute. + """ + builder = SCons.Builder.Builder(action="foo") + assert builder.action.cmd_list == "foo" + + def func(): + pass + builder = SCons.Builder.Builder(action=func) + assert isinstance(builder.action, SCons.Action.FunctionAction) + # Preserve the following so that the baseline test will fail. + # Remove it in favor of the previous test at some convenient + # point in the future. + assert builder.action.execfunction == func + + def test_generator(self): + """Test Builder creation given a generator function.""" + + def generator(): + pass + + builder = SCons.Builder.Builder(generator=generator) + assert builder.action.generator == generator + + def test_get_name(self): + """Test the get_name() method + """ + + def test_cmp(self): + """Test simple comparisons of Builder objects + """ + b1 = SCons.Builder.Builder(src_suffix = '.o') + b2 = SCons.Builder.Builder(src_suffix = '.o') + assert b1 == b2 + b3 = SCons.Builder.Builder(src_suffix = '.x') + assert b1 != b3 + assert b2 != b3 + + def test_target_factory(self): + """Test a Builder that creates target nodes of a specified class + """ + class Foo: + pass + def FooFactory(target): + global Foo + return Foo(target) + builder = SCons.Builder.Builder(target_factory = FooFactory) + assert builder.target_factory is FooFactory + assert not builder.source_factory is FooFactory + + def test_source_factory(self): + """Test a Builder that creates source nodes of a specified class + """ + class Foo: + pass + def FooFactory(source): + global Foo + return Foo(source) + builder = SCons.Builder.Builder(source_factory = FooFactory) + assert not builder.target_factory is FooFactory + assert builder.source_factory is FooFactory + + def test_splitext(self): + """Test the splitext() method attached to a Builder.""" + b = SCons.Builder.Builder() + assert b.splitext('foo') == ('foo','') + assert b.splitext('foo.bar') == ('foo','.bar') + assert b.splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'') + + class MyBuilder(SCons.Builder.BuilderBase): + def splitext(self, path): + return "called splitext()" + + b = MyBuilder() + ret = b.splitext('xyz.c') + assert ret == "called splitext()", ret + + def test_adjust_suffix(self): + """Test how a Builder adjusts file suffixes + """ + b = SCons.Builder.Builder() + assert b.adjust_suffix('.foo') == '.foo' + assert b.adjust_suffix('foo') == '.foo' + assert b.adjust_suffix('$foo') == '$foo' + + class MyBuilder(SCons.Builder.BuilderBase): + def adjust_suffix(self, suff): + return "called adjust_suffix()" + + b = MyBuilder() + ret = b.adjust_suffix('.foo') + assert ret == "called adjust_suffix()", ret + + def test_prefix(self): + """Test Builder creation with a specified target prefix + + Make sure that there is no '.' separator appended. + """ + env = Environment() + builder = SCons.Builder.Builder(prefix = 'lib.') + assert builder.get_prefix(env) == 'lib.' + builder = SCons.Builder.Builder(prefix = 'lib', action='') + assert builder.get_prefix(env) == 'lib' + tgt = builder(env, target = 'tgt1', source = 'src1')[0] + assert tgt.path == 'libtgt1', \ + "Target has unexpected name: %s" % tgt.path + tgt = builder(env, target = 'tgt2a tgt2b', source = 'src2')[0] + assert tgt.path == 'libtgt2a tgt2b', \ + "Target has unexpected name: %s" % tgt.path + tgt = builder(env, target = None, source = 'src3')[0] + assert tgt.path == 'libsrc3', \ + "Target has unexpected name: %s" % tgt.path + tgt = builder(env, target = None, source = 'lib/src4')[0] + assert tgt.path == os.path.join('lib', 'libsrc4'), \ + "Target has unexpected name: %s" % tgt.path + tgt = builder(env, target = 'lib/tgt5', source = 'lib/src5')[0] + assert tgt.path == os.path.join('lib', 'libtgt5'), \ + "Target has unexpected name: %s" % tgt.path + + def gen_prefix(env, sources): + return "gen_prefix() says " + env['FOO'] + my_env = Environment(FOO = 'xyzzy') + builder = SCons.Builder.Builder(prefix = gen_prefix) + assert builder.get_prefix(my_env) == "gen_prefix() says xyzzy" + my_env['FOO'] = 'abracadabra' + assert builder.get_prefix(my_env) == "gen_prefix() says abracadabra" + + def my_emit(env, sources): + return env.subst('$EMIT') + my_env = Environment(FOO = '.foo', EMIT = 'emit-') + builder = SCons.Builder.Builder(prefix = {None : 'default-', + '.in' : 'out-', + '.x' : 'y-', + '$FOO' : 'foo-', + '.zzz' : my_emit}, + action = '') + tgt = builder(my_env, target = None, source = 'f1')[0] + assert tgt.path == 'default-f1', tgt.path + tgt = builder(my_env, target = None, source = 'f2.c')[0] + assert tgt.path == 'default-f2', tgt.path + tgt = builder(my_env, target = None, source = 'f3.in')[0] + assert tgt.path == 'out-f3', tgt.path + tgt = builder(my_env, target = None, source = 'f4.x')[0] + assert tgt.path == 'y-f4', tgt.path + tgt = builder(my_env, target = None, source = 'f5.foo')[0] + assert tgt.path == 'foo-f5', tgt.path + tgt = builder(my_env, target = None, source = 'f6.zzz')[0] + assert tgt.path == 'emit-f6', tgt.path + + def test_set_suffix(self): + """Test the set_suffix() method""" + b = SCons.Builder.Builder(action='') + env = Environment(XSUFFIX = '.x') + + s = b.get_suffix(env) + assert s == '', s + + b.set_suffix('.foo') + s = b.get_suffix(env) + assert s == '.foo', s + + b.set_suffix('$XSUFFIX') + s = b.get_suffix(env) + assert s == '.x', s + + def test_src_suffix(self): + """Test Builder creation with a specified source file suffix + + Make sure that the '.' separator is appended to the + beginning if it isn't already present. + """ + env = Environment(XSUFFIX = '.x', YSUFFIX = '.y') + + b1 = SCons.Builder.Builder(src_suffix = '.c', action='') + assert b1.src_suffixes(env) == ['.c'], b1.src_suffixes(env) + + tgt = b1(env, target = 'tgt2', source = 'src2')[0] + assert tgt.sources[0].path == 'src2.c', \ + "Source has unexpected name: %s" % tgt.sources[0].path + + tgt = b1(env, target = 'tgt3', source = 'src3a src3b')[0] + assert len(tgt.sources) == 1 + assert tgt.sources[0].path == 'src3a src3b.c', \ + "Unexpected tgt.sources[0] name: %s" % tgt.sources[0].path + + b2 = SCons.Builder.Builder(src_suffix = '.2', src_builder = b1) + r = b2.src_suffixes(env) + r.sort() + assert r == ['.2', '.c'], r + + b3 = SCons.Builder.Builder(action = {'.3a' : '', '.3b' : ''}) + s = b3.src_suffixes(env) + s.sort() + assert s == ['.3a', '.3b'], s + + b4 = SCons.Builder.Builder(src_suffix = '$XSUFFIX') + assert b4.src_suffixes(env) == ['.x'], b4.src_suffixes(env) + + b5 = SCons.Builder.Builder(action = { '.y' : ''}) + assert b5.src_suffixes(env) == ['.y'], b5.src_suffixes(env) + + def test_srcsuffix_nonext(self): + "Test target generation from non-extension source suffixes" + env = Environment() + b6 = SCons.Builder.Builder(action = '', + src_suffix='_src.a', + suffix='.b') + tgt = b6(env, target=None, source='foo_src.a') + assert str(tgt[0]) == 'foo.b', str(tgt[0]) + + b7 = SCons.Builder.Builder(action = '', + src_suffix='_source.a', + suffix='_obj.b') + b8 = SCons.Builder.Builder(action = '', + src_builder=b7, + suffix='.c') + tgt = b8(env, target=None, source='foo_source.a') + assert str(tgt[0]) == 'foo_obj.c', str(tgt[0]) + src = env.fs.File('foo_source.a') + tgt = b8(env, target=None, source=src) + assert str(tgt[0]) == 'foo_obj.c', str(tgt[0]) + + b9 = SCons.Builder.Builder(action={'_src.a' : 'srcaction'}, + suffix='.c') + b9.add_action('_altsrc.b', 'altaction') + tgt = b9(env, target=None, source='foo_altsrc.b') + assert str(tgt[0]) == 'foo.c', str(tgt[0]) + + def test_src_suffix_expansion(self): + """Test handling source suffixes when an expansion is involved""" + env = Environment(OBJSUFFIX = '.obj') + + b1 = SCons.Builder.Builder(action = '', + src_suffix='.c', + suffix='.obj') + b2 = SCons.Builder.Builder(action = '', + src_builder=b1, + src_suffix='.obj', + suffix='.exe') + tgt = b2(env, target=None, source=['foo$OBJSUFFIX']) + s = map(str, tgt[0].sources) + assert s == ['foo.obj'], s + + def test_suffix(self): + """Test Builder creation with a specified target suffix + + Make sure that the '.' separator is appended to the + beginning if it isn't already present. + """ + env = Environment() + builder = SCons.Builder.Builder(suffix = '.o') + assert builder.get_suffix(env) == '.o', builder.get_suffix(env) + builder = SCons.Builder.Builder(suffix = 'o', action='') + assert builder.get_suffix(env) == '.o', builder.get_suffix(env) + tgt = builder(env, target = 'tgt3', source = 'src3')[0] + assert tgt.path == 'tgt3.o', \ + "Target has unexpected name: %s" % tgt.path + tgt = builder(env, target = 'tgt4a tgt4b', source = 'src4')[0] + assert tgt.path == 'tgt4a tgt4b.o', \ + "Target has unexpected name: %s" % tgt.path + tgt = builder(env, target = None, source = 'src5')[0] + assert tgt.path == 'src5.o', \ + "Target has unexpected name: %s" % tgt.path + + def gen_suffix(env, sources): + return "gen_suffix() says " + env['BAR'] + my_env = Environment(BAR = 'hocus pocus') + builder = SCons.Builder.Builder(suffix = gen_suffix) + assert builder.get_suffix(my_env) == "gen_suffix() says hocus pocus", builder.get_suffix(my_env) + my_env['BAR'] = 'presto chango' + assert builder.get_suffix(my_env) == "gen_suffix() says presto chango" + + def my_emit(env, sources): + return env.subst('$EMIT') + my_env = Environment(BAR = '.bar', EMIT = '.emit') + builder = SCons.Builder.Builder(suffix = {None : '.default', + '.in' : '.out', + '.x' : '.y', + '$BAR' : '.new', + '.zzz' : my_emit}, + action='') + tgt = builder(my_env, target = None, source = 'f1')[0] + assert tgt.path == 'f1.default', tgt.path + tgt = builder(my_env, target = None, source = 'f2.c')[0] + assert tgt.path == 'f2.default', tgt.path + tgt = builder(my_env, target = None, source = 'f3.in')[0] + assert tgt.path == 'f3.out', tgt.path + tgt = builder(my_env, target = None, source = 'f4.x')[0] + assert tgt.path == 'f4.y', tgt.path + tgt = builder(my_env, target = None, source = 'f5.bar')[0] + assert tgt.path == 'f5.new', tgt.path + tgt = builder(my_env, target = None, source = 'f6.zzz')[0] + assert tgt.path == 'f6.emit', tgt.path + + def test_single_source(self): + """Test Builder with single_source flag set""" + def func(target, source, env): + open(str(target[0]), "w") + if (len(source) == 1 and len(target) == 1): + env['CNT'][0] = env['CNT'][0] + 1 + + env = Environment() + infiles = [] + outfiles = [] + for i in range(10): + infiles.append(test.workpath('%d.in' % i)) + outfiles.append(test.workpath('%d.out' % i)) + test.write(infiles[-1], "\n") + builder = SCons.Builder.Builder(action=SCons.Action.Action(func,None), + single_source = 1, suffix='.out') + env['CNT'] = [0] + tgt = builder(env, target=outfiles[0], source=infiles[0])[0] + s = str(tgt) + assert s == test.workpath('0.out'), s + tgt.prepare() + tgt.build() + assert env['CNT'][0] == 1, env['CNT'][0] + tgt = builder(env, outfiles[1], infiles[1])[0] + s = str(tgt) + assert s == test.workpath('1.out'), s + tgt.prepare() + tgt.build() + assert env['CNT'][0] == 2 + tgts = builder(env, None, infiles[2:4]) + try: + [].extend(UserList.UserList()) + except TypeError: + # Old Python version (1.5.2) that can't handle extending + # a list with list-like objects. That means the return + # value from the builder call is a real list with Nodes, + # and doesn't have a __str__() method that stringifies + # the individual elements. Since we're gong to drop 1.5.2 + # support anyway, don't bother trying to test for it. + pass + else: + s = str(tgts) + expect = str([test.workpath('2.out'), test.workpath('3.out')]) + assert s == expect, s + for t in tgts: t.prepare() + tgts[0].build() + tgts[1].build() + assert env['CNT'][0] == 4 + try: + tgt = builder(env, outfiles[4], infiles[4:6]) + except SCons.Errors.UserError: + pass + else: + assert 0 + try: + # The builder may output more than one target per input file. + tgt = builder(env, outfiles[4:6], infiles[4:6]) + except SCons.Errors.UserError: + pass + else: + assert 0 + + + def test_lists(self): + """Testing handling lists of targets and source""" + def function2(target, source, env, tlist = [outfile, outfile2], **kw): + for t in target: + open(str(t), 'w').write("function2\n") + for t in tlist: + if not t in map(str, target): + open(t, 'w').write("function2\n") + return 1 + + env = Environment() + builder = SCons.Builder.Builder(action = function2) + + tgts = builder(env, source=[]) + assert tgts == [], tgts + + tgts = builder(env, target = [outfile, outfile2], source = infile) + for t in tgts: + t.prepare() + try: + tgts[0].build() + except SCons.Errors.BuildError: + pass + c = test.read(outfile, 'r') + assert c == "function2\n", c + c = test.read(outfile2, 'r') + assert c == "function2\n", c + + sub1_out = test.workpath('sub1', 'out') + sub2_out = test.workpath('sub2', 'out') + + def function3(target, source, env, tlist = [sub1_out, sub2_out]): + for t in target: + open(str(t), 'w').write("function3\n") + for t in tlist: + if not t in map(str, target): + open(t, 'w').write("function3\n") + return 1 + + builder = SCons.Builder.Builder(action = function3) + tgts = builder(env, target = [sub1_out, sub2_out], source = infile) + for t in tgts: + t.prepare() + try: + tgts[0].build() + except SCons.Errors.BuildError: + pass + c = test.read(sub1_out, 'r') + assert c == "function3\n", c + c = test.read(sub2_out, 'r') + assert c == "function3\n", c + assert os.path.exists(test.workpath('sub1')) + assert os.path.exists(test.workpath('sub2')) + + def test_src_builder(self): + """Testing Builders with src_builder""" + # These used to be MultiStepBuilder objects until we + # eliminated it as a separate class + env = Environment() + builder1 = SCons.Builder.Builder(action='foo', + src_suffix='.bar', + suffix='.foo') + builder2 = SCons.Builder.Builder(action=MyAction('act'), + src_builder = builder1, + src_suffix = '.foo') + + tgt = builder2(env, source=[]) + assert tgt == [], tgt + + sources = ['test.bar', 'test2.foo', 'test3.txt', 'test4'] + tgt = builder2(env, target='baz', source=sources)[0] + s = str(tgt) + assert s == 'baz', s + s = map(str, tgt.sources) + assert s == ['test.foo', 'test2.foo', 'test3.txt', 'test4.foo'], s + s = map(str, tgt.sources[0].sources) + assert s == ['test.bar'], s + + tgt = builder2(env, None, 'aaa.bar')[0] + s = str(tgt) + assert s == 'aaa', s + s = map(str, tgt.sources) + assert s == ['aaa.foo'], s + s = map(str, tgt.sources[0].sources) + assert s == ['aaa.bar'], s + + builder3 = SCons.Builder.Builder(action='bld3') + assert not builder3.src_builder is builder1.src_builder + + builder4 = SCons.Builder.Builder(action='bld4', + src_suffix='.i', + suffix='_wrap.c') + builder5 = SCons.Builder.Builder(action=MyAction('act'), + src_builder=builder4, + suffix='.obj', + src_suffix='.c') + builder6 = SCons.Builder.Builder(action=MyAction('act'), + src_builder=builder5, + suffix='.exe', + src_suffix='.obj') + tgt = builder6(env, 'test', 'test.i')[0] + s = str(tgt) + assert s == 'test.exe', s + s = map(str, tgt.sources) + assert s == ['test_wrap.obj'], s + s = map(str, tgt.sources[0].sources) + assert s == ['test_wrap.c'], s + s = map(str, tgt.sources[0].sources[0].sources) + assert s == ['test.i'], s + + def test_target_scanner(self): + """Testing ability to set target and source scanners through a builder.""" + global instanced + class TestScanner: + pass + tscan = TestScanner() + sscan = TestScanner() + env = Environment() + builder = SCons.Builder.Builder(target_scanner=tscan, + source_scanner=sscan, + action='') + tgt = builder(env, target='foo2', source='bar')[0] + assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner + assert tgt.builder.source_scanner == sscan, tgt.builder.source_scanner + + builder1 = SCons.Builder.Builder(action='foo', + src_suffix='.bar', + suffix='.foo') + builder2 = SCons.Builder.Builder(action='foo', + src_builder = builder1, + target_scanner = tscan, + source_scanner = tscan) + tgt = builder2(env, target='baz2', source='test.bar test2.foo test3.txt')[0] + assert tgt.builder.target_scanner == tscan, tgt.builder.target_scanner + assert tgt.builder.source_scanner == tscan, tgt.builder.source_scanner + + def test_actual_scanner(self): + """Test usage of actual Scanner objects.""" + + import SCons.Scanner + + def func(self): + pass + + scanner = SCons.Scanner.Base(func, name='fooscan') + + b1 = SCons.Builder.Builder(action='bld', target_scanner=scanner) + b2 = SCons.Builder.Builder(action='bld', target_scanner=scanner) + b3 = SCons.Builder.Builder(action='bld') + + assert b1 == b2 + assert b1 != b3 + + def test_src_scanner(slf): + """Testing ability to set a source file scanner through a builder.""" + class TestScanner: + def key(self, env): + return 'TestScannerkey' + def instance(self, env): + return self + def select(self, node): + return self + name = 'TestScanner' + def __str__(self): + return self.name + + scanner = TestScanner() + builder = SCons.Builder.Builder(action='action') + + # With no scanner specified, source_scanner and + # backup_source_scanner are None. + bar_y = MyNode('bar.y') + env1 = Environment() + tgt = builder(env1, target='foo1.x', source='bar.y')[0] + src = tgt.sources[0] + assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner + assert tgt.builder.source_scanner is None, tgt.builder.source_scanner + assert tgt.get_source_scanner(bar_y) is None, tgt.get_source_scanner(bar_y) + assert not src.has_builder(), src.has_builder() + s = src.get_source_scanner(bar_y) + assert isinstance(s, SCons.Util.Null), repr(s) + + # An Environment that has suffix-specified SCANNERS should + # provide a source scanner to the target. + class EnvTestScanner: + def key(self, env): + return '.y' + def instance(self, env): + return self + name = 'EnvTestScanner' + def __str__(self): + return self.name + def select(self, node): + return self + def path(self, env, dir=None): + return () + def __call__(self, node, env, path): + return [] + env3 = Environment(SCANNERS = [EnvTestScanner()]) + env3.scanner = EnvTestScanner() # test env's version of SCANNERS + tgt = builder(env3, target='foo2.x', source='bar.y')[0] + src = tgt.sources[0] + assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner + assert not tgt.builder.source_scanner, tgt.builder.source_scanner + assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y) + assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y) + assert not src.has_builder(), src.has_builder() + s = src.get_source_scanner(bar_y) + assert isinstance(s, SCons.Util.Null), repr(s) + + # Can't simply specify the scanner as a builder argument; it's + # global to all invocations of this builder. + tgt = builder(env3, target='foo3.x', source='bar.y', source_scanner = scanner)[0] + src = tgt.sources[0] + assert tgt.builder.target_scanner != scanner, tgt.builder.target_scanner + assert not tgt.builder.source_scanner, tgt.builder.source_scanner + assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y) + assert str(tgt.get_source_scanner(bar_y)) == 'EnvTestScanner', tgt.get_source_scanner(bar_y) + assert not src.has_builder(), src.has_builder() + s = src.get_source_scanner(bar_y) + assert isinstance(s, SCons.Util.Null), s + + # Now use a builder that actually has scanners and ensure that + # the target is set accordingly (using the specified scanner + # instead of the Environment's scanner) + builder = SCons.Builder.Builder(action='action', + source_scanner=scanner, + target_scanner=scanner) + tgt = builder(env3, target='foo4.x', source='bar.y')[0] + src = tgt.sources[0] + assert tgt.builder.target_scanner == scanner, tgt.builder.target_scanner + assert tgt.builder.source_scanner, tgt.builder.source_scanner + assert tgt.builder.source_scanner == scanner, tgt.builder.source_scanner + assert str(tgt.builder.source_scanner) == 'TestScanner', str(tgt.builder.source_scanner) + assert tgt.get_source_scanner(bar_y), tgt.get_source_scanner(bar_y) + assert tgt.get_source_scanner(bar_y) == scanner, tgt.get_source_scanner(bar_y) + assert str(tgt.get_source_scanner(bar_y)) == 'TestScanner', tgt.get_source_scanner(bar_y) + assert not src.has_builder(), src.has_builder() + s = src.get_source_scanner(bar_y) + assert isinstance(s, SCons.Util.Null), s + + + + def test_Builder_API(self): + """Test Builder interface. + + Some of this is tested elsewhere in this file, but this is a + quick collection of common operations on builders with various + forms of component specifications.""" + + builder = SCons.Builder.Builder() + env = Environment(BUILDERS={'Bld':builder}) + + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == '', r + r = builder.get_suffix(env) + assert r == '', r + r = builder.get_src_suffix(env) + assert r == '', r + r = builder.src_suffixes(env) + assert r == [], r + + # src_suffix can be a single string or a list of strings + # src_suffixes() caches its return value, so we use a new + # Builder each time we do any of these tests + + bld = SCons.Builder.Builder() + env = Environment(BUILDERS={'Bld':bld}) + + bld.set_src_suffix('.foo') + r = bld.get_src_suffix(env) + assert r == '.foo', r + r = bld.src_suffixes(env) + assert r == ['.foo'], r + + bld = SCons.Builder.Builder() + env = Environment(BUILDERS={'Bld':bld}) + + bld.set_src_suffix(['.foo', '.bar']) + r = bld.get_src_suffix(env) + assert r == '.foo', r + r = bld.src_suffixes(env) + assert r == ['.foo', '.bar'], r + + bld = SCons.Builder.Builder() + env = Environment(BUILDERS={'Bld':bld}) + + bld.set_src_suffix(['.bar', '.foo']) + r = bld.get_src_suffix(env) + assert r == '.bar', r + r = bld.src_suffixes(env) + r.sort() + assert r == ['.bar', '.foo'], r + + # adjust_suffix normalizes the suffix, adding a `.' if needed + + r = builder.adjust_suffix('.foo') + assert r == '.foo', r + r = builder.adjust_suffix('_foo') + assert r == '_foo', r + r = builder.adjust_suffix('$foo') + assert r == '$foo', r + r = builder.adjust_suffix('foo') + assert r == '.foo', r + r = builder.adjust_suffix('f._$oo') + assert r == '.f._$oo', r + + # prefix and suffix can be one of: + # 1. a string (adjusted and env variables substituted), + # 2. a function (passed (env,sources), returns suffix string) + # 3. a dict of src_suffix:suffix settings, key==None is + # default suffix (special case of #2, so adjust_suffix + # not applied) + + builder = SCons.Builder.Builder(prefix='lib', suffix='foo') + + env = Environment(BUILDERS={'Bld':builder}) + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'lib', r + r = builder.get_suffix(env) + assert r == '.foo', r + + mkpref = lambda env,sources: 'Lib' + mksuff = lambda env,sources: '.Foo' + builder = SCons.Builder.Builder(prefix=mkpref, suffix=mksuff) + + env = Environment(BUILDERS={'Bld':builder}) + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'Lib', r + r = builder.get_suffix(env) + assert r == '.Foo', r + + builder = SCons.Builder.Builder(prefix='$PREF', suffix='$SUFF') + + env = Environment(BUILDERS={'Bld':builder},PREF="LIB",SUFF=".FOO") + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'LIB', r + r = builder.get_suffix(env) + assert r == '.FOO', r + + builder = SCons.Builder.Builder(prefix={None:'A_', + '.C':'E_'}, + suffix={None:'.B', + '.C':'.D'}) + + env = Environment(BUILDERS={'Bld':builder}) + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'A_', r + r = builder.get_suffix(env) + assert r == '.B', r + r = builder.get_prefix(env, [MyNode('X.C')]) + assert r == 'E_', r + r = builder.get_suffix(env, [MyNode('X.C')]) + assert r == '.D', r + + builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={}) + env = Environment(BUILDERS={'Bld':builder}) + + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'A_', r + r = builder.get_suffix(env) + assert r is None, r + r = builder.get_src_suffix(env) + assert r == '', r + r = builder.src_suffixes(env) + assert r == [], r + + # Builder actions can be a string, a list, or a dictionary + # whose keys are the source suffix. The add_action() + # specifies a new source suffix/action binding. + + builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={}) + env = Environment(BUILDERS={'Bld':builder}) + builder.add_action('.src_sfx1', 'FOO') + + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'A_', r + r = builder.get_suffix(env) + assert r is None, r + r = builder.get_suffix(env, [MyNode('X.src_sfx1')]) + assert r is None, r + r = builder.get_src_suffix(env) + assert r == '.src_sfx1', r + r = builder.src_suffixes(env) + assert r == ['.src_sfx1'], r + + builder = SCons.Builder.Builder(prefix='A_', suffix={}, action={}) + env = Environment(BUILDERS={'Bld':builder}) + builder.add_action('.src_sfx1', 'FOO') + builder.add_action('.src_sfx2', 'BAR') + + r = builder.get_name(env) + assert r == 'Bld', r + r = builder.get_prefix(env) + assert r == 'A_', r + r = builder.get_suffix(env) + assert r is None, r + r = builder.get_src_suffix(env) + assert r == '.src_sfx1', r + r = builder.src_suffixes(env) + r.sort() + assert r == ['.src_sfx1', '.src_sfx2'], r + + + def test_Builder_Args(self): + """Testing passing extra args to a builder.""" + def buildFunc(target, source, env, s=self): + s.foo=env['foo'] + s.bar=env['bar'] + assert env['CC'] == 'mycc' + + env=Environment(CC='cc') + + builder = SCons.Builder.Builder(action=buildFunc) + tgt = builder(env, target='foo', source='bar', foo=1, bar=2, CC='mycc')[0] + tgt.build() + assert self.foo == 1, self.foo + assert self.bar == 2, self.bar + + def test_emitter(self): + """Test emitter functions.""" + def emit(target, source, env): + foo = env.get('foo', 0) + bar = env.get('bar', 0) + for t in target: + assert isinstance(t, MyNode) + assert t.has_builder() + for s in source: + assert isinstance(s, MyNode) + if foo: + target.append("bar%d"%foo) + if bar: + source.append("baz") + return ( target, source ) + + env = Environment() + builder = SCons.Builder.Builder(action='foo', + emitter=emit, + target_factory=MyNode, + source_factory=MyNode) + tgt = builder(env, target='foo2', source='bar')[0] + assert str(tgt) == 'foo2', str(tgt) + assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0]) + + tgt = builder(env, target='foo3', source='bar', foo=1) + assert len(tgt) == 2, len(tgt) + assert 'foo3' in map(str, tgt), map(str, tgt) + assert 'bar1' in map(str, tgt), map(str, tgt) + + tgt = builder(env, target='foo4', source='bar', bar=1)[0] + assert str(tgt) == 'foo4', str(tgt) + assert len(tgt.sources) == 2, len(tgt.sources) + assert 'baz' in map(str, tgt.sources), map(str, tgt.sources) + assert 'bar' in map(str, tgt.sources), map(str, tgt.sources) + + env2=Environment(FOO=emit) + builder2=SCons.Builder.Builder(action='foo', + emitter="$FOO", + target_factory=MyNode, + source_factory=MyNode) + + builder2a=SCons.Builder.Builder(action='foo', + emitter="$FOO", + target_factory=MyNode, + source_factory=MyNode) + + assert builder2 == builder2a, repr(builder2.__dict__) + "\n" + repr(builder2a.__dict__) + + tgt = builder2(env2, target='foo5', source='bar')[0] + assert str(tgt) == 'foo5', str(tgt) + assert str(tgt.sources[0]) == 'bar', str(tgt.sources[0]) + + tgt = builder2(env2, target='foo6', source='bar', foo=2) + assert len(tgt) == 2, len(tgt) + assert 'foo6' in map(str, tgt), map(str, tgt) + assert 'bar2' in map(str, tgt), map(str, tgt) + + tgt = builder2(env2, target='foo7', source='bar', bar=1)[0] + assert str(tgt) == 'foo7', str(tgt) + assert len(tgt.sources) == 2, len(tgt.sources) + assert 'baz' in map(str, tgt.sources), map(str, tgt.sources) + assert 'bar' in map(str, tgt.sources), map(str, tgt.sources) + + def test_emitter_preserve_builder(self): + """Test an emitter not overwriting a newly-set builder""" + env = Environment() + + new_builder = SCons.Builder.Builder(action='new') + node = MyNode('foo8') + new_node = MyNode('foo8.new') + + def emit(target, source, env, nb=new_builder, nn=new_node): + for t in target: + t.builder = nb + return [nn], source + + builder=SCons.Builder.Builder(action='foo', + emitter=emit, + target_factory=MyNode, + source_factory=MyNode) + tgt = builder(env, target=node, source='bar')[0] + assert tgt is new_node, tgt + assert tgt.builder is builder, tgt.builder + assert node.builder is new_builder, node.builder + + def test_emitter_suffix_map(self): + """Test mapping file suffixes to emitter functions""" + env = Environment() + + def emit4a(target, source, env): + source = map(str, source) + target = map(lambda x: 'emit4a-' + x[:-3], source) + return (target, source) + def emit4b(target, source, env): + source = map(str, source) + target = map(lambda x: 'emit4b-' + x[:-3], source) + return (target, source) + + builder = SCons.Builder.Builder(action='foo', + emitter={'.4a':emit4a, + '.4b':emit4b}, + target_factory=MyNode, + source_factory=MyNode) + tgt = builder(env, None, source='aaa.4a')[0] + assert str(tgt) == 'emit4a-aaa', str(tgt) + tgt = builder(env, None, source='bbb.4b')[0] + assert str(tgt) == 'emit4b-bbb', str(tgt) + tgt = builder(env, None, source='ccc.4c')[0] + assert str(tgt) == 'ccc', str(tgt) + + def emit4c(target, source, env): + source = map(str, source) + target = map(lambda x: 'emit4c-' + x[:-3], source) + return (target, source) + + builder.add_emitter('.4c', emit4c) + tgt = builder(env, None, source='ccc.4c')[0] + assert str(tgt) == 'emit4c-ccc', str(tgt) + + def test_emitter_function_list(self): + """Test lists of emitter functions""" + env = Environment() + + def emit1a(target, source, env): + source = map(str, source) + target = target + map(lambda x: 'emit1a-' + x[:-2], source) + return (target, source) + def emit1b(target, source, env): + source = map(str, source) + target = target + map(lambda x: 'emit1b-' + x[:-2], source) + return (target, source) + builder1 = SCons.Builder.Builder(action='foo', + emitter=[emit1a, emit1b], + node_factory=MyNode) + + tgts = builder1(env, target='target-1', source='aaa.1') + tgts = map(str, tgts) + assert tgts == ['target-1', 'emit1a-aaa', 'emit1b-aaa'], tgts + + # Test a list of emitter functions through the environment. + def emit2a(target, source, env): + source = map(str, source) + target = target + map(lambda x: 'emit2a-' + x[:-2], source) + return (target, source) + def emit2b(target, source, env): + source = map(str, source) + target = target + map(lambda x: 'emit2b-' + x[:-2], source) + return (target, source) + builder2 = SCons.Builder.Builder(action='foo', + emitter='$EMITTERLIST', + node_factory=MyNode) + + env = Environment(EMITTERLIST = [emit2a, emit2b]) + + tgts = builder2(env, target='target-2', source='aaa.2') + tgts = map(str, tgts) + assert tgts == ['target-2', 'emit2a-aaa', 'emit2b-aaa'], tgts + + def test_emitter_TARGET_SOURCE(self): + """Test use of $TARGET and $SOURCE in emitter results""" + + env = SCons.Environment.Environment() + + def emit(target, source, env): + return (target + ['${SOURCE}.s1', '${TARGET}.t1'], + source + ['${TARGET}.t2', '${SOURCE}.s2']) + + builder = SCons.Builder.Builder(action='foo', + emitter = emit, + node_factory = MyNode) + + targets = builder(env, target = 'TTT', source ='SSS') + sources = targets[0].sources + targets = map(str, targets) + sources = map(str, sources) + assert targets == ['TTT', 'SSS.s1', 'TTT.t1'], targets + assert sources == ['SSS', 'TTT.t2', 'SSS.s2'], targets + + def test_no_target(self): + """Test deducing the target from the source.""" + + env = Environment() + b = SCons.Builder.Builder(action='foo', suffix='.o') + + tgt = b(env, None, 'aaa')[0] + assert str(tgt) == 'aaa.o', str(tgt) + assert len(tgt.sources) == 1, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'aaa', map(str, tgt.sources) + + tgt = b(env, None, 'bbb.c')[0] + assert str(tgt) == 'bbb.o', str(tgt) + assert len(tgt.sources) == 1, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'bbb.c', map(str, tgt.sources) + + tgt = b(env, None, 'ccc.x.c')[0] + assert str(tgt) == 'ccc.x.o', str(tgt) + assert len(tgt.sources) == 1, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'ccc.x.c', map(str, tgt.sources) + + tgt = b(env, None, ['d0.c', 'd1.c'])[0] + assert str(tgt) == 'd0.o', str(tgt) + assert len(tgt.sources) == 2, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'd0.c', map(str, tgt.sources) + assert str(tgt.sources[1]) == 'd1.c', map(str, tgt.sources) + + tgt = b(env, target = None, source='eee')[0] + assert str(tgt) == 'eee.o', str(tgt) + assert len(tgt.sources) == 1, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'eee', map(str, tgt.sources) + + tgt = b(env, target = None, source='fff.c')[0] + assert str(tgt) == 'fff.o', str(tgt) + assert len(tgt.sources) == 1, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'fff.c', map(str, tgt.sources) + + tgt = b(env, target = None, source='ggg.x.c')[0] + assert str(tgt) == 'ggg.x.o', str(tgt) + assert len(tgt.sources) == 1, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'ggg.x.c', map(str, tgt.sources) + + tgt = b(env, target = None, source=['h0.c', 'h1.c'])[0] + assert str(tgt) == 'h0.o', str(tgt) + assert len(tgt.sources) == 2, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'h0.c', map(str, tgt.sources) + assert str(tgt.sources[1]) == 'h1.c', map(str, tgt.sources) + + w = b(env, target='i0.w', source=['i0.x'])[0] + y = b(env, target='i1.y', source=['i1.z'])[0] + tgt = b(env, None, source=[w, y])[0] + assert str(tgt) == 'i0.o', str(tgt) + assert len(tgt.sources) == 2, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'i0.w', map(str, tgt.sources) + assert str(tgt.sources[1]) == 'i1.y', map(str, tgt.sources) + + def test_get_name(self): + """Test getting name of builder. + + Each type of builder should return its environment-specific + name when queried appropriately. """ + + b1 = SCons.Builder.Builder(action='foo', suffix='.o') + b2 = SCons.Builder.Builder(action='foo', suffix='.c') + b3 = SCons.Builder.Builder(action='bar', src_suffix = '.foo', + src_builder = b1) + b4 = SCons.Builder.Builder(action={}) + b5 = SCons.Builder.Builder(action='foo', name='builder5') + b6 = SCons.Builder.Builder(action='foo') + assert isinstance(b4, SCons.Builder.CompositeBuilder) + assert isinstance(b4.action, SCons.Action.CommandGeneratorAction) + + env = Environment(BUILDERS={'bldr1': b1, + 'bldr2': b2, + 'bldr3': b3, + 'bldr4': b4}) + env2 = Environment(BUILDERS={'B1': b1, + 'B2': b2, + 'B3': b3, + 'B4': b4}) + # With no name, get_name will return the class. Allow + # for caching... + b6_names = [ + 'SCons.Builder.BuilderBase', + "", + 'SCons.Memoize.BuilderBase', + "", + ] + + assert b1.get_name(env) == 'bldr1', b1.get_name(env) + assert b2.get_name(env) == 'bldr2', b2.get_name(env) + assert b3.get_name(env) == 'bldr3', b3.get_name(env) + assert b4.get_name(env) == 'bldr4', b4.get_name(env) + assert b5.get_name(env) == 'builder5', b5.get_name(env) + assert b6.get_name(env) in b6_names, b6.get_name(env) + + assert b1.get_name(env2) == 'B1', b1.get_name(env2) + assert b2.get_name(env2) == 'B2', b2.get_name(env2) + assert b3.get_name(env2) == 'B3', b3.get_name(env2) + assert b4.get_name(env2) == 'B4', b4.get_name(env2) + assert b5.get_name(env2) == 'builder5', b5.get_name(env2) + assert b6.get_name(env2) in b6_names, b6.get_name(env2) + + assert b5.get_name(None) == 'builder5', b5.get_name(None) + assert b6.get_name(None) in b6_names, b6.get_name(None) + + # This test worked before adding batch builders, but we must now + # be able to disambiguate a CompositeAction into a more specific + # action based on file suffix at call time. Leave this commented + # out (for now) in case this reflects a real-world use case that + # we must accomodate and we want to resurrect this test. + #tgt = b4(env, target = 'moo', source='cow') + #assert tgt[0].builder.get_name(env) == 'bldr4' + +class CompositeBuilderTestCase(unittest.TestCase): + + def setUp(self): + def func_action(target, source, env): + return 0 + + builder = SCons.Builder.Builder(action={ '.foo' : func_action, + '.bar' : func_action}) + + self.func_action = func_action + self.builder = builder + + def test___init__(self): + """Test CompositeBuilder creation""" + env = Environment() + builder = SCons.Builder.Builder(action={}) + + tgt = builder(env, source=[]) + assert tgt == [], tgt + + assert isinstance(builder, SCons.Builder.CompositeBuilder) + assert isinstance(builder.action, SCons.Action.CommandGeneratorAction) + + def test_target_action(self): + """Test CompositeBuilder setting of target builder actions""" + env = Environment() + builder = self.builder + + tgt = builder(env, target='test1', source='test1.foo')[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase) + assert tgt.builder.action is builder.action + + tgt = builder(env, target='test2', source='test1.bar')[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase) + assert tgt.builder.action is builder.action + + def test_multiple_suffix_error(self): + """Test the CompositeBuilder multiple-source-suffix error""" + env = Environment() + builder = self.builder + + flag = 0 + try: + builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0] + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown when we call a builder with files of different suffixes." + expect = "While building `['test3']' from `test1.foo': Cannot build multiple sources with different extensions: .bar, .foo" + assert str(e) == expect, e + + def test_source_ext_match(self): + """Test the CompositeBuilder source_ext_match argument""" + env = Environment() + func_action = self.func_action + builder = SCons.Builder.Builder(action={ '.foo' : func_action, + '.bar' : func_action}, + source_ext_match = None) + + tgt = builder(env, target='test3', source=['test2.bar', 'test1.foo'])[0] + tgt.build() + + def test_suffix_variable(self): + """Test CompositeBuilder defining action suffixes through a variable""" + env = Environment(BAR_SUFFIX = '.BAR2', FOO_SUFFIX = '.FOO2') + func_action = self.func_action + builder = SCons.Builder.Builder(action={ '.foo' : func_action, + '.bar' : func_action, + '$BAR_SUFFIX' : func_action, + '$FOO_SUFFIX' : func_action }) + + tgt = builder(env, target='test4', source=['test4.BAR2'])[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase) + try: + tgt.build() + flag = 1 + except SCons.Errors.UserError, e: + print e + flag = 0 + assert flag, "It should be possible to define actions in composite builders using variables." + env['FOO_SUFFIX'] = '.BAR2' + builder.add_action('$NEW_SUFFIX', func_action) + flag = 0 + try: + builder(env, target='test5', source=['test5.BAR2'])[0] + except SCons.Errors.UserError: + flag = 1 + assert flag, "UserError should be thrown when we call a builder with ambigous suffixes." + + def test_src_builder(self): + """Test CompositeBuilder's use of a src_builder""" + env = Environment() + + foo_bld = SCons.Builder.Builder(action = 'a-foo', + src_suffix = '.ina', + suffix = '.foo') + assert isinstance(foo_bld, SCons.Builder.BuilderBase) + builder = SCons.Builder.Builder(action = { '.foo' : 'foo', + '.bar' : 'bar' }, + src_builder = foo_bld) + assert isinstance(builder, SCons.Builder.CompositeBuilder) + assert isinstance(builder.action, SCons.Action.CommandGeneratorAction) + + tgt = builder(env, target='t1', source='t1a.ina t1b.ina')[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase) + + tgt = builder(env, target='t2', source='t2a.foo t2b.ina')[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase), tgt.builder.__dict__ + + bar_bld = SCons.Builder.Builder(action = 'a-bar', + src_suffix = '.inb', + suffix = '.bar') + assert isinstance(bar_bld, SCons.Builder.BuilderBase) + builder = SCons.Builder.Builder(action = { '.foo' : 'foo'}, + src_builder = [foo_bld, bar_bld]) + assert isinstance(builder, SCons.Builder.CompositeBuilder) + assert isinstance(builder.action, SCons.Action.CommandGeneratorAction) + + builder.add_action('.bar', 'bar') + + tgt = builder(env, target='t3-foo', source='t3a.foo t3b.ina')[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase) + + tgt = builder(env, target='t3-bar', source='t3a.bar t3b.inb')[0] + assert isinstance(tgt.builder, SCons.Builder.BuilderBase) + + flag = 0 + try: + builder(env, target='t5', source=['test5a.foo', 'test5b.inb'])[0] + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown when we call a builder with files of different suffixes." + expect = "While building `['t5']' from `test5b.bar': Cannot build multiple sources with different extensions: .foo, .bar" + assert str(e) == expect, e + + flag = 0 + try: + builder(env, target='t6', source=['test6a.bar', 'test6b.ina'])[0] + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown when we call a builder with files of different suffixes." + expect = "While building `['t6']' from `test6b.foo': Cannot build multiple sources with different extensions: .bar, .foo" + assert str(e) == expect, e + + flag = 0 + try: + builder(env, target='t4', source=['test4a.ina', 'test4b.inb'])[0] + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown when we call a builder with files of different suffixes." + expect = "While building `['t4']' from `test4b.bar': Cannot build multiple sources with different extensions: .foo, .bar" + assert str(e) == expect, e + + flag = 0 + try: + builder(env, target='t7', source=[env.fs.File('test7')])[0] + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown when we call a builder with files of different suffixes." + expect = "While building `['t7']': Cannot deduce file extension from source files: ['test7']" + assert str(e) == expect, e + + flag = 0 + try: + builder(env, target='t8', source=['test8.unknown'])[0] + except SCons.Errors.UserError, e: + flag = 1 + assert flag, "UserError should be thrown when we call a builder target with an unknown suffix." + expect = "While building `['t8']' from `['test8.unknown']': Don't know how to build from a source file with suffix `.unknown'. Expected a suffix in this list: ['.foo', '.bar']." + assert str(e) == expect, e + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + BuilderTestCase, + CompositeBuilderTestCase + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/CacheDir.py b/src/engine/SCons/CacheDir.py new file mode 100644 index 0000000..5181d49 --- /dev/null +++ b/src/engine/SCons/CacheDir.py @@ -0,0 +1,217 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/CacheDir.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +CacheDir support +""" + +import os.path +import stat +import string +import sys + +import SCons.Action + +cache_enabled = True +cache_debug = False +cache_force = False +cache_show = False + +def CacheRetrieveFunc(target, source, env): + t = target[0] + fs = t.fs + cd = env.get_CacheDir() + cachedir, cachefile = cd.cachepath(t) + if not fs.exists(cachefile): + cd.CacheDebug('CacheRetrieve(%s): %s not in cache\n', t, cachefile) + return 1 + cd.CacheDebug('CacheRetrieve(%s): retrieving from %s\n', t, cachefile) + if SCons.Action.execute_actions: + if fs.islink(cachefile): + fs.symlink(fs.readlink(cachefile), t.path) + else: + env.copy_from_cache(cachefile, t.path) + st = fs.stat(cachefile) + fs.chmod(t.path, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + return 0 + +def CacheRetrieveString(target, source, env): + t = target[0] + fs = t.fs + cd = env.get_CacheDir() + cachedir, cachefile = cd.cachepath(t) + if t.fs.exists(cachefile): + return "Retrieved `%s' from cache" % t.path + return None + +CacheRetrieve = SCons.Action.Action(CacheRetrieveFunc, CacheRetrieveString) + +CacheRetrieveSilent = SCons.Action.Action(CacheRetrieveFunc, None) + +def CachePushFunc(target, source, env): + t = target[0] + if t.nocache: + return + fs = t.fs + cd = env.get_CacheDir() + cachedir, cachefile = cd.cachepath(t) + if fs.exists(cachefile): + # Don't bother copying it if it's already there. Note that + # usually this "shouldn't happen" because if the file already + # existed in cache, we'd have retrieved the file from there, + # not built it. This can happen, though, in a race, if some + # other person running the same build pushes their copy to + # the cache after we decide we need to build it but before our + # build completes. + cd.CacheDebug('CachePush(%s): %s already exists in cache\n', t, cachefile) + return + + cd.CacheDebug('CachePush(%s): pushing to %s\n', t, cachefile) + + tempfile = cachefile+'.tmp'+str(os.getpid()) + errfmt = "Unable to copy %s to cache. Cache file is %s" + + if not fs.isdir(cachedir): + try: + fs.makedirs(cachedir) + except EnvironmentError: + # We may have received an exception because another process + # has beaten us creating the directory. + if not fs.isdir(cachedir): + msg = errfmt % (str(target), cachefile) + raise SCons.Errors.EnvironmentError, msg + + try: + if fs.islink(t.path): + fs.symlink(fs.readlink(t.path), tempfile) + else: + fs.copy2(t.path, tempfile) + fs.rename(tempfile, cachefile) + st = fs.stat(t.path) + fs.chmod(cachefile, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + except EnvironmentError: + # It's possible someone else tried writing the file at the + # same time we did, or else that there was some problem like + # the CacheDir being on a separate file system that's full. + # In any case, inability to push a file to cache doesn't affect + # the correctness of the build, so just print a warning. + msg = errfmt % (str(target), cachefile) + SCons.Warnings.warn(SCons.Warnings.CacheWriteErrorWarning, msg) + +CachePush = SCons.Action.Action(CachePushFunc, None) + +class CacheDir: + + def __init__(self, path): + try: + import hashlib + except ImportError: + msg = "No hashlib or MD5 module available, CacheDir() not supported" + SCons.Warnings.warn(SCons.Warnings.NoMD5ModuleWarning, msg) + self.path = None + else: + self.path = path + self.current_cache_debug = None + self.debugFP = None + + def CacheDebug(self, fmt, target, cachefile): + if cache_debug != self.current_cache_debug: + if cache_debug == '-': + self.debugFP = sys.stdout + elif cache_debug: + self.debugFP = open(cache_debug, 'w') + else: + self.debugFP = None + self.current_cache_debug = cache_debug + if self.debugFP: + self.debugFP.write(fmt % (target, os.path.split(cachefile)[1])) + + def is_enabled(self): + return (cache_enabled and not self.path is None) + + def cachepath(self, node): + """ + """ + if not self.is_enabled(): + return None, None + + sig = node.get_cachedir_bsig() + subdir = string.upper(sig[0]) + dir = os.path.join(self.path, subdir) + return dir, os.path.join(dir, sig) + + def retrieve(self, node): + """ + This method is called from multiple threads in a parallel build, + so only do thread safe stuff here. Do thread unsafe stuff in + built(). + + Note that there's a special trick here with the execute flag + (one that's not normally done for other actions). Basically + if the user requested a no_exec (-n) build, then + SCons.Action.execute_actions is set to 0 and when any action + is called, it does its showing but then just returns zero + instead of actually calling the action execution operation. + The problem for caching is that if the file does NOT exist in + cache then the CacheRetrieveString won't return anything to + show for the task, but the Action.__call__ won't call + CacheRetrieveFunc; instead it just returns zero, which makes + the code below think that the file *was* successfully + retrieved from the cache, therefore it doesn't do any + subsequent building. However, the CacheRetrieveString didn't + print anything because it didn't actually exist in the cache, + and no more build actions will be performed, so the user just + sees nothing. The fix is to tell Action.__call__ to always + execute the CacheRetrieveFunc and then have the latter + explicitly check SCons.Action.execute_actions itself. + """ + if not self.is_enabled(): + return False + + env = node.get_build_env() + if cache_show: + if CacheRetrieveSilent(node, [], env, execute=1) == 0: + node.build(presub=0, execute=0) + return True + else: + if CacheRetrieve(node, [], env, execute=1) == 0: + return True + + return False + + def push(self, node): + if not self.is_enabled(): + return + return CachePush(node, [], node.get_build_env()) + + def push_if_forced(self, node): + if cache_force: + return self.push(node) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/CacheDirTests.py b/src/engine/SCons/CacheDirTests.py new file mode 100644 index 0000000..0e28885 --- /dev/null +++ b/src/engine/SCons/CacheDirTests.py @@ -0,0 +1,297 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/CacheDirTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import shutil +import sys +import unittest + +from TestCmd import TestCmd + +import SCons.CacheDir + +built_it = None + +class Action: + def __call__(self, targets, sources, env, **kw): + global built_it + if kw.get('execute', 1): + built_it = 1 + return 0 + def genstring(self, target, source, env): + return str(self) + def get_contents(self, target, source, env): + return '' + +class Builder: + def __init__(self, environment, action): + self.env = environment + self.action = action + self.overrides = {} + self.source_scanner = None + self.target_scanner = None + +class Environment: + def __init__(self, cachedir): + self.cachedir = cachedir + def Override(self, overrides): + return self + def get_CacheDir(self): + return self.cachedir + +class BaseTestCase(unittest.TestCase): + """ + Base fixtures common to our other unittest classes. + """ + def setUp(self): + self.test = TestCmd(workdir='') + + import SCons.Node.FS + self.fs = SCons.Node.FS.FS() + + self._CacheDir = SCons.CacheDir.CacheDir('cache') + + def File(self, name, bsig=None, action=Action()): + node = self.fs.File(name) + node.builder_set(Builder(Environment(self._CacheDir), action)) + if bsig: + node.cachesig = bsig + #node.binfo = node.BuildInfo(node) + #node.binfo.ninfo.bsig = bsig + return node + +class CacheDirTestCase(BaseTestCase): + """ + Test calling CacheDir code directly. + """ + def test_cachepath(self): + """Test the cachepath() method""" + + # Verify how the cachepath() method determines the name + # of the file in cache. + def my_collect(list): + return list[0] + save_collect = SCons.Util.MD5collect + SCons.Util.MD5collect = my_collect + + try: + f5 = self.File("cd.f5", 'a_fake_bsig') + result = self._CacheDir.cachepath(f5) + dirname = os.path.join('cache', 'A') + filename = os.path.join(dirname, 'a_fake_bsig') + assert result == (dirname, filename), result + finally: + SCons.Util.MD5collect = save_collect + +class FileTestCase(BaseTestCase): + """ + Test calling CacheDir code through Node.FS.File interfaces. + """ + # These tests were originally in Nodes/FSTests.py and got moved + # when the CacheDir support was refactored into its own module. + # Look in the history for Node/FSTests.py if any of this needs + # to be re-examined. + def retrieve_succeed(self, target, source, env, execute=1): + self.retrieved.append(target) + return 0 + + def retrieve_fail(self, target, source, env, execute=1): + self.retrieved.append(target) + return 1 + + def push(self, target, source, env): + self.pushed.append(target) + return 0 + + def test_CacheRetrieve(self): + """Test the CacheRetrieve() function""" + + save_CacheRetrieve = SCons.CacheDir.CacheRetrieve + self.retrieved = [] + + f1 = self.File("cd.f1") + try: + SCons.CacheDir.CacheRetrieve = self.retrieve_succeed + self.retrieved = [] + built_it = None + + r = f1.retrieve_from_cache() + assert r == 1, r + assert self.retrieved == [f1], self.retrieved + assert built_it is None, built_it + + SCons.CacheDir.CacheRetrieve = self.retrieve_fail + self.retrieved = [] + built_it = None + + r = f1.retrieve_from_cache() + assert not r, r + assert self.retrieved == [f1], self.retrieved + assert built_it is None, built_it + finally: + SCons.CacheDir.CacheRetrieve = save_CacheRetrieve + + def test_CacheRetrieveSilent(self): + """Test the CacheRetrieveSilent() function""" + + save_CacheRetrieveSilent = SCons.CacheDir.CacheRetrieveSilent + + SCons.CacheDir.cache_show = 1 + + f2 = self.File("cd.f2", 'f2_bsig') + try: + SCons.CacheDir.CacheRetrieveSilent = self.retrieve_succeed + self.retrieved = [] + built_it = None + + r = f2.retrieve_from_cache() + assert r == 1, r + assert self.retrieved == [f2], self.retrieved + assert built_it is None, built_it + + SCons.CacheDir.CacheRetrieveSilent = self.retrieve_fail + self.retrieved = [] + built_it = None + + r = f2.retrieve_from_cache() + assert r is False, r + assert self.retrieved == [f2], self.retrieved + assert built_it is None, built_it + finally: + SCons.CacheDir.CacheRetrieveSilent = save_CacheRetrieveSilent + + def test_CachePush(self): + """Test the CachePush() function""" + + save_CachePush = SCons.CacheDir.CachePush + + SCons.CacheDir.CachePush = self.push + + try: + self.pushed = [] + + cd_f3 = self.test.workpath("cd.f3") + f3 = self.File(cd_f3) + f3.push_to_cache() + assert self.pushed == [], self.pushed + self.test.write(cd_f3, "cd.f3\n") + f3.push_to_cache() + assert self.pushed == [f3], self.pushed + + self.pushed = [] + + cd_f4 = self.test.workpath("cd.f4") + f4 = self.File(cd_f4) + f4.visited() + assert self.pushed == [], self.pushed + self.test.write(cd_f4, "cd.f4\n") + f4.clear() + f4.visited() + assert self.pushed == [], self.pushed + SCons.CacheDir.cache_force = 1 + f4.clear() + f4.visited() + assert self.pushed == [f4], self.pushed + finally: + SCons.CacheDir.CachePush = save_CachePush + + def test_warning(self): + """Test raising a warning if we can't copy a file to cache.""" + + test = TestCmd(workdir='') + + save_copy2 = shutil.copy2 + def copy2(src, dst): + raise OSError + shutil.copy2 = copy2 + save_mkdir = os.mkdir + def mkdir(dir, mode=0): + pass + os.mkdir = mkdir + old_warn_exceptions = SCons.Warnings.warningAsException(1) + SCons.Warnings.enableWarningClass(SCons.Warnings.CacheWriteErrorWarning) + + try: + cd_f7 = self.test.workpath("cd.f7") + self.test.write(cd_f7, "cd.f7\n") + f7 = self.File(cd_f7, 'f7_bsig') + + warn_caught = 0 + try: + f7.push_to_cache() + except SCons.Errors.BuildError, e: + assert e.exc_info[0] == SCons.Warnings.CacheWriteErrorWarning + warn_caught = 1 + assert warn_caught + finally: + shutil.copy2 = save_copy2 + os.mkdir = save_mkdir + SCons.Warnings.warningAsException(old_warn_exceptions) + SCons.Warnings.suppressWarningClass(SCons.Warnings.CacheWriteErrorWarning) + + def test_no_strfunction(self): + """Test handling no strfunction() for an action.""" + + save_CacheRetrieveSilent = SCons.CacheDir.CacheRetrieveSilent + + f8 = self.File("cd.f8", 'f8_bsig') + try: + SCons.CacheDir.CacheRetrieveSilent = self.retrieve_succeed + self.retrieved = [] + built_it = None + + r = f8.retrieve_from_cache() + assert r == 1, r + assert self.retrieved == [f8], self.retrieved + assert built_it is None, built_it + + SCons.CacheDir.CacheRetrieveSilent = self.retrieve_fail + self.retrieved = [] + built_it = None + + r = f8.retrieve_from_cache() + assert r is False, r + assert self.retrieved == [f8], self.retrieved + assert built_it is None, built_it + finally: + SCons.CacheDir.CacheRetrieveSilent = save_CacheRetrieveSilent + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + CacheDirTestCase, + FileTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Conftest.py b/src/engine/SCons/Conftest.py new file mode 100644 index 0000000..e995e77 --- /dev/null +++ b/src/engine/SCons/Conftest.py @@ -0,0 +1,794 @@ +"""SCons.Conftest + +Autoconf-like configuration support; low level implementation of tests. +""" + +# +# Copyright (c) 2003 Stichting NLnet Labs +# Copyright (c) 2001, 2002, 2003 Steven Knight +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +# +# The purpose of this module is to define how a check is to be performed. +# Use one of the Check...() functions below. +# + +# +# A context class is used that defines functions for carrying out the tests, +# logging and messages. The following methods and members must be present: +# +# context.Display(msg) Function called to print messages that are normally +# displayed for the user. Newlines are explicitly used. +# The text should also be written to the logfile! +# +# context.Log(msg) Function called to write to a log file. +# +# context.BuildProg(text, ext) +# Function called to build a program, using "ext" for the +# file extention. Must return an empty string for +# success, an error message for failure. +# For reliable test results building should be done just +# like an actual program would be build, using the same +# command and arguments (including configure results so +# far). +# +# context.CompileProg(text, ext) +# Function called to compile a program, using "ext" for +# the file extention. Must return an empty string for +# success, an error message for failure. +# For reliable test results compiling should be done just +# like an actual source file would be compiled, using the +# same command and arguments (including configure results +# so far). +# +# context.AppendLIBS(lib_name_list) +# Append "lib_name_list" to the value of LIBS. +# "lib_namelist" is a list of strings. +# Return the value of LIBS before changing it (any type +# can be used, it is passed to SetLIBS() later.) +# +# context.PrependLIBS(lib_name_list) +# Prepend "lib_name_list" to the value of LIBS. +# "lib_namelist" is a list of strings. +# Return the value of LIBS before changing it (any type +# can be used, it is passed to SetLIBS() later.) +# +# context.SetLIBS(value) +# Set LIBS to "value". The type of "value" is what +# AppendLIBS() returned. +# Return the value of LIBS before changing it (any type +# can be used, it is passed to SetLIBS() later.) +# +# context.headerfilename +# Name of file to append configure results to, usually +# "confdefs.h". +# The file must not exist or be empty when starting. +# Empty or None to skip this (some tests will not work!). +# +# context.config_h (may be missing). If present, must be a string, which +# will be filled with the contents of a config_h file. +# +# context.vardict Dictionary holding variables used for the tests and +# stores results from the tests, used for the build +# commands. +# Normally contains "CC", "LIBS", "CPPFLAGS", etc. +# +# context.havedict Dictionary holding results from the tests that are to +# be used inside a program. +# Names often start with "HAVE_". These are zero +# (feature not present) or one (feature present). Other +# variables may have any value, e.g., "PERLVERSION" can +# be a number and "SYSTEMNAME" a string. +# + +import re +import string +from types import IntType + +# +# PUBLIC VARIABLES +# + +LogInputFiles = 1 # Set that to log the input files in case of a failed test +LogErrorMessages = 1 # Set that to log Conftest-generated error messages + +# +# PUBLIC FUNCTIONS +# + +# Generic remarks: +# - When a language is specified which is not supported the test fails. The +# message is a bit different, because not all the arguments for the normal +# message are available yet (chicken-egg problem). + + +def CheckBuilder(context, text = None, language = None): + """ + Configure check to see if the compiler works. + Note that this uses the current value of compiler and linker flags, make + sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly. + "language" should be "C" or "C++" and is used to select the compiler. + Default is "C". + "text" may be used to specify the code to be build. + Returns an empty string for success, an error message for failure. + """ + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("%s\n" % msg) + return msg + + if not text: + text = """ +int main() { + return 0; +} +""" + + context.Display("Checking if building a %s file works... " % lang) + ret = context.BuildProg(text, suffix) + _YesNoResult(context, ret, None, text) + return ret + +def CheckCC(context): + """ + Configure check for a working C compiler. + + This checks whether the C compiler, as defined in the $CC construction + variable, can compile a C source file. It uses the current $CCCOM value + too, so that it can test against non working flags. + + """ + context.Display("Checking whether the C compiler works") + text = """ +int main() +{ + return 0; +} +""" + ret = _check_empty_program(context, 'CC', text, 'C') + _YesNoResult(context, ret, None, text) + return ret + +def CheckSHCC(context): + """ + Configure check for a working shared C compiler. + + This checks whether the C compiler, as defined in the $SHCC construction + variable, can compile a C source file. It uses the current $SHCCCOM value + too, so that it can test against non working flags. + + """ + context.Display("Checking whether the (shared) C compiler works") + text = """ +int foo() +{ + return 0; +} +""" + ret = _check_empty_program(context, 'SHCC', text, 'C', use_shared = True) + _YesNoResult(context, ret, None, text) + return ret + +def CheckCXX(context): + """ + Configure check for a working CXX compiler. + + This checks whether the CXX compiler, as defined in the $CXX construction + variable, can compile a CXX source file. It uses the current $CXXCOM value + too, so that it can test against non working flags. + + """ + context.Display("Checking whether the C++ compiler works") + text = """ +int main() +{ + return 0; +} +""" + ret = _check_empty_program(context, 'CXX', text, 'C++') + _YesNoResult(context, ret, None, text) + return ret + +def CheckSHCXX(context): + """ + Configure check for a working shared CXX compiler. + + This checks whether the CXX compiler, as defined in the $SHCXX construction + variable, can compile a CXX source file. It uses the current $SHCXXCOM value + too, so that it can test against non working flags. + + """ + context.Display("Checking whether the (shared) C++ compiler works") + text = """ +int main() +{ + return 0; +} +""" + ret = _check_empty_program(context, 'SHCXX', text, 'C++', use_shared = True) + _YesNoResult(context, ret, None, text) + return ret + +def _check_empty_program(context, comp, text, language, use_shared = False): + """Return 0 on success, 1 otherwise.""" + if not context.env.has_key(comp) or not context.env[comp]: + # The compiler construction variable is not set or empty + return 1 + + lang, suffix, msg = _lang2suffix(language) + if msg: + return 1 + + if use_shared: + return context.CompileSharedObject(text, suffix) + else: + return context.CompileProg(text, suffix) + + +def CheckFunc(context, function_name, header = None, language = None): + """ + Configure check for a function "function_name". + "language" should be "C" or "C++" and is used to select the compiler. + Default is "C". + Optional "header" can be defined to define a function prototype, include a + header file or anything else that comes before main(). + Sets HAVE_function_name in context.havedict according to the result. + Note that this uses the current value of compiler and linker flags, make + sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly. + Returns an empty string for success, an error message for failure. + """ + + # Remarks from autoconf: + # - Don't include because on OSF/1 3.0 it includes + # which includes which contains a prototype for select. + # Similarly for bzero. + # - assert.h is included to define __stub macros and hopefully few + # prototypes, which can conflict with char $1(); below. + # - Override any gcc2 internal prototype to avoid an error. + # - We use char for the function declaration because int might match the + # return type of a gcc2 builtin and then its argument prototype would + # still apply. + # - The GNU C library defines this for functions which it implements to + # always fail with ENOSYS. Some functions are actually named something + # starting with __ and the normal name is an alias. + + if context.headerfilename: + includetext = '#include "%s"' % context.headerfilename + else: + includetext = '' + if not header: + header = """ +#ifdef __cplusplus +extern "C" +#endif +char %s();""" % function_name + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for %s(): %s\n" % (function_name, msg)) + return msg + + text = """ +%(include)s +#include +%(hdr)s + +int main() { +#if defined (__stub_%(name)s) || defined (__stub___%(name)s) + fail fail fail +#else + %(name)s(); +#endif + + return 0; +} +""" % { 'name': function_name, + 'include': includetext, + 'hdr': header } + + context.Display("Checking for %s function %s()... " % (lang, function_name)) + ret = context.BuildProg(text, suffix) + _YesNoResult(context, ret, "HAVE_" + function_name, text, + "Define to 1 if the system has the function `%s'." %\ + function_name) + return ret + + +def CheckHeader(context, header_name, header = None, language = None, + include_quotes = None): + """ + Configure check for a C or C++ header file "header_name". + Optional "header" can be defined to do something before including the + header file (unusual, supported for consistency). + "language" should be "C" or "C++" and is used to select the compiler. + Default is "C". + Sets HAVE_header_name in context.havedict according to the result. + Note that this uses the current value of compiler and linker flags, make + sure $CFLAGS and $CPPFLAGS are set correctly. + Returns an empty string for success, an error message for failure. + """ + # Why compile the program instead of just running the preprocessor? + # It is possible that the header file exists, but actually using it may + # fail (e.g., because it depends on other header files). Thus this test is + # more strict. It may require using the "header" argument. + # + # Use <> by default, because the check is normally used for system header + # files. SCons passes '""' to overrule this. + + # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. + if context.headerfilename: + includetext = '#include "%s"\n' % context.headerfilename + else: + includetext = '' + if not header: + header = "" + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for header file %s: %s\n" + % (header_name, msg)) + return msg + + if not include_quotes: + include_quotes = "<>" + + text = "%s%s\n#include %s%s%s\n\n" % (includetext, header, + include_quotes[0], header_name, include_quotes[1]) + + context.Display("Checking for %s header file %s... " % (lang, header_name)) + ret = context.CompileProg(text, suffix) + _YesNoResult(context, ret, "HAVE_" + header_name, text, + "Define to 1 if you have the <%s> header file." % header_name) + return ret + + +def CheckType(context, type_name, fallback = None, + header = None, language = None): + """ + Configure check for a C or C++ type "type_name". + Optional "header" can be defined to include a header file. + "language" should be "C" or "C++" and is used to select the compiler. + Default is "C". + Sets HAVE_type_name in context.havedict according to the result. + Note that this uses the current value of compiler and linker flags, make + sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly. + Returns an empty string for success, an error message for failure. + """ + + # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. + if context.headerfilename: + includetext = '#include "%s"' % context.headerfilename + else: + includetext = '' + if not header: + header = "" + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for %s type: %s\n" % (type_name, msg)) + return msg + + # Remarks from autoconf about this test: + # - Grepping for the type in include files is not reliable (grep isn't + # portable anyway). + # - Using "TYPE my_var;" doesn't work for const qualified types in C++. + # Adding an initializer is not valid for some C++ classes. + # - Using the type as parameter to a function either fails for K&$ C or for + # C++. + # - Using "TYPE *my_var;" is valid in C for some types that are not + # declared (struct something). + # - Using "sizeof(TYPE)" is valid when TYPE is actually a variable. + # - Using the previous two together works reliably. + text = """ +%(include)s +%(header)s + +int main() { + if ((%(name)s *) 0) + return 0; + if (sizeof (%(name)s)) + return 0; +} +""" % { 'include': includetext, + 'header': header, + 'name': type_name } + + context.Display("Checking for %s type %s... " % (lang, type_name)) + ret = context.BuildProg(text, suffix) + _YesNoResult(context, ret, "HAVE_" + type_name, text, + "Define to 1 if the system has the type `%s'." % type_name) + if ret and fallback and context.headerfilename: + f = open(context.headerfilename, "a") + f.write("typedef %s %s;\n" % (fallback, type_name)) + f.close() + + return ret + +def CheckTypeSize(context, type_name, header = None, language = None, expect = None): + """This check can be used to get the size of a given type, or to check whether + the type is of expected size. + + Arguments: + - type : str + the type to check + - includes : sequence + list of headers to include in the test code before testing the type + - language : str + 'C' or 'C++' + - expect : int + if given, will test wether the type has the given number of bytes. + If not given, will automatically find the size. + + Returns: + status : int + 0 if the check failed, or the found size of the type if the check succeeded.""" + + # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. + if context.headerfilename: + includetext = '#include "%s"' % context.headerfilename + else: + includetext = '' + + if not header: + header = "" + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for %s type: %s\n" % (type_name, msg)) + return msg + + src = includetext + header + if not expect is None: + # Only check if the given size is the right one + context.Display('Checking %s is %d bytes... ' % (type_name, expect)) + + # test code taken from autoconf: this is a pretty clever hack to find that + # a type is of a given size using only compilation. This speeds things up + # quite a bit compared to straightforward code using TryRun + src = src + r""" +typedef %s scons_check_type; + +int main() +{ + static int test_array[1 - 2 * !(((long int) (sizeof(scons_check_type))) == %d)]; + test_array[0] = 0; + + return 0; +} +""" + + st = context.CompileProg(src % (type_name, expect), suffix) + if not st: + context.Display("yes\n") + _Have(context, "SIZEOF_%s" % type_name, expect, + "The size of `%s', as computed by sizeof." % type_name) + return expect + else: + context.Display("no\n") + _LogFailed(context, src, st) + return 0 + else: + # Only check if the given size is the right one + context.Message('Checking size of %s ... ' % type_name) + + # We have to be careful with the program we wish to test here since + # compilation will be attempted using the current environment's flags. + # So make sure that the program will compile without any warning. For + # example using: 'int main(int argc, char** argv)' will fail with the + # '-Wall -Werror' flags since the variables argc and argv would not be + # used in the program... + # + src = src + """ +#include +#include +int main() { + printf("%d", (int)sizeof(""" + type_name + """)); + return 0; +} + """ + st, out = context.RunProg(src, suffix) + try: + size = int(out) + except ValueError: + # If cannot convert output of test prog to an integer (the size), + # something went wront, so just fail + st = 1 + size = 0 + + if not st: + context.Display("yes\n") + _Have(context, "SIZEOF_%s" % type_name, size, + "The size of `%s', as computed by sizeof." % type_name) + return size + else: + context.Display("no\n") + _LogFailed(context, src, st) + return 0 + + return 0 + +def CheckDeclaration(context, symbol, includes = None, language = None): + """Checks whether symbol is declared. + + Use the same test as autoconf, that is test whether the symbol is defined + as a macro or can be used as an r-value. + + Arguments: + symbol : str + the symbol to check + includes : str + Optional "header" can be defined to include a header file. + language : str + only C and C++ supported. + + Returns: + status : bool + True if the check failed, False if succeeded.""" + + # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. + if context.headerfilename: + includetext = '#include "%s"' % context.headerfilename + else: + includetext = '' + + if not includes: + includes = "" + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for declaration %s: %s\n" % (type_name, msg)) + return msg + + src = includetext + includes + context.Display('Checking whether %s is declared... ' % symbol) + + src = src + r""" +int main() +{ +#ifndef %s + (void) %s; +#endif + ; + return 0; +} +""" % (symbol, symbol) + + st = context.CompileProg(src, suffix) + _YesNoResult(context, st, "HAVE_DECL_" + symbol, src, + "Set to 1 if %s is defined." % symbol) + return st + +def CheckLib(context, libs, func_name = None, header = None, + extra_libs = None, call = None, language = None, autoadd = 1, + append = True): + """ + Configure check for a C or C++ libraries "libs". Searches through + the list of libraries, until one is found where the test succeeds. + Tests if "func_name" or "call" exists in the library. Note: if it exists + in another library the test succeeds anyway! + Optional "header" can be defined to include a header file. If not given a + default prototype for "func_name" is added. + Optional "extra_libs" is a list of library names to be added after + "lib_name" in the build command. To be used for libraries that "lib_name" + depends on. + Optional "call" replaces the call to "func_name" in the test code. It must + consist of complete C statements, including a trailing ";". + Both "func_name" and "call" arguments are optional, and in that case, just + linking against the libs is tested. + "language" should be "C" or "C++" and is used to select the compiler. + Default is "C". + Note that this uses the current value of compiler and linker flags, make + sure $CFLAGS, $CPPFLAGS and $LIBS are set correctly. + Returns an empty string for success, an error message for failure. + """ + # Include "confdefs.h" first, so that the header can use HAVE_HEADER_H. + if context.headerfilename: + includetext = '#include "%s"' % context.headerfilename + else: + includetext = '' + if not header: + header = "" + + text = """ +%s +%s""" % (includetext, header) + + # Add a function declaration if needed. + if func_name and func_name != "main": + if not header: + text = text + """ +#ifdef __cplusplus +extern "C" +#endif +char %s(); +""" % func_name + + # The actual test code. + if not call: + call = "%s();" % func_name + + # if no function to test, leave main() blank + text = text + """ +int +main() { + %s +return 0; +} +""" % (call or "") + + if call: + i = string.find(call, "\n") + if i > 0: + calltext = call[:i] + ".." + elif call[-1] == ';': + calltext = call[:-1] + else: + calltext = call + + for lib_name in libs: + + lang, suffix, msg = _lang2suffix(language) + if msg: + context.Display("Cannot check for library %s: %s\n" % (lib_name, msg)) + return msg + + # if a function was specified to run in main(), say it + if call: + context.Display("Checking for %s in %s library %s... " + % (calltext, lang, lib_name)) + # otherwise, just say the name of library and language + else: + context.Display("Checking for %s library %s... " + % (lang, lib_name)) + + if lib_name: + l = [ lib_name ] + if extra_libs: + l.extend(extra_libs) + if append: + oldLIBS = context.AppendLIBS(l) + else: + oldLIBS = context.PrependLIBS(l) + sym = "HAVE_LIB" + lib_name + else: + oldLIBS = -1 + sym = None + + ret = context.BuildProg(text, suffix) + + _YesNoResult(context, ret, sym, text, + "Define to 1 if you have the `%s' library." % lib_name) + if oldLIBS != -1 and (ret or not autoadd): + context.SetLIBS(oldLIBS) + + if not ret: + return ret + + return ret + +# +# END OF PUBLIC FUNCTIONS +# + +def _YesNoResult(context, ret, key, text, comment = None): + """ + Handle the result of a test with a "yes" or "no" result. + "ret" is the return value: empty if OK, error message when not. + "key" is the name of the symbol to be defined (HAVE_foo). + "text" is the source code of the program used for testing. + "comment" is the C comment to add above the line defining the symbol (the + comment is automatically put inside a /* */). If None, no comment is added. + """ + if key: + _Have(context, key, not ret, comment) + if ret: + context.Display("no\n") + _LogFailed(context, text, ret) + else: + context.Display("yes\n") + + +def _Have(context, key, have, comment = None): + """ + Store result of a test in context.havedict and context.headerfilename. + "key" is a "HAVE_abc" name. It is turned into all CAPITALS and non- + alphanumerics are replaced by an underscore. + The value of "have" can be: + 1 - Feature is defined, add "#define key". + 0 - Feature is not defined, add "/* #undef key */". + Adding "undef" is what autoconf does. Not useful for the + compiler, but it shows that the test was done. + number - Feature is defined to this number "#define key have". + Doesn't work for 0 or 1, use a string then. + string - Feature is defined to this string "#define key have". + Give "have" as is should appear in the header file, include quotes + when desired and escape special characters! + """ + key_up = string.upper(key) + key_up = re.sub('[^A-Z0-9_]', '_', key_up) + context.havedict[key_up] = have + if have == 1: + line = "#define %s 1\n" % key_up + elif have == 0: + line = "/* #undef %s */\n" % key_up + elif type(have) == IntType: + line = "#define %s %d\n" % (key_up, have) + else: + line = "#define %s %s\n" % (key_up, str(have)) + + if comment is not None: + lines = "\n/* %s */\n" % comment + line + else: + lines = "\n" + line + + if context.headerfilename: + f = open(context.headerfilename, "a") + f.write(lines) + f.close() + elif hasattr(context,'config_h'): + context.config_h = context.config_h + lines + + +def _LogFailed(context, text, msg): + """ + Write to the log about a failed program. + Add line numbers, so that error messages can be understood. + """ + if LogInputFiles: + context.Log("Failed program was:\n") + lines = string.split(text, '\n') + if len(lines) and lines[-1] == '': + lines = lines[:-1] # remove trailing empty line + n = 1 + for line in lines: + context.Log("%d: %s\n" % (n, line)) + n = n + 1 + if LogErrorMessages: + context.Log("Error message: %s\n" % msg) + + +def _lang2suffix(lang): + """ + Convert a language name to a suffix. + When "lang" is empty or None C is assumed. + Returns a tuple (lang, suffix, None) when it works. + For an unrecognized language returns (None, None, msg). + Where: + lang = the unified language name + suffix = the suffix, including the leading dot + msg = an error message + """ + if not lang or lang in ["C", "c"]: + return ("C", ".c", None) + if lang in ["c++", "C++", "cpp", "CXX", "cxx"]: + return ("C++", ".cpp", None) + + return None, None, "Unsupported language: %s" % lang + + +# vim: set sw=4 et sts=4 tw=79 fo+=l: + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Debug.py b/src/engine/SCons/Debug.py new file mode 100644 index 0000000..5536169 --- /dev/null +++ b/src/engine/SCons/Debug.py @@ -0,0 +1,237 @@ +"""SCons.Debug + +Code for debugging SCons internal things. Not everything here is +guaranteed to work all the way back to Python 1.5.2, and shouldn't be +needed by most users. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Debug.py 4577 2009/12/27 19:44:43 scons" + +import os +import string +import sys +import time + +# Recipe 14.10 from the Python Cookbook. +try: + import weakref +except ImportError: + def logInstanceCreation(instance, name=None): + pass +else: + def logInstanceCreation(instance, name=None): + if name is None: + name = instance.__class__.__name__ + if not tracked_classes.has_key(name): + tracked_classes[name] = [] + tracked_classes[name].append(weakref.ref(instance)) + + + +tracked_classes = {} + +def string_to_classes(s): + if s == '*': + c = tracked_classes.keys() + c.sort() + return c + else: + return string.split(s) + +def fetchLoggedInstances(classes="*"): + classnames = string_to_classes(classes) + return map(lambda cn: (cn, len(tracked_classes[cn])), classnames) + +def countLoggedInstances(classes, file=sys.stdout): + for classname in string_to_classes(classes): + file.write("%s: %d\n" % (classname, len(tracked_classes[classname]))) + +def listLoggedInstances(classes, file=sys.stdout): + for classname in string_to_classes(classes): + file.write('\n%s:\n' % classname) + for ref in tracked_classes[classname]: + obj = ref() + if obj is not None: + file.write(' %s\n' % repr(obj)) + +def dumpLoggedInstances(classes, file=sys.stdout): + for classname in string_to_classes(classes): + file.write('\n%s:\n' % classname) + for ref in tracked_classes[classname]: + obj = ref() + if obj is not None: + file.write(' %s:\n' % obj) + for key, value in obj.__dict__.items(): + file.write(' %20s : %s\n' % (key, value)) + + + +if sys.platform[:5] == "linux": + # Linux doesn't actually support memory usage stats from getrusage(). + def memory(): + mstr = open('/proc/self/stat').read() + mstr = string.split(mstr)[22] + return int(mstr) +else: + try: + import resource + except ImportError: + try: + import win32process + import win32api + except ImportError: + def memory(): + return 0 + else: + def memory(): + process_handle = win32api.GetCurrentProcess() + memory_info = win32process.GetProcessMemoryInfo( process_handle ) + return memory_info['PeakWorkingSetSize'] + else: + def memory(): + res = resource.getrusage(resource.RUSAGE_SELF) + return res[4] + +# returns caller's stack +def caller_stack(*backlist): + import traceback + if not backlist: + backlist = [0] + result = [] + for back in backlist: + tb = traceback.extract_stack(limit=3+back) + key = tb[0][:3] + result.append('%s:%d(%s)' % func_shorten(key)) + return result + +caller_bases = {} +caller_dicts = {} + +# trace a caller's stack +def caller_trace(back=0): + import traceback + tb = traceback.extract_stack(limit=3+back) + tb.reverse() + callee = tb[1][:3] + caller_bases[callee] = caller_bases.get(callee, 0) + 1 + for caller in tb[2:]: + caller = callee + caller[:3] + try: + entry = caller_dicts[callee] + except KeyError: + caller_dicts[callee] = entry = {} + entry[caller] = entry.get(caller, 0) + 1 + callee = caller + +# print a single caller and its callers, if any +def _dump_one_caller(key, file, level=0): + l = [] + for c,v in caller_dicts[key].items(): + l.append((-v,c)) + l.sort() + leader = ' '*level + for v,c in l: + file.write("%s %6d %s:%d(%s)\n" % ((leader,-v) + func_shorten(c[-3:]))) + if caller_dicts.has_key(c): + _dump_one_caller(c, file, level+1) + +# print each call tree +def dump_caller_counts(file=sys.stdout): + keys = caller_bases.keys() + keys.sort() + for k in keys: + file.write("Callers of %s:%d(%s), %d calls:\n" + % (func_shorten(k) + (caller_bases[k],))) + _dump_one_caller(k, file) + +shorten_list = [ + ( '/scons/SCons/', 1), + ( '/src/engine/SCons/', 1), + ( '/usr/lib/python', 0), +] + +if os.sep != '/': + def platformize(t): + return (string.replace(t[0], '/', os.sep), t[1]) + shorten_list = map(platformize, shorten_list) + del platformize + +def func_shorten(func_tuple): + f = func_tuple[0] + for t in shorten_list: + i = string.find(f, t[0]) + if i >= 0: + if t[1]: + i = i + len(t[0]) + return (f[i:],)+func_tuple[1:] + return func_tuple + + +TraceFP = {} +if sys.platform == 'win32': + TraceDefault = 'con' +else: + TraceDefault = '/dev/tty' + +TimeStampDefault = None +StartTime = time.time() +PreviousTime = StartTime + +def Trace(msg, file=None, mode='w', tstamp=None): + """Write a trace message to a file. Whenever a file is specified, + it becomes the default for the next call to Trace().""" + global TraceDefault + global TimeStamp + global PreviousTime + if file is None: + file = TraceDefault + else: + TraceDefault = file + if tstamp is None: + tstamp = TimeStampDefault + else: + TimeStampDefault = tstamp + try: + fp = TraceFP[file] + except KeyError: + try: + fp = TraceFP[file] = open(file, mode) + except TypeError: + # Assume we were passed an open file pointer. + fp = file + if tstamp: + now = time.time() + fp.write('%8.4f %8.4f: ' % (now - StartTime, now - PreviousTime)) + PreviousTime = now + fp.write(msg) + fp.flush() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Defaults.py b/src/engine/SCons/Defaults.py new file mode 100644 index 0000000..0ff0399 --- /dev/null +++ b/src/engine/SCons/Defaults.py @@ -0,0 +1,485 @@ +"""SCons.Defaults + +Builders and other things for the local site. Here's where we'll +duplicate the functionality of autoconf until we move it into the +installation procedure or use something like qmconf. + +The code that reads the registry to find MSVC components was borrowed +from distutils.msvccompiler. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Defaults.py 4577 2009/12/27 19:44:43 scons" + + + +import os +import os.path +import errno +import shutil +import stat +import string +import time +import types +import sys + +import SCons.Action +import SCons.Builder +import SCons.CacheDir +import SCons.Environment +import SCons.PathList +import SCons.Subst +import SCons.Tool + +# A placeholder for a default Environment (for fetching source files +# from source code management systems and the like). This must be +# initialized later, after the top-level directory is set by the calling +# interface. +_default_env = None + +# Lazily instantiate the default environment so the overhead of creating +# it doesn't apply when it's not needed. +def _fetch_DefaultEnvironment(*args, **kw): + """ + Returns the already-created default construction environment. + """ + global _default_env + return _default_env + +def DefaultEnvironment(*args, **kw): + """ + Initial public entry point for creating the default construction + Environment. + + After creating the environment, we overwrite our name + (DefaultEnvironment) with the _fetch_DefaultEnvironment() function, + which more efficiently returns the initialized default construction + environment without checking for its existence. + + (This function still exists with its _default_check because someone + else (*cough* Script/__init__.py *cough*) may keep a reference + to this function. So we can't use the fully functional idiom of + having the name originally be a something that *only* creates the + construction environment and then overwrites the name.) + """ + global _default_env + if not _default_env: + import SCons.Util + _default_env = apply(SCons.Environment.Environment, args, kw) + if SCons.Util.md5: + _default_env.Decider('MD5') + else: + _default_env.Decider('timestamp-match') + global DefaultEnvironment + DefaultEnvironment = _fetch_DefaultEnvironment + _default_env._CacheDir_path = None + return _default_env + +# Emitters for setting the shared attribute on object files, +# and an action for checking that all of the source files +# going into a shared library are, in fact, shared. +def StaticObjectEmitter(target, source, env): + for tgt in target: + tgt.attributes.shared = None + return (target, source) + +def SharedObjectEmitter(target, source, env): + for tgt in target: + tgt.attributes.shared = 1 + return (target, source) + +def SharedFlagChecker(source, target, env): + same = env.subst('$STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME') + if same == '0' or same == '' or same == 'False': + for src in source: + try: + shared = src.attributes.shared + except AttributeError: + shared = None + if not shared: + raise SCons.Errors.UserError, "Source file: %s is static and is not compatible with shared target: %s" % (src, target[0]) + +SharedCheck = SCons.Action.Action(SharedFlagChecker, None) + +# Some people were using these variable name before we made +# SourceFileScanner part of the public interface. Don't break their +# SConscript files until we've given them some fair warning and a +# transition period. +CScan = SCons.Tool.CScanner +DScan = SCons.Tool.DScanner +LaTeXScan = SCons.Tool.LaTeXScanner +ObjSourceScan = SCons.Tool.SourceFileScanner +ProgScan = SCons.Tool.ProgramScanner + +# These aren't really tool scanners, so they don't quite belong with +# the rest of those in Tool/__init__.py, but I'm not sure where else +# they should go. Leave them here for now. +import SCons.Scanner.Dir +DirScanner = SCons.Scanner.Dir.DirScanner() +DirEntryScanner = SCons.Scanner.Dir.DirEntryScanner() + +# Actions for common languages. +CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR") +ShCAction = SCons.Action.Action("$SHCCCOM", "$SHCCCOMSTR") +CXXAction = SCons.Action.Action("$CXXCOM", "$CXXCOMSTR") +ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR") + +ASAction = SCons.Action.Action("$ASCOM", "$ASCOMSTR") +ASPPAction = SCons.Action.Action("$ASPPCOM", "$ASPPCOMSTR") + +LinkAction = SCons.Action.Action("$LINKCOM", "$LINKCOMSTR") +ShLinkAction = SCons.Action.Action("$SHLINKCOM", "$SHLINKCOMSTR") + +LdModuleLinkAction = SCons.Action.Action("$LDMODULECOM", "$LDMODULECOMSTR") + +# Common tasks that we allow users to perform in platform-independent +# ways by creating ActionFactory instances. +ActionFactory = SCons.Action.ActionFactory + +def get_paths_str(dest): + # If dest is a list, we need to manually call str() on each element + if SCons.Util.is_List(dest): + elem_strs = [] + for element in dest: + elem_strs.append('"' + str(element) + '"') + return '[' + string.join(elem_strs, ', ') + ']' + else: + return '"' + str(dest) + '"' + +def chmod_func(dest, mode): + SCons.Node.FS.invalidate_node_memos(dest) + if not SCons.Util.is_List(dest): + dest = [dest] + for element in dest: + os.chmod(str(element), mode) + +def chmod_strfunc(dest, mode): + return 'Chmod(%s, 0%o)' % (get_paths_str(dest), mode) + +Chmod = ActionFactory(chmod_func, chmod_strfunc) + +def copy_func(dest, src): + SCons.Node.FS.invalidate_node_memos(dest) + if SCons.Util.is_List(src) and os.path.isdir(dest): + for file in src: + shutil.copy2(file, dest) + return 0 + elif os.path.isfile(src): + return shutil.copy2(src, dest) + else: + return shutil.copytree(src, dest, 1) + +Copy = ActionFactory(copy_func, + lambda dest, src: 'Copy("%s", "%s")' % (dest, src), + convert=str) + +def delete_func(dest, must_exist=0): + SCons.Node.FS.invalidate_node_memos(dest) + if not SCons.Util.is_List(dest): + dest = [dest] + for entry in dest: + entry = str(entry) + if not must_exist and not os.path.exists(entry): + continue + if not os.path.exists(entry) or os.path.isfile(entry): + os.unlink(entry) + continue + else: + shutil.rmtree(entry, 1) + continue + +def delete_strfunc(dest, must_exist=0): + return 'Delete(%s)' % get_paths_str(dest) + +Delete = ActionFactory(delete_func, delete_strfunc) + +def mkdir_func(dest): + SCons.Node.FS.invalidate_node_memos(dest) + if not SCons.Util.is_List(dest): + dest = [dest] + for entry in dest: + try: + os.makedirs(str(entry)) + except os.error, e: + p = str(entry) + if (e[0] == errno.EEXIST or (sys.platform=='win32' and e[0]==183)) \ + and os.path.isdir(str(entry)): + pass # not an error if already exists + else: + raise + +Mkdir = ActionFactory(mkdir_func, + lambda dir: 'Mkdir(%s)' % get_paths_str(dir)) + +def move_func(dest, src): + SCons.Node.FS.invalidate_node_memos(dest) + SCons.Node.FS.invalidate_node_memos(src) + shutil.move(src, dest) + +Move = ActionFactory(move_func, + lambda dest, src: 'Move("%s", "%s")' % (dest, src), + convert=str) + +def touch_func(dest): + SCons.Node.FS.invalidate_node_memos(dest) + if not SCons.Util.is_List(dest): + dest = [dest] + for file in dest: + file = str(file) + mtime = int(time.time()) + if os.path.exists(file): + atime = os.path.getatime(file) + else: + open(file, 'w') + atime = mtime + os.utime(file, (atime, mtime)) + +Touch = ActionFactory(touch_func, + lambda file: 'Touch(%s)' % get_paths_str(file)) + +# Internal utility functions + +def _concat(prefix, list, suffix, env, f=lambda x: x, target=None, source=None): + """ + Creates a new list from 'list' by first interpolating each element + in the list using the 'env' dictionary and then calling f on the + list, and finally calling _concat_ixes to concatenate 'prefix' and + 'suffix' onto each element of the list. + """ + if not list: + return list + + l = f(SCons.PathList.PathList(list).subst_path(env, target, source)) + if l is not None: + list = l + + return _concat_ixes(prefix, list, suffix, env) + +def _concat_ixes(prefix, list, suffix, env): + """ + Creates a new list from 'list' by concatenating the 'prefix' and + 'suffix' arguments onto each element of the list. A trailing space + on 'prefix' or leading space on 'suffix' will cause them to be put + into separate list elements rather than being concatenated. + """ + + result = [] + + # ensure that prefix and suffix are strings + prefix = str(env.subst(prefix, SCons.Subst.SUBST_RAW)) + suffix = str(env.subst(suffix, SCons.Subst.SUBST_RAW)) + + for x in list: + if isinstance(x, SCons.Node.FS.File): + result.append(x) + continue + x = str(x) + if x: + + if prefix: + if prefix[-1] == ' ': + result.append(prefix[:-1]) + elif x[:len(prefix)] != prefix: + x = prefix + x + + result.append(x) + + if suffix: + if suffix[0] == ' ': + result.append(suffix[1:]) + elif x[-len(suffix):] != suffix: + result[-1] = result[-1]+suffix + + return result + +def _stripixes(prefix, list, suffix, stripprefixes, stripsuffixes, env, c=None): + """ + This is a wrapper around _concat()/_concat_ixes() that checks for the + existence of prefixes or suffixes on list elements and strips them + where it finds them. This is used by tools (like the GNU linker) + that need to turn something like 'libfoo.a' into '-lfoo'. + """ + + if not list: + return list + + if not callable(c): + env_c = env['_concat'] + if env_c != _concat and callable(env_c): + # There's a custom _concat() method in the construction + # environment, and we've allowed people to set that in + # the past (see test/custom-concat.py), so preserve the + # backwards compatibility. + c = env_c + else: + c = _concat_ixes + + stripprefixes = map(env.subst, SCons.Util.flatten(stripprefixes)) + stripsuffixes = map(env.subst, SCons.Util.flatten(stripsuffixes)) + + stripped = [] + for l in SCons.PathList.PathList(list).subst_path(env, None, None): + if isinstance(l, SCons.Node.FS.File): + stripped.append(l) + continue + + if not SCons.Util.is_String(l): + l = str(l) + + for stripprefix in stripprefixes: + lsp = len(stripprefix) + if l[:lsp] == stripprefix: + l = l[lsp:] + # Do not strip more than one prefix + break + + for stripsuffix in stripsuffixes: + lss = len(stripsuffix) + if l[-lss:] == stripsuffix: + l = l[:-lss] + # Do not strip more than one suffix + break + + stripped.append(l) + + return c(prefix, stripped, suffix, env) + +def processDefines(defs): + """process defines, resolving strings, lists, dictionaries, into a list of + strings + """ + if SCons.Util.is_List(defs): + l = [] + for d in defs: + if SCons.Util.is_List(d) or type(d) is types.TupleType: + l.append(str(d[0]) + '=' + str(d[1])) + else: + l.append(str(d)) + elif SCons.Util.is_Dict(defs): + # The items in a dictionary are stored in random order, but + # if the order of the command-line options changes from + # invocation to invocation, then the signature of the command + # line will change and we'll get random unnecessary rebuilds. + # Consequently, we have to sort the keys to ensure a + # consistent order... + l = [] + keys = defs.keys() + keys.sort() + for k in keys: + v = defs[k] + if v is None: + l.append(str(k)) + else: + l.append(str(k) + '=' + str(v)) + else: + l = [str(defs)] + return l + +def _defines(prefix, defs, suffix, env, c=_concat_ixes): + """A wrapper around _concat_ixes that turns a list or string + into a list of C preprocessor command-line definitions. + """ + + return c(prefix, env.subst_path(processDefines(defs)), suffix, env) + +class NullCmdGenerator: + """This is a callable class that can be used in place of other + command generators if you don't want them to do anything. + + The __call__ method for this class simply returns the thing + you instantiated it with. + + Example usage: + env["DO_NOTHING"] = NullCmdGenerator + env["LINKCOM"] = "${DO_NOTHING('$LINK $SOURCES $TARGET')}" + """ + + def __init__(self, cmd): + self.cmd = cmd + + def __call__(self, target, source, env, for_signature=None): + return self.cmd + +class Variable_Method_Caller: + """A class for finding a construction variable on the stack and + calling one of its methods. + + We use this to support "construction variables" in our string + eval()s that actually stand in for methods--specifically, use + of "RDirs" in call to _concat that should actually execute the + "TARGET.RDirs" method. (We used to support this by creating a little + "build dictionary" that mapped RDirs to the method, but this got in + the way of Memoizing construction environments, because we had to + create new environment objects to hold the variables.) + """ + def __init__(self, variable, method): + self.variable = variable + self.method = method + def __call__(self, *args, **kw): + try: 1/0 + except ZeroDivisionError: + # Don't start iterating with the current stack-frame to + # prevent creating reference cycles (f_back is safe). + frame = sys.exc_info()[2].tb_frame.f_back + variable = self.variable + while frame: + if frame.f_locals.has_key(variable): + v = frame.f_locals[variable] + if v: + method = getattr(v, self.method) + return apply(method, args, kw) + frame = frame.f_back + return None + +ConstructionEnvironment = { + 'BUILDERS' : {}, + 'SCANNERS' : [], + 'CONFIGUREDIR' : '#/.sconf_temp', + 'CONFIGURELOG' : '#/config.log', + 'CPPSUFFIXES' : SCons.Tool.CSuffixes, + 'DSUFFIXES' : SCons.Tool.DSuffixes, + 'ENV' : {}, + 'IDLSUFFIXES' : SCons.Tool.IDLSuffixes, +# 'LATEXSUFFIXES' : SCons.Tool.LaTeXSuffixes, # moved to the TeX tools generate functions + '_concat' : _concat, + '_defines' : _defines, + '_stripixes' : _stripixes, + '_LIBFLAGS' : '${_concat(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, __env__)}', + '_LIBDIRFLAGS' : '$( ${_concat(LIBDIRPREFIX, LIBPATH, LIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', + '_CPPINCFLAGS' : '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)', + '_CPPDEFFLAGS' : '${_defines(CPPDEFPREFIX, CPPDEFINES, CPPDEFSUFFIX, __env__)}', + 'TEMPFILE' : NullCmdGenerator, + 'Dir' : Variable_Method_Caller('TARGET', 'Dir'), + 'Dirs' : Variable_Method_Caller('TARGET', 'Dirs'), + 'File' : Variable_Method_Caller('TARGET', 'File'), + 'RDirs' : Variable_Method_Caller('TARGET', 'RDirs'), +} + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Defaults.xml b/src/engine/SCons/Defaults.xml new file mode 100644 index 0000000..8b617ae --- /dev/null +++ b/src/engine/SCons/Defaults.xml @@ -0,0 +1,472 @@ + + + +A function used to produce variables like &cv-_CPPINCFLAGS;. It takes +four or five +arguments: a prefix to concatenate onto each element, a list of +elements, a suffix to concatenate onto each element, an environment +for variable interpolation, and an optional function that will be +called to transform the list before concatenation. + + +env['_CPPINCFLAGS'] = '$( ${_concat(INCPREFIX, CPPPATH, INCSUFFIX, __env__, RDirs)} $)', + + + + + + +The name of the directory in which +Configure context test files are written. +The default is +.sconf_temp +in the top-level directory +containing the +SConstruct +file. + + + + + +The name of the Configure context log file. +The default is +config.log +in the top-level directory +containing the +SConstruct +file. + + + + + +An automatically-generated construction variable +containing the C preprocessor command-line options +to define values. +The value of &cv-_CPPDEFFLAGS; is created +by appending &cv-CPPDEFPREFIX; and &cv-CPPDEFSUFFIX; +to the beginning and end +of each definition in &cv-CPPDEFINES;. + + + + + +A platform independent specification of C preprocessor definitions. +The definitions will be added to command lines +through the automatically-generated +&cv-_CPPDEFFLAGS; construction variable (see above), +which is constructed according to +the type of value of &cv-CPPDEFINES;: + +If &cv-CPPDEFINES; is a string, +the values of the +&cv-CPPDEFPREFIX; and &cv-CPPDEFSUFFIX; +construction variables +will be added to the beginning and end. + + +# Will add -Dxyz to POSIX compiler command lines, +# and /Dxyz to Microsoft Visual C++ command lines. +env = Environment(CPPDEFINES='xyz') + + +If &cv-CPPDEFINES; is a list, +the values of the +&cv-CPPDEFPREFIX; and &cv-CPPDEFSUFFIX; +construction variables +will be appended to the beginning and end +of each element in the list. +If any element is a list or tuple, +then the first item is the name being +defined and the second item is its value: + + +# Will add -DB=2 -DA to POSIX compiler command lines, +# and /DB=2 /DA to Microsoft Visual C++ command lines. +env = Environment(CPPDEFINES=[('B', 2), 'A']) + + +If &cv-CPPDEFINES; is a dictionary, +the values of the +&cv-CPPDEFPREFIX; and &cv-CPPDEFSUFFIX; +construction variables +will be appended to the beginning and end +of each item from the dictionary. +The key of each dictionary item +is a name being defined +to the dictionary item's corresponding value; +if the value is +None, +then the name is defined without an explicit value. +Note that the resulting flags are sorted by keyword +to ensure that the order of the options on the +command line is consistent each time +&scons; +is run. + + +# Will add -DA -DB=2 to POSIX compiler command lines, +# and /DA /DB=2 to Microsoft Visual C++ command lines. +env = Environment(CPPDEFINES={'B':2, 'A':None}) + + + + + + +The prefix used to specify preprocessor definitions +on the C compiler command line. +This will be appended to the beginning of each definition +in the &cv-CPPDEFINES; construction variable +when the &cv-_CPPDEFFLAGS; variable is automatically generated. + + + + + +The suffix used to specify preprocessor definitions +on the C compiler command line. +This will be appended to the end of each definition +in the &cv-CPPDEFINES; construction variable +when the &cv-_CPPDEFFLAGS; variable is automatically generated. + + + + + +An automatically-generated construction variable +containing the C preprocessor command-line options +for specifying directories to be searched for include files. +The value of &cv-_CPPINCFLAGS; is created +by appending &cv-INCPREFIX; and &cv-INCSUFFIX; +to the beginning and end +of each directory in &cv-CPPPATH;. + + + + + +The list of directories that the C preprocessor will search for include +directories. The C/C++ implicit dependency scanner will search these +directories for include files. Don't explicitly put include directory +arguments in CCFLAGS or CXXFLAGS because the result will be non-portable +and the directories will not be searched by the dependency scanner. Note: +directory names in CPPPATH will be looked-up relative to the SConscript +directory when they are used in a command. To force +&scons; +to look-up a directory relative to the root of the source tree use #: + + +env = Environment(CPPPATH='#/include') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +include = Dir('include') +env = Environment(CPPPATH=include) + + +The directory list will be added to command lines +through the automatically-generated +&cv-_CPPINCFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-INCPREFIX; and &cv-INCSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-CPPPATH;. +Any command lines you define that need +the CPPPATH directory list should +include &cv-_CPPINCFLAGS;: + + +env = Environment(CCCOM="my_compiler $_CPPINCFLAGS -c -o $TARGET $SOURCE") + + + + + + +A function that converts a string +into a Dir instance relative to the target being built. + + + + + +A function that converts a list of strings +into a list of Dir instances relative to the target being built. + + + + + +The list of suffixes of files that will be scanned +for imported D package files. +The default list is: + + +['.d'] + + + + + + +A function that converts a string into a File instance relative to the +target being built. + + + + + +The list of suffixes of files that will be scanned +for IDL implicit dependencies +(#include or import lines). +The default list is: + + +[".idl", ".IDL"] + + + + + + +The prefix used to specify an include directory on the C compiler command +line. +This will be appended to the beginning of each directory +in the &cv-CPPPATH; and &cv-FORTRANPATH; construction variables +when the &cv-_CPPINCFLAGS; and &cv-_FORTRANINCFLAGS; +variables are automatically generated. + + + + + +The suffix used to specify an include directory on the C compiler command +line. +This will be appended to the end of each directory +in the &cv-CPPPATH; and &cv-FORTRANPATH; construction variables +when the &cv-_CPPINCFLAGS; and &cv-_FORTRANINCFLAGS; +variables are automatically generated. + + + + + +A function to be called to install a file into a +destination file name. +The default function copies the file into the destination +(and sets the destination file's mode and permission bits +to match the source file's). +The function takes the following arguments: + + +def install(dest, source, env): + + +dest +is the path name of the destination file. +source +is the path name of the source file. +env +is the construction environment +(a dictionary of construction values) +in force for this file installation. + + + + + +The string displayed when a file is +installed into a destination file name. +The default is: + +Install file: "$SOURCE" as "$TARGET" + + + + + + +The list of suffixes of files that will be scanned +for LaTeX implicit dependencies +(\include or \import files). +The default list is: + + +[".tex", ".ltx", ".latex"] + + + + + + +An automatically-generated construction variable +containing the linker command-line options +for specifying directories to be searched for library. +The value of &cv-_LIBDIRFLAGS; is created +by appending &cv-LIBDIRPREFIX; and &cv-LIBDIRSUFFIX; +to the beginning and end +of each directory in &cv-LIBPATH;. + + + + + +The prefix used to specify a library directory on the linker command line. +This will be appended to the beginning of each directory +in the &cv-LIBPATH; construction variable +when the &cv-_LIBDIRFLAGS; variable is automatically generated. + + + + + +The suffix used to specify a library directory on the linker command line. +This will be appended to the end of each directory +in the &cv-LIBPATH; construction variable +when the &cv-_LIBDIRFLAGS; variable is automatically generated. + + + + + +An automatically-generated construction variable +containing the linker command-line options +for specifying libraries to be linked with the resulting target. +The value of &cv-_LIBFLAGS; is created +by appending &cv-LIBLINKPREFIX; and &cv-LIBLINKSUFFIX; +to the beginning and end +of each filename in &cv-LIBS;. + + + + + +The prefix used to specify a library to link on the linker command line. +This will be appended to the beginning of each library +in the &cv-LIBS; construction variable +when the &cv-_LIBFLAGS; variable is automatically generated. + + + + + +The suffix used to specify a library to link on the linker command line. +This will be appended to the end of each library +in the &cv-LIBS; construction variable +when the &cv-_LIBFLAGS; variable is automatically generated. + + + + + +The list of directories that will be searched for libraries. +The implicit dependency scanner will search these +directories for include files. Don't explicitly put include directory +arguments in &cv-LINKFLAGS; or &cv-SHLINKFLAGS; +because the result will be non-portable +and the directories will not be searched by the dependency scanner. Note: +directory names in LIBPATH will be looked-up relative to the SConscript +directory when they are used in a command. To force +&scons; +to look-up a directory relative to the root of the source tree use #: + + +env = Environment(LIBPATH='#/libs') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +libs = Dir('libs') +env = Environment(LIBPATH=libs) + + +The directory list will be added to command lines +through the automatically-generated +&cv-_LIBDIRFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-LIBDIRPREFIX; and &cv-LIBDIRSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-LIBPATH;. +Any command lines you define that need +the LIBPATH directory list should +include &cv-_LIBDIRFLAGS;: + + +env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE") + + + + + + +A list of one or more libraries +that will be linked with +any executable programs +created by this environment. + +The library list will be added to command lines +through the automatically-generated +&cv-_LIBFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-LIBLINKPREFIX; and &cv-LIBLINKSUFFIX; +construction variables +to the beginning and end +of each filename in &cv-LIBS;. +Any command lines you define that need +the LIBS library list should +include &cv-_LIBFLAGS;: + + +env = Environment(LINKCOM="my_linker $_LIBDIRFLAGS $_LIBFLAGS -o $TARGET $SOURCE") + + +If you add a +File +object to the +&cv-LIBS; +list, the name of that file will be added to +&cv-_LIBFLAGS;, +and thus the link line, as is, without +&cv-LIBLINKPREFIX; +or +&cv-LIBLINKSUFFIX;. +For example: + + +env.Append(LIBS=File('/tmp/mylib.so')) + + +In all cases, scons will add dependencies from the executable program to +all the libraries in this list. + + + + + +A function that converts a string into a list of Dir instances by +searching the repositories. + + diff --git a/src/engine/SCons/DefaultsTests.py b/src/engine/SCons/DefaultsTests.py new file mode 100644 index 0000000..25d828b --- /dev/null +++ b/src/engine/SCons/DefaultsTests.py @@ -0,0 +1,94 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/DefaultsTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string +import StringIO +import sys +import types +import unittest + +from UserDict import UserDict + +import TestCmd + +import SCons.Errors + +from SCons.Defaults import * + +class DefaultsTestCase(unittest.TestCase): + def test_mkdir_func0(self): + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub') + subdir2 = test.workpath('sub', 'dir1', 'dir2') + # Simple smoke test + mkdir_func(subdir2) + mkdir_func(subdir2) # 2nd time should be OK too + + def test_mkdir_func1(self): + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub') + subdir1 = test.workpath('sub', 'dir1') + subdir2 = test.workpath('sub', 'dir1', 'dir2') + # No error if asked to create existing dir + os.makedirs(subdir2) + mkdir_func(subdir2) + mkdir_func(subdir1) + + def test_mkdir_func2(self): + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub') + subdir1 = test.workpath('sub', 'dir1') + subdir2 = test.workpath('sub', 'dir1', 'dir2') + file = test.workpath('sub', 'dir1', 'dir2', 'file') + + # make sure it does error if asked to create a dir + # where there's already a file + os.makedirs(subdir2) + test.write(file, "test\n") + try: + mkdir_func(file) + except os.error, e: + pass + else: + fail("expected os.error") + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ DefaultsTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Environment.py b/src/engine/SCons/Environment.py new file mode 100644 index 0000000..1ce8b4a --- /dev/null +++ b/src/engine/SCons/Environment.py @@ -0,0 +1,2327 @@ +"""SCons.Environment + +Base class for construction Environments. These are +the primary objects used to communicate dependency and +construction information to the build engine. + +Keyword arguments supplied when the construction Environment +is created are construction variables used to initialize the +Environment +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Environment.py 4577 2009/12/27 19:44:43 scons" + + +import copy +import os +import sys +import re +import shlex +import string +from UserDict import UserDict + +import SCons.Action +import SCons.Builder +from SCons.Debug import logInstanceCreation +import SCons.Defaults +import SCons.Errors +import SCons.Memoize +import SCons.Node +import SCons.Node.Alias +import SCons.Node.FS +import SCons.Node.Python +import SCons.Platform +import SCons.SConf +import SCons.SConsign +import SCons.Subst +import SCons.Tool +import SCons.Util +import SCons.Warnings + +class _Null: + pass + +_null = _Null + +_warn_copy_deprecated = True +_warn_source_signatures_deprecated = True +_warn_target_signatures_deprecated = True + +CleanTargets = {} +CalculatorArgs = {} + +semi_deepcopy = SCons.Util.semi_deepcopy + +# Pull UserError into the global name space for the benefit of +# Environment().SourceSignatures(), which has some import statements +# which seem to mess up its ability to reference SCons directly. +UserError = SCons.Errors.UserError + +def alias_builder(env, target, source): + pass + +AliasBuilder = SCons.Builder.Builder(action = alias_builder, + target_factory = SCons.Node.Alias.default_ans.Alias, + source_factory = SCons.Node.FS.Entry, + multi = 1, + is_explicit = None, + name='AliasBuilder') + +def apply_tools(env, tools, toolpath): + # Store the toolpath in the Environment. + if toolpath is not None: + env['toolpath'] = toolpath + + if not tools: + return + # Filter out null tools from the list. + for tool in filter(None, tools): + if SCons.Util.is_List(tool) or type(tool)==type(()): + toolname = tool[0] + toolargs = tool[1] # should be a dict of kw args + tool = apply(env.Tool, [toolname], toolargs) + else: + env.Tool(tool) + +# These names are (or will be) controlled by SCons; users should never +# set or override them. This warning can optionally be turned off, +# but scons will still ignore the illegal variable names even if it's off. +reserved_construction_var_names = [ + 'CHANGED_SOURCES', + 'CHANGED_TARGETS', + 'SOURCE', + 'SOURCES', + 'TARGET', + 'TARGETS', + 'UNCHANGED_SOURCES', + 'UNCHANGED_TARGETS', +] + +future_reserved_construction_var_names = [ + #'HOST_OS', + #'HOST_ARCH', + #'HOST_CPU', + ] + +def copy_non_reserved_keywords(dict): + result = semi_deepcopy(dict) + for k in result.keys(): + if k in reserved_construction_var_names: + msg = "Ignoring attempt to set reserved variable `$%s'" + SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % k) + del result[k] + return result + +def _set_reserved(env, key, value): + msg = "Ignoring attempt to set reserved variable `$%s'" + SCons.Warnings.warn(SCons.Warnings.ReservedVariableWarning, msg % key) + +def _set_future_reserved(env, key, value): + env._dict[key] = value + msg = "`$%s' will be reserved in a future release and setting it will become ignored" + SCons.Warnings.warn(SCons.Warnings.FutureReservedVariableWarning, msg % key) + +def _set_BUILDERS(env, key, value): + try: + bd = env._dict[key] + for k in bd.keys(): + del bd[k] + except KeyError: + bd = BuilderDict(kwbd, env) + env._dict[key] = bd + bd.update(value) + +def _del_SCANNERS(env, key): + del env._dict[key] + env.scanner_map_delete() + +def _set_SCANNERS(env, key, value): + env._dict[key] = value + env.scanner_map_delete() + +def _delete_duplicates(l, keep_last): + """Delete duplicates from a sequence, keeping the first or last.""" + seen={} + result=[] + if keep_last: # reverse in & out, then keep first + l.reverse() + for i in l: + try: + if not seen.has_key(i): + result.append(i) + seen[i]=1 + except TypeError: + # probably unhashable. Just keep it. + result.append(i) + if keep_last: + result.reverse() + return result + + + +# The following is partly based on code in a comment added by Peter +# Shannon at the following page (there called the "transplant" class): +# +# ASPN : Python Cookbook : Dynamically added methods to a class +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732 +# +# We had independently been using the idiom as BuilderWrapper, but +# factoring out the common parts into this base class, and making +# BuilderWrapper a subclass that overrides __call__() to enforce specific +# Builder calling conventions, simplified some of our higher-layer code. + +class MethodWrapper: + """ + A generic Wrapper class that associates a method (which can + actually be any callable) with an object. As part of creating this + MethodWrapper object an attribute with the specified (by default, + the name of the supplied method) is added to the underlying object. + When that new "method" is called, our __call__() method adds the + object as the first argument, simulating the Python behavior of + supplying "self" on method calls. + + We hang on to the name by which the method was added to the underlying + base class so that we can provide a method to "clone" ourselves onto + a new underlying object being copied (without which we wouldn't need + to save that info). + """ + def __init__(self, object, method, name=None): + if name is None: + name = method.__name__ + self.object = object + self.method = method + self.name = name + setattr(self.object, name, self) + + def __call__(self, *args, **kwargs): + nargs = (self.object,) + args + return apply(self.method, nargs, kwargs) + + def clone(self, new_object): + """ + Returns an object that re-binds the underlying "method" to + the specified new object. + """ + return self.__class__(new_object, self.method, self.name) + +class BuilderWrapper(MethodWrapper): + """ + A MethodWrapper subclass that that associates an environment with + a Builder. + + This mainly exists to wrap the __call__() function so that all calls + to Builders can have their argument lists massaged in the same way + (treat a lone argument as the source, treat two arguments as target + then source, make sure both target and source are lists) without + having to have cut-and-paste code to do it. + + As a bit of obsessive backwards compatibility, we also intercept + attempts to get or set the "env" or "builder" attributes, which were + the names we used before we put the common functionality into the + MethodWrapper base class. We'll keep this around for a while in case + people shipped Tool modules that reached into the wrapper (like the + Tool/qt.py module does, or did). There shouldn't be a lot attribute + fetching or setting on these, so a little extra work shouldn't hurt. + """ + def __call__(self, target=None, source=_null, *args, **kw): + if source is _null: + source = target + target = None + if target is not None and not SCons.Util.is_List(target): + target = [target] + if source is not None and not SCons.Util.is_List(source): + source = [source] + return apply(MethodWrapper.__call__, (self, target, source) + args, kw) + + def __repr__(self): + return '' % repr(self.name) + + def __str__(self): + return self.__repr__() + + def __getattr__(self, name): + if name == 'env': + return self.object + elif name == 'builder': + return self.method + else: + raise AttributeError, name + + def __setattr__(self, name, value): + if name == 'env': + self.object = value + elif name == 'builder': + self.method = value + else: + self.__dict__[name] = value + + # This allows a Builder to be executed directly + # through the Environment to which it's attached. + # In practice, we shouldn't need this, because + # builders actually get executed through a Node. + # But we do have a unit test for this, and can't + # yet rule out that it would be useful in the + # future, so leave it for now. + #def execute(self, **kw): + # kw['env'] = self.env + # apply(self.builder.execute, (), kw) + +class BuilderDict(UserDict): + """This is a dictionary-like class used by an Environment to hold + the Builders. We need to do this because every time someone changes + the Builders in the Environment's BUILDERS dictionary, we must + update the Environment's attributes.""" + def __init__(self, dict, env): + # Set self.env before calling the superclass initialization, + # because it will end up calling our other methods, which will + # need to point the values in this dictionary to self.env. + self.env = env + UserDict.__init__(self, dict) + + def __semi_deepcopy__(self): + return self.__class__(self.data, self.env) + + def __setitem__(self, item, val): + try: + method = getattr(self.env, item).method + except AttributeError: + pass + else: + self.env.RemoveMethod(method) + UserDict.__setitem__(self, item, val) + BuilderWrapper(self.env, val, item) + + def __delitem__(self, item): + UserDict.__delitem__(self, item) + delattr(self.env, item) + + def update(self, dict): + for i, v in dict.items(): + self.__setitem__(i, v) + + + +_is_valid_var = re.compile(r'[_a-zA-Z]\w*$') + +def is_valid_construction_var(varstr): + """Return if the specified string is a legitimate construction + variable. + """ + return _is_valid_var.match(varstr) + + + +class SubstitutionEnvironment: + """Base class for different flavors of construction environments. + + This class contains a minimal set of methods that handle contruction + variable expansion and conversion of strings to Nodes, which may or + may not be actually useful as a stand-alone class. Which methods + ended up in this class is pretty arbitrary right now. They're + basically the ones which we've empirically determined are common to + the different construction environment subclasses, and most of the + others that use or touch the underlying dictionary of construction + variables. + + Eventually, this class should contain all the methods that we + determine are necessary for a "minimal" interface to the build engine. + A full "native Python" SCons environment has gotten pretty heavyweight + with all of the methods and Tools and construction variables we've + jammed in there, so it would be nice to have a lighter weight + alternative for interfaces that don't need all of the bells and + whistles. (At some point, we'll also probably rename this class + "Base," since that more reflects what we want this class to become, + but because we've released comments that tell people to subclass + Environment.Base to create their own flavors of construction + environment, we'll save that for a future refactoring when this + class actually becomes useful.) + """ + + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + def __init__(self, **kw): + """Initialization of an underlying SubstitutionEnvironment class. + """ + if __debug__: logInstanceCreation(self, 'Environment.SubstitutionEnvironment') + self.fs = SCons.Node.FS.get_default_fs() + self.ans = SCons.Node.Alias.default_ans + self.lookup_list = SCons.Node.arg2nodes_lookups + self._dict = kw.copy() + self._init_special() + self.added_methods = [] + #self._memo = {} + + def _init_special(self): + """Initial the dispatch tables for special handling of + special construction variables.""" + self._special_del = {} + self._special_del['SCANNERS'] = _del_SCANNERS + + self._special_set = {} + for key in reserved_construction_var_names: + self._special_set[key] = _set_reserved + for key in future_reserved_construction_var_names: + self._special_set[key] = _set_future_reserved + self._special_set['BUILDERS'] = _set_BUILDERS + self._special_set['SCANNERS'] = _set_SCANNERS + + # Freeze the keys of self._special_set in a list for use by + # methods that need to check. (Empirically, list scanning has + # gotten better than dict.has_key() in Python 2.5.) + self._special_set_keys = self._special_set.keys() + + def __cmp__(self, other): + return cmp(self._dict, other._dict) + + def __delitem__(self, key): + special = self._special_del.get(key) + if special: + special(self, key) + else: + del self._dict[key] + + def __getitem__(self, key): + return self._dict[key] + + def __setitem__(self, key, value): + # This is heavily used. This implementation is the best we have + # according to the timings in bench/env.__setitem__.py. + # + # The "key in self._special_set_keys" test here seems to perform + # pretty well for the number of keys we have. A hard-coded + # list works a little better in Python 2.5, but that has the + # disadvantage of maybe getting out of sync if we ever add more + # variable names. Using self._special_set.has_key() works a + # little better in Python 2.4, but is worse then this test. + # So right now it seems like a good trade-off, but feel free to + # revisit this with bench/env.__setitem__.py as needed (and + # as newer versions of Python come out). + if key in self._special_set_keys: + self._special_set[key](self, key, value) + else: + # If we already have the entry, then it's obviously a valid + # key and we don't need to check. If we do check, using a + # global, pre-compiled regular expression directly is more + # efficient than calling another function or a method. + if not self._dict.has_key(key) \ + and not _is_valid_var.match(key): + raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key + self._dict[key] = value + + def get(self, key, default=None): + """Emulates the get() method of dictionaries.""" + return self._dict.get(key, default) + + def has_key(self, key): + return self._dict.has_key(key) + + def __contains__(self, key): + return self._dict.__contains__(key) + + def items(self): + return self._dict.items() + + def arg2nodes(self, args, node_factory=_null, lookup_list=_null, **kw): + if node_factory is _null: + node_factory = self.fs.File + if lookup_list is _null: + lookup_list = self.lookup_list + + if not args: + return [] + + args = SCons.Util.flatten(args) + + nodes = [] + for v in args: + if SCons.Util.is_String(v): + n = None + for l in lookup_list: + n = l(v) + if n is not None: + break + if n is not None: + if SCons.Util.is_String(n): + # n = self.subst(n, raw=1, **kw) + kw['raw'] = 1 + n = apply(self.subst, (n,), kw) + if node_factory: + n = node_factory(n) + if SCons.Util.is_List(n): + nodes.extend(n) + else: + nodes.append(n) + elif node_factory: + # v = node_factory(self.subst(v, raw=1, **kw)) + kw['raw'] = 1 + v = node_factory(apply(self.subst, (v,), kw)) + if SCons.Util.is_List(v): + nodes.extend(v) + else: + nodes.append(v) + else: + nodes.append(v) + + return nodes + + def gvars(self): + return self._dict + + def lvars(self): + return {} + + def subst(self, string, raw=0, target=None, source=None, conv=None, executor=None): + """Recursively interpolates construction variables from the + Environment into the specified string, returning the expanded + result. Construction variables are specified by a $ prefix + in the string and begin with an initial underscore or + alphabetic character followed by any number of underscores + or alphanumeric characters. The construction variable names + may be surrounded by curly braces to separate the name from + trailing characters. + """ + gvars = self.gvars() + lvars = self.lvars() + lvars['__env__'] = self + if executor: + lvars.update(executor.get_lvars()) + return SCons.Subst.scons_subst(string, self, raw, target, source, gvars, lvars, conv) + + def subst_kw(self, kw, raw=0, target=None, source=None): + nkw = {} + for k, v in kw.items(): + k = self.subst(k, raw, target, source) + if SCons.Util.is_String(v): + v = self.subst(v, raw, target, source) + nkw[k] = v + return nkw + + def subst_list(self, string, raw=0, target=None, source=None, conv=None, executor=None): + """Calls through to SCons.Subst.scons_subst_list(). See + the documentation for that function.""" + gvars = self.gvars() + lvars = self.lvars() + lvars['__env__'] = self + if executor: + lvars.update(executor.get_lvars()) + return SCons.Subst.scons_subst_list(string, self, raw, target, source, gvars, lvars, conv) + + def subst_path(self, path, target=None, source=None): + """Substitute a path list, turning EntryProxies into Nodes + and leaving Nodes (and other objects) as-is.""" + + if not SCons.Util.is_List(path): + path = [path] + + def s(obj): + """This is the "string conversion" routine that we have our + substitutions use to return Nodes, not strings. This relies + on the fact that an EntryProxy object has a get() method that + returns the underlying Node that it wraps, which is a bit of + architectural dependence that we might need to break or modify + in the future in response to additional requirements.""" + try: + get = obj.get + except AttributeError: + obj = SCons.Util.to_String_for_subst(obj) + else: + obj = get() + return obj + + r = [] + for p in path: + if SCons.Util.is_String(p): + p = self.subst(p, target=target, source=source, conv=s) + if SCons.Util.is_List(p): + if len(p) == 1: + p = p[0] + else: + # We have an object plus a string, or multiple + # objects that we need to smush together. No choice + # but to make them into a string. + p = string.join(map(SCons.Util.to_String_for_subst, p), '') + else: + p = s(p) + r.append(p) + return r + + subst_target_source = subst + + def backtick(self, command): + import subprocess + # common arguments + kw = { 'stdin' : 'devnull', + 'stdout' : subprocess.PIPE, + 'stderr' : subprocess.PIPE, + 'universal_newlines' : True, + } + # if the command is a list, assume it's been quoted + # othewise force a shell + if not SCons.Util.is_List(command): kw['shell'] = True + # run constructed command + #TODO(1.5) p = SCons.Action._subproc(self, command, **kw) + p = apply(SCons.Action._subproc, (self, command), kw) + out,err = p.communicate() + status = p.wait() + if err: + sys.stderr.write(err) + if status: + raise OSError("'%s' exited %d" % (command, status)) + return out + + def AddMethod(self, function, name=None): + """ + Adds the specified function as a method of this construction + environment with the specified name. If the name is omitted, + the default name is the name of the function itself. + """ + method = MethodWrapper(self, function, name) + self.added_methods.append(method) + + def RemoveMethod(self, function): + """ + Removes the specified function's MethodWrapper from the + added_methods list, so we don't re-bind it when making a clone. + """ + is_not_func = lambda dm, f=function: not dm.method is f + self.added_methods = filter(is_not_func, self.added_methods) + + def Override(self, overrides): + """ + Produce a modified environment whose variables are overriden by + the overrides dictionaries. "overrides" is a dictionary that + will override the variables of this environment. + + This function is much more efficient than Clone() or creating + a new Environment because it doesn't copy the construction + environment dictionary, it just wraps the underlying construction + environment, and doesn't even create a wrapper object if there + are no overrides. + """ + if not overrides: return self + o = copy_non_reserved_keywords(overrides) + if not o: return self + overrides = {} + merges = None + for key, value in o.items(): + if key == 'parse_flags': + merges = value + else: + overrides[key] = SCons.Subst.scons_subst_once(value, self, key) + env = OverrideEnvironment(self, overrides) + if merges: env.MergeFlags(merges) + return env + + def ParseFlags(self, *flags): + """ + Parse the set of flags and return a dict with the flags placed + in the appropriate entry. The flags are treated as a typical + set of command-line flags for a GNU-like toolchain and used to + populate the entries in the dict immediately below. If one of + the flag strings begins with a bang (exclamation mark), it is + assumed to be a command and the rest of the string is executed; + the result of that evaluation is then added to the dict. + """ + dict = { + 'ASFLAGS' : SCons.Util.CLVar(''), + 'CFLAGS' : SCons.Util.CLVar(''), + 'CCFLAGS' : SCons.Util.CLVar(''), + 'CPPDEFINES' : [], + 'CPPFLAGS' : SCons.Util.CLVar(''), + 'CPPPATH' : [], + 'FRAMEWORKPATH' : SCons.Util.CLVar(''), + 'FRAMEWORKS' : SCons.Util.CLVar(''), + 'LIBPATH' : [], + 'LIBS' : [], + 'LINKFLAGS' : SCons.Util.CLVar(''), + 'RPATH' : [], + } + + # The use of the "me" parameter to provide our own name for + # recursion is an egregious hack to support Python 2.1 and before. + def do_parse(arg, me, self = self, dict = dict): + # if arg is a sequence, recurse with each element + if not arg: + return + + if not SCons.Util.is_String(arg): + for t in arg: me(t, me) + return + + # if arg is a command, execute it + if arg[0] == '!': + arg = self.backtick(arg[1:]) + + # utility function to deal with -D option + def append_define(name, dict = dict): + t = string.split(name, '=') + if len(t) == 1: + dict['CPPDEFINES'].append(name) + else: + dict['CPPDEFINES'].append([t[0], string.join(t[1:], '=')]) + + # Loop through the flags and add them to the appropriate option. + # This tries to strike a balance between checking for all possible + # flags and keeping the logic to a finite size, so it doesn't + # check for some that don't occur often. It particular, if the + # flag is not known to occur in a config script and there's a way + # of passing the flag to the right place (by wrapping it in a -W + # flag, for example) we don't check for it. Note that most + # preprocessor options are not handled, since unhandled options + # are placed in CCFLAGS, so unless the preprocessor is invoked + # separately, these flags will still get to the preprocessor. + # Other options not currently handled: + # -iqoutedir (preprocessor search path) + # -u symbol (linker undefined symbol) + # -s (linker strip files) + # -static* (linker static binding) + # -shared* (linker dynamic binding) + # -symbolic (linker global binding) + # -R dir (deprecated linker rpath) + # IBM compilers may also accept -qframeworkdir=foo + + params = shlex.split(arg) + append_next_arg_to = None # for multi-word args + for arg in params: + if append_next_arg_to: + if append_next_arg_to == 'CPPDEFINES': + append_define(arg) + elif append_next_arg_to == '-include': + t = ('-include', self.fs.File(arg)) + dict['CCFLAGS'].append(t) + elif append_next_arg_to == '-isysroot': + t = ('-isysroot', arg) + dict['CCFLAGS'].append(t) + dict['LINKFLAGS'].append(t) + elif append_next_arg_to == '-arch': + t = ('-arch', arg) + dict['CCFLAGS'].append(t) + dict['LINKFLAGS'].append(t) + else: + dict[append_next_arg_to].append(arg) + append_next_arg_to = None + elif not arg[0] in ['-', '+']: + dict['LIBS'].append(self.fs.File(arg)) + elif arg[:2] == '-L': + if arg[2:]: + dict['LIBPATH'].append(arg[2:]) + else: + append_next_arg_to = 'LIBPATH' + elif arg[:2] == '-l': + if arg[2:]: + dict['LIBS'].append(arg[2:]) + else: + append_next_arg_to = 'LIBS' + elif arg[:2] == '-I': + if arg[2:]: + dict['CPPPATH'].append(arg[2:]) + else: + append_next_arg_to = 'CPPPATH' + elif arg[:4] == '-Wa,': + dict['ASFLAGS'].append(arg[4:]) + dict['CCFLAGS'].append(arg) + elif arg[:4] == '-Wl,': + if arg[:11] == '-Wl,-rpath=': + dict['RPATH'].append(arg[11:]) + elif arg[:7] == '-Wl,-R,': + dict['RPATH'].append(arg[7:]) + elif arg[:6] == '-Wl,-R': + dict['RPATH'].append(arg[6:]) + else: + dict['LINKFLAGS'].append(arg) + elif arg[:4] == '-Wp,': + dict['CPPFLAGS'].append(arg) + elif arg[:2] == '-D': + if arg[2:]: + append_define(arg[2:]) + else: + append_next_arg_to = 'CPPDEFINES' + elif arg == '-framework': + append_next_arg_to = 'FRAMEWORKS' + elif arg[:14] == '-frameworkdir=': + dict['FRAMEWORKPATH'].append(arg[14:]) + elif arg[:2] == '-F': + if arg[2:]: + dict['FRAMEWORKPATH'].append(arg[2:]) + else: + append_next_arg_to = 'FRAMEWORKPATH' + elif arg == '-mno-cygwin': + dict['CCFLAGS'].append(arg) + dict['LINKFLAGS'].append(arg) + elif arg == '-mwindows': + dict['LINKFLAGS'].append(arg) + elif arg == '-pthread': + dict['CCFLAGS'].append(arg) + dict['LINKFLAGS'].append(arg) + elif arg[:5] == '-std=': + dict['CFLAGS'].append(arg) # C only + elif arg[0] == '+': + dict['CCFLAGS'].append(arg) + dict['LINKFLAGS'].append(arg) + elif arg in ['-include', '-isysroot', '-arch']: + append_next_arg_to = arg + else: + dict['CCFLAGS'].append(arg) + + for arg in flags: + do_parse(arg, do_parse) + return dict + + def MergeFlags(self, args, unique=1, dict=None): + """ + Merge the dict in args into the construction variables of this + env, or the passed-in dict. If args is not a dict, it is + converted into a dict using ParseFlags. If unique is not set, + the flags are appended rather than merged. + """ + + if dict is None: + dict = self + if not SCons.Util.is_Dict(args): + args = self.ParseFlags(args) + if not unique: + apply(self.Append, (), args) + return self + for key, value in args.items(): + if not value: + continue + try: + orig = self[key] + except KeyError: + orig = value + else: + if not orig: + orig = value + elif value: + # Add orig and value. The logic here was lifted from + # part of env.Append() (see there for a lot of comments + # about the order in which things are tried) and is + # used mainly to handle coercion of strings to CLVar to + # "do the right thing" given (e.g.) an original CCFLAGS + # string variable like '-pipe -Wall'. + try: + orig = orig + value + except (KeyError, TypeError): + try: + add_to_orig = orig.append + except AttributeError: + value.insert(0, orig) + orig = value + else: + add_to_orig(value) + t = [] + if key[-4:] == 'PATH': + ### keep left-most occurence + for v in orig: + if v not in t: + t.append(v) + else: + ### keep right-most occurence + orig.reverse() + for v in orig: + if v not in t: + t.insert(0, v) + self[key] = t + return self + +# def MergeShellPaths(self, args, prepend=1): +# """ +# Merge the dict in args into the shell environment in env['ENV']. +# Shell path elements are appended or prepended according to prepend. + +# Uses Pre/AppendENVPath, so it always appends or prepends uniquely. + +# Example: env.MergeShellPaths({'LIBPATH': '/usr/local/lib'}) +# prepends /usr/local/lib to env['ENV']['LIBPATH']. +# """ + +# for pathname, pathval in args.items(): +# if not pathval: +# continue +# if prepend: +# apply(self.PrependENVPath, (pathname, pathval)) +# else: +# apply(self.AppendENVPath, (pathname, pathval)) + + +# Used by the FindSourceFiles() method, below. +# Stuck here for support of pre-2.2 Python versions. +def build_source(ss, result): + for s in ss: + if isinstance(s, SCons.Node.FS.Dir): + build_source(s.all_children(), result) + elif s.has_builder(): + build_source(s.sources, result) + elif isinstance(s.disambiguate(), SCons.Node.FS.File): + result.append(s) + +def default_decide_source(dependency, target, prev_ni): + f = SCons.Defaults.DefaultEnvironment().decide_source + return f(dependency, target, prev_ni) + +def default_decide_target(dependency, target, prev_ni): + f = SCons.Defaults.DefaultEnvironment().decide_target + return f(dependency, target, prev_ni) + +def default_copy_from_cache(src, dst): + f = SCons.Defaults.DefaultEnvironment().copy_from_cache + return f(src, dst) + +class Base(SubstitutionEnvironment): + """Base class for "real" construction Environments. These are the + primary objects used to communicate dependency and construction + information to the build engine. + + Keyword arguments supplied when the construction Environment + is created are construction variables used to initialize the + Environment. + """ + + memoizer_counters = [] + + ####################################################################### + # This is THE class for interacting with the SCons build engine, + # and it contains a lot of stuff, so we're going to try to keep this + # a little organized by grouping the methods. + ####################################################################### + + ####################################################################### + # Methods that make an Environment act like a dictionary. These have + # the expected standard names for Python mapping objects. Note that + # we don't actually make an Environment a subclass of UserDict for + # performance reasons. Note also that we only supply methods for + # dictionary functionality that we actually need and use. + ####################################################################### + + def __init__(self, + platform=None, + tools=None, + toolpath=None, + variables=None, + parse_flags = None, + **kw): + """ + Initialization of a basic SCons construction environment, + including setting up special construction variables like BUILDER, + PLATFORM, etc., and searching for and applying available Tools. + + Note that we do *not* call the underlying base class + (SubsitutionEnvironment) initialization, because we need to + initialize things in a very specific order that doesn't work + with the much simpler base class initialization. + """ + if __debug__: logInstanceCreation(self, 'Environment.Base') + self._memo = {} + self.fs = SCons.Node.FS.get_default_fs() + self.ans = SCons.Node.Alias.default_ans + self.lookup_list = SCons.Node.arg2nodes_lookups + self._dict = semi_deepcopy(SCons.Defaults.ConstructionEnvironment) + self._init_special() + self.added_methods = [] + + # We don't use AddMethod, or define these as methods in this + # class, because we *don't* want these functions to be bound + # methods. They need to operate independently so that the + # settings will work properly regardless of whether a given + # target ends up being built with a Base environment or an + # OverrideEnvironment or what have you. + self.decide_target = default_decide_target + self.decide_source = default_decide_source + + self.copy_from_cache = default_copy_from_cache + + self._dict['BUILDERS'] = BuilderDict(self._dict['BUILDERS'], self) + + if platform is None: + platform = self._dict.get('PLATFORM', None) + if platform is None: + platform = SCons.Platform.Platform() + if SCons.Util.is_String(platform): + platform = SCons.Platform.Platform(platform) + self._dict['PLATFORM'] = str(platform) + platform(self) + + self._dict['HOST_OS'] = self._dict.get('HOST_OS',None) + self._dict['HOST_ARCH'] = self._dict.get('HOST_ARCH',None) + + # Now set defaults for TARGET_{OS|ARCH} + self._dict['TARGET_OS'] = self._dict.get('HOST_OS',None) + self._dict['TARGET_ARCH'] = self._dict.get('HOST_ARCH',None) + + + # Apply the passed-in and customizable variables to the + # environment before calling the tools, because they may use + # some of them during initialization. + if kw.has_key('options'): + # Backwards compatibility: they may stll be using the + # old "options" keyword. + variables = kw['options'] + del kw['options'] + apply(self.Replace, (), kw) + keys = kw.keys() + if variables: + keys = keys + variables.keys() + variables.Update(self) + + save = {} + for k in keys: + try: + save[k] = self._dict[k] + except KeyError: + # No value may have been set if they tried to pass in a + # reserved variable name like TARGETS. + pass + + SCons.Tool.Initializers(self) + + if tools is None: + tools = self._dict.get('TOOLS', None) + if tools is None: + tools = ['default'] + apply_tools(self, tools, toolpath) + + # Now restore the passed-in and customized variables + # to the environment, since the values the user set explicitly + # should override any values set by the tools. + for key, val in save.items(): + self._dict[key] = val + + # Finally, apply any flags to be merged in + if parse_flags: self.MergeFlags(parse_flags) + + ####################################################################### + # Utility methods that are primarily for internal use by SCons. + # These begin with lower-case letters. + ####################################################################### + + def get_builder(self, name): + """Fetch the builder with the specified name from the environment. + """ + try: + return self._dict['BUILDERS'][name] + except KeyError: + return None + + def get_CacheDir(self): + try: + path = self._CacheDir_path + except AttributeError: + path = SCons.Defaults.DefaultEnvironment()._CacheDir_path + try: + if path == self._last_CacheDir_path: + return self._last_CacheDir + except AttributeError: + pass + cd = SCons.CacheDir.CacheDir(path) + self._last_CacheDir_path = path + self._last_CacheDir = cd + return cd + + def get_factory(self, factory, default='File'): + """Return a factory function for creating Nodes for this + construction environment. + """ + name = default + try: + is_node = issubclass(factory, SCons.Node.FS.Base) + except TypeError: + # The specified factory isn't a Node itself--it's + # most likely None, or possibly a callable. + pass + else: + if is_node: + # The specified factory is a Node (sub)class. Try to + # return the FS method that corresponds to the Node's + # name--that is, we return self.fs.Dir if they want a Dir, + # self.fs.File for a File, etc. + try: name = factory.__name__ + except AttributeError: pass + else: factory = None + if not factory: + # They passed us None, or we picked up a name from a specified + # class, so return the FS method. (Note that we *don't* + # use our own self.{Dir,File} methods because that would + # cause env.subst() to be called twice on the file name, + # interfering with files that have $$ in them.) + factory = getattr(self.fs, name) + return factory + + memoizer_counters.append(SCons.Memoize.CountValue('_gsm')) + + def _gsm(self): + try: + return self._memo['_gsm'] + except KeyError: + pass + + result = {} + + try: + scanners = self._dict['SCANNERS'] + except KeyError: + pass + else: + # Reverse the scanner list so that, if multiple scanners + # claim they can scan the same suffix, earlier scanners + # in the list will overwrite later scanners, so that + # the result looks like a "first match" to the user. + if not SCons.Util.is_List(scanners): + scanners = [scanners] + else: + scanners = scanners[:] # copy so reverse() doesn't mod original + scanners.reverse() + for scanner in scanners: + for k in scanner.get_skeys(self): + if k and self['PLATFORM'] == 'win32': + k = string.lower(k) + result[k] = scanner + + self._memo['_gsm'] = result + + return result + + def get_scanner(self, skey): + """Find the appropriate scanner given a key (usually a file suffix). + """ + if skey and self['PLATFORM'] == 'win32': + skey = string.lower(skey) + return self._gsm().get(skey) + + def scanner_map_delete(self, kw=None): + """Delete the cached scanner map (if we need to). + """ + try: + del self._memo['_gsm'] + except KeyError: + pass + + def _update(self, dict): + """Update an environment's values directly, bypassing the normal + checks that occur when users try to set items. + """ + self._dict.update(dict) + + def get_src_sig_type(self): + try: + return self.src_sig_type + except AttributeError: + t = SCons.Defaults.DefaultEnvironment().src_sig_type + self.src_sig_type = t + return t + + def get_tgt_sig_type(self): + try: + return self.tgt_sig_type + except AttributeError: + t = SCons.Defaults.DefaultEnvironment().tgt_sig_type + self.tgt_sig_type = t + return t + + ####################################################################### + # Public methods for manipulating an Environment. These begin with + # upper-case letters. The essential characteristic of methods in + # this section is that they do *not* have corresponding same-named + # global functions. For example, a stand-alone Append() function + # makes no sense, because Append() is all about appending values to + # an Environment's construction variables. + ####################################################################### + + def Append(self, **kw): + """Append values to existing construction variables + in an Environment. + """ + kw = copy_non_reserved_keywords(kw) + for key, val in kw.items(): + # It would be easier on the eyes to write this using + # "continue" statements whenever we finish processing an item, + # but Python 1.5.2 apparently doesn't let you use "continue" + # within try:-except: blocks, so we have to nest our code. + try: + orig = self._dict[key] + except KeyError: + # No existing variable in the environment, so just set + # it to the new value. + self._dict[key] = val + else: + try: + # Check if the original looks like a dictionary. + # If it is, we can't just try adding the value because + # dictionaries don't have __add__() methods, and + # things like UserList will incorrectly coerce the + # original dict to a list (which we don't want). + update_dict = orig.update + except AttributeError: + try: + # Most straightforward: just try to add them + # together. This will work in most cases, when the + # original and new values are of compatible types. + self._dict[key] = orig + val + except (KeyError, TypeError): + try: + # Check if the original is a list. + add_to_orig = orig.append + except AttributeError: + # The original isn't a list, but the new + # value is (by process of elimination), + # so insert the original in the new value + # (if there's one to insert) and replace + # the variable with it. + if orig: + val.insert(0, orig) + self._dict[key] = val + else: + # The original is a list, so append the new + # value to it (if there's a value to append). + if val: + add_to_orig(val) + else: + # The original looks like a dictionary, so update it + # based on what we think the value looks like. + if SCons.Util.is_List(val): + for v in val: + orig[v] = None + else: + try: + update_dict(val) + except (AttributeError, TypeError, ValueError): + if SCons.Util.is_Dict(val): + for k, v in val.items(): + orig[k] = v + else: + orig[val] = None + self.scanner_map_delete(kw) + + # allow Dirs and strings beginning with # for top-relative + # Note this uses the current env's fs (in self). + def _canonicalize(self, path): + if not SCons.Util.is_String(path): # typically a Dir + path = str(path) + if path and path[0] == '#': + path = str(self.fs.Dir(path)) + return path + + def AppendENVPath(self, name, newpath, envname = 'ENV', + sep = os.pathsep, delete_existing=1): + """Append path elements to the path 'name' in the 'ENV' + dictionary for this environment. Will only add any particular + path once, and will normpath and normcase all paths to help + assure this. This can also handle the case where the env + variable is a list instead of a string. + + If delete_existing is 0, a newpath which is already in the path + will not be moved to the end (it will be left where it is). + """ + + orig = '' + if self._dict.has_key(envname) and self._dict[envname].has_key(name): + orig = self._dict[envname][name] + + nv = SCons.Util.AppendPath(orig, newpath, sep, delete_existing, + canonicalize=self._canonicalize) + + if not self._dict.has_key(envname): + self._dict[envname] = {} + + self._dict[envname][name] = nv + + def AppendUnique(self, delete_existing=0, **kw): + """Append values to existing construction variables + in an Environment, if they're not already there. + If delete_existing is 1, removes existing values first, so + values move to end. + """ + kw = copy_non_reserved_keywords(kw) + for key, val in kw.items(): + if SCons.Util.is_List(val): + val = _delete_duplicates(val, delete_existing) + if not self._dict.has_key(key) or self._dict[key] in ('', None): + self._dict[key] = val + elif SCons.Util.is_Dict(self._dict[key]) and \ + SCons.Util.is_Dict(val): + self._dict[key].update(val) + elif SCons.Util.is_List(val): + dk = self._dict[key] + if not SCons.Util.is_List(dk): + dk = [dk] + if delete_existing: + dk = filter(lambda x, val=val: x not in val, dk) + else: + val = filter(lambda x, dk=dk: x not in dk, val) + self._dict[key] = dk + val + else: + dk = self._dict[key] + if SCons.Util.is_List(dk): + # By elimination, val is not a list. Since dk is a + # list, wrap val in a list first. + if delete_existing: + dk = filter(lambda x, val=val: x not in val, dk) + self._dict[key] = dk + [val] + else: + if not val in dk: + self._dict[key] = dk + [val] + else: + if delete_existing: + dk = filter(lambda x, val=val: x not in val, dk) + self._dict[key] = dk + val + self.scanner_map_delete(kw) + + def Clone(self, tools=[], toolpath=None, parse_flags = None, **kw): + """Return a copy of a construction Environment. The + copy is like a Python "deep copy"--that is, independent + copies are made recursively of each objects--except that + a reference is copied when an object is not deep-copyable + (like a function). There are no references to any mutable + objects in the original Environment. + """ + clone = copy.copy(self) + clone._dict = semi_deepcopy(self._dict) + + try: + cbd = clone._dict['BUILDERS'] + except KeyError: + pass + else: + clone._dict['BUILDERS'] = BuilderDict(cbd, clone) + + # Check the methods added via AddMethod() and re-bind them to + # the cloned environment. Only do this if the attribute hasn't + # been overwritten by the user explicitly and still points to + # the added method. + clone.added_methods = [] + for mw in self.added_methods: + if mw == getattr(self, mw.name): + clone.added_methods.append(mw.clone(clone)) + + clone._memo = {} + + # Apply passed-in variables before the tools + # so the tools can use the new variables + kw = copy_non_reserved_keywords(kw) + new = {} + for key, value in kw.items(): + new[key] = SCons.Subst.scons_subst_once(value, self, key) + apply(clone.Replace, (), new) + + apply_tools(clone, tools, toolpath) + + # apply them again in case the tools overwrote them + apply(clone.Replace, (), new) + + # Finally, apply any flags to be merged in + if parse_flags: clone.MergeFlags(parse_flags) + + if __debug__: logInstanceCreation(self, 'Environment.EnvironmentClone') + return clone + + def Copy(self, *args, **kw): + global _warn_copy_deprecated + if _warn_copy_deprecated: + msg = "The env.Copy() method is deprecated; use the env.Clone() method instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedCopyWarning, msg) + _warn_copy_deprecated = False + return apply(self.Clone, args, kw) + + def _changed_build(self, dependency, target, prev_ni): + if dependency.changed_state(target, prev_ni): + return 1 + return self.decide_source(dependency, target, prev_ni) + + def _changed_content(self, dependency, target, prev_ni): + return dependency.changed_content(target, prev_ni) + + def _changed_source(self, dependency, target, prev_ni): + target_env = dependency.get_build_env() + type = target_env.get_tgt_sig_type() + if type == 'source': + return target_env.decide_source(dependency, target, prev_ni) + else: + return target_env.decide_target(dependency, target, prev_ni) + + def _changed_timestamp_then_content(self, dependency, target, prev_ni): + return dependency.changed_timestamp_then_content(target, prev_ni) + + def _changed_timestamp_newer(self, dependency, target, prev_ni): + return dependency.changed_timestamp_newer(target, prev_ni) + + def _changed_timestamp_match(self, dependency, target, prev_ni): + return dependency.changed_timestamp_match(target, prev_ni) + + def _copy_from_cache(self, src, dst): + return self.fs.copy(src, dst) + + def _copy2_from_cache(self, src, dst): + return self.fs.copy2(src, dst) + + def Decider(self, function): + copy_function = self._copy2_from_cache + if function in ('MD5', 'content'): + if not SCons.Util.md5: + raise UserError, "MD5 signatures are not available in this version of Python." + function = self._changed_content + elif function == 'MD5-timestamp': + function = self._changed_timestamp_then_content + elif function in ('timestamp-newer', 'make'): + function = self._changed_timestamp_newer + copy_function = self._copy_from_cache + elif function == 'timestamp-match': + function = self._changed_timestamp_match + elif not callable(function): + raise UserError, "Unknown Decider value %s" % repr(function) + + # We don't use AddMethod because we don't want to turn the + # function, which only expects three arguments, into a bound + # method, which would add self as an initial, fourth argument. + self.decide_target = function + self.decide_source = function + + self.copy_from_cache = copy_function + + def Detect(self, progs): + """Return the first available program in progs. + """ + if not SCons.Util.is_List(progs): + progs = [ progs ] + for prog in progs: + path = self.WhereIs(prog) + if path: return prog + return None + + def Dictionary(self, *args): + if not args: + return self._dict + dlist = map(lambda x, s=self: s._dict[x], args) + if len(dlist) == 1: + dlist = dlist[0] + return dlist + + def Dump(self, key = None): + """ + Using the standard Python pretty printer, dump the contents of the + scons build environment to stdout. + + If the key passed in is anything other than None, then that will + be used as an index into the build environment dictionary and + whatever is found there will be fed into the pretty printer. Note + that this key is case sensitive. + """ + import pprint + pp = pprint.PrettyPrinter(indent=2) + if key: + dict = self.Dictionary(key) + else: + dict = self.Dictionary() + return pp.pformat(dict) + + def FindIxes(self, paths, prefix, suffix): + """ + Search a list of paths for something that matches the prefix and suffix. + + paths - the list of paths or nodes. + prefix - construction variable for the prefix. + suffix - construction variable for the suffix. + """ + + suffix = self.subst('$'+suffix) + prefix = self.subst('$'+prefix) + + for path in paths: + dir,name = os.path.split(str(path)) + if name[:len(prefix)] == prefix and name[-len(suffix):] == suffix: + return path + + def ParseConfig(self, command, function=None, unique=1): + """ + Use the specified function to parse the output of the command + in order to modify the current environment. The 'command' can + be a string or a list of strings representing a command and + its arguments. 'Function' is an optional argument that takes + the environment, the output of the command, and the unique flag. + If no function is specified, MergeFlags, which treats the output + as the result of a typical 'X-config' command (i.e. gtk-config), + will merge the output into the appropriate variables. + """ + if function is None: + def parse_conf(env, cmd, unique=unique): + return env.MergeFlags(cmd, unique) + function = parse_conf + if SCons.Util.is_List(command): + command = string.join(command) + command = self.subst(command) + return function(self, self.backtick(command)) + + def ParseDepends(self, filename, must_exist=None, only_one=0): + """ + Parse a mkdep-style file for explicit dependencies. This is + completely abusable, and should be unnecessary in the "normal" + case of proper SCons configuration, but it may help make + the transition from a Make hierarchy easier for some people + to swallow. It can also be genuinely useful when using a tool + that can write a .d file, but for which writing a scanner would + be too complicated. + """ + filename = self.subst(filename) + try: + fp = open(filename, 'r') + except IOError: + if must_exist: + raise + return + lines = SCons.Util.LogicalLines(fp).readlines() + lines = filter(lambda l: l[0] != '#', lines) + tdlist = [] + for line in lines: + try: + target, depends = string.split(line, ':', 1) + except (AttributeError, TypeError, ValueError): + # Python 1.5.2 throws TypeError if line isn't a string, + # Python 2.x throws AttributeError because it tries + # to call line.split(). Either can throw ValueError + # if the line doesn't split into two or more elements. + pass + else: + tdlist.append((string.split(target), string.split(depends))) + if only_one: + targets = reduce(lambda x, y: x+y, map(lambda p: p[0], tdlist)) + if len(targets) > 1: + raise SCons.Errors.UserError, "More than one dependency target found in `%s': %s" % (filename, targets) + for target, depends in tdlist: + self.Depends(target, depends) + + def Platform(self, platform): + platform = self.subst(platform) + return SCons.Platform.Platform(platform)(self) + + def Prepend(self, **kw): + """Prepend values to existing construction variables + in an Environment. + """ + kw = copy_non_reserved_keywords(kw) + for key, val in kw.items(): + # It would be easier on the eyes to write this using + # "continue" statements whenever we finish processing an item, + # but Python 1.5.2 apparently doesn't let you use "continue" + # within try:-except: blocks, so we have to nest our code. + try: + orig = self._dict[key] + except KeyError: + # No existing variable in the environment, so just set + # it to the new value. + self._dict[key] = val + else: + try: + # Check if the original looks like a dictionary. + # If it is, we can't just try adding the value because + # dictionaries don't have __add__() methods, and + # things like UserList will incorrectly coerce the + # original dict to a list (which we don't want). + update_dict = orig.update + except AttributeError: + try: + # Most straightforward: just try to add them + # together. This will work in most cases, when the + # original and new values are of compatible types. + self._dict[key] = val + orig + except (KeyError, TypeError): + try: + # Check if the added value is a list. + add_to_val = val.append + except AttributeError: + # The added value isn't a list, but the + # original is (by process of elimination), + # so insert the the new value in the original + # (if there's one to insert). + if val: + orig.insert(0, val) + else: + # The added value is a list, so append + # the original to it (if there's a value + # to append). + if orig: + add_to_val(orig) + self._dict[key] = val + else: + # The original looks like a dictionary, so update it + # based on what we think the value looks like. + if SCons.Util.is_List(val): + for v in val: + orig[v] = None + else: + try: + update_dict(val) + except (AttributeError, TypeError, ValueError): + if SCons.Util.is_Dict(val): + for k, v in val.items(): + orig[k] = v + else: + orig[val] = None + self.scanner_map_delete(kw) + + def PrependENVPath(self, name, newpath, envname = 'ENV', sep = os.pathsep, + delete_existing=1): + """Prepend path elements to the path 'name' in the 'ENV' + dictionary for this environment. Will only add any particular + path once, and will normpath and normcase all paths to help + assure this. This can also handle the case where the env + variable is a list instead of a string. + + If delete_existing is 0, a newpath which is already in the path + will not be moved to the front (it will be left where it is). + """ + + orig = '' + if self._dict.has_key(envname) and self._dict[envname].has_key(name): + orig = self._dict[envname][name] + + nv = SCons.Util.PrependPath(orig, newpath, sep, delete_existing, + canonicalize=self._canonicalize) + + if not self._dict.has_key(envname): + self._dict[envname] = {} + + self._dict[envname][name] = nv + + def PrependUnique(self, delete_existing=0, **kw): + """Prepend values to existing construction variables + in an Environment, if they're not already there. + If delete_existing is 1, removes existing values first, so + values move to front. + """ + kw = copy_non_reserved_keywords(kw) + for key, val in kw.items(): + if SCons.Util.is_List(val): + val = _delete_duplicates(val, not delete_existing) + if not self._dict.has_key(key) or self._dict[key] in ('', None): + self._dict[key] = val + elif SCons.Util.is_Dict(self._dict[key]) and \ + SCons.Util.is_Dict(val): + self._dict[key].update(val) + elif SCons.Util.is_List(val): + dk = self._dict[key] + if not SCons.Util.is_List(dk): + dk = [dk] + if delete_existing: + dk = filter(lambda x, val=val: x not in val, dk) + else: + val = filter(lambda x, dk=dk: x not in dk, val) + self._dict[key] = val + dk + else: + dk = self._dict[key] + if SCons.Util.is_List(dk): + # By elimination, val is not a list. Since dk is a + # list, wrap val in a list first. + if delete_existing: + dk = filter(lambda x, val=val: x not in val, dk) + self._dict[key] = [val] + dk + else: + if not val in dk: + self._dict[key] = [val] + dk + else: + if delete_existing: + dk = filter(lambda x, val=val: x not in val, dk) + self._dict[key] = val + dk + self.scanner_map_delete(kw) + + def Replace(self, **kw): + """Replace existing construction variables in an Environment + with new construction variables and/or values. + """ + try: + kwbd = kw['BUILDERS'] + except KeyError: + pass + else: + kwbd = semi_deepcopy(kwbd) + del kw['BUILDERS'] + self.__setitem__('BUILDERS', kwbd) + kw = copy_non_reserved_keywords(kw) + self._update(semi_deepcopy(kw)) + self.scanner_map_delete(kw) + + def ReplaceIxes(self, path, old_prefix, old_suffix, new_prefix, new_suffix): + """ + Replace old_prefix with new_prefix and old_suffix with new_suffix. + + env - Environment used to interpolate variables. + path - the path that will be modified. + old_prefix - construction variable for the old prefix. + old_suffix - construction variable for the old suffix. + new_prefix - construction variable for the new prefix. + new_suffix - construction variable for the new suffix. + """ + old_prefix = self.subst('$'+old_prefix) + old_suffix = self.subst('$'+old_suffix) + + new_prefix = self.subst('$'+new_prefix) + new_suffix = self.subst('$'+new_suffix) + + dir,name = os.path.split(str(path)) + if name[:len(old_prefix)] == old_prefix: + name = name[len(old_prefix):] + if name[-len(old_suffix):] == old_suffix: + name = name[:-len(old_suffix)] + return os.path.join(dir, new_prefix+name+new_suffix) + + def SetDefault(self, **kw): + for k in kw.keys(): + if self._dict.has_key(k): + del kw[k] + apply(self.Replace, (), kw) + + def _find_toolpath_dir(self, tp): + return self.fs.Dir(self.subst(tp)).srcnode().abspath + + def Tool(self, tool, toolpath=None, **kw): + if SCons.Util.is_String(tool): + tool = self.subst(tool) + if toolpath is None: + toolpath = self.get('toolpath', []) + toolpath = map(self._find_toolpath_dir, toolpath) + tool = apply(SCons.Tool.Tool, (tool, toolpath), kw) + tool(self) + + def WhereIs(self, prog, path=None, pathext=None, reject=[]): + """Find prog in the path. + """ + if path is None: + try: + path = self['ENV']['PATH'] + except KeyError: + pass + elif SCons.Util.is_String(path): + path = self.subst(path) + if pathext is None: + try: + pathext = self['ENV']['PATHEXT'] + except KeyError: + pass + elif SCons.Util.is_String(pathext): + pathext = self.subst(pathext) + prog = self.subst(prog) + path = SCons.Util.WhereIs(prog, path, pathext, reject) + if path: return path + return None + + ####################################################################### + # Public methods for doing real "SCons stuff" (manipulating + # dependencies, setting attributes on targets, etc.). These begin + # with upper-case letters. The essential characteristic of methods + # in this section is that they all *should* have corresponding + # same-named global functions. + ####################################################################### + + def Action(self, *args, **kw): + def subst_string(a, self=self): + if SCons.Util.is_String(a): + a = self.subst(a) + return a + nargs = map(subst_string, args) + nkw = self.subst_kw(kw) + return apply(SCons.Action.Action, nargs, nkw) + + def AddPreAction(self, files, action): + nodes = self.arg2nodes(files, self.fs.Entry) + action = SCons.Action.Action(action) + uniq = {} + for executor in map(lambda n: n.get_executor(), nodes): + uniq[executor] = 1 + for executor in uniq.keys(): + executor.add_pre_action(action) + return nodes + + def AddPostAction(self, files, action): + nodes = self.arg2nodes(files, self.fs.Entry) + action = SCons.Action.Action(action) + uniq = {} + for executor in map(lambda n: n.get_executor(), nodes): + uniq[executor] = 1 + for executor in uniq.keys(): + executor.add_post_action(action) + return nodes + + def Alias(self, target, source=[], action=None, **kw): + tlist = self.arg2nodes(target, self.ans.Alias) + if not SCons.Util.is_List(source): + source = [source] + source = filter(None, source) + + if not action: + if not source: + # There are no source files and no action, so just + # return a target list of classic Alias Nodes, without + # any builder. The externally visible effect is that + # this will make the wrapping Script.BuildTask class + # say that there's "Nothing to be done" for this Alias, + # instead of that it's "up to date." + return tlist + + # No action, but there are sources. Re-call all the target + # builders to add the sources to each target. + result = [] + for t in tlist: + bld = t.get_builder(AliasBuilder) + result.extend(bld(self, t, source)) + return result + + nkw = self.subst_kw(kw) + nkw.update({ + 'action' : SCons.Action.Action(action), + 'source_factory' : self.fs.Entry, + 'multi' : 1, + 'is_explicit' : None, + }) + bld = apply(SCons.Builder.Builder, (), nkw) + + # Apply the Builder separately to each target so that the Aliases + # stay separate. If we did one "normal" Builder call with the + # whole target list, then all of the target Aliases would be + # associated under a single Executor. + result = [] + for t in tlist: + # Calling the convert() method will cause a new Executor to be + # created from scratch, so we have to explicitly initialize + # it with the target's existing sources, plus our new ones, + # so nothing gets lost. + b = t.get_builder() + if b is None or b is AliasBuilder: + b = bld + else: + nkw['action'] = b.action + action + b = apply(SCons.Builder.Builder, (), nkw) + t.convert() + result.extend(b(self, t, t.sources + source)) + return result + + def AlwaysBuild(self, *targets): + tlist = [] + for t in targets: + tlist.extend(self.arg2nodes(t, self.fs.Entry)) + for t in tlist: + t.set_always_build() + return tlist + + def BuildDir(self, *args, **kw): + if kw.has_key('build_dir'): + kw['variant_dir'] = kw['build_dir'] + del kw['build_dir'] + return apply(self.VariantDir, args, kw) + + def Builder(self, **kw): + nkw = self.subst_kw(kw) + return apply(SCons.Builder.Builder, [], nkw) + + def CacheDir(self, path): + import SCons.CacheDir + if path is not None: + path = self.subst(path) + self._CacheDir_path = path + + def Clean(self, targets, files): + global CleanTargets + tlist = self.arg2nodes(targets, self.fs.Entry) + flist = self.arg2nodes(files, self.fs.Entry) + for t in tlist: + try: + CleanTargets[t].extend(flist) + except KeyError: + CleanTargets[t] = flist + + def Configure(self, *args, **kw): + nargs = [self] + if args: + nargs = nargs + self.subst_list(args)[0] + nkw = self.subst_kw(kw) + nkw['_depth'] = kw.get('_depth', 0) + 1 + try: + nkw['custom_tests'] = self.subst_kw(nkw['custom_tests']) + except KeyError: + pass + return apply(SCons.SConf.SConf, nargs, nkw) + + def Command(self, target, source, action, **kw): + """Builds the supplied target files from the supplied + source files using the supplied action. Action may + be any type that the Builder constructor will accept + for an action.""" + bkw = { + 'action' : action, + 'target_factory' : self.fs.Entry, + 'source_factory' : self.fs.Entry, + } + try: bkw['source_scanner'] = kw['source_scanner'] + except KeyError: pass + else: del kw['source_scanner'] + bld = apply(SCons.Builder.Builder, (), bkw) + return apply(bld, (self, target, source), kw) + + def Depends(self, target, dependency): + """Explicity specify that 'target's depend on 'dependency'.""" + tlist = self.arg2nodes(target, self.fs.Entry) + dlist = self.arg2nodes(dependency, self.fs.Entry) + for t in tlist: + t.add_dependency(dlist) + return tlist + + def Dir(self, name, *args, **kw): + """ + """ + s = self.subst(name) + if SCons.Util.is_Sequence(s): + result=[] + for e in s: + result.append(apply(self.fs.Dir, (e,) + args, kw)) + return result + return apply(self.fs.Dir, (s,) + args, kw) + + def NoClean(self, *targets): + """Tags a target so that it will not be cleaned by -c""" + tlist = [] + for t in targets: + tlist.extend(self.arg2nodes(t, self.fs.Entry)) + for t in tlist: + t.set_noclean() + return tlist + + def NoCache(self, *targets): + """Tags a target so that it will not be cached""" + tlist = [] + for t in targets: + tlist.extend(self.arg2nodes(t, self.fs.Entry)) + for t in tlist: + t.set_nocache() + return tlist + + def Entry(self, name, *args, **kw): + """ + """ + s = self.subst(name) + if SCons.Util.is_Sequence(s): + result=[] + for e in s: + result.append(apply(self.fs.Entry, (e,) + args, kw)) + return result + return apply(self.fs.Entry, (s,) + args, kw) + + def Environment(self, **kw): + return apply(SCons.Environment.Environment, [], self.subst_kw(kw)) + + def Execute(self, action, *args, **kw): + """Directly execute an action through an Environment + """ + action = apply(self.Action, (action,) + args, kw) + result = action([], [], self) + if isinstance(result, SCons.Errors.BuildError): + errstr = result.errstr + if result.filename: + errstr = result.filename + ': ' + errstr + sys.stderr.write("scons: *** %s\n" % errstr) + return result.status + else: + return result + + def File(self, name, *args, **kw): + """ + """ + s = self.subst(name) + if SCons.Util.is_Sequence(s): + result=[] + for e in s: + result.append(apply(self.fs.File, (e,) + args, kw)) + return result + return apply(self.fs.File, (s,) + args, kw) + + def FindFile(self, file, dirs): + file = self.subst(file) + nodes = self.arg2nodes(dirs, self.fs.Dir) + return SCons.Node.FS.find_file(file, tuple(nodes)) + + def Flatten(self, sequence): + return SCons.Util.flatten(sequence) + + def GetBuildPath(self, files): + result = map(str, self.arg2nodes(files, self.fs.Entry)) + if SCons.Util.is_List(files): + return result + else: + return result[0] + + def Glob(self, pattern, ondisk=True, source=False, strings=False): + return self.fs.Glob(self.subst(pattern), ondisk, source, strings) + + def Ignore(self, target, dependency): + """Ignore a dependency.""" + tlist = self.arg2nodes(target, self.fs.Entry) + dlist = self.arg2nodes(dependency, self.fs.Entry) + for t in tlist: + t.add_ignore(dlist) + return tlist + + def Literal(self, string): + return SCons.Subst.Literal(string) + + def Local(self, *targets): + ret = [] + for targ in targets: + if isinstance(targ, SCons.Node.Node): + targ.set_local() + ret.append(targ) + else: + for t in self.arg2nodes(targ, self.fs.Entry): + t.set_local() + ret.append(t) + return ret + + def Precious(self, *targets): + tlist = [] + for t in targets: + tlist.extend(self.arg2nodes(t, self.fs.Entry)) + for t in tlist: + t.set_precious() + return tlist + + def Repository(self, *dirs, **kw): + dirs = self.arg2nodes(list(dirs), self.fs.Dir) + apply(self.fs.Repository, dirs, kw) + + def Requires(self, target, prerequisite): + """Specify that 'prerequisite' must be built before 'target', + (but 'target' does not actually depend on 'prerequisite' + and need not be rebuilt if it changes).""" + tlist = self.arg2nodes(target, self.fs.Entry) + plist = self.arg2nodes(prerequisite, self.fs.Entry) + for t in tlist: + t.add_prerequisite(plist) + return tlist + + def Scanner(self, *args, **kw): + nargs = [] + for arg in args: + if SCons.Util.is_String(arg): + arg = self.subst(arg) + nargs.append(arg) + nkw = self.subst_kw(kw) + return apply(SCons.Scanner.Base, nargs, nkw) + + def SConsignFile(self, name=".sconsign", dbm_module=None): + if name is not None: + name = self.subst(name) + if not os.path.isabs(name): + name = os.path.join(str(self.fs.SConstruct_dir), name) + if name: + name = os.path.normpath(name) + sconsign_dir = os.path.dirname(name) + if sconsign_dir and not os.path.exists(sconsign_dir): + self.Execute(SCons.Defaults.Mkdir(sconsign_dir)) + SCons.SConsign.File(name, dbm_module) + + def SideEffect(self, side_effect, target): + """Tell scons that side_effects are built as side + effects of building targets.""" + side_effects = self.arg2nodes(side_effect, self.fs.Entry) + targets = self.arg2nodes(target, self.fs.Entry) + + for side_effect in side_effects: + if side_effect.multiple_side_effect_has_builder(): + raise SCons.Errors.UserError, "Multiple ways to build the same target were specified for: %s" % str(side_effect) + side_effect.add_source(targets) + side_effect.side_effect = 1 + self.Precious(side_effect) + for target in targets: + target.side_effects.append(side_effect) + return side_effects + + def SourceCode(self, entry, builder): + """Arrange for a source code builder for (part of) a tree.""" + entries = self.arg2nodes(entry, self.fs.Entry) + for entry in entries: + entry.set_src_builder(builder) + return entries + + def SourceSignatures(self, type): + global _warn_source_signatures_deprecated + if _warn_source_signatures_deprecated: + msg = "The env.SourceSignatures() method is deprecated;\n" + \ + "\tconvert your build to use the env.Decider() method instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedSourceSignaturesWarning, msg) + _warn_source_signatures_deprecated = False + type = self.subst(type) + self.src_sig_type = type + if type == 'MD5': + if not SCons.Util.md5: + raise UserError, "MD5 signatures are not available in this version of Python." + self.decide_source = self._changed_content + elif type == 'timestamp': + self.decide_source = self._changed_timestamp_match + else: + raise UserError, "Unknown source signature type '%s'" % type + + def Split(self, arg): + """This function converts a string or list into a list of strings + or Nodes. This makes things easier for users by allowing files to + be specified as a white-space separated list to be split. + The input rules are: + - A single string containing names separated by spaces. These will be + split apart at the spaces. + - A single Node instance + - A list containing either strings or Node instances. Any strings + in the list are not split at spaces. + In all cases, the function returns a list of Nodes and strings.""" + if SCons.Util.is_List(arg): + return map(self.subst, arg) + elif SCons.Util.is_String(arg): + return string.split(self.subst(arg)) + else: + return [self.subst(arg)] + + def TargetSignatures(self, type): + global _warn_target_signatures_deprecated + if _warn_target_signatures_deprecated: + msg = "The env.TargetSignatures() method is deprecated;\n" + \ + "\tconvert your build to use the env.Decider() method instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedTargetSignaturesWarning, msg) + _warn_target_signatures_deprecated = False + type = self.subst(type) + self.tgt_sig_type = type + if type in ('MD5', 'content'): + if not SCons.Util.md5: + raise UserError, "MD5 signatures are not available in this version of Python." + self.decide_target = self._changed_content + elif type == 'timestamp': + self.decide_target = self._changed_timestamp_match + elif type == 'build': + self.decide_target = self._changed_build + elif type == 'source': + self.decide_target = self._changed_source + else: + raise UserError, "Unknown target signature type '%s'"%type + + def Value(self, value, built_value=None): + """ + """ + return SCons.Node.Python.Value(value, built_value) + + def VariantDir(self, variant_dir, src_dir, duplicate=1): + variant_dir = self.arg2nodes(variant_dir, self.fs.Dir)[0] + src_dir = self.arg2nodes(src_dir, self.fs.Dir)[0] + self.fs.VariantDir(variant_dir, src_dir, duplicate) + + def FindSourceFiles(self, node='.'): + """ returns a list of all source files. + """ + node = self.arg2nodes(node, self.fs.Entry)[0] + + sources = [] + # Uncomment this and get rid of the global definition when we + # drop support for pre-2.2 Python versions. + #def build_source(ss, result): + # for s in ss: + # if isinstance(s, SCons.Node.FS.Dir): + # build_source(s.all_children(), result) + # elif s.has_builder(): + # build_source(s.sources, result) + # elif isinstance(s.disambiguate(), SCons.Node.FS.File): + # result.append(s) + build_source(node.all_children(), sources) + + # THIS CODE APPEARS TO HAVE NO EFFECT + # # get the final srcnode for all nodes, this means stripping any + # # attached build node by calling the srcnode function + # for file in sources: + # srcnode = file.srcnode() + # while srcnode != file.srcnode(): + # srcnode = file.srcnode() + + # remove duplicates + return list(set(sources)) + + def FindInstalledFiles(self): + """ returns the list of all targets of the Install and InstallAs Builder. + """ + from SCons.Tool import install + if install._UNIQUE_INSTALLED_FILES is None: + install._UNIQUE_INSTALLED_FILES = SCons.Util.uniquer_hashables(install._INSTALLED_FILES) + return install._UNIQUE_INSTALLED_FILES + +class OverrideEnvironment(Base): + """A proxy that overrides variables in a wrapped construction + environment by returning values from an overrides dictionary in + preference to values from the underlying subject environment. + + This is a lightweight (I hope) proxy that passes through most use of + attributes to the underlying Environment.Base class, but has just + enough additional methods defined to act like a real construction + environment with overridden values. It can wrap either a Base + construction environment, or another OverrideEnvironment, which + can in turn nest arbitrary OverrideEnvironments... + + Note that we do *not* call the underlying base class + (SubsitutionEnvironment) initialization, because we get most of those + from proxying the attributes of the subject construction environment. + But because we subclass SubstitutionEnvironment, this class also + has inherited arg2nodes() and subst*() methods; those methods can't + be proxied because they need *this* object's methods to fetch the + values from the overrides dictionary. + """ + + def __init__(self, subject, overrides={}): + if __debug__: logInstanceCreation(self, 'Environment.OverrideEnvironment') + self.__dict__['__subject'] = subject + self.__dict__['overrides'] = overrides + + # Methods that make this class act like a proxy. + def __getattr__(self, name): + return getattr(self.__dict__['__subject'], name) + def __setattr__(self, name, value): + setattr(self.__dict__['__subject'], name, value) + + # Methods that make this class act like a dictionary. + def __getitem__(self, key): + try: + return self.__dict__['overrides'][key] + except KeyError: + return self.__dict__['__subject'].__getitem__(key) + def __setitem__(self, key, value): + if not is_valid_construction_var(key): + raise SCons.Errors.UserError, "Illegal construction variable `%s'" % key + self.__dict__['overrides'][key] = value + def __delitem__(self, key): + try: + del self.__dict__['overrides'][key] + except KeyError: + deleted = 0 + else: + deleted = 1 + try: + result = self.__dict__['__subject'].__delitem__(key) + except KeyError: + if not deleted: + raise + result = None + return result + def get(self, key, default=None): + """Emulates the get() method of dictionaries.""" + try: + return self.__dict__['overrides'][key] + except KeyError: + return self.__dict__['__subject'].get(key, default) + def has_key(self, key): + try: + self.__dict__['overrides'][key] + return 1 + except KeyError: + return self.__dict__['__subject'].has_key(key) + def __contains__(self, key): + if self.__dict__['overrides'].__contains__(key): + return 1 + return self.__dict__['__subject'].__contains__(key) + def Dictionary(self): + """Emulates the items() method of dictionaries.""" + d = self.__dict__['__subject'].Dictionary().copy() + d.update(self.__dict__['overrides']) + return d + def items(self): + """Emulates the items() method of dictionaries.""" + return self.Dictionary().items() + + # Overridden private construction environment methods. + def _update(self, dict): + """Update an environment's values directly, bypassing the normal + checks that occur when users try to set items. + """ + self.__dict__['overrides'].update(dict) + + def gvars(self): + return self.__dict__['__subject'].gvars() + + def lvars(self): + lvars = self.__dict__['__subject'].lvars() + lvars.update(self.__dict__['overrides']) + return lvars + + # Overridden public construction environment methods. + def Replace(self, **kw): + kw = copy_non_reserved_keywords(kw) + self.__dict__['overrides'].update(semi_deepcopy(kw)) + +# The entry point that will be used by the external world +# to refer to a construction environment. This allows the wrapper +# interface to extend a construction environment for its own purposes +# by subclassing SCons.Environment.Base and then assigning the +# class to SCons.Environment.Environment. + +Environment = Base + +# An entry point for returning a proxy subclass instance that overrides +# the subst*() methods so they don't actually perform construction +# variable substitution. This is specifically intended to be the shim +# layer in between global function calls (which don't want construction +# variable substitution) and the DefaultEnvironment() (which would +# substitute variables if left to its own devices).""" +# +# We have to wrap this in a function that allows us to delay definition of +# the class until it's necessary, so that when it subclasses Environment +# it will pick up whatever Environment subclass the wrapper interface +# might have assigned to SCons.Environment.Environment. + +def NoSubstitutionProxy(subject): + class _NoSubstitutionProxy(Environment): + def __init__(self, subject): + self.__dict__['__subject'] = subject + def __getattr__(self, name): + return getattr(self.__dict__['__subject'], name) + def __setattr__(self, name, value): + return setattr(self.__dict__['__subject'], name, value) + def raw_to_mode(self, dict): + try: + raw = dict['raw'] + except KeyError: + pass + else: + del dict['raw'] + dict['mode'] = raw + def subst(self, string, *args, **kwargs): + return string + def subst_kw(self, kw, *args, **kwargs): + return kw + def subst_list(self, string, *args, **kwargs): + nargs = (string, self,) + args + nkw = kwargs.copy() + nkw['gvars'] = {} + self.raw_to_mode(nkw) + return apply(SCons.Subst.scons_subst_list, nargs, nkw) + def subst_target_source(self, string, *args, **kwargs): + nargs = (string, self,) + args + nkw = kwargs.copy() + nkw['gvars'] = {} + self.raw_to_mode(nkw) + return apply(SCons.Subst.scons_subst, nargs, nkw) + return _NoSubstitutionProxy(subject) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Environment.xml b/src/engine/SCons/Environment.xml new file mode 100644 index 0000000..6a245a5 --- /dev/null +++ b/src/engine/SCons/Environment.xml @@ -0,0 +1,187 @@ + + + +A dictionary mapping the names of the builders +available through this environment +to underlying Builder objects. +Builders named +Alias, CFile, CXXFile, DVI, Library, Object, PDF, PostScript, and Program +are available by default. +If you initialize this variable when an +Environment is created: + + +env = Environment(BUILDERS = {'NewBuilder' : foo}) + + +the default Builders will no longer be available. +To use a new Builder object in addition to the default Builders, +add your new Builder object like this: + + +env = Environment() +env.Append(BUILDERS = {'NewBuilder' : foo}) + + +or this: + + +env = Environment() +env['BUILDERS]['NewBuilder'] = foo + + + + + + +A function that converts a string +into a Dir instance relative to the target being built. + + + + + +A dictionary of environment variables +to use when invoking commands. When +&cv-ENV; is used in a command all list +values will be joined using the path separator and any other non-string +values will simply be coerced to a string. +Note that, by default, +&scons; +does +not +propagate the environment in force when you +execute +&scons; +to the commands used to build target files. +This is so that builds will be guaranteed +repeatable regardless of the environment +variables set at the time +&scons; +is invoked. + +If you want to propagate your +environment variables +to the commands executed +to build target files, +you must do so explicitly: + + +import os +env = Environment(ENV = os.environ) + + +Note that you can choose only to propagate +certain environment variables. +A common example is +the system +PATH +environment variable, +so that +&scons; +uses the same utilities +as the invoking shell (or other process): + + +import os +env = Environment(ENV = {'PATH' : os.environ['PATH']}) + + + + + + +A function that converts a string into a File instance relative to the +target being built. + + + + + +A list of the available implicit dependency scanners. +New file scanners may be added by +appending to this list, +although the more flexible approach +is to associate scanners +with a specific Builder. +See the sections "Builder Objects" +and "Scanner Objects," +below, for more information. + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A reserved variable name +that may not be set or used in a construction environment. +(See "Variable Substitution," below.) + + + + + +A list of the names of the Tool specifications +that are part of this construction environment. + + diff --git a/src/engine/SCons/EnvironmentTests.py b/src/engine/SCons/EnvironmentTests.py new file mode 100644 index 0000000..73ebf3c --- /dev/null +++ b/src/engine/SCons/EnvironmentTests.py @@ -0,0 +1,3987 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/EnvironmentTests.py 4577 2009/12/27 19:44:43 scons" + +import copy +import os +import string +import StringIO +import sys +import TestCmd +import unittest +import UserList + +from SCons.Environment import * +import SCons.Warnings + +def diff_env(env1, env2): + s1 = "env1 = {\n" + s2 = "env2 = {\n" + d = {} + for k in env1._dict.keys() + env2._dict.keys(): + d[k] = None + keys = d.keys() + keys.sort() + for k in keys: + if env1.has_key(k): + if env2.has_key(k): + if env1[k] != env2[k]: + s1 = s1 + " " + repr(k) + " : " + repr(env1[k]) + "\n" + s2 = s2 + " " + repr(k) + " : " + repr(env2[k]) + "\n" + else: + s1 = s1 + " " + repr(k) + " : " + repr(env1[k]) + "\n" + elif env2.has_key(k): + s2 = s2 + " " + repr(k) + " : " + repr(env2[k]) + "\n" + s1 = s1 + "}\n" + s2 = s2 + "}\n" + return s1 + s2 + +def diff_dict(d1, d2): + s1 = "d1 = {\n" + s2 = "d2 = {\n" + d = {} + for k in d1.keys() + d2.keys(): + d[k] = None + keys = d.keys() + keys.sort() + for k in keys: + if d1.has_key(k): + if d2.has_key(k): + if d1[k] != d2[k]: + s1 = s1 + " " + repr(k) + " : " + repr(d1[k]) + "\n" + s2 = s2 + " " + repr(k) + " : " + repr(d2[k]) + "\n" + else: + s1 = s1 + " " + repr(k) + " : " + repr(d1[k]) + "\n" + elif env2.has_key(k): + s2 = s2 + " " + repr(k) + " : " + repr(d2[k]) + "\n" + s1 = s1 + "}\n" + s2 = s2 + "}\n" + return s1 + s2 + +called_it = {} +built_it = {} + +class Builder: + """A dummy Builder class for testing purposes. "Building" + a target is simply setting a value in the dictionary. + """ + def __init__(self, name = None): + self.name = name + + def __call__(self, env, target=None, source=None, **kw): + global called_it + called_it['target'] = target + called_it['source'] = source + called_it.update(kw) + + def execute(self, target = None, **kw): + global built_it + built_it[target] = 1 + + + +scanned_it = {} + +class Scanner: + """A dummy Scanner class for testing purposes. "Scanning" + a target is simply setting a value in the dictionary. + """ + def __init__(self, name, skeys=[]): + self.name = name + self.skeys = skeys + + def __call__(self, filename): + global scanned_it + scanned_it[filename] = 1 + + def __cmp__(self, other): + try: + return cmp(self.__dict__, other.__dict__) + except AttributeError: + return 1 + + def get_skeys(self, env): + return self.skeys + + def __str__(self): + return self.name + + + +class CLVar(UserList.UserList): + def __init__(self, seq): + if type(seq) == type(''): + seq = string.split(seq) + UserList.UserList.__init__(self, seq) + def __add__(self, other): + return UserList.UserList.__add__(self, CLVar(other)) + def __radd__(self, other): + return UserList.UserList.__radd__(self, CLVar(other)) + def __coerce__(self, other): + return (self, CLVar(other)) + + + +class DummyNode: + def __init__(self, name): + self.name = name + def __str__(self): + return self.name + def rfile(self): + return self + def get_subst_proxy(self): + return self + +def test_tool( env ): + env['_F77INCFLAGS'] = '$( ${_concat(INCPREFIX, F77PATH, INCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' + +class TestEnvironmentFixture: + def TestEnvironment(self, *args, **kw): + if not kw or not kw.has_key('tools'): + kw['tools'] = [test_tool] + default_keys = { 'CC' : 'cc', + 'CCFLAGS' : '-DNDEBUG', + 'ENV' : { 'TMP' : '/tmp' } } + for key, value in default_keys.items(): + if not kw.has_key(key): + kw[key] = value + if not kw.has_key('BUILDERS'): + static_obj = SCons.Builder.Builder(action = {}, + emitter = {}, + suffix = '.o', + single_source = 1) + kw['BUILDERS'] = {'Object' : static_obj} + static_obj.add_action('.cpp', 'fake action') + + env = apply(Environment, args, kw) + return env + +class SubstitutionTestCase(unittest.TestCase): + + def test___init__(self): + """Test initializing a SubstitutionEnvironment + """ + env = SubstitutionEnvironment() + assert not env.has_key('__env__') + + def test___cmp__(self): + """Test comparing SubstitutionEnvironments + """ + + env1 = SubstitutionEnvironment(XXX = 'x') + env2 = SubstitutionEnvironment(XXX = 'x') + env3 = SubstitutionEnvironment(XXX = 'xxx') + env4 = SubstitutionEnvironment(XXX = 'x', YYY = 'x') + + assert env1 == env2 + assert env1 != env3 + assert env1 != env4 + + def test___delitem__(self): + """Test deleting a variable from a SubstitutionEnvironment + """ + env1 = SubstitutionEnvironment(XXX = 'x', YYY = 'y') + env2 = SubstitutionEnvironment(XXX = 'x') + del env1['YYY'] + assert env1 == env2 + + def test___getitem__(self): + """Test fetching a variable from a SubstitutionEnvironment + """ + env = SubstitutionEnvironment(XXX = 'x') + assert env['XXX'] == 'x', env['XXX'] + + def test___setitem__(self): + """Test setting a variable in a SubstitutionEnvironment + """ + env1 = SubstitutionEnvironment(XXX = 'x') + env2 = SubstitutionEnvironment(XXX = 'x', YYY = 'y') + env1['YYY'] = 'y' + assert env1 == env2 + + def test_get(self): + """Test the SubstitutionEnvironment get() method + """ + env = SubstitutionEnvironment(XXX = 'x') + assert env.get('XXX') == 'x', env.get('XXX') + assert env.get('YYY') is None, env.get('YYY') + + def test_has_key(self): + """Test the SubstitutionEnvironment has_key() method + """ + env = SubstitutionEnvironment(XXX = 'x') + assert env.has_key('XXX') + assert not env.has_key('YYY') + + def test_contains(self): + """Test the SubstitutionEnvironment __contains__() method + """ + try: + 'x' in {'x':1} + except TypeError: + # TODO(1.5) + # An early version of Python that doesn't support "in" + # on dictionaries. Just pass the test. + pass + else: + env = SubstitutionEnvironment(XXX = 'x') + assert 'XXX' in env + assert not 'YYY' in env + + def test_items(self): + """Test the SubstitutionEnvironment items() method + """ + env = SubstitutionEnvironment(XXX = 'x', YYY = 'y') + items = env.items() + assert items == [('XXX','x'), ('YYY','y')], items + + def test_arg2nodes(self): + """Test the arg2nodes method + """ + env = SubstitutionEnvironment() + dict = {} + class X(SCons.Node.Node): + pass + def Factory(name, directory = None, create = 1, dict=dict, X=X): + if not dict.has_key(name): + dict[name] = X() + dict[name].name = name + return dict[name] + + nodes = env.arg2nodes("Util.py UtilTests.py", Factory) + assert len(nodes) == 1, nodes + assert isinstance(nodes[0], X) + assert nodes[0].name == "Util.py UtilTests.py" + + import types + if hasattr(types, 'UnicodeType'): + code = """if 1: + nodes = env.arg2nodes(u"Util.py UtilTests.py", Factory) + assert len(nodes) == 1, nodes + assert isinstance(nodes[0], X) + assert nodes[0].name == u"Util.py UtilTests.py" + \n""" + exec code in globals(), locals() + + nodes = env.arg2nodes(["Util.py", "UtilTests.py"], Factory) + assert len(nodes) == 2, nodes + assert isinstance(nodes[0], X) + assert isinstance(nodes[1], X) + assert nodes[0].name == "Util.py" + assert nodes[1].name == "UtilTests.py" + + n1 = Factory("Util.py") + nodes = env.arg2nodes([n1, "UtilTests.py"], Factory) + assert len(nodes) == 2, nodes + assert isinstance(nodes[0], X) + assert isinstance(nodes[1], X) + assert nodes[0].name == "Util.py" + assert nodes[1].name == "UtilTests.py" + + class SConsNode(SCons.Node.Node): + pass + nodes = env.arg2nodes(SConsNode()) + assert len(nodes) == 1, nodes + assert isinstance(nodes[0], SConsNode), node + + class OtherNode: + pass + nodes = env.arg2nodes(OtherNode()) + assert len(nodes) == 1, nodes + assert isinstance(nodes[0], OtherNode), node + + def lookup_a(str, F=Factory): + if str[0] == 'a': + n = F(str) + n.a = 1 + return n + else: + return None + + def lookup_b(str, F=Factory): + if str[0] == 'b': + n = F(str) + n.b = 1 + return n + else: + return None + + env_ll = SubstitutionEnvironment() + env_ll.lookup_list = [lookup_a, lookup_b] + + nodes = env_ll.arg2nodes(['aaa', 'bbb', 'ccc'], Factory) + assert len(nodes) == 3, nodes + + assert nodes[0].name == 'aaa', nodes[0] + assert nodes[0].a == 1, nodes[0] + assert not hasattr(nodes[0], 'b'), nodes[0] + + assert nodes[1].name == 'bbb' + assert not hasattr(nodes[1], 'a'), nodes[1] + assert nodes[1].b == 1, nodes[1] + + assert nodes[2].name == 'ccc' + assert not hasattr(nodes[2], 'a'), nodes[1] + assert not hasattr(nodes[2], 'b'), nodes[1] + + def lookup_bbbb(str, F=Factory): + if str == 'bbbb': + n = F(str) + n.bbbb = 1 + return n + else: + return None + + def lookup_c(str, F=Factory): + if str[0] == 'c': + n = F(str) + n.c = 1 + return n + else: + return None + + nodes = env.arg2nodes(['bbbb', 'ccc'], Factory, + [lookup_c, lookup_bbbb, lookup_b]) + assert len(nodes) == 2, nodes + + assert nodes[0].name == 'bbbb' + assert not hasattr(nodes[0], 'a'), nodes[1] + assert not hasattr(nodes[0], 'b'), nodes[1] + assert nodes[0].bbbb == 1, nodes[1] + assert not hasattr(nodes[0], 'c'), nodes[0] + + assert nodes[1].name == 'ccc' + assert not hasattr(nodes[1], 'a'), nodes[1] + assert not hasattr(nodes[1], 'b'), nodes[1] + assert not hasattr(nodes[1], 'bbbb'), nodes[0] + assert nodes[1].c == 1, nodes[1] + + def test_arg2nodes_target_source(self): + """Test the arg2nodes method with target= and source= keywords + """ + targets = [DummyNode('t1'), DummyNode('t2')] + sources = [DummyNode('s1'), DummyNode('s2')] + env = SubstitutionEnvironment() + nodes = env.arg2nodes(['${TARGET}-a', + '${SOURCE}-b', + '${TARGETS[1]}-c', + '${SOURCES[1]}-d'], + DummyNode, + target=targets, + source=sources) + names = map(lambda n: n.name, nodes) + assert names == ['t1-a', 's1-b', 't2-c', 's2-d'], names + + def test_gvars(self): + """Test the base class gvars() method""" + env = SubstitutionEnvironment() + gvars = env.gvars() + assert gvars == {}, gvars + + def test_lvars(self): + """Test the base class lvars() method""" + env = SubstitutionEnvironment() + lvars = env.lvars() + assert lvars == {}, lvars + + def test_subst(self): + """Test substituting construction variables within strings + + Check various combinations, including recursive expansion + of variables into other variables. + """ + env = SubstitutionEnvironment(AAA = 'a', BBB = 'b') + mystr = env.subst("$AAA ${AAA}A $BBBB $BBB") + assert mystr == "a aA b", mystr + + # Changed the tests below to reflect a bug fix in + # subst() + env = SubstitutionEnvironment(AAA = '$BBB', BBB = 'b', BBBA = 'foo') + mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB") + assert mystr == "b bA bB b", mystr + + env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = 'c') + mystr = env.subst("$AAA ${AAA}A ${AAA}B $BBB") + assert mystr == "c cA cB c", mystr + + # Lists: + env = SubstitutionEnvironment(AAA = ['a', 'aa', 'aaa']) + mystr = env.subst("$AAA") + assert mystr == "a aa aaa", mystr + + # Tuples: + env = SubstitutionEnvironment(AAA = ('a', 'aa', 'aaa')) + mystr = env.subst("$AAA") + assert mystr == "a aa aaa", mystr + + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + + env = SubstitutionEnvironment(AAA = 'aaa') + s = env.subst('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2]) + assert s == "aaa t1 s1 s2", s + s = env.subst('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2]) + assert s == "aaa t1 t2 s1", s + + # Test callables in the SubstitutionEnvironment + def foo(target, source, env, for_signature): + assert str(target) == 't', target + assert str(source) == 's', source + return env["FOO"] + + env = SubstitutionEnvironment(BAR=foo, FOO='baz') + t = DummyNode('t') + s = DummyNode('s') + + subst = env.subst('test $BAR', target=t, source=s) + assert subst == 'test baz', subst + + # Test not calling callables in the SubstitutionEnvironment + if 0: + # This will take some serious surgery to subst() and + # subst_list(), so just leave these tests out until we can + # do that. + def bar(arg): + pass + + env = SubstitutionEnvironment(BAR=bar, FOO='$BAR') + + subst = env.subst('$BAR', call=None) + assert subst is bar, subst + + subst = env.subst('$FOO', call=None) + assert subst is bar, subst + + def test_subst_kw(self): + """Test substituting construction variables within dictionaries""" + env = SubstitutionEnvironment(AAA = 'a', BBB = 'b') + kw = env.subst_kw({'$AAA' : 'aaa', 'bbb' : '$BBB'}) + assert len(kw) == 2, kw + assert kw['a'] == 'aaa', kw['a'] + assert kw['bbb'] == 'b', kw['bbb'] + + def test_subst_list(self): + """Test substituting construction variables in command lists + """ + env = SubstitutionEnvironment(AAA = 'a', BBB = 'b') + l = env.subst_list("$AAA ${AAA}A $BBBB $BBB") + assert l == [["a", "aA", "b"]], l + + # Changed the tests below to reflect a bug fix in + # subst() + env = SubstitutionEnvironment(AAA = '$BBB', BBB = 'b', BBBA = 'foo') + l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB") + assert l == [["b", "bA", "bB", "b"]], l + + env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = 'c') + l = env.subst_list("$AAA ${AAA}A ${AAA}B $BBB") + assert l == [["c", "cA", "cB", "c"]], mystr + + env = SubstitutionEnvironment(AAA = '$BBB', BBB = '$CCC', CCC = [ 'a', 'b\nc' ]) + lst = env.subst_list([ "$AAA", "B $CCC" ]) + assert lst == [[ "a", "b"], ["c", "B a", "b"], ["c"]], lst + + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + + env = SubstitutionEnvironment(AAA = 'aaa') + s = env.subst_list('$AAA $TARGET $SOURCES', target=[t1, t2], source=[s1, s2]) + assert s == [["aaa", "t1", "s1", "s2"]], s + s = env.subst_list('$AAA $TARGETS $SOURCE', target=[t1, t2], source=[s1, s2]) + assert s == [["aaa", "t1", "t2", "s1"]], s + + # Test callables in the SubstitutionEnvironment + def foo(target, source, env, for_signature): + assert str(target) == 't', target + assert str(source) == 's', source + return env["FOO"] + + env = SubstitutionEnvironment(BAR=foo, FOO='baz') + t = DummyNode('t') + s = DummyNode('s') + + lst = env.subst_list('test $BAR', target=t, source=s) + assert lst == [['test', 'baz']], lst + + # Test not calling callables in the SubstitutionEnvironment + if 0: + # This will take some serious surgery to subst() and + # subst_list(), so just leave these tests out until we can + # do that. + def bar(arg): + pass + + env = SubstitutionEnvironment(BAR=bar, FOO='$BAR') + + subst = env.subst_list('$BAR', call=None) + assert subst is bar, subst + + subst = env.subst_list('$FOO', call=None) + assert subst is bar, subst + + def test_subst_path(self): + """Test substituting a path list + """ + class MyProxy: + def __init__(self, val): + self.val = val + def get(self): + return self.val + '-proxy' + + class MyNode: + def __init__(self, val): + self.val = val + def get_subst_proxy(self): + return self + def __str__(self): + return self.val + + class MyObj: + def get(self): + return self + + env = SubstitutionEnvironment(FOO='foo', + BAR='bar', + LIST=['one', 'two'], + PROXY=MyProxy('my1')) + + r = env.subst_path('$FOO') + assert r == ['foo'], r + + r = env.subst_path(['$FOO', 'xxx', '$BAR']) + assert r == ['foo', 'xxx', 'bar'], r + + r = env.subst_path(['$FOO', '$LIST', '$BAR']) + assert map(str, r) == ['foo', 'one two', 'bar'], r + + r = env.subst_path(['$FOO', '$TARGET', '$SOURCE', '$BAR']) + assert r == ['foo', '', '', 'bar'], r + + r = env.subst_path(['$FOO', '$TARGET', '$BAR'], target=MyNode('ttt')) + assert map(str, r) == ['foo', 'ttt', 'bar'], r + + r = env.subst_path(['$FOO', '$SOURCE', '$BAR'], source=MyNode('sss')) + assert map(str, r) == ['foo', 'sss', 'bar'], r + + n = MyObj() + + r = env.subst_path(['$PROXY', MyProxy('my2'), n]) + assert r == ['my1-proxy', 'my2-proxy', n], r + + class StringableObj: + def __init__(self, s): + self.s = s + def __str__(self): + return self.s + + env = SubstitutionEnvironment(FOO=StringableObj("foo"), + BAR=StringableObj("bar")) + + r = env.subst_path([ "${FOO}/bar", "${BAR}/baz" ]) + assert r == [ "foo/bar", "bar/baz" ], r + + r = env.subst_path([ "bar/${FOO}", "baz/${BAR}" ]) + assert r == [ "bar/foo", "baz/bar" ], r + + r = env.subst_path([ "bar/${FOO}/bar", "baz/${BAR}/baz" ]) + assert r == [ "bar/foo/bar", "baz/bar/baz" ], r + + def test_subst_target_source(self): + """Test the base environment subst_target_source() method""" + env = SubstitutionEnvironment(AAA = 'a', BBB = 'b') + mystr = env.subst_target_source("$AAA ${AAA}A $BBBB $BBB") + assert mystr == "a aA b", mystr + + def test_backtick(self): + """Test the backtick() method for capturing command output""" + env = SubstitutionEnvironment() + + test = TestCmd.TestCmd(workdir = '') + test.write('stdout.py', """\ +import sys +sys.stdout.write('this came from stdout.py\\n') +sys.exit(0) +""") + test.write('stderr.py', """\ +import sys +sys.stderr.write('this came from stderr.py\\n') +sys.exit(0) +""") + test.write('fail.py', """\ +import sys +sys.exit(1) +""") + test.write('echo.py', """\ +import os, sys +sys.stdout.write(os.environ['ECHO'] + '\\n') +sys.exit(0) +""") + + save_stderr = sys.stderr + + python = '"' + sys.executable + '"' + + try: + sys.stderr = StringIO.StringIO() + cmd = '%s %s' % (python, test.workpath('stdout.py')) + output = env.backtick(cmd) + errout = sys.stderr.getvalue() + assert output == 'this came from stdout.py\n', output + assert errout == '', errout + + sys.stderr = StringIO.StringIO() + cmd = '%s %s' % (python, test.workpath('stderr.py')) + output = env.backtick(cmd) + errout = sys.stderr.getvalue() + assert output == '', output + assert errout == 'this came from stderr.py\n', errout + + sys.stderr = StringIO.StringIO() + cmd = '%s %s' % (python, test.workpath('fail.py')) + try: + env.backtick(cmd) + except OSError, e: + assert str(e) == "'%s' exited 1" % cmd, str(e) + else: + self.fail("did not catch expected OSError") + + sys.stderr = StringIO.StringIO() + cmd = '%s %s' % (python, test.workpath('echo.py')) + env['ENV'] = os.environ.copy() + env['ENV']['ECHO'] = 'this came from ECHO' + output = env.backtick(cmd) + errout = sys.stderr.getvalue() + assert output == 'this came from ECHO\n', output + assert errout == '', errout + + finally: + sys.stderr = save_stderr + + def test_AddMethod(self): + """Test the AddMethod() method""" + env = SubstitutionEnvironment(FOO = 'foo') + + def func(self): + return 'func-' + self['FOO'] + + assert not hasattr(env, 'func') + env.AddMethod(func) + r = env.func() + assert r == 'func-foo', r + + assert not hasattr(env, 'bar') + env.AddMethod(func, 'bar') + r = env.bar() + assert r == 'func-foo', r + + def func2(self, arg=''): + return 'func2-' + self['FOO'] + arg + + env.AddMethod(func2) + r = env.func2() + assert r == 'func2-foo', r + r = env.func2('-xxx') + assert r == 'func2-foo-xxx', r + + env.AddMethod(func2, 'func') + r = env.func() + assert r == 'func2-foo', r + r = env.func('-yyy') + assert r == 'func2-foo-yyy', r + + # Test that clones of clones correctly re-bind added methods. + env1 = Environment(FOO = '1') + env1.AddMethod(func2) + env2 = env1.Clone(FOO = '2') + env3 = env2.Clone(FOO = '3') + env4 = env3.Clone(FOO = '4') + r = env1.func2() + assert r == 'func2-1', r + r = env2.func2() + assert r == 'func2-2', r + r = env3.func2() + assert r == 'func2-3', r + r = env4.func2() + assert r == 'func2-4', r + + # Test that clones don't re-bind an attribute that the user + env1 = Environment(FOO = '1') + env1.AddMethod(func2) + def replace_func2(): + return 'replace_func2' + env1.func2 = replace_func2 + env2 = env1.Clone(FOO = '2') + r = env2.func2() + assert r == 'replace_func2', r + + def test_Override(self): + "Test overriding construction variables" + env = SubstitutionEnvironment(ONE=1, TWO=2, THREE=3, FOUR=4) + assert env['ONE'] == 1, env['ONE'] + assert env['TWO'] == 2, env['TWO'] + assert env['THREE'] == 3, env['THREE'] + assert env['FOUR'] == 4, env['FOUR'] + + env2 = env.Override({'TWO' : '10', + 'THREE' :'x $THREE y', + 'FOUR' : ['x', '$FOUR', 'y']}) + assert env2['ONE'] == 1, env2['ONE'] + assert env2['TWO'] == '10', env2['TWO'] + assert env2['THREE'] == 'x 3 y', env2['THREE'] + assert env2['FOUR'] == ['x', 4, 'y'], env2['FOUR'] + + assert env['ONE'] == 1, env['ONE'] + assert env['TWO'] == 2, env['TWO'] + assert env['THREE'] == 3, env['THREE'] + assert env['FOUR'] == 4, env['FOUR'] + + env2.Replace(ONE = "won") + assert env2['ONE'] == "won", env2['ONE'] + assert env['ONE'] == 1, env['ONE'] + + def test_ParseFlags(self): + """Test the ParseFlags() method + """ + env = SubstitutionEnvironment() + + empty = { + 'ASFLAGS' : [], + 'CFLAGS' : [], + 'CCFLAGS' : [], + 'CPPDEFINES' : [], + 'CPPFLAGS' : [], + 'CPPPATH' : [], + 'FRAMEWORKPATH' : [], + 'FRAMEWORKS' : [], + 'LIBPATH' : [], + 'LIBS' : [], + 'LINKFLAGS' : [], + 'RPATH' : [], + } + + d = env.ParseFlags(None) + assert d == empty, d + + d = env.ParseFlags('') + assert d == empty, d + + d = env.ParseFlags([]) + assert d == empty, d + + s = "-I/usr/include/fum -I bar -X\n" + \ + '-I"C:\\Program Files\\ASCEND\\include" ' + \ + "-L/usr/fax -L foo -lxxx -l yyy " + \ + '-L"C:\\Program Files\\ASCEND" -lascend ' + \ + "-Wa,-as -Wl,-link " + \ + "-Wl,-rpath=rpath1 " + \ + "-Wl,-R,rpath2 " + \ + "-Wl,-Rrpath3 " + \ + "-Wp,-cpp " + \ + "-std=c99 " + \ + "-framework Carbon " + \ + "-frameworkdir=fwd1 " + \ + "-Ffwd2 " + \ + "-F fwd3 " + \ + "-pthread " + \ + "-mno-cygwin -mwindows " + \ + "-arch i386 -isysroot /tmp +DD64 " + \ + "-DFOO -DBAR=value -D BAZ " + + d = env.ParseFlags(s) + + assert d['ASFLAGS'] == ['-as'], d['ASFLAGS'] + assert d['CFLAGS'] == ['-std=c99'] + assert d['CCFLAGS'] == ['-X', '-Wa,-as', + '-pthread', '-mno-cygwin', + ('-arch', 'i386'), ('-isysroot', '/tmp'), + '+DD64'], d['CCFLAGS'] + assert d['CPPDEFINES'] == ['FOO', ['BAR', 'value'], 'BAZ'], d['CPPDEFINES'] + assert d['CPPFLAGS'] == ['-Wp,-cpp'], d['CPPFLAGS'] + assert d['CPPPATH'] == ['/usr/include/fum', + 'bar', + 'C:\\Program Files\\ASCEND\\include'], d['CPPPATH'] + assert d['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], d['FRAMEWORKPATH'] + assert d['FRAMEWORKS'] == ['Carbon'], d['FRAMEWORKS'] + assert d['LIBPATH'] == ['/usr/fax', + 'foo', + 'C:\\Program Files\\ASCEND'], d['LIBPATH'] + LIBS = map(str, d['LIBS']) + assert LIBS == ['xxx', 'yyy', 'ascend'], (d['LIBS'], LIBS) + assert d['LINKFLAGS'] == ['-Wl,-link', '-pthread', + '-mno-cygwin', '-mwindows', + ('-arch', 'i386'), + ('-isysroot', '/tmp'), + '+DD64'], d['LINKFLAGS'] + assert d['RPATH'] == ['rpath1', 'rpath2', 'rpath3'], d['RPATH'] + + + def test_MergeFlags(self): + """Test the MergeFlags() method + """ + env = SubstitutionEnvironment() + env.MergeFlags('') + assert not env.has_key('CCFLAGS'), env['CCFLAGS'] + env.MergeFlags('-X') + assert env['CCFLAGS'] == ['-X'], env['CCFLAGS'] + env.MergeFlags('-X') + assert env['CCFLAGS'] == ['-X'], env['CCFLAGS'] + + env = SubstitutionEnvironment(CCFLAGS=None) + env.MergeFlags('-Y') + assert env['CCFLAGS'] == ['-Y'], env['CCFLAGS'] + + env = SubstitutionEnvironment() + env.MergeFlags({'A':['aaa'], 'B':['bbb']}) + assert env['A'] == ['aaa'], env['A'] + assert env['B'] == ['bbb'], env['B'] + +# def test_MergeShellPaths(self): +# """Test the MergeShellPaths() method +# """ +# env = Environment() +# env.MergeShellPaths({}) +# assert not env['ENV'].has_key('INCLUDE'), env['INCLUDE'] +# env.MergeShellPaths({'INCLUDE': r'c:\Program Files\Stuff'}) +# assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff', env['ENV']['INCLUDE'] +# env.MergeShellPaths({'INCLUDE': r'c:\Program Files\Stuff'}) +# assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff', env['ENV']['INCLUDE'] +# env.MergeShellPaths({'INCLUDE': r'xyz'}) +# assert env['ENV']['INCLUDE'] == r'xyz%sc:\Program Files\Stuff'%os.pathsep, env['ENV']['INCLUDE'] + +# env = Environment() +# env['ENV']['INCLUDE'] = 'xyz' +# env.MergeShellPaths({'INCLUDE':['c:/inc1', 'c:/inc2']} ) +# assert env['ENV']['INCLUDE'] == r'c:/inc1%sc:/inc2%sxyz'%(os.pathsep, os.pathsep), env['ENV']['INCLUDE'] + +# # test prepend=0 +# env = Environment() +# env.MergeShellPaths({'INCLUDE': r'c:\Program Files\Stuff'}, prepend=0) +# assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff', env['ENV']['INCLUDE'] +# env.MergeShellPaths({'INCLUDE': r'xyz'}, prepend=0) +# assert env['ENV']['INCLUDE'] == r'c:\Program Files\Stuff%sxyz'%os.pathsep, env['ENV']['INCLUDE'] + + +class BaseTestCase(unittest.TestCase,TestEnvironmentFixture): + + reserved_variables = [ + 'CHANGED_SOURCES', + 'CHANGED_TARGETS', + 'SOURCE', + 'SOURCES', + 'TARGET', + 'TARGETS', + 'UNCHANGED_SOURCES', + 'UNCHANGED_TARGETS', + ] + + def test___init__(self): + """Test construction Environment creation + + Create two with identical arguments and check that + they compare the same. + """ + env1 = self.TestEnvironment(XXX = 'x', YYY = 'y') + env2 = self.TestEnvironment(XXX = 'x', YYY = 'y') + assert env1 == env2, diff_env(env1, env2) + + assert not env1.has_key('__env__') + assert not env2.has_key('__env__') + + def test_variables(self): + """Test that variables only get applied once.""" + class FakeOptions: + def __init__(self, key, val): + self.calls = 0 + self.key = key + self.val = val + def keys(self): + return [self.key] + def Update(self, env): + env[self.key] = self.val + self.calls = self.calls + 1 + + o = FakeOptions('AAA', 'fake_opt') + env = Environment(variables=o, AAA='keyword_arg') + assert o.calls == 1, o.calls + assert env['AAA'] == 'fake_opt', env['AAA'] + + def test_get(self): + """Test the get() method.""" + env = self.TestEnvironment(aaa = 'AAA') + + x = env.get('aaa') + assert x == 'AAA', x + x = env.get('aaa', 'XXX') + assert x == 'AAA', x + x = env.get('bbb') + assert x is None, x + x = env.get('bbb', 'XXX') + assert x == 'XXX', x + + def test_Builder_calls(self): + """Test Builder calls through different environments + """ + global called_it + + b1 = Builder() + b2 = Builder() + + env = Environment() + env.Replace(BUILDERS = { 'builder1' : b1, + 'builder2' : b2 }) + called_it = {} + env.builder1('in1') + assert called_it['target'] is None, called_it + assert called_it['source'] == ['in1'], called_it + + called_it = {} + env.builder2(source = 'in2', xyzzy = 1) + assert called_it['target'] is None, called_it + assert called_it['source'] == ['in2'], called_it + assert called_it['xyzzy'] == 1, called_it + + called_it = {} + env.builder1(foo = 'bar') + assert called_it['foo'] == 'bar', called_it + assert called_it['target'] is None, called_it + assert called_it['source'] is None, called_it + + def test_BuilderWrapper_attributes(self): + """Test getting and setting of BuilderWrapper attributes + """ + b1 = Builder() + b2 = Builder() + e1 = Environment() + e2 = Environment() + + e1.Replace(BUILDERS = {'b' : b1}) + bw = e1.b + + assert bw.env is e1 + bw.env = e2 + assert bw.env is e2 + + assert bw.builder is b1 + bw.builder = b2 + assert bw.builder is b2 + + self.assertRaises(AttributeError, getattr, bw, 'foobar') + bw.foobar = 42 + assert bw.foobar is 42 + + # This unit test is currently disabled because we don't think the + # underlying method it tests (Environment.BuilderWrapper.execute()) + # is necessary, but we're leaving the code here for now in case + # that's mistaken. + def _DO_NOT_test_Builder_execs(self): + """Test Builder execution through different environments + + One environment is initialized with a single + Builder object, one with a list of a single Builder + object, and one with a list of two Builder objects. + """ + global built_it + + b1 = Builder() + b2 = Builder() + + built_it = {} + env3 = Environment() + env3.Replace(BUILDERS = { 'builder1' : b1, + 'builder2' : b2 }) + env3.builder1.execute(target = 'out1') + env3.builder2.execute(target = 'out2') + env3.builder1.execute(target = 'out3') + assert built_it['out1'] + assert built_it['out2'] + assert built_it['out3'] + + env4 = env3.Clone() + assert env4.builder1.env is env4, "builder1.env (%s) == env3 (%s)?" % ( +env4.builder1.env, env3) + assert env4.builder2.env is env4, "builder2.env (%s) == env3 (%s)?" % ( +env4.builder1.env, env3) + + # Now test BUILDERS as a dictionary. + built_it = {} + env5 = self.TestEnvironment(BUILDERS={ 'foo' : b1 }) + env5['BUILDERS']['bar'] = b2 + env5.foo.execute(target='out1') + env5.bar.execute(target='out2') + assert built_it['out1'] + assert built_it['out2'] + + built_it = {} + env6 = Environment() + env6['BUILDERS'] = { 'foo' : b1, + 'bar' : b2 } + env6.foo.execute(target='out1') + env6.bar.execute(target='out2') + assert built_it['out1'] + assert built_it['out2'] + + + + def test_Scanners(self): + """Test setting SCANNERS in various ways + + One environment is initialized with a single + Scanner object, one with a list of a single Scanner + object, and one with a list of two Scanner objects. + """ + global scanned_it + + s1 = Scanner(name = 'scanner1', skeys = [".c", ".cc"]) + s2 = Scanner(name = 'scanner2', skeys = [".m4"]) + s3 = Scanner(name = 'scanner3', skeys = [".m4", ".m5"]) + s4 = Scanner(name = 'scanner4', skeys = [None]) + +# XXX Tests for scanner execution through different environments, +# XXX if we ever want to do that some day +# scanned_it = {} +# env1 = self.TestEnvironment(SCANNERS = s1) +# env1.scanner1(filename = 'out1') +# assert scanned_it['out1'] +# +# scanned_it = {} +# env2 = self.TestEnvironment(SCANNERS = [s1]) +# env1.scanner1(filename = 'out1') +# assert scanned_it['out1'] +# +# scanned_it = {} +# env3 = Environment() +# env3.Replace(SCANNERS = [s1]) +# env3.scanner1(filename = 'out1') +# env3.scanner2(filename = 'out2') +# env3.scanner1(filename = 'out3') +# assert scanned_it['out1'] +# assert scanned_it['out2'] +# assert scanned_it['out3'] + + suffixes = [".c", ".cc", ".cxx", ".m4", ".m5"] + + env = Environment() + try: del env['SCANNERS'] + except KeyError: pass + s = map(env.get_scanner, suffixes) + assert s == [None, None, None, None, None], s + + env = self.TestEnvironment(SCANNERS = []) + s = map(env.get_scanner, suffixes) + assert s == [None, None, None, None, None], s + + env.Replace(SCANNERS = [s1]) + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, None, None], s + + env.Append(SCANNERS = [s2]) + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, s2, None], s + + env.AppendUnique(SCANNERS = [s3]) + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, s2, s3], s + + env = env.Clone(SCANNERS = [s2]) + s = map(env.get_scanner, suffixes) + assert s == [None, None, None, s2, None], s + + env['SCANNERS'] = [s1] + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, None, None], s + + env.PrependUnique(SCANNERS = [s2, s1]) + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, s2, None], s + + env.Prepend(SCANNERS = [s3]) + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, s3, s3], s + + # Verify behavior of case-insensitive suffix matches on Windows. + uc_suffixes = map(string.upper, suffixes) + + env = Environment(SCANNERS = [s1, s2, s3], + PLATFORM = 'linux') + + s = map(env.get_scanner, suffixes) + assert s == [s1, s1, None, s2, s3], s + + s = map(env.get_scanner, uc_suffixes) + assert s == [None, None, None, None, None], s + + env['PLATFORM'] = 'win32' + + s = map(env.get_scanner, uc_suffixes) + assert s == [s1, s1, None, s2, s3], s + + # Verify behavior for a scanner returning None (on Windows + # where we might try to perform case manipulation on None). + env.Replace(SCANNERS = [s4]) + s = map(env.get_scanner, suffixes) + assert s == [None, None, None, None, None], s + + def test_ENV(self): + """Test setting the external ENV in Environments + """ + env = Environment() + assert env.Dictionary().has_key('ENV') + + env = self.TestEnvironment(ENV = { 'PATH' : '/foo:/bar' }) + assert env.Dictionary('ENV')['PATH'] == '/foo:/bar' + + def test_ReservedVariables(self): + """Test warning generation when reserved variable names are set""" + + reserved_variables = [ + 'CHANGED_SOURCES', + 'CHANGED_TARGETS', + 'SOURCE', + 'SOURCES', + 'TARGET', + 'TARGETS', + 'UNCHANGED_SOURCES', + 'UNCHANGED_TARGETS', + ] + + warning = SCons.Warnings.ReservedVariableWarning + SCons.Warnings.enableWarningClass(warning) + old = SCons.Warnings.warningAsException(1) + + try: + env4 = Environment() + for kw in self.reserved_variables: + exc_caught = None + try: + env4[kw] = 'xyzzy' + except warning: + exc_caught = 1 + assert exc_caught, "Did not catch ReservedVariableWarning for `%s'" % kw + assert not env4.has_key(kw), "`%s' variable was incorrectly set" % kw + finally: + SCons.Warnings.warningAsException(old) + + def test_FutureReservedVariables(self): + """Test warning generation when future reserved variable names are set""" + + future_reserved_variables = [] + + warning = SCons.Warnings.FutureReservedVariableWarning + SCons.Warnings.enableWarningClass(warning) + old = SCons.Warnings.warningAsException(1) + + try: + env4 = Environment() + for kw in future_reserved_variables: + exc_caught = None + try: + env4[kw] = 'xyzzy' + except warning: + exc_caught = 1 + assert exc_caught, "Did not catch FutureReservedVariableWarning for `%s'" % kw + assert env4.has_key(kw), "`%s' variable was not set" % kw + finally: + SCons.Warnings.warningAsException(old) + + def test_IllegalVariables(self): + """Test that use of illegal variables raises an exception""" + env = Environment() + def test_it(var, env=env): + exc_caught = None + try: + env[var] = 1 + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch UserError for '%s'" % var + env['aaa'] = 1 + assert env['aaa'] == 1, env['aaa'] + test_it('foo/bar') + test_it('foo.bar') + test_it('foo-bar') + + def test_autogenerate(dict): + """Test autogenerating variables in a dictionary.""" + + drive, p = os.path.splitdrive(os.getcwd()) + def normalize_path(path, drive=drive): + if path[0] in '\\/': + path = drive + path + path = os.path.normpath(path) + drive, path = os.path.splitdrive(path) + return string.lower(drive) + path + + env = dict.TestEnvironment(LIBS = [ 'foo', 'bar', 'baz' ], + LIBLINKPREFIX = 'foo', + LIBLINKSUFFIX = 'bar') + + def RDirs(pathlist, fs=env.fs): + return fs.Dir('xx').Rfindalldirs(pathlist) + + env['RDirs'] = RDirs + flags = env.subst_list('$_LIBFLAGS', 1)[0] + assert flags == ['foobar', 'foobar', 'foobazbar'], flags + + blat = env.fs.Dir('blat') + + env.Replace(CPPPATH = [ 'foo', '$FOO/bar', blat ], + INCPREFIX = 'foo ', + INCSUFFIX = 'bar', + FOO = 'baz') + flags = env.subst_list('$_CPPINCFLAGS', 1)[0] + expect = [ '$(', + normalize_path('foo'), + normalize_path('xx/foobar'), + normalize_path('foo'), + normalize_path('xx/baz/bar'), + normalize_path('foo'), + normalize_path('blatbar'), + '$)', + ] + assert flags == expect, flags + + env.Replace(F77PATH = [ 'foo', '$FOO/bar', blat ], + INCPREFIX = 'foo ', + INCSUFFIX = 'bar', + FOO = 'baz') + flags = env.subst_list('$_F77INCFLAGS', 1)[0] + expect = [ '$(', + normalize_path('foo'), + normalize_path('xx/foobar'), + normalize_path('foo'), + normalize_path('xx/baz/bar'), + normalize_path('foo'), + normalize_path('blatbar'), + '$)', + ] + assert flags == expect, flags + + env.Replace(CPPPATH = '', F77PATH = '', LIBPATH = '') + l = env.subst_list('$_CPPINCFLAGS') + assert l == [[]], l + l = env.subst_list('$_F77INCFLAGS') + assert l == [[]], l + l = env.subst_list('$_LIBDIRFLAGS') + assert l == [[]], l + + env.fs.Repository('/rep1') + env.fs.Repository('/rep2') + env.Replace(CPPPATH = [ 'foo', '/a/b', '$FOO/bar', blat], + INCPREFIX = '-I ', + INCSUFFIX = 'XXX', + FOO = 'baz') + flags = env.subst_list('$_CPPINCFLAGS', 1)[0] + expect = [ '$(', + '-I', normalize_path('xx/fooXXX'), + '-I', normalize_path('/rep1/xx/fooXXX'), + '-I', normalize_path('/rep2/xx/fooXXX'), + '-I', normalize_path('/a/bXXX'), + '-I', normalize_path('xx/baz/barXXX'), + '-I', normalize_path('/rep1/xx/baz/barXXX'), + '-I', normalize_path('/rep2/xx/baz/barXXX'), + '-I', normalize_path('blatXXX'), + '$)' + ] + def normalize_if_path(arg, np=normalize_path): + if arg not in ('$(','$)','-I'): + return np(str(arg)) + return arg + flags = map(normalize_if_path, flags) + assert flags == expect, flags + + def test_platform(self): + """Test specifying a platform callable when instantiating.""" + class platform: + def __str__(self): return "TestPlatform" + def __call__(self, env): env['XYZZY'] = 777 + + def tool(env): + env['SET_TOOL'] = 'initialized' + assert env['PLATFORM'] == "TestPlatform" + + env = self.TestEnvironment(platform = platform(), tools = [tool]) + assert env['XYZZY'] == 777, env + assert env['PLATFORM'] == "TestPlatform" + assert env['SET_TOOL'] == "initialized" + + def test_Default_PLATFORM(self): + """Test overriding the default PLATFORM variable""" + class platform: + def __str__(self): return "DefaultTestPlatform" + def __call__(self, env): env['XYZZY'] = 888 + + def tool(env): + env['SET_TOOL'] = 'abcde' + assert env['PLATFORM'] == "DefaultTestPlatform" + + import SCons.Defaults + save = SCons.Defaults.ConstructionEnvironment.copy() + try: + import SCons.Defaults + SCons.Defaults.ConstructionEnvironment.update({ + 'PLATFORM' : platform(), + }) + env = self.TestEnvironment(tools = [tool]) + assert env['XYZZY'] == 888, env + assert env['PLATFORM'] == "DefaultTestPlatform" + assert env['SET_TOOL'] == "abcde" + finally: + SCons.Defaults.ConstructionEnvironment = save + + def test_tools(self): + """Test specifying a tool callable when instantiating.""" + def t1(env): + env['TOOL1'] = 111 + def t2(env): + env['TOOL2'] = 222 + def t3(env): + env['AAA'] = env['XYZ'] + def t4(env): + env['TOOL4'] = 444 + env = self.TestEnvironment(tools = [t1, t2, t3], XYZ = 'aaa') + assert env['TOOL1'] == 111, env['TOOL1'] + assert env['TOOL2'] == 222, env + assert env['AAA'] == 'aaa', env + t4(env) + assert env['TOOL4'] == 444, env + + test = TestCmd.TestCmd(workdir = '') + test.write('faketool.py', """\ +def generate(env, **kw): + for k, v in kw.items(): + env[k] = v + +def exists(env): + return 1 +""") + + env = self.TestEnvironment(tools = [('faketool', {'a':1, 'b':2, 'c':3})], + toolpath = [test.workpath('')]) + assert env['a'] == 1, env['a'] + assert env['b'] == 2, env['b'] + assert env['c'] == 3, env['c'] + + def test_Default_TOOLS(self): + """Test overriding the default TOOLS variable""" + def t5(env): + env['TOOL5'] = 555 + def t6(env): + env['TOOL6'] = 666 + def t7(env): + env['BBB'] = env['XYZ'] + def t8(env): + env['TOOL8'] = 888 + + import SCons.Defaults + save = SCons.Defaults.ConstructionEnvironment.copy() + try: + SCons.Defaults.ConstructionEnvironment.update({ + 'TOOLS' : [t5, t6, t7], + }) + env = Environment(XYZ = 'bbb') + assert env['TOOL5'] == 555, env['TOOL5'] + assert env['TOOL6'] == 666, env + assert env['BBB'] == 'bbb', env + t8(env) + assert env['TOOL8'] == 888, env + finally: + SCons.Defaults.ConstructionEnvironment = save + + def test_null_tools(self): + """Test specifying a tool of None is OK.""" + def t1(env): + env['TOOL1'] = 111 + def t2(env): + env['TOOL2'] = 222 + env = self.TestEnvironment(tools = [t1, None, t2], XYZ = 'aaa') + assert env['TOOL1'] == 111, env['TOOL1'] + assert env['TOOL2'] == 222, env + assert env['XYZ'] == 'aaa', env + env = self.TestEnvironment(tools = [None], XYZ = 'xyz') + assert env['XYZ'] == 'xyz', env + env = self.TestEnvironment(tools = [t1, '', t2], XYZ = 'ddd') + assert env['TOOL1'] == 111, env['TOOL1'] + assert env['TOOL2'] == 222, env + assert env['XYZ'] == 'ddd', env + + def test_concat(self): + "Test _concat()" + e1 = self.TestEnvironment(PRE='pre', SUF='suf', STR='a b', LIST=['a', 'b']) + s = e1.subst + x = s("${_concat('', '', '', __env__)}") + assert x == '', x + x = s("${_concat('', [], '', __env__)}") + assert x == '', x + x = s("${_concat(PRE, '', SUF, __env__)}") + assert x == '', x + x = s("${_concat(PRE, STR, SUF, __env__)}") + assert x == 'prea bsuf', x + x = s("${_concat(PRE, LIST, SUF, __env__)}") + assert x == 'preasuf prebsuf', x + + def test_gvars(self): + """Test the Environment gvars() method""" + env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z') + gvars = env.gvars() + assert gvars['XXX'] == 'x', gvars['XXX'] + assert gvars['YYY'] == 'y', gvars['YYY'] + assert gvars['ZZZ'] == 'z', gvars['ZZZ'] + + def test__update(self): + """Test the _update() method""" + env = self.TestEnvironment(X = 'x', Y = 'y', Z = 'z') + assert env['X'] == 'x', env['X'] + assert env['Y'] == 'y', env['Y'] + assert env['Z'] == 'z', env['Z'] + env._update({'X' : 'xxx', + 'TARGET' : 't', + 'TARGETS' : 'ttt', + 'SOURCE' : 's', + 'SOURCES' : 'sss', + 'Z' : 'zzz'}) + assert env['X'] == 'xxx', env['X'] + assert env['Y'] == 'y', env['Y'] + assert env['Z'] == 'zzz', env['Z'] + assert env['TARGET'] == 't', env['TARGET'] + assert env['TARGETS'] == 'ttt', env['TARGETS'] + assert env['SOURCE'] == 's', env['SOURCE'] + assert env['SOURCES'] == 'sss', env['SOURCES'] + + + + def test_Append(self): + """Test appending to construction variables in an Environment + """ + + b1 = Environment()['BUILDERS'] + b2 = Environment()['BUILDERS'] + assert b1 == b2, diff_dict(b1, b2) + + import UserDict + UD = UserDict.UserDict + import UserList + UL = UserList.UserList + + cases = [ + 'a1', 'A1', 'a1A1', + 'a2', ['A2'], ['a2', 'A2'], + 'a3', UL(['A3']), UL(['a', '3', 'A3']), + 'a4', '', 'a4', + 'a5', [], ['a5'], + 'a6', UL([]), UL(['a', '6']), + 'a7', [''], ['a7', ''], + 'a8', UL(['']), UL(['a', '8', '']), + + ['e1'], 'E1', ['e1', 'E1'], + ['e2'], ['E2'], ['e2', 'E2'], + ['e3'], UL(['E3']), UL(['e3', 'E3']), + ['e4'], '', ['e4'], + ['e5'], [], ['e5'], + ['e6'], UL([]), UL(['e6']), + ['e7'], [''], ['e7', ''], + ['e8'], UL(['']), UL(['e8', '']), + + UL(['i1']), 'I1', UL(['i1', 'I', '1']), + UL(['i2']), ['I2'], UL(['i2', 'I2']), + UL(['i3']), UL(['I3']), UL(['i3', 'I3']), + UL(['i4']), '', UL(['i4']), + UL(['i5']), [], UL(['i5']), + UL(['i6']), UL([]), UL(['i6']), + UL(['i7']), [''], UL(['i7', '']), + UL(['i8']), UL(['']), UL(['i8', '']), + + {'d1':1}, 'D1', {'d1':1, 'D1':None}, + {'d2':1}, ['D2'], {'d2':1, 'D2':None}, + {'d3':1}, UL(['D3']), {'d3':1, 'D3':None}, + {'d4':1}, {'D4':1}, {'d4':1, 'D4':1}, + {'d5':1}, UD({'D5':1}), UD({'d5':1, 'D5':1}), + + UD({'u1':1}), 'U1', UD({'u1':1, 'U1':None}), + UD({'u2':1}), ['U2'], UD({'u2':1, 'U2':None}), + UD({'u3':1}), UL(['U3']), UD({'u3':1, 'U3':None}), + UD({'u4':1}), {'U4':1}, UD({'u4':1, 'U4':1}), + UD({'u5':1}), UD({'U5':1}), UD({'u5':1, 'U5':1}), + + '', 'M1', 'M1', + '', ['M2'], ['M2'], + '', UL(['M3']), UL(['M3']), + '', '', '', + '', [], [], + '', UL([]), UL([]), + '', [''], [''], + '', UL(['']), UL(['']), + + [], 'N1', ['N1'], + [], ['N2'], ['N2'], + [], UL(['N3']), UL(['N3']), + [], '', [], + [], [], [], + [], UL([]), UL([]), + [], [''], [''], + [], UL(['']), UL(['']), + + UL([]), 'O1', ['O', '1'], + UL([]), ['O2'], ['O2'], + UL([]), UL(['O3']), UL(['O3']), + UL([]), '', UL([]), + UL([]), [], UL([]), + UL([]), UL([]), UL([]), + UL([]), [''], UL(['']), + UL([]), UL(['']), UL(['']), + + [''], 'P1', ['', 'P1'], + [''], ['P2'], ['', 'P2'], + [''], UL(['P3']), UL(['', 'P3']), + [''], '', [''], + [''], [], [''], + [''], UL([]), UL(['']), + [''], [''], ['', ''], + [''], UL(['']), UL(['', '']), + + UL(['']), 'Q1', ['', 'Q', '1'], + UL(['']), ['Q2'], ['', 'Q2'], + UL(['']), UL(['Q3']), UL(['', 'Q3']), + UL(['']), '', UL(['']), + UL(['']), [], UL(['']), + UL(['']), UL([]), UL(['']), + UL(['']), [''], UL(['', '']), + UL(['']), UL(['']), UL(['', '']), + ] + + env = Environment() + failed = 0 + while cases: + input, append, expect = cases[:3] + env['XXX'] = copy.copy(input) + try: + env.Append(XXX = append) + except Exception, e: + if failed == 0: print + print " %s Append %s exception: %s" % \ + (repr(input), repr(append), e) + failed = failed + 1 + else: + result = env['XXX'] + if result != expect: + if failed == 0: print + print " %s Append %s => %s did not match %s" % \ + (repr(input), repr(append), repr(result), repr(expect)) + failed = failed + 1 + del cases[:3] + assert failed == 0, "%d Append() cases failed" % failed + + env['UL'] = UL(['foo']) + env.Append(UL = 'bar') + result = env['UL'] + assert isinstance(result, UL), repr(result) + assert result == ['foo', 'b', 'a', 'r'], result + + env['CLVar'] = CLVar(['foo']) + env.Append(CLVar = 'bar') + result = env['CLVar'] + assert isinstance(result, CLVar), repr(result) + assert result == ['foo', 'bar'], result + + class C: + def __init__(self, name): + self.name = name + def __str__(self): + return self.name + def __cmp__(self, other): + raise "should not compare" + + ccc = C('ccc') + + env2 = self.TestEnvironment(CCC1 = ['c1'], CCC2 = ccc) + env2.Append(CCC1 = ccc, CCC2 = ['c2']) + assert env2['CCC1'][0] == 'c1', env2['CCC1'] + assert env2['CCC1'][1] is ccc, env2['CCC1'] + assert env2['CCC2'][0] is ccc, env2['CCC2'] + assert env2['CCC2'][1] == 'c2', env2['CCC2'] + + env3 = self.TestEnvironment(X = {'x1' : 7}) + env3.Append(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10}) + assert env3['X'] == {'x1': 8, 'x2': 9}, env3['X'] + assert env3['Y'] == {'y1': 10}, env3['Y'] + + env4 = self.TestEnvironment(BUILDERS = {'z1' : 11}) + env4.Append(BUILDERS = {'z2' : 12}) + assert env4['BUILDERS'] == {'z1' : 11, 'z2' : 12}, env4['BUILDERS'] + assert hasattr(env4, 'z1') + assert hasattr(env4, 'z2') + + def test_AppendENVPath(self): + """Test appending to an ENV path.""" + env1 = self.TestEnvironment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'}, + MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'}) + # have to include the pathsep here so that the test will work on UNIX too. + env1.AppendENVPath('PATH',r'C:\dir\num\two', sep = ';') + env1.AppendENVPath('PATH',r'C:\dir\num\three', sep = ';') + env1.AppendENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';') + env1.AppendENVPath('MYPATH',r'C:\mydir\num\one','MYENV', sep = ';') + # this should do nothing since delete_existing is 0 + env1.AppendENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';', delete_existing=0) + assert(env1['ENV']['PATH'] == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three') + assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one') + + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub1', 'sub2') + p=env1['ENV']['PATH'] + env1.AppendENVPath('PATH','#sub1', sep = ';') + env1.AppendENVPath('PATH',env1.fs.Dir('sub2'), sep = ';') + assert env1['ENV']['PATH'] == p + ';sub1;sub2', env1['ENV']['PATH'] + + def test_AppendUnique(self): + """Test appending to unique values to construction variables + + This strips values that are already present when lists are + involved.""" + env = self.TestEnvironment(AAA1 = 'a1', + AAA2 = 'a2', + AAA3 = 'a3', + AAA4 = 'a4', + AAA5 = 'a5', + BBB1 = ['b1'], + BBB2 = ['b2'], + BBB3 = ['b3'], + BBB4 = ['b4'], + BBB5 = ['b5'], + CCC1 = '', + CCC2 = '', + DDD1 = ['a', 'b', 'c']) + env.AppendUnique(AAA1 = 'a1', + AAA2 = ['a2'], + AAA3 = ['a3', 'b', 'c', 'c', 'b', 'a3'], # ignore dups + AAA4 = 'a4.new', + AAA5 = ['a5.new'], + BBB1 = 'b1', + BBB2 = ['b2'], + BBB3 = ['b3', 'c', 'd', 'c', 'b3'], + BBB4 = 'b4.new', + BBB5 = ['b5.new'], + CCC1 = 'c1', + CCC2 = ['c2'], + DDD1 = 'b') + + assert env['AAA1'] == 'a1a1', env['AAA1'] + assert env['AAA2'] == ['a2'], env['AAA2'] + assert env['AAA3'] == ['a3', 'b', 'c'], env['AAA3'] + assert env['AAA4'] == 'a4a4.new', env['AAA4'] + assert env['AAA5'] == ['a5', 'a5.new'], env['AAA5'] + assert env['BBB1'] == ['b1'], env['BBB1'] + assert env['BBB2'] == ['b2'], env['BBB2'] + assert env['BBB3'] == ['b3', 'c', 'd'], env['BBB3'] + assert env['BBB4'] == ['b4', 'b4.new'], env['BBB4'] + assert env['BBB5'] == ['b5', 'b5.new'], env['BBB5'] + assert env['CCC1'] == 'c1', env['CCC1'] + assert env['CCC2'] == ['c2'], env['CCC2'] + assert env['DDD1'] == ['a', 'b', 'c'], env['DDD1'] + + env.AppendUnique(DDD1 = 'b', delete_existing=1) + assert env['DDD1'] == ['a', 'c', 'b'], env['DDD1'] # b moves to end + env.AppendUnique(DDD1 = ['a','b'], delete_existing=1) + assert env['DDD1'] == ['c', 'a', 'b'], env['DDD1'] # a & b move to end + env.AppendUnique(DDD1 = ['e','f', 'e'], delete_existing=1) + assert env['DDD1'] == ['c', 'a', 'b', 'f', 'e'], env['DDD1'] # add last + + env['CLVar'] = CLVar([]) + env.AppendUnique(CLVar = 'bar') + result = env['CLVar'] + if sys.version[0] == '1' or sys.version[:3] == '2.0': + # Python 2.0 and before have a quirky behavior where CLVar([]) + # actually matches '' and [] due to different __coerce__() + # semantics in the UserList implementation. It isn't worth a + # lot of effort to get this corner case to work identically + # (support for Python 1.5 support will die soon anyway), + # so just treat it separately for now. + assert result == 'bar', result + else: + assert isinstance(result, CLVar), repr(result) + assert result == ['bar'], result + + env['CLVar'] = CLVar(['abc']) + env.AppendUnique(CLVar = 'bar') + result = env['CLVar'] + assert isinstance(result, CLVar), repr(result) + assert result == ['abc', 'bar'], result + + env['CLVar'] = CLVar(['bar']) + env.AppendUnique(CLVar = 'bar') + result = env['CLVar'] + assert isinstance(result, CLVar), repr(result) + assert result == ['bar'], result + + def test_Clone(self): + """Test construction environment copying + + Update the copy independently afterwards and check that + the original remains intact (that is, no dangling + references point to objects in the copied environment). + Clone the original with some construction variable + updates and check that the original remains intact + and the copy has the updated values. + """ + env1 = self.TestEnvironment(XXX = 'x', YYY = 'y') + env2 = env1.Clone() + env1copy = env1.Clone() + assert env1copy == env1copy + assert env2 == env2 + env2.Replace(YYY = 'yyy') + assert env2 == env2 + assert env1 != env2 + assert env1 == env1copy + + env3 = env1.Clone(XXX = 'x3', ZZZ = 'z3') + assert env3 == env3 + assert env3.Dictionary('XXX') == 'x3' + assert env3.Dictionary('YYY') == 'y' + assert env3.Dictionary('ZZZ') == 'z3' + assert env1 == env1copy + + # Ensure that lists and dictionaries are + # deep copied, but not instances. + class TestA: + pass + env1 = self.TestEnvironment(XXX=TestA(), YYY = [ 1, 2, 3 ], + ZZZ = { 1:2, 3:4 }) + env2=env1.Clone() + env2.Dictionary('YYY').append(4) + env2.Dictionary('ZZZ')[5] = 6 + assert env1.Dictionary('XXX') is env2.Dictionary('XXX') + assert 4 in env2.Dictionary('YYY') + assert not 4 in env1.Dictionary('YYY') + assert env2.Dictionary('ZZZ').has_key(5) + assert not env1.Dictionary('ZZZ').has_key(5) + + # + env1 = self.TestEnvironment(BUILDERS = {'b1' : 1}) + assert hasattr(env1, 'b1'), "env1.b1 was not set" + assert env1.b1.object == env1, "b1.object doesn't point to env1" + env2 = env1.Clone(BUILDERS = {'b2' : 2}) + assert env2 is env2 + assert env2 == env2 + assert hasattr(env1, 'b1'), "b1 was mistakenly cleared from env1" + assert env1.b1.object == env1, "b1.object was changed" + assert not hasattr(env2, 'b1'), "b1 was not cleared from env2" + assert hasattr(env2, 'b2'), "env2.b2 was not set" + assert env2.b2.object == env2, "b2.object doesn't point to env2" + + # Ensure that specifying new tools in a copied environment + # works. + def foo(env): env['FOO'] = 1 + def bar(env): env['BAR'] = 2 + def baz(env): env['BAZ'] = 3 + env1 = self.TestEnvironment(tools=[foo]) + env2 = env1.Clone() + env3 = env1.Clone(tools=[bar, baz]) + + assert env1.get('FOO') is 1 + assert env1.get('BAR') is None + assert env1.get('BAZ') is None + assert env2.get('FOO') is 1 + assert env2.get('BAR') is None + assert env2.get('BAZ') is None + assert env3.get('FOO') is 1 + assert env3.get('BAR') is 2 + assert env3.get('BAZ') is 3 + + # Ensure that recursive variable substitution when copying + # environments works properly. + env1 = self.TestEnvironment(CCFLAGS = '-DFOO', XYZ = '-DXYZ') + env2 = env1.Clone(CCFLAGS = '$CCFLAGS -DBAR', + XYZ = ['-DABC', 'x $XYZ y', '-DDEF']) + x = env2.get('CCFLAGS') + assert x == '-DFOO -DBAR', x + x = env2.get('XYZ') + assert x == ['-DABC', 'x -DXYZ y', '-DDEF'], x + + # Ensure that special properties of a class don't get + # lost on copying. + env1 = self.TestEnvironment(FLAGS = CLVar('flag1 flag2')) + x = env1.get('FLAGS') + assert x == ['flag1', 'flag2'], x + env2 = env1.Clone() + env2.Append(FLAGS = 'flag3 flag4') + x = env2.get('FLAGS') + assert x == ['flag1', 'flag2', 'flag3', 'flag4'], x + + # Test that the environment stores the toolpath and + # re-uses it for copies. + test = TestCmd.TestCmd(workdir = '') + + test.write('xxx.py', """\ +def exists(env): + 1 +def generate(env): + env['XXX'] = 'one' +""") + + test.write('yyy.py', """\ +def exists(env): + 1 +def generate(env): + env['YYY'] = 'two' +""") + + env = self.TestEnvironment(tools=['xxx'], toolpath=[test.workpath('')]) + assert env['XXX'] == 'one', env['XXX'] + env = env.Clone(tools=['yyy']) + assert env['YYY'] == 'two', env['YYY'] + + + # Test that + real_value = [4] + + def my_tool(env, rv=real_value): + assert env['KEY_THAT_I_WANT'] == rv[0] + env['KEY_THAT_I_WANT'] = rv[0] + 1 + + env = self.TestEnvironment() + + real_value[0] = 5 + env = env.Clone(KEY_THAT_I_WANT=5, tools=[my_tool]) + assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT'] + + real_value[0] = 6 + env = env.Clone(KEY_THAT_I_WANT=6, tools=[my_tool]) + assert env['KEY_THAT_I_WANT'] == real_value[0], env['KEY_THAT_I_WANT'] + + + def test_Copy(self): + """Test copying using the old env.Copy() method""" + env1 = self.TestEnvironment(XXX = 'x', YYY = 'y') + env2 = env1.Copy() + env1copy = env1.Copy() + assert env1copy == env1copy + assert env2 == env2 + env2.Replace(YYY = 'yyy') + assert env2 == env2 + assert env1 != env2 + assert env1 == env1copy + + def test_Detect(self): + """Test Detect()ing tools""" + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub1', 'sub2') + sub1 = test.workpath('sub1') + sub2 = test.workpath('sub2') + + if sys.platform == 'win32': + test.write(['sub1', 'xxx'], "sub1/xxx\n") + test.write(['sub2', 'xxx'], "sub2/xxx\n") + + env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] }) + + x = env.Detect('xxx.exe') + assert x is None, x + + test.write(['sub2', 'xxx.exe'], "sub2/xxx.exe\n") + + env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] }) + + x = env.Detect('xxx.exe') + assert x == 'xxx.exe', x + + test.write(['sub1', 'xxx.exe'], "sub1/xxx.exe\n") + + x = env.Detect('xxx.exe') + assert x == 'xxx.exe', x + + else: + test.write(['sub1', 'xxx.exe'], "sub1/xxx.exe\n") + test.write(['sub2', 'xxx.exe'], "sub2/xxx.exe\n") + + env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] }) + + x = env.Detect('xxx.exe') + assert x is None, x + + sub2_xxx_exe = test.workpath('sub2', 'xxx.exe') + os.chmod(sub2_xxx_exe, 0755) + + env = self.TestEnvironment(ENV = { 'PATH' : [sub1, sub2] }) + + x = env.Detect('xxx.exe') + assert x == 'xxx.exe', x + + sub1_xxx_exe = test.workpath('sub1', 'xxx.exe') + os.chmod(sub1_xxx_exe, 0755) + + x = env.Detect('xxx.exe') + assert x == 'xxx.exe', x + + env = self.TestEnvironment(ENV = { 'PATH' : [] }) + x = env.Detect('xxx.exe') + assert x is None, x + + def test_Dictionary(self): + """Test retrieval of known construction variables + + Fetch them from the Dictionary and check for well-known + defaults that get inserted. + """ + env = self.TestEnvironment(XXX = 'x', YYY = 'y', ZZZ = 'z') + assert env.Dictionary('XXX') == 'x' + assert env.Dictionary('YYY') == 'y' + assert env.Dictionary('XXX', 'ZZZ') == ['x', 'z'] + xxx, zzz = env.Dictionary('XXX', 'ZZZ') + assert xxx == 'x' + assert zzz == 'z' + assert env.Dictionary().has_key('BUILDERS') + assert env.Dictionary().has_key('CC') + assert env.Dictionary().has_key('CCFLAGS') + assert env.Dictionary().has_key('ENV') + + assert env['XXX'] == 'x' + env['XXX'] = 'foo' + assert env.Dictionary('XXX') == 'foo' + del env['XXX'] + assert not env.Dictionary().has_key('XXX') + + def test_FindIxes(self): + "Test FindIxes()" + env = self.TestEnvironment(LIBPREFIX='lib', + LIBSUFFIX='.a', + SHLIBPREFIX='lib', + SHLIBSUFFIX='.so', + PREFIX='pre', + SUFFIX='post') + + paths = [os.path.join('dir', 'libfoo.a'), + os.path.join('dir', 'libfoo.so')] + + assert paths[0] == env.FindIxes(paths, 'LIBPREFIX', 'LIBSUFFIX') + assert paths[1] == env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX') + assert None is env.FindIxes(paths, 'PREFIX', 'POST') + + paths = ['libfoo.a', 'prefoopost'] + + assert paths[0] == env.FindIxes(paths, 'LIBPREFIX', 'LIBSUFFIX') + assert None is env.FindIxes(paths, 'SHLIBPREFIX', 'SHLIBSUFFIX') + assert paths[1] == env.FindIxes(paths, 'PREFIX', 'SUFFIX') + + def test_ParseConfig(self): + """Test the ParseConfig() method""" + env = self.TestEnvironment(COMMAND='command', + ASFLAGS='assembler', + CCFLAGS=[''], + CPPDEFINES=[], + CPPFLAGS=[''], + CPPPATH='string', + FRAMEWORKPATH=[], + FRAMEWORKS=[], + LIBPATH=['list'], + LIBS='', + LINKFLAGS=[''], + RPATH=[]) + + orig_backtick = env.backtick + class my_backtick: + def __init__(self, save_command, output): + self.save_command = save_command + self.output = output + def __call__(self, command): + self.save_command.append(command) + return self.output + + try: + save_command = [] + env.backtick = my_backtick(save_command, + "-I/usr/include/fum -I bar -X\n" + \ + "-L/usr/fax -L foo -lxxx -l yyy " + \ + "-Wa,-as -Wl,-link " + \ + "-Wl,-rpath=rpath1 " + \ + "-Wl,-R,rpath2 " + \ + "-Wl,-Rrpath3 " + \ + "-Wp,-cpp abc " + \ + "-framework Carbon " + \ + "-frameworkdir=fwd1 " + \ + "-Ffwd2 " + \ + "-F fwd3 " + \ + "-pthread " + \ + "-mno-cygwin -mwindows " + \ + "-arch i386 -isysroot /tmp +DD64 " + \ + "-DFOO -DBAR=value") + env.ParseConfig("fake $COMMAND") + assert save_command == ['fake command'], save_command + assert env['ASFLAGS'] == ['assembler', '-as'], env['ASFLAGS'] + assert env['CCFLAGS'] == ['', '-X', '-Wa,-as', + '-pthread', '-mno-cygwin', + ('-arch', 'i386'), ('-isysroot', '/tmp'), + '+DD64'], env['CCFLAGS'] + assert env['CPPDEFINES'] == ['FOO', ['BAR', 'value']], env['CPPDEFINES'] + assert env['CPPFLAGS'] == ['', '-Wp,-cpp'], env['CPPFLAGS'] + assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar'], env['CPPPATH'] + assert env['FRAMEWORKPATH'] == ['fwd1', 'fwd2', 'fwd3'], env['FRAMEWORKPATH'] + assert env['FRAMEWORKS'] == ['Carbon'], env['FRAMEWORKS'] + assert env['LIBPATH'] == ['list', '/usr/fax', 'foo'], env['LIBPATH'] + assert env['LIBS'] == ['xxx', 'yyy', env.File('abc')], env['LIBS'] + assert env['LINKFLAGS'] == ['', '-Wl,-link', '-pthread', + '-mno-cygwin', '-mwindows', + ('-arch', 'i386'), + ('-isysroot', '/tmp'), + '+DD64'], env['LINKFLAGS'] + assert env['RPATH'] == ['rpath1', 'rpath2', 'rpath3'], env['RPATH'] + + env.backtick = my_backtick([], "-Ibar") + env.ParseConfig("fake2") + assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar'], env['CPPPATH'] + env.ParseConfig("fake2", unique=0) + assert env['CPPPATH'] == ['string', '/usr/include/fum', 'bar', 'bar'], env['CPPPATH'] + finally: + env.backtick = orig_backtick + + def test_ParseDepends(self): + """Test the ParseDepends() method""" + test = TestCmd.TestCmd(workdir = '') + + test.write('single', """ +#file: dependency + +f0: \ + d1 \ + d2 \ + d3 \ + +""") + + test.write('multiple', """ +f1: foo +f2 f3: bar +f4: abc def +#file: dependency +f5: \ + ghi \ + jkl \ + mno \ +""") + + env = self.TestEnvironment(SINGLE = test.workpath('single')) + + tlist = [] + dlist = [] + def my_depends(target, dependency, tlist=tlist, dlist=dlist): + tlist.extend(target) + dlist.extend(dependency) + + env.Depends = my_depends + + env.ParseDepends(test.workpath('does_not_exist')) + + exc_caught = None + try: + env.ParseDepends(test.workpath('does_not_exist'), must_exist=1) + except IOError: + exc_caught = 1 + assert exc_caught, "did not catch expected IOError" + + del tlist[:] + del dlist[:] + + env.ParseDepends('$SINGLE', only_one=1) + t = map(str, tlist) + d = map(str, dlist) + assert t == ['f0'], t + assert d == ['d1', 'd2', 'd3'], d + + del tlist[:] + del dlist[:] + + env.ParseDepends(test.workpath('multiple')) + t = map(str, tlist) + d = map(str, dlist) + assert t == ['f1', 'f2', 'f3', 'f4', 'f5'], t + assert d == ['foo', 'bar', 'abc', 'def', 'ghi', 'jkl', 'mno'], d + + exc_caught = None + try: + env.ParseDepends(test.workpath('multiple'), only_one=1) + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + + def test_Platform(self): + """Test the Platform() method""" + env = self.TestEnvironment(WIN32='win32', NONE='no-such-platform') + + exc_caught = None + try: + env.Platform('does_not_exist') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + + exc_caught = None + try: + env.Platform('$NONE') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + + env.Platform('posix') + assert env['OBJSUFFIX'] == '.o', env['OBJSUFFIX'] + + env.Platform('$WIN32') + assert env['OBJSUFFIX'] == '.obj', env['OBJSUFFIX'] + + def test_Prepend(self): + """Test prepending to construction variables in an Environment + """ + import UserDict + UD = UserDict.UserDict + import UserList + UL = UserList.UserList + + cases = [ + 'a1', 'A1', 'A1a1', + 'a2', ['A2'], ['A2', 'a2'], + 'a3', UL(['A3']), UL(['A3', 'a', '3']), + 'a4', '', 'a4', + 'a5', [], ['a5'], + 'a6', UL([]), UL(['a', '6']), + 'a7', [''], ['', 'a7'], + 'a8', UL(['']), UL(['', 'a', '8']), + + ['e1'], 'E1', ['E1', 'e1'], + ['e2'], ['E2'], ['E2', 'e2'], + ['e3'], UL(['E3']), UL(['E3', 'e3']), + ['e4'], '', ['e4'], + ['e5'], [], ['e5'], + ['e6'], UL([]), UL(['e6']), + ['e7'], [''], ['', 'e7'], + ['e8'], UL(['']), UL(['', 'e8']), + + UL(['i1']), 'I1', UL(['I', '1', 'i1']), + UL(['i2']), ['I2'], UL(['I2', 'i2']), + UL(['i3']), UL(['I3']), UL(['I3', 'i3']), + UL(['i4']), '', UL(['i4']), + UL(['i5']), [], UL(['i5']), + UL(['i6']), UL([]), UL(['i6']), + UL(['i7']), [''], UL(['', 'i7']), + UL(['i8']), UL(['']), UL(['', 'i8']), + + {'d1':1}, 'D1', {'d1':1, 'D1':None}, + {'d2':1}, ['D2'], {'d2':1, 'D2':None}, + {'d3':1}, UL(['D3']), {'d3':1, 'D3':None}, + {'d4':1}, {'D4':1}, {'d4':1, 'D4':1}, + {'d5':1}, UD({'D5':1}), UD({'d5':1, 'D5':1}), + + UD({'u1':1}), 'U1', UD({'u1':1, 'U1':None}), + UD({'u2':1}), ['U2'], UD({'u2':1, 'U2':None}), + UD({'u3':1}), UL(['U3']), UD({'u3':1, 'U3':None}), + UD({'u4':1}), {'U4':1}, UD({'u4':1, 'U4':1}), + UD({'u5':1}), UD({'U5':1}), UD({'u5':1, 'U5':1}), + + '', 'M1', 'M1', + '', ['M2'], ['M2'], + '', UL(['M3']), UL(['M3']), + '', '', '', + '', [], [], + '', UL([]), UL([]), + '', [''], [''], + '', UL(['']), UL(['']), + + [], 'N1', ['N1'], + [], ['N2'], ['N2'], + [], UL(['N3']), UL(['N3']), + [], '', [], + [], [], [], + [], UL([]), UL([]), + [], [''], [''], + [], UL(['']), UL(['']), + + UL([]), 'O1', UL(['O', '1']), + UL([]), ['O2'], UL(['O2']), + UL([]), UL(['O3']), UL(['O3']), + UL([]), '', UL([]), + UL([]), [], UL([]), + UL([]), UL([]), UL([]), + UL([]), [''], UL(['']), + UL([]), UL(['']), UL(['']), + + [''], 'P1', ['P1', ''], + [''], ['P2'], ['P2', ''], + [''], UL(['P3']), UL(['P3', '']), + [''], '', [''], + [''], [], [''], + [''], UL([]), UL(['']), + [''], [''], ['', ''], + [''], UL(['']), UL(['', '']), + + UL(['']), 'Q1', UL(['Q', '1', '']), + UL(['']), ['Q2'], UL(['Q2', '']), + UL(['']), UL(['Q3']), UL(['Q3', '']), + UL(['']), '', UL(['']), + UL(['']), [], UL(['']), + UL(['']), UL([]), UL(['']), + UL(['']), [''], UL(['', '']), + UL(['']), UL(['']), UL(['', '']), + ] + + env = Environment() + failed = 0 + while cases: + input, prepend, expect = cases[:3] + env['XXX'] = copy.copy(input) + try: + env.Prepend(XXX = prepend) + except Exception, e: + if failed == 0: print + print " %s Prepend %s exception: %s" % \ + (repr(input), repr(prepend), e) + failed = failed + 1 + else: + result = env['XXX'] + if result != expect: + if failed == 0: print + print " %s Prepend %s => %s did not match %s" % \ + (repr(input), repr(prepend), repr(result), repr(expect)) + failed = failed + 1 + del cases[:3] + assert failed == 0, "%d Prepend() cases failed" % failed + + env['UL'] = UL(['foo']) + env.Prepend(UL = 'bar') + result = env['UL'] + assert isinstance(result, UL), repr(result) + assert result == ['b', 'a', 'r', 'foo'], result + + env['CLVar'] = CLVar(['foo']) + env.Prepend(CLVar = 'bar') + result = env['CLVar'] + assert isinstance(result, CLVar), repr(result) + assert result == ['bar', 'foo'], result + + env3 = self.TestEnvironment(X = {'x1' : 7}) + env3.Prepend(X = {'x1' : 8, 'x2' : 9}, Y = {'y1' : 10}) + assert env3['X'] == {'x1': 8, 'x2' : 9}, env3['X'] + assert env3['Y'] == {'y1': 10}, env3['Y'] + + env4 = self.TestEnvironment(BUILDERS = {'z1' : 11}) + env4.Prepend(BUILDERS = {'z2' : 12}) + assert env4['BUILDERS'] == {'z1' : 11, 'z2' : 12}, env4['BUILDERS'] + assert hasattr(env4, 'z1') + assert hasattr(env4, 'z2') + + def test_PrependENVPath(self): + """Test prepending to an ENV path.""" + env1 = self.TestEnvironment(ENV = {'PATH': r'C:\dir\num\one;C:\dir\num\two'}, + MYENV = {'MYPATH': r'C:\mydir\num\one;C:\mydir\num\two'}) + # have to include the pathsep here so that the test will work on UNIX too. + env1.PrependENVPath('PATH',r'C:\dir\num\two',sep = ';') + env1.PrependENVPath('PATH',r'C:\dir\num\three',sep = ';') + env1.PrependENVPath('MYPATH',r'C:\mydir\num\three','MYENV',sep = ';') + env1.PrependENVPath('MYPATH',r'C:\mydir\num\one','MYENV',sep = ';') + # this should do nothing since delete_existing is 0 + env1.PrependENVPath('MYPATH',r'C:\mydir\num\three','MYENV', sep = ';', delete_existing=0) + assert(env1['ENV']['PATH'] == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one') + assert(env1['MYENV']['MYPATH'] == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two') + + test = TestCmd.TestCmd(workdir = '') + test.subdir('sub1', 'sub2') + p=env1['ENV']['PATH'] + env1.PrependENVPath('PATH','#sub1', sep = ';') + env1.PrependENVPath('PATH',env1.fs.Dir('sub2'), sep = ';') + assert env1['ENV']['PATH'] == 'sub2;sub1;' + p, env1['ENV']['PATH'] + + def test_PrependUnique(self): + """Test prepending unique values to construction variables + + This strips values that are already present when lists are + involved.""" + env = self.TestEnvironment(AAA1 = 'a1', + AAA2 = 'a2', + AAA3 = 'a3', + AAA4 = 'a4', + AAA5 = 'a5', + BBB1 = ['b1'], + BBB2 = ['b2'], + BBB3 = ['b3'], + BBB4 = ['b4'], + BBB5 = ['b5'], + CCC1 = '', + CCC2 = '', + DDD1 = ['a', 'b', 'c']) + env.PrependUnique(AAA1 = 'a1', + AAA2 = ['a2'], + AAA3 = ['a3', 'b', 'c', 'b', 'a3'], # ignore dups + AAA4 = 'a4.new', + AAA5 = ['a5.new'], + BBB1 = 'b1', + BBB2 = ['b2'], + BBB3 = ['b3', 'b', 'c', 'b3'], + BBB4 = 'b4.new', + BBB5 = ['b5.new'], + CCC1 = 'c1', + CCC2 = ['c2'], + DDD1 = 'b') + assert env['AAA1'] == 'a1a1', env['AAA1'] + assert env['AAA2'] == ['a2'], env['AAA2'] + assert env['AAA3'] == ['c', 'b', 'a3'], env['AAA3'] + assert env['AAA4'] == 'a4.newa4', env['AAA4'] + assert env['AAA5'] == ['a5.new', 'a5'], env['AAA5'] + assert env['BBB1'] == ['b1'], env['BBB1'] + assert env['BBB2'] == ['b2'], env['BBB2'] + assert env['BBB3'] == ['b', 'c', 'b3'], env['BBB3'] + assert env['BBB4'] == ['b4.new', 'b4'], env['BBB4'] + assert env['BBB5'] == ['b5.new', 'b5'], env['BBB5'] + assert env['CCC1'] == 'c1', env['CCC1'] + assert env['CCC2'] == ['c2'], env['CCC2'] + assert env['DDD1'] == ['a', 'b', 'c'], env['DDD1'] + + env.PrependUnique(DDD1 = 'b', delete_existing=1) + assert env['DDD1'] == ['b', 'a', 'c'], env['DDD1'] # b moves to front + env.PrependUnique(DDD1 = ['a','c'], delete_existing=1) + assert env['DDD1'] == ['a', 'c', 'b'], env['DDD1'] # a & c move to front + env.PrependUnique(DDD1 = ['d','e','d'], delete_existing=1) + assert env['DDD1'] == ['d', 'e', 'a', 'c', 'b'], env['DDD1'] + + + env['CLVar'] = CLVar([]) + env.PrependUnique(CLVar = 'bar') + result = env['CLVar'] + if sys.version[0] == '1' or sys.version[:3] == '2.0': + # Python 2.0 and before have a quirky behavior where CLVar([]) + # actually matches '' and [] due to different __coerce__() + # semantics in the UserList implementation. It isn't worth a + # lot of effort to get this corner case to work identically + # (support for Python 1.5 support will die soon anyway), + # so just treat it separately for now. + assert result == 'bar', result + else: + assert isinstance(result, CLVar), repr(result) + assert result == ['bar'], result + + env['CLVar'] = CLVar(['abc']) + env.PrependUnique(CLVar = 'bar') + result = env['CLVar'] + assert isinstance(result, CLVar), repr(result) + assert result == ['bar', 'abc'], result + + env['CLVar'] = CLVar(['bar']) + env.PrependUnique(CLVar = 'bar') + result = env['CLVar'] + assert isinstance(result, CLVar), repr(result) + assert result == ['bar'], result + + def test_Replace(self): + """Test replacing construction variables in an Environment + + After creation of the Environment, of course. + """ + env1 = self.TestEnvironment(AAA = 'a', BBB = 'b') + env1.Replace(BBB = 'bbb', CCC = 'ccc') + + env2 = self.TestEnvironment(AAA = 'a', BBB = 'bbb', CCC = 'ccc') + assert env1 == env2, diff_env(env1, env2) + + env3 = self.TestEnvironment(BUILDERS = {'b1' : 1}) + assert hasattr(env3, 'b1'), "b1 was not set" + env3.Replace(BUILDERS = {'b2' : 2}) + assert not hasattr(env3, 'b1'), "b1 was not cleared" + assert hasattr(env3, 'b2'), "b2 was not set" + + def test_ReplaceIxes(self): + "Test ReplaceIxes()" + env = self.TestEnvironment(LIBPREFIX='lib', + LIBSUFFIX='.a', + SHLIBPREFIX='lib', + SHLIBSUFFIX='.so', + PREFIX='pre', + SUFFIX='post') + + assert 'libfoo.a' == env.ReplaceIxes('libfoo.so', + 'SHLIBPREFIX', 'SHLIBSUFFIX', + 'LIBPREFIX', 'LIBSUFFIX') + + assert os.path.join('dir', 'libfoo.a') == env.ReplaceIxes(os.path.join('dir', 'libfoo.so'), + 'SHLIBPREFIX', 'SHLIBSUFFIX', + 'LIBPREFIX', 'LIBSUFFIX') + + assert 'libfoo.a' == env.ReplaceIxes('prefoopost', + 'PREFIX', 'SUFFIX', + 'LIBPREFIX', 'LIBSUFFIX') + + def test_SetDefault(self): + """Test the SetDefault method""" + env = self.TestEnvironment(tools = []) + env.SetDefault(V1 = 1) + env.SetDefault(V1 = 2) + assert env['V1'] == 1 + env['V2'] = 2 + env.SetDefault(V2 = 1) + assert env['V2'] == 2 + + def test_Tool(self): + """Test the Tool() method""" + env = self.TestEnvironment(LINK='link', NONE='no-such-tool') + + exc_caught = None + try: + env.Tool('does_not_exist') + except SCons.Errors.EnvironmentError: + exc_caught = 1 + assert exc_caught, "did not catch expected EnvironmentError" + + exc_caught = None + try: + env.Tool('$NONE') + except SCons.Errors.EnvironmentError: + exc_caught = 1 + assert exc_caught, "did not catch expected EnvironmentError" + + # Use a non-existent toolpath directory just to make sure we + # can call Tool() with the keyword argument. + env.Tool('cc', toolpath=['/no/such/directory']) + assert env['CC'] == 'cc', env['CC'] + + env.Tool('$LINK') + assert env['LINK'] == '$SMARTLINK', env['LINK'] + + # Test that the environment stores the toolpath and + # re-uses it for later calls. + test = TestCmd.TestCmd(workdir = '') + + test.write('xxx.py', """\ +def exists(env): + 1 +def generate(env): + env['XXX'] = 'one' +""") + + test.write('yyy.py', """\ +def exists(env): + 1 +def generate(env): + env['YYY'] = 'two' +""") + + env = self.TestEnvironment(tools=['xxx'], toolpath=[test.workpath('')]) + assert env['XXX'] == 'one', env['XXX'] + env.Tool('yyy') + assert env['YYY'] == 'two', env['YYY'] + + def test_WhereIs(self): + """Test the WhereIs() method""" + test = TestCmd.TestCmd(workdir = '') + + sub1_xxx_exe = test.workpath('sub1', 'xxx.exe') + sub2_xxx_exe = test.workpath('sub2', 'xxx.exe') + sub3_xxx_exe = test.workpath('sub3', 'xxx.exe') + sub4_xxx_exe = test.workpath('sub4', 'xxx.exe') + + test.subdir('subdir', 'sub1', 'sub2', 'sub3', 'sub4') + + if sys.platform != 'win32': + test.write(sub1_xxx_exe, "\n") + + os.mkdir(sub2_xxx_exe) + + test.write(sub3_xxx_exe, "\n") + os.chmod(sub3_xxx_exe, 0777) + + test.write(sub4_xxx_exe, "\n") + os.chmod(sub4_xxx_exe, 0777) + + env_path = os.environ['PATH'] + + pathdirs_1234 = [ test.workpath('sub1'), + test.workpath('sub2'), + test.workpath('sub3'), + test.workpath('sub4'), + ] + string.split(env_path, os.pathsep) + + pathdirs_1243 = [ test.workpath('sub1'), + test.workpath('sub2'), + test.workpath('sub4'), + test.workpath('sub3'), + ] + string.split(env_path, os.pathsep) + + path = string.join(pathdirs_1234, os.pathsep) + env = self.TestEnvironment(ENV = {'PATH' : path}) + wi = env.WhereIs('xxx.exe') + assert wi == test.workpath(sub3_xxx_exe), wi + wi = env.WhereIs('xxx.exe', pathdirs_1243) + assert wi == test.workpath(sub4_xxx_exe), wi + wi = env.WhereIs('xxx.exe', string.join(pathdirs_1243, os.pathsep)) + assert wi == test.workpath(sub4_xxx_exe), wi + + wi = env.WhereIs('xxx.exe', reject = sub3_xxx_exe) + assert wi == test.workpath(sub4_xxx_exe), wi + wi = env.WhereIs('xxx.exe', pathdirs_1243, reject = sub3_xxx_exe) + assert wi == test.workpath(sub4_xxx_exe), wi + + path = string.join(pathdirs_1243, os.pathsep) + env = self.TestEnvironment(ENV = {'PATH' : path}) + wi = env.WhereIs('xxx.exe') + assert wi == test.workpath(sub4_xxx_exe), wi + wi = env.WhereIs('xxx.exe', pathdirs_1234) + assert wi == test.workpath(sub3_xxx_exe), wi + wi = env.WhereIs('xxx.exe', string.join(pathdirs_1234, os.pathsep)) + assert wi == test.workpath(sub3_xxx_exe), wi + + if sys.platform == 'win32': + wi = env.WhereIs('xxx', pathext = '') + assert wi is None, wi + + wi = env.WhereIs('xxx', pathext = '.exe') + assert wi == test.workpath(sub4_xxx_exe), wi + + wi = env.WhereIs('xxx', path = pathdirs_1234, pathext = '.BAT;.EXE') + assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi + + # Test that we return a normalized path even when + # the path contains forward slashes. + forward_slash = test.workpath('') + '/sub3' + wi = env.WhereIs('xxx', path = forward_slash, pathext = '.EXE') + assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi + + + + def test_Action(self): + """Test the Action() method""" + import SCons.Action + + env = self.TestEnvironment(FOO = 'xyzzy') + + a = env.Action('foo') + assert a, a + assert a.__class__ is SCons.Action.CommandAction, a.__class__ + + a = env.Action('$FOO') + assert a, a + assert a.__class__ is SCons.Action.CommandAction, a.__class__ + + a = env.Action('$$FOO') + assert a, a + assert a.__class__ is SCons.Action.LazyAction, a.__class__ + + a = env.Action(['$FOO', 'foo']) + assert a, a + assert a.__class__ is SCons.Action.ListAction, a.__class__ + + def func(arg): + pass + a = env.Action(func) + assert a, a + assert a.__class__ is SCons.Action.FunctionAction, a.__class__ + + def test_AddPostAction(self): + """Test the AddPostAction() method""" + env = self.TestEnvironment(FOO='fff', BAR='bbb') + + n = env.AddPostAction('$FOO', lambda x: x) + assert str(n[0]) == 'fff', n[0] + + n = env.AddPostAction(['ggg', '$BAR'], lambda x: x) + assert str(n[0]) == 'ggg', n[0] + assert str(n[1]) == 'bbb', n[1] + + def test_AddPreAction(self): + """Test the AddPreAction() method""" + env = self.TestEnvironment(FOO='fff', BAR='bbb') + + n = env.AddPreAction('$FOO', lambda x: x) + assert str(n[0]) == 'fff', n[0] + + n = env.AddPreAction(['ggg', '$BAR'], lambda x: x) + assert str(n[0]) == 'ggg', n[0] + assert str(n[1]) == 'bbb', n[1] + + def test_Alias(self): + """Test the Alias() method""" + env = self.TestEnvironment(FOO='kkk', BAR='lll', EA='export_alias') + + tgt = env.Alias('new_alias')[0] + assert str(tgt) == 'new_alias', tgt + assert tgt.sources == [], tgt.sources + assert not hasattr(tgt, 'builder'), tgt.builder + + tgt = env.Alias('None_alias', None)[0] + assert str(tgt) == 'None_alias', tgt + assert tgt.sources == [], tgt.sources + + tgt = env.Alias('empty_list', [])[0] + assert str(tgt) == 'empty_list', tgt + assert tgt.sources == [], tgt.sources + + tgt = env.Alias('export_alias', [ 'asrc1', '$FOO' ])[0] + assert str(tgt) == 'export_alias', tgt + assert len(tgt.sources) == 2, map(str, tgt.sources) + assert str(tgt.sources[0]) == 'asrc1', map(str, tgt.sources) + assert str(tgt.sources[1]) == 'kkk', map(str, tgt.sources) + + n = env.Alias(tgt, source = ['$BAR', 'asrc4'])[0] + assert n is tgt, n + assert len(tgt.sources) == 4, map(str, tgt.sources) + assert str(tgt.sources[2]) == 'lll', map(str, tgt.sources) + assert str(tgt.sources[3]) == 'asrc4', map(str, tgt.sources) + + n = env.Alias('$EA', 'asrc5')[0] + assert n is tgt, n + assert len(tgt.sources) == 5, map(str, tgt.sources) + assert str(tgt.sources[4]) == 'asrc5', map(str, tgt.sources) + + t1, t2 = env.Alias(['t1', 't2'], ['asrc6', 'asrc7']) + assert str(t1) == 't1', t1 + assert str(t2) == 't2', t2 + assert len(t1.sources) == 2, map(str, t1.sources) + assert str(t1.sources[0]) == 'asrc6', map(str, t1.sources) + assert str(t1.sources[1]) == 'asrc7', map(str, t1.sources) + assert len(t2.sources) == 2, map(str, t2.sources) + assert str(t2.sources[0]) == 'asrc6', map(str, t2.sources) + assert str(t2.sources[1]) == 'asrc7', map(str, t2.sources) + + tgt = env.Alias('add', 's1') + tgt = env.Alias('add', 's2')[0] + s = map(str, tgt.sources) + assert s == ['s1', 's2'], s + tgt = env.Alias(tgt, 's3')[0] + s = map(str, tgt.sources) + assert s == ['s1', 's2', 's3'], s + + tgt = env.Alias('act', None, "action1")[0] + s = str(tgt.builder.action) + assert s == "action1", s + tgt = env.Alias('act', None, "action2")[0] + s = str(tgt.builder.action) + assert s == "action1\naction2", s + tgt = env.Alias(tgt, None, "action3")[0] + s = str(tgt.builder.action) + assert s == "action1\naction2\naction3", s + + def test_AlwaysBuild(self): + """Test the AlwaysBuild() method""" + env = self.TestEnvironment(FOO='fff', BAR='bbb') + t = env.AlwaysBuild('a', 'b$FOO', ['c', 'd'], '$BAR', + env.fs.Dir('dir'), env.fs.File('file')) + assert t[0].__class__.__name__ == 'Entry' + assert t[0].path == 'a' + assert t[0].always_build + assert t[1].__class__.__name__ == 'Entry' + assert t[1].path == 'bfff' + assert t[1].always_build + assert t[2].__class__.__name__ == 'Entry' + assert t[2].path == 'c' + assert t[2].always_build + assert t[3].__class__.__name__ == 'Entry' + assert t[3].path == 'd' + assert t[3].always_build + assert t[4].__class__.__name__ == 'Entry' + assert t[4].path == 'bbb' + assert t[4].always_build + assert t[5].__class__.__name__ == 'Dir' + assert t[5].path == 'dir' + assert t[5].always_build + assert t[6].__class__.__name__ == 'File' + assert t[6].path == 'file' + assert t[6].always_build + + def test_VariantDir(self): + """Test the VariantDir() method""" + class MyFS: + def Dir(self, name): + return name + def VariantDir(self, variant_dir, src_dir, duplicate): + self.variant_dir = variant_dir + self.src_dir = src_dir + self.duplicate = duplicate + + env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb') + env.fs = MyFS() + + env.VariantDir('build', 'src') + assert env.fs.variant_dir == 'build', env.fs.variant_dir + assert env.fs.src_dir == 'src', env.fs.src_dir + assert env.fs.duplicate == 1, env.fs.duplicate + + env.VariantDir('build${FOO}', '${BAR}src', 0) + assert env.fs.variant_dir == 'buildfff', env.fs.variant_dir + assert env.fs.src_dir == 'bbbsrc', env.fs.src_dir + assert env.fs.duplicate == 0, env.fs.duplicate + + def test_Builder(self): + """Test the Builder() method""" + env = self.TestEnvironment(FOO = 'xyzzy') + + b = env.Builder(action = 'foo') + assert b is not None, b + + b = env.Builder(action = '$FOO') + assert b is not None, b + + b = env.Builder(action = ['$FOO', 'foo']) + assert b is not None, b + + def func(arg): + pass + b = env.Builder(action = func) + assert b is not None, b + b = env.Builder(generator = func) + assert b is not None, b + + def test_CacheDir(self): + """Test the CacheDir() method""" + env = self.TestEnvironment(CD = 'CacheDir') + + env.CacheDir('foo') + assert env._CacheDir_path == 'foo', env._CacheDir_path + + env.CacheDir('$CD') + assert env._CacheDir_path == 'CacheDir', env._CacheDir_path + + def test_Clean(self): + """Test the Clean() method""" + env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb') + + CT = SCons.Environment.CleanTargets + + foo = env.arg2nodes('foo')[0] + fff = env.arg2nodes('fff')[0] + + t = env.Clean('foo', 'aaa') + l = map(str, CT[foo]) + assert l == ['aaa'], l + + t = env.Clean(foo, ['$BAR', 'ccc']) + l = map(str, CT[foo]) + assert l == ['aaa', 'bbb', 'ccc'], l + + eee = env.arg2nodes('eee')[0] + + t = env.Clean('$FOO', 'ddd') + l = map(str, CT[fff]) + assert l == ['ddd'], l + t = env.Clean(fff, [eee, 'fff']) + l = map(str, CT[fff]) + assert l == ['ddd', 'eee', 'fff'], l + + def test_Command(self): + """Test the Command() method.""" + env = Environment() + t = env.Command(target='foo.out', source=['foo1.in', 'foo2.in'], + action='buildfoo $target $source')[0] + assert t.builder is not None + assert t.builder.action.__class__.__name__ == 'CommandAction' + assert t.builder.action.cmd_list == 'buildfoo $target $source' + assert 'foo1.in' in map(lambda x: x.path, t.sources) + assert 'foo2.in' in map(lambda x: x.path, t.sources) + + sub = env.fs.Dir('sub') + t = env.Command(target='bar.out', source='sub', + action='buildbar $target $source')[0] + assert 'sub' in map(lambda x: x.path, t.sources) + + def testFunc(env, target, source): + assert str(target[0]) == 'foo.out' + assert 'foo1.in' in map(str, source) and 'foo2.in' in map(str, source), map(str, source) + return 0 + t = env.Command(target='foo.out', source=['foo1.in','foo2.in'], + action=testFunc)[0] + assert t.builder is not None + assert t.builder.action.__class__.__name__ == 'FunctionAction' + t.build() + assert 'foo1.in' in map(lambda x: x.path, t.sources) + assert 'foo2.in' in map(lambda x: x.path, t.sources) + + x = [] + def test2(baz, x=x): + x.append(baz) + env = self.TestEnvironment(TEST2 = test2) + t = env.Command(target='baz.out', source='baz.in', + action='${TEST2(XYZ)}', + XYZ='magic word')[0] + assert t.builder is not None + t.build() + assert x[0] == 'magic word', x + + t = env.Command(target='${X}.out', source='${X}.in', + action = 'foo', + X = 'xxx')[0] + assert str(t) == 'xxx.out', str(t) + assert 'xxx.in' in map(lambda x: x.path, t.sources) + + env = self.TestEnvironment(source_scanner = 'should_not_find_this') + t = env.Command(target='file.out', source='file.in', + action = 'foo', + source_scanner = 'fake')[0] + assert t.builder.source_scanner == 'fake', t.builder.source_scanner + + def test_Configure(self): + """Test the Configure() method""" + # Configure() will write to a local temporary file. + test = TestCmd.TestCmd(workdir = '') + save = os.getcwd() + + try: + os.chdir(test.workpath()) + + env = self.TestEnvironment(FOO = 'xyzzy') + + def func(arg): + pass + + c = env.Configure() + assert c is not None, c + c.Finish() + + c = env.Configure(custom_tests = {'foo' : func, '$FOO' : func}) + assert c is not None, c + assert hasattr(c, 'foo') + assert hasattr(c, 'xyzzy') + c.Finish() + finally: + os.chdir(save) + + def test_Depends(self): + """Test the explicit Depends method.""" + env = self.TestEnvironment(FOO = 'xxx', BAR='yyy') + env.Dir('dir1') + env.Dir('dir2') + env.File('xxx.py') + env.File('yyy.py') + t = env.Depends(target='EnvironmentTest.py', + dependency='Environment.py')[0] + assert t.__class__.__name__ == 'Entry', t.__class__.__name__ + assert t.path == 'EnvironmentTest.py' + assert len(t.depends) == 1 + d = t.depends[0] + assert d.__class__.__name__ == 'Entry', d.__class__.__name__ + assert d.path == 'Environment.py' + + t = env.Depends(target='${FOO}.py', dependency='${BAR}.py')[0] + assert t.__class__.__name__ == 'File', t.__class__.__name__ + assert t.path == 'xxx.py' + assert len(t.depends) == 1 + d = t.depends[0] + assert d.__class__.__name__ == 'File', d.__class__.__name__ + assert d.path == 'yyy.py' + + t = env.Depends(target='dir1', dependency='dir2')[0] + assert t.__class__.__name__ == 'Dir', t.__class__.__name__ + assert t.path == 'dir1' + assert len(t.depends) == 1 + d = t.depends[0] + assert d.__class__.__name__ == 'Dir', d.__class__.__name__ + assert d.path == 'dir2' + + def test_Dir(self): + """Test the Dir() method""" + class MyFS: + def Dir(self, name): + return 'Dir(%s)' % name + + env = self.TestEnvironment(FOO = 'foodir', BAR = 'bardir') + env.fs = MyFS() + + d = env.Dir('d') + assert d == 'Dir(d)', d + + d = env.Dir('$FOO') + assert d == 'Dir(foodir)', d + + d = env.Dir('${BAR}_$BAR') + assert d == 'Dir(bardir_bardir)', d + + d = env.Dir(['dir1']) + assert d == ['Dir(dir1)'], d + + d = env.Dir(['dir1', 'dir2']) + assert d == ['Dir(dir1)', 'Dir(dir2)'], d + + def test_NoClean(self): + """Test the NoClean() method""" + env = self.TestEnvironment(FOO='ggg', BAR='hhh') + env.Dir('p_hhhb') + env.File('p_d') + t = env.NoClean('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO') + + assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__ + assert t[0].path == 'p_a' + assert t[0].noclean + assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__ + assert t[1].path == 'p_hhhb' + assert t[1].noclean + assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__ + assert t[2].path == 'p_c' + assert t[2].noclean + assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__ + assert t[3].path == 'p_d' + assert t[3].noclean + assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__ + assert t[4].path == 'p_ggg' + assert t[4].noclean + + def test_Dump(self): + """Test the Dump() method""" + + env = self.TestEnvironment(FOO = 'foo') + assert env.Dump('FOO') == "'foo'", env.Dump('FOO') + assert len(env.Dump()) > 200, env.Dump() # no args version + + def test_Environment(self): + """Test the Environment() method""" + env = self.TestEnvironment(FOO = 'xxx', BAR = 'yyy') + + e2 = env.Environment(X = '$FOO', Y = '$BAR') + assert e2['X'] == 'xxx', e2['X'] + assert e2['Y'] == 'yyy', e2['Y'] + + def test_Execute(self): + """Test the Execute() method""" + + class MyAction: + def __init__(self, *args, **kw): + self.args = args + def __call__(self, target, source, env): + return "%s executed" % self.args + + env = Environment() + env.Action = MyAction + + result = env.Execute("foo") + assert result == "foo executed", result + + def test_Entry(self): + """Test the Entry() method""" + class MyFS: + def Entry(self, name): + return 'Entry(%s)' % name + + env = self.TestEnvironment(FOO = 'fooentry', BAR = 'barentry') + env.fs = MyFS() + + e = env.Entry('e') + assert e == 'Entry(e)', e + + e = env.Entry('$FOO') + assert e == 'Entry(fooentry)', e + + e = env.Entry('${BAR}_$BAR') + assert e == 'Entry(barentry_barentry)', e + + e = env.Entry(['entry1']) + assert e == ['Entry(entry1)'], e + + e = env.Entry(['entry1', 'entry2']) + assert e == ['Entry(entry1)', 'Entry(entry2)'], e + + def test_File(self): + """Test the File() method""" + class MyFS: + def File(self, name): + return 'File(%s)' % name + + env = self.TestEnvironment(FOO = 'foofile', BAR = 'barfile') + env.fs = MyFS() + + f = env.File('f') + assert f == 'File(f)', f + + f = env.File('$FOO') + assert f == 'File(foofile)', f + + f = env.File('${BAR}_$BAR') + assert f == 'File(barfile_barfile)', f + + f = env.File(['file1']) + assert f == ['File(file1)'], f + + f = env.File(['file1', 'file2']) + assert f == ['File(file1)', 'File(file2)'], f + + def test_FindFile(self): + """Test the FindFile() method""" + env = self.TestEnvironment(FOO = 'fff', BAR = 'bbb') + + r = env.FindFile('foo', ['no_such_directory']) + assert r is None, r + + # XXX + + def test_Flatten(self): + """Test the Flatten() method""" + env = Environment() + l = env.Flatten([1]) + assert l == [1] + l = env.Flatten([1, [2, [3, [4]]]]) + assert l == [1, 2, 3, 4], l + + def test_GetBuildPath(self): + """Test the GetBuildPath() method.""" + env = self.TestEnvironment(MAGIC = 'xyzzy') + + p = env.GetBuildPath('foo') + assert p == 'foo', p + + p = env.GetBuildPath('$MAGIC') + assert p == 'xyzzy', p + + def test_Ignore(self): + """Test the explicit Ignore method.""" + env = self.TestEnvironment(FOO='yyy', BAR='zzz') + env.Dir('dir1') + env.Dir('dir2') + env.File('yyyzzz') + env.File('zzzyyy') + + t = env.Ignore(target='targ.py', dependency='dep.py')[0] + assert t.__class__.__name__ == 'Entry', t.__class__.__name__ + assert t.path == 'targ.py' + assert len(t.ignore) == 1 + i = t.ignore[0] + assert i.__class__.__name__ == 'Entry', i.__class__.__name__ + assert i.path == 'dep.py' + + t = env.Ignore(target='$FOO$BAR', dependency='$BAR$FOO')[0] + assert t.__class__.__name__ == 'File', t.__class__.__name__ + assert t.path == 'yyyzzz' + assert len(t.ignore) == 1 + i = t.ignore[0] + assert i.__class__.__name__ == 'File', i.__class__.__name__ + assert i.path == 'zzzyyy' + + t = env.Ignore(target='dir1', dependency='dir2')[0] + assert t.__class__.__name__ == 'Dir', t.__class__.__name__ + assert t.path == 'dir1' + assert len(t.ignore) == 1 + i = t.ignore[0] + assert i.__class__.__name__ == 'Dir', i.__class__.__name__ + assert i.path == 'dir2' + + def test_Literal(self): + """Test the Literal() method""" + env = self.TestEnvironment(FOO='fff', BAR='bbb') + list = env.subst_list([env.Literal('$FOO'), '$BAR'])[0] + assert list == ['$FOO', 'bbb'], list + list = env.subst_list(['$FOO', env.Literal('$BAR')])[0] + assert list == ['fff', '$BAR'], list + + def test_Local(self): + """Test the Local() method.""" + env = self.TestEnvironment(FOO='lll') + + l = env.Local(env.fs.File('fff')) + assert str(l[0]) == 'fff', l[0] + + l = env.Local('ggg', '$FOO') + assert str(l[0]) == 'ggg', l[0] + assert str(l[1]) == 'lll', l[1] + + def test_Precious(self): + """Test the Precious() method""" + env = self.TestEnvironment(FOO='ggg', BAR='hhh') + env.Dir('p_hhhb') + env.File('p_d') + t = env.Precious('p_a', 'p_${BAR}b', ['p_c', 'p_d'], 'p_$FOO') + + assert t[0].__class__.__name__ == 'Entry', t[0].__class__.__name__ + assert t[0].path == 'p_a' + assert t[0].precious + assert t[1].__class__.__name__ == 'Dir', t[1].__class__.__name__ + assert t[1].path == 'p_hhhb' + assert t[1].precious + assert t[2].__class__.__name__ == 'Entry', t[2].__class__.__name__ + assert t[2].path == 'p_c' + assert t[2].precious + assert t[3].__class__.__name__ == 'File', t[3].__class__.__name__ + assert t[3].path == 'p_d' + assert t[3].precious + assert t[4].__class__.__name__ == 'Entry', t[4].__class__.__name__ + assert t[4].path == 'p_ggg' + assert t[4].precious + + def test_Repository(self): + """Test the Repository() method.""" + class MyFS: + def __init__(self): + self.list = [] + def Repository(self, *dirs): + self.list.extend(list(dirs)) + def Dir(self, name): + return name + env = self.TestEnvironment(FOO='rrr', BAR='sss') + env.fs = MyFS() + env.Repository('/tmp/foo') + env.Repository('/tmp/$FOO', '/tmp/$BAR/foo') + expect = ['/tmp/foo', '/tmp/rrr', '/tmp/sss/foo'] + assert env.fs.list == expect, env.fs.list + + def test_Scanner(self): + """Test the Scanner() method""" + def scan(node, env, target, arg): + pass + + env = self.TestEnvironment(FOO = scan) + + s = env.Scanner('foo') + assert s is not None, s + + s = env.Scanner(function = 'foo') + assert s is not None, s + + if 0: + s = env.Scanner('$FOO') + assert s is not None, s + + s = env.Scanner(function = '$FOO') + assert s is not None, s + + def test_SConsignFile(self): + """Test the SConsignFile() method""" + import SCons.SConsign + + class MyFS: + SConstruct_dir = os.sep + 'dir' + + env = self.TestEnvironment(FOO = 'SConsign', + BAR = os.path.join(os.sep, 'File')) + env.fs = MyFS() + env.Execute = lambda action: None + + try: + fnames = [] + dbms = [] + def capture(name, dbm_module, fnames=fnames, dbms=dbms): + fnames.append(name) + dbms.append(dbm_module) + + save_SConsign_File = SCons.SConsign.File + SCons.SConsign.File = capture + + env.SConsignFile('foo') + assert fnames[-1] == os.path.join(os.sep, 'dir', 'foo'), fnames + assert dbms[-1] is None, dbms + + env.SConsignFile('$FOO') + assert fnames[-1] == os.path.join(os.sep, 'dir', 'SConsign'), fnames + assert dbms[-1] is None, dbms + + env.SConsignFile('/$FOO') + assert fnames[-1] == os.sep + 'SConsign', fnames + assert dbms[-1] is None, dbms + + env.SConsignFile(os.sep + '$FOO') + assert fnames[-1] == os.sep + 'SConsign', fnames + assert dbms[-1] is None, dbms + + env.SConsignFile('$BAR', 'x') + assert fnames[-1] == os.path.join(os.sep, 'File'), fnames + assert dbms[-1] == 'x', dbms + + env.SConsignFile('__$BAR', 7) + assert fnames[-1] == os.path.join(os.sep, 'dir', '__', 'File'), fnames + assert dbms[-1] == 7, dbms + + env.SConsignFile() + assert fnames[-1] == os.path.join(os.sep, 'dir', '.sconsign'), fnames + assert dbms[-1] is None, dbms + + env.SConsignFile(None) + assert fnames[-1] is None, fnames + assert dbms[-1] is None, dbms + finally: + SCons.SConsign.File = save_SConsign_File + + def test_SideEffect(self): + """Test the SideEffect() method""" + env = self.TestEnvironment(LIB='lll', FOO='fff', BAR='bbb') + env.File('mylll.pdb') + env.Dir('mymmm.pdb') + + foo = env.Object('foo.obj', 'foo.cpp')[0] + bar = env.Object('bar.obj', 'bar.cpp')[0] + s = env.SideEffect('mylib.pdb', ['foo.obj', 'bar.obj'])[0] + assert s.__class__.__name__ == 'Entry', s.__class__.__name__ + assert s.path == 'mylib.pdb' + assert s.side_effect + assert foo.side_effects == [s] + assert bar.side_effects == [s] + + fff = env.Object('fff.obj', 'fff.cpp')[0] + bbb = env.Object('bbb.obj', 'bbb.cpp')[0] + s = env.SideEffect('my${LIB}.pdb', ['${FOO}.obj', '${BAR}.obj'])[0] + assert s.__class__.__name__ == 'File', s.__class__.__name__ + assert s.path == 'mylll.pdb' + assert s.side_effect + assert fff.side_effects == [s], fff.side_effects + assert bbb.side_effects == [s], bbb.side_effects + + ggg = env.Object('ggg.obj', 'ggg.cpp')[0] + ccc = env.Object('ccc.obj', 'ccc.cpp')[0] + s = env.SideEffect('mymmm.pdb', ['ggg.obj', 'ccc.obj'])[0] + assert s.__class__.__name__ == 'Dir', s.__class__.__name__ + assert s.path == 'mymmm.pdb' + assert s.side_effect + assert ggg.side_effects == [s], ggg.side_effects + assert ccc.side_effects == [s], ccc.side_effects + + def test_SourceCode(self): + """Test the SourceCode() method.""" + env = self.TestEnvironment(FOO='mmm', BAR='nnn') + e = env.SourceCode('foo', None)[0] + assert e.path == 'foo' + s = e.src_builder() + assert s is None, s + + b = Builder() + e = env.SourceCode(e, b)[0] + assert e.path == 'foo' + s = e.src_builder() + assert s is b, s + + e = env.SourceCode('$BAR$FOO', None)[0] + assert e.path == 'nnnmmm' + s = e.src_builder() + assert s is None, s + + def test_SourceSignatures(type): + """Test the SourceSignatures() method""" + import SCons.Errors + + env = type.TestEnvironment(M = 'MD5', T = 'timestamp') + + exc_caught = None + try: + env.SourceSignatures('invalid_type') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + + env.SourceSignatures('MD5') + assert env.src_sig_type == 'MD5', env.src_sig_type + + env.SourceSignatures('$M') + assert env.src_sig_type == 'MD5', env.src_sig_type + + env.SourceSignatures('timestamp') + assert env.src_sig_type == 'timestamp', env.src_sig_type + + env.SourceSignatures('$T') + assert env.src_sig_type == 'timestamp', env.src_sig_type + + try: + import SCons.Util + save_md5 = SCons.Util.md5 + SCons.Util.md5 = None + try: + env.SourceSignatures('MD5') + except SCons.Errors.UserError: + pass + else: + self.fail('Did not catch expected UserError') + finally: + SCons.Util.md5 = save_md5 + + def test_Split(self): + """Test the Split() method""" + env = self.TestEnvironment(FOO='fff', BAR='bbb') + s = env.Split("foo bar") + assert s == ["foo", "bar"], s + s = env.Split("$FOO bar") + assert s == ["fff", "bar"], s + s = env.Split(["foo", "bar"]) + assert s == ["foo", "bar"], s + s = env.Split(["foo", "${BAR}-bbb"]) + assert s == ["foo", "bbb-bbb"], s + s = env.Split("foo") + assert s == ["foo"], s + s = env.Split("$FOO$BAR") + assert s == ["fffbbb"], s + + def test_TargetSignatures(type): + """Test the TargetSignatures() method""" + import SCons.Errors + + env = type.TestEnvironment(B = 'build', C = 'content') + + exc_caught = None + try: + env.TargetSignatures('invalid_type') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch expected UserError" + assert not hasattr(env, '_build_signature') + + env.TargetSignatures('build') + assert env.tgt_sig_type == 'build', env.tgt_sig_type + + env.TargetSignatures('$B') + assert env.tgt_sig_type == 'build', env.tgt_sig_type + + env.TargetSignatures('content') + assert env.tgt_sig_type == 'content', env.tgt_sig_type + + env.TargetSignatures('$C') + assert env.tgt_sig_type == 'content', env.tgt_sig_type + + env.TargetSignatures('MD5') + assert env.tgt_sig_type == 'MD5', env.tgt_sig_type + + env.TargetSignatures('timestamp') + assert env.tgt_sig_type == 'timestamp', env.tgt_sig_type + + try: + import SCons.Util + save_md5 = SCons.Util.md5 + SCons.Util.md5 = None + try: + env.TargetSignatures('MD5') + except SCons.Errors.UserError: + pass + else: + self.fail('Did not catch expected UserError') + try: + env.TargetSignatures('content') + except SCons.Errors.UserError: + pass + else: + self.fail('Did not catch expected UserError') + finally: + SCons.Util.md5 = save_md5 + + def test_Value(self): + """Test creating a Value() object + """ + env = Environment() + v1 = env.Value('a') + assert v1.value == 'a', v1.value + + value2 = 'a' + v2 = env.Value(value2) + assert v2.value == value2, v2.value + assert v2.value is value2, v2.value + + assert not v1 is v2 + assert v1.value == v2.value + + v3 = env.Value('c', 'build-c') + assert v3.value == 'c', v3.value + + + + def test_Environment_global_variable(type): + """Test setting Environment variable to an Environment.Base subclass""" + class MyEnv(SCons.Environment.Base): + def xxx(self, string): + return self.subst(string) + + SCons.Environment.Environment = MyEnv + + env = SCons.Environment.Environment(FOO = 'foo') + + f = env.subst('$FOO') + assert f == 'foo', f + + f = env.xxx('$FOO') + assert f == 'foo', f + + def test_bad_keywords(self): + """Test trying to use reserved keywords in an Environment""" + added = [] + + env = self.TestEnvironment(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + INIT = 'init') + bad_msg = '%s is not reserved, but got omitted; see Environment.construction_var_name_ok' + added.append('INIT') + for x in self.reserved_variables: + assert not env.has_key(x), env[x] + for x in added: + assert env.has_key(x), bad_msg % x + + env.Append(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + APPEND = 'append') + added.append('APPEND') + for x in self.reserved_variables: + assert not env.has_key(x), env[x] + for x in added: + assert env.has_key(x), bad_msg % x + + env.AppendUnique(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + APPENDUNIQUE = 'appendunique') + added.append('APPENDUNIQUE') + for x in self.reserved_variables: + assert not env.has_key(x), env[x] + for x in added: + assert env.has_key(x), bad_msg % x + + env.Prepend(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + PREPEND = 'prepend') + added.append('PREPEND') + for x in self.reserved_variables: + assert not env.has_key(x), env[x] + for x in added: + assert env.has_key(x), bad_msg % x + + env.Prepend(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + PREPENDUNIQUE = 'prependunique') + added.append('PREPENDUNIQUE') + for x in self.reserved_variables: + assert not env.has_key(x), env[x] + for x in added: + assert env.has_key(x), bad_msg % x + + env.Replace(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + REPLACE = 'replace') + added.append('REPLACE') + for x in self.reserved_variables: + assert not env.has_key(x), env[x] + for x in added: + assert env.has_key(x), bad_msg % x + + copy = env.Clone(TARGETS = 'targets', + SOURCES = 'sources', + SOURCE = 'source', + TARGET = 'target', + CHANGED_SOURCES = 'changed_sources', + CHANGED_TARGETS = 'changed_targets', + UNCHANGED_SOURCES = 'unchanged_sources', + UNCHANGED_TARGETS = 'unchanged_targets', + COPY = 'copy') + for x in self.reserved_variables: + assert not copy.has_key(x), env[x] + for x in added + ['COPY']: + assert copy.has_key(x), bad_msg % x + + over = env.Override({'TARGETS' : 'targets', + 'SOURCES' : 'sources', + 'SOURCE' : 'source', + 'TARGET' : 'target', + 'CHANGED_SOURCES' : 'changed_sources', + 'CHANGED_TARGETS' : 'changed_targets', + 'UNCHANGED_SOURCES' : 'unchanged_sources', + 'UNCHANGED_TARGETS' : 'unchanged_targets', + 'OVERRIDE' : 'override'}) + for x in self.reserved_variables: + assert not over.has_key(x), over[x] + for x in added + ['OVERRIDE']: + assert over.has_key(x), bad_msg % x + + def test_parse_flags(self): + '''Test the Base class parse_flags argument''' + # all we have to show is that it gets to MergeFlags internally + env = Environment(tools=[], parse_flags = '-X') + assert env['CCFLAGS'] == ['-X'], env['CCFLAGS'] + + env = Environment(tools=[], CCFLAGS=None, parse_flags = '-Y') + assert env['CCFLAGS'] == ['-Y'], env['CCFLAGS'] + + env = Environment(tools=[], CPPDEFINES = 'FOO', parse_flags = '-std=c99 -X -DBAR') + assert env['CFLAGS'] == ['-std=c99'], env['CFLAGS'] + assert env['CCFLAGS'] == ['-X'], env['CCFLAGS'] + assert env['CPPDEFINES'] == ['FOO', 'BAR'], env['CPPDEFINES'] + + def test_clone_parse_flags(self): + '''Test the env.Clone() parse_flags argument''' + # all we have to show is that it gets to MergeFlags internally + env = Environment(tools = []) + env2 = env.Clone(parse_flags = '-X') + assert not env.has_key('CCFLAGS') + assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS'] + + env = Environment(tools = [], CCFLAGS=None) + env2 = env.Clone(parse_flags = '-Y') + assert env['CCFLAGS'] is None, env['CCFLAGS'] + assert env2['CCFLAGS'] == ['-Y'], env2['CCFLAGS'] + + env = Environment(tools = [], CPPDEFINES = 'FOO') + env2 = env.Clone(parse_flags = '-std=c99 -X -DBAR') + assert not env.has_key('CFLAGS') + assert env2['CFLAGS'] == ['-std=c99'], env2['CFLAGS'] + assert not env.has_key('CCFLAGS') + assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS'] + assert env['CPPDEFINES'] == 'FOO', env['CPPDEFINES'] + assert env2['CPPDEFINES'] == ['FOO','BAR'], env2['CPPDEFINES'] + + + +class OverrideEnvironmentTestCase(unittest.TestCase,TestEnvironmentFixture): + + def setUp(self): + env = Environment() + env._dict = {'XXX' : 'x', 'YYY' : 'y'} + env2 = OverrideEnvironment(env, {'XXX' : 'x2'}) + env3 = OverrideEnvironment(env2, {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}) + self.envs = [ env, env2, env3 ] + + def checkpath(self, node, expect): + return str(node) == os.path.normpath(expect) + + def test___init__(self): + """Test OverrideEnvironment initialization""" + env, env2, env3 = self.envs + assert env['XXX'] == 'x', env['XXX'] + assert env2['XXX'] == 'x2', env2['XXX'] + assert env3['XXX'] == 'x3', env3['XXX'] + assert env['YYY'] == 'y', env['YYY'] + assert env2['YYY'] == 'y', env2['YYY'] + assert env3['YYY'] == 'y3', env3['YYY'] + + def test___delitem__(self): + """Test deleting variables from an OverrideEnvironment""" + env, env2, env3 = self.envs + + del env3['XXX'] + assert not env.has_key('XXX'), "env has XXX?" + assert not env2.has_key('XXX'), "env2 has XXX?" + assert not env3.has_key('XXX'), "env3 has XXX?" + + del env3['YYY'] + assert not env.has_key('YYY'), "env has YYY?" + assert not env2.has_key('YYY'), "env2 has YYY?" + assert not env3.has_key('YYY'), "env3 has YYY?" + + del env3['ZZZ'] + assert not env.has_key('ZZZ'), "env has ZZZ?" + assert not env2.has_key('ZZZ'), "env2 has ZZZ?" + assert not env3.has_key('ZZZ'), "env3 has ZZZ?" + + def test_get(self): + """Test the OverrideEnvironment get() method""" + env, env2, env3 = self.envs + assert env.get('XXX') == 'x', env.get('XXX') + assert env2.get('XXX') == 'x2', env2.get('XXX') + assert env3.get('XXX') == 'x3', env3.get('XXX') + assert env.get('YYY') == 'y', env.get('YYY') + assert env2.get('YYY') == 'y', env2.get('YYY') + assert env3.get('YYY') == 'y3', env3.get('YYY') + assert env.get('ZZZ') is None, env.get('ZZZ') + assert env2.get('ZZZ') is None, env2.get('ZZZ') + assert env3.get('ZZZ') == 'z3', env3.get('ZZZ') + + def test_has_key(self): + """Test the OverrideEnvironment has_key() method""" + env, env2, env3 = self.envs + assert env.has_key('XXX'), env.has_key('XXX') + assert env2.has_key('XXX'), env2.has_key('XXX') + assert env3.has_key('XXX'), env3.has_key('XXX') + assert env.has_key('YYY'), env.has_key('YYY') + assert env2.has_key('YYY'), env2.has_key('YYY') + assert env3.has_key('YYY'), env3.has_key('YYY') + assert not env.has_key('ZZZ'), env.has_key('ZZZ') + assert not env2.has_key('ZZZ'), env2.has_key('ZZZ') + assert env3.has_key('ZZZ'), env3.has_key('ZZZ') + + def test_contains(self): + """Test the OverrideEnvironment __contains__() method""" + try: + 'x' in {'x':1} + except TypeError: + # TODO(1.5) + # An early version of Python that doesn't support "in" + # on dictionaries. Just pass the test. + pass + else: + env, env2, env3 = self.envs + assert 'XXX' in env + assert 'XXX' in env2 + assert 'XXX' in env3 + assert 'YYY' in env + assert 'YYY' in env2 + assert 'YYY' in env3 + assert not 'ZZZ' in env + assert not 'ZZZ' in env2 + assert 'ZZZ' in env3 + + def test_items(self): + """Test the OverrideEnvironment Dictionary() method""" + env, env2, env3 = self.envs + items = env.Dictionary() + assert items == {'XXX' : 'x', 'YYY' : 'y'}, items + items = env2.Dictionary() + assert items == {'XXX' : 'x2', 'YYY' : 'y'}, items + items = env3.Dictionary() + assert items == {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, items + + def test_items(self): + """Test the OverrideEnvironment items() method""" + env, env2, env3 = self.envs + items = env.items() + items.sort() + assert items == [('XXX', 'x'), ('YYY', 'y')], items + items = env2.items() + items.sort() + assert items == [('XXX', 'x2'), ('YYY', 'y')], items + items = env3.items() + items.sort() + assert items == [('XXX', 'x3'), ('YYY', 'y3'), ('ZZZ', 'z3')], items + + def test_gvars(self): + """Test the OverrideEnvironment gvars() method""" + env, env2, env3 = self.envs + gvars = env.gvars() + assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars + gvars = env2.gvars() + assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars + gvars = env3.gvars() + assert gvars == {'XXX' : 'x', 'YYY' : 'y'}, gvars + + def test_lvars(self): + """Test the OverrideEnvironment lvars() method""" + env, env2, env3 = self.envs + lvars = env.lvars() + assert lvars == {}, lvars + lvars = env2.lvars() + assert lvars == {'XXX' : 'x2'}, lvars + lvars = env3.lvars() + assert lvars == {'XXX' : 'x3', 'YYY' : 'y3', 'ZZZ' : 'z3'}, lvars + + def test_Replace(self): + """Test the OverrideEnvironment Replace() method""" + env, env2, env3 = self.envs + assert env['XXX'] == 'x', env['XXX'] + assert env2['XXX'] == 'x2', env2['XXX'] + assert env3['XXX'] == 'x3', env3['XXX'] + assert env['YYY'] == 'y', env['YYY'] + assert env2['YYY'] == 'y', env2['YYY'] + assert env3['YYY'] == 'y3', env3['YYY'] + + env.Replace(YYY = 'y4') + + assert env['XXX'] == 'x', env['XXX'] + assert env2['XXX'] == 'x2', env2['XXX'] + assert env3['XXX'] == 'x3', env3['XXX'] + assert env['YYY'] == 'y4', env['YYY'] + assert env2['YYY'] == 'y4', env2['YYY'] + assert env3['YYY'] == 'y3', env3['YYY'] + + # Tests a number of Base methods through an OverrideEnvironment to + # make sure they handle overridden constructionv variables properly. + # + # The following Base methods also call self.subst(), and so could + # theoretically be subject to problems with evaluating overridden + # variables, but they're never really called that way in the rest + # of our code, so we won't worry about them (at least for now): + # + # ParseConfig() + # ParseDepends() + # Platform() + # Tool() + # + # Action() + # Alias() + # Builder() + # CacheDir() + # Configure() + # Environment() + # FindFile() + # Scanner() + # SourceSignatures() + # TargetSignatures() + + # It's unlikely Clone() will ever be called this way, so let the + # other methods test that handling overridden values works. + #def test_Clone(self): + # """Test the OverrideEnvironment Clone() method""" + # pass + + def test_FindIxes(self): + """Test the OverrideEnvironment FindIxes() method""" + env, env2, env3 = self.envs + x = env.FindIxes(['xaaay'], 'XXX', 'YYY') + assert x == 'xaaay', x + x = env2.FindIxes(['x2aaay'], 'XXX', 'YYY') + assert x == 'x2aaay', x + x = env3.FindIxes(['x3aaay3'], 'XXX', 'YYY') + assert x == 'x3aaay3', x + + def test_ReplaceIxes(self): + """Test the OverrideEnvironment ReplaceIxes() method""" + env, env2, env3 = self.envs + x = env.ReplaceIxes('xaaay', 'XXX', 'YYY', 'YYY', 'XXX') + assert x == 'yaaax', x + x = env2.ReplaceIxes('x2aaay', 'XXX', 'YYY', 'YYY', 'XXX') + assert x == 'yaaax2', x + x = env3.ReplaceIxes('x3aaay3', 'XXX', 'YYY', 'YYY', 'XXX') + assert x == 'y3aaax3', x + + # It's unlikely WhereIs() will ever be called this way, so let the + # other methods test that handling overridden values works. + #def test_WhereIs(self): + # """Test the OverrideEnvironment WhereIs() method""" + # pass + + def test_Dir(self): + """Test the OverrideEnvironment Dir() method""" + env, env2, env3 = self.envs + x = env.Dir('ddir/$XXX') + assert self.checkpath(x, 'ddir/x'), str(x) + x = env2.Dir('ddir/$XXX') + assert self.checkpath(x, 'ddir/x2'), str(x) + x = env3.Dir('ddir/$XXX') + assert self.checkpath(x, 'ddir/x3'), str(x) + + def test_Entry(self): + """Test the OverrideEnvironment Entry() method""" + env, env2, env3 = self.envs + x = env.Entry('edir/$XXX') + assert self.checkpath(x, 'edir/x'), str(x) + x = env2.Entry('edir/$XXX') + assert self.checkpath(x, 'edir/x2'), str(x) + x = env3.Entry('edir/$XXX') + assert self.checkpath(x, 'edir/x3'), str(x) + + def test_File(self): + """Test the OverrideEnvironment File() method""" + env, env2, env3 = self.envs + x = env.File('fdir/$XXX') + assert self.checkpath(x, 'fdir/x'), str(x) + x = env2.File('fdir/$XXX') + assert self.checkpath(x, 'fdir/x2'), str(x) + x = env3.File('fdir/$XXX') + assert self.checkpath(x, 'fdir/x3'), str(x) + + def test_Split(self): + """Test the OverrideEnvironment Split() method""" + env, env2, env3 = self.envs + env['AAA'] = '$XXX $YYY $ZZZ' + x = env.Split('$AAA') + assert x == ['x', 'y'], x + x = env2.Split('$AAA') + assert x == ['x2', 'y'], x + x = env3.Split('$AAA') + assert x == ['x3', 'y3', 'z3'], x + + def test_parse_flags(self): + '''Test the OverrideEnvironment parse_flags argument''' + # all we have to show is that it gets to MergeFlags internally + env = SubstitutionEnvironment() + env2 = env.Override({'parse_flags' : '-X'}) + assert not env.has_key('CCFLAGS') + assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS'] + + env = SubstitutionEnvironment(CCFLAGS=None) + env2 = env.Override({'parse_flags' : '-Y'}) + assert env['CCFLAGS'] is None, env['CCFLAGS'] + assert env2['CCFLAGS'] == ['-Y'], env2['CCFLAGS'] + + env = SubstitutionEnvironment(CPPDEFINES = 'FOO') + env2 = env.Override({'parse_flags' : '-std=c99 -X -DBAR'}) + assert not env.has_key('CFLAGS') + assert env2['CFLAGS'] == ['-std=c99'], env2['CFLAGS'] + assert not env.has_key('CCFLAGS') + assert env2['CCFLAGS'] == ['-X'], env2['CCFLAGS'] + assert env['CPPDEFINES'] == 'FOO', env['CPPDEFINES'] + assert env2['CPPDEFINES'] == ['FOO','BAR'], env2['CPPDEFINES'] + + + +class NoSubstitutionProxyTestCase(unittest.TestCase,TestEnvironmentFixture): + + def test___init__(self): + """Test NoSubstitutionProxy initialization""" + env = self.TestEnvironment(XXX = 'x', YYY = 'y') + assert env['XXX'] == 'x', env['XXX'] + assert env['YYY'] == 'y', env['YYY'] + + proxy = NoSubstitutionProxy(env) + assert proxy['XXX'] == 'x', proxy['XXX'] + assert proxy['YYY'] == 'y', proxy['YYY'] + + def test_attributes(self): + """Test getting and setting NoSubstitutionProxy attributes""" + env = Environment() + setattr(env, 'env_attr', 'value1') + + proxy = NoSubstitutionProxy(env) + setattr(proxy, 'proxy_attr', 'value2') + + x = getattr(env, 'env_attr') + assert x == 'value1', x + x = getattr(proxy, 'env_attr') + assert x == 'value1', x + + x = getattr(env, 'proxy_attr') + assert x == 'value2', x + x = getattr(proxy, 'proxy_attr') + assert x == 'value2', x + + def test_subst(self): + """Test the NoSubstitutionProxy.subst() method""" + env = self.TestEnvironment(XXX = 'x', YYY = 'y') + assert env['XXX'] == 'x', env['XXX'] + assert env['YYY'] == 'y', env['YYY'] + + proxy = NoSubstitutionProxy(env) + assert proxy['XXX'] == 'x', proxy['XXX'] + assert proxy['YYY'] == 'y', proxy['YYY'] + + x = env.subst('$XXX') + assert x == 'x', x + x = proxy.subst('$XXX') + assert x == '$XXX', x + + x = proxy.subst('$YYY', raw=7, target=None, source=None, + conv=None, + extra_meaningless_keyword_argument=None) + assert x == '$YYY', x + + def test_subst_kw(self): + """Test the NoSubstitutionProxy.subst_kw() method""" + env = self.TestEnvironment(XXX = 'x', YYY = 'y') + assert env['XXX'] == 'x', env['XXX'] + assert env['YYY'] == 'y', env['YYY'] + + proxy = NoSubstitutionProxy(env) + assert proxy['XXX'] == 'x', proxy['XXX'] + assert proxy['YYY'] == 'y', proxy['YYY'] + + x = env.subst_kw({'$XXX':'$YYY'}) + assert x == {'x':'y'}, x + x = proxy.subst_kw({'$XXX':'$YYY'}) + assert x == {'$XXX':'$YYY'}, x + + def test_subst_list(self): + """Test the NoSubstitutionProxy.subst_list() method""" + env = self.TestEnvironment(XXX = 'x', YYY = 'y') + assert env['XXX'] == 'x', env['XXX'] + assert env['YYY'] == 'y', env['YYY'] + + proxy = NoSubstitutionProxy(env) + assert proxy['XXX'] == 'x', proxy['XXX'] + assert proxy['YYY'] == 'y', proxy['YYY'] + + x = env.subst_list('$XXX') + assert x == [['x']], x + x = proxy.subst_list('$XXX') + assert x == [[]], x + + x = proxy.subst_list('$YYY', raw=0, target=None, source=None, conv=None) + assert x == [[]], x + + def test_subst_target_source(self): + """Test the NoSubstitutionProxy.subst_target_source() method""" + env = self.TestEnvironment(XXX = 'x', YYY = 'y') + assert env['XXX'] == 'x', env['XXX'] + assert env['YYY'] == 'y', env['YYY'] + + proxy = NoSubstitutionProxy(env) + assert proxy['XXX'] == 'x', proxy['XXX'] + assert proxy['YYY'] == 'y', proxy['YYY'] + + args = ('$XXX $TARGET $SOURCE $YYY',) + kw = {'target' : DummyNode('ttt'), 'source' : DummyNode('sss')} + x = apply(env.subst_target_source, args, kw) + assert x == 'x ttt sss y', x + x = apply(proxy.subst_target_source, args, kw) + assert x == ' ttt sss ', x + +class EnvironmentVariableTestCase(unittest.TestCase): + + def test_is_valid_construction_var(self): + """Testing is_valid_construction_var()""" + r = is_valid_construction_var("_a") + assert r is not None, r + r = is_valid_construction_var("z_") + assert r is not None, r + r = is_valid_construction_var("X_") + assert r is not None, r + r = is_valid_construction_var("2a") + assert r is None, r + r = is_valid_construction_var("a2_") + assert r is not None, r + r = is_valid_construction_var("/") + assert r is None, r + r = is_valid_construction_var("_/") + assert r is None, r + r = is_valid_construction_var("a/") + assert r is None, r + r = is_valid_construction_var(".b") + assert r is None, r + r = is_valid_construction_var("_.b") + assert r is None, r + r = is_valid_construction_var("b1._") + assert r is None, r + r = is_valid_construction_var("-b") + assert r is None, r + r = is_valid_construction_var("_-b") + assert r is None, r + r = is_valid_construction_var("b1-_") + assert r is None, r + + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ SubstitutionTestCase, + BaseTestCase, + OverrideEnvironmentTestCase, + NoSubstitutionProxyTestCase, + EnvironmentVariableTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Errors.py b/src/engine/SCons/Errors.py new file mode 100644 index 0000000..3044de9 --- /dev/null +++ b/src/engine/SCons/Errors.py @@ -0,0 +1,207 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +"""SCons.Errors + +This file contains the exception classes used to handle internal +and user errors in SCons. + +""" + +__revision__ = "src/engine/SCons/Errors.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +import exceptions + +class BuildError(Exception): + """ Errors occuring while building. + + BuildError have the following attributes: + + Information about the cause of the build error: + ----------------------------------------------- + + errstr : a description of the error message + + status : the return code of the action that caused the build + error. Must be set to a non-zero value even if the + build error is not due to an action returning a + non-zero returned code. + + exitstatus : SCons exit status due to this build error. + Must be nonzero unless due to an explicit Exit() + call. Not always the same as status, since + actions return a status code that should be + respected, but SCons typically exits with 2 + irrespective of the return value of the failed + action. + + filename : The name of the file or directory that caused the + build error. Set to None if no files are associated with + this error. This might be different from the target + being built. For example, failure to create the + directory in which the target file will appear. It + can be None if the error is not due to a particular + filename. + + exc_info : Info about exception that caused the build + error. Set to (None, None, None) if this build + error is not due to an exception. + + + Information about the cause of the location of the error: + --------------------------------------------------------- + + node : the error occured while building this target node(s) + + executor : the executor that caused the build to fail (might + be None if the build failures is not due to the + executor failing) + + action : the action that caused the build to fail (might be + None if the build failures is not due to the an + action failure) + + command : the command line for the action that caused the + build to fail (might be None if the build failures + is not due to the an action failure) + """ + + def __init__(self, + node=None, errstr="Unknown error", status=2, exitstatus=2, + filename=None, executor=None, action=None, command=None, + exc_info=(None, None, None)): + + self.errstr = errstr + self.status = status + self.exitstatus = exitstatus + self.filename = filename + self.exc_info = exc_info + + self.node = node + self.executor = executor + self.action = action + self.command = command + + Exception.__init__(self, node, errstr, status, exitstatus, filename, + executor, action, command, exc_info) + + def __str__(self): + if self.filename: + return self.filename + ': ' + self.errstr + else: + return self.errstr + +class InternalError(Exception): + pass + +class UserError(Exception): + pass + +class StopError(Exception): + pass + +class EnvironmentError(Exception): + pass + +class MSVCError(IOError): + pass + +class ExplicitExit(Exception): + def __init__(self, node=None, status=None, *args): + self.node = node + self.status = status + self.exitstatus = status + apply(Exception.__init__, (self,) + args) + +def convert_to_BuildError(status, exc_info=None): + """ + Convert any return code a BuildError Exception. + + `status' can either be a return code or an Exception. + The buildError.status we set here will normally be + used as the exit status of the "scons" process. + """ + if not exc_info and isinstance(status, Exception): + exc_info = (status.__class__, status, None) + + if isinstance(status, BuildError): + buildError = status + buildError.exitstatus = 2 # always exit with 2 on build errors + elif isinstance(status, ExplicitExit): + status = status.status + errstr = 'Explicit exit, status %s' % status + buildError = BuildError( + errstr=errstr, + status=status, # might be 0, OK here + exitstatus=status, # might be 0, OK here + exc_info=exc_info) + # TODO(1.5): + #elif isinstance(status, (StopError, UserError)): + elif isinstance(status, StopError) or isinstance(status, UserError): + buildError = BuildError( + errstr=str(status), + status=2, + exitstatus=2, + exc_info=exc_info) + elif isinstance(status, exceptions.EnvironmentError): + # If an IOError/OSError happens, raise a BuildError. + # Report the name of the file or directory that caused the + # error, which might be different from the target being built + # (for example, failure to create the directory in which the + # target file will appear). + try: filename = status.filename + except AttributeError: filename = None + buildError = BuildError( + errstr=status.strerror, + status=status.errno, + exitstatus=2, + filename=filename, + exc_info=exc_info) + elif isinstance(status, Exception): + buildError = BuildError( + errstr='%s : %s' % (status.__class__.__name__, status), + status=2, + exitstatus=2, + exc_info=exc_info) + elif SCons.Util.is_String(status): + buildError = BuildError( + errstr=status, + status=2, + exitstatus=2) + else: + buildError = BuildError( + errstr="Error %s" % status, + status=status, + exitstatus=2) + + #import sys + #sys.stderr.write("convert_to_BuildError: status %s => (errstr %s, status %s)"%(status,buildError.errstr, buildError.status)) + return buildError + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/ErrorsTests.py b/src/engine/SCons/ErrorsTests.py new file mode 100644 index 0000000..cf5437b --- /dev/null +++ b/src/engine/SCons/ErrorsTests.py @@ -0,0 +1,109 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/ErrorsTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest +import SCons.Errors + + +class ErrorsTestCase(unittest.TestCase): + def test_BuildError(self): + """Test the BuildError exception.""" + try: + raise SCons.Errors.BuildError( + errstr = "foo", status=57, filename="file", exc_info=(1,2,3), + node = "n", executor="e", action="a", command="c") + except SCons.Errors.BuildError, e: + assert e.errstr == "foo" + assert e.status == 57 + assert e.exitstatus == 2, e.exitstatus + assert e.filename == "file" + assert e.exc_info == (1,2,3) + + assert e.node == "n" + assert e.executor == "e" + assert e.action == "a" + assert e.command == "c" + + try: + raise SCons.Errors.BuildError("n", "foo", 57, 3, "file", + "e", "a", "c", (1,2,3)) + except SCons.Errors.BuildError, e: + assert e.errstr == "foo", e.errstr + assert e.status == 57, e.status + assert e.exitstatus == 3, e.exitstatus + assert e.filename == "file", e.filename + assert e.exc_info == (1,2,3), e.exc_info + + assert e.node == "n" + assert e.executor == "e" + assert e.action == "a" + assert e.command == "c" + + try: + raise SCons.Errors.BuildError() + except SCons.Errors.BuildError, e: + assert e.errstr == "Unknown error" + assert e.status == 2 + assert e.exitstatus == 2 + assert e.filename is None + assert e.exc_info == (None, None, None) + + assert e.node is None + assert e.executor is None + assert e.action is None + assert e.command is None + + def test_InternalError(self): + """Test the InternalError exception.""" + try: + raise SCons.Errors.InternalError, "test internal error" + except SCons.Errors.InternalError, e: + assert e.args == ("test internal error",) + + def test_UserError(self): + """Test the UserError exception.""" + try: + raise SCons.Errors.UserError, "test user error" + except SCons.Errors.UserError, e: + assert e.args == ("test user error",) + + def test_ExplicitExit(self): + """Test the ExplicitExit exception.""" + try: + raise SCons.Errors.ExplicitExit, "node" + except SCons.Errors.ExplicitExit, e: + assert e.node == "node" + +if __name__ == "__main__": + suite = unittest.makeSuite(ErrorsTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Executor.py b/src/engine/SCons/Executor.py new file mode 100644 index 0000000..2e873eb --- /dev/null +++ b/src/engine/SCons/Executor.py @@ -0,0 +1,636 @@ +"""SCons.Executor + +A module for executing actions with specific lists of target and source +Nodes. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Executor.py 4577 2009/12/27 19:44:43 scons" + +import string +import UserList + +from SCons.Debug import logInstanceCreation +import SCons.Errors +import SCons.Memoize + + +class Batch: + """Remembers exact association between targets + and sources of executor.""" + def __init__(self, targets=[], sources=[]): + self.targets = targets + self.sources = sources + + + +class TSList(UserList.UserList): + """A class that implements $TARGETS or $SOURCES expansions by wrapping + an executor Method. This class is used in the Executor.lvars() + to delay creation of NodeList objects until they're needed. + + Note that we subclass UserList.UserList purely so that the + is_Sequence() function will identify an object of this class as + a list during variable expansion. We're not really using any + UserList.UserList methods in practice. + """ + def __init__(self, func): + self.func = func + def __getattr__(self, attr): + nl = self.func() + return getattr(nl, attr) + def __getitem__(self, i): + nl = self.func() + return nl[i] + def __getslice__(self, i, j): + nl = self.func() + i = max(i, 0); j = max(j, 0) + return nl[i:j] + def __str__(self): + nl = self.func() + return str(nl) + def __repr__(self): + nl = self.func() + return repr(nl) + +class TSObject: + """A class that implements $TARGET or $SOURCE expansions by wrapping + an Executor method. + """ + def __init__(self, func): + self.func = func + def __getattr__(self, attr): + n = self.func() + return getattr(n, attr) + def __str__(self): + n = self.func() + if n: + return str(n) + return '' + def __repr__(self): + n = self.func() + if n: + return repr(n) + return '' + +def rfile(node): + """ + A function to return the results of a Node's rfile() method, + if it exists, and the Node itself otherwise (if it's a Value + Node, e.g.). + """ + try: + rfile = node.rfile + except AttributeError: + return node + else: + return rfile() + + +class Executor: + """A class for controlling instances of executing an action. + + This largely exists to hold a single association of an action, + environment, list of environment override dictionaries, targets + and sources for later processing as needed. + """ + + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + memoizer_counters = [] + + def __init__(self, action, env=None, overridelist=[{}], + targets=[], sources=[], builder_kw={}): + if __debug__: logInstanceCreation(self, 'Executor.Executor') + self.set_action_list(action) + self.pre_actions = [] + self.post_actions = [] + self.env = env + self.overridelist = overridelist + if targets or sources: + self.batches = [Batch(targets[:], sources[:])] + else: + self.batches = [] + self.builder_kw = builder_kw + self._memo = {} + + def get_lvars(self): + try: + return self.lvars + except AttributeError: + self.lvars = { + 'CHANGED_SOURCES' : TSList(self._get_changed_sources), + 'CHANGED_TARGETS' : TSList(self._get_changed_targets), + 'SOURCE' : TSObject(self._get_source), + 'SOURCES' : TSList(self._get_sources), + 'TARGET' : TSObject(self._get_target), + 'TARGETS' : TSList(self._get_targets), + 'UNCHANGED_SOURCES' : TSList(self._get_unchanged_sources), + 'UNCHANGED_TARGETS' : TSList(self._get_unchanged_targets), + } + return self.lvars + + def _get_changes(self): + cs = [] + ct = [] + us = [] + ut = [] + for b in self.batches: + if b.targets[0].is_up_to_date(): + us.extend(map(rfile, b.sources)) + ut.extend(b.targets) + else: + cs.extend(map(rfile, b.sources)) + ct.extend(b.targets) + self._changed_sources_list = SCons.Util.NodeList(cs) + self._changed_targets_list = SCons.Util.NodeList(ct) + self._unchanged_sources_list = SCons.Util.NodeList(us) + self._unchanged_targets_list = SCons.Util.NodeList(ut) + + def _get_changed_sources(self, *args, **kw): + try: + return self._changed_sources_list + except AttributeError: + self._get_changes() + return self._changed_sources_list + + def _get_changed_targets(self, *args, **kw): + try: + return self._changed_targets_list + except AttributeError: + self._get_changes() + return self._changed_targets_list + + def _get_source(self, *args, **kw): + #return SCons.Util.NodeList([rfile(self.batches[0].sources[0]).get_subst_proxy()]) + return rfile(self.batches[0].sources[0]).get_subst_proxy() + + def _get_sources(self, *args, **kw): + return SCons.Util.NodeList(map(lambda n: rfile(n).get_subst_proxy(), self.get_all_sources())) + + def _get_target(self, *args, **kw): + #return SCons.Util.NodeList([self.batches[0].targets[0].get_subst_proxy()]) + return self.batches[0].targets[0].get_subst_proxy() + + def _get_targets(self, *args, **kw): + return SCons.Util.NodeList(map(lambda n: n.get_subst_proxy(), self.get_all_targets())) + + def _get_unchanged_sources(self, *args, **kw): + try: + return self._unchanged_sources_list + except AttributeError: + self._get_changes() + return self._unchanged_sources_list + + def _get_unchanged_targets(self, *args, **kw): + try: + return self._unchanged_targets_list + except AttributeError: + self._get_changes() + return self._unchanged_targets_list + + def get_action_targets(self): + if not self.action_list: + return [] + targets_string = self.action_list[0].get_targets(self.env, self) + if targets_string[0] == '$': + targets_string = targets_string[1:] + return self.get_lvars()[targets_string] + + def set_action_list(self, action): + import SCons.Util + if not SCons.Util.is_List(action): + if not action: + import SCons.Errors + raise SCons.Errors.UserError, "Executor must have an action." + action = [action] + self.action_list = action + + def get_action_list(self): + return self.pre_actions + self.action_list + self.post_actions + + def get_all_targets(self): + """Returns all targets for all batches of this Executor.""" + result = [] + for batch in self.batches: + # TODO(1.5): remove the list() cast + result.extend(list(batch.targets)) + return result + + def get_all_sources(self): + """Returns all sources for all batches of this Executor.""" + result = [] + for batch in self.batches: + # TODO(1.5): remove the list() cast + result.extend(list(batch.sources)) + return result + + def get_all_children(self): + """Returns all unique children (dependencies) for all batches + of this Executor. + + The Taskmaster can recognize when it's already evaluated a + Node, so we don't have to make this list unique for its intended + canonical use case, but we expect there to be a lot of redundancy + (long lists of batched .cc files #including the same .h files + over and over), so removing the duplicates once up front should + save the Taskmaster a lot of work. + """ + result = SCons.Util.UniqueList([]) + for target in self.get_all_targets(): + result.extend(target.children()) + return result + + def get_all_prerequisites(self): + """Returns all unique (order-only) prerequisites for all batches + of this Executor. + """ + result = SCons.Util.UniqueList([]) + for target in self.get_all_targets(): + # TODO(1.5): remove the list() cast + result.extend(list(target.prerequisites)) + return result + + def get_action_side_effects(self): + + """Returns all side effects for all batches of this + Executor used by the underlying Action. + """ + result = SCons.Util.UniqueList([]) + for target in self.get_action_targets(): + result.extend(target.side_effects) + return result + + memoizer_counters.append(SCons.Memoize.CountValue('get_build_env')) + + def get_build_env(self): + """Fetch or create the appropriate build Environment + for this Executor. + """ + try: + return self._memo['get_build_env'] + except KeyError: + pass + + # Create the build environment instance with appropriate + # overrides. These get evaluated against the current + # environment's construction variables so that users can + # add to existing values by referencing the variable in + # the expansion. + overrides = {} + for odict in self.overridelist: + overrides.update(odict) + + import SCons.Defaults + env = self.env or SCons.Defaults.DefaultEnvironment() + build_env = env.Override(overrides) + + self._memo['get_build_env'] = build_env + + return build_env + + def get_build_scanner_path(self, scanner): + """Fetch the scanner path for this executor's targets and sources. + """ + env = self.get_build_env() + try: + cwd = self.batches[0].targets[0].cwd + except (IndexError, AttributeError): + cwd = None + return scanner.path(env, cwd, + self.get_all_targets(), + self.get_all_sources()) + + def get_kw(self, kw={}): + result = self.builder_kw.copy() + result.update(kw) + result['executor'] = self + return result + + def do_nothing(self, target, kw): + return 0 + + def do_execute(self, target, kw): + """Actually execute the action list.""" + env = self.get_build_env() + kw = self.get_kw(kw) + status = 0 + for act in self.get_action_list(): + #args = (self.get_all_targets(), self.get_all_sources(), env) + args = ([], [], env) + status = apply(act, args, kw) + if isinstance(status, SCons.Errors.BuildError): + status.executor = self + raise status + elif status: + msg = "Error %s" % status + raise SCons.Errors.BuildError( + errstr=msg, + node=self.batches[0].targets, + executor=self, + action=act) + return status + + # use extra indirection because with new-style objects (Python 2.2 + # and above) we can't override special methods, and nullify() needs + # to be able to do this. + + def __call__(self, target, **kw): + return self.do_execute(target, kw) + + def cleanup(self): + self._memo = {} + + def add_sources(self, sources): + """Add source files to this Executor's list. This is necessary + for "multi" Builders that can be called repeatedly to build up + a source file list for a given target.""" + # TODO(batch): extend to multiple batches + assert (len(self.batches) == 1) + # TODO(batch): remove duplicates? + sources = filter(lambda x, s=self.batches[0].sources: x not in s, sources) + self.batches[0].sources.extend(sources) + + def get_sources(self): + return self.batches[0].sources + + def add_batch(self, targets, sources): + """Add pair of associated target and source to this Executor's list. + This is necessary for "batch" Builders that can be called repeatedly + to build up a list of matching target and source files that will be + used in order to update multiple target files at once from multiple + corresponding source files, for tools like MSVC that support it.""" + self.batches.append(Batch(targets, sources)) + + def prepare(self): + """ + Preparatory checks for whether this Executor can go ahead + and (try to) build its targets. + """ + for s in self.get_all_sources(): + if s.missing(): + msg = "Source `%s' not found, needed by target `%s'." + raise SCons.Errors.StopError, msg % (s, self.batches[0].targets[0]) + + def add_pre_action(self, action): + self.pre_actions.append(action) + + def add_post_action(self, action): + self.post_actions.append(action) + + # another extra indirection for new-style objects and nullify... + + def my_str(self): + env = self.get_build_env() + get = lambda action, t=self.get_all_targets(), s=self.get_all_sources(), e=env: \ + action.genstring(t, s, e) + return string.join(map(get, self.get_action_list()), "\n") + + + def __str__(self): + return self.my_str() + + def nullify(self): + self.cleanup() + self.do_execute = self.do_nothing + self.my_str = lambda S=self: '' + + memoizer_counters.append(SCons.Memoize.CountValue('get_contents')) + + def get_contents(self): + """Fetch the signature contents. This is the main reason this + class exists, so we can compute this once and cache it regardless + of how many target or source Nodes there are. + """ + try: + return self._memo['get_contents'] + except KeyError: + pass + env = self.get_build_env() + get = lambda action, t=self.get_all_targets(), s=self.get_all_sources(), e=env: \ + action.get_contents(t, s, e) + result = string.join(map(get, self.get_action_list()), "") + self._memo['get_contents'] = result + return result + + def get_timestamp(self): + """Fetch a time stamp for this Executor. We don't have one, of + course (only files do), but this is the interface used by the + timestamp module. + """ + return 0 + + def scan_targets(self, scanner): + # TODO(batch): scan by batches + self.scan(scanner, self.get_all_targets()) + + def scan_sources(self, scanner): + # TODO(batch): scan by batches + if self.batches[0].sources: + self.scan(scanner, self.get_all_sources()) + + def scan(self, scanner, node_list): + """Scan a list of this Executor's files (targets or sources) for + implicit dependencies and update all of the targets with them. + This essentially short-circuits an N*M scan of the sources for + each individual target, which is a hell of a lot more efficient. + """ + env = self.get_build_env() + + # TODO(batch): scan by batches) + deps = [] + if scanner: + for node in node_list: + node.disambiguate() + s = scanner.select(node) + if not s: + continue + path = self.get_build_scanner_path(s) + deps.extend(node.get_implicit_deps(env, s, path)) + else: + kw = self.get_kw() + for node in node_list: + node.disambiguate() + scanner = node.get_env_scanner(env, kw) + if not scanner: + continue + scanner = scanner.select(node) + if not scanner: + continue + path = self.get_build_scanner_path(scanner) + deps.extend(node.get_implicit_deps(env, scanner, path)) + + deps.extend(self.get_implicit_deps()) + + for tgt in self.get_all_targets(): + tgt.add_to_implicit(deps) + + def _get_unignored_sources_key(self, node, ignore=()): + return (node,) + tuple(ignore) + + memoizer_counters.append(SCons.Memoize.CountDict('get_unignored_sources', _get_unignored_sources_key)) + + def get_unignored_sources(self, node, ignore=()): + key = (node,) + tuple(ignore) + try: + memo_dict = self._memo['get_unignored_sources'] + except KeyError: + memo_dict = {} + self._memo['get_unignored_sources'] = memo_dict + else: + try: + return memo_dict[key] + except KeyError: + pass + + if node: + # TODO: better way to do this (it's a linear search, + # but it may not be critical path)? + sourcelist = [] + for b in self.batches: + if node in b.targets: + sourcelist = b.sources + break + else: + sourcelist = self.get_all_sources() + if ignore: + idict = {} + for i in ignore: + idict[i] = 1 + sourcelist = filter(lambda s, i=idict: not i.has_key(s), sourcelist) + + memo_dict[key] = sourcelist + + return sourcelist + + def get_implicit_deps(self): + """Return the executor's implicit dependencies, i.e. the nodes of + the commands to be executed.""" + result = [] + build_env = self.get_build_env() + for act in self.get_action_list(): + deps = act.get_implicit_deps(self.get_all_targets(), + self.get_all_sources(), + build_env) + result.extend(deps) + return result + + + +_batch_executors = {} + +def GetBatchExecutor(key): + return _batch_executors[key] + +def AddBatchExecutor(key, executor): + assert not _batch_executors.has_key(key) + _batch_executors[key] = executor + +nullenv = None + + +def get_NullEnvironment(): + """Use singleton pattern for Null Environments.""" + global nullenv + + import SCons.Util + class NullEnvironment(SCons.Util.Null): + import SCons.CacheDir + _CacheDir_path = None + _CacheDir = SCons.CacheDir.CacheDir(None) + def get_CacheDir(self): + return self._CacheDir + + if not nullenv: + nullenv = NullEnvironment() + return nullenv + +class Null: + """A null Executor, with a null build Environment, that does + nothing when the rest of the methods call it. + + This might be able to disapper when we refactor things to + disassociate Builders from Nodes entirely, so we're not + going to worry about unit tests for this--at least for now. + """ + def __init__(self, *args, **kw): + if __debug__: logInstanceCreation(self, 'Executor.Null') + self.batches = [Batch(kw['targets'][:], [])] + def get_build_env(self): + return get_NullEnvironment() + def get_build_scanner_path(self): + return None + def cleanup(self): + pass + def prepare(self): + pass + def get_unignored_sources(self, *args, **kw): + return tuple(()) + def get_action_targets(self): + return [] + def get_action_list(self): + return [] + def get_all_targets(self): + return self.batches[0].targets + def get_all_sources(self): + return self.batches[0].targets[0].sources + def get_all_children(self): + return self.get_all_sources() + def get_all_prerequisites(self): + return [] + def get_action_side_effects(self): + return [] + def __call__(self, *args, **kw): + return 0 + def get_contents(self): + return '' + def _morph(self): + """Morph this Null executor to a real Executor object.""" + batches = self.batches + self.__class__ = Executor + self.__init__([]) + self.batches = batches + + # The following methods require morphing this Null Executor to a + # real Executor object. + + def add_pre_action(self, action): + self._morph() + self.add_pre_action(action) + def add_post_action(self, action): + self._morph() + self.add_post_action(action) + def set_action_list(self, action): + self._morph() + self.set_action_list(action) + + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/ExecutorTests.py b/src/engine/SCons/ExecutorTests.py new file mode 100644 index 0000000..a0194e9 --- /dev/null +++ b/src/engine/SCons/ExecutorTests.py @@ -0,0 +1,466 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/ExecutorTests.py 4577 2009/12/27 19:44:43 scons" + +import string +import sys +import unittest + +import SCons.Executor + + +class MyEnvironment: + def __init__(self, **kw): + self._dict = {} + self._dict.update(kw) + def __getitem__(self, key): + return self._dict[key] + def Override(self, overrides): + d = self._dict.copy() + d.update(overrides) + return apply(MyEnvironment, (), d) + def _update(self, dict): + self._dict.update(dict) + +class MyAction: + def __init__(self, actions=['action1', 'action2']): + self.actions = actions + def __call__(self, target, source, env, **kw): + for action in self.actions: + apply(action, (target, source, env), kw) + def genstring(self, target, source, env): + return string.join(['GENSTRING'] + map(str, self.actions) + target + source) + def get_contents(self, target, source, env): + return string.join(self.actions + target + source) + def get_implicit_deps(self, target, source, env): + return [] + +class MyBuilder: + def __init__(self, env, overrides): + self.env = env + self.overrides = overrides + self.action = MyAction() + +class MyNode: + def __init__(self, name=None, pre=[], post=[]): + self.name = name + self.implicit = [] + self.pre_actions = pre + self.post_actions = post + self.missing_val = None + def __str__(self): + return self.name + def build(self): + executor = SCons.Executor.Executor(MyAction(self.pre_actions + + [self.builder.action] + + self.post_actions), + self.builder.env, + [], + [self], + ['s1', 's2']) + apply(executor, (self), {}) + def get_env_scanner(self, env, kw): + return MyScanner('dep-') + def get_implicit_deps(self, env, scanner, path): + return [scanner.prefix + str(self)] + def add_to_implicit(self, deps): + self.implicit.extend(deps) + def missing(self): + return self.missing_val + def calc_signature(self, calc): + return 'cs-'+calc+'-'+self.name + def disambiguate(self): + return self + +class MyScanner: + def __init__(self, prefix): + self.prefix = prefix + def path(self, env, cwd, target, source): + return () + def select(self, node): + return self + +class ExecutorTestCase(unittest.TestCase): + + def test__init__(self): + """Test creating an Executor""" + source_list = ['s1', 's2'] + x = SCons.Executor.Executor('a', 'e', ['o'], 't', source_list) + assert x.action_list == ['a'], x.action_list + assert x.env == 'e', x.env + assert x.overridelist == ['o'], x.overridelist + targets = x.get_all_targets() + assert targets == ['t'], targets + source_list.append('s3') + sources = x.get_all_sources() + assert sources == ['s1', 's2'], sources + try: + x = SCons.Executor.Executor(None, 'e', ['o'], 't', source_list) + except SCons.Errors.UserError: + pass + else: + raise "Did not catch expected UserError" + + def test__action_list(self): + """Test the {get,set}_action_list() methods""" + x = SCons.Executor.Executor('a', 'e', 'o', 't', ['s1', 's2']) + + l = x.get_action_list() + assert l == ['a'], l + + x.add_pre_action('pre') + x.add_post_action('post') + l = x.get_action_list() + assert l == ['pre', 'a', 'post'], l + + x.set_action_list('b') + l = x.get_action_list() + assert l == ['pre', 'b', 'post'], l + + x.set_action_list(['c']) + l = x.get_action_list() + assert l == ['pre', 'c', 'post'], l + + def test_get_build_env(self): + """Test fetching and generating a build environment""" + x = SCons.Executor.Executor(MyAction(), MyEnvironment(e=1), [], + 't', ['s1', 's2']) + x.env = MyEnvironment(eee=1) + be = x.get_build_env() + assert be['eee'] == 1, be + + env = MyEnvironment(X='xxx') + x = SCons.Executor.Executor(MyAction(), + env, + [{'O':'o2'}], + 't', + ['s1', 's2']) + be = x.get_build_env() + assert be['O'] == 'o2', be['O'] + assert be['X'] == 'xxx', be['X'] + + env = MyEnvironment(Y='yyy') + overrides = [{'O':'ob3'}, {'O':'oo3'}] + x = SCons.Executor.Executor(MyAction(), env, overrides, ['t'], ['s']) + be = x.get_build_env() + assert be['O'] == 'oo3', be['O'] + assert be['Y'] == 'yyy', be['Y'] + overrides = [{'O':'ob3'}] + x = SCons.Executor.Executor(MyAction(), env, overrides, ['t'], ['s']) + be = x.get_build_env() + assert be['O'] == 'ob3', be['O'] + assert be['Y'] == 'yyy', be['Y'] + + def test_get_build_scanner_path(self): + """Test fetching the path for the specified scanner.""" + t = MyNode('t') + t.cwd = 'here' + x = SCons.Executor.Executor(MyAction(), + MyEnvironment(SCANNERVAL='sss'), + [], + [t], + ['s1', 's2']) + + class LocalScanner: + def path(self, env, dir, target, source): + target = map(str, target) + source = map(str, source) + return "scanner: %s, %s, %s, %s" % (env['SCANNERVAL'], dir, target, source) + s = LocalScanner() + + p = x.get_build_scanner_path(s) + assert p == "scanner: sss, here, ['t'], ['s1', 's2']", p + + def test_get_kw(self): + """Test the get_kw() method""" + t = MyNode('t') + x = SCons.Executor.Executor(MyAction(), + MyEnvironment(), + [], + [t], + ['s1', 's2'], + builder_kw={'X':1, 'Y':2}) + kw = x.get_kw() + assert kw == {'X':1, 'Y':2, 'executor':x}, kw + kw = x.get_kw({'Z':3}) + assert kw == {'X':1, 'Y':2, 'Z':3, 'executor':x}, kw + kw = x.get_kw({'X':4}) + assert kw == {'X':4, 'Y':2, 'executor':x}, kw + + def test__call__(self): + """Test calling an Executor""" + result = [] + def pre(target, source, env, result=result, **kw): + result.append('pre') + def action1(target, source, env, result=result, **kw): + result.append('action1') + def action2(target, source, env, result=result, **kw): + result.append('action2') + def post(target, source, env, result=result, **kw): + result.append('post') + + env = MyEnvironment() + a = MyAction([action1, action2]) + t = MyNode('t') + + x = SCons.Executor.Executor(a, env, [], [t], ['s1', 's2']) + x.add_pre_action(pre) + x.add_post_action(post) + x(t) + assert result == ['pre', 'action1', 'action2', 'post'], result + del result[:] + + def pre_err(target, source, env, result=result, **kw): + result.append('pre_err') + return 1 + + x = SCons.Executor.Executor(a, env, [], [t], ['s1', 's2']) + x.add_pre_action(pre_err) + x.add_post_action(post) + try: + x(t) + except SCons.Errors.BuildError: + pass + else: + raise Exception, "Did not catch expected BuildError" + assert result == ['pre_err'], result + del result[:] + + def test_cleanup(self): + """Test cleaning up an Executor""" + orig_env = MyEnvironment(e=1) + x = SCons.Executor.Executor('b', orig_env, [{'o':1}], + 't', ['s1', 's2']) + + be = x.get_build_env() + assert be['e'] == 1, be['e'] + + x.cleanup() + + x.env = MyEnvironment(eee=1) + be = x.get_build_env() + assert be['eee'] == 1, be['eee'] + + x.cleanup() + + be = x.get_build_env() + assert be['eee'] == 1, be['eee'] + + def test_add_sources(self): + """Test adding sources to an Executor""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + sources = x.get_all_sources() + assert sources == ['s1', 's2'], sources + + x.add_sources(['s1', 's2']) + sources = x.get_all_sources() + assert sources == ['s1', 's2'], sources + + x.add_sources(['s3', 's1', 's4']) + sources = x.get_all_sources() + assert sources == ['s1', 's2', 's3', 's4'], sources + + def test_get_sources(self): + """Test getting sources from an Executor""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + sources = x.get_sources() + assert sources == ['s1', 's2'], sources + + x.add_sources(['s1', 's2']) + sources = x.get_sources() + assert sources == ['s1', 's2'], sources + + x.add_sources(['s3', 's1', 's4']) + sources = x.get_sources() + assert sources == ['s1', 's2', 's3', 's4'], sources + + def test_prepare(self): + """Test the Executor's prepare() method""" + env = MyEnvironment() + t1 = MyNode('t1') + s1 = MyNode('s1') + s2 = MyNode('s2') + s3 = MyNode('s3') + x = SCons.Executor.Executor('b', env, [{}], [t1], [s1, s2, s3]) + + s2.missing_val = True + + try: + r = x.prepare() + except SCons.Errors.StopError, e: + assert str(e) == "Source `s2' not found, needed by target `t1'.", e + else: + raise AssertionError, "did not catch expected StopError: %s" % r + + def test_add_pre_action(self): + """Test adding pre-actions to an Executor""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + x.add_pre_action('a1') + assert x.pre_actions == ['a1'] + x.add_pre_action('a2') + assert x.pre_actions == ['a1', 'a2'] + + def test_add_post_action(self): + """Test adding post-actions to an Executor""" + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + x.add_post_action('a1') + assert x.post_actions == ['a1'] + x.add_post_action('a2') + assert x.post_actions == ['a1', 'a2'] + + def test___str__(self): + """Test the __str__() method""" + env = MyEnvironment(S='string') + + x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s']) + c = str(x) + assert c == 'GENSTRING action1 action2 t s', c + + x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s']) + x.add_pre_action(MyAction(['pre'])) + x.add_post_action(MyAction(['post'])) + c = str(x) + expect = 'GENSTRING pre t s\n' + \ + 'GENSTRING action1 action2 t s\n' + \ + 'GENSTRING post t s' + assert c == expect, c + + def test_nullify(self): + """Test the nullify() method""" + env = MyEnvironment(S='string') + + result = [] + def action1(target, source, env, result=result, **kw): + result.append('action1') + + env = MyEnvironment() + a = MyAction([action1]) + x = SCons.Executor.Executor(a, env, [], ['t1', 't2'], ['s1', 's2']) + + x(MyNode('', [], [])) + assert result == ['action1'], result + s = str(x) + assert s[:10] == 'GENSTRING ', s + + del result[:] + x.nullify() + + assert result == [], result + x(MyNode('', [], [])) + assert result == [], result + s = str(x) + assert s == '', s + + def test_get_contents(self): + """Test fetching the signatures contents""" + env = MyEnvironment(C='contents') + + x = SCons.Executor.Executor(MyAction(), env, [], ['t'], ['s']) + c = x.get_contents() + assert c == 'action1 action2 t s', c + + x = SCons.Executor.Executor(MyAction(actions=['grow']), env, [], + ['t'], ['s']) + x.add_pre_action(MyAction(['pre'])) + x.add_post_action(MyAction(['post'])) + c = x.get_contents() + assert c == 'pre t sgrow t spost t s', c + + def test_get_timestamp(self): + """Test fetching the "timestamp" """ + x = SCons.Executor.Executor('b', 'e', 'o', 't', ['s1', 's2']) + ts = x.get_timestamp() + assert ts == 0, ts + + def test_scan_targets(self): + """Test scanning the targets for implicit dependencies""" + env = MyEnvironment(S='string') + t1 = MyNode('t1') + t2 = MyNode('t2') + sources = [MyNode('s1'), MyNode('s2')] + x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources) + + deps = x.scan_targets(None) + assert t1.implicit == ['dep-t1', 'dep-t2'], t1.implicit + assert t2.implicit == ['dep-t1', 'dep-t2'], t2.implicit + + t1.implicit = [] + t2.implicit = [] + + deps = x.scan_targets(MyScanner('scanner-')) + assert t1.implicit == ['scanner-t1', 'scanner-t2'], t1.implicit + assert t2.implicit == ['scanner-t1', 'scanner-t2'], t2.implicit + + def test_scan_sources(self): + """Test scanning the sources for implicit dependencies""" + env = MyEnvironment(S='string') + t1 = MyNode('t1') + t2 = MyNode('t2') + sources = [MyNode('s1'), MyNode('s2')] + x = SCons.Executor.Executor(MyAction(), env, [{}], [t1, t2], sources) + + deps = x.scan_sources(None) + assert t1.implicit == ['dep-s1', 'dep-s2'], t1.implicit + assert t2.implicit == ['dep-s1', 'dep-s2'], t2.implicit + + t1.implicit = [] + t2.implicit = [] + + deps = x.scan_sources(MyScanner('scanner-')) + assert t1.implicit == ['scanner-s1', 'scanner-s2'], t1.implicit + assert t2.implicit == ['scanner-s1', 'scanner-s2'], t2.implicit + + def test_get_unignored_sources(self): + """Test fetching the unignored source list""" + env = MyEnvironment() + s1 = MyNode('s1') + s2 = MyNode('s2') + s3 = MyNode('s3') + x = SCons.Executor.Executor('b', env, [{}], [], [s1, s2, s3]) + + r = x.get_unignored_sources(None, []) + assert r == [s1, s2, s3], map(str, r) + + r = x.get_unignored_sources(None, [s2]) + assert r == [s1, s3], map(str, r) + + r = x.get_unignored_sources(None, [s1, s3]) + assert r == [s2], map(str, r) + + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ ExecutorTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Job.py b/src/engine/SCons/Job.py new file mode 100644 index 0000000..6d652bf --- /dev/null +++ b/src/engine/SCons/Job.py @@ -0,0 +1,435 @@ +"""SCons.Job + +This module defines the Serial and Parallel classes that execute tasks to +complete a build. The Jobs class provides a higher level interface to start, +stop, and wait on jobs. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Job.py 4577 2009/12/27 19:44:43 scons" + +import os +import signal + +import SCons.Errors + +# The default stack size (in kilobytes) of the threads used to execute +# jobs in parallel. +# +# We use a stack size of 256 kilobytes. The default on some platforms +# is too large and prevents us from creating enough threads to fully +# parallelized the build. For example, the default stack size on linux +# is 8 MBytes. + +explicit_stack_size = None +default_stack_size = 256 + +interrupt_msg = 'Build interrupted.' + + +class InterruptState: + def __init__(self): + self.interrupted = False + + def set(self): + self.interrupted = True + + def __call__(self): + return self.interrupted + + +class Jobs: + """An instance of this class initializes N jobs, and provides + methods for starting, stopping, and waiting on all N jobs. + """ + + def __init__(self, num, taskmaster): + """ + create 'num' jobs using the given taskmaster. + + If 'num' is 1 or less, then a serial job will be used, + otherwise a parallel job with 'num' worker threads will + be used. + + The 'num_jobs' attribute will be set to the actual number of jobs + allocated. If more than one job is requested but the Parallel + class can't do it, it gets reset to 1. Wrapping interfaces that + care should check the value of 'num_jobs' after initialization. + """ + + self.job = None + if num > 1: + stack_size = explicit_stack_size + if stack_size is None: + stack_size = default_stack_size + + try: + self.job = Parallel(taskmaster, num, stack_size) + self.num_jobs = num + except NameError: + pass + if self.job is None: + self.job = Serial(taskmaster) + self.num_jobs = 1 + + def run(self, postfunc=lambda: None): + """Run the jobs. + + postfunc() will be invoked after the jobs has run. It will be + invoked even if the jobs are interrupted by a keyboard + interrupt (well, in fact by a signal such as either SIGINT, + SIGTERM or SIGHUP). The execution of postfunc() is protected + against keyboard interrupts and is guaranteed to run to + completion.""" + self._setup_sig_handler() + try: + self.job.start() + finally: + postfunc() + self._reset_sig_handler() + + def were_interrupted(self): + """Returns whether the jobs were interrupted by a signal.""" + return self.job.interrupted() + + def _setup_sig_handler(self): + """Setup an interrupt handler so that SCons can shutdown cleanly in + various conditions: + + a) SIGINT: Keyboard interrupt + b) SIGTERM: kill or system shutdown + c) SIGHUP: Controlling shell exiting + + We handle all of these cases by stopping the taskmaster. It + turns out that it very difficult to stop the build process + by throwing asynchronously an exception such as + KeyboardInterrupt. For example, the python Condition + variables (threading.Condition) and Queue's do not seem to + asynchronous-exception-safe. It would require adding a whole + bunch of try/finally block and except KeyboardInterrupt all + over the place. + + Note also that we have to be careful to handle the case when + SCons forks before executing another process. In that case, we + want the child to exit immediately. + """ + def handler(signum, stack, self=self, parentpid=os.getpid()): + if os.getpid() == parentpid: + self.job.taskmaster.stop() + self.job.interrupted.set() + else: + os._exit(2) + + self.old_sigint = signal.signal(signal.SIGINT, handler) + self.old_sigterm = signal.signal(signal.SIGTERM, handler) + try: + self.old_sighup = signal.signal(signal.SIGHUP, handler) + except AttributeError: + pass + + def _reset_sig_handler(self): + """Restore the signal handlers to their previous state (before the + call to _setup_sig_handler().""" + + signal.signal(signal.SIGINT, self.old_sigint) + signal.signal(signal.SIGTERM, self.old_sigterm) + try: + signal.signal(signal.SIGHUP, self.old_sighup) + except AttributeError: + pass + +class Serial: + """This class is used to execute tasks in series, and is more efficient + than Parallel, but is only appropriate for non-parallel builds. Only + one instance of this class should be in existence at a time. + + This class is not thread safe. + """ + + def __init__(self, taskmaster): + """Create a new serial job given a taskmaster. + + The taskmaster's next_task() method should return the next task + that needs to be executed, or None if there are no more tasks. The + taskmaster's executed() method will be called for each task when it + is successfully executed or failed() will be called if it failed to + execute (e.g. execute() raised an exception).""" + + self.taskmaster = taskmaster + self.interrupted = InterruptState() + + def start(self): + """Start the job. This will begin pulling tasks from the taskmaster + and executing them, and return when there are no more tasks. If a task + fails to execute (i.e. execute() raises an exception), then the job will + stop.""" + + while 1: + task = self.taskmaster.next_task() + + if task is None: + break + + try: + task.prepare() + if task.needs_execute(): + task.execute() + except: + if self.interrupted(): + try: + raise SCons.Errors.BuildError( + task.targets[0], errstr=interrupt_msg) + except: + task.exception_set() + else: + task.exception_set() + + # Let the failed() callback function arrange for the + # build to stop if that's appropriate. + task.failed() + else: + task.executed() + + task.postprocess() + self.taskmaster.cleanup() + + +# Trap import failure so that everything in the Job module but the +# Parallel class (and its dependent classes) will work if the interpreter +# doesn't support threads. +try: + import Queue + import threading +except ImportError: + pass +else: + class Worker(threading.Thread): + """A worker thread waits on a task to be posted to its request queue, + dequeues the task, executes it, and posts a tuple including the task + and a boolean indicating whether the task executed successfully. """ + + def __init__(self, requestQueue, resultsQueue, interrupted): + threading.Thread.__init__(self) + self.setDaemon(1) + self.requestQueue = requestQueue + self.resultsQueue = resultsQueue + self.interrupted = interrupted + self.start() + + def run(self): + while 1: + task = self.requestQueue.get() + + if task is None: + # The "None" value is used as a sentinel by + # ThreadPool.cleanup(). This indicates that there + # are no more tasks, so we should quit. + break + + try: + if self.interrupted(): + raise SCons.Errors.BuildError( + task.targets[0], errstr=interrupt_msg) + task.execute() + except: + task.exception_set() + ok = False + else: + ok = True + + self.resultsQueue.put((task, ok)) + + class ThreadPool: + """This class is responsible for spawning and managing worker threads.""" + + def __init__(self, num, stack_size, interrupted): + """Create the request and reply queues, and 'num' worker threads. + + One must specify the stack size of the worker threads. The + stack size is specified in kilobytes. + """ + self.requestQueue = Queue.Queue(0) + self.resultsQueue = Queue.Queue(0) + + try: + prev_size = threading.stack_size(stack_size*1024) + except AttributeError, e: + # Only print a warning if the stack size has been + # explicitly set. + if not explicit_stack_size is None: + msg = "Setting stack size is unsupported by this version of Python:\n " + \ + e.args[0] + SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg) + except ValueError, e: + msg = "Setting stack size failed:\n " + str(e) + SCons.Warnings.warn(SCons.Warnings.StackSizeWarning, msg) + + # Create worker threads + self.workers = [] + for _ in range(num): + worker = Worker(self.requestQueue, self.resultsQueue, interrupted) + self.workers.append(worker) + + # Once we drop Python 1.5 we can change the following to: + #if 'prev_size' in locals(): + if 'prev_size' in locals().keys(): + threading.stack_size(prev_size) + + def put(self, task): + """Put task into request queue.""" + self.requestQueue.put(task) + + def get(self): + """Remove and return a result tuple from the results queue.""" + return self.resultsQueue.get() + + def preparation_failed(self, task): + self.resultsQueue.put((task, False)) + + def cleanup(self): + """ + Shuts down the thread pool, giving each worker thread a + chance to shut down gracefully. + """ + # For each worker thread, put a sentinel "None" value + # on the requestQueue (indicating that there's no work + # to be done) so that each worker thread will get one and + # terminate gracefully. + for _ in self.workers: + self.requestQueue.put(None) + + # Wait for all of the workers to terminate. + # + # If we don't do this, later Python versions (2.4, 2.5) often + # seem to raise exceptions during shutdown. This happens + # in requestQueue.get(), as an assertion failure that + # requestQueue.not_full is notified while not acquired, + # seemingly because the main thread has shut down (or is + # in the process of doing so) while the workers are still + # trying to pull sentinels off the requestQueue. + # + # Normally these terminations should happen fairly quickly, + # but we'll stick a one-second timeout on here just in case + # someone gets hung. + for worker in self.workers: + worker.join(1.0) + self.workers = [] + + class Parallel: + """This class is used to execute tasks in parallel, and is somewhat + less efficient than Serial, but is appropriate for parallel builds. + + This class is thread safe. + """ + + def __init__(self, taskmaster, num, stack_size): + """Create a new parallel job given a taskmaster. + + The taskmaster's next_task() method should return the next + task that needs to be executed, or None if there are no more + tasks. The taskmaster's executed() method will be called + for each task when it is successfully executed or failed() + will be called if the task failed to execute (i.e. execute() + raised an exception). + + Note: calls to taskmaster are serialized, but calls to + execute() on distinct tasks are not serialized, because + that is the whole point of parallel jobs: they can execute + multiple tasks simultaneously. """ + + self.taskmaster = taskmaster + self.interrupted = InterruptState() + self.tp = ThreadPool(num, stack_size, self.interrupted) + + self.maxjobs = num + + def start(self): + """Start the job. This will begin pulling tasks from the + taskmaster and executing them, and return when there are no + more tasks. If a task fails to execute (i.e. execute() raises + an exception), then the job will stop.""" + + jobs = 0 + + while 1: + # Start up as many available tasks as we're + # allowed to. + while jobs < self.maxjobs: + task = self.taskmaster.next_task() + if task is None: + break + + try: + # prepare task for execution + task.prepare() + except: + task.exception_set() + task.failed() + task.postprocess() + else: + if task.needs_execute(): + # dispatch task + self.tp.put(task) + jobs = jobs + 1 + else: + task.executed() + task.postprocess() + + if not task and not jobs: break + + # Let any/all completed tasks finish up before we go + # back and put the next batch of tasks on the queue. + while 1: + task, ok = self.tp.get() + jobs = jobs - 1 + + if ok: + task.executed() + else: + if self.interrupted(): + try: + raise SCons.Errors.BuildError( + task.targets[0], errstr=interrupt_msg) + except: + task.exception_set() + + # Let the failed() callback function arrange + # for the build to stop if that's appropriate. + task.failed() + + task.postprocess() + + if self.tp.resultsQueue.empty(): + break + + self.tp.cleanup() + self.taskmaster.cleanup() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/JobTests.py b/src/engine/SCons/JobTests.py new file mode 100644 index 0000000..00e83bd --- /dev/null +++ b/src/engine/SCons/JobTests.py @@ -0,0 +1,539 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/JobTests.py 4577 2009/12/27 19:44:43 scons" + +import unittest +import random +import math +import SCons.Job +import sys +import time + +# a large number +num_sines = 10000 + +# how many parallel jobs to perform for the test +num_jobs = 11 + +# how many tasks to perform for the test +num_tasks = num_jobs*5 + +class DummyLock: + "fake lock class to use if threads are not supported" + def acquire(self): + pass + + def release(self): + pass + +class NoThreadsException: + "raised by the ParallelTestCase if threads are not supported" + + def __str__(self): + return "the interpreter doesn't support threads" + +class Task: + """A dummy task class for testing purposes.""" + + def __init__(self, i, taskmaster): + self.i = i + self.taskmaster = taskmaster + self.was_executed = 0 + self.was_prepared = 0 + + def prepare(self): + self.was_prepared = 1 + + def _do_something(self): + pass + + def needs_execute(self): + return True + + def execute(self): + self.taskmaster.test_case.failUnless(self.was_prepared, + "the task wasn't prepared") + + self.taskmaster.guard.acquire() + self.taskmaster.begin_list.append(self.i) + self.taskmaster.guard.release() + + self._do_something() + + self.was_executed = 1 + + self.taskmaster.guard.acquire() + self.taskmaster.end_list.append(self.i) + self.taskmaster.guard.release() + + def executed(self): + self.taskmaster.num_executed = self.taskmaster.num_executed + 1 + + self.taskmaster.test_case.failUnless(self.was_prepared, + "the task wasn't prepared") + self.taskmaster.test_case.failUnless(self.was_executed, + "the task wasn't really executed") + self.taskmaster.test_case.failUnless(isinstance(self, Task), + "the task wasn't really a Task instance") + + def failed(self): + self.taskmaster.num_failed = self.taskmaster.num_failed + 1 + self.taskmaster.stop = 1 + self.taskmaster.test_case.failUnless(self.was_prepared, + "the task wasn't prepared") + + def postprocess(self): + self.taskmaster.num_postprocessed = self.taskmaster.num_postprocessed + 1 + +class RandomTask(Task): + def _do_something(self): + # do something that will take some random amount of time: + for i in range(random.randrange(0, num_sines, 1)): + x = math.sin(i) + time.sleep(0.01) + +class ExceptionTask: + """A dummy task class for testing purposes.""" + + def __init__(self, i, taskmaster): + self.taskmaster = taskmaster + self.was_prepared = 0 + + def prepare(self): + self.was_prepared = 1 + + def needs_execute(self): + return True + + def execute(self): + raise Exception + + def executed(self): + self.taskmaster.num_executed = self.taskmaster.num_executed + 1 + + self.taskmaster.test_case.failUnless(self.was_prepared, + "the task wasn't prepared") + self.taskmaster.test_case.failUnless(self.was_executed, + "the task wasn't really executed") + self.taskmaster.test_case.failUnless(self.__class__ is Task, + "the task wasn't really a Task instance") + + def failed(self): + self.taskmaster.num_failed = self.taskmaster.num_failed + 1 + self.taskmaster.stop = 1 + self.taskmaster.test_case.failUnless(self.was_prepared, + "the task wasn't prepared") + + def postprocess(self): + self.taskmaster.num_postprocessed = self.taskmaster.num_postprocessed + 1 + + def exception_set(self): + self.taskmaster.exception_set() + +class Taskmaster: + """A dummy taskmaster class for testing the job classes.""" + + def __init__(self, n, test_case, Task): + """n is the number of dummy tasks to perform.""" + + self.test_case = test_case + self.stop = None + self.num_tasks = n + self.num_iterated = 0 + self.num_executed = 0 + self.num_failed = 0 + self.num_postprocessed = 0 + self.Task = Task + # 'guard' guards 'task_begin_list' and 'task_end_list' + try: + import threading + self.guard = threading.Lock() + except: + self.guard = DummyLock() + + # keep track of the order tasks are begun in + self.begin_list = [] + + # keep track of the order tasks are completed in + self.end_list = [] + + def next_task(self): + if self.stop or self.all_tasks_are_iterated(): + return None + else: + self.num_iterated = self.num_iterated + 1 + return self.Task(self.num_iterated, self) + + def all_tasks_are_executed(self): + return self.num_executed == self.num_tasks + + def all_tasks_are_iterated(self): + return self.num_iterated == self.num_tasks + + def all_tasks_are_postprocessed(self): + return self.num_postprocessed == self.num_tasks + + def tasks_were_serial(self): + "analyze the task order to see if they were serial" + serial = 1 # assume the tasks were serial + for i in range(num_tasks): + serial = serial and (self.begin_list[i] + == self.end_list[i] + == (i + 1)) + return serial + + def exception_set(self): + pass + + def cleanup(self): + pass + +SaveThreadPool = None +ThreadPoolCallList = [] + +class ParallelTestCase(unittest.TestCase): + def runTest(self): + "test parallel jobs" + + try: + import threading + except: + raise NoThreadsException() + + taskmaster = Taskmaster(num_tasks, self, RandomTask) + jobs = SCons.Job.Jobs(num_jobs, taskmaster) + jobs.run() + + self.failUnless(not taskmaster.tasks_were_serial(), + "the tasks were not executed in parallel") + self.failUnless(taskmaster.all_tasks_are_executed(), + "all the tests were not executed") + self.failUnless(taskmaster.all_tasks_are_iterated(), + "all the tests were not iterated over") + self.failUnless(taskmaster.all_tasks_are_postprocessed(), + "all the tests were not postprocessed") + self.failIf(taskmaster.num_failed, + "some task(s) failed to execute") + + # Verify that parallel jobs will pull all of the completed tasks + # out of the queue at once, instead of one by one. We do this by + # replacing the default ThreadPool class with one that records the + # order in which tasks are put() and get() to/from the pool, and + # which sleeps a little bit before call get() to let the initial + # tasks complete and get their notifications on the resultsQueue. + + class SleepTask(Task): + def _do_something(self): + time.sleep(0.1) + + global SaveThreadPool + SaveThreadPool = SCons.Job.ThreadPool + + class WaitThreadPool(SaveThreadPool): + def put(self, task): + ThreadPoolCallList.append('put(%s)' % task.i) + return SaveThreadPool.put(self, task) + def get(self): + time.sleep(0.5) + result = SaveThreadPool.get(self) + ThreadPoolCallList.append('get(%s)' % result[0].i) + return result + + SCons.Job.ThreadPool = WaitThreadPool + + try: + taskmaster = Taskmaster(3, self, SleepTask) + jobs = SCons.Job.Jobs(2, taskmaster) + jobs.run() + + # The key here is that we get(1) and get(2) from the + # resultsQueue before we put(3), but get(1) and get(2) can + # be in either order depending on how the first two parallel + # tasks get scheduled by the operating system. + expect = [ + ['put(1)', 'put(2)', 'get(1)', 'get(2)', 'put(3)', 'get(3)'], + ['put(1)', 'put(2)', 'get(2)', 'get(1)', 'put(3)', 'get(3)'], + ] + assert ThreadPoolCallList in expect, ThreadPoolCallList + + finally: + SCons.Job.ThreadPool = SaveThreadPool + +class SerialTestCase(unittest.TestCase): + def runTest(self): + "test a serial job" + + taskmaster = Taskmaster(num_tasks, self, RandomTask) + jobs = SCons.Job.Jobs(1, taskmaster) + jobs.run() + + self.failUnless(taskmaster.tasks_were_serial(), + "the tasks were not executed in series") + self.failUnless(taskmaster.all_tasks_are_executed(), + "all the tests were not executed") + self.failUnless(taskmaster.all_tasks_are_iterated(), + "all the tests were not iterated over") + self.failUnless(taskmaster.all_tasks_are_postprocessed(), + "all the tests were not postprocessed") + self.failIf(taskmaster.num_failed, + "some task(s) failed to execute") + +class NoParallelTestCase(unittest.TestCase): + def runTest(self): + "test handling lack of parallel support" + def NoParallel(tm, num, stack_size): + raise NameError + save_Parallel = SCons.Job.Parallel + SCons.Job.Parallel = NoParallel + try: + taskmaster = Taskmaster(num_tasks, self, RandomTask) + jobs = SCons.Job.Jobs(2, taskmaster) + self.failUnless(jobs.num_jobs == 1, + "unexpected number of jobs %d" % jobs.num_jobs) + jobs.run() + self.failUnless(taskmaster.tasks_were_serial(), + "the tasks were not executed in series") + self.failUnless(taskmaster.all_tasks_are_executed(), + "all the tests were not executed") + self.failUnless(taskmaster.all_tasks_are_iterated(), + "all the tests were not iterated over") + self.failUnless(taskmaster.all_tasks_are_postprocessed(), + "all the tests were not postprocessed") + self.failIf(taskmaster.num_failed, + "some task(s) failed to execute") + finally: + SCons.Job.Parallel = save_Parallel + + +class SerialExceptionTestCase(unittest.TestCase): + def runTest(self): + "test a serial job with tasks that raise exceptions" + + taskmaster = Taskmaster(num_tasks, self, ExceptionTask) + jobs = SCons.Job.Jobs(1, taskmaster) + jobs.run() + + self.failIf(taskmaster.num_executed, + "a task was executed") + self.failUnless(taskmaster.num_iterated == 1, + "exactly one task should have been iterated") + self.failUnless(taskmaster.num_failed == 1, + "exactly one task should have failed") + self.failUnless(taskmaster.num_postprocessed == 1, + "exactly one task should have been postprocessed") + +class ParallelExceptionTestCase(unittest.TestCase): + def runTest(self): + "test parallel jobs with tasks that raise exceptions" + + taskmaster = Taskmaster(num_tasks, self, ExceptionTask) + jobs = SCons.Job.Jobs(num_jobs, taskmaster) + jobs.run() + + self.failIf(taskmaster.num_executed, + "a task was executed") + self.failUnless(taskmaster.num_iterated >= 1, + "one or more task should have been iterated") + self.failUnless(taskmaster.num_failed >= 1, + "one or more tasks should have failed") + self.failUnless(taskmaster.num_postprocessed >= 1, + "one or more tasks should have been postprocessed") + +#--------------------------------------------------------------------- +# Above tested Job object with contrived Task and Taskmaster objects. +# Now test Job object with actual Task and Taskmaster objects. + +import SCons.Taskmaster +import SCons.Node +import time + +class DummyNodeInfo: + def update(self, obj): + pass + +class testnode (SCons.Node.Node): + def __init__(self): + SCons.Node.Node.__init__(self) + self.expect_to_be = SCons.Node.executed + self.ninfo = DummyNodeInfo() + +class goodnode (testnode): + def __init__(self): + SCons.Node.Node.__init__(self) + self.expect_to_be = SCons.Node.up_to_date + self.ninfo = DummyNodeInfo() + +class slowgoodnode (goodnode): + def prepare(self): + # Delay to allow scheduled Jobs to run while the dispatcher + # sleeps. Keep this short because it affects the time taken + # by this test. + time.sleep(0.15) + goodnode.prepare(self) + +class badnode (goodnode): + def __init__(self): + goodnode.__init__(self) + self.expect_to_be = SCons.Node.failed + def build(self, **kw): + raise Exception, 'badnode exception' + +class slowbadnode (badnode): + def build(self, **kw): + # Appears to take a while to build, allowing faster builds to + # overlap. Time duration is not especially important, but if + # it is faster than slowgoodnode then these could complete + # while the scheduler is sleeping. + time.sleep(0.05) + raise Exception, 'slowbadnode exception' + +class badpreparenode (badnode): + def prepare(self): + raise Exception, 'badpreparenode exception' + +class _SConsTaskTest(unittest.TestCase): + + def _test_seq(self, num_jobs): + for node_seq in [ + [goodnode], + [badnode], + [slowbadnode], + [slowgoodnode], + [badpreparenode], + [goodnode, badnode], + [slowgoodnode, badnode], + [goodnode, slowbadnode], + [goodnode, goodnode, goodnode, slowbadnode], + [goodnode, slowbadnode, badpreparenode, slowgoodnode], + [goodnode, slowbadnode, slowgoodnode, badnode] + ]: + + self._do_test(num_jobs, node_seq) + + def _do_test(self, num_jobs, node_seq): + + testnodes = [] + for tnum in range(num_tasks): + testnodes.append(node_seq[tnum % len(node_seq)]()) + + taskmaster = SCons.Taskmaster.Taskmaster(testnodes, + tasker=SCons.Taskmaster.AlwaysTask) + + jobs = SCons.Job.Jobs(num_jobs, taskmaster) + + # Exceptions thrown by tasks are not actually propagated to + # this level, but are instead stored in the Taskmaster. + + jobs.run() + + # Now figure out if tests proceeded correctly. The first test + # that fails will shutdown the initiation of subsequent tests, + # but any tests currently queued for execution will still be + # processed, and any tests that completed before the failure + # would have resulted in new tests being queued for execution. + + # Apply the following operational heuristics of Job.py: + # 0) An initial jobset of tasks will be queued before any + # good/bad results are obtained (from "execute" of task in + # thread). + # 1) A goodnode will complete immediately on its thread and + # allow another node to be queued for execution. + # 2) A badnode will complete immediately and suppress any + # subsequent execution queuing, but all currently queued + # tasks will still be processed. + # 3) A slowbadnode will fail later. It will block slots in + # the job queue. Nodes that complete immediately will + # allow other nodes to be queued in their place, and this + # will continue until either (#2) above or until all job + # slots are filled with slowbadnode entries. + + # One approach to validating this test would be to try to + # determine exactly how many nodes executed, how many didn't, + # and the results of each, and then to assert failure on any + # mismatch (including the total number of built nodes). + # However, while this is possible to do for a single-processor + # system, it is nearly impossible to predict correctly for a + # multi-processor system and still test the characteristics of + # delayed execution nodes. Stated another way, multithreading + # is inherently non-deterministic unless you can completely + # characterize the entire system, and since that's not + # possible here, we shouldn't try. + + # Therefore, this test will simply scan the set of nodes to + # see if the node was executed or not and if it was executed + # that it obtained the expected value for that node + # (i.e. verifying we don't get failure crossovers or + # mislabelling of results). + + for N in testnodes: + state = N.get_state() + self.failUnless(state in [SCons.Node.no_state, N.expect_to_be], + "Node %s got unexpected result: %s" % (N, state)) + + self.failUnless(filter(lambda N: N.get_state(), testnodes), + "no nodes ran at all.") + + +class SerialTaskTest(_SConsTaskTest): + def runTest(self): + "test serial jobs with actual Taskmaster and Task" + self._test_seq(1) + + +class ParallelTaskTest(_SConsTaskTest): + def runTest(self): + "test parallel jobs with actual Taskmaster and Task" + self._test_seq(num_jobs) + + + +#--------------------------------------------------------------------- + +def suite(): + suite = unittest.TestSuite() + suite.addTest(ParallelTestCase()) + suite.addTest(SerialTestCase()) + suite.addTest(NoParallelTestCase()) + suite.addTest(SerialExceptionTestCase()) + suite.addTest(ParallelExceptionTestCase()) + suite.addTest(SerialTaskTest()) + suite.addTest(ParallelTaskTest()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if (len(result.failures) == 0 + and len(result.errors) == 1 + and type(result.errors[0][0]) == SerialTestCase + and type(result.errors[0][1][0]) == NoThreadsException): + sys.exit(2) + elif not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Memoize.py b/src/engine/SCons/Memoize.py new file mode 100644 index 0000000..fc79157 --- /dev/null +++ b/src/engine/SCons/Memoize.py @@ -0,0 +1,292 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Memoize.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Memoizer + +A metaclass implementation to count hits and misses of the computed +values that various methods cache in memory. + +Use of this modules assumes that wrapped methods be coded to cache their +values in a consistent way. Here is an example of wrapping a method +that returns a computed value, with no input parameters: + + memoizer_counters = [] # Memoization + + memoizer_counters.append(SCons.Memoize.CountValue('foo')) # Memoization + + def foo(self): + + try: # Memoization + return self._memo['foo'] # Memoization + except KeyError: # Memoization + pass # Memoization + + result = self.compute_foo_value() + + self._memo['foo'] = result # Memoization + + return result + +Here is an example of wrapping a method that will return different values +based on one or more input arguments: + + def _bar_key(self, argument): # Memoization + return argument # Memoization + + memoizer_counters.append(SCons.Memoize.CountDict('bar', _bar_key)) # Memoization + + def bar(self, argument): + + memo_key = argument # Memoization + try: # Memoization + memo_dict = self._memo['bar'] # Memoization + except KeyError: # Memoization + memo_dict = {} # Memoization + self._memo['dict'] = memo_dict # Memoization + else: # Memoization + try: # Memoization + return memo_dict[memo_key] # Memoization + except KeyError: # Memoization + pass # Memoization + + result = self.compute_bar_value(argument) + + memo_dict[memo_key] = result # Memoization + + return result + +At one point we avoided replicating this sort of logic in all the methods +by putting it right into this module, but we've moved away from that at +present (see the "Historical Note," below.). + +Deciding what to cache is tricky, because different configurations +can have radically different performance tradeoffs, and because the +tradeoffs involved are often so non-obvious. Consequently, deciding +whether or not to cache a given method will likely be more of an art than +a science, but should still be based on available data from this module. +Here are some VERY GENERAL guidelines about deciding whether or not to +cache return values from a method that's being called a lot: + + -- The first question to ask is, "Can we change the calling code + so this method isn't called so often?" Sometimes this can be + done by changing the algorithm. Sometimes the *caller* should + be memoized, not the method you're looking at. + + -- The memoized function should be timed with multiple configurations + to make sure it doesn't inadvertently slow down some other + configuration. + + -- When memoizing values based on a dictionary key composed of + input arguments, you don't need to use all of the arguments + if some of them don't affect the return values. + +Historical Note: The initial Memoizer implementation actually handled +the caching of values for the wrapped methods, based on a set of generic +algorithms for computing hashable values based on the method's arguments. +This collected caching logic nicely, but had two drawbacks: + + Running arguments through a generic key-conversion mechanism is slower + (and less flexible) than just coding these things directly. Since the + methods that need memoized values are generally performance-critical, + slowing them down in order to collect the logic isn't the right + tradeoff. + + Use of the memoizer really obscured what was being called, because + all the memoized methods were wrapped with re-used generic methods. + This made it more difficult, for example, to use the Python profiler + to figure out how to optimize the underlying methods. +""" + +import new + +# A flag controlling whether or not we actually use memoization. +use_memoizer = None + +CounterList = [] + +class Counter: + """ + Base class for counting memoization hits and misses. + + We expect that the metaclass initialization will have filled in + the .name attribute that represents the name of the function + being counted. + """ + def __init__(self, method_name): + """ + """ + self.method_name = method_name + self.hit = 0 + self.miss = 0 + CounterList.append(self) + def display(self): + fmt = " %7d hits %7d misses %s()" + print fmt % (self.hit, self.miss, self.name) + def __cmp__(self, other): + try: + return cmp(self.name, other.name) + except AttributeError: + return 0 + +class CountValue(Counter): + """ + A counter class for simple, atomic memoized values. + + A CountValue object should be instantiated in a class for each of + the class's methods that memoizes its return value by simply storing + the return value in its _memo dictionary. + + We expect that the metaclass initialization will fill in the + .underlying_method attribute with the method that we're wrapping. + We then call the underlying_method method after counting whether + its memoized value has already been set (a hit) or not (a miss). + """ + def __call__(self, *args, **kw): + obj = args[0] + if obj._memo.has_key(self.method_name): + self.hit = self.hit + 1 + else: + self.miss = self.miss + 1 + return apply(self.underlying_method, args, kw) + +class CountDict(Counter): + """ + A counter class for memoized values stored in a dictionary, with + keys based on the method's input arguments. + + A CountDict object is instantiated in a class for each of the + class's methods that memoizes its return value in a dictionary, + indexed by some key that can be computed from one or more of + its input arguments. + + We expect that the metaclass initialization will fill in the + .underlying_method attribute with the method that we're wrapping. + We then call the underlying_method method after counting whether the + computed key value is already present in the memoization dictionary + (a hit) or not (a miss). + """ + def __init__(self, method_name, keymaker): + """ + """ + Counter.__init__(self, method_name) + self.keymaker = keymaker + def __call__(self, *args, **kw): + obj = args[0] + try: + memo_dict = obj._memo[self.method_name] + except KeyError: + self.miss = self.miss + 1 + else: + key = apply(self.keymaker, args, kw) + if memo_dict.has_key(key): + self.hit = self.hit + 1 + else: + self.miss = self.miss + 1 + return apply(self.underlying_method, args, kw) + +class Memoizer: + """Object which performs caching of method calls for its 'primary' + instance.""" + + def __init__(self): + pass + +# Find out if we support metaclasses (Python 2.2 and later). + +class M: + def __init__(cls, name, bases, cls_dict): + cls.use_metaclass = 1 + def fake_method(self): + pass + new.instancemethod(fake_method, None, cls) + +try: + class A: + __metaclass__ = M + + use_metaclass = A.use_metaclass +except AttributeError: + use_metaclass = None + reason = 'no metaclasses' +except TypeError: + use_metaclass = None + reason = 'new.instancemethod() bug' +else: + del A + +del M + +if not use_metaclass: + + def Dump(title): + pass + + try: + class Memoized_Metaclass(type): + # Just a place-holder so pre-metaclass Python versions don't + # have to have special code for the Memoized classes. + pass + except TypeError: + class Memoized_Metaclass: + # A place-holder so pre-metaclass Python versions don't + # have to have special code for the Memoized classes. + pass + + def EnableMemoization(): + import SCons.Warnings + msg = 'memoization is not supported in this version of Python (%s)' + raise SCons.Warnings.NoMetaclassSupportWarning, msg % reason + +else: + + def Dump(title=None): + if title: + print title + CounterList.sort() + for counter in CounterList: + counter.display() + + class Memoized_Metaclass(type): + def __init__(cls, name, bases, cls_dict): + super(Memoized_Metaclass, cls).__init__(name, bases, cls_dict) + + for counter in cls_dict.get('memoizer_counters', []): + method_name = counter.method_name + + counter.name = cls.__name__ + '.' + method_name + counter.underlying_method = cls_dict[method_name] + + replacement_method = new.instancemethod(counter, None, cls) + setattr(cls, method_name, replacement_method) + + def EnableMemoization(): + global use_memoizer + use_memoizer = 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/MemoizeTests.py b/src/engine/SCons/MemoizeTests.py new file mode 100644 index 0000000..e9f55a6 --- /dev/null +++ b/src/engine/SCons/MemoizeTests.py @@ -0,0 +1,198 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/MemoizeTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Memoize + + + +class FakeObject: + + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + memoizer_counters = [] + + def __init__(self): + self._memo = {} + + def _dict_key(self, argument): + return argument + + memoizer_counters.append(SCons.Memoize.CountDict('dict', _dict_key)) + + def dict(self, argument): + + memo_key = argument + try: + memo_dict = self._memo['dict'] + except KeyError: + memo_dict = {} + self._memo['dict'] = memo_dict + else: + try: + return memo_dict[memo_key] + except KeyError: + pass + + result = self.compute_dict(argument) + + memo_dict[memo_key] = result + + return result + + memoizer_counters.append(SCons.Memoize.CountValue('value')) + + def value(self): + + try: + return self._memo['value'] + except KeyError: + pass + + result = self.compute_value() + + self._memo['value'] = result + + return result + + def get_memoizer_counter(self, name): + for mc in self.memoizer_counters: + if mc.method_name == name: + return mc + return None + +class Returner: + def __init__(self, result): + self.result = result + self.calls = 0 + def __call__(self, *args, **kw): + self.calls = self.calls + 1 + return self.result + + +class CountDictTestCase(unittest.TestCase): + + def test___call__(self): + """Calling a Memoized dict method + """ + obj = FakeObject() + + called = [] + + fd1 = Returner(1) + fd2 = Returner(2) + + obj.compute_dict = fd1 + + r = obj.dict(11) + assert r == 1, r + + obj.compute_dict = fd2 + + r = obj.dict(12) + assert r == 2, r + + r = obj.dict(11) + assert r == 1, r + + obj.compute_dict = fd1 + + r = obj.dict(11) + assert r == 1, r + + r = obj.dict(12) + assert r == 2, r + + assert fd1.calls == 1, fd1.calls + assert fd2.calls == 1, fd2.calls + + c = obj.get_memoizer_counter('dict') + + if SCons.Memoize.use_metaclass: + assert c.hit == 3, c.hit + assert c.miss == 2, c.miss + else: + assert c.hit == 0, c.hit + assert c.miss == 0, c.miss + + +class CountValueTestCase(unittest.TestCase): + + def test___call__(self): + """Calling a Memoized value method + """ + obj = FakeObject() + + called = [] + + fv1 = Returner(1) + fv2 = Returner(2) + + obj.compute_value = fv1 + + r = obj.value() + assert r == 1, r + r = obj.value() + assert r == 1, r + + obj.compute_value = fv2 + + r = obj.value() + assert r == 1, r + r = obj.value() + assert r == 1, r + + assert fv1.calls == 1, fv1.calls + assert fv2.calls == 0, fv2.calls + + c = obj.get_memoizer_counter('value') + + if SCons.Memoize.use_metaclass: + assert c.hit == 3, c.hit + assert c.miss == 1, c.miss + else: + assert c.hit == 0, c.hit + assert c.miss == 0, c.miss + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + CountDictTestCase, + CountValueTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/Alias.py b/src/engine/SCons/Node/Alias.py new file mode 100644 index 0000000..2aa5821 --- /dev/null +++ b/src/engine/SCons/Node/Alias.py @@ -0,0 +1,153 @@ + +"""scons.Node.Alias + +Alias nodes. + +This creates a hash of global Aliases (dummy targets). + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/Alias.py 4577 2009/12/27 19:44:43 scons" + +import string +import UserDict + +import SCons.Errors +import SCons.Node +import SCons.Util + +class AliasNameSpace(UserDict.UserDict): + def Alias(self, name, **kw): + if isinstance(name, SCons.Node.Alias.Alias): + return name + try: + a = self[name] + except KeyError: + a = apply(SCons.Node.Alias.Alias, (name,), kw) + self[name] = a + return a + + def lookup(self, name, **kw): + try: + return self[name] + except KeyError: + return None + +class AliasNodeInfo(SCons.Node.NodeInfoBase): + current_version_id = 1 + field_list = ['csig'] + def str_to_node(self, s): + return default_ans.Alias(s) + +class AliasBuildInfo(SCons.Node.BuildInfoBase): + current_version_id = 1 + +class Alias(SCons.Node.Node): + + NodeInfo = AliasNodeInfo + BuildInfo = AliasBuildInfo + + def __init__(self, name): + SCons.Node.Node.__init__(self) + self.name = name + + def str_for_display(self): + return '"' + self.__str__() + '"' + + def __str__(self): + return self.name + + def make_ready(self): + self.get_csig() + + really_build = SCons.Node.Node.build + is_up_to_date = SCons.Node.Node.children_are_up_to_date + + def is_under(self, dir): + # Make Alias nodes get built regardless of + # what directory scons was run from. Alias nodes + # are outside the filesystem: + return 1 + + def get_contents(self): + """The contents of an alias is the concatenation + of the content signatures of all its sources.""" + childsigs = map(lambda n: n.get_csig(), self.children()) + return string.join(childsigs, '') + + def sconsign(self): + """An Alias is not recorded in .sconsign files""" + pass + + # + # + # + + def changed_since_last_build(self, target, prev_ni): + cur_csig = self.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + + def build(self): + """A "builder" for aliases.""" + pass + + def convert(self): + try: del self.builder + except AttributeError: pass + self.reset_executor() + self.build = self.really_build + + def get_csig(self): + """ + Generate a node's content signature, the digested signature + of its content. + + node - the node + cache - alternate node to use for the signature cache + returns - the content signature + """ + try: + return self.ninfo.csig + except AttributeError: + pass + + contents = self.get_contents() + csig = SCons.Util.MD5signature(contents) + self.get_ninfo().csig = csig + return csig + +default_ans = AliasNameSpace() + +SCons.Node.arg2nodes_lookups.append(default_ans.lookup) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/AliasTests.py b/src/engine/SCons/Node/AliasTests.py new file mode 100644 index 0000000..8828c19 --- /dev/null +++ b/src/engine/SCons/Node/AliasTests.py @@ -0,0 +1,130 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/AliasTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Node.Alias + +class AliasTestCase(unittest.TestCase): + + def test_AliasNameSpace(self): + """Test creating an Alias name space + """ + ans = SCons.Node.Alias.AliasNameSpace() + assert ans is not None, ans + + def test_ANS_Alias(self): + """Test the Alias() factory + """ + ans = SCons.Node.Alias.AliasNameSpace() + + a1 = ans.Alias('a1') + assert a1.name == 'a1', a1.name + + a2 = ans.Alias('a1') + assert a1 is a2, (a1, a2) + + def test_get_contents(self): + """Test the get_contents() method + """ + class DummyNode: + def __init__(self, contents): + self.contents = contents + def get_csig(self): + return self.contents + def get_contents(self): + return self.contents + + ans = SCons.Node.Alias.AliasNameSpace() + + ans.Alias('a1') + a = ans.lookup('a1') + + a.sources = [ DummyNode('one'), DummyNode('two'), DummyNode('three') ] + + c = a.get_contents() + assert c == 'onetwothree', c + + def test_lookup(self): + """Test the lookup() method + """ + ans = SCons.Node.Alias.AliasNameSpace() + + ans.Alias('a1') + a = ans.lookup('a1') + assert a.name == 'a1', a.name + + a1 = ans.lookup('a1') + assert a is a1, a1 + + a = ans.lookup('a2') + assert a is None, a + + def test_Alias(self): + """Test creating an Alias() object + """ + a1 = SCons.Node.Alias.Alias('a') + assert a1.name == 'a', a1.name + + a2 = SCons.Node.Alias.Alias('a') + assert a2.name == 'a', a2.name + + assert not a1 is a2 + assert a1.name == a2.name + +class AliasNodeInfoTestCase(unittest.TestCase): + def test___init__(self): + """Test AliasNodeInfo initialization""" + ans = SCons.Node.Alias.AliasNameSpace() + aaa = ans.Alias('aaa') + ni = SCons.Node.Alias.AliasNodeInfo(aaa) + +class AliasBuildInfoTestCase(unittest.TestCase): + def test___init__(self): + """Test AliasBuildInfo initialization""" + ans = SCons.Node.Alias.AliasNameSpace() + aaa = ans.Alias('aaa') + bi = SCons.Node.Alias.AliasBuildInfo(aaa) + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + AliasTestCase, + AliasBuildInfoTestCase, + AliasNodeInfoTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/FS.py b/src/engine/SCons/Node/FS.py new file mode 100644 index 0000000..0e9f14f --- /dev/null +++ b/src/engine/SCons/Node/FS.py @@ -0,0 +1,3220 @@ +"""scons.Node.FS + +File system nodes. + +These Nodes represent the canonical external objects that people think +of when they think of building software: files and directories. + +This holds a "default_fs" variable that should be initialized with an FS +that can be used by scripts or modules looking for the canonical default. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/FS.py 4577 2009/12/27 19:44:43 scons" + +from itertools import izip +import cStringIO +import fnmatch +import os +import os.path +import re +import shutil +import stat +import string +import sys +import time + +try: + import codecs +except ImportError: + pass +else: + # TODO(2.2): Remove when 2.3 becomes the minimal supported version. + try: + codecs.BOM_UTF8 + except AttributeError: + codecs.BOM_UTF8 = '\xef\xbb\xbf' + try: + codecs.BOM_UTF16_LE + codecs.BOM_UTF16_BE + except AttributeError: + codecs.BOM_UTF16_LE = '\xff\xfe' + codecs.BOM_UTF16_BE = '\xfe\xff' + + # Provide a wrapper function to handle decoding differences in + # different versions of Python. Normally, we'd try to do this in the + # compat layer (and maybe it still makes sense to move there?) but + # that doesn't provide a way to supply the string class used in + # pre-2.3 Python versions with a .decode() method that all strings + # naturally have. Plus, the 2.[01] encodings behave differently + # enough that we have to settle for a lowest-common-denominator + # wrapper approach. + # + # Note that the 2.[012] implementations below may be inefficient + # because they perform an explicit look up of the encoding for every + # decode, but they're old enough (and we want to stop supporting + # them soon enough) that it's not worth complicating the interface. + # Think of it as additional incentive for people to upgrade... + try: + ''.decode + except AttributeError: + # 2.0 through 2.2: strings have no .decode() method + try: + codecs.lookup('ascii').decode + except AttributeError: + # 2.0 and 2.1: encodings are a tuple of functions, and the + # decode() function returns a (result, length) tuple. + def my_decode(contents, encoding): + return codecs.lookup(encoding)[1](contents)[0] + else: + # 2.2: encodings are an object with methods, and the + # .decode() method returns just the decoded bytes. + def my_decode(contents, encoding): + return codecs.lookup(encoding).decode(contents) + else: + # 2.3 or later: use the .decode() string method + def my_decode(contents, encoding): + return contents.decode(encoding) + +import SCons.Action +from SCons.Debug import logInstanceCreation +import SCons.Errors +import SCons.Memoize +import SCons.Node +import SCons.Node.Alias +import SCons.Subst +import SCons.Util +import SCons.Warnings + +from SCons.Debug import Trace + +do_store_info = True + + +class EntryProxyAttributeError(AttributeError): + """ + An AttributeError subclass for recording and displaying the name + of the underlying Entry involved in an AttributeError exception. + """ + def __init__(self, entry_proxy, attribute): + AttributeError.__init__(self) + self.entry_proxy = entry_proxy + self.attribute = attribute + def __str__(self): + entry = self.entry_proxy.get() + fmt = "%s instance %s has no attribute %s" + return fmt % (entry.__class__.__name__, + repr(entry.name), + repr(self.attribute)) + +# The max_drift value: by default, use a cached signature value for +# any file that's been untouched for more than two days. +default_max_drift = 2*24*60*60 + +# +# We stringify these file system Nodes a lot. Turning a file system Node +# into a string is non-trivial, because the final string representation +# can depend on a lot of factors: whether it's a derived target or not, +# whether it's linked to a repository or source directory, and whether +# there's duplication going on. The normal technique for optimizing +# calculations like this is to memoize (cache) the string value, so you +# only have to do the calculation once. +# +# A number of the above factors, however, can be set after we've already +# been asked to return a string for a Node, because a Repository() or +# VariantDir() call or the like may not occur until later in SConscript +# files. So this variable controls whether we bother trying to save +# string values for Nodes. The wrapper interface can set this whenever +# they're done mucking with Repository and VariantDir and the other stuff, +# to let this module know it can start returning saved string values +# for Nodes. +# +Save_Strings = None + +def save_strings(val): + global Save_Strings + Save_Strings = val + +# +# Avoid unnecessary function calls by recording a Boolean value that +# tells us whether or not os.path.splitdrive() actually does anything +# on this system, and therefore whether we need to bother calling it +# when looking up path names in various methods below. +# + +do_splitdrive = None + +def initialize_do_splitdrive(): + global do_splitdrive + drive, path = os.path.splitdrive('X:/foo') + do_splitdrive = not not drive + +initialize_do_splitdrive() + +# + +needs_normpath_check = None + +def initialize_normpath_check(): + """ + Initialize the normpath_check regular expression. + + This function is used by the unit tests to re-initialize the pattern + when testing for behavior with different values of os.sep. + """ + global needs_normpath_check + if os.sep == '/': + pattern = r'.*/|\.$|\.\.$' + else: + pattern = r'.*[/%s]|\.$|\.\.$' % re.escape(os.sep) + needs_normpath_check = re.compile(pattern) + +initialize_normpath_check() + +# +# SCons.Action objects for interacting with the outside world. +# +# The Node.FS methods in this module should use these actions to +# create and/or remove files and directories; they should *not* use +# os.{link,symlink,unlink,mkdir}(), etc., directly. +# +# Using these SCons.Action objects ensures that descriptions of these +# external activities are properly displayed, that the displays are +# suppressed when the -s (silent) option is used, and (most importantly) +# the actions are disabled when the the -n option is used, in which case +# there should be *no* changes to the external file system(s)... +# + +if hasattr(os, 'link'): + def _hardlink_func(fs, src, dst): + # If the source is a symlink, we can't just hard-link to it + # because a relative symlink may point somewhere completely + # different. We must disambiguate the symlink and then + # hard-link the final destination file. + while fs.islink(src): + link = fs.readlink(src) + if not os.path.isabs(link): + src = link + else: + src = os.path.join(os.path.dirname(src), link) + fs.link(src, dst) +else: + _hardlink_func = None + +if hasattr(os, 'symlink'): + def _softlink_func(fs, src, dst): + fs.symlink(src, dst) +else: + _softlink_func = None + +def _copy_func(fs, src, dest): + shutil.copy2(src, dest) + st = fs.stat(src) + fs.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + + +Valid_Duplicates = ['hard-soft-copy', 'soft-hard-copy', + 'hard-copy', 'soft-copy', 'copy'] + +Link_Funcs = [] # contains the callables of the specified duplication style + +def set_duplicate(duplicate): + # Fill in the Link_Funcs list according to the argument + # (discarding those not available on the platform). + + # Set up the dictionary that maps the argument names to the + # underlying implementations. We do this inside this function, + # not in the top-level module code, so that we can remap os.link + # and os.symlink for testing purposes. + link_dict = { + 'hard' : _hardlink_func, + 'soft' : _softlink_func, + 'copy' : _copy_func + } + + if not duplicate in Valid_Duplicates: + raise SCons.Errors.InternalError, ("The argument of set_duplicate " + "should be in Valid_Duplicates") + global Link_Funcs + Link_Funcs = [] + for func in string.split(duplicate,'-'): + if link_dict[func]: + Link_Funcs.append(link_dict[func]) + +def LinkFunc(target, source, env): + # Relative paths cause problems with symbolic links, so + # we use absolute paths, which may be a problem for people + # who want to move their soft-linked src-trees around. Those + # people should use the 'hard-copy' mode, softlinks cannot be + # used for that; at least I have no idea how ... + src = source[0].abspath + dest = target[0].abspath + dir, file = os.path.split(dest) + if dir and not target[0].fs.isdir(dir): + os.makedirs(dir) + if not Link_Funcs: + # Set a default order of link functions. + set_duplicate('hard-soft-copy') + fs = source[0].fs + # Now link the files with the previously specified order. + for func in Link_Funcs: + try: + func(fs, src, dest) + break + except (IOError, OSError): + # An OSError indicates something happened like a permissions + # problem or an attempt to symlink across file-system + # boundaries. An IOError indicates something like the file + # not existing. In either case, keeping trying additional + # functions in the list and only raise an error if the last + # one failed. + if func == Link_Funcs[-1]: + # exception of the last link method (copy) are fatal + raise + return 0 + +Link = SCons.Action.Action(LinkFunc, None) +def LocalString(target, source, env): + return 'Local copy of %s from %s' % (target[0], source[0]) + +LocalCopy = SCons.Action.Action(LinkFunc, LocalString) + +def UnlinkFunc(target, source, env): + t = target[0] + t.fs.unlink(t.abspath) + return 0 + +Unlink = SCons.Action.Action(UnlinkFunc, None) + +def MkdirFunc(target, source, env): + t = target[0] + if not t.exists(): + t.fs.mkdir(t.abspath) + return 0 + +Mkdir = SCons.Action.Action(MkdirFunc, None, presub=None) + +MkdirBuilder = None + +def get_MkdirBuilder(): + global MkdirBuilder + if MkdirBuilder is None: + import SCons.Builder + import SCons.Defaults + # "env" will get filled in by Executor.get_build_env() + # calling SCons.Defaults.DefaultEnvironment() when necessary. + MkdirBuilder = SCons.Builder.Builder(action = Mkdir, + env = None, + explain = None, + is_explicit = None, + target_scanner = SCons.Defaults.DirEntryScanner, + name = "MkdirBuilder") + return MkdirBuilder + +class _Null: + pass + +_null = _Null() + +DefaultSCCSBuilder = None +DefaultRCSBuilder = None + +def get_DefaultSCCSBuilder(): + global DefaultSCCSBuilder + if DefaultSCCSBuilder is None: + import SCons.Builder + # "env" will get filled in by Executor.get_build_env() + # calling SCons.Defaults.DefaultEnvironment() when necessary. + act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR') + DefaultSCCSBuilder = SCons.Builder.Builder(action = act, + env = None, + name = "DefaultSCCSBuilder") + return DefaultSCCSBuilder + +def get_DefaultRCSBuilder(): + global DefaultRCSBuilder + if DefaultRCSBuilder is None: + import SCons.Builder + # "env" will get filled in by Executor.get_build_env() + # calling SCons.Defaults.DefaultEnvironment() when necessary. + act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR') + DefaultRCSBuilder = SCons.Builder.Builder(action = act, + env = None, + name = "DefaultRCSBuilder") + return DefaultRCSBuilder + +# Cygwin's os.path.normcase pretends it's on a case-sensitive filesystem. +_is_cygwin = sys.platform == "cygwin" +if os.path.normcase("TeSt") == os.path.normpath("TeSt") and not _is_cygwin: + def _my_normcase(x): + return x +else: + def _my_normcase(x): + return string.upper(x) + + + +class DiskChecker: + def __init__(self, type, do, ignore): + self.type = type + self.do = do + self.ignore = ignore + self.set_do() + def set_do(self): + self.__call__ = self.do + def set_ignore(self): + self.__call__ = self.ignore + def set(self, list): + if self.type in list: + self.set_do() + else: + self.set_ignore() + +def do_diskcheck_match(node, predicate, errorfmt): + result = predicate() + try: + # If calling the predicate() cached a None value from stat(), + # remove it so it doesn't interfere with later attempts to + # build this Node as we walk the DAG. (This isn't a great way + # to do this, we're reaching into an interface that doesn't + # really belong to us, but it's all about performance, so + # for now we'll just document the dependency...) + if node._memo['stat'] is None: + del node._memo['stat'] + except (AttributeError, KeyError): + pass + if result: + raise TypeError, errorfmt % node.abspath + +def ignore_diskcheck_match(node, predicate, errorfmt): + pass + +def do_diskcheck_rcs(node, name): + try: + rcs_dir = node.rcs_dir + except AttributeError: + if node.entry_exists_on_disk('RCS'): + rcs_dir = node.Dir('RCS') + else: + rcs_dir = None + node.rcs_dir = rcs_dir + if rcs_dir: + return rcs_dir.entry_exists_on_disk(name+',v') + return None + +def ignore_diskcheck_rcs(node, name): + return None + +def do_diskcheck_sccs(node, name): + try: + sccs_dir = node.sccs_dir + except AttributeError: + if node.entry_exists_on_disk('SCCS'): + sccs_dir = node.Dir('SCCS') + else: + sccs_dir = None + node.sccs_dir = sccs_dir + if sccs_dir: + return sccs_dir.entry_exists_on_disk('s.'+name) + return None + +def ignore_diskcheck_sccs(node, name): + return None + +diskcheck_match = DiskChecker('match', do_diskcheck_match, ignore_diskcheck_match) +diskcheck_rcs = DiskChecker('rcs', do_diskcheck_rcs, ignore_diskcheck_rcs) +diskcheck_sccs = DiskChecker('sccs', do_diskcheck_sccs, ignore_diskcheck_sccs) + +diskcheckers = [ + diskcheck_match, + diskcheck_rcs, + diskcheck_sccs, +] + +def set_diskcheck(list): + for dc in diskcheckers: + dc.set(list) + +def diskcheck_types(): + return map(lambda dc: dc.type, diskcheckers) + + + +class EntryProxy(SCons.Util.Proxy): + def __get_abspath(self): + entry = self.get() + return SCons.Subst.SpecialAttrWrapper(entry.get_abspath(), + entry.name + "_abspath") + + def __get_filebase(self): + name = self.get().name + return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[0], + name + "_filebase") + + def __get_suffix(self): + name = self.get().name + return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(name)[1], + name + "_suffix") + + def __get_file(self): + name = self.get().name + return SCons.Subst.SpecialAttrWrapper(name, name + "_file") + + def __get_base_path(self): + """Return the file's directory and file name, with the + suffix stripped.""" + entry = self.get() + return SCons.Subst.SpecialAttrWrapper(SCons.Util.splitext(entry.get_path())[0], + entry.name + "_base") + + def __get_posix_path(self): + """Return the path with / as the path separator, + regardless of platform.""" + if os.sep == '/': + return self + else: + entry = self.get() + r = string.replace(entry.get_path(), os.sep, '/') + return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_posix") + + def __get_windows_path(self): + """Return the path with \ as the path separator, + regardless of platform.""" + if os.sep == '\\': + return self + else: + entry = self.get() + r = string.replace(entry.get_path(), os.sep, '\\') + return SCons.Subst.SpecialAttrWrapper(r, entry.name + "_windows") + + def __get_srcnode(self): + return EntryProxy(self.get().srcnode()) + + def __get_srcdir(self): + """Returns the directory containing the source node linked to this + node via VariantDir(), or the directory of this node if not linked.""" + return EntryProxy(self.get().srcnode().dir) + + def __get_rsrcnode(self): + return EntryProxy(self.get().srcnode().rfile()) + + def __get_rsrcdir(self): + """Returns the directory containing the source node linked to this + node via VariantDir(), or the directory of this node if not linked.""" + return EntryProxy(self.get().srcnode().rfile().dir) + + def __get_dir(self): + return EntryProxy(self.get().dir) + + dictSpecialAttrs = { "base" : __get_base_path, + "posix" : __get_posix_path, + "windows" : __get_windows_path, + "win32" : __get_windows_path, + "srcpath" : __get_srcnode, + "srcdir" : __get_srcdir, + "dir" : __get_dir, + "abspath" : __get_abspath, + "filebase" : __get_filebase, + "suffix" : __get_suffix, + "file" : __get_file, + "rsrcpath" : __get_rsrcnode, + "rsrcdir" : __get_rsrcdir, + } + + def __getattr__(self, name): + # This is how we implement the "special" attributes + # such as base, posix, srcdir, etc. + try: + attr_function = self.dictSpecialAttrs[name] + except KeyError: + try: + attr = SCons.Util.Proxy.__getattr__(self, name) + except AttributeError, e: + # Raise our own AttributeError subclass with an + # overridden __str__() method that identifies the + # name of the entry that caused the exception. + raise EntryProxyAttributeError(self, name) + return attr + else: + return attr_function(self) + +class Base(SCons.Node.Node): + """A generic class for file system entries. This class is for + when we don't know yet whether the entry being looked up is a file + or a directory. Instances of this class can morph into either + Dir or File objects by a later, more precise lookup. + + Note: this class does not define __cmp__ and __hash__ for + efficiency reasons. SCons does a lot of comparing of + Node.FS.{Base,Entry,File,Dir} objects, so those operations must be + as fast as possible, which means we want to use Python's built-in + object identity comparisons. + """ + + memoizer_counters = [] + + def __init__(self, name, directory, fs): + """Initialize a generic Node.FS.Base object. + + Call the superclass initialization, take care of setting up + our relative and absolute paths, identify our parent + directory, and indicate that this node should use + signatures.""" + if __debug__: logInstanceCreation(self, 'Node.FS.Base') + SCons.Node.Node.__init__(self) + + # Filenames and paths are probably reused and are intern'ed to + # save some memory. + self.name = SCons.Util.silent_intern(name) + self.suffix = SCons.Util.silent_intern(SCons.Util.splitext(name)[1]) + self.fs = fs + + assert directory, "A directory must be provided" + + self.abspath = SCons.Util.silent_intern(directory.entry_abspath(name)) + self.labspath = SCons.Util.silent_intern(directory.entry_labspath(name)) + if directory.path == '.': + self.path = SCons.Util.silent_intern(name) + else: + self.path = SCons.Util.silent_intern(directory.entry_path(name)) + if directory.tpath == '.': + self.tpath = SCons.Util.silent_intern(name) + else: + self.tpath = SCons.Util.silent_intern(directory.entry_tpath(name)) + self.path_elements = directory.path_elements + [self] + + self.dir = directory + self.cwd = None # will hold the SConscript directory for target nodes + self.duplicate = directory.duplicate + + def str_for_display(self): + return '"' + self.__str__() + '"' + + def must_be_same(self, klass): + """ + This node, which already existed, is being looked up as the + specified klass. Raise an exception if it isn't. + """ + if isinstance(self, klass) or klass is Entry: + return + raise TypeError, "Tried to lookup %s '%s' as a %s." %\ + (self.__class__.__name__, self.path, klass.__name__) + + def get_dir(self): + return self.dir + + def get_suffix(self): + return self.suffix + + def rfile(self): + return self + + def __str__(self): + """A Node.FS.Base object's string representation is its path + name.""" + global Save_Strings + if Save_Strings: + return self._save_str() + return self._get_str() + + memoizer_counters.append(SCons.Memoize.CountValue('_save_str')) + + def _save_str(self): + try: + return self._memo['_save_str'] + except KeyError: + pass + result = intern(self._get_str()) + self._memo['_save_str'] = result + return result + + def _get_str(self): + global Save_Strings + if self.duplicate or self.is_derived(): + return self.get_path() + srcnode = self.srcnode() + if srcnode.stat() is None and self.stat() is not None: + result = self.get_path() + else: + result = srcnode.get_path() + if not Save_Strings: + # We're not at the point where we're saving the string string + # representations of FS Nodes (because we haven't finished + # reading the SConscript files and need to have str() return + # things relative to them). That also means we can't yet + # cache values returned (or not returned) by stat(), since + # Python code in the SConscript files might still create + # or otherwise affect the on-disk file. So get rid of the + # values that the underlying stat() method saved. + try: del self._memo['stat'] + except KeyError: pass + if self is not srcnode: + try: del srcnode._memo['stat'] + except KeyError: pass + return result + + rstr = __str__ + + memoizer_counters.append(SCons.Memoize.CountValue('stat')) + + def stat(self): + try: return self._memo['stat'] + except KeyError: pass + try: result = self.fs.stat(self.abspath) + except os.error: result = None + self._memo['stat'] = result + return result + + def exists(self): + return self.stat() is not None + + def rexists(self): + return self.rfile().exists() + + def getmtime(self): + st = self.stat() + if st: return st[stat.ST_MTIME] + else: return None + + def getsize(self): + st = self.stat() + if st: return st[stat.ST_SIZE] + else: return None + + def isdir(self): + st = self.stat() + return st is not None and stat.S_ISDIR(st[stat.ST_MODE]) + + def isfile(self): + st = self.stat() + return st is not None and stat.S_ISREG(st[stat.ST_MODE]) + + if hasattr(os, 'symlink'): + def islink(self): + try: st = self.fs.lstat(self.abspath) + except os.error: return 0 + return stat.S_ISLNK(st[stat.ST_MODE]) + else: + def islink(self): + return 0 # no symlinks + + def is_under(self, dir): + if self is dir: + return 1 + else: + return self.dir.is_under(dir) + + def set_local(self): + self._local = 1 + + def srcnode(self): + """If this node is in a build path, return the node + corresponding to its source file. Otherwise, return + ourself. + """ + srcdir_list = self.dir.srcdir_list() + if srcdir_list: + srcnode = srcdir_list[0].Entry(self.name) + srcnode.must_be_same(self.__class__) + return srcnode + return self + + def get_path(self, dir=None): + """Return path relative to the current working directory of the + Node.FS.Base object that owns us.""" + if not dir: + dir = self.fs.getcwd() + if self == dir: + return '.' + path_elems = self.path_elements + try: i = path_elems.index(dir) + except ValueError: pass + else: path_elems = path_elems[i+1:] + path_elems = map(lambda n: n.name, path_elems) + return string.join(path_elems, os.sep) + + def set_src_builder(self, builder): + """Set the source code builder for this node.""" + self.sbuilder = builder + if not self.has_builder(): + self.builder_set(builder) + + def src_builder(self): + """Fetch the source code builder for this node. + + If there isn't one, we cache the source code builder specified + for the directory (which in turn will cache the value from its + parent directory, and so on up to the file system root). + """ + try: + scb = self.sbuilder + except AttributeError: + scb = self.dir.src_builder() + self.sbuilder = scb + return scb + + def get_abspath(self): + """Get the absolute path of the file.""" + return self.abspath + + def for_signature(self): + # Return just our name. Even an absolute path would not work, + # because that can change thanks to symlinks or remapped network + # paths. + return self.name + + def get_subst_proxy(self): + try: + return self._proxy + except AttributeError: + ret = EntryProxy(self) + self._proxy = ret + return ret + + def target_from_source(self, prefix, suffix, splitext=SCons.Util.splitext): + """ + + Generates a target entry that corresponds to this entry (usually + a source file) with the specified prefix and suffix. + + Note that this method can be overridden dynamically for generated + files that need different behavior. See Tool/swig.py for + an example. + """ + return self.dir.Entry(prefix + splitext(self.name)[0] + suffix) + + def _Rfindalldirs_key(self, pathlist): + return pathlist + + memoizer_counters.append(SCons.Memoize.CountDict('Rfindalldirs', _Rfindalldirs_key)) + + def Rfindalldirs(self, pathlist): + """ + Return all of the directories for a given path list, including + corresponding "backing" directories in any repositories. + + The Node lookups are relative to this Node (typically a + directory), so memoizing result saves cycles from looking + up the same path for each target in a given directory. + """ + try: + memo_dict = self._memo['Rfindalldirs'] + except KeyError: + memo_dict = {} + self._memo['Rfindalldirs'] = memo_dict + else: + try: + return memo_dict[pathlist] + except KeyError: + pass + + create_dir_relative_to_self = self.Dir + result = [] + for path in pathlist: + if isinstance(path, SCons.Node.Node): + result.append(path) + else: + dir = create_dir_relative_to_self(path) + result.extend(dir.get_all_rdirs()) + + memo_dict[pathlist] = result + + return result + + def RDirs(self, pathlist): + """Search for a list of directories in the Repository list.""" + cwd = self.cwd or self.fs._cwd + return cwd.Rfindalldirs(pathlist) + + memoizer_counters.append(SCons.Memoize.CountValue('rentry')) + + def rentry(self): + try: + return self._memo['rentry'] + except KeyError: + pass + result = self + if not self.exists(): + norm_name = _my_normcase(self.name) + for dir in self.dir.get_all_rdirs(): + try: + node = dir.entries[norm_name] + except KeyError: + if dir.entry_exists_on_disk(self.name): + result = dir.Entry(self.name) + break + self._memo['rentry'] = result + return result + + def _glob1(self, pattern, ondisk=True, source=False, strings=False): + return [] + +class Entry(Base): + """This is the class for generic Node.FS entries--that is, things + that could be a File or a Dir, but we're just not sure yet. + Consequently, the methods in this class really exist just to + transform their associated object into the right class when the + time comes, and then call the same-named method in the transformed + class.""" + + def diskcheck_match(self): + pass + + def disambiguate(self, must_exist=None): + """ + """ + if self.isdir(): + self.__class__ = Dir + self._morph() + elif self.isfile(): + self.__class__ = File + self._morph() + self.clear() + else: + # There was nothing on-disk at this location, so look in + # the src directory. + # + # We can't just use self.srcnode() straight away because + # that would create an actual Node for this file in the src + # directory, and there might not be one. Instead, use the + # dir_on_disk() method to see if there's something on-disk + # with that name, in which case we can go ahead and call + # self.srcnode() to create the right type of entry. + srcdir = self.dir.srcnode() + if srcdir != self.dir and \ + srcdir.entry_exists_on_disk(self.name) and \ + self.srcnode().isdir(): + self.__class__ = Dir + self._morph() + elif must_exist: + msg = "No such file or directory: '%s'" % self.abspath + raise SCons.Errors.UserError, msg + else: + self.__class__ = File + self._morph() + self.clear() + return self + + def rfile(self): + """We're a generic Entry, but the caller is actually looking for + a File at this point, so morph into one.""" + self.__class__ = File + self._morph() + self.clear() + return File.rfile(self) + + def scanner_key(self): + return self.get_suffix() + + def get_contents(self): + """Fetch the contents of the entry. Returns the exact binary + contents of the file.""" + try: + self = self.disambiguate(must_exist=1) + except SCons.Errors.UserError: + # There was nothing on disk with which to disambiguate + # this entry. Leave it as an Entry, but return a null + # string so calls to get_contents() in emitters and the + # like (e.g. in qt.py) don't have to disambiguate by hand + # or catch the exception. + return '' + else: + return self.get_contents() + + def get_text_contents(self): + """Fetch the decoded text contents of a Unicode encoded Entry. + + Since this should return the text contents from the file + system, we check to see into what sort of subclass we should + morph this Entry.""" + try: + self = self.disambiguate(must_exist=1) + except SCons.Errors.UserError: + # There was nothing on disk with which to disambiguate + # this entry. Leave it as an Entry, but return a null + # string so calls to get_text_contents() in emitters and + # the like (e.g. in qt.py) don't have to disambiguate by + # hand or catch the exception. + return '' + else: + return self.get_text_contents() + + def must_be_same(self, klass): + """Called to make sure a Node is a Dir. Since we're an + Entry, we can morph into one.""" + if self.__class__ is not klass: + self.__class__ = klass + self._morph() + self.clear() + + # The following methods can get called before the Taskmaster has + # had a chance to call disambiguate() directly to see if this Entry + # should really be a Dir or a File. We therefore use these to call + # disambiguate() transparently (from our caller's point of view). + # + # Right now, this minimal set of methods has been derived by just + # looking at some of the methods that will obviously be called early + # in any of the various Taskmasters' calling sequences, and then + # empirically figuring out which additional methods are necessary + # to make various tests pass. + + def exists(self): + """Return if the Entry exists. Check the file system to see + what we should turn into first. Assume a file if there's no + directory.""" + return self.disambiguate().exists() + + def rel_path(self, other): + d = self.disambiguate() + if d.__class__ is Entry: + raise "rel_path() could not disambiguate File/Dir" + return d.rel_path(other) + + def new_ninfo(self): + return self.disambiguate().new_ninfo() + + def changed_since_last_build(self, target, prev_ni): + return self.disambiguate().changed_since_last_build(target, prev_ni) + + def _glob1(self, pattern, ondisk=True, source=False, strings=False): + return self.disambiguate()._glob1(pattern, ondisk, source, strings) + + def get_subst_proxy(self): + return self.disambiguate().get_subst_proxy() + +# This is for later so we can differentiate between Entry the class and Entry +# the method of the FS class. +_classEntry = Entry + + +class LocalFS: + + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + # This class implements an abstraction layer for operations involving + # a local file system. Essentially, this wraps any function in + # the os, os.path or shutil modules that we use to actually go do + # anything with or to the local file system. + # + # Note that there's a very good chance we'll refactor this part of + # the architecture in some way as we really implement the interface(s) + # for remote file system Nodes. For example, the right architecture + # might be to have this be a subclass instead of a base class. + # Nevertheless, we're using this as a first step in that direction. + # + # We're not using chdir() yet because the calling subclass method + # needs to use os.chdir() directly to avoid recursion. Will we + # really need this one? + #def chdir(self, path): + # return os.chdir(path) + def chmod(self, path, mode): + return os.chmod(path, mode) + def copy(self, src, dst): + return shutil.copy(src, dst) + def copy2(self, src, dst): + return shutil.copy2(src, dst) + def exists(self, path): + return os.path.exists(path) + def getmtime(self, path): + return os.path.getmtime(path) + def getsize(self, path): + return os.path.getsize(path) + def isdir(self, path): + return os.path.isdir(path) + def isfile(self, path): + return os.path.isfile(path) + def link(self, src, dst): + return os.link(src, dst) + def lstat(self, path): + return os.lstat(path) + def listdir(self, path): + return os.listdir(path) + def makedirs(self, path): + return os.makedirs(path) + def mkdir(self, path): + return os.mkdir(path) + def rename(self, old, new): + return os.rename(old, new) + def stat(self, path): + return os.stat(path) + def symlink(self, src, dst): + return os.symlink(src, dst) + def open(self, path): + return open(path) + def unlink(self, path): + return os.unlink(path) + + if hasattr(os, 'symlink'): + def islink(self, path): + return os.path.islink(path) + else: + def islink(self, path): + return 0 # no symlinks + + if hasattr(os, 'readlink'): + def readlink(self, file): + return os.readlink(file) + else: + def readlink(self, file): + return '' + + +#class RemoteFS: +# # Skeleton for the obvious methods we might need from the +# # abstraction layer for a remote filesystem. +# def upload(self, local_src, remote_dst): +# pass +# def download(self, remote_src, local_dst): +# pass + + +class FS(LocalFS): + + memoizer_counters = [] + + def __init__(self, path = None): + """Initialize the Node.FS subsystem. + + The supplied path is the top of the source tree, where we + expect to find the top-level build file. If no path is + supplied, the current directory is the default. + + The path argument must be a valid absolute path. + """ + if __debug__: logInstanceCreation(self, 'Node.FS') + + self._memo = {} + + self.Root = {} + self.SConstruct_dir = None + self.max_drift = default_max_drift + + self.Top = None + if path is None: + self.pathTop = os.getcwd() + else: + self.pathTop = path + self.defaultDrive = _my_normcase(os.path.splitdrive(self.pathTop)[0]) + + self.Top = self.Dir(self.pathTop) + self.Top.path = '.' + self.Top.tpath = '.' + self._cwd = self.Top + + DirNodeInfo.fs = self + FileNodeInfo.fs = self + + def set_SConstruct_dir(self, dir): + self.SConstruct_dir = dir + + def get_max_drift(self): + return self.max_drift + + def set_max_drift(self, max_drift): + self.max_drift = max_drift + + def getcwd(self): + return self._cwd + + def chdir(self, dir, change_os_dir=0): + """Change the current working directory for lookups. + If change_os_dir is true, we will also change the "real" cwd + to match. + """ + curr=self._cwd + try: + if dir is not None: + self._cwd = dir + if change_os_dir: + os.chdir(dir.abspath) + except OSError: + self._cwd = curr + raise + + def get_root(self, drive): + """ + Returns the root directory for the specified drive, creating + it if necessary. + """ + drive = _my_normcase(drive) + try: + return self.Root[drive] + except KeyError: + root = RootDir(drive, self) + self.Root[drive] = root + if not drive: + self.Root[self.defaultDrive] = root + elif drive == self.defaultDrive: + self.Root[''] = root + return root + + def _lookup(self, p, directory, fsclass, create=1): + """ + The generic entry point for Node lookup with user-supplied data. + + This translates arbitrary input into a canonical Node.FS object + of the specified fsclass. The general approach for strings is + to turn it into a fully normalized absolute path and then call + the root directory's lookup_abs() method for the heavy lifting. + + If the path name begins with '#', it is unconditionally + interpreted relative to the top-level directory of this FS. '#' + is treated as a synonym for the top-level SConstruct directory, + much like '~' is treated as a synonym for the user's home + directory in a UNIX shell. So both '#foo' and '#/foo' refer + to the 'foo' subdirectory underneath the top-level SConstruct + directory. + + If the path name is relative, then the path is looked up relative + to the specified directory, or the current directory (self._cwd, + typically the SConscript directory) if the specified directory + is None. + """ + if isinstance(p, Base): + # It's already a Node.FS object. Make sure it's the right + # class and return. + p.must_be_same(fsclass) + return p + # str(p) in case it's something like a proxy object + p = str(p) + + initial_hash = (p[0:1] == '#') + if initial_hash: + # There was an initial '#', so we strip it and override + # whatever directory they may have specified with the + # top-level SConstruct directory. + p = p[1:] + directory = self.Top + + if directory and not isinstance(directory, Dir): + directory = self.Dir(directory) + + if do_splitdrive: + drive, p = os.path.splitdrive(p) + else: + drive = '' + if drive and not p: + # This causes a naked drive letter to be treated as a synonym + # for the root directory on that drive. + p = os.sep + absolute = os.path.isabs(p) + + needs_normpath = needs_normpath_check.match(p) + + if initial_hash or not absolute: + # This is a relative lookup, either to the top-level + # SConstruct directory (because of the initial '#') or to + # the current directory (the path name is not absolute). + # Add the string to the appropriate directory lookup path, + # after which the whole thing gets normalized. + if not directory: + directory = self._cwd + if p: + p = directory.labspath + '/' + p + else: + p = directory.labspath + + if needs_normpath: + p = os.path.normpath(p) + + if drive or absolute: + root = self.get_root(drive) + else: + if not directory: + directory = self._cwd + root = directory.root + + if os.sep != '/': + p = string.replace(p, os.sep, '/') + return root._lookup_abs(p, fsclass, create) + + def Entry(self, name, directory = None, create = 1): + """Look up or create a generic Entry node with the specified name. + If the name is a relative path (begins with ./, ../, or a file + name), then it is looked up relative to the supplied directory + node, or to the top level directory of the FS (supplied at + construction time) if no directory is supplied. + """ + return self._lookup(name, directory, Entry, create) + + def File(self, name, directory = None, create = 1): + """Look up or create a File node with the specified name. If + the name is a relative path (begins with ./, ../, or a file name), + then it is looked up relative to the supplied directory node, + or to the top level directory of the FS (supplied at construction + time) if no directory is supplied. + + This method will raise TypeError if a directory is found at the + specified path. + """ + return self._lookup(name, directory, File, create) + + def Dir(self, name, directory = None, create = True): + """Look up or create a Dir node with the specified name. If + the name is a relative path (begins with ./, ../, or a file name), + then it is looked up relative to the supplied directory node, + or to the top level directory of the FS (supplied at construction + time) if no directory is supplied. + + This method will raise TypeError if a normal file is found at the + specified path. + """ + return self._lookup(name, directory, Dir, create) + + def VariantDir(self, variant_dir, src_dir, duplicate=1): + """Link the supplied variant directory to the source directory + for purposes of building files.""" + + if not isinstance(src_dir, SCons.Node.Node): + src_dir = self.Dir(src_dir) + if not isinstance(variant_dir, SCons.Node.Node): + variant_dir = self.Dir(variant_dir) + if src_dir.is_under(variant_dir): + raise SCons.Errors.UserError, "Source directory cannot be under variant directory." + if variant_dir.srcdir: + if variant_dir.srcdir == src_dir: + return # We already did this. + raise SCons.Errors.UserError, "'%s' already has a source directory: '%s'."%(variant_dir, variant_dir.srcdir) + variant_dir.link(src_dir, duplicate) + + def Repository(self, *dirs): + """Specify Repository directories to search.""" + for d in dirs: + if not isinstance(d, SCons.Node.Node): + d = self.Dir(d) + self.Top.addRepository(d) + + def variant_dir_target_climb(self, orig, dir, tail): + """Create targets in corresponding variant directories + + Climb the directory tree, and look up path names + relative to any linked variant directories we find. + + Even though this loops and walks up the tree, we don't memoize + the return value because this is really only used to process + the command-line targets. + """ + targets = [] + message = None + fmt = "building associated VariantDir targets: %s" + start_dir = dir + while dir: + for bd in dir.variant_dirs: + if start_dir.is_under(bd): + # If already in the build-dir location, don't reflect + return [orig], fmt % str(orig) + p = apply(os.path.join, [bd.path] + tail) + targets.append(self.Entry(p)) + tail = [dir.name] + tail + dir = dir.up() + if targets: + message = fmt % string.join(map(str, targets)) + return targets, message + + def Glob(self, pathname, ondisk=True, source=True, strings=False, cwd=None): + """ + Globs + + This is mainly a shim layer + """ + if cwd is None: + cwd = self.getcwd() + return cwd.glob(pathname, ondisk, source, strings) + +class DirNodeInfo(SCons.Node.NodeInfoBase): + # This should get reset by the FS initialization. + current_version_id = 1 + + fs = None + + def str_to_node(self, s): + top = self.fs.Top + root = top.root + if do_splitdrive: + drive, s = os.path.splitdrive(s) + if drive: + root = self.fs.get_root(drive) + if not os.path.isabs(s): + s = top.labspath + '/' + s + return root._lookup_abs(s, Entry) + +class DirBuildInfo(SCons.Node.BuildInfoBase): + current_version_id = 1 + +glob_magic_check = re.compile('[*?[]') + +def has_glob_magic(s): + return glob_magic_check.search(s) is not None + +class Dir(Base): + """A class for directories in a file system. + """ + + memoizer_counters = [] + + NodeInfo = DirNodeInfo + BuildInfo = DirBuildInfo + + def __init__(self, name, directory, fs): + if __debug__: logInstanceCreation(self, 'Node.FS.Dir') + Base.__init__(self, name, directory, fs) + self._morph() + + def _morph(self): + """Turn a file system Node (either a freshly initialized directory + object or a separate Entry object) into a proper directory object. + + Set up this directory's entries and hook it into the file + system tree. Specify that directories (this Node) don't use + signatures for calculating whether they're current. + """ + + self.repositories = [] + self.srcdir = None + + self.entries = {} + self.entries['.'] = self + self.entries['..'] = self.dir + self.cwd = self + self.searched = 0 + self._sconsign = None + self.variant_dirs = [] + self.root = self.dir.root + + # Don't just reset the executor, replace its action list, + # because it might have some pre-or post-actions that need to + # be preserved. + self.builder = get_MkdirBuilder() + self.get_executor().set_action_list(self.builder.action) + + def diskcheck_match(self): + diskcheck_match(self, self.isfile, + "File %s found where directory expected.") + + def __clearRepositoryCache(self, duplicate=None): + """Called when we change the repository(ies) for a directory. + This clears any cached information that is invalidated by changing + the repository.""" + + for node in self.entries.values(): + if node != self.dir: + if node != self and isinstance(node, Dir): + node.__clearRepositoryCache(duplicate) + else: + node.clear() + try: + del node._srcreps + except AttributeError: + pass + if duplicate is not None: + node.duplicate=duplicate + + def __resetDuplicate(self, node): + if node != self: + node.duplicate = node.get_dir().duplicate + + def Entry(self, name): + """ + Looks up or creates an entry node named 'name' relative to + this directory. + """ + return self.fs.Entry(name, self) + + def Dir(self, name, create=True): + """ + Looks up or creates a directory node named 'name' relative to + this directory. + """ + return self.fs.Dir(name, self, create) + + def File(self, name): + """ + Looks up or creates a file node named 'name' relative to + this directory. + """ + return self.fs.File(name, self) + + def _lookup_rel(self, name, klass, create=1): + """ + Looks up a *normalized* relative path name, relative to this + directory. + + This method is intended for use by internal lookups with + already-normalized path data. For general-purpose lookups, + use the Entry(), Dir() and File() methods above. + + This method does *no* input checking and will die or give + incorrect results if it's passed a non-normalized path name (e.g., + a path containing '..'), an absolute path name, a top-relative + ('#foo') path name, or any kind of object. + """ + name = self.entry_labspath(name) + return self.root._lookup_abs(name, klass, create) + + def link(self, srcdir, duplicate): + """Set this directory as the variant directory for the + supplied source directory.""" + self.srcdir = srcdir + self.duplicate = duplicate + self.__clearRepositoryCache(duplicate) + srcdir.variant_dirs.append(self) + + def getRepositories(self): + """Returns a list of repositories for this directory. + """ + if self.srcdir and not self.duplicate: + return self.srcdir.get_all_rdirs() + self.repositories + return self.repositories + + memoizer_counters.append(SCons.Memoize.CountValue('get_all_rdirs')) + + def get_all_rdirs(self): + try: + return list(self._memo['get_all_rdirs']) + except KeyError: + pass + + result = [self] + fname = '.' + dir = self + while dir: + for rep in dir.getRepositories(): + result.append(rep.Dir(fname)) + if fname == '.': + fname = dir.name + else: + fname = dir.name + os.sep + fname + dir = dir.up() + + self._memo['get_all_rdirs'] = list(result) + + return result + + def addRepository(self, dir): + if dir != self and not dir in self.repositories: + self.repositories.append(dir) + dir.tpath = '.' + self.__clearRepositoryCache() + + def up(self): + return self.entries['..'] + + def _rel_path_key(self, other): + return str(other) + + memoizer_counters.append(SCons.Memoize.CountDict('rel_path', _rel_path_key)) + + def rel_path(self, other): + """Return a path to "other" relative to this directory. + """ + + # This complicated and expensive method, which constructs relative + # paths between arbitrary Node.FS objects, is no longer used + # by SCons itself. It was introduced to store dependency paths + # in .sconsign files relative to the target, but that ended up + # being significantly inefficient. + # + # We're continuing to support the method because some SConstruct + # files out there started using it when it was available, and + # we're all about backwards compatibility.. + + try: + memo_dict = self._memo['rel_path'] + except KeyError: + memo_dict = {} + self._memo['rel_path'] = memo_dict + else: + try: + return memo_dict[other] + except KeyError: + pass + + if self is other: + result = '.' + + elif not other in self.path_elements: + try: + other_dir = other.get_dir() + except AttributeError: + result = str(other) + else: + if other_dir is None: + result = other.name + else: + dir_rel_path = self.rel_path(other_dir) + if dir_rel_path == '.': + result = other.name + else: + result = dir_rel_path + os.sep + other.name + else: + i = self.path_elements.index(other) + 1 + + path_elems = ['..'] * (len(self.path_elements) - i) \ + + map(lambda n: n.name, other.path_elements[i:]) + + result = string.join(path_elems, os.sep) + + memo_dict[other] = result + + return result + + def get_env_scanner(self, env, kw={}): + import SCons.Defaults + return SCons.Defaults.DirEntryScanner + + def get_target_scanner(self): + import SCons.Defaults + return SCons.Defaults.DirEntryScanner + + def get_found_includes(self, env, scanner, path): + """Return this directory's implicit dependencies. + + We don't bother caching the results because the scan typically + shouldn't be requested more than once (as opposed to scanning + .h file contents, which can be requested as many times as the + files is #included by other files). + """ + if not scanner: + return [] + # Clear cached info for this Dir. If we already visited this + # directory on our walk down the tree (because we didn't know at + # that point it was being used as the source for another Node) + # then we may have calculated build signature before realizing + # we had to scan the disk. Now that we have to, though, we need + # to invalidate the old calculated signature so that any node + # dependent on our directory structure gets one that includes + # info about everything on disk. + self.clear() + return scanner(self, env, path) + + # + # Taskmaster interface subsystem + # + + def prepare(self): + pass + + def build(self, **kw): + """A null "builder" for directories.""" + global MkdirBuilder + if self.builder is not MkdirBuilder: + apply(SCons.Node.Node.build, [self,], kw) + + # + # + # + + def _create(self): + """Create this directory, silently and without worrying about + whether the builder is the default or not.""" + listDirs = [] + parent = self + while parent: + if parent.exists(): + break + listDirs.append(parent) + p = parent.up() + if p is None: + # Don't use while: - else: for this condition because + # if so, then parent is None and has no .path attribute. + raise SCons.Errors.StopError, parent.path + parent = p + listDirs.reverse() + for dirnode in listDirs: + try: + # Don't call dirnode.build(), call the base Node method + # directly because we definitely *must* create this + # directory. The dirnode.build() method will suppress + # the build if it's the default builder. + SCons.Node.Node.build(dirnode) + dirnode.get_executor().nullify() + # The build() action may or may not have actually + # created the directory, depending on whether the -n + # option was used or not. Delete the _exists and + # _rexists attributes so they can be reevaluated. + dirnode.clear() + except OSError: + pass + + def multiple_side_effect_has_builder(self): + global MkdirBuilder + return self.builder is not MkdirBuilder and self.has_builder() + + def alter_targets(self): + """Return any corresponding targets in a variant directory. + """ + return self.fs.variant_dir_target_climb(self, self, []) + + def scanner_key(self): + """A directory does not get scanned.""" + return None + + def get_text_contents(self): + """We already emit things in text, so just return the binary + version.""" + return self.get_contents() + + def get_contents(self): + """Return content signatures and names of all our children + separated by new-lines. Ensure that the nodes are sorted.""" + contents = [] + name_cmp = lambda a, b: cmp(a.name, b.name) + sorted_children = self.children()[:] + sorted_children.sort(name_cmp) + for node in sorted_children: + contents.append('%s %s\n' % (node.get_csig(), node.name)) + return string.join(contents, '') + + def get_csig(self): + """Compute the content signature for Directory nodes. In + general, this is not needed and the content signature is not + stored in the DirNodeInfo. However, if get_contents on a Dir + node is called which has a child directory, the child + directory should return the hash of its contents.""" + contents = self.get_contents() + return SCons.Util.MD5signature(contents) + + def do_duplicate(self, src): + pass + + changed_since_last_build = SCons.Node.Node.state_has_changed + + def is_up_to_date(self): + """If any child is not up-to-date, then this directory isn't, + either.""" + if self.builder is not MkdirBuilder and not self.exists(): + return 0 + up_to_date = SCons.Node.up_to_date + for kid in self.children(): + if kid.get_state() > up_to_date: + return 0 + return 1 + + def rdir(self): + if not self.exists(): + norm_name = _my_normcase(self.name) + for dir in self.dir.get_all_rdirs(): + try: node = dir.entries[norm_name] + except KeyError: node = dir.dir_on_disk(self.name) + if node and node.exists() and \ + (isinstance(dir, Dir) or isinstance(dir, Entry)): + return node + return self + + def sconsign(self): + """Return the .sconsign file info for this directory, + creating it first if necessary.""" + if not self._sconsign: + import SCons.SConsign + self._sconsign = SCons.SConsign.ForDirectory(self) + return self._sconsign + + def srcnode(self): + """Dir has a special need for srcnode()...if we + have a srcdir attribute set, then that *is* our srcnode.""" + if self.srcdir: + return self.srcdir + return Base.srcnode(self) + + def get_timestamp(self): + """Return the latest timestamp from among our children""" + stamp = 0 + for kid in self.children(): + if kid.get_timestamp() > stamp: + stamp = kid.get_timestamp() + return stamp + + def entry_abspath(self, name): + return self.abspath + os.sep + name + + def entry_labspath(self, name): + return self.labspath + '/' + name + + def entry_path(self, name): + return self.path + os.sep + name + + def entry_tpath(self, name): + return self.tpath + os.sep + name + + def entry_exists_on_disk(self, name): + try: + d = self.on_disk_entries + except AttributeError: + d = {} + try: + entries = os.listdir(self.abspath) + except OSError: + pass + else: + for entry in map(_my_normcase, entries): + d[entry] = True + self.on_disk_entries = d + if sys.platform == 'win32': + name = _my_normcase(name) + result = d.get(name) + if result is None: + # Belt-and-suspenders for Windows: check directly for + # 8.3 file names that don't show up in os.listdir(). + result = os.path.exists(self.abspath + os.sep + name) + d[name] = result + return result + else: + return d.has_key(name) + + memoizer_counters.append(SCons.Memoize.CountValue('srcdir_list')) + + def srcdir_list(self): + try: + return self._memo['srcdir_list'] + except KeyError: + pass + + result = [] + + dirname = '.' + dir = self + while dir: + if dir.srcdir: + result.append(dir.srcdir.Dir(dirname)) + dirname = dir.name + os.sep + dirname + dir = dir.up() + + self._memo['srcdir_list'] = result + + return result + + def srcdir_duplicate(self, name): + for dir in self.srcdir_list(): + if self.is_under(dir): + # We shouldn't source from something in the build path; + # variant_dir is probably under src_dir, in which case + # we are reflecting. + break + if dir.entry_exists_on_disk(name): + srcnode = dir.Entry(name).disambiguate() + if self.duplicate: + node = self.Entry(name).disambiguate() + node.do_duplicate(srcnode) + return node + else: + return srcnode + return None + + def _srcdir_find_file_key(self, filename): + return filename + + memoizer_counters.append(SCons.Memoize.CountDict('srcdir_find_file', _srcdir_find_file_key)) + + def srcdir_find_file(self, filename): + try: + memo_dict = self._memo['srcdir_find_file'] + except KeyError: + memo_dict = {} + self._memo['srcdir_find_file'] = memo_dict + else: + try: + return memo_dict[filename] + except KeyError: + pass + + def func(node): + if (isinstance(node, File) or isinstance(node, Entry)) and \ + (node.is_derived() or node.exists()): + return node + return None + + norm_name = _my_normcase(filename) + + for rdir in self.get_all_rdirs(): + try: node = rdir.entries[norm_name] + except KeyError: node = rdir.file_on_disk(filename) + else: node = func(node) + if node: + result = (node, self) + memo_dict[filename] = result + return result + + for srcdir in self.srcdir_list(): + for rdir in srcdir.get_all_rdirs(): + try: node = rdir.entries[norm_name] + except KeyError: node = rdir.file_on_disk(filename) + else: node = func(node) + if node: + result = (File(filename, self, self.fs), srcdir) + memo_dict[filename] = result + return result + + result = (None, None) + memo_dict[filename] = result + return result + + def dir_on_disk(self, name): + if self.entry_exists_on_disk(name): + try: return self.Dir(name) + except TypeError: pass + node = self.srcdir_duplicate(name) + if isinstance(node, File): + return None + return node + + def file_on_disk(self, name): + if self.entry_exists_on_disk(name) or \ + diskcheck_rcs(self, name) or \ + diskcheck_sccs(self, name): + try: return self.File(name) + except TypeError: pass + node = self.srcdir_duplicate(name) + if isinstance(node, Dir): + return None + return node + + def walk(self, func, arg): + """ + Walk this directory tree by calling the specified function + for each directory in the tree. + + This behaves like the os.path.walk() function, but for in-memory + Node.FS.Dir objects. The function takes the same arguments as + the functions passed to os.path.walk(): + + func(arg, dirname, fnames) + + Except that "dirname" will actually be the directory *Node*, + not the string. The '.' and '..' entries are excluded from + fnames. The fnames list may be modified in-place to filter the + subdirectories visited or otherwise impose a specific order. + The "arg" argument is always passed to func() and may be used + in any way (or ignored, passing None is common). + """ + entries = self.entries + names = entries.keys() + names.remove('.') + names.remove('..') + func(arg, self, names) + select_dirs = lambda n, e=entries: isinstance(e[n], Dir) + for dirname in filter(select_dirs, names): + entries[dirname].walk(func, arg) + + def glob(self, pathname, ondisk=True, source=False, strings=False): + """ + Returns a list of Nodes (or strings) matching a specified + pathname pattern. + + Pathname patterns follow UNIX shell semantics: * matches + any-length strings of any characters, ? matches any character, + and [] can enclose lists or ranges of characters. Matches do + not span directory separators. + + The matches take into account Repositories, returning local + Nodes if a corresponding entry exists in a Repository (either + an in-memory Node or something on disk). + + By defafult, the glob() function matches entries that exist + on-disk, in addition to in-memory Nodes. Setting the "ondisk" + argument to False (or some other non-true value) causes the glob() + function to only match in-memory Nodes. The default behavior is + to return both the on-disk and in-memory Nodes. + + The "source" argument, when true, specifies that corresponding + source Nodes must be returned if you're globbing in a build + directory (initialized with VariantDir()). The default behavior + is to return Nodes local to the VariantDir(). + + The "strings" argument, when true, returns the matches as strings, + not Nodes. The strings are path names relative to this directory. + + The underlying algorithm is adapted from the glob.glob() function + in the Python library (but heavily modified), and uses fnmatch() + under the covers. + """ + dirname, basename = os.path.split(pathname) + if not dirname: + result = self._glob1(basename, ondisk, source, strings) + result.sort(lambda a, b: cmp(str(a), str(b))) + return result + if has_glob_magic(dirname): + list = self.glob(dirname, ondisk, source, strings=False) + else: + list = [self.Dir(dirname, create=True)] + result = [] + for dir in list: + r = dir._glob1(basename, ondisk, source, strings) + if strings: + r = map(lambda x, d=str(dir): os.path.join(d, x), r) + result.extend(r) + result.sort(lambda a, b: cmp(str(a), str(b))) + return result + + def _glob1(self, pattern, ondisk=True, source=False, strings=False): + """ + Globs for and returns a list of entry names matching a single + pattern in this directory. + + This searches any repositories and source directories for + corresponding entries and returns a Node (or string) relative + to the current directory if an entry is found anywhere. + + TODO: handle pattern with no wildcard + """ + search_dir_list = self.get_all_rdirs() + for srcdir in self.srcdir_list(): + search_dir_list.extend(srcdir.get_all_rdirs()) + + selfEntry = self.Entry + names = [] + for dir in search_dir_list: + # We use the .name attribute from the Node because the keys of + # the dir.entries dictionary are normalized (that is, all upper + # case) on case-insensitive systems like Windows. + #node_names = [ v.name for k, v in dir.entries.items() if k not in ('.', '..') ] + entry_names = filter(lambda n: n not in ('.', '..'), dir.entries.keys()) + node_names = map(lambda n, e=dir.entries: e[n].name, entry_names) + names.extend(node_names) + if not strings: + # Make sure the working directory (self) actually has + # entries for all Nodes in repositories or variant dirs. + for name in node_names: selfEntry(name) + if ondisk: + try: + disk_names = os.listdir(dir.abspath) + except os.error: + continue + names.extend(disk_names) + if not strings: + # We're going to return corresponding Nodes in + # the local directory, so we need to make sure + # those Nodes exist. We only want to create + # Nodes for the entries that will match the + # specified pattern, though, which means we + # need to filter the list here, even though + # the overall list will also be filtered later, + # after we exit this loop. + if pattern[0] != '.': + #disk_names = [ d for d in disk_names if d[0] != '.' ] + disk_names = filter(lambda x: x[0] != '.', disk_names) + disk_names = fnmatch.filter(disk_names, pattern) + dirEntry = dir.Entry + for name in disk_names: + # Add './' before disk filename so that '#' at + # beginning of filename isn't interpreted. + name = './' + name + node = dirEntry(name).disambiguate() + n = selfEntry(name) + if n.__class__ != node.__class__: + n.__class__ = node.__class__ + n._morph() + + names = set(names) + if pattern[0] != '.': + #names = [ n for n in names if n[0] != '.' ] + names = filter(lambda x: x[0] != '.', names) + names = fnmatch.filter(names, pattern) + + if strings: + return names + + #return [ self.entries[_my_normcase(n)] for n in names ] + return map(lambda n, e=self.entries: e[_my_normcase(n)], names) + +class RootDir(Dir): + """A class for the root directory of a file system. + + This is the same as a Dir class, except that the path separator + ('/' or '\\') is actually part of the name, so we don't need to + add a separator when creating the path names of entries within + this directory. + """ + def __init__(self, name, fs): + if __debug__: logInstanceCreation(self, 'Node.FS.RootDir') + # We're going to be our own parent directory (".." entry and .dir + # attribute) so we have to set up some values so Base.__init__() + # won't gag won't it calls some of our methods. + self.abspath = '' + self.labspath = '' + self.path = '' + self.tpath = '' + self.path_elements = [] + self.duplicate = 0 + self.root = self + Base.__init__(self, name, self, fs) + + # Now set our paths to what we really want them to be: the + # initial drive letter (the name) plus the directory separator, + # except for the "lookup abspath," which does not have the + # drive letter. + self.abspath = name + os.sep + self.labspath = '' + self.path = name + os.sep + self.tpath = name + os.sep + self._morph() + + self._lookupDict = {} + + # The // and os.sep + os.sep entries are necessary because + # os.path.normpath() seems to preserve double slashes at the + # beginning of a path (presumably for UNC path names), but + # collapses triple slashes to a single slash. + self._lookupDict[''] = self + self._lookupDict['/'] = self + self._lookupDict['//'] = self + self._lookupDict[os.sep] = self + self._lookupDict[os.sep + os.sep] = self + + def must_be_same(self, klass): + if klass is Dir: + return + Base.must_be_same(self, klass) + + def _lookup_abs(self, p, klass, create=1): + """ + Fast (?) lookup of a *normalized* absolute path. + + This method is intended for use by internal lookups with + already-normalized path data. For general-purpose lookups, + use the FS.Entry(), FS.Dir() or FS.File() methods. + + The caller is responsible for making sure we're passed a + normalized absolute path; we merely let Python's dictionary look + up and return the One True Node.FS object for the path. + + If no Node for the specified "p" doesn't already exist, and + "create" is specified, the Node may be created after recursive + invocation to find or create the parent directory or directories. + """ + k = _my_normcase(p) + try: + result = self._lookupDict[k] + except KeyError: + if not create: + msg = "No such file or directory: '%s' in '%s' (and create is False)" % (p, str(self)) + raise SCons.Errors.UserError, msg + # There is no Node for this path name, and we're allowed + # to create it. + dir_name, file_name = os.path.split(p) + dir_node = self._lookup_abs(dir_name, Dir) + result = klass(file_name, dir_node, self.fs) + + # Double-check on disk (as configured) that the Node we + # created matches whatever is out there in the real world. + result.diskcheck_match() + + self._lookupDict[k] = result + dir_node.entries[_my_normcase(file_name)] = result + dir_node.implicit = None + else: + # There is already a Node for this path name. Allow it to + # complain if we were looking for an inappropriate type. + result.must_be_same(klass) + return result + + def __str__(self): + return self.abspath + + def entry_abspath(self, name): + return self.abspath + name + + def entry_labspath(self, name): + return '/' + name + + def entry_path(self, name): + return self.path + name + + def entry_tpath(self, name): + return self.tpath + name + + def is_under(self, dir): + if self is dir: + return 1 + else: + return 0 + + def up(self): + return None + + def get_dir(self): + return None + + def src_builder(self): + return _null + +class FileNodeInfo(SCons.Node.NodeInfoBase): + current_version_id = 1 + + field_list = ['csig', 'timestamp', 'size'] + + # This should get reset by the FS initialization. + fs = None + + def str_to_node(self, s): + top = self.fs.Top + root = top.root + if do_splitdrive: + drive, s = os.path.splitdrive(s) + if drive: + root = self.fs.get_root(drive) + if not os.path.isabs(s): + s = top.labspath + '/' + s + return root._lookup_abs(s, Entry) + +class FileBuildInfo(SCons.Node.BuildInfoBase): + current_version_id = 1 + + def convert_to_sconsign(self): + """ + Converts this FileBuildInfo object for writing to a .sconsign file + + This replaces each Node in our various dependency lists with its + usual string representation: relative to the top-level SConstruct + directory, or an absolute path if it's outside. + """ + if os.sep == '/': + node_to_str = str + else: + def node_to_str(n): + try: + s = n.path + except AttributeError: + s = str(n) + else: + s = string.replace(s, os.sep, '/') + return s + for attr in ['bsources', 'bdepends', 'bimplicit']: + try: + val = getattr(self, attr) + except AttributeError: + pass + else: + setattr(self, attr, map(node_to_str, val)) + def convert_from_sconsign(self, dir, name): + """ + Converts a newly-read FileBuildInfo object for in-SCons use + + For normal up-to-date checking, we don't have any conversion to + perform--but we're leaving this method here to make that clear. + """ + pass + def prepare_dependencies(self): + """ + Prepares a FileBuildInfo object for explaining what changed + + The bsources, bdepends and bimplicit lists have all been + stored on disk as paths relative to the top-level SConstruct + directory. Convert the strings to actual Nodes (for use by the + --debug=explain code and --implicit-cache). + """ + attrs = [ + ('bsources', 'bsourcesigs'), + ('bdepends', 'bdependsigs'), + ('bimplicit', 'bimplicitsigs'), + ] + for (nattr, sattr) in attrs: + try: + strings = getattr(self, nattr) + nodeinfos = getattr(self, sattr) + except AttributeError: + continue + nodes = [] + for s, ni in izip(strings, nodeinfos): + if not isinstance(s, SCons.Node.Node): + s = ni.str_to_node(s) + nodes.append(s) + setattr(self, nattr, nodes) + def format(self, names=0): + result = [] + bkids = self.bsources + self.bdepends + self.bimplicit + bkidsigs = self.bsourcesigs + self.bdependsigs + self.bimplicitsigs + for bkid, bkidsig in izip(bkids, bkidsigs): + result.append(str(bkid) + ': ' + + string.join(bkidsig.format(names=names), ' ')) + result.append('%s [%s]' % (self.bactsig, self.bact)) + return string.join(result, '\n') + +class File(Base): + """A class for files in a file system. + """ + + memoizer_counters = [] + + NodeInfo = FileNodeInfo + BuildInfo = FileBuildInfo + + md5_chunksize = 64 + + def diskcheck_match(self): + diskcheck_match(self, self.isdir, + "Directory %s found where file expected.") + + def __init__(self, name, directory, fs): + if __debug__: logInstanceCreation(self, 'Node.FS.File') + Base.__init__(self, name, directory, fs) + self._morph() + + def Entry(self, name): + """Create an entry node named 'name' relative to + the directory of this file.""" + return self.dir.Entry(name) + + def Dir(self, name, create=True): + """Create a directory node named 'name' relative to + the directory of this file.""" + return self.dir.Dir(name, create=create) + + def Dirs(self, pathlist): + """Create a list of directories relative to the SConscript + directory of this file.""" + # TODO(1.5) + # return [self.Dir(p) for p in pathlist] + return map(lambda p, s=self: s.Dir(p), pathlist) + + def File(self, name): + """Create a file node named 'name' relative to + the directory of this file.""" + return self.dir.File(name) + + #def generate_build_dict(self): + # """Return an appropriate dictionary of values for building + # this File.""" + # return {'Dir' : self.Dir, + # 'File' : self.File, + # 'RDirs' : self.RDirs} + + def _morph(self): + """Turn a file system node into a File object.""" + self.scanner_paths = {} + if not hasattr(self, '_local'): + self._local = 0 + + # If there was already a Builder set on this entry, then + # we need to make sure we call the target-decider function, + # not the source-decider. Reaching in and doing this by hand + # is a little bogus. We'd prefer to handle this by adding + # an Entry.builder_set() method that disambiguates like the + # other methods, but that starts running into problems with the + # fragile way we initialize Dir Nodes with their Mkdir builders, + # yet still allow them to be overridden by the user. Since it's + # not clear right now how to fix that, stick with what works + # until it becomes clear... + if self.has_builder(): + self.changed_since_last_build = self.decide_target + + def scanner_key(self): + return self.get_suffix() + + def get_contents(self): + if not self.rexists(): + return '' + fname = self.rfile().abspath + try: + contents = open(fname, "rb").read() + except EnvironmentError, e: + if not e.filename: + e.filename = fname + raise + return contents + + try: + import codecs + except ImportError: + get_text_contents = get_contents + else: + # This attempts to figure out what the encoding of the text is + # based upon the BOM bytes, and then decodes the contents so that + # it's a valid python string. + def get_text_contents(self): + contents = self.get_contents() + # The behavior of various decode() methods and functions + # w.r.t. the initial BOM bytes is different for different + # encodings and/or Python versions. ('utf-8' does not strip + # them, but has a 'utf-8-sig' which does; 'utf-16' seems to + # strip them; etc.) Just side step all the complication by + # explicitly stripping the BOM before we decode(). + if contents.startswith(codecs.BOM_UTF8): + contents = contents[len(codecs.BOM_UTF8):] + # TODO(2.2): Remove when 2.3 becomes floor. + #contents = contents.decode('utf-8') + contents = my_decode(contents, 'utf-8') + elif contents.startswith(codecs.BOM_UTF16_LE): + contents = contents[len(codecs.BOM_UTF16_LE):] + # TODO(2.2): Remove when 2.3 becomes floor. + #contents = contents.decode('utf-16-le') + contents = my_decode(contents, 'utf-16-le') + elif contents.startswith(codecs.BOM_UTF16_BE): + contents = contents[len(codecs.BOM_UTF16_BE):] + # TODO(2.2): Remove when 2.3 becomes floor. + #contents = contents.decode('utf-16-be') + contents = my_decode(contents, 'utf-16-be') + return contents + + def get_content_hash(self): + """ + Compute and return the MD5 hash for this file. + """ + if not self.rexists(): + return SCons.Util.MD5signature('') + fname = self.rfile().abspath + try: + cs = SCons.Util.MD5filesignature(fname, + chunksize=SCons.Node.FS.File.md5_chunksize*1024) + except EnvironmentError, e: + if not e.filename: + e.filename = fname + raise + return cs + + + memoizer_counters.append(SCons.Memoize.CountValue('get_size')) + + def get_size(self): + try: + return self._memo['get_size'] + except KeyError: + pass + + if self.rexists(): + size = self.rfile().getsize() + else: + size = 0 + + self._memo['get_size'] = size + + return size + + memoizer_counters.append(SCons.Memoize.CountValue('get_timestamp')) + + def get_timestamp(self): + try: + return self._memo['get_timestamp'] + except KeyError: + pass + + if self.rexists(): + timestamp = self.rfile().getmtime() + else: + timestamp = 0 + + self._memo['get_timestamp'] = timestamp + + return timestamp + + def store_info(self): + # Merge our build information into the already-stored entry. + # This accomodates "chained builds" where a file that's a target + # in one build (SConstruct file) is a source in a different build. + # See test/chained-build.py for the use case. + if do_store_info: + self.dir.sconsign().store_info(self.name, self) + + convert_copy_attrs = [ + 'bsources', + 'bimplicit', + 'bdepends', + 'bact', + 'bactsig', + 'ninfo', + ] + + + convert_sig_attrs = [ + 'bsourcesigs', + 'bimplicitsigs', + 'bdependsigs', + ] + + def convert_old_entry(self, old_entry): + # Convert a .sconsign entry from before the Big Signature + # Refactoring, doing what we can to convert its information + # to the new .sconsign entry format. + # + # The old format looked essentially like this: + # + # BuildInfo + # .ninfo (NodeInfo) + # .bsig + # .csig + # .timestamp + # .size + # .bsources + # .bsourcesigs ("signature" list) + # .bdepends + # .bdependsigs ("signature" list) + # .bimplicit + # .bimplicitsigs ("signature" list) + # .bact + # .bactsig + # + # The new format looks like this: + # + # .ninfo (NodeInfo) + # .bsig + # .csig + # .timestamp + # .size + # .binfo (BuildInfo) + # .bsources + # .bsourcesigs (NodeInfo list) + # .bsig + # .csig + # .timestamp + # .size + # .bdepends + # .bdependsigs (NodeInfo list) + # .bsig + # .csig + # .timestamp + # .size + # .bimplicit + # .bimplicitsigs (NodeInfo list) + # .bsig + # .csig + # .timestamp + # .size + # .bact + # .bactsig + # + # The basic idea of the new structure is that a NodeInfo always + # holds all available information about the state of a given Node + # at a certain point in time. The various .b*sigs lists can just + # be a list of pointers to the .ninfo attributes of the different + # dependent nodes, without any copying of information until it's + # time to pickle it for writing out to a .sconsign file. + # + # The complicating issue is that the *old* format only stored one + # "signature" per dependency, based on however the *last* build + # was configured. We don't know from just looking at it whether + # it was a build signature, a content signature, or a timestamp + # "signature". Since we no longer use build signatures, the + # best we can do is look at the length and if it's thirty two, + # assume that it was (or might have been) a content signature. + # If it was actually a build signature, then it will cause a + # rebuild anyway when it doesn't match the new content signature, + # but that's probably the best we can do. + import SCons.SConsign + new_entry = SCons.SConsign.SConsignEntry() + new_entry.binfo = self.new_binfo() + binfo = new_entry.binfo + for attr in self.convert_copy_attrs: + try: + value = getattr(old_entry, attr) + except AttributeError: + continue + setattr(binfo, attr, value) + delattr(old_entry, attr) + for attr in self.convert_sig_attrs: + try: + sig_list = getattr(old_entry, attr) + except AttributeError: + continue + value = [] + for sig in sig_list: + ninfo = self.new_ninfo() + if len(sig) == 32: + ninfo.csig = sig + else: + ninfo.timestamp = sig + value.append(ninfo) + setattr(binfo, attr, value) + delattr(old_entry, attr) + return new_entry + + memoizer_counters.append(SCons.Memoize.CountValue('get_stored_info')) + + def get_stored_info(self): + try: + return self._memo['get_stored_info'] + except KeyError: + pass + + try: + sconsign_entry = self.dir.sconsign().get_entry(self.name) + except (KeyError, EnvironmentError): + import SCons.SConsign + sconsign_entry = SCons.SConsign.SConsignEntry() + sconsign_entry.binfo = self.new_binfo() + sconsign_entry.ninfo = self.new_ninfo() + else: + if isinstance(sconsign_entry, FileBuildInfo): + # This is a .sconsign file from before the Big Signature + # Refactoring; convert it as best we can. + sconsign_entry = self.convert_old_entry(sconsign_entry) + try: + delattr(sconsign_entry.ninfo, 'bsig') + except AttributeError: + pass + + self._memo['get_stored_info'] = sconsign_entry + + return sconsign_entry + + def get_stored_implicit(self): + binfo = self.get_stored_info().binfo + binfo.prepare_dependencies() + try: return binfo.bimplicit + except AttributeError: return None + + def rel_path(self, other): + return self.dir.rel_path(other) + + def _get_found_includes_key(self, env, scanner, path): + return (id(env), id(scanner), path) + + memoizer_counters.append(SCons.Memoize.CountDict('get_found_includes', _get_found_includes_key)) + + def get_found_includes(self, env, scanner, path): + """Return the included implicit dependencies in this file. + Cache results so we only scan the file once per path + regardless of how many times this information is requested. + """ + memo_key = (id(env), id(scanner), path) + try: + memo_dict = self._memo['get_found_includes'] + except KeyError: + memo_dict = {} + self._memo['get_found_includes'] = memo_dict + else: + try: + return memo_dict[memo_key] + except KeyError: + pass + + if scanner: + # result = [n.disambiguate() for n in scanner(self, env, path)] + result = scanner(self, env, path) + result = map(lambda N: N.disambiguate(), result) + else: + result = [] + + memo_dict[memo_key] = result + + return result + + def _createDir(self): + # ensure that the directories for this node are + # created. + self.dir._create() + + def push_to_cache(self): + """Try to push the node into a cache + """ + # This should get called before the Nodes' .built() method is + # called, which would clear the build signature if the file has + # a source scanner. + # + # We have to clear the local memoized values *before* we push + # the node to cache so that the memoization of the self.exists() + # return value doesn't interfere. + if self.nocache: + return + self.clear_memoized_values() + if self.exists(): + self.get_build_env().get_CacheDir().push(self) + + def retrieve_from_cache(self): + """Try to retrieve the node's content from a cache + + This method is called from multiple threads in a parallel build, + so only do thread safe stuff here. Do thread unsafe stuff in + built(). + + Returns true iff the node was successfully retrieved. + """ + if self.nocache: + return None + if not self.is_derived(): + return None + return self.get_build_env().get_CacheDir().retrieve(self) + + def visited(self): + if self.exists(): + self.get_build_env().get_CacheDir().push_if_forced(self) + + ninfo = self.get_ninfo() + + csig = self.get_max_drift_csig() + if csig: + ninfo.csig = csig + + ninfo.timestamp = self.get_timestamp() + ninfo.size = self.get_size() + + if not self.has_builder(): + # This is a source file, but it might have been a target file + # in another build that included more of the DAG. Copy + # any build information that's stored in the .sconsign file + # into our binfo object so it doesn't get lost. + old = self.get_stored_info() + self.get_binfo().__dict__.update(old.binfo.__dict__) + + self.store_info() + + def find_src_builder(self): + if self.rexists(): + return None + scb = self.dir.src_builder() + if scb is _null: + if diskcheck_sccs(self.dir, self.name): + scb = get_DefaultSCCSBuilder() + elif diskcheck_rcs(self.dir, self.name): + scb = get_DefaultRCSBuilder() + else: + scb = None + if scb is not None: + try: + b = self.builder + except AttributeError: + b = None + if b is None: + self.builder_set(scb) + return scb + + def has_src_builder(self): + """Return whether this Node has a source builder or not. + + If this Node doesn't have an explicit source code builder, this + is where we figure out, on the fly, if there's a transparent + source code builder for it. + + Note that if we found a source builder, we also set the + self.builder attribute, so that all of the methods that actually + *build* this file don't have to do anything different. + """ + try: + scb = self.sbuilder + except AttributeError: + scb = self.sbuilder = self.find_src_builder() + return scb is not None + + def alter_targets(self): + """Return any corresponding targets in a variant directory. + """ + if self.is_derived(): + return [], None + return self.fs.variant_dir_target_climb(self, self.dir, [self.name]) + + def _rmv_existing(self): + self.clear_memoized_values() + e = Unlink(self, [], None) + if isinstance(e, SCons.Errors.BuildError): + raise e + + # + # Taskmaster interface subsystem + # + + def make_ready(self): + self.has_src_builder() + self.get_binfo() + + def prepare(self): + """Prepare for this file to be created.""" + SCons.Node.Node.prepare(self) + + if self.get_state() != SCons.Node.up_to_date: + if self.exists(): + if self.is_derived() and not self.precious: + self._rmv_existing() + else: + try: + self._createDir() + except SCons.Errors.StopError, drive: + desc = "No drive `%s' for target `%s'." % (drive, self) + raise SCons.Errors.StopError, desc + + # + # + # + + def remove(self): + """Remove this file.""" + if self.exists() or self.islink(): + self.fs.unlink(self.path) + return 1 + return None + + def do_duplicate(self, src): + self._createDir() + Unlink(self, None, None) + e = Link(self, src, None) + if isinstance(e, SCons.Errors.BuildError): + desc = "Cannot duplicate `%s' in `%s': %s." % (src.path, self.dir.path, e.errstr) + raise SCons.Errors.StopError, desc + self.linked = 1 + # The Link() action may or may not have actually + # created the file, depending on whether the -n + # option was used or not. Delete the _exists and + # _rexists attributes so they can be reevaluated. + self.clear() + + memoizer_counters.append(SCons.Memoize.CountValue('exists')) + + def exists(self): + try: + return self._memo['exists'] + except KeyError: + pass + # Duplicate from source path if we are set up to do this. + if self.duplicate and not self.is_derived() and not self.linked: + src = self.srcnode() + if src is not self: + # At this point, src is meant to be copied in a variant directory. + src = src.rfile() + if src.abspath != self.abspath: + if src.exists(): + self.do_duplicate(src) + # Can't return 1 here because the duplication might + # not actually occur if the -n option is being used. + else: + # The source file does not exist. Make sure no old + # copy remains in the variant directory. + if Base.exists(self) or self.islink(): + self.fs.unlink(self.path) + # Return None explicitly because the Base.exists() call + # above will have cached its value if the file existed. + self._memo['exists'] = None + return None + result = Base.exists(self) + self._memo['exists'] = result + return result + + # + # SIGNATURE SUBSYSTEM + # + + def get_max_drift_csig(self): + """ + Returns the content signature currently stored for this node + if it's been unmodified longer than the max_drift value, or the + max_drift value is 0. Returns None otherwise. + """ + old = self.get_stored_info() + mtime = self.get_timestamp() + + max_drift = self.fs.max_drift + if max_drift > 0: + if (time.time() - mtime) > max_drift: + try: + n = old.ninfo + if n.timestamp and n.csig and n.timestamp == mtime: + return n.csig + except AttributeError: + pass + elif max_drift == 0: + try: + return old.ninfo.csig + except AttributeError: + pass + + return None + + def get_csig(self): + """ + Generate a node's content signature, the digested signature + of its content. + + node - the node + cache - alternate node to use for the signature cache + returns - the content signature + """ + ninfo = self.get_ninfo() + try: + return ninfo.csig + except AttributeError: + pass + + csig = self.get_max_drift_csig() + if csig is None: + + try: + if self.get_size() < SCons.Node.FS.File.md5_chunksize: + contents = self.get_contents() + else: + csig = self.get_content_hash() + except IOError: + # This can happen if there's actually a directory on-disk, + # which can be the case if they've disabled disk checks, + # or if an action with a File target actually happens to + # create a same-named directory by mistake. + csig = '' + else: + if not csig: + csig = SCons.Util.MD5signature(contents) + + ninfo.csig = csig + + return csig + + # + # DECISION SUBSYSTEM + # + + def builder_set(self, builder): + SCons.Node.Node.builder_set(self, builder) + self.changed_since_last_build = self.decide_target + + def changed_content(self, target, prev_ni): + cur_csig = self.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + + def changed_state(self, target, prev_ni): + return self.state != SCons.Node.up_to_date + + def changed_timestamp_then_content(self, target, prev_ni): + if not self.changed_timestamp_match(target, prev_ni): + try: + self.get_ninfo().csig = prev_ni.csig + except AttributeError: + pass + return False + return self.changed_content(target, prev_ni) + + def changed_timestamp_newer(self, target, prev_ni): + try: + return self.get_timestamp() > target.get_timestamp() + except AttributeError: + return 1 + + def changed_timestamp_match(self, target, prev_ni): + try: + return self.get_timestamp() != prev_ni.timestamp + except AttributeError: + return 1 + + def decide_source(self, target, prev_ni): + return target.get_build_env().decide_source(self, target, prev_ni) + + def decide_target(self, target, prev_ni): + return target.get_build_env().decide_target(self, target, prev_ni) + + # Initialize this Node's decider function to decide_source() because + # every file is a source file until it has a Builder attached... + changed_since_last_build = decide_source + + def is_up_to_date(self): + T = 0 + if T: Trace('is_up_to_date(%s):' % self) + if not self.exists(): + if T: Trace(' not self.exists():') + # The file doesn't exist locally... + r = self.rfile() + if r != self: + # ...but there is one in a Repository... + if not self.changed(r): + if T: Trace(' changed(%s):' % r) + # ...and it's even up-to-date... + if self._local: + # ...and they'd like a local copy. + e = LocalCopy(self, r, None) + if isinstance(e, SCons.Errors.BuildError): + raise + self.store_info() + if T: Trace(' 1\n') + return 1 + self.changed() + if T: Trace(' None\n') + return None + else: + r = self.changed() + if T: Trace(' self.exists(): %s\n' % r) + return not r + + memoizer_counters.append(SCons.Memoize.CountValue('rfile')) + + def rfile(self): + try: + return self._memo['rfile'] + except KeyError: + pass + result = self + if not self.exists(): + norm_name = _my_normcase(self.name) + for dir in self.dir.get_all_rdirs(): + try: node = dir.entries[norm_name] + except KeyError: node = dir.file_on_disk(self.name) + if node and node.exists() and \ + (isinstance(node, File) or isinstance(node, Entry) \ + or not node.is_derived()): + result = node + # Copy over our local attributes to the repository + # Node so we identify shared object files in the + # repository and don't assume they're static. + # + # This isn't perfect; the attribute would ideally + # be attached to the object in the repository in + # case it was built statically in the repository + # and we changed it to shared locally, but that's + # rarely the case and would only occur if you + # intentionally used the same suffix for both + # shared and static objects anyway. So this + # should work well in practice. + result.attributes = self.attributes + break + self._memo['rfile'] = result + return result + + def rstr(self): + return str(self.rfile()) + + def get_cachedir_csig(self): + """ + Fetch a Node's content signature for purposes of computing + another Node's cachesig. + + This is a wrapper around the normal get_csig() method that handles + the somewhat obscure case of using CacheDir with the -n option. + Any files that don't exist would normally be "built" by fetching + them from the cache, but the normal get_csig() method will try + to open up the local file, which doesn't exist because the -n + option meant we didn't actually pull the file from cachedir. + But since the file *does* actually exist in the cachedir, we + can use its contents for the csig. + """ + try: + return self.cachedir_csig + except AttributeError: + pass + + cachedir, cachefile = self.get_build_env().get_CacheDir().cachepath(self) + if not self.exists() and cachefile and os.path.exists(cachefile): + self.cachedir_csig = SCons.Util.MD5filesignature(cachefile, \ + SCons.Node.FS.File.md5_chunksize * 1024) + else: + self.cachedir_csig = self.get_csig() + return self.cachedir_csig + + def get_cachedir_bsig(self): + try: + return self.cachesig + except AttributeError: + pass + + # Add the path to the cache signature, because multiple + # targets built by the same action will all have the same + # build signature, and we have to differentiate them somehow. + children = self.children() + executor = self.get_executor() + # sigs = [n.get_cachedir_csig() for n in children] + sigs = map(lambda n: n.get_cachedir_csig(), children) + sigs.append(SCons.Util.MD5signature(executor.get_contents())) + sigs.append(self.path) + result = self.cachesig = SCons.Util.MD5collect(sigs) + return result + + +default_fs = None + +def get_default_fs(): + global default_fs + if not default_fs: + default_fs = FS() + return default_fs + +class FileFinder: + """ + """ + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + memoizer_counters = [] + + def __init__(self): + self._memo = {} + + def filedir_lookup(self, p, fd=None): + """ + A helper method for find_file() that looks up a directory for + a file we're trying to find. This only creates the Dir Node if + it exists on-disk, since if the directory doesn't exist we know + we won't find any files in it... :-) + + It would be more compact to just use this as a nested function + with a default keyword argument (see the commented-out version + below), but that doesn't work unless you have nested scopes, + so we define it here just so this work under Python 1.5.2. + """ + if fd is None: + fd = self.default_filedir + dir, name = os.path.split(fd) + drive, d = os.path.splitdrive(dir) + if not name and d[:1] in ('/', os.sep): + #return p.fs.get_root(drive).dir_on_disk(name) + return p.fs.get_root(drive) + if dir: + p = self.filedir_lookup(p, dir) + if not p: + return None + norm_name = _my_normcase(name) + try: + node = p.entries[norm_name] + except KeyError: + return p.dir_on_disk(name) + if isinstance(node, Dir): + return node + if isinstance(node, Entry): + node.must_be_same(Dir) + return node + return None + + def _find_file_key(self, filename, paths, verbose=None): + return (filename, paths) + + memoizer_counters.append(SCons.Memoize.CountDict('find_file', _find_file_key)) + + def find_file(self, filename, paths, verbose=None): + """ + find_file(str, [Dir()]) -> [nodes] + + filename - a filename to find + paths - a list of directory path *nodes* to search in. Can be + represented as a list, a tuple, or a callable that is + called with no arguments and returns the list or tuple. + + returns - the node created from the found file. + + Find a node corresponding to either a derived file or a file + that exists already. + + Only the first file found is returned, and none is returned + if no file is found. + """ + memo_key = self._find_file_key(filename, paths) + try: + memo_dict = self._memo['find_file'] + except KeyError: + memo_dict = {} + self._memo['find_file'] = memo_dict + else: + try: + return memo_dict[memo_key] + except KeyError: + pass + + if verbose and not callable(verbose): + if not SCons.Util.is_String(verbose): + verbose = "find_file" + verbose = ' %s: ' % verbose + verbose = lambda s, v=verbose: sys.stdout.write(v + s) + + filedir, filename = os.path.split(filename) + if filedir: + # More compact code that we can't use until we drop + # support for Python 1.5.2: + # + #def filedir_lookup(p, fd=filedir): + # """ + # A helper function that looks up a directory for a file + # we're trying to find. This only creates the Dir Node + # if it exists on-disk, since if the directory doesn't + # exist we know we won't find any files in it... :-) + # """ + # dir, name = os.path.split(fd) + # if dir: + # p = filedir_lookup(p, dir) + # if not p: + # return None + # norm_name = _my_normcase(name) + # try: + # node = p.entries[norm_name] + # except KeyError: + # return p.dir_on_disk(name) + # if isinstance(node, Dir): + # return node + # if isinstance(node, Entry): + # node.must_be_same(Dir) + # return node + # if isinstance(node, Dir) or isinstance(node, Entry): + # return node + # return None + #paths = filter(None, map(filedir_lookup, paths)) + + self.default_filedir = filedir + paths = filter(None, map(self.filedir_lookup, paths)) + + result = None + for dir in paths: + if verbose: + verbose("looking for '%s' in '%s' ...\n" % (filename, dir)) + node, d = dir.srcdir_find_file(filename) + if node: + if verbose: + verbose("... FOUND '%s' in '%s'\n" % (filename, d)) + result = node + break + + memo_dict[memo_key] = result + + return result + +find_file = FileFinder().find_file + + +def invalidate_node_memos(targets): + """ + Invalidate the memoized values of all Nodes (files or directories) + that are associated with the given entries. Has been added to + clear the cache of nodes affected by a direct execution of an + action (e.g. Delete/Copy/Chmod). Existing Node caches become + inconsistent if the action is run through Execute(). The argument + `targets` can be a single Node object or filename, or a sequence + of Nodes/filenames. + """ + from traceback import extract_stack + + # First check if the cache really needs to be flushed. Only + # actions run in the SConscript with Execute() seem to be + # affected. XXX The way to check if Execute() is in the stacktrace + # is a very dirty hack and should be replaced by a more sensible + # solution. + for f in extract_stack(): + if f[2] == 'Execute' and f[0][-14:] == 'Environment.py': + break + else: + # Dont have to invalidate, so return + return + + if not SCons.Util.is_List(targets): + targets = [targets] + + for entry in targets: + # If the target is a Node object, clear the cache. If it is a + # filename, look up potentially existing Node object first. + try: + entry.clear_memoized_values() + except AttributeError: + # Not a Node object, try to look up Node by filename. XXX + # This creates Node objects even for those filenames which + # do not correspond to an existing Node object. + node = get_default_fs().Entry(entry) + if node: + node.clear_memoized_values() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/FSTests.py b/src/engine/SCons/Node/FSTests.py new file mode 100644 index 0000000..bf64ab7 --- /dev/null +++ b/src/engine/SCons/Node/FSTests.py @@ -0,0 +1,3520 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/FSTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string +import sys +import time +import unittest +from TestCmd import TestCmd +import shutil +import stat + +import SCons.Errors +import SCons.Node.FS +import SCons.Util +import SCons.Warnings + +built_it = None + +# This will be built-in in 2.3. For now fake it. +try : + True , False +except NameError : + True = 1 ; False = 0 + + +scanner_count = 0 + +class Scanner: + def __init__(self, node=None): + global scanner_count + scanner_count = scanner_count + 1 + self.hash = scanner_count + self.node = node + def path(self, env, dir, target=None, source=None): + return () + def __call__(self, node, env, path): + return [self.node] + def __hash__(self): + return self.hash + def select(self, node): + return self + def recurse_nodes(self, nodes): + return nodes + +class Environment: + def __init__(self): + self.scanner = Scanner() + def Dictionary(self, *args): + return {} + def autogenerate(self, **kw): + return {} + def get_scanner(self, skey): + return self.scanner + def Override(self, overrides): + return self + def _update(self, dict): + pass + +class Action: + def __call__(self, targets, sources, env, **kw): + global built_it + if kw.get('execute', 1): + built_it = 1 + return 0 + def show(self, string): + pass + def get_contents(self, target, source, env): + return "" + def genstring(self, target, source, env): + return "" + def strfunction(self, targets, sources, env): + return "" + def get_implicit_deps(self, target, source, env): + return [] + +class Builder: + def __init__(self, factory, action=Action()): + self.factory = factory + self.env = Environment() + self.overrides = {} + self.action = action + self.target_scanner = None + self.source_scanner = None + + def targets(self, t): + return [t] + + def source_factory(self, name): + return self.factory(name) + +class _tempdirTestCase(unittest.TestCase): + def setUp(self): + self.save_cwd = os.getcwd() + self.test = TestCmd(workdir='') + # FS doesn't like the cwd to be something other than its root. + os.chdir(self.test.workpath("")) + self.fs = SCons.Node.FS.FS() + + def tearDown(self): + os.chdir(self.save_cwd) + +class VariantDirTestCase(unittest.TestCase): + def runTest(self): + """Test variant dir functionality""" + test=TestCmd(workdir='') + + fs = SCons.Node.FS.FS() + f1 = fs.File('build/test1') + fs.VariantDir('build', 'src') + f2 = fs.File('build/test2') + d1 = fs.Dir('build') + assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path + assert f2.srcnode().path == os.path.normpath('src/test2'), f2.srcnode().path + assert d1.srcnode().path == 'src', d1.srcnode().path + + fs = SCons.Node.FS.FS() + f1 = fs.File('build/test1') + fs.VariantDir('build', '.') + f2 = fs.File('build/test2') + d1 = fs.Dir('build') + assert f1.srcnode().path == 'test1', f1.srcnode().path + assert f2.srcnode().path == 'test2', f2.srcnode().path + assert d1.srcnode().path == '.', d1.srcnode().path + + fs = SCons.Node.FS.FS() + fs.VariantDir('build/var1', 'src') + fs.VariantDir('build/var2', 'src') + f1 = fs.File('build/var1/test1') + f2 = fs.File('build/var2/test1') + assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path + assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path + + fs = SCons.Node.FS.FS() + fs.VariantDir('../var1', 'src') + fs.VariantDir('../var2', 'src') + f1 = fs.File('../var1/test1') + f2 = fs.File('../var2/test1') + assert f1.srcnode().path == os.path.normpath('src/test1'), f1.srcnode().path + assert f2.srcnode().path == os.path.normpath('src/test1'), f2.srcnode().path + + # Set up some files + test.subdir('work', ['work', 'src']) + test.subdir(['work', 'build'], ['work', 'build', 'var1']) + test.subdir(['work', 'build', 'var2']) + test.subdir('rep1', ['rep1', 'src']) + test.subdir(['rep1', 'build'], ['rep1', 'build', 'var1']) + test.subdir(['rep1', 'build', 'var2']) + + # A source file in the source directory + test.write([ 'work', 'src', 'test.in' ], 'test.in') + + # A source file in a subdir of the source directory + test.subdir([ 'work', 'src', 'new_dir' ]) + test.write([ 'work', 'src', 'new_dir', 'test9.out' ], 'test9.out\n') + + # A source file in the repository + test.write([ 'rep1', 'src', 'test2.in' ], 'test2.in') + + # Some source files in the variant directory + test.write([ 'work', 'build', 'var2', 'test.in' ], 'test.old') + test.write([ 'work', 'build', 'var2', 'test2.in' ], 'test2.old') + + # An old derived file in the variant directories + test.write([ 'work', 'build', 'var1', 'test.out' ], 'test.old') + test.write([ 'work', 'build', 'var2', 'test.out' ], 'test.old') + + # And just in case we are weird, a derived file in the source + # dir. + test.write([ 'work', 'src', 'test.out' ], 'test.out.src') + + # A derived file in the repository + test.write([ 'rep1', 'build', 'var1', 'test2.out' ], 'test2.out_rep') + test.write([ 'rep1', 'build', 'var2', 'test2.out' ], 'test2.out_rep') + + os.chdir(test.workpath('work')) + + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.VariantDir('build/var1', 'src', duplicate=0) + fs.VariantDir('build/var2', 'src') + f1 = fs.File('build/var1/test.in') + f1out = fs.File('build/var1/test.out') + f1out.builder = 1 + f1out_2 = fs.File('build/var1/test2.out') + f1out_2.builder = 1 + f2 = fs.File('build/var2/test.in') + f2out = fs.File('build/var2/test.out') + f2out.builder = 1 + f2out_2 = fs.File('build/var2/test2.out') + f2out_2.builder = 1 + fs.Repository(test.workpath('rep1')) + + assert f1.srcnode().path == os.path.normpath('src/test.in'),\ + f1.srcnode().path + # str(node) returns source path for duplicate = 0 + assert str(f1) == os.path.normpath('src/test.in'), str(f1) + # Build path does not exist + assert not f1.exists() + # ...but the actual file is not there... + assert not os.path.exists(f1.get_abspath()) + # And duplicate=0 should also work just like a Repository + assert f1.rexists() + # rfile() should point to the source path + assert f1.rfile().path == os.path.normpath('src/test.in'),\ + f1.rfile().path + + assert f2.srcnode().path == os.path.normpath('src/test.in'),\ + f2.srcnode().path + # str(node) returns build path for duplicate = 1 + assert str(f2) == os.path.normpath('build/var2/test.in'), str(f2) + # Build path exists + assert f2.exists() + # ...and exists() should copy the file from src to build path + assert test.read(['work', 'build', 'var2', 'test.in']) == 'test.in',\ + test.read(['work', 'build', 'var2', 'test.in']) + # Since exists() is true, so should rexists() be + assert f2.rexists() + + f3 = fs.File('build/var1/test2.in') + f4 = fs.File('build/var2/test2.in') + + assert f3.srcnode().path == os.path.normpath('src/test2.in'),\ + f3.srcnode().path + # str(node) returns source path for duplicate = 0 + assert str(f3) == os.path.normpath('src/test2.in'), str(f3) + # Build path does not exist + assert not f3.exists() + # Source path does not either + assert not f3.srcnode().exists() + # But we do have a file in the Repository + assert f3.rexists() + # rfile() should point to the source path + assert f3.rfile().path == os.path.normpath(test.workpath('rep1/src/test2.in')),\ + f3.rfile().path + + assert f4.srcnode().path == os.path.normpath('src/test2.in'),\ + f4.srcnode().path + # str(node) returns build path for duplicate = 1 + assert str(f4) == os.path.normpath('build/var2/test2.in'), str(f4) + # Build path should exist + assert f4.exists() + # ...and copy over the file into the local build path + assert test.read(['work', 'build', 'var2', 'test2.in']) == 'test2.in' + # should exist in repository, since exists() is true + assert f4.rexists() + # rfile() should point to ourselves + assert f4.rfile().path == os.path.normpath('build/var2/test2.in'),\ + f4.rfile().path + + f5 = fs.File('build/var1/test.out') + f6 = fs.File('build/var2/test.out') + + assert f5.exists() + # We should not copy the file from the source dir, since this is + # a derived file. + assert test.read(['work', 'build', 'var1', 'test.out']) == 'test.old' + + assert f6.exists() + # We should not copy the file from the source dir, since this is + # a derived file. + assert test.read(['work', 'build', 'var2', 'test.out']) == 'test.old' + + f7 = fs.File('build/var1/test2.out') + f8 = fs.File('build/var2/test2.out') + + assert not f7.exists() + assert f7.rexists() + r = f7.rfile().path + expect = os.path.normpath(test.workpath('rep1/build/var1/test2.out')) + assert r == expect, (repr(r), repr(expect)) + + assert not f8.exists() + assert f8.rexists() + assert f8.rfile().path == os.path.normpath(test.workpath('rep1/build/var2/test2.out')),\ + f8.rfile().path + + # Verify the Mkdir and Link actions are called + d9 = fs.Dir('build/var2/new_dir') + f9 = fs.File('build/var2/new_dir/test9.out') + + class MkdirAction(Action): + def __init__(self, dir_made): + self.dir_made = dir_made + def __call__(self, target, source, env, executor=None): + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + self.dir_made.extend(target) + + save_Link = SCons.Node.FS.Link + link_made = [] + def link_func(target, source, env, link_made=link_made): + link_made.append(target) + SCons.Node.FS.Link = link_func + + try: + dir_made = [] + d9.builder = Builder(fs.Dir, action=MkdirAction(dir_made)) + d9.reset_executor() + f9.exists() + expect = os.path.join('build', 'var2', 'new_dir') + assert dir_made[0].path == expect, dir_made[0].path + expect = os.path.join('build', 'var2', 'new_dir', 'test9.out') + assert link_made[0].path == expect, link_made[0].path + assert f9.linked + finally: + SCons.Node.FS.Link = save_Link + + # Test for an interesting pathological case...we have a source + # file in a build path, but not in a source path. This can + # happen if you switch from duplicate=1 to duplicate=0, then + # delete a source file. At one time, this would cause exists() + # to return a 1 but get_contents() to throw. + test.write([ 'work', 'build', 'var1', 'asourcefile' ], 'stuff') + f10 = fs.File('build/var1/asourcefile') + assert f10.exists() + assert f10.get_contents() == 'stuff', f10.get_contents() + + f11 = fs.File('src/file11') + t, m = f11.alter_targets() + bdt = map(lambda n: n.path, t) + var1_file11 = os.path.normpath('build/var1/file11') + var2_file11 = os.path.normpath('build/var2/file11') + assert bdt == [var1_file11, var2_file11], bdt + + f12 = fs.File('src/file12') + f12.builder = 1 + bdt, m = f12.alter_targets() + assert bdt == [], map(lambda n: n.path, bdt) + + d13 = fs.Dir('src/new_dir') + t, m = d13.alter_targets() + bdt = map(lambda n: n.path, t) + var1_new_dir = os.path.normpath('build/var1/new_dir') + var2_new_dir = os.path.normpath('build/var2/new_dir') + assert bdt == [var1_new_dir, var2_new_dir], bdt + + # Test that an IOError trying to Link a src file + # into a VariantDir ends up throwing a StopError. + fIO = fs.File("build/var2/IOError") + + save_Link = SCons.Node.FS.Link + def Link_IOError(target, source, env): + raise IOError, (17, "Link_IOError") + SCons.Node.FS.Link = SCons.Action.Action(Link_IOError, None) + + test.write(['work', 'src', 'IOError'], "work/src/IOError\n") + + try: + exc_caught = 0 + try: + fIO.exists() + except SCons.Errors.StopError: + exc_caught = 1 + assert exc_caught, "Should have caught a StopError" + + finally: + SCons.Node.FS.Link = save_Link + + # Test to see if Link() works... + test.subdir('src','build') + test.write('src/foo', 'src/foo\n') + os.chmod(test.workpath('src/foo'), stat.S_IRUSR) + SCons.Node.FS.Link(fs.File(test.workpath('build/foo')), + fs.File(test.workpath('src/foo')), + None) + os.chmod(test.workpath('src/foo'), stat.S_IRUSR | stat.S_IWRITE) + st=os.stat(test.workpath('build/foo')) + assert (stat.S_IMODE(st[stat.ST_MODE]) & stat.S_IWRITE), \ + stat.S_IMODE(st[stat.ST_MODE]) + + # This used to generate a UserError when we forbid the source + # directory from being outside the top-level SConstruct dir. + fs = SCons.Node.FS.FS() + fs.VariantDir('build', '/test/foo') + + exc_caught = 0 + try: + try: + fs = SCons.Node.FS.FS() + fs.VariantDir('build', 'build/src') + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "Should have caught a UserError." + finally: + test.unlink( "src/foo" ) + test.unlink( "build/foo" ) + + fs = SCons.Node.FS.FS() + fs.VariantDir('build', 'src1') + + # Calling the same VariantDir twice should work fine. + fs.VariantDir('build', 'src1') + + # Trying to move a variant dir to a second source dir + # should blow up + try: + fs.VariantDir('build', 'src2') + except SCons.Errors.UserError: + pass + else: + assert 0, "Should have caught a UserError." + + # Test against a former bug. Make sure we can get a repository + # path for the variant directory itself! + fs=SCons.Node.FS.FS(test.workpath('work')) + test.subdir('work') + fs.VariantDir('build/var3', 'src', duplicate=0) + d1 = fs.Dir('build/var3') + r = d1.rdir() + assert r == d1, "%s != %s" % (r, d1) + + # verify the link creation attempts in file_link() + class LinkSimulator : + """A class to intercept os.[sym]link() calls and track them.""" + + def __init__( self, duplicate, link, symlink, copy ) : + self.duplicate = duplicate + self.have = {} + self.have['hard'] = link + self.have['soft'] = symlink + self.have['copy'] = copy + + self.links_to_be_called = [] + for link in string.split(self.duplicate, '-'): + if self.have[link]: + self.links_to_be_called.append(link) + + def link_fail( self , src , dest ) : + next_link = self.links_to_be_called.pop(0) + assert next_link == "hard", \ + "Wrong link order: expected %s to be called "\ + "instead of hard" % next_link + raise OSError( "Simulating hard link creation error." ) + + def symlink_fail( self , src , dest ) : + next_link = self.links_to_be_called.pop(0) + assert next_link == "soft", \ + "Wrong link order: expected %s to be called "\ + "instead of soft" % next_link + raise OSError( "Simulating symlink creation error." ) + + def copy( self , src , dest ) : + next_link = self.links_to_be_called.pop(0) + assert next_link == "copy", \ + "Wrong link order: expected %s to be called "\ + "instead of copy" % next_link + # copy succeeds, but use the real copy + self.have['copy'](src, dest) + # end class LinkSimulator + + try: + SCons.Node.FS.set_duplicate("no-link-order") + assert 0, "Expected exception when passing an invalid duplicate to set_duplicate" + except SCons.Errors.InternalError: + pass + + for duplicate in SCons.Node.FS.Valid_Duplicates: + # save the real functions for later restoration + try: + real_link = os.link + except AttributeError: + real_link = None + try: + real_symlink = os.symlink + except AttributeError: + real_symlink = None + real_copy = shutil.copy2 + + simulator = LinkSimulator(duplicate, real_link, real_symlink, real_copy) + + # override the real functions with our simulation + os.link = simulator.link_fail + os.symlink = simulator.symlink_fail + shutil.copy2 = simulator.copy + + try: + + SCons.Node.FS.set_duplicate(duplicate) + + src_foo = test.workpath('src', 'foo') + build_foo = test.workpath('build', 'foo') + + test.write(src_foo, 'src/foo\n') + os.chmod(src_foo, stat.S_IRUSR) + try: + SCons.Node.FS.Link(fs.File(build_foo), + fs.File(src_foo), + None) + finally: + os.chmod(src_foo, stat.S_IRUSR | stat.S_IWRITE) + test.unlink(src_foo) + test.unlink(build_foo) + + finally: + # restore the real functions + if real_link: + os.link = real_link + else: + delattr(os, 'link') + if real_symlink: + os.symlink = real_symlink + else: + delattr(os, 'symlink') + shutil.copy2 = real_copy + + # Test VariantDir "reflection," where a same-named subdirectory + # exists underneath a variant_dir. + fs = SCons.Node.FS.FS() + fs.VariantDir('work/src/b1/b2', 'work/src') + + dir_list = [ + 'work/src', + 'work/src/b1', + 'work/src/b1/b2', + 'work/src/b1/b2/b1', + 'work/src/b1/b2/b1/b2', + 'work/src/b1/b2/b1/b2/b1', + 'work/src/b1/b2/b1/b2/b1/b2', + ] + + srcnode_map = { + 'work/src/b1/b2' : 'work/src', + 'work/src/b1/b2/f' : 'work/src/f', + 'work/src/b1/b2/b1' : 'work/src/b1/', + 'work/src/b1/b2/b1/f' : 'work/src/b1/f', + 'work/src/b1/b2/b1/b2' : 'work/src/b1/b2', + 'work/src/b1/b2/b1/b2/f' : 'work/src/b1/b2/f', + 'work/src/b1/b2/b1/b2/b1' : 'work/src/b1/b2/b1', + 'work/src/b1/b2/b1/b2/b1/f' : 'work/src/b1/b2/b1/f', + 'work/src/b1/b2/b1/b2/b1/b2' : 'work/src/b1/b2/b1/b2', + 'work/src/b1/b2/b1/b2/b1/b2/f' : 'work/src/b1/b2/b1/b2/f', + } + + alter_map = { + 'work/src' : 'work/src/b1/b2', + 'work/src/f' : 'work/src/b1/b2/f', + 'work/src/b1' : 'work/src/b1/b2/b1', + 'work/src/b1/f' : 'work/src/b1/b2/b1/f', + } + + errors = 0 + + for dir in dir_list: + dnode = fs.Dir(dir) + f = dir + '/f' + fnode = fs.File(dir + '/f') + + dp = dnode.srcnode().path + expect = os.path.normpath(srcnode_map.get(dir, dir)) + if dp != expect: + print "Dir `%s' srcnode() `%s' != expected `%s'" % (dir, dp, expect) + errors = errors + 1 + + fp = fnode.srcnode().path + expect = os.path.normpath(srcnode_map.get(f, f)) + if fp != expect: + print "File `%s' srcnode() `%s' != expected `%s'" % (f, fp, expect) + errors = errors + 1 + + for dir in dir_list: + dnode = fs.Dir(dir) + f = dir + '/f' + fnode = fs.File(dir + '/f') + + t, m = dnode.alter_targets() + tp = t[0].path + expect = os.path.normpath(alter_map.get(dir, dir)) + if tp != expect: + print "Dir `%s' alter_targets() `%s' != expected `%s'" % (dir, tp, expect) + errors = errors + 1 + + t, m = fnode.alter_targets() + tp = t[0].path + expect = os.path.normpath(alter_map.get(f, f)) + if tp != expect: + print "File `%s' alter_targets() `%s' != expected `%s'" % (f, tp, expect) + errors = errors + 1 + + self.failIf(errors) + +class BaseTestCase(_tempdirTestCase): + def test_stat(self): + """Test the Base.stat() method""" + test = self.test + test.write("e1", "e1\n") + fs = SCons.Node.FS.FS() + + e1 = fs.Entry('e1') + s = e1.stat() + assert s is not None, s + + e2 = fs.Entry('e2') + s = e2.stat() + assert s is None, s + + def test_getmtime(self): + """Test the Base.getmtime() method""" + test = self.test + test.write("file", "file\n") + fs = SCons.Node.FS.FS() + + file = fs.Entry('file') + assert file.getmtime() + + file = fs.Entry('nonexistent') + mtime = file.getmtime() + assert mtime is None, mtime + + def test_getsize(self): + """Test the Base.getsize() method""" + test = self.test + test.write("file", "file\n") + fs = SCons.Node.FS.FS() + + file = fs.Entry('file') + size = file.getsize() + assert size == 5, size + + file = fs.Entry('nonexistent') + size = file.getsize() + assert size is None, size + + def test_isdir(self): + """Test the Base.isdir() method""" + test = self.test + test.subdir('dir') + test.write("file", "file\n") + fs = SCons.Node.FS.FS() + + dir = fs.Entry('dir') + assert dir.isdir() + + file = fs.Entry('file') + assert not file.isdir() + + nonexistent = fs.Entry('nonexistent') + assert not nonexistent.isdir() + + def test_isfile(self): + """Test the Base.isfile() method""" + test = self.test + test.subdir('dir') + test.write("file", "file\n") + fs = SCons.Node.FS.FS() + + dir = fs.Entry('dir') + assert not dir.isfile() + + file = fs.Entry('file') + assert file.isfile() + + nonexistent = fs.Entry('nonexistent') + assert not nonexistent.isfile() + + if hasattr(os, 'symlink'): + def test_islink(self): + """Test the Base.islink() method""" + test = self.test + test.subdir('dir') + test.write("file", "file\n") + test.symlink("symlink", "symlink") + fs = SCons.Node.FS.FS() + + dir = fs.Entry('dir') + assert not dir.islink() + + file = fs.Entry('file') + assert not file.islink() + + symlink = fs.Entry('symlink') + assert symlink.islink() + + nonexistent = fs.Entry('nonexistent') + assert not nonexistent.islink() + +class DirNodeInfoTestCase(_tempdirTestCase): + def test___init__(self): + """Test DirNodeInfo initialization""" + ddd = self.fs.Dir('ddd') + ni = SCons.Node.FS.DirNodeInfo(ddd) + +class DirBuildInfoTestCase(_tempdirTestCase): + def test___init__(self): + """Test DirBuildInfo initialization""" + ddd = self.fs.Dir('ddd') + bi = SCons.Node.FS.DirBuildInfo(ddd) + +class FileNodeInfoTestCase(_tempdirTestCase): + def test___init__(self): + """Test FileNodeInfo initialization""" + fff = self.fs.File('fff') + ni = SCons.Node.FS.FileNodeInfo(fff) + assert isinstance(ni, SCons.Node.FS.FileNodeInfo) + + def test_update(self): + """Test updating a File.NodeInfo with on-disk information""" + test = self.test + fff = self.fs.File('fff') + + ni = SCons.Node.FS.FileNodeInfo(fff) + + test.write('fff', "fff\n") + + st = os.stat('fff') + + ni.update(fff) + + assert hasattr(ni, 'timestamp') + assert hasattr(ni, 'size') + + ni.timestamp = 0 + ni.size = 0 + + ni.update(fff) + + mtime = st[stat.ST_MTIME] + assert ni.timestamp == mtime, (ni.timestamp, mtime) + size = st[stat.ST_SIZE] + assert ni.size == size, (ni.size, size) + + import time + time.sleep(2) + + test.write('fff', "fff longer size, different time stamp\n") + + st = os.stat('fff') + + mtime = st[stat.ST_MTIME] + assert ni.timestamp != mtime, (ni.timestamp, mtime) + size = st[stat.ST_SIZE] + assert ni.size != size, (ni.size, size) + + #fff.clear() + #ni.update(fff) + + #st = os.stat('fff') + + #mtime = st[stat.ST_MTIME] + #assert ni.timestamp == mtime, (ni.timestamp, mtime) + #size = st[stat.ST_SIZE] + #assert ni.size == size, (ni.size, size) + +class FileBuildInfoTestCase(_tempdirTestCase): + def test___init__(self): + """Test File.BuildInfo initialization""" + fff = self.fs.File('fff') + bi = SCons.Node.FS.FileBuildInfo(fff) + assert bi, bi + + def test_convert_to_sconsign(self): + """Test converting to .sconsign file format""" + fff = self.fs.File('fff') + bi = SCons.Node.FS.FileBuildInfo(fff) + assert hasattr(bi, 'convert_to_sconsign') + + def test_convert_from_sconsign(self): + """Test converting from .sconsign file format""" + fff = self.fs.File('fff') + bi = SCons.Node.FS.FileBuildInfo(fff) + assert hasattr(bi, 'convert_from_sconsign') + + def test_prepare_dependencies(self): + """Test that we have a prepare_dependencies() method""" + fff = self.fs.File('fff') + bi = SCons.Node.FS.FileBuildInfo(fff) + bi.prepare_dependencies() + + def test_format(self): + """Test the format() method""" + f1 = self.fs.File('f1') + bi1 = SCons.Node.FS.FileBuildInfo(f1) + + s1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n1')) + s1sig.csig = 1 + d1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n2')) + d1sig.timestamp = 2 + i1sig = SCons.Node.FS.FileNodeInfo(self.fs.File('n3')) + i1sig.size = 3 + + bi1.bsources = [self.fs.File('s1')] + bi1.bdepends = [self.fs.File('d1')] + bi1.bimplicit = [self.fs.File('i1')] + bi1.bsourcesigs = [s1sig] + bi1.bdependsigs = [d1sig] + bi1.bimplicitsigs = [i1sig] + bi1.bact = 'action' + bi1.bactsig = 'actionsig' + + expect_lines = [ + 's1: 1 None None', + 'd1: None 2 None', + 'i1: None None 3', + 'actionsig [action]', + ] + + expect = string.join(expect_lines, '\n') + format = bi1.format() + assert format == expect, (repr(expect), repr(format)) + +class FSTestCase(_tempdirTestCase): + def test_runTest(self): + """Test FS (file system) Node operations + + This test case handles all of the file system node + tests in one environment, so we don't have to set up a + complicated directory structure for each test individually. + """ + test = self.test + + test.subdir('sub', ['sub', 'dir']) + + wp = test.workpath('') + sub = test.workpath('sub', '') + sub_dir = test.workpath('sub', 'dir', '') + sub_dir_foo = test.workpath('sub', 'dir', 'foo', '') + sub_dir_foo_bar = test.workpath('sub', 'dir', 'foo', 'bar', '') + sub_foo = test.workpath('sub', 'foo', '') + + os.chdir(sub_dir) + + fs = SCons.Node.FS.FS() + + e1 = fs.Entry('e1') + assert isinstance(e1, SCons.Node.FS.Entry) + + d1 = fs.Dir('d1') + assert isinstance(d1, SCons.Node.FS.Dir) + assert d1.cwd is d1, d1 + + f1 = fs.File('f1', directory = d1) + assert isinstance(f1, SCons.Node.FS.File) + + d1_f1 = os.path.join('d1', 'f1') + assert f1.path == d1_f1, "f1.path %s != %s" % (f1.path, d1_f1) + assert str(f1) == d1_f1, "str(f1) %s != %s" % (str(f1), d1_f1) + + x1 = d1.File('x1') + assert isinstance(x1, SCons.Node.FS.File) + assert str(x1) == os.path.join('d1', 'x1') + + x2 = d1.Dir('x2') + assert isinstance(x2, SCons.Node.FS.Dir) + assert str(x2) == os.path.join('d1', 'x2') + + x3 = d1.Entry('x3') + assert isinstance(x3, SCons.Node.FS.Entry) + assert str(x3) == os.path.join('d1', 'x3') + + assert d1.File(x1) == x1 + assert d1.Dir(x2) == x2 + assert d1.Entry(x3) == x3 + + x1.cwd = d1 + + x4 = x1.File('x4') + assert str(x4) == os.path.join('d1', 'x4') + + x5 = x1.Dir('x5') + assert str(x5) == os.path.join('d1', 'x5') + + x6 = x1.Entry('x6') + assert str(x6) == os.path.join('d1', 'x6') + x7 = x1.Entry('x7') + assert str(x7) == os.path.join('d1', 'x7') + + assert x1.File(x4) == x4 + assert x1.Dir(x5) == x5 + assert x1.Entry(x6) == x6 + assert x1.Entry(x7) == x7 + + assert x1.Entry(x5) == x5 + try: + x1.File(x5) + except TypeError: + pass + else: + raise Exception, "did not catch expected TypeError" + + assert x1.Entry(x4) == x4 + try: + x1.Dir(x4) + except TypeError: + pass + else: + raise Exception, "did not catch expected TypeError" + + x6 = x1.File(x6) + assert isinstance(x6, SCons.Node.FS.File) + + x7 = x1.Dir(x7) + assert isinstance(x7, SCons.Node.FS.Dir) + + seps = [os.sep] + if os.sep != '/': + seps = seps + ['/'] + + drive, path = os.path.splitdrive(os.getcwd()) + + def _do_Dir_test(lpath, path_, abspath_, up_path_, sep, fileSys=fs, drive=drive): + dir = fileSys.Dir(string.replace(lpath, '/', sep)) + + if os.sep != '/': + path_ = string.replace(path_, '/', os.sep) + abspath_ = string.replace(abspath_, '/', os.sep) + up_path_ = string.replace(up_path_, '/', os.sep) + + def strip_slash(p, drive=drive): + if p[-1] == os.sep and len(p) > 1: + p = p[:-1] + if p[0] == os.sep: + p = drive + p + return p + path = strip_slash(path_) + abspath = strip_slash(abspath_) + up_path = strip_slash(up_path_) + name = string.split(abspath, os.sep)[-1] + + assert dir.name == name, \ + "dir.name %s != expected name %s" % \ + (dir.name, name) + assert dir.path == path, \ + "dir.path %s != expected path %s" % \ + (dir.path, path) + assert str(dir) == path, \ + "str(dir) %s != expected path %s" % \ + (str(dir), path) + assert dir.get_abspath() == abspath, \ + "dir.abspath %s != expected absolute path %s" % \ + (dir.get_abspath(), abspath) + assert dir.up().path == up_path, \ + "dir.up().path %s != expected parent path %s" % \ + (dir.up().path, up_path) + + for sep in seps: + + def Dir_test(lpath, path_, abspath_, up_path_, sep=sep, func=_do_Dir_test): + return func(lpath, path_, abspath_, up_path_, sep) + + Dir_test('', './', sub_dir, sub) + Dir_test('foo', 'foo/', sub_dir_foo, './') + Dir_test('foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/') + Dir_test('/foo', '/foo/', '/foo/', '/') + Dir_test('/foo/bar', '/foo/bar/', '/foo/bar/', '/foo/') + Dir_test('..', sub, sub, wp) + Dir_test('foo/..', './', sub_dir, sub) + Dir_test('../foo', sub_foo, sub_foo, sub) + Dir_test('.', './', sub_dir, sub) + Dir_test('./.', './', sub_dir, sub) + Dir_test('foo/./bar', 'foo/bar/', sub_dir_foo_bar, 'foo/') + Dir_test('#../foo', sub_foo, sub_foo, sub) + Dir_test('#/../foo', sub_foo, sub_foo, sub) + Dir_test('#foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/') + Dir_test('#/foo/bar', 'foo/bar/', sub_dir_foo_bar, 'foo/') + Dir_test('#', './', sub_dir, sub) + + try: + f2 = fs.File(string.join(['f1', 'f2'], sep), directory = d1) + except TypeError, x: + assert str(x) == ("Tried to lookup File '%s' as a Dir." % + d1_f1), x + except: + raise + + try: + dir = fs.Dir(string.join(['d1', 'f1'], sep)) + except TypeError, x: + assert str(x) == ("Tried to lookup File '%s' as a Dir." % + d1_f1), x + except: + raise + + try: + f2 = fs.File('d1') + except TypeError, x: + assert str(x) == ("Tried to lookup Dir '%s' as a File." % + 'd1'), x + except: + raise + + # Test that just specifying the drive works to identify + # its root directory. + p = os.path.abspath(test.workpath('root_file')) + drive, path = os.path.splitdrive(p) + if drive: + # The assert below probably isn't correct for the general + # case, but it works for Windows, which covers a lot + # of ground... + dir = fs.Dir(drive) + assert str(dir) == drive + os.sep, str(dir) + + # Make sure that lookups with and without the drive are + # equivalent. + p = os.path.abspath(test.workpath('some/file')) + drive, path = os.path.splitdrive(p) + + e1 = fs.Entry(p) + e2 = fs.Entry(path) + assert e1 is e2, (e1, e2) + assert str(e1) is str(e2), (str(e1), str(e2)) + + # Test for a bug in 0.04 that did not like looking up + # dirs with a trailing slash on Windows. + d=fs.Dir('./') + assert d.path == '.', d.abspath + d=fs.Dir('foo/') + assert d.path == 'foo', d.abspath + + # Test for sub-classing of node building. + global built_it + + built_it = None + assert not built_it + d1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE + d1.builder_set(Builder(fs.File)) + d1.reset_executor() + d1.env_set(Environment()) + d1.build() + assert built_it + + built_it = None + assert not built_it + f1.add_source([SCons.Node.Node()]) # XXX FAKE SUBCLASS ATTRIBUTE + f1.builder_set(Builder(fs.File)) + f1.reset_executor() + f1.env_set(Environment()) + f1.build() + assert built_it + + def match(path, expect): + expect = string.replace(expect, '/', os.sep) + assert path == expect, "path %s != expected %s" % (path, expect) + + e1 = fs.Entry("d1") + assert e1.__class__.__name__ == 'Dir' + match(e1.path, "d1") + match(e1.dir.path, ".") + + e2 = fs.Entry("d1/f1") + assert e2.__class__.__name__ == 'File' + match(e2.path, "d1/f1") + match(e2.dir.path, "d1") + + e3 = fs.Entry("e3") + assert e3.__class__.__name__ == 'Entry' + match(e3.path, "e3") + match(e3.dir.path, ".") + + e4 = fs.Entry("d1/e4") + assert e4.__class__.__name__ == 'Entry' + match(e4.path, "d1/e4") + match(e4.dir.path, "d1") + + e5 = fs.Entry("e3/e5") + assert e3.__class__.__name__ == 'Dir' + match(e3.path, "e3") + match(e3.dir.path, ".") + assert e5.__class__.__name__ == 'Entry' + match(e5.path, "e3/e5") + match(e5.dir.path, "e3") + + e6 = fs.Dir("d1/e4") + assert e6 is e4 + assert e4.__class__.__name__ == 'Dir' + match(e4.path, "d1/e4") + match(e4.dir.path, "d1") + + e7 = fs.File("e3/e5") + assert e7 is e5 + assert e5.__class__.__name__ == 'File' + match(e5.path, "e3/e5") + match(e5.dir.path, "e3") + + fs.chdir(fs.Dir('subdir')) + f11 = fs.File("f11") + match(f11.path, "subdir/f11") + d12 = fs.Dir("d12") + e13 = fs.Entry("subdir/e13") + match(e13.path, "subdir/subdir/e13") + fs.chdir(fs.Dir('..')) + + # Test scanning + f1.builder_set(Builder(fs.File)) + f1.env_set(Environment()) + xyz = fs.File("xyz") + f1.builder.target_scanner = Scanner(xyz) + + f1.scan() + assert f1.implicit[0].path == "xyz" + f1.implicit = [] + f1.scan() + assert f1.implicit == [] + f1.implicit = None + f1.scan() + assert f1.implicit[0].path == "xyz" + + # Test underlying scanning functionality in get_found_includes() + env = Environment() + f12 = fs.File("f12") + t1 = fs.File("t1") + + deps = f12.get_found_includes(env, None, t1) + assert deps == [], deps + + class MyScanner(Scanner): + call_count = 0 + def __call__(self, node, env, path): + self.call_count = self.call_count + 1 + return Scanner.__call__(self, node, env, path) + s = MyScanner(xyz) + + deps = f12.get_found_includes(env, s, t1) + assert deps == [xyz], deps + assert s.call_count == 1, s.call_count + + f12.built() + + deps = f12.get_found_includes(env, s, t1) + assert deps == [xyz], deps + assert s.call_count == 2, s.call_count + + env2 = Environment() + + deps = f12.get_found_includes(env2, s, t1) + assert deps == [xyz], deps + assert s.call_count == 3, s.call_count + + + + # Make sure we can scan this file even if the target isn't + # a file that has a scanner (it might be an Alias, e.g.). + class DummyNode: + pass + + deps = f12.get_found_includes(env, s, DummyNode()) + assert deps == [xyz], deps + + # Test building a file whose directory is not there yet... + f1 = fs.File(test.workpath("foo/bar/baz/ack")) + assert not f1.dir.exists() + f1.prepare() + f1.build() + assert f1.dir.exists() + + os.chdir('..') + + # Test getcwd() + fs = SCons.Node.FS.FS() + assert str(fs.getcwd()) == ".", str(fs.getcwd()) + fs.chdir(fs.Dir('subdir')) + # The cwd's path is always "." + assert str(fs.getcwd()) == ".", str(fs.getcwd()) + assert fs.getcwd().path == 'subdir', fs.getcwd().path + fs.chdir(fs.Dir('../..')) + assert fs.getcwd().path == test.workdir, fs.getcwd().path + + f1 = fs.File(test.workpath("do_i_exist")) + assert not f1.exists() + test.write("do_i_exist","\n") + assert not f1.exists(), "exists() call not cached" + f1.built() + assert f1.exists(), "exists() call caching not reset" + test.unlink("do_i_exist") + assert f1.exists() + f1.built() + assert not f1.exists() + + # For some reason, in Windows, the \x1a character terminates + # the reading of files in text mode. This tests that + # get_contents() returns the binary contents. + test.write("binary_file", "Foo\x1aBar") + f1 = fs.File(test.workpath("binary_file")) + assert f1.get_contents() == "Foo\x1aBar", f1.get_contents() + + try: + # TODO(1.5) + eval('test_string = u"Foo\x1aBar"') + except SyntaxError: + pass + else: + # This tests to make sure we can decode UTF-8 text files. + test.write("utf8_file", test_string.encode('utf-8')) + f1 = fs.File(test.workpath("utf8_file")) + assert eval('f1.get_text_contents() == u"Foo\x1aBar"'), \ + f1.get_text_contents() + + def nonexistent(method, s): + try: + x = method(s, create = 0) + except SCons.Errors.UserError: + pass + else: + raise Exception, "did not catch expected UserError" + + nonexistent(fs.Entry, 'nonexistent') + nonexistent(fs.Entry, 'nonexistent/foo') + + nonexistent(fs.File, 'nonexistent') + nonexistent(fs.File, 'nonexistent/foo') + + nonexistent(fs.Dir, 'nonexistent') + nonexistent(fs.Dir, 'nonexistent/foo') + + test.write("preserve_me", "\n") + assert os.path.exists(test.workpath("preserve_me")) + f1 = fs.File(test.workpath("preserve_me")) + f1.prepare() + assert os.path.exists(test.workpath("preserve_me")) + + test.write("remove_me", "\n") + assert os.path.exists(test.workpath("remove_me")) + f1 = fs.File(test.workpath("remove_me")) + f1.builder = Builder(fs.File) + f1.env_set(Environment()) + f1.prepare() + assert not os.path.exists(test.workpath("remove_me")) + + e = fs.Entry('e_local') + assert not hasattr(e, '_local') + e.set_local() + assert e._local == 1 + f = fs.File('e_local') + assert f._local == 1 + f = fs.File('f_local') + assert f._local == 0 + + #XXX test_is_up_to_date() for directories + + #XXX test_sconsign() for directories + + #XXX test_set_signature() for directories + + #XXX test_build() for directories + + #XXX test_root() + + # test Entry.get_contents() + e = fs.Entry('does_not_exist') + c = e.get_contents() + assert c == "", c + assert e.__class__ == SCons.Node.FS.Entry + + test.write("file", "file\n") + try: + e = fs.Entry('file') + c = e.get_contents() + assert c == "file\n", c + assert e.__class__ == SCons.Node.FS.File + finally: + test.unlink("file") + + # test Entry.get_text_contents() + e = fs.Entry('does_not_exist') + c = e.get_text_contents() + assert c == "", c + assert e.__class__ == SCons.Node.FS.Entry + + test.write("file", "file\n") + try: + e = fs.Entry('file') + c = e.get_text_contents() + assert c == "file\n", c + assert e.__class__ == SCons.Node.FS.File + finally: + test.unlink("file") + + test.subdir("dir") + e = fs.Entry('dir') + c = e.get_contents() + assert c == "", c + assert e.__class__ == SCons.Node.FS.Dir + + c = e.get_text_contents() + try: + eval('assert c == u"", c') + except SyntaxError: + assert c == "" + + if hasattr(os, 'symlink'): + os.symlink('nonexistent', test.workpath('dangling_symlink')) + e = fs.Entry('dangling_symlink') + c = e.get_contents() + assert e.__class__ == SCons.Node.FS.Entry, e.__class__ + assert c == "", c + c = e.get_text_contents() + try: + eval('assert c == u"", c') + except SyntaxError: + assert c == "", c + + test.write("tstamp", "tstamp\n") + try: + # Okay, *this* manipulation accomodates Windows FAT file systems + # that only have two-second granularity on their timestamps. + # We round down the current time to the nearest even integer + # value, subtract two to make sure the timestamp is not "now," + # and then convert it back to a float. + tstamp = float(int(time.time() / 2) * 2) - 2 + os.utime(test.workpath("tstamp"), (tstamp - 2.0, tstamp)) + f = fs.File("tstamp") + t = f.get_timestamp() + assert t == tstamp, "expected %f, got %f" % (tstamp, t) + finally: + test.unlink("tstamp") + + test.subdir('tdir1') + d = fs.Dir('tdir1') + t = d.get_timestamp() + assert t == 0, "expected 0, got %s" % str(t) + + test.subdir('tdir2') + f1 = test.workpath('tdir2', 'file1') + f2 = test.workpath('tdir2', 'file2') + test.write(f1, 'file1\n') + test.write(f2, 'file2\n') + current_time = float(int(time.time() / 2) * 2) + t1 = current_time - 4.0 + t2 = current_time - 2.0 + os.utime(f1, (t1 - 2.0, t1)) + os.utime(f2, (t2 - 2.0, t2)) + d = fs.Dir('tdir2') + fs.File(f1) + fs.File(f2) + t = d.get_timestamp() + assert t == t2, "expected %f, got %f" % (t2, t) + + skey = fs.Entry('eee.x').scanner_key() + assert skey == '.x', skey + skey = fs.Entry('eee.xyz').scanner_key() + assert skey == '.xyz', skey + + skey = fs.File('fff.x').scanner_key() + assert skey == '.x', skey + skey = fs.File('fff.xyz').scanner_key() + assert skey == '.xyz', skey + + skey = fs.Dir('ddd.x').scanner_key() + assert skey is None, skey + + test.write("i_am_not_a_directory", "\n") + try: + exc_caught = 0 + try: + fs.Dir(test.workpath("i_am_not_a_directory")) + except TypeError: + exc_caught = 1 + assert exc_caught, "Should have caught a TypeError" + finally: + test.unlink("i_am_not_a_directory") + + exc_caught = 0 + try: + fs.File(sub_dir) + except TypeError: + exc_caught = 1 + assert exc_caught, "Should have caught a TypeError" + + # XXX test_is_up_to_date() + + d = fs.Dir('dir') + r = d.remove() + assert r is None, r + + f = fs.File('does_not_exist') + r = f.remove() + assert r is None, r + + test.write('exists', "exists\n") + f = fs.File('exists') + r = f.remove() + assert r, r + assert not os.path.exists(test.workpath('exists')), "exists was not removed" + + symlink = test.workpath('symlink') + try: + os.symlink(test.workpath('does_not_exist'), symlink) + assert os.path.islink(symlink) + f = fs.File('symlink') + r = f.remove() + assert r, r + assert not os.path.islink(symlink), "symlink was not removed" + except AttributeError: + pass + + test.write('can_not_remove', "can_not_remove\n") + test.writable(test.workpath('.'), 0) + fp = open(test.workpath('can_not_remove')) + + f = fs.File('can_not_remove') + exc_caught = 0 + try: + r = f.remove() + except OSError: + exc_caught = 1 + + fp.close() + + assert exc_caught, "Should have caught an OSError, r = " + str(r) + + f = fs.Entry('foo/bar/baz') + assert f.for_signature() == 'baz', f.for_signature() + assert f.get_string(0) == os.path.normpath('foo/bar/baz'), \ + f.get_string(0) + assert f.get_string(1) == 'baz', f.get_string(1) + + def test_drive_letters(self): + """Test drive-letter look-ups""" + + test = self.test + + test.subdir('sub', ['sub', 'dir']) + + def drive_workpath(drive, dirs, test=test): + x = apply(test.workpath, dirs) + drive, path = os.path.splitdrive(x) + return 'X:' + path + + wp = drive_workpath('X:', ['']) + + if wp[-1] in (os.sep, '/'): + tmp = os.path.split(wp[:-1])[0] + else: + tmp = os.path.split(wp)[0] + + parent_tmp = os.path.split(tmp)[0] + if parent_tmp == 'X:': + parent_tmp = 'X:' + os.sep + + tmp_foo = os.path.join(tmp, 'foo') + + foo = drive_workpath('X:', ['foo']) + foo_bar = drive_workpath('X:', ['foo', 'bar']) + sub = drive_workpath('X:', ['sub', '']) + sub_dir = drive_workpath('X:', ['sub', 'dir', '']) + sub_dir_foo = drive_workpath('X:', ['sub', 'dir', 'foo', '']) + sub_dir_foo_bar = drive_workpath('X:', ['sub', 'dir', 'foo', 'bar', '']) + sub_foo = drive_workpath('X:', ['sub', 'foo', '']) + + fs = SCons.Node.FS.FS() + + seps = [os.sep] + if os.sep != '/': + seps = seps + ['/'] + + def _do_Dir_test(lpath, path_, up_path_, sep, fileSys=fs): + dir = fileSys.Dir(string.replace(lpath, '/', sep)) + + if os.sep != '/': + path_ = string.replace(path_, '/', os.sep) + up_path_ = string.replace(up_path_, '/', os.sep) + + def strip_slash(p): + if p[-1] == os.sep and len(p) > 3: + p = p[:-1] + return p + path = strip_slash(path_) + up_path = strip_slash(up_path_) + name = string.split(path, os.sep)[-1] + + assert dir.name == name, \ + "dir.name %s != expected name %s" % \ + (dir.name, name) + assert dir.path == path, \ + "dir.path %s != expected path %s" % \ + (dir.path, path) + assert str(dir) == path, \ + "str(dir) %s != expected path %s" % \ + (str(dir), path) + assert dir.up().path == up_path, \ + "dir.up().path %s != expected parent path %s" % \ + (dir.up().path, up_path) + + save_os_path = os.path + save_os_sep = os.sep + try: + import ntpath + os.path = ntpath + os.sep = '\\' + SCons.Node.FS.initialize_do_splitdrive() + SCons.Node.FS.initialize_normpath_check() + + for sep in seps: + + def Dir_test(lpath, path_, up_path_, sep=sep, func=_do_Dir_test): + return func(lpath, path_, up_path_, sep) + + Dir_test('#X:', wp, tmp) + Dir_test('X:foo', foo, wp) + Dir_test('X:foo/bar', foo_bar, foo) + Dir_test('X:/foo', 'X:/foo', 'X:/') + Dir_test('X:/foo/bar', 'X:/foo/bar/', 'X:/foo/') + Dir_test('X:..', tmp, parent_tmp) + Dir_test('X:foo/..', wp, tmp) + Dir_test('X:../foo', tmp_foo, tmp) + Dir_test('X:.', wp, tmp) + Dir_test('X:./.', wp, tmp) + Dir_test('X:foo/./bar', foo_bar, foo) + Dir_test('#X:../foo', tmp_foo, tmp) + Dir_test('#X:/../foo', tmp_foo, tmp) + Dir_test('#X:foo/bar', foo_bar, foo) + Dir_test('#X:/foo/bar', foo_bar, foo) + Dir_test('#X:/', wp, tmp) + finally: + os.path = save_os_path + os.sep = save_os_sep + SCons.Node.FS.initialize_do_splitdrive() + SCons.Node.FS.initialize_normpath_check() + + def test_target_from_source(self): + """Test the method for generating target nodes from sources""" + fs = self.fs + + x = fs.File('x.c') + t = x.target_from_source('pre-', '-suf') + assert str(t) == 'pre-x-suf', str(t) + assert t.__class__ == SCons.Node.FS.Entry + + y = fs.File('dir/y') + t = y.target_from_source('pre-', '-suf') + assert str(t) == os.path.join('dir', 'pre-y-suf'), str(t) + assert t.__class__ == SCons.Node.FS.Entry + + z = fs.File('zz') + t = z.target_from_source('pre-', '-suf', lambda x: x[:-1]) + assert str(t) == 'pre-z-suf', str(t) + assert t.__class__ == SCons.Node.FS.Entry + + d = fs.Dir('ddd') + t = d.target_from_source('pre-', '-suf') + assert str(t) == 'pre-ddd-suf', str(t) + assert t.__class__ == SCons.Node.FS.Entry + + e = fs.Entry('eee') + t = e.target_from_source('pre-', '-suf') + assert str(t) == 'pre-eee-suf', str(t) + assert t.__class__ == SCons.Node.FS.Entry + + def test_same_name(self): + """Test that a local same-named file isn't found for a Dir lookup""" + test = self.test + fs = self.fs + + test.subdir('subdir') + test.write(['subdir', 'build'], "subdir/build\n") + + subdir = fs.Dir('subdir') + fs.chdir(subdir, change_os_dir=1) + self.fs._lookup('#build/file', subdir, SCons.Node.FS.File) + + def test_above_root(self): + """Testing looking up a path above the root directory""" + test = self.test + fs = self.fs + + d1 = fs.Dir('d1') + d2 = d1.Dir('d2') + dirs = string.split(os.path.normpath(d2.abspath), os.sep) + above_path = apply(os.path.join, ['..']*len(dirs) + ['above']) + above = d2.Dir(above_path) + + def test_rel_path(self): + """Test the rel_path() method""" + test = self.test + fs = self.fs + + d1 = fs.Dir('d1') + d1_f = d1.File('f') + d1_d2 = d1.Dir('d2') + d1_d2_f = d1_d2.File('f') + + d3 = fs.Dir('d3') + d3_f = d3.File('f') + d3_d4 = d3.Dir('d4') + d3_d4_f = d3_d4.File('f') + + cases = [ + d1, d1, '.', + d1, d1_f, 'f', + d1, d1_d2, 'd2', + d1, d1_d2_f, 'd2/f', + d1, d3, '../d3', + d1, d3_f, '../d3/f', + d1, d3_d4, '../d3/d4', + d1, d3_d4_f, '../d3/d4/f', + + d1_f, d1, '.', + d1_f, d1_f, 'f', + d1_f, d1_d2, 'd2', + d1_f, d1_d2_f, 'd2/f', + d1_f, d3, '../d3', + d1_f, d3_f, '../d3/f', + d1_f, d3_d4, '../d3/d4', + d1_f, d3_d4_f, '../d3/d4/f', + + d1_d2, d1, '..', + d1_d2, d1_f, '../f', + d1_d2, d1_d2, '.', + d1_d2, d1_d2_f, 'f', + d1_d2, d3, '../../d3', + d1_d2, d3_f, '../../d3/f', + d1_d2, d3_d4, '../../d3/d4', + d1_d2, d3_d4_f, '../../d3/d4/f', + + d1_d2_f, d1, '..', + d1_d2_f, d1_f, '../f', + d1_d2_f, d1_d2, '.', + d1_d2_f, d1_d2_f, 'f', + d1_d2_f, d3, '../../d3', + d1_d2_f, d3_f, '../../d3/f', + d1_d2_f, d3_d4, '../../d3/d4', + d1_d2_f, d3_d4_f, '../../d3/d4/f', + ] + + if sys.platform in ('win32',): + x_d1 = fs.Dir(r'X:\d1') + x_d1_d2 = x_d1.Dir('d2') + y_d1 = fs.Dir(r'Y:\d1') + y_d1_d2 = y_d1.Dir('d2') + y_d2 = fs.Dir(r'Y:\d2') + + win32_cases = [ + x_d1, x_d1, '.', + x_d1, x_d1_d2, 'd2', + x_d1, y_d1, r'Y:\d1', + x_d1, y_d1_d2, r'Y:\d1\d2', + x_d1, y_d2, r'Y:\d2', + ] + + cases.extend(win32_cases) + + failed = 0 + while cases: + dir, other, expect = cases[:3] + expect = os.path.normpath(expect) + del cases[:3] + result = dir.rel_path(other) + if result != expect: + if failed == 0: print + fmt = " dir_path(%(dir)s, %(other)s) => '%(result)s' did not match '%(expect)s'" + print fmt % locals() + failed = failed + 1 + assert failed == 0, "%d rel_path() cases failed" % failed + + def test_proxy(self): + """Test a Node.FS object wrapped in a proxy instance""" + f1 = self.fs.File('fff') + class Proxy: + # Simplest possibly Proxy class that works for our test, + # this is stripped down from SCons.Util.Proxy. + def __init__(self, subject): + self.__subject = subject + def __getattr__(self, name): + return getattr(self.__subject, name) + p = Proxy(f1) + f2 = self.fs.Entry(p) + assert f1 is f2, (f1, f2) + + + +class DirTestCase(_tempdirTestCase): + + def test__morph(self): + """Test handling of actions when morphing an Entry into a Dir""" + test = self.test + e = self.fs.Entry('eee') + x = e.get_executor() + x.add_pre_action('pre') + x.add_post_action('post') + e.must_be_same(SCons.Node.FS.Dir) + a = x.get_action_list() + assert a[0] == 'pre', a + assert a[2] == 'post', a + + def test_subclass(self): + """Test looking up subclass of Dir nodes""" + class DirSubclass(SCons.Node.FS.Dir): + pass + sd = self.fs._lookup('special_dir', None, DirSubclass, create=1) + sd.must_be_same(SCons.Node.FS.Dir) + + def test_get_env_scanner(self): + """Test the Dir.get_env_scanner() method + """ + import SCons.Defaults + d = self.fs.Dir('ddd') + s = d.get_env_scanner(Environment()) + assert s is SCons.Defaults.DirEntryScanner, s + + def test_get_target_scanner(self): + """Test the Dir.get_target_scanner() method + """ + import SCons.Defaults + d = self.fs.Dir('ddd') + s = d.get_target_scanner() + assert s is SCons.Defaults.DirEntryScanner, s + + def test_scan(self): + """Test scanning a directory for in-memory entries + """ + fs = self.fs + + dir = fs.Dir('ddd') + fs.File(os.path.join('ddd', 'f1')) + fs.File(os.path.join('ddd', 'f2')) + fs.File(os.path.join('ddd', 'f3')) + fs.Dir(os.path.join('ddd', 'd1')) + fs.Dir(os.path.join('ddd', 'd1', 'f4')) + fs.Dir(os.path.join('ddd', 'd1', 'f5')) + dir.scan() + kids = map(lambda x: x.path, dir.children(None)) + kids.sort() + assert kids == [os.path.join('ddd', 'd1'), + os.path.join('ddd', 'f1'), + os.path.join('ddd', 'f2'), + os.path.join('ddd', 'f3')], kids + + def test_get_contents(self): + """Test getting the contents for a directory. + """ + test = self.test + + test.subdir('d') + test.write(['d', 'g'], "67890\n") + test.write(['d', 'f'], "12345\n") + test.subdir(['d','sub']) + test.write(['d', 'sub','h'], "abcdef\n") + test.subdir(['d','empty']) + + d = self.fs.Dir('d') + g = self.fs.File(os.path.join('d', 'g')) + f = self.fs.File(os.path.join('d', 'f')) + h = self.fs.File(os.path.join('d', 'sub', 'h')) + e = self.fs.Dir(os.path.join('d', 'empty')) + s = self.fs.Dir(os.path.join('d', 'sub')) + + #TODO(1.5) files = d.get_contents().split('\n') + files = string.split(d.get_contents(), '\n') + + assert e.get_contents() == '', e.get_contents() + assert e.get_text_contents() == '', e.get_text_contents() + assert e.get_csig()+" empty" == files[0], files + assert f.get_csig()+" f" == files[1], files + assert g.get_csig()+" g" == files[2], files + assert s.get_csig()+" sub" == files[3], files + + def test_implicit_re_scans(self): + """Test that adding entries causes a directory to be re-scanned + """ + + fs = self.fs + + dir = fs.Dir('ddd') + + fs.File(os.path.join('ddd', 'f1')) + dir.scan() + kids = map(lambda x: x.path, dir.children()) + kids.sort() + assert kids == [os.path.join('ddd', 'f1')], kids + + fs.File(os.path.join('ddd', 'f2')) + dir.scan() + kids = map(lambda x: x.path, dir.children()) + kids.sort() + assert kids == [os.path.join('ddd', 'f1'), + os.path.join('ddd', 'f2')], kids + + def test_entry_exists_on_disk(self): + """Test the Dir.entry_exists_on_disk() method + """ + test = self.test + + does_not_exist = self.fs.Dir('does_not_exist') + assert not does_not_exist.entry_exists_on_disk('foo') + + test.subdir('d') + test.write(['d', 'exists'], "d/exists\n") + test.write(['d', 'Case-Insensitive'], "d/Case-Insensitive\n") + + d = self.fs.Dir('d') + assert d.entry_exists_on_disk('exists') + assert not d.entry_exists_on_disk('does_not_exist') + + if os.path.normcase("TeSt") != os.path.normpath("TeSt") or sys.platform == "cygwin": + assert d.entry_exists_on_disk('case-insensitive') + + def test_srcdir_list(self): + """Test the Dir.srcdir_list() method + """ + src = self.fs.Dir('src') + bld = self.fs.Dir('bld') + sub1 = bld.Dir('sub') + sub2 = sub1.Dir('sub') + sub3 = sub2.Dir('sub') + self.fs.VariantDir(bld, src, duplicate=0) + self.fs.VariantDir(sub2, src, duplicate=0) + + def check(result, expect): + result = map(str, result) + expect = map(os.path.normpath, expect) + assert result == expect, result + + s = src.srcdir_list() + check(s, []) + + s = bld.srcdir_list() + check(s, ['src']) + + s = sub1.srcdir_list() + check(s, ['src/sub']) + + s = sub2.srcdir_list() + check(s, ['src', 'src/sub/sub']) + + s = sub3.srcdir_list() + check(s, ['src/sub', 'src/sub/sub/sub']) + + self.fs.VariantDir('src/b1/b2', 'src') + b1 = src.Dir('b1') + b1_b2 = b1.Dir('b2') + b1_b2_b1 = b1_b2.Dir('b1') + b1_b2_b1_b2 = b1_b2_b1.Dir('b2') + b1_b2_b1_b2_sub = b1_b2_b1_b2.Dir('sub') + + s = b1.srcdir_list() + check(s, []) + + s = b1_b2.srcdir_list() + check(s, ['src']) + + s = b1_b2_b1.srcdir_list() + check(s, ['src/b1']) + + s = b1_b2_b1_b2.srcdir_list() + check(s, ['src/b1/b2']) + + s = b1_b2_b1_b2_sub.srcdir_list() + check(s, ['src/b1/b2/sub']) + + def test_srcdir_duplicate(self): + """Test the Dir.srcdir_duplicate() method + """ + test = self.test + + test.subdir('src0') + test.write(['src0', 'exists'], "src0/exists\n") + + bld0 = self.fs.Dir('bld0') + src0 = self.fs.Dir('src0') + self.fs.VariantDir(bld0, src0, duplicate=0) + + n = bld0.srcdir_duplicate('does_not_exist') + assert n is None, n + assert not os.path.exists(test.workpath('bld0', 'does_not_exist')) + + n = bld0.srcdir_duplicate('exists') + assert str(n) == os.path.normpath('src0/exists'), str(n) + assert not os.path.exists(test.workpath('bld0', 'exists')) + + test.subdir('src1') + test.write(['src1', 'exists'], "src0/exists\n") + + bld1 = self.fs.Dir('bld1') + src1 = self.fs.Dir('src1') + self.fs.VariantDir(bld1, src1, duplicate=1) + + n = bld1.srcdir_duplicate('does_not_exist') + assert n is None, n + assert not os.path.exists(test.workpath('bld1', 'does_not_exist')) + + n = bld1.srcdir_duplicate('exists') + assert str(n) == os.path.normpath('bld1/exists'), str(n) + assert os.path.exists(test.workpath('bld1', 'exists')) + + def test_srcdir_find_file(self): + """Test the Dir.srcdir_find_file() method + """ + test = self.test + + return_true = lambda: 1 + + test.subdir('src0') + test.write(['src0', 'on-disk-f1'], "src0/on-disk-f1\n") + test.write(['src0', 'on-disk-f2'], "src0/on-disk-f2\n") + test.write(['src0', 'on-disk-e1'], "src0/on-disk-e1\n") + test.write(['src0', 'on-disk-e2'], "src0/on-disk-e2\n") + + bld0 = self.fs.Dir('bld0') + src0 = self.fs.Dir('src0') + self.fs.VariantDir(bld0, src0, duplicate=0) + + derived_f = src0.File('derived-f') + derived_f.is_derived = return_true + exists_f = src0.File('exists-f') + exists_f.exists = return_true + + derived_e = src0.Entry('derived-e') + derived_e.is_derived = return_true + exists_e = src0.Entry('exists-e') + exists_e.exists = return_true + + def check(result, expect): + result = map(str, result) + expect = map(os.path.normpath, expect) + assert result == expect, result + + # First check from the source directory. + n = src0.srcdir_find_file('does_not_exist') + assert n == (None, None), n + + n = src0.srcdir_find_file('derived-f') + check(n, ['src0/derived-f', 'src0']) + n = src0.srcdir_find_file('exists-f') + check(n, ['src0/exists-f', 'src0']) + n = src0.srcdir_find_file('on-disk-f1') + check(n, ['src0/on-disk-f1', 'src0']) + + n = src0.srcdir_find_file('derived-e') + check(n, ['src0/derived-e', 'src0']) + n = src0.srcdir_find_file('exists-e') + check(n, ['src0/exists-e', 'src0']) + n = src0.srcdir_find_file('on-disk-e1') + check(n, ['src0/on-disk-e1', 'src0']) + + # Now check from the variant directory. + n = bld0.srcdir_find_file('does_not_exist') + assert n == (None, None), n + + n = bld0.srcdir_find_file('derived-f') + check(n, ['src0/derived-f', 'bld0']) + n = bld0.srcdir_find_file('exists-f') + check(n, ['src0/exists-f', 'bld0']) + n = bld0.srcdir_find_file('on-disk-f2') + check(n, ['src0/on-disk-f2', 'bld0']) + + n = bld0.srcdir_find_file('derived-e') + check(n, ['src0/derived-e', 'bld0']) + n = bld0.srcdir_find_file('exists-e') + check(n, ['src0/exists-e', 'bld0']) + n = bld0.srcdir_find_file('on-disk-e2') + check(n, ['src0/on-disk-e2', 'bld0']) + + test.subdir('src1') + test.write(['src1', 'on-disk-f1'], "src1/on-disk-f1\n") + test.write(['src1', 'on-disk-f2'], "src1/on-disk-f2\n") + test.write(['src1', 'on-disk-e1'], "src1/on-disk-e1\n") + test.write(['src1', 'on-disk-e2'], "src1/on-disk-e2\n") + + bld1 = self.fs.Dir('bld1') + src1 = self.fs.Dir('src1') + self.fs.VariantDir(bld1, src1, duplicate=1) + + derived_f = src1.File('derived-f') + derived_f.is_derived = return_true + exists_f = src1.File('exists-f') + exists_f.exists = return_true + + derived_e = src1.Entry('derived-e') + derived_e.is_derived = return_true + exists_e = src1.Entry('exists-e') + exists_e.exists = return_true + + # First check from the source directory. + n = src1.srcdir_find_file('does_not_exist') + assert n == (None, None), n + + n = src1.srcdir_find_file('derived-f') + check(n, ['src1/derived-f', 'src1']) + n = src1.srcdir_find_file('exists-f') + check(n, ['src1/exists-f', 'src1']) + n = src1.srcdir_find_file('on-disk-f1') + check(n, ['src1/on-disk-f1', 'src1']) + + n = src1.srcdir_find_file('derived-e') + check(n, ['src1/derived-e', 'src1']) + n = src1.srcdir_find_file('exists-e') + check(n, ['src1/exists-e', 'src1']) + n = src1.srcdir_find_file('on-disk-e1') + check(n, ['src1/on-disk-e1', 'src1']) + + # Now check from the variant directory. + n = bld1.srcdir_find_file('does_not_exist') + assert n == (None, None), n + + n = bld1.srcdir_find_file('derived-f') + check(n, ['bld1/derived-f', 'src1']) + n = bld1.srcdir_find_file('exists-f') + check(n, ['bld1/exists-f', 'src1']) + n = bld1.srcdir_find_file('on-disk-f2') + check(n, ['bld1/on-disk-f2', 'bld1']) + + n = bld1.srcdir_find_file('derived-e') + check(n, ['bld1/derived-e', 'src1']) + n = bld1.srcdir_find_file('exists-e') + check(n, ['bld1/exists-e', 'src1']) + n = bld1.srcdir_find_file('on-disk-e2') + check(n, ['bld1/on-disk-e2', 'bld1']) + + def test_dir_on_disk(self): + """Test the Dir.dir_on_disk() method""" + self.test.subdir('sub', ['sub', 'exists']) + self.test.write(['sub', 'file'], "self/file\n") + sub = self.fs.Dir('sub') + + r = sub.dir_on_disk('does_not_exist') + assert not r, r + + r = sub.dir_on_disk('exists') + assert r, r + + r = sub.dir_on_disk('file') + assert not r, r + + def test_file_on_disk(self): + """Test the Dir.file_on_disk() method""" + self.test.subdir('sub', ['sub', 'dir']) + self.test.write(['sub', 'exists'], "self/exists\n") + sub = self.fs.Dir('sub') + + r = sub.file_on_disk('does_not_exist') + assert not r, r + + r = sub.file_on_disk('exists') + assert r, r + + r = sub.file_on_disk('dir') + assert not r, r + +class EntryTestCase(_tempdirTestCase): + def test_runTest(self): + """Test methods specific to the Entry sub-class. + """ + test = TestCmd(workdir='') + # FS doesn't like the cwd to be something other than its root. + os.chdir(test.workpath("")) + + fs = SCons.Node.FS.FS() + + e1 = fs.Entry('e1') + e1.rfile() + assert e1.__class__ is SCons.Node.FS.File, e1.__class__ + + test.subdir('e3d') + test.write('e3f', "e3f\n") + + e3d = fs.Entry('e3d') + e3d.get_contents() + assert e3d.__class__ is SCons.Node.FS.Dir, e3d.__class__ + + e3f = fs.Entry('e3f') + e3f.get_contents() + assert e3f.__class__ is SCons.Node.FS.File, e3f.__class__ + + e3n = fs.Entry('e3n') + e3n.get_contents() + assert e3n.__class__ is SCons.Node.FS.Entry, e3n.__class__ + + test.subdir('e4d') + test.write('e4f', "e4f\n") + + e4d = fs.Entry('e4d') + exists = e4d.exists() + assert e4d.__class__ is SCons.Node.FS.Dir, e4d.__class__ + assert exists, "e4d does not exist?" + + e4f = fs.Entry('e4f') + exists = e4f.exists() + assert e4f.__class__ is SCons.Node.FS.File, e4f.__class__ + assert exists, "e4f does not exist?" + + e4n = fs.Entry('e4n') + exists = e4n.exists() + assert e4n.__class__ is SCons.Node.FS.File, e4n.__class__ + assert not exists, "e4n exists?" + + class MyCalc: + def __init__(self, val): + self.max_drift = 0 + class M: + def __init__(self, val): + self.val = val + def collect(self, args): + return reduce(lambda x, y: x+y, args) + def signature(self, executor): + return self.val + 222 + self.module = M(val) + + test.subdir('e5d') + test.write('e5f', "e5f\n") + + def test_Entry_Entry_lookup(self): + """Test looking up an Entry within another Entry""" + self.fs.Entry('#topdir') + self.fs.Entry('#topdir/a/b/c') + + + +class FileTestCase(_tempdirTestCase): + + def test_subclass(self): + """Test looking up subclass of File nodes""" + class FileSubclass(SCons.Node.FS.File): + pass + sd = self.fs._lookup('special_file', None, FileSubclass, create=1) + sd.must_be_same(SCons.Node.FS.File) + + def test_Dirs(self): + """Test the File.Dirs() method""" + fff = self.fs.File('subdir/fff') + # This simulates that the SConscript file that defined + # fff is in subdir/. + fff.cwd = self.fs.Dir('subdir') + d1 = self.fs.Dir('subdir/d1') + d2 = self.fs.Dir('subdir/d2') + dirs = fff.Dirs(['d1', 'd2']) + assert dirs == [d1, d2], map(str, dirs) + + def test_exists(self): + """Test the File.exists() method""" + fs = self.fs + test = self.test + + src_f1 = fs.File('src/f1') + assert not src_f1.exists(), "%s apparently exists?" % src_f1 + + test.subdir('src') + test.write(['src', 'f1'], "src/f1\n") + + assert not src_f1.exists(), "%s did not cache previous exists() value" % src_f1 + src_f1.clear() + assert src_f1.exists(), "%s apparently does not exist?" % src_f1 + + test.subdir('build') + fs.VariantDir('build', 'src') + build_f1 = fs.File('build/f1') + + assert build_f1.exists(), "%s did not realize that %s exists" % (build_f1, src_f1) + assert os.path.exists(build_f1.abspath), "%s did not get duplicated on disk" % build_f1.abspath + + test.unlink(['src', 'f1']) + src_f1.clear() # so the next exists() call will look on disk again + + assert build_f1.exists(), "%s did not cache previous exists() value" % build_f1 + build_f1.clear() + build_f1.linked = None + assert not build_f1.exists(), "%s did not realize that %s disappeared" % (build_f1, src_f1) + assert not os.path.exists(build_f1.abspath), "%s did not get removed after %s was removed" % (build_f1, src_f1) + + + +class GlobTestCase(_tempdirTestCase): + def setUp(self): + _tempdirTestCase.setUp(self) + + fs = SCons.Node.FS.FS() + self.fs = fs + + # Make entries on disk that will not have Nodes, so we can verify + # the behavior of looking for things on disk. + self.test.write('disk-bbb', "disk-bbb\n") + self.test.write('disk-aaa', "disk-aaa\n") + self.test.write('disk-ccc', "disk-ccc\n") + self.test.write('#disk-hash', "#disk-hash\n") + self.test.subdir('disk-sub') + self.test.write(['disk-sub', 'disk-ddd'], "disk-sub/disk-ddd\n") + self.test.write(['disk-sub', 'disk-eee'], "disk-sub/disk-eee\n") + self.test.write(['disk-sub', 'disk-fff'], "disk-sub/disk-fff\n") + + # Make some entries that have both Nodes and on-disk entries, + # so we can verify what we do with + self.test.write('both-aaa', "both-aaa\n") + self.test.write('both-bbb', "both-bbb\n") + self.test.write('both-ccc', "both-ccc\n") + self.test.write('#both-hash', "#both-hash\n") + self.test.subdir('both-sub1') + self.test.write(['both-sub1', 'both-ddd'], "both-sub1/both-ddd\n") + self.test.write(['both-sub1', 'both-eee'], "both-sub1/both-eee\n") + self.test.write(['both-sub1', 'both-fff'], "both-sub1/both-fff\n") + self.test.subdir('both-sub2') + self.test.write(['both-sub2', 'both-ddd'], "both-sub2/both-ddd\n") + self.test.write(['both-sub2', 'both-eee'], "both-sub2/both-eee\n") + self.test.write(['both-sub2', 'both-fff'], "both-sub2/both-fff\n") + + self.both_aaa = fs.File('both-aaa') + self.both_bbb = fs.File('both-bbb') + self.both_ccc = fs.File('both-ccc') + self._both_hash = fs.File('./#both-hash') + self.both_sub1 = fs.Dir('both-sub1') + self.both_sub1_both_ddd = self.both_sub1.File('both-ddd') + self.both_sub1_both_eee = self.both_sub1.File('both-eee') + self.both_sub1_both_fff = self.both_sub1.File('both-fff') + self.both_sub2 = fs.Dir('both-sub2') + self.both_sub2_both_ddd = self.both_sub2.File('both-ddd') + self.both_sub2_both_eee = self.both_sub2.File('both-eee') + self.both_sub2_both_fff = self.both_sub2.File('both-fff') + + # Make various Nodes (that don't have on-disk entries) so we + # can verify how we match them. + self.ggg = fs.File('ggg') + self.hhh = fs.File('hhh') + self.iii = fs.File('iii') + self._hash = fs.File('./#hash') + self.subdir1 = fs.Dir('subdir1') + self.subdir1_lll = self.subdir1.File('lll') + self.subdir1_jjj = self.subdir1.File('jjj') + self.subdir1_kkk = self.subdir1.File('kkk') + self.subdir2 = fs.Dir('subdir2') + self.subdir2_lll = self.subdir2.File('lll') + self.subdir2_kkk = self.subdir2.File('kkk') + self.subdir2_jjj = self.subdir2.File('jjj') + self.sub = fs.Dir('sub') + self.sub_dir3 = self.sub.Dir('dir3') + self.sub_dir3_kkk = self.sub_dir3.File('kkk') + self.sub_dir3_jjj = self.sub_dir3.File('jjj') + self.sub_dir3_lll = self.sub_dir3.File('lll') + + + def do_cases(self, cases, **kwargs): + + # First, execute all of the cases with string=True and verify + # that we get the expected strings returned. We do this first + # so the Glob() calls don't add Nodes to the self.fs file system + # hierarchy. + + import copy + strings_kwargs = copy.copy(kwargs) + strings_kwargs['strings'] = True + for input, string_expect, node_expect in cases: + r = apply(self.fs.Glob, (input,), strings_kwargs) + r.sort() + assert r == string_expect, "Glob(%s, strings=True) expected %s, got %s" % (input, string_expect, r) + + # Now execute all of the cases without string=True and look for + # the expected Nodes to be returned. If we don't have a list of + # actual expected Nodes, that means we're expecting a search for + # on-disk-only files to have returned some newly-created nodes. + # Verify those by running the list through str() before comparing + # them with the expected list of strings. + for input, string_expect, node_expect in cases: + r = apply(self.fs.Glob, (input,), kwargs) + if node_expect: + r.sort(lambda a,b: cmp(a.path, b.path)) + result = [] + for n in node_expect: + if type(n) == type(''): + n = self.fs.Entry(n) + result.append(n) + fmt = lambda n: "%s %s" % (repr(n), repr(str(n))) + else: + r = map(str, r) + r.sort() + result = string_expect + fmt = lambda n: n + if r != result: + import pprint + print "Glob(%s) expected:" % repr(input) + pprint.pprint(map(fmt, result)) + print "Glob(%s) got:" % repr(input) + pprint.pprint(map(fmt, r)) + self.fail() + + def test_exact_match(self): + """Test globbing for exact Node matches""" + join = os.path.join + + cases = ( + ('ggg', ['ggg'], [self.ggg]), + + ('subdir1', ['subdir1'], [self.subdir1]), + + ('subdir1/jjj', [join('subdir1', 'jjj')], [self.subdir1_jjj]), + + ('disk-aaa', ['disk-aaa'], None), + + ('disk-sub', ['disk-sub'], None), + + ('both-aaa', ['both-aaa'], []), + ) + + self.do_cases(cases) + + def test_subdir_matches(self): + """Test globbing for exact Node matches in subdirectories""" + join = os.path.join + + cases = ( + ('*/jjj', + [join('subdir1', 'jjj'), join('subdir2', 'jjj')], + [self.subdir1_jjj, self.subdir2_jjj]), + + ('*/disk-ddd', + [join('disk-sub', 'disk-ddd')], + None), + ) + + self.do_cases(cases) + + def test_asterisk1(self): + """Test globbing for simple asterisk Node matches (1)""" + cases = ( + ('h*', + ['hhh'], + [self.hhh]), + + ('*', + ['#both-hash', '#hash', + 'both-aaa', 'both-bbb', 'both-ccc', + 'both-sub1', 'both-sub2', + 'ggg', 'hhh', 'iii', + 'sub', 'subdir1', 'subdir2'], + [self._both_hash, self._hash, + self.both_aaa, self.both_bbb, self.both_ccc, 'both-hash', + self.both_sub1, self.both_sub2, + self.ggg, 'hash', self.hhh, self.iii, + self.sub, self.subdir1, self.subdir2]), + ) + + self.do_cases(cases, ondisk=False) + + def test_asterisk2(self): + """Test globbing for simple asterisk Node matches (2)""" + cases = ( + ('disk-b*', + ['disk-bbb'], + None), + + ('*', + ['#both-hash', '#disk-hash', '#hash', + 'both-aaa', 'both-bbb', 'both-ccc', + 'both-sub1', 'both-sub2', + 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub', + 'ggg', 'hhh', 'iii', + 'sub', 'subdir1', 'subdir2'], + ['./#both-hash', './#disk-hash', './#hash', + 'both-aaa', 'both-bbb', 'both-ccc', 'both-hash', + 'both-sub1', 'both-sub2', + 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub', + 'ggg', 'hash', 'hhh', 'iii', + 'sub', 'subdir1', 'subdir2']), + ) + + self.do_cases(cases) + + def test_question_mark(self): + """Test globbing for simple question-mark Node matches""" + join = os.path.join + + cases = ( + ('ii?', + ['iii'], + [self.iii]), + + ('both-sub?/both-eee', + [join('both-sub1', 'both-eee'), join('both-sub2', 'both-eee')], + [self.both_sub1_both_eee, self.both_sub2_both_eee]), + + ('subdir?/jjj', + [join('subdir1', 'jjj'), join('subdir2', 'jjj')], + [self.subdir1_jjj, self.subdir2_jjj]), + + ('disk-cc?', + ['disk-ccc'], + None), + ) + + self.do_cases(cases) + + def test_does_not_exist(self): + """Test globbing for things that don't exist""" + + cases = ( + ('does_not_exist', [], []), + ('no_subdir/*', [], []), + ('subdir?/no_file', [], []), + ) + + self.do_cases(cases) + + def test_subdir_asterisk(self): + """Test globbing for asterisk Node matches in subdirectories""" + join = os.path.join + + cases = ( + ('*/k*', + [join('subdir1', 'kkk'), join('subdir2', 'kkk')], + [self.subdir1_kkk, self.subdir2_kkk]), + + ('both-sub?/*', + [join('both-sub1', 'both-ddd'), + join('both-sub1', 'both-eee'), + join('both-sub1', 'both-fff'), + join('both-sub2', 'both-ddd'), + join('both-sub2', 'both-eee'), + join('both-sub2', 'both-fff')], + [self.both_sub1_both_ddd, + self.both_sub1_both_eee, + self.both_sub1_both_fff, + self.both_sub2_both_ddd, + self.both_sub2_both_eee, + self.both_sub2_both_fff], + ), + + ('subdir?/*', + [join('subdir1', 'jjj'), + join('subdir1', 'kkk'), + join('subdir1', 'lll'), + join('subdir2', 'jjj'), + join('subdir2', 'kkk'), + join('subdir2', 'lll')], + [self.subdir1_jjj, self.subdir1_kkk, self.subdir1_lll, + self.subdir2_jjj, self.subdir2_kkk, self.subdir2_lll]), + + ('sub/*/*', + [join('sub', 'dir3', 'jjj'), + join('sub', 'dir3', 'kkk'), + join('sub', 'dir3', 'lll')], + [self.sub_dir3_jjj, self.sub_dir3_kkk, self.sub_dir3_lll]), + + ('*/k*', + [join('subdir1', 'kkk'), join('subdir2', 'kkk')], + None), + + ('subdir?/*', + [join('subdir1', 'jjj'), + join('subdir1', 'kkk'), + join('subdir1', 'lll'), + join('subdir2', 'jjj'), + join('subdir2', 'kkk'), + join('subdir2', 'lll')], + None), + + ('sub/*/*', + [join('sub', 'dir3', 'jjj'), + join('sub', 'dir3', 'kkk'), + join('sub', 'dir3', 'lll')], + None), + ) + + self.do_cases(cases) + + def test_subdir_question(self): + """Test globbing for question-mark Node matches in subdirectories""" + join = os.path.join + + cases = ( + ('*/?kk', + [join('subdir1', 'kkk'), join('subdir2', 'kkk')], + [self.subdir1_kkk, self.subdir2_kkk]), + + ('subdir?/l?l', + [join('subdir1', 'lll'), join('subdir2', 'lll')], + [self.subdir1_lll, self.subdir2_lll]), + + ('*/disk-?ff', + [join('disk-sub', 'disk-fff')], + None), + + ('subdir?/l?l', + [join('subdir1', 'lll'), join('subdir2', 'lll')], + None), + ) + + self.do_cases(cases) + + def test_sort(self): + """Test whether globbing sorts""" + join = os.path.join + # At least sometimes this should return out-of-order items + # if Glob doesn't sort. + # It's not a very good test though since it depends on the + # order returned by glob, which might already be sorted. + g = self.fs.Glob('disk-sub/*', strings=True) + expect = [ + os.path.join('disk-sub', 'disk-ddd'), + os.path.join('disk-sub', 'disk-eee'), + os.path.join('disk-sub', 'disk-fff'), + ] + assert g == expect, str(g) + " is not sorted, but should be!" + + g = self.fs.Glob('disk-*', strings=True) + expect = [ 'disk-aaa', 'disk-bbb', 'disk-ccc', 'disk-sub' ] + assert g == expect, str(g) + " is not sorted, but should be!" + + +class RepositoryTestCase(_tempdirTestCase): + + def setUp(self): + _tempdirTestCase.setUp(self) + + self.test.subdir('rep1', 'rep2', 'rep3', 'work') + + self.rep1 = self.test.workpath('rep1') + self.rep2 = self.test.workpath('rep2') + self.rep3 = self.test.workpath('rep3') + + os.chdir(self.test.workpath('work')) + + self.fs = SCons.Node.FS.FS() + self.fs.Repository(self.rep1, self.rep2, self.rep3) + + def test_getRepositories(self): + """Test the Dir.getRepositories() method""" + self.fs.Repository('foo') + self.fs.Repository(os.path.join('foo', 'bar')) + self.fs.Repository('bar/foo') + self.fs.Repository('bar') + + expect = [ + self.rep1, + self.rep2, + self.rep3, + 'foo', + os.path.join('foo', 'bar'), + os.path.join('bar', 'foo'), + 'bar' + ] + + rep = self.fs.Dir('#').getRepositories() + r = map(lambda x, np=os.path.normpath: np(str(x)), rep) + assert r == expect, r + + def test_get_all_rdirs(self): + """Test the Dir.get_all_rdirs() method""" + self.fs.Repository('foo') + self.fs.Repository(os.path.join('foo', 'bar')) + self.fs.Repository('bar/foo') + self.fs.Repository('bar') + + expect = [ + '.', + self.rep1, + self.rep2, + self.rep3, + 'foo', + os.path.join('foo', 'bar'), + os.path.join('bar', 'foo'), + 'bar' + ] + + rep = self.fs.Dir('#').get_all_rdirs() + r = map(lambda x, np=os.path.normpath: np(str(x)), rep) + assert r == expect, r + + def test_rentry(self): + """Test the Base.entry() method""" + return_true = lambda: 1 + return_false = lambda: 0 + + d1 = self.fs.Dir('d1') + d2 = self.fs.Dir('d2') + d3 = self.fs.Dir('d3') + + e1 = self.fs.Entry('e1') + e2 = self.fs.Entry('e2') + e3 = self.fs.Entry('e3') + + f1 = self.fs.File('f1') + f2 = self.fs.File('f2') + f3 = self.fs.File('f3') + + self.test.write([self.rep1, 'd2'], "") + self.test.subdir([self.rep2, 'd3']) + self.test.write([self.rep3, 'd3'], "") + + self.test.write([self.rep1, 'e2'], "") + self.test.subdir([self.rep2, 'e3']) + self.test.write([self.rep3, 'e3'], "") + + self.test.write([self.rep1, 'f2'], "") + self.test.subdir([self.rep2, 'f3']) + self.test.write([self.rep3, 'f3'], "") + + r = d1.rentry() + assert r is d1, r + + r = d2.rentry() + assert not r is d2, r + r = str(r) + assert r == os.path.join(self.rep1, 'd2'), r + + r = d3.rentry() + assert not r is d3, r + r = str(r) + assert r == os.path.join(self.rep2, 'd3'), r + + r = e1.rentry() + assert r is e1, r + + r = e2.rentry() + assert not r is e2, r + r = str(r) + assert r == os.path.join(self.rep1, 'e2'), r + + r = e3.rentry() + assert not r is e3, r + r = str(r) + assert r == os.path.join(self.rep2, 'e3'), r + + r = f1.rentry() + assert r is f1, r + + r = f2.rentry() + assert not r is f2, r + r = str(r) + assert r == os.path.join(self.rep1, 'f2'), r + + r = f3.rentry() + assert not r is f3, r + r = str(r) + assert r == os.path.join(self.rep2, 'f3'), r + + def test_rdir(self): + """Test the Dir.rdir() method""" + return_true = lambda: 1 + return_false = lambda: 0 + + d1 = self.fs.Dir('d1') + d2 = self.fs.Dir('d2') + d3 = self.fs.Dir('d3') + + self.test.subdir([self.rep1, 'd2']) + self.test.write([self.rep2, 'd3'], "") + self.test.subdir([self.rep3, 'd3']) + + r = d1.rdir() + assert r is d1, r + + r = d2.rdir() + assert not r is d2, r + r = str(r) + assert r == os.path.join(self.rep1, 'd2'), r + + r = d3.rdir() + assert not r is d3, r + r = str(r) + assert r == os.path.join(self.rep3, 'd3'), r + + e1 = self.fs.Dir('e1') + e1.exists = return_false + e2 = self.fs.Dir('e2') + e2.exists = return_false + + # Make sure we match entries in repositories, + # regardless of whether they're derived or not. + + re1 = self.fs.Entry(os.path.join(self.rep1, 'e1')) + re1.exists = return_true + re1.is_derived = return_true + re2 = self.fs.Entry(os.path.join(self.rep2, 'e2')) + re2.exists = return_true + re2.is_derived = return_false + + r = e1.rdir() + assert r is re1, r + + r = e2.rdir() + assert r is re2, r + + def test_rfile(self): + """Test the File.rfile() method""" + return_true = lambda: 1 + return_false = lambda: 0 + + f1 = self.fs.File('f1') + f2 = self.fs.File('f2') + f3 = self.fs.File('f3') + + self.test.write([self.rep1, 'f2'], "") + self.test.subdir([self.rep2, 'f3']) + self.test.write([self.rep3, 'f3'], "") + + r = f1.rfile() + assert r is f1, r + + r = f2.rfile() + assert not r is f2, r + r = str(r) + assert r == os.path.join(self.rep1, 'f2'), r + + r = f3.rfile() + assert not r is f3, r + r = f3.rstr() + assert r == os.path.join(self.rep3, 'f3'), r + + e1 = self.fs.File('e1') + e1.exists = return_false + e2 = self.fs.File('e2') + e2.exists = return_false + + # Make sure we match entries in repositories, + # regardless of whether they're derived or not. + + re1 = self.fs.Entry(os.path.join(self.rep1, 'e1')) + re1.exists = return_true + re1.is_derived = return_true + re2 = self.fs.Entry(os.path.join(self.rep2, 'e2')) + re2.exists = return_true + re2.is_derived = return_false + + r = e1.rfile() + assert r is re1, r + + r = e2.rfile() + assert r is re2, r + + def test_Rfindalldirs(self): + """Test the Rfindalldirs() methods""" + fs = self.fs + test = self.test + + d1 = fs.Dir('d1') + d2 = fs.Dir('d2') + rep1_d1 = fs.Dir(test.workpath('rep1', 'd1')) + rep2_d1 = fs.Dir(test.workpath('rep2', 'd1')) + rep3_d1 = fs.Dir(test.workpath('rep3', 'd1')) + sub = fs.Dir('sub') + sub_d1 = sub.Dir('d1') + rep1_sub_d1 = fs.Dir(test.workpath('rep1', 'sub', 'd1')) + rep2_sub_d1 = fs.Dir(test.workpath('rep2', 'sub', 'd1')) + rep3_sub_d1 = fs.Dir(test.workpath('rep3', 'sub', 'd1')) + + r = fs.Top.Rfindalldirs((d1,)) + assert r == [d1], map(str, r) + + r = fs.Top.Rfindalldirs((d1, d2)) + assert r == [d1, d2], map(str, r) + + r = fs.Top.Rfindalldirs(('d1',)) + assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r) + + r = fs.Top.Rfindalldirs(('#d1',)) + assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r) + + r = sub.Rfindalldirs(('d1',)) + assert r == [sub_d1, rep1_sub_d1, rep2_sub_d1, rep3_sub_d1], map(str, r) + + r = sub.Rfindalldirs(('#d1',)) + assert r == [d1, rep1_d1, rep2_d1, rep3_d1], map(str, r) + + r = fs.Top.Rfindalldirs(('d1', d2)) + assert r == [d1, rep1_d1, rep2_d1, rep3_d1, d2], map(str, r) + + def test_rexists(self): + """Test the Entry.rexists() method""" + fs = self.fs + test = self.test + + test.write([self.rep1, 'f2'], "") + test.write([self.rep2, "i_exist"], "\n") + test.write(["work", "i_exist_too"], "\n") + + fs.VariantDir('build', '.') + + f = fs.File(test.workpath("work", "i_do_not_exist")) + assert not f.rexists() + + f = fs.File(test.workpath("work", "i_exist")) + assert f.rexists() + + f = fs.File(test.workpath("work", "i_exist_too")) + assert f.rexists() + + f1 = fs.File(os.path.join('build', 'f1')) + assert not f1.rexists() + + f2 = fs.File(os.path.join('build', 'f2')) + assert f2.rexists() + + def test_FAT_timestamps(self): + """Test repository timestamps on FAT file systems""" + fs = self.fs + test = self.test + + test.write(["rep2", "tstamp"], "tstamp\n") + try: + # Okay, *this* manipulation accomodates Windows FAT file systems + # that only have two-second granularity on their timestamps. + # We round down the current time to the nearest even integer + # value, subtract two to make sure the timestamp is not "now," + # and then convert it back to a float. + tstamp = float(int(time.time() / 2) * 2) - 2 + os.utime(test.workpath("rep2", "tstamp"), (tstamp - 2.0, tstamp)) + f = fs.File("tstamp") + t = f.get_timestamp() + assert t == tstamp, "expected %f, got %f" % (tstamp, t) + finally: + test.unlink(["rep2", "tstamp"]) + + def test_get_contents(self): + """Ensure get_contents() returns binary contents from Repositories""" + fs = self.fs + test = self.test + + test.write(["rep3", "contents"], "Con\x1aTents\n") + try: + c = fs.File("contents").get_contents() + assert c == "Con\x1aTents\n", "got '%s'" % c + finally: + test.unlink(["rep3", "contents"]) + + def test_get_text_contents(self): + """Ensure get_text_contents() returns text contents from + Repositories""" + fs = self.fs + test = self.test + + # Use a test string that has a file terminator in it to make + # sure we read the entire file, regardless of its contents. + try: + eval('test_string = u"Con\x1aTents\n"') + except SyntaxError: + import UserString + class FakeUnicodeString(UserString.UserString): + def encode(self, encoding): + return str(self) + test_string = FakeUnicodeString("Con\x1aTents\n") + + + # Test with ASCII. + test.write(["rep3", "contents"], test_string.encode('ascii')) + try: + c = fs.File("contents").get_text_contents() + assert test_string == c, "got %s" % repr(c) + finally: + test.unlink(["rep3", "contents"]) + + # Test with utf-8 + test.write(["rep3", "contents"], test_string.encode('utf-8')) + try: + c = fs.File("contents").get_text_contents() + assert test_string == c, "got %s" % repr(c) + finally: + test.unlink(["rep3", "contents"]) + + # Test with utf-16 + test.write(["rep3", "contents"], test_string.encode('utf-16')) + try: + c = fs.File("contents").get_text_contents() + assert test_string == c, "got %s" % repr(c) + finally: + test.unlink(["rep3", "contents"]) + + #def test_is_up_to_date(self): + + + +class find_fileTestCase(unittest.TestCase): + def runTest(self): + """Testing find_file function""" + test = TestCmd(workdir = '') + test.write('./foo', 'Some file\n') + test.write('./foo2', 'Another file\n') + test.subdir('same') + test.subdir('bar') + test.write(['bar', 'on_disk'], 'Another file\n') + test.write(['bar', 'same'], 'bar/same\n') + + fs = SCons.Node.FS.FS(test.workpath("")) + # FS doesn't like the cwd to be something other than its root. + os.chdir(test.workpath("")) + + node_derived = fs.File(test.workpath('bar/baz')) + node_derived.builder_set(1) # Any non-zero value. + node_pseudo = fs.File(test.workpath('pseudo')) + node_pseudo.set_src_builder(1) # Any non-zero value. + + paths = tuple(map(fs.Dir, ['.', 'same', './bar'])) + nodes = [SCons.Node.FS.find_file('foo', paths)] + nodes.append(SCons.Node.FS.find_file('baz', paths)) + nodes.append(SCons.Node.FS.find_file('pseudo', paths)) + nodes.append(SCons.Node.FS.find_file('same', paths)) + + file_names = map(str, nodes) + file_names = map(os.path.normpath, file_names) + expect = ['./foo', './bar/baz', './pseudo', './bar/same'] + expect = map(os.path.normpath, expect) + assert file_names == expect, file_names + + # Make sure we don't blow up if there's already a File in place + # of a directory that we'd otherwise try to search. If this + # is broken, we'll see an exception like "Tried to lookup File + # 'bar/baz' as a Dir. + SCons.Node.FS.find_file('baz/no_file_here', paths) + + import StringIO + save_sys_stdout = sys.stdout + + try: + sio = StringIO.StringIO() + sys.stdout = sio + SCons.Node.FS.find_file('foo2', paths, verbose="xyz") + expect = " xyz: looking for 'foo2' in '.' ...\n" + \ + " xyz: ... FOUND 'foo2' in '.'\n" + c = sio.getvalue() + assert c == expect, c + + sio = StringIO.StringIO() + sys.stdout = sio + SCons.Node.FS.find_file('baz2', paths, verbose=1) + expect = " find_file: looking for 'baz2' in '.' ...\n" + \ + " find_file: looking for 'baz2' in 'same' ...\n" + \ + " find_file: looking for 'baz2' in 'bar' ...\n" + c = sio.getvalue() + assert c == expect, c + + sio = StringIO.StringIO() + sys.stdout = sio + SCons.Node.FS.find_file('on_disk', paths, verbose=1) + expect = " find_file: looking for 'on_disk' in '.' ...\n" + \ + " find_file: looking for 'on_disk' in 'same' ...\n" + \ + " find_file: looking for 'on_disk' in 'bar' ...\n" + \ + " find_file: ... FOUND 'on_disk' in 'bar'\n" + c = sio.getvalue() + assert c == expect, c + finally: + sys.stdout = save_sys_stdout + +class StringDirTestCase(unittest.TestCase): + def runTest(self): + """Test using a string as the second argument of + File() and Dir()""" + + test = TestCmd(workdir = '') + test.subdir('sub') + fs = SCons.Node.FS.FS(test.workpath('')) + + d = fs.Dir('sub', '.') + assert str(d) == 'sub', str(d) + assert d.exists() + f = fs.File('file', 'sub') + assert str(f) == os.path.join('sub', 'file') + assert not f.exists() + +class stored_infoTestCase(unittest.TestCase): + def runTest(self): + """Test how we store build information""" + test = TestCmd(workdir = '') + test.subdir('sub') + fs = SCons.Node.FS.FS(test.workpath('')) + + d = fs.Dir('sub') + f = fs.File('file1', d) + bi = f.get_stored_info() + assert hasattr(bi, 'ninfo') + + class MySConsign: + class Null: + def __init__(self): + self.xyzzy = 7 + def get_entry(self, name): + return self.Null() + + f = fs.File('file2', d) + f.dir.sconsign = MySConsign + bi = f.get_stored_info() + assert bi.xyzzy == 7, bi + +class has_src_builderTestCase(unittest.TestCase): + def runTest(self): + """Test the has_src_builder() method""" + test = TestCmd(workdir = '') + fs = SCons.Node.FS.FS(test.workpath('')) + os.chdir(test.workpath('')) + test.subdir('sub1') + test.subdir('sub2', ['sub2', 'SCCS'], ['sub2', 'RCS']) + + sub1 = fs.Dir('sub1', '.') + f1 = fs.File('f1', sub1) + f2 = fs.File('f2', sub1) + f3 = fs.File('f3', sub1) + sub2 = fs.Dir('sub2', '.') + f4 = fs.File('f4', sub2) + f5 = fs.File('f5', sub2) + f6 = fs.File('f6', sub2) + f7 = fs.File('f7', sub2) + f8 = fs.File('f8', sub2) + + h = f1.has_src_builder() + assert not h, h + h = f1.has_builder() + assert not h, h + + b1 = Builder(fs.File) + sub1.set_src_builder(b1) + + test.write(['sub1', 'f2'], "sub1/f2\n") + h = f1.has_src_builder() # cached from previous call + assert not h, h + h = f1.has_builder() # cached from previous call + assert not h, h + h = f2.has_src_builder() + assert not h, h + h = f2.has_builder() + assert not h, h + h = f3.has_src_builder() + assert h, h + h = f3.has_builder() + assert h, h + assert f3.builder is b1, f3.builder + + f7.set_src_builder(b1) + f8.builder_set(b1) + + test.write(['sub2', 'SCCS', 's.f5'], "sub2/SCCS/s.f5\n") + test.write(['sub2', 'RCS', 'f6,v'], "sub2/RCS/f6,v\n") + h = f4.has_src_builder() + assert not h, h + h = f4.has_builder() + assert not h, h + h = f5.has_src_builder() + assert h, h + h = f5.has_builder() + assert h, h + h = f6.has_src_builder() + assert h, h + h = f6.has_builder() + assert h, h + h = f7.has_src_builder() + assert h, h + h = f7.has_builder() + assert h, h + h = f8.has_src_builder() + assert not h, h + h = f8.has_builder() + assert h, h + +class prepareTestCase(unittest.TestCase): + def runTest(self): + """Test the prepare() method""" + + class MyFile(SCons.Node.FS.File): + def _createDir(self, update=None): + raise SCons.Errors.StopError + def exists(self): + return None + + fs = SCons.Node.FS.FS() + file = MyFile('foo', fs.Dir('.'), fs) + + exc_caught = 0 + try: + file.prepare() + except SCons.Errors.StopError: + exc_caught = 1 + assert exc_caught, "Should have caught a StopError." + + class MkdirAction(Action): + def __init__(self, dir_made): + self.dir_made = dir_made + def __call__(self, target, source, env, executor=None): + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + self.dir_made.extend(target) + + dir_made = [] + new_dir = fs.Dir("new_dir") + new_dir.builder = Builder(fs.Dir, action=MkdirAction(dir_made)) + new_dir.reset_executor() + xyz = fs.File(os.path.join("new_dir", "xyz")) + + xyz.set_state(SCons.Node.up_to_date) + xyz.prepare() + assert dir_made == [], dir_made + + xyz.set_state(0) + xyz.prepare() + assert dir_made[0].path == "new_dir", dir_made[0] + + dir = fs.Dir("dir") + dir.prepare() + + + +class SConstruct_dirTestCase(unittest.TestCase): + def runTest(self): + """Test setting the SConstruct directory""" + + fs = SCons.Node.FS.FS() + fs.set_SConstruct_dir(fs.Dir('xxx')) + assert fs.SConstruct_dir.path == 'xxx' + + + +class CacheDirTestCase(unittest.TestCase): + + def test_get_cachedir_csig(self): + fs = SCons.Node.FS.FS() + + f9 = fs.File('f9') + r = f9.get_cachedir_csig() + assert r == 'd41d8cd98f00b204e9800998ecf8427e', r + + + +class clearTestCase(unittest.TestCase): + def runTest(self): + """Test clearing FS nodes of cached data.""" + fs = SCons.Node.FS.FS() + test = TestCmd(workdir='') + + e = fs.Entry('e') + assert not e.exists() + assert not e.rexists() + assert str(e) == 'e', str(d) + e.clear() + assert not e.exists() + assert not e.rexists() + assert str(e) == 'e', str(d) + + d = fs.Dir(test.workpath('d')) + test.subdir('d') + assert d.exists() + assert d.rexists() + assert str(d) == test.workpath('d'), str(d) + fs.rename(test.workpath('d'), test.workpath('gone')) + # Verify caching is active + assert d.exists(), 'caching not active' + assert d.rexists() + assert str(d) == test.workpath('d'), str(d) + # Now verify clear() resets the cache + d.clear() + assert not d.exists() + assert not d.rexists() + assert str(d) == test.workpath('d'), str(d) + + f = fs.File(test.workpath('f')) + test.write(test.workpath('f'), 'file f') + assert f.exists() + assert f.rexists() + assert str(f) == test.workpath('f'), str(f) + # Verify caching is active + test.unlink(test.workpath('f')) + assert f.exists() + assert f.rexists() + assert str(f) == test.workpath('f'), str(f) + # Now verify clear() resets the cache + f.clear() + assert not f.exists() + assert not f.rexists() + assert str(f) == test.workpath('f'), str(f) + + + +class disambiguateTestCase(unittest.TestCase): + def runTest(self): + """Test calling the disambiguate() method.""" + test = TestCmd(workdir='') + + fs = SCons.Node.FS.FS() + + ddd = fs.Dir('ddd') + d = ddd.disambiguate() + assert d is ddd, d + + fff = fs.File('fff') + f = fff.disambiguate() + assert f is fff, f + + test.subdir('edir') + test.write('efile', "efile\n") + + edir = fs.Entry(test.workpath('edir')) + d = edir.disambiguate() + assert d.__class__ is ddd.__class__, d.__class__ + + efile = fs.Entry(test.workpath('efile')) + f = efile.disambiguate() + assert f.__class__ is fff.__class__, f.__class__ + + test.subdir('build') + test.subdir(['build', 'bdir']) + test.write(['build', 'bfile'], "build/bfile\n") + + test.subdir('src') + test.write(['src', 'bdir'], "src/bdir\n") + test.subdir(['src', 'bfile']) + + test.subdir(['src', 'edir']) + test.write(['src', 'efile'], "src/efile\n") + + fs.VariantDir(test.workpath('build'), test.workpath('src')) + + build_bdir = fs.Entry(test.workpath('build/bdir')) + d = build_bdir.disambiguate() + assert d is build_bdir, d + assert d.__class__ is ddd.__class__, d.__class__ + + build_bfile = fs.Entry(test.workpath('build/bfile')) + f = build_bfile.disambiguate() + assert f is build_bfile, f + assert f.__class__ is fff.__class__, f.__class__ + + build_edir = fs.Entry(test.workpath('build/edir')) + d = build_edir.disambiguate() + assert d.__class__ is ddd.__class__, d.__class__ + + build_efile = fs.Entry(test.workpath('build/efile')) + f = build_efile.disambiguate() + assert f.__class__ is fff.__class__, f.__class__ + + build_nonexistant = fs.Entry(test.workpath('build/nonexistant')) + f = build_nonexistant.disambiguate() + assert f.__class__ is fff.__class__, f.__class__ + +class postprocessTestCase(unittest.TestCase): + def runTest(self): + """Test calling the postprocess() method.""" + fs = SCons.Node.FS.FS() + + e = fs.Entry('e') + e.postprocess() + + d = fs.Dir('d') + d.postprocess() + + f = fs.File('f') + f.postprocess() + + + +class SpecialAttrTestCase(unittest.TestCase): + def runTest(self): + """Test special attributes of file nodes.""" + test=TestCmd(workdir='') + fs = SCons.Node.FS.FS(test.workpath('work')) + + f = fs.Entry('foo/bar/baz.blat').get_subst_proxy() + + s = str(f.dir) + assert s == os.path.normpath('foo/bar'), s + assert f.dir.is_literal(), f.dir + for_sig = f.dir.for_signature() + assert for_sig == 'bar', for_sig + + s = str(f.file) + assert s == 'baz.blat', s + assert f.file.is_literal(), f.file + for_sig = f.file.for_signature() + assert for_sig == 'baz.blat_file', for_sig + + s = str(f.base) + assert s == os.path.normpath('foo/bar/baz'), s + assert f.base.is_literal(), f.base + for_sig = f.base.for_signature() + assert for_sig == 'baz.blat_base', for_sig + + s = str(f.filebase) + assert s == 'baz', s + assert f.filebase.is_literal(), f.filebase + for_sig = f.filebase.for_signature() + assert for_sig == 'baz.blat_filebase', for_sig + + s = str(f.suffix) + assert s == '.blat', s + assert f.suffix.is_literal(), f.suffix + for_sig = f.suffix.for_signature() + assert for_sig == 'baz.blat_suffix', for_sig + + s = str(f.abspath) + assert s == test.workpath('work', 'foo', 'bar', 'baz.blat'), s + assert f.abspath.is_literal(), f.abspath + for_sig = f.abspath.for_signature() + assert for_sig == 'baz.blat_abspath', for_sig + + s = str(f.posix) + assert s == 'foo/bar/baz.blat', s + assert f.posix.is_literal(), f.posix + if f.posix != f: + for_sig = f.posix.for_signature() + assert for_sig == 'baz.blat_posix', for_sig + + s = str(f.windows) + assert s == 'foo\\bar\\baz.blat', repr(s) + assert f.windows.is_literal(), f.windows + if f.windows != f: + for_sig = f.windows.for_signature() + assert for_sig == 'baz.blat_windows', for_sig + + # Deprecated synonym for the .windows suffix. + s = str(f.win32) + assert s == 'foo\\bar\\baz.blat', repr(s) + assert f.win32.is_literal(), f.win32 + if f.win32 != f: + for_sig = f.win32.for_signature() + assert for_sig == 'baz.blat_windows', for_sig + + # And now, combinations!!! + s = str(f.srcpath.base) + assert s == os.path.normpath('foo/bar/baz'), s + s = str(f.srcpath.dir) + assert s == str(f.srcdir), s + s = str(f.srcpath.posix) + assert s == 'foo/bar/baz.blat', s + s = str(f.srcpath.windows) + assert s == 'foo\\bar\\baz.blat', s + s = str(f.srcpath.win32) + assert s == 'foo\\bar\\baz.blat', s + + # Test what happens with VariantDir() + fs.VariantDir('foo', 'baz') + + s = str(f.srcpath) + assert s == os.path.normpath('baz/bar/baz.blat'), s + assert f.srcpath.is_literal(), f.srcpath + g = f.srcpath.get() + assert isinstance(g, SCons.Node.FS.File), g.__class__ + + s = str(f.srcdir) + assert s == os.path.normpath('baz/bar'), s + assert f.srcdir.is_literal(), f.srcdir + g = f.srcdir.get() + assert isinstance(g, SCons.Node.FS.Dir), g.__class__ + + # And now what happens with VariantDir() + Repository() + fs.Repository(test.workpath('repository')) + + f = fs.Entry('foo/sub/file.suffix').get_subst_proxy() + test.subdir('repository', + ['repository', 'baz'], + ['repository', 'baz', 'sub']) + + rd = test.workpath('repository', 'baz', 'sub') + rf = test.workpath('repository', 'baz', 'sub', 'file.suffix') + test.write(rf, "\n") + + s = str(f.srcpath) + assert s == os.path.normpath('baz/sub/file.suffix'), s + assert f.srcpath.is_literal(), f.srcpath + g = f.srcpath.get() + # Gets disambiguated to SCons.Node.FS.File by get_subst_proxy(). + assert isinstance(g, SCons.Node.FS.File), g.__class__ + + s = str(f.srcdir) + assert s == os.path.normpath('baz/sub'), s + assert f.srcdir.is_literal(), f.srcdir + g = f.srcdir.get() + assert isinstance(g, SCons.Node.FS.Dir), g.__class__ + + s = str(f.rsrcpath) + assert s == rf, s + assert f.rsrcpath.is_literal(), f.rsrcpath + g = f.rsrcpath.get() + assert isinstance(g, SCons.Node.FS.File), g.__class__ + + s = str(f.rsrcdir) + assert s == rd, s + assert f.rsrcdir.is_literal(), f.rsrcdir + g = f.rsrcdir.get() + assert isinstance(g, SCons.Node.FS.Dir), g.__class__ + + # Check that attempts to access non-existent attributes of the + # subst proxy generate the right exceptions and messages. + caught = None + try: + fs.Dir('ddd').get_subst_proxy().no_such_attr + except AttributeError, e: + assert str(e) == "Dir instance 'ddd' has no attribute 'no_such_attr'", e + caught = 1 + assert caught, "did not catch expected AttributeError" + + caught = None + try: + fs.Entry('eee').get_subst_proxy().no_such_attr + except AttributeError, e: + # Gets disambiguated to File instance by get_subst_proxy(). + assert str(e) == "File instance 'eee' has no attribute 'no_such_attr'", e + caught = 1 + assert caught, "did not catch expected AttributeError" + + caught = None + try: + fs.File('fff').get_subst_proxy().no_such_attr + except AttributeError, e: + assert str(e) == "File instance 'fff' has no attribute 'no_such_attr'", e + caught = 1 + assert caught, "did not catch expected AttributeError" + + + +class SaveStringsTestCase(unittest.TestCase): + def runTest(self): + """Test caching string values of nodes.""" + test=TestCmd(workdir='') + + def setup(fs): + fs.Dir('src') + fs.Dir('d0') + fs.Dir('d1') + + d0_f = fs.File('d0/f') + d1_f = fs.File('d1/f') + d0_b = fs.File('d0/b') + d1_b = fs.File('d1/b') + d1_f.duplicate = 1 + d1_b.duplicate = 1 + d0_b.builder = 1 + d1_b.builder = 1 + + return [d0_f, d1_f, d0_b, d1_b] + + def modify(nodes): + d0_f, d1_f, d0_b, d1_b = nodes + d1_f.duplicate = 0 + d1_b.duplicate = 0 + d0_b.builder = 0 + d1_b.builder = 0 + + fs1 = SCons.Node.FS.FS(test.workpath('fs1')) + nodes = setup(fs1) + fs1.VariantDir('d0', 'src', duplicate=0) + fs1.VariantDir('d1', 'src', duplicate=1) + + s = map(str, nodes) + expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']) + assert s == expect, s + + modify(nodes) + + s = map(str, nodes) + expect = map(os.path.normpath, ['src/f', 'src/f', 'd0/b', 'd1/b']) + assert s == expect, s + + SCons.Node.FS.save_strings(1) + fs2 = SCons.Node.FS.FS(test.workpath('fs2')) + nodes = setup(fs2) + fs2.VariantDir('d0', 'src', duplicate=0) + fs2.VariantDir('d1', 'src', duplicate=1) + + s = map(str, nodes) + expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']) + assert s == expect, s + + modify(nodes) + + s = map(str, nodes) + expect = map(os.path.normpath, ['src/f', 'd1/f', 'd0/b', 'd1/b']) + assert s == expect, 'node str() not cached: %s'%s + + +class AbsolutePathTestCase(unittest.TestCase): + def test_root_lookup_equivalence(self): + """Test looking up /fff vs. fff in the / directory""" + test=TestCmd(workdir='') + + fs = SCons.Node.FS.FS('/') + + save_cwd = os.getcwd() + try: + os.chdir('/') + fff1 = fs.File('fff') + fff2 = fs.File('/fff') + assert fff1 is fff2, "fff and /fff returned different Nodes!" + finally: + os.chdir(save_cwd) + + + +if __name__ == "__main__": + suite = unittest.TestSuite() + suite.addTest(VariantDirTestCase()) + suite.addTest(find_fileTestCase()) + suite.addTest(StringDirTestCase()) + suite.addTest(stored_infoTestCase()) + suite.addTest(has_src_builderTestCase()) + suite.addTest(prepareTestCase()) + suite.addTest(SConstruct_dirTestCase()) + suite.addTest(clearTestCase()) + suite.addTest(disambiguateTestCase()) + suite.addTest(postprocessTestCase()) + suite.addTest(SpecialAttrTestCase()) + suite.addTest(SaveStringsTestCase()) + tclasses = [ + AbsolutePathTestCase, + BaseTestCase, + CacheDirTestCase, + DirTestCase, + DirBuildInfoTestCase, + DirNodeInfoTestCase, + EntryTestCase, + FileTestCase, + FileBuildInfoTestCase, + FileNodeInfoTestCase, + FSTestCase, + GlobTestCase, + RepositoryTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/NodeTests.py b/src/engine/SCons/Node/NodeTests.py new file mode 100644 index 0000000..7920dcb --- /dev/null +++ b/src/engine/SCons/Node/NodeTests.py @@ -0,0 +1,1317 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/NodeTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import re +import string +import sys +import types +import unittest +import UserList + +import SCons.Errors +import SCons.Node +import SCons.Util + + + +built_it = None +built_target = None +built_source = None +cycle_detected = None +built_order = 0 + +def _actionAppend(a1, a2): + all = [] + for curr_a in [a1, a2]: + if isinstance(curr_a, MyAction): + all.append(curr_a) + elif isinstance(curr_a, MyListAction): + all.extend(curr_a.list) + elif type(curr_a) == type([1,2]): + all.extend(curr_a) + else: + raise 'Cannot Combine Actions' + return MyListAction(all) + +class MyActionBase: + def __add__(self, other): + return _actionAppend(self, other) + + def __radd__(self, other): + return _actionAppend(other, self) + +class MyAction(MyActionBase): + def __init__(self): + self.order = 0 + + def __call__(self, target, source, env, executor=None): + global built_it, built_target, built_source, built_args, built_order + if executor: + target = executor.get_all_targets() + source = executor.get_all_sources() + built_it = 1 + built_target = target + built_source = source + built_args = env + built_order = built_order + 1 + self.order = built_order + return 0 + + def get_implicit_deps(self, target, source, env): + return [] + +class MyExecutor: + def __init__(self, env=None, targets=[], sources=[]): + self.env = env + self.targets = targets + self.sources = sources + def get_build_env(self): + return self.env + def get_build_scanner_path(self, scanner): + return 'executor would call %s' % scanner + def cleanup(self): + self.cleaned_up = 1 + def scan_targets(self, scanner): + if not scanner: + return + d = scanner(self.targets) + for t in self.targets: + t.implicit.extend(d) + def scan_sources(self, scanner): + if not scanner: + return + d = scanner(self.sources) + for t in self.targets: + t.implicit.extend(d) + +class MyListAction(MyActionBase): + def __init__(self, list): + self.list = list + def __call__(self, target, source, env): + for A in self.list: + A(target, source, env) + +class Environment: + def __init__(self, **kw): + self._dict = {} + self._dict.update(kw) + def __getitem__(self, key): + return self._dict[key] + def Dictionary(self, *args): + return {} + def Override(self, overrides): + d = self._dict.copy() + d.update(overrides) + return apply(Environment, (), d) + def _update(self, dict): + self._dict.update(dict) + def get_factory(self, factory): + return factory or MyNode + def get_scanner(self, scanner_key): + return self._dict['SCANNERS'][0] + +class Builder: + def __init__(self, env=None, is_explicit=1): + if env is None: env = Environment() + self.env = env + self.overrides = {} + self.action = MyAction() + self.source_factory = MyNode + self.is_explicit = is_explicit + self.target_scanner = None + self.source_scanner = None + def targets(self, t): + return [t] + def get_actions(self): + return [self.action] + def get_contents(self, target, source, env): + return 7 + +class NoneBuilder(Builder): + def execute(self, target, source, env): + Builder.execute(self, target, source, env) + return None + +class ListBuilder(Builder): + def __init__(self, *nodes): + Builder.__init__(self) + self.nodes = nodes + def execute(self, target, source, env): + if hasattr(self, 'status'): + return self.status + for n in self.nodes: + n.prepare() + target = self.nodes[0] + self.status = Builder.execute(self, target, source, env) + +class FailBuilder: + def execute(self, target, source, env): + return 1 + +class ExceptBuilder: + def execute(self, target, source, env): + raise SCons.Errors.BuildError + +class ExceptBuilder2: + def execute(self, target, source, env): + raise "foo" + +class Scanner: + called = None + def __call__(self, node): + self.called = 1 + return node.found_includes + def path(self, env, dir, target=None, source=None): + return () + def select(self, node): + return self + def recurse_nodes(self, nodes): + return nodes + +class MyNode(SCons.Node.Node): + """The base Node class contains a number of do-nothing methods that + we expect to be overridden by real, functional Node subclasses. So + simulate a real, functional Node subclass. + """ + def __init__(self, name): + SCons.Node.Node.__init__(self) + self.name = name + self.found_includes = [] + def __str__(self): + return self.name + def get_found_includes(self, env, scanner, target): + return scanner(self) + +class Calculator: + def __init__(self, val): + self.max_drift = 0 + class M: + def __init__(self, val): + self.val = val + def signature(self, args): + return self.val + def collect(self, args): + return reduce(lambda x, y: x+y, args, self.val) + self.module = M(val) + + + +class NodeInfoBaseTestCase(unittest.TestCase): + + def test_merge(self): + """Test merging NodeInfoBase attributes""" + ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + ni2 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + + ni1.a1 = 1 + ni1.a2 = 2 + + ni2.a2 = 222 + ni2.a3 = 333 + + ni1.merge(ni2) + expect = {'a1':1, 'a2':222, 'a3':333, '_version_id':1} + assert ni1.__dict__ == expect, ni1.__dict__ + + def test_update(self): + """Test the update() method""" + ni = SCons.Node.NodeInfoBase(SCons.Node.Node()) + ni.update(SCons.Node.Node()) + + def test_format(self): + """Test the NodeInfoBase.format() method""" + ni1 = SCons.Node.NodeInfoBase(SCons.Node.Node()) + ni1.xxx = 'x' + ni1.yyy = 'y' + ni1.zzz = 'z' + + f = ni1.format() + assert f == ['1', 'x', 'y', 'z'], f + + ni1.field_list = ['xxx', 'zzz', 'aaa'] + + f = ni1.format() + assert f == ['x', 'z', 'None'], f + + + +class BuildInfoBaseTestCase(unittest.TestCase): + + def test___init__(self): + """Test BuildInfoBase initialization""" + n = SCons.Node.Node() + bi = SCons.Node.BuildInfoBase(n) + assert bi + + def test_merge(self): + """Test merging BuildInfoBase attributes""" + n1 = SCons.Node.Node() + bi1 = SCons.Node.BuildInfoBase(n1) + n2 = SCons.Node.Node() + bi2 = SCons.Node.BuildInfoBase(n2) + + bi1.a1 = 1 + bi1.a2 = 2 + + bi2.a2 = 222 + bi2.a3 = 333 + + bi1.merge(bi2) + assert bi1.a1 == 1, bi1.a1 + assert bi1.a2 == 222, bi1.a2 + assert bi1.a3 == 333, bi1.a3 + + +class NodeTestCase(unittest.TestCase): + + def test_build(self): + """Test building a node + """ + global built_it, built_order + + # Make sure it doesn't blow up if no builder is set. + node = MyNode("www") + node.build() + assert built_it is None + node.build(extra_kw_argument = 1) + assert built_it is None + + node = MyNode("xxx") + node.builder_set(Builder()) + node.env_set(Environment()) + node.path = "xxx" + node.sources = ["yyy", "zzz"] + node.build() + assert built_it + assert built_target == [node], built_target + assert built_source == ["yyy", "zzz"], built_source + + built_it = None + node = MyNode("qqq") + node.builder_set(NoneBuilder()) + node.env_set(Environment()) + node.path = "qqq" + node.sources = ["rrr", "sss"] + node.builder.overrides = { "foo" : 1, "bar" : 2 } + node.build() + assert built_it + assert built_target == [node], built_target + assert built_source == ["rrr", "sss"], built_source + assert built_args["foo"] == 1, built_args + assert built_args["bar"] == 2, built_args + + fff = MyNode("fff") + ggg = MyNode("ggg") + lb = ListBuilder(fff, ggg) + e = Environment() + fff.builder_set(lb) + fff.env_set(e) + fff.path = "fff" + ggg.builder_set(lb) + ggg.env_set(e) + ggg.path = "ggg" + fff.sources = ["hhh", "iii"] + ggg.sources = ["hhh", "iii"] + # [Charles C. 1/7/2002] Uhhh, why are there no asserts here? + # [SK, 15 May 2003] I dunno, let's add some... + built_it = None + fff.build() + assert built_it + assert built_target == [fff], built_target + assert built_source == ["hhh", "iii"], built_source + built_it = None + ggg.build() + assert built_it + assert built_target == [ggg], built_target + assert built_source == ["hhh", "iii"], built_source + + built_it = None + jjj = MyNode("jjj") + b = Builder() + jjj.builder_set(b) + # NOTE: No env_set()! We should pull the environment from the builder. + b.env = Environment() + b.overrides = { "on" : 3, "off" : 4 } + e.builder = b + jjj.build() + assert built_it + assert built_target[0] == jjj, built_target[0] + assert built_source == [], built_source + assert built_args["on"] == 3, built_args + assert built_args["off"] == 4, built_args + + def test_get_build_scanner_path(self): + """Test the get_build_scanner_path() method""" + n = SCons.Node.Node() + x = MyExecutor() + n.set_executor(x) + p = n.get_build_scanner_path('fake_scanner') + assert p == "executor would call fake_scanner", p + + def test_get_executor(self): + """Test the get_executor() method""" + n = SCons.Node.Node() + + try: + n.get_executor(0) + except AttributeError: + pass + else: + self.fail("did not catch expected AttributeError") + + class Builder: + action = 'act' + env = 'env1' + overrides = {} + + n = SCons.Node.Node() + n.builder_set(Builder()) + x = n.get_executor() + assert x.env == 'env1', x.env + + n = SCons.Node.Node() + n.builder_set(Builder()) + n.env_set('env2') + x = n.get_executor() + assert x.env == 'env2', x.env + + def test_set_executor(self): + """Test the set_executor() method""" + n = SCons.Node.Node() + n.set_executor(1) + assert n.executor == 1, n.executor + + def test_executor_cleanup(self): + """Test letting the executor cleanup its cache""" + n = SCons.Node.Node() + x = MyExecutor() + n.set_executor(x) + n.executor_cleanup() + assert x.cleaned_up + + def test_reset_executor(self): + """Test the reset_executor() method""" + n = SCons.Node.Node() + n.set_executor(1) + assert n.executor == 1, n.executor + n.reset_executor() + assert not hasattr(n, 'executor'), "unexpected executor attribute" + + def test_built(self): + """Test the built() method""" + class SubNodeInfo(SCons.Node.NodeInfoBase): + def update(self, node): + self.updated = 1 + class SubNode(SCons.Node.Node): + def clear(self): + self.cleared = 1 + + n = SubNode() + n.ninfo = SubNodeInfo(n) + n.built() + assert n.cleared, n.cleared + assert n.ninfo.updated, n.ninfo.cleared + + def test_push_to_cache(self): + """Test the base push_to_cache() method""" + n = SCons.Node.Node() + r = n.push_to_cache() + assert r is None, r + + def test_retrieve_from_cache(self): + """Test the base retrieve_from_cache() method""" + n = SCons.Node.Node() + r = n.retrieve_from_cache() + assert r == 0, r + + def test_visited(self): + """Test the base visited() method + + Just make sure it's there and we can call it. + """ + n = SCons.Node.Node() + n.visited() + + def test_builder_set(self): + """Test setting a Node's Builder + """ + node = SCons.Node.Node() + b = Builder() + node.builder_set(b) + assert node.builder == b + + def test_has_builder(self): + """Test the has_builder() method + """ + n1 = SCons.Node.Node() + assert n1.has_builder() == 0 + n1.builder_set(Builder()) + assert n1.has_builder() == 1 + + def test_has_explicit_builder(self): + """Test the has_explicit_builder() method + """ + n1 = SCons.Node.Node() + assert not n1.has_explicit_builder() + n1.set_explicit(1) + assert n1.has_explicit_builder() + n1.set_explicit(None) + assert not n1.has_explicit_builder() + + def test_get_builder(self): + """Test the get_builder() method""" + n1 = SCons.Node.Node() + b = n1.get_builder() + assert b is None, b + b = n1.get_builder(777) + assert b == 777, b + n1.builder_set(888) + b = n1.get_builder() + assert b == 888, b + b = n1.get_builder(999) + assert b == 888, b + + def test_multiple_side_effect_has_builder(self): + """Test the multiple_side_effect_has_builder() method + """ + n1 = SCons.Node.Node() + assert n1.multiple_side_effect_has_builder() == 0 + n1.builder_set(Builder()) + assert n1.multiple_side_effect_has_builder() == 1 + + def test_is_derived(self): + """Test the is_derived() method + """ + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + n3 = SCons.Node.Node() + + n2.builder_set(Builder()) + n3.side_effect = 1 + + assert n1.is_derived() == 0 + assert n2.is_derived() == 1 + assert n3.is_derived() == 1 + + def test_alter_targets(self): + """Test the alter_targets() method + """ + n = SCons.Node.Node() + t, m = n.alter_targets() + assert t == [], t + assert m is None, m + + def test_is_up_to_date(self): + """Test the default is_up_to_date() method + """ + node = SCons.Node.Node() + assert node.is_up_to_date() is None + + def test_children_are_up_to_date(self): + """Test the children_are_up_to_date() method used by subclasses + """ + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + + n1.add_source([n2]) + assert n1.children_are_up_to_date(), "expected up to date" + n2.set_state(SCons.Node.executed) + assert not n1.children_are_up_to_date(), "expected not up to date" + n2.set_state(SCons.Node.up_to_date) + assert n1.children_are_up_to_date(), "expected up to date" + n1.always_build = 1 + assert not n1.children_are_up_to_date(), "expected not up to date" + + def test_env_set(self): + """Test setting a Node's Environment + """ + node = SCons.Node.Node() + e = Environment() + node.env_set(e) + assert node.env == e + + def test_get_actions(self): + """Test fetching a Node's action list + """ + node = SCons.Node.Node() + node.builder_set(Builder()) + a = node.builder.get_actions() + assert isinstance(a[0], MyAction), a[0] + + def test_get_csig(self): + """Test generic content signature calculation + """ + node = SCons.Node.Node() + node.get_contents = lambda: 444 + result = node.get_csig() + assert result == '550a141f12de6341fba65b0ad0433500', result + + def test_get_cachedir_csig(self): + """Test content signature calculation for CacheDir + """ + node = SCons.Node.Node() + node.get_contents = lambda: 555 + result = node.get_cachedir_csig() + assert result == '15de21c670ae7c3f6f3f1f37029303c9', result + + def test_get_binfo(self): + """Test fetching/creating a build information structure + """ + node = SCons.Node.Node() + + binfo = node.get_binfo() + assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo + + node = SCons.Node.Node() + d = SCons.Node.Node() + d.get_ninfo().csig = 777 + i = SCons.Node.Node() + i.get_ninfo().csig = 888 + node.depends = [d] + node.implicit = [i] + + binfo = node.get_binfo() + assert isinstance(binfo, SCons.Node.BuildInfoBase), binfo + assert hasattr(binfo, 'bsources') + assert hasattr(binfo, 'bsourcesigs') + assert binfo.bdepends == [d] + assert hasattr(binfo, 'bdependsigs') + assert binfo.bimplicit == [i] + assert hasattr(binfo, 'bimplicitsigs') + + def test_explain(self): + """Test explaining why a Node must be rebuilt + """ + class testNode(SCons.Node.Node): + def __str__(self): return 'xyzzy' + node = testNode() + node.exists = lambda: None + # Can't do this with new-style classes (python bug #1066490) + #node.__str__ = lambda: 'xyzzy' + result = node.explain() + assert result == "building `xyzzy' because it doesn't exist\n", result + + class testNode2(SCons.Node.Node): + def __str__(self): return 'null_binfo' + class FS: + pass + node = testNode2() + node.fs = FS() + node.fs.Top = SCons.Node.Node() + result = node.explain() + assert result is None, result + + def get_null_info(): + class Null_SConsignEntry: + class Null_BuildInfo: + def prepare_dependencies(self): + pass + binfo = Null_BuildInfo() + return Null_SConsignEntry() + + node.get_stored_info = get_null_info + #see above: node.__str__ = lambda: 'null_binfo' + result = node.explain() + assert result == "Cannot explain why `null_binfo' is being rebuilt: No previous build information found\n", result + + # XXX additional tests for the guts of the functionality some day + + #def test_del_binfo(self): + # """Test deleting the build information from a Node + # """ + # node = SCons.Node.Node() + # node.binfo = None + # node.del_binfo() + # assert not hasattr(node, 'binfo'), node + + def test_store_info(self): + """Test calling the method to store build information + """ + node = SCons.Node.Node() + node.store_info() + + def test_get_stored_info(self): + """Test calling the method to fetch stored build information + """ + node = SCons.Node.Node() + result = node.get_stored_info() + assert result is None, result + + def test_set_always_build(self): + """Test setting a Node's always_build value + """ + node = SCons.Node.Node() + node.set_always_build() + assert node.always_build + node.set_always_build(3) + assert node.always_build == 3 + + def test_set_noclean(self): + """Test setting a Node's noclean value + """ + node = SCons.Node.Node() + node.set_noclean() + assert node.noclean == 1, node.noclean + node.set_noclean(7) + assert node.noclean == 1, node.noclean + node.set_noclean(0) + assert node.noclean == 0, node.noclean + node.set_noclean(None) + assert node.noclean == 0, node.noclean + + def test_set_precious(self): + """Test setting a Node's precious value + """ + node = SCons.Node.Node() + node.set_precious() + assert node.precious + node.set_precious(7) + assert node.precious == 7 + + def test_exists(self): + """Test evaluating whether a Node exists. + """ + node = SCons.Node.Node() + e = node.exists() + assert e == 1, e + + def test_exists(self): + """Test evaluating whether a Node exists locally or in a repository. + """ + node = SCons.Node.Node() + e = node.rexists() + assert e == 1, e + + class MyNode(SCons.Node.Node): + def exists(self): + return 'xyz' + + node = MyNode() + e = node.rexists() + assert e == 'xyz', e + + def test_prepare(self): + """Test preparing a node to be built + + By extension, this also tests the missing() method. + """ + node = SCons.Node.Node() + + n1 = SCons.Node.Node() + n1.builder_set(Builder()) + node.implicit = [] + node.implicit_set = set() + node._add_child(node.implicit, node.implicit_set, [n1]) + + node.prepare() # should not throw an exception + + n2 = SCons.Node.Node() + n2.linked = 1 + node.implicit = [] + node.implicit_set = set() + node._add_child(node.implicit, node.implicit_set, [n2]) + + node.prepare() # should not throw an exception + + n3 = SCons.Node.Node() + node.implicit = [] + node.implicit_set = set() + node._add_child(node.implicit, node.implicit_set, [n3]) + + node.prepare() # should not throw an exception + + class MyNode(SCons.Node.Node): + def rexists(self): + return None + n4 = MyNode() + node.implicit = [] + node.implicit_set = set() + node._add_child(node.implicit, node.implicit_set, [n4]) + exc_caught = 0 + try: + node.prepare() + except SCons.Errors.StopError: + exc_caught = 1 + assert exc_caught, "did not catch expected StopError" + + def test_add_dependency(self): + """Test adding dependencies to a Node's list. + """ + node = SCons.Node.Node() + assert node.depends == [] + + zero = SCons.Node.Node() + + one = SCons.Node.Node() + two = SCons.Node.Node() + three = SCons.Node.Node() + four = SCons.Node.Node() + five = SCons.Node.Node() + six = SCons.Node.Node() + + node.add_dependency([zero]) + assert node.depends == [zero] + node.add_dependency([one]) + assert node.depends == [zero, one] + node.add_dependency([two, three]) + assert node.depends == [zero, one, two, three] + node.add_dependency([three, four, one]) + assert node.depends == [zero, one, two, three, four] + + try: + node.add_depends([[five, six]]) + except: + pass + else: + raise "did not catch expected exception" + assert node.depends == [zero, one, two, three, four] + + + def test_add_source(self): + """Test adding sources to a Node's list. + """ + node = SCons.Node.Node() + assert node.sources == [] + + zero = SCons.Node.Node() + one = SCons.Node.Node() + two = SCons.Node.Node() + three = SCons.Node.Node() + four = SCons.Node.Node() + five = SCons.Node.Node() + six = SCons.Node.Node() + + node.add_source([zero]) + assert node.sources == [zero] + node.add_source([one]) + assert node.sources == [zero, one] + node.add_source([two, three]) + assert node.sources == [zero, one, two, three] + node.add_source([three, four, one]) + assert node.sources == [zero, one, two, three, four] + + try: + node.add_source([[five, six]]) + except: + pass + else: + raise "did not catch expected exception" + assert node.sources == [zero, one, two, three, four], node.sources + + def test_add_ignore(self): + """Test adding files whose dependencies should be ignored. + """ + node = SCons.Node.Node() + assert node.ignore == [] + + zero = SCons.Node.Node() + one = SCons.Node.Node() + two = SCons.Node.Node() + three = SCons.Node.Node() + four = SCons.Node.Node() + five = SCons.Node.Node() + six = SCons.Node.Node() + + node.add_ignore([zero]) + assert node.ignore == [zero] + node.add_ignore([one]) + assert node.ignore == [zero, one] + node.add_ignore([two, three]) + assert node.ignore == [zero, one, two, three] + node.add_ignore([three, four, one]) + assert node.ignore == [zero, one, two, three, four] + + try: + node.add_ignore([[five, six]]) + except: + pass + else: + raise "did not catch expected exception" + assert node.ignore == [zero, one, two, three, four] + + def test_get_found_includes(self): + """Test the default get_found_includes() method + """ + node = SCons.Node.Node() + target = SCons.Node.Node() + e = Environment() + deps = node.get_found_includes(e, None, target) + assert deps == [], deps + + def test_get_implicit_deps(self): + """Test get_implicit_deps() + """ + node = MyNode("nnn") + target = MyNode("ttt") + env = Environment() + + # No scanner at all returns [] + deps = node.get_implicit_deps(env, None, target) + assert deps == [], deps + + s = Scanner() + d1 = MyNode("d1") + d2 = MyNode("d2") + node.found_includes = [d1, d2] + + # Simple return of the found includes + deps = node.get_implicit_deps(env, s, target) + assert deps == [d1, d2], deps + + # By default, our fake scanner recurses + e = MyNode("eee") + f = MyNode("fff") + g = MyNode("ggg") + d1.found_includes = [e, f] + d2.found_includes = [e, f] + f.found_includes = [g] + deps = node.get_implicit_deps(env, s, target) + assert deps == [d1, d2, e, f, g], map(str, deps) + + # Recursive scanning eliminates duplicates + e.found_includes = [f] + deps = node.get_implicit_deps(env, s, target) + assert deps == [d1, d2, e, f, g], map(str, deps) + + # Scanner method can select specific nodes to recurse + def no_fff(nodes): + return filter(lambda n: str(n)[0] != 'f', nodes) + s.recurse_nodes = no_fff + deps = node.get_implicit_deps(env, s, target) + assert deps == [d1, d2, e, f], map(str, deps) + + # Scanner method can short-circuit recursing entirely + s.recurse_nodes = lambda nodes: [] + deps = node.get_implicit_deps(env, s, target) + assert deps == [d1, d2], map(str, deps) + + def test_get_env_scanner(self): + """Test fetching the environment scanner for a Node + """ + node = SCons.Node.Node() + scanner = Scanner() + env = Environment(SCANNERS = [scanner]) + s = node.get_env_scanner(env) + assert s == scanner, s + s = node.get_env_scanner(env, {'X':1}) + assert s == scanner, s + + def test_get_target_scanner(self): + """Test fetching the target scanner for a Node + """ + s = Scanner() + b = Builder() + b.target_scanner = s + n = SCons.Node.Node() + n.builder = b + x = n.get_target_scanner() + assert x is s, x + + def test_get_source_scanner(self): + """Test fetching the source scanner for a Node + """ + target = SCons.Node.Node() + source = SCons.Node.Node() + s = target.get_source_scanner(source) + assert isinstance(s, SCons.Util.Null), s + + ts1 = Scanner() + ts2 = Scanner() + ts3 = Scanner() + + class Builder1(Builder): + def __call__(self, source): + r = SCons.Node.Node() + r.builder = self + return [r] + class Builder2(Builder1): + def __init__(self, scanner): + self.source_scanner = scanner + + builder = Builder2(ts1) + + targets = builder([source]) + s = targets[0].get_source_scanner(source) + assert s is ts1, s + + target.builder_set(Builder2(ts1)) + target.builder.source_scanner = ts2 + s = target.get_source_scanner(source) + assert s is ts2, s + + builder = Builder1(env=Environment(SCANNERS = [ts3])) + + targets = builder([source]) + + s = targets[0].get_source_scanner(source) + assert s is ts3, s + + + def test_scan(self): + """Test Scanner functionality + """ + env = Environment() + node = MyNode("nnn") + node.builder = Builder() + node.env_set(env) + x = MyExecutor(env, [node]) + + s = Scanner() + d = MyNode("ddd") + node.found_includes = [d] + + node.builder.target_scanner = s + assert node.implicit is None + + node.scan() + assert s.called + assert node.implicit == [d], node.implicit + + # Check that scanning a node with some stored implicit + # dependencies resets internal attributes appropriately + # if the stored dependencies need recalculation. + class StoredNode(MyNode): + def get_stored_implicit(self): + return [MyNode('implicit1'), MyNode('implicit2')] + + save_implicit_cache = SCons.Node.implicit_cache + save_implicit_deps_changed = SCons.Node.implicit_deps_changed + save_implicit_deps_unchanged = SCons.Node.implicit_deps_unchanged + SCons.Node.implicit_cache = 1 + SCons.Node.implicit_deps_changed = None + SCons.Node.implicit_deps_unchanged = None + try: + sn = StoredNode("eee") + sn.builder_set(Builder()) + sn.builder.target_scanner = s + + sn.scan() + + assert sn.implicit == [], sn.implicit + assert sn.children() == [], sn.children() + + finally: + SCons.Node.implicit_cache = save_implicit_cache + SCons.Node.implicit_deps_changed = save_implicit_deps_changed + SCons.Node.implicit_deps_unchanged = save_implicit_deps_unchanged + + def test_scanner_key(self): + """Test that a scanner_key() method exists""" + assert SCons.Node.Node().scanner_key() is None + + def test_children(self): + """Test fetching the non-ignored "children" of a Node. + """ + node = SCons.Node.Node() + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + n3 = SCons.Node.Node() + n4 = SCons.Node.Node() + n5 = SCons.Node.Node() + n6 = SCons.Node.Node() + n7 = SCons.Node.Node() + n8 = SCons.Node.Node() + n9 = SCons.Node.Node() + n10 = SCons.Node.Node() + n11 = SCons.Node.Node() + n12 = SCons.Node.Node() + + node.add_source([n1, n2, n3]) + node.add_dependency([n4, n5, n6]) + node.implicit = [] + node.implicit_set = set() + node._add_child(node.implicit, node.implicit_set, [n7, n8, n9]) + node._add_child(node.implicit, node.implicit_set, [n10, n11, n12]) + node.add_ignore([n2, n5, n8, n11]) + + kids = node.children() + for kid in [n1, n3, n4, n6, n7, n9, n10, n12]: + assert kid in kids, kid + for kid in [n2, n5, n8, n11]: + assert not kid in kids, kid + + def test_all_children(self): + """Test fetching all the "children" of a Node. + """ + node = SCons.Node.Node() + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + n3 = SCons.Node.Node() + n4 = SCons.Node.Node() + n5 = SCons.Node.Node() + n6 = SCons.Node.Node() + n7 = SCons.Node.Node() + n8 = SCons.Node.Node() + n9 = SCons.Node.Node() + n10 = SCons.Node.Node() + n11 = SCons.Node.Node() + n12 = SCons.Node.Node() + + node.add_source([n1, n2, n3]) + node.add_dependency([n4, n5, n6]) + node.implicit = [] + node.implicit_set = set() + node._add_child(node.implicit, node.implicit_set, [n7, n8, n9]) + node._add_child(node.implicit, node.implicit_set, [n10, n11, n12]) + node.add_ignore([n2, n5, n8, n11]) + + kids = node.all_children() + for kid in [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12]: + assert kid in kids, kid + + def test_state(self): + """Test setting and getting the state of a node + """ + node = SCons.Node.Node() + assert node.get_state() == SCons.Node.no_state + node.set_state(SCons.Node.executing) + assert node.get_state() == SCons.Node.executing + assert SCons.Node.pending < SCons.Node.executing + assert SCons.Node.executing < SCons.Node.up_to_date + assert SCons.Node.up_to_date < SCons.Node.executed + assert SCons.Node.executed < SCons.Node.failed + + def test_walker(self): + """Test walking a Node tree. + """ + + n1 = MyNode("n1") + + nw = SCons.Node.Walker(n1) + assert not nw.is_done() + assert nw.next().name == "n1" + assert nw.is_done() + assert nw.next() is None + + n2 = MyNode("n2") + n3 = MyNode("n3") + n1.add_source([n2, n3]) + + nw = SCons.Node.Walker(n1) + n = nw.next() + assert n.name == "n2", n.name + n = nw.next() + assert n.name == "n3", n.name + n = nw.next() + assert n.name == "n1", n.name + n = nw.next() + assert n is None, n + + n4 = MyNode("n4") + n5 = MyNode("n5") + n6 = MyNode("n6") + n7 = MyNode("n7") + n2.add_source([n4, n5]) + n3.add_dependency([n6, n7]) + + nw = SCons.Node.Walker(n1) + assert nw.next().name == "n4" + assert nw.next().name == "n5" + assert nw.history.has_key(n2) + assert nw.next().name == "n2" + assert nw.next().name == "n6" + assert nw.next().name == "n7" + assert nw.history.has_key(n3) + assert nw.next().name == "n3" + assert nw.history.has_key(n1) + assert nw.next().name == "n1" + assert nw.next() is None + + n8 = MyNode("n8") + n8.add_dependency([n3]) + n7.add_dependency([n8]) + + def cycle(node, stack): + global cycle_detected + cycle_detected = 1 + + global cycle_detected + + nw = SCons.Node.Walker(n3, cycle_func = cycle) + n = nw.next() + assert n.name == "n6", n.name + n = nw.next() + assert n.name == "n8", n.name + assert cycle_detected + cycle_detected = None + n = nw.next() + assert n.name == "n7", n.name + n = nw.next() + assert nw.next() is None + + def test_abspath(self): + """Test the get_abspath() method.""" + n = MyNode("foo") + assert n.get_abspath() == str(n), n.get_abspath() + + def test_for_signature(self): + """Test the for_signature() method.""" + n = MyNode("foo") + assert n.for_signature() == str(n), n.get_abspath() + + def test_get_string(self): + """Test the get_string() method.""" + class TestNode(MyNode): + def __init__(self, name, sig): + MyNode.__init__(self, name) + self.sig = sig + + def for_signature(self): + return self.sig + + n = TestNode("foo", "bar") + assert n.get_string(0) == "foo", n.get_string(0) + assert n.get_string(1) == "bar", n.get_string(1) + + def test_literal(self): + """Test the is_literal() function.""" + n=SCons.Node.Node() + assert n.is_literal() + + def test_Annotate(self): + """Test using an interface-specific Annotate function.""" + def my_annotate(node, self=self): + node.annotation = self.node_string + + save_Annotate = SCons.Node.Annotate + SCons.Node.Annotate = my_annotate + + try: + self.node_string = '#1' + n = SCons.Node.Node() + assert n.annotation == '#1', n.annotation + + self.node_string = '#2' + n = SCons.Node.Node() + assert n.annotation == '#2', n.annotation + finally: + SCons.Node.Annotate = save_Annotate + + def test_clear(self): + """Test clearing all cached state information.""" + n = SCons.Node.Node() + + n.set_state(3) + n.binfo = 'xyz' + n.includes = 'testincludes' + n.found_include = {'testkey':'testvalue'} + n.implicit = 'testimplicit' + + x = MyExecutor() + n.set_executor(x) + + n.clear() + + assert n.includes is None, n.includes + assert x.cleaned_up + + def test_get_subst_proxy(self): + """Test the get_subst_proxy method.""" + n = MyNode("test") + + assert n.get_subst_proxy() == n, n.get_subst_proxy() + + def test_new_binfo(self): + """Test the new_binfo() method""" + n = SCons.Node.Node() + result = n.new_binfo() + assert isinstance(result, SCons.Node.BuildInfoBase), result + + def test_get_suffix(self): + """Test the base Node get_suffix() method""" + n = SCons.Node.Node() + s = n.get_suffix() + assert s == '', s + + def test_postprocess(self): + """Test calling the base Node postprocess() method""" + n = SCons.Node.Node() + n.waiting_parents = set( ['foo','bar'] ) + + n.postprocess() + assert n.waiting_parents == set(), n.waiting_parents + + def test_add_to_waiting_parents(self): + """Test the add_to_waiting_parents() method""" + n1 = SCons.Node.Node() + n2 = SCons.Node.Node() + assert n1.waiting_parents == set(), n1.waiting_parents + r = n1.add_to_waiting_parents(n2) + assert r == 1, r + assert n1.waiting_parents == set((n2,)), n1.waiting_parents + r = n1.add_to_waiting_parents(n2) + assert r == 0, r + + +class NodeListTestCase(unittest.TestCase): + def test___str__(self): + """Test""" + n1 = MyNode("n1") + n2 = MyNode("n2") + n3 = MyNode("n3") + nl = SCons.Node.NodeList([n3, n2, n1]) + + l = [1] + ul = UserList.UserList([2]) + try: + l.extend(ul) + except TypeError: + # An older version of Python (*cough* 1.5.2 *cough*) + # that doesn't allow UserList objects to extend lists. + pass + else: + s = str(nl) + assert s == "['n3', 'n2', 'n1']", s + + r = repr(nl) + r = re.sub('at (0[xX])?[0-9a-fA-F]+', 'at 0x', r) + # Don't care about ancestry: just leaf value of MyNode + r = re.sub('<.*?\.MyNode', '"]*3, ", ") + assert r == '[%s]' % l, r + + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ BuildInfoBaseTestCase, + NodeInfoBaseTestCase, + NodeTestCase, + NodeListTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/Python.py b/src/engine/SCons/Node/Python.py new file mode 100644 index 0000000..941b4ed --- /dev/null +++ b/src/engine/SCons/Node/Python.py @@ -0,0 +1,128 @@ +"""scons.Node.Python + +Python nodes. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/Python.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node + +class ValueNodeInfo(SCons.Node.NodeInfoBase): + current_version_id = 1 + + field_list = ['csig'] + + def str_to_node(self, s): + return Value(s) + +class ValueBuildInfo(SCons.Node.BuildInfoBase): + current_version_id = 1 + +class Value(SCons.Node.Node): + """A class for Python variables, typically passed on the command line + or generated by a script, but not from a file or some other source. + """ + + NodeInfo = ValueNodeInfo + BuildInfo = ValueBuildInfo + + def __init__(self, value, built_value=None): + SCons.Node.Node.__init__(self) + self.value = value + if built_value is not None: + self.built_value = built_value + + def str_for_display(self): + return repr(self.value) + + def __str__(self): + return str(self.value) + + def make_ready(self): + self.get_csig() + + def build(self, **kw): + if not hasattr(self, 'built_value'): + apply (SCons.Node.Node.build, (self,), kw) + + is_up_to_date = SCons.Node.Node.children_are_up_to_date + + def is_under(self, dir): + # Make Value nodes get built regardless of + # what directory scons was run from. Value nodes + # are outside the filesystem: + return 1 + + def write(self, built_value): + """Set the value of the node.""" + self.built_value = built_value + + def read(self): + """Return the value. If necessary, the value is built.""" + self.build() + if not hasattr(self, 'built_value'): + self.built_value = self.value + return self.built_value + + def get_text_contents(self): + """By the assumption that the node.built_value is a + deterministic product of the sources, the contents of a Value + are the concatenation of all the contents of its sources. As + the value need not be built when get_contents() is called, we + cannot use the actual node.built_value.""" + ###TODO: something reasonable about universal newlines + contents = str(self.value) + for kid in self.children(None): + contents = contents + kid.get_contents() + return contents + + get_contents = get_text_contents ###TODO should return 'bytes' value + + def changed_since_last_build(self, target, prev_ni): + cur_csig = self.get_csig() + try: + return cur_csig != prev_ni.csig + except AttributeError: + return 1 + + def get_csig(self, calc=None): + """Because we're a Python value node and don't have a real + timestamp, we get to ignore the calculator and just use the + value contents.""" + try: + return self.ninfo.csig + except AttributeError: + pass + contents = self.get_contents() + self.get_ninfo().csig = contents + return contents + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/PythonTests.py b/src/engine/SCons/Node/PythonTests.py new file mode 100644 index 0000000..01844ac --- /dev/null +++ b/src/engine/SCons/Node/PythonTests.py @@ -0,0 +1,130 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/PythonTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Node.Python + +class ValueTestCase(unittest.TestCase): + + def test_Value(self): + """Test creating a Value() object + """ + v1 = SCons.Node.Python.Value('a') + assert v1.value == 'a', v1.value + + value2 = 'a' + v2 = SCons.Node.Python.Value(value2) + assert v2.value == value2, v2.value + assert v2.value is value2, v2.value + + assert not v1 is v2 + assert v1.value == v2.value + + v3 = SCons.Node.Python.Value('c', 'cb') + assert v3.built_value == 'cb' + + def test_build(self): + """Test "building" a Value Node + """ + class fake_executor: + def __call__(self, node): + node.write('faked') + + v1 = SCons.Node.Python.Value('b', 'built') + v1.executor = fake_executor() + v1.build() + assert v1.built_value == 'built', v1.built_value + + v2 = SCons.Node.Python.Value('b') + v2.executor = fake_executor() + v2.build() + assert v2.built_value == 'faked', v2.built_value + + def test_read(self): + """Test the Value.read() method + """ + v1 = SCons.Node.Python.Value('a') + x = v1.read() + assert x == 'a', x + + def test_write(self): + """Test the Value.write() method + """ + v1 = SCons.Node.Python.Value('a') + assert v1.value == 'a', v1.value + assert not hasattr(v1, 'built_value') + + v1.write('new') + assert v1.value == 'a', v1.value + assert v1.built_value == 'new', v1.built_value + + def test_get_csig(self): + """Test calculating the content signature of a Value() object + """ + v1 = SCons.Node.Python.Value('aaa') + csig = v1.get_csig(None) + assert csig == 'aaa', csig + + v2 = SCons.Node.Python.Value(7) + csig = v2.get_csig(None) + assert csig == '7', csig + + v3 = SCons.Node.Python.Value(None) + csig = v3.get_csig(None) + assert csig == 'None', csig + +class ValueNodeInfoTestCase(unittest.TestCase): + def test___init__(self): + """Test ValueNodeInfo initialization""" + vvv = SCons.Node.Python.Value('vvv') + ni = SCons.Node.Python.ValueNodeInfo(vvv) + +class ValueBuildInfoTestCase(unittest.TestCase): + def test___init__(self): + """Test ValueBuildInfo initialization""" + vvv = SCons.Node.Python.Value('vvv') + bi = SCons.Node.Python.ValueBuildInfo(vvv) + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + ValueTestCase, + ValueBuildInfoTestCase, + ValueNodeInfoTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Node/__init__.py b/src/engine/SCons/Node/__init__.py new file mode 100644 index 0000000..94fd4df --- /dev/null +++ b/src/engine/SCons/Node/__init__.py @@ -0,0 +1,1341 @@ +"""SCons.Node + +The Node package for the SCons software construction utility. + +This is, in many ways, the heart of SCons. + +A Node is where we encapsulate all of the dependency information about +any thing that SCons can build, or about any thing which SCons can use +to build some other thing. The canonical "thing," of course, is a file, +but a Node can also represent something remote (like a web page) or +something completely abstract (like an Alias). + +Each specific type of "thing" is specifically represented by a subclass +of the Node base class: Node.FS.File for files, Node.Alias for aliases, +etc. Dependency information is kept here in the base class, and +information specific to files/aliases/etc. is in the subclass. The +goal, if we've done this correctly, is that any type of "thing" should +be able to depend on any other type of "thing." + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Node/__init__.py 4577 2009/12/27 19:44:43 scons" + +import copy +from itertools import chain, izip +import string +import UserList + +from SCons.Debug import logInstanceCreation +import SCons.Executor +import SCons.Memoize +import SCons.Util + +from SCons.Debug import Trace + +def classname(obj): + return string.split(str(obj.__class__), '.')[-1] + +# Node states +# +# These are in "priority" order, so that the maximum value for any +# child/dependency of a node represents the state of that node if +# it has no builder of its own. The canonical example is a file +# system directory, which is only up to date if all of its children +# were up to date. +no_state = 0 +pending = 1 +executing = 2 +up_to_date = 3 +executed = 4 +failed = 5 + +StateString = { + 0 : "no_state", + 1 : "pending", + 2 : "executing", + 3 : "up_to_date", + 4 : "executed", + 5 : "failed", +} + +# controls whether implicit dependencies are cached: +implicit_cache = 0 + +# controls whether implicit dep changes are ignored: +implicit_deps_unchanged = 0 + +# controls whether the cached implicit deps are ignored: +implicit_deps_changed = 0 + +# A variable that can be set to an interface-specific function be called +# to annotate a Node with information about its creation. +def do_nothing(node): pass + +Annotate = do_nothing + +# Classes for signature info for Nodes. + +class NodeInfoBase: + """ + The generic base class for signature information for a Node. + + Node subclasses should subclass NodeInfoBase to provide their own + logic for dealing with their own Node-specific signature information. + """ + current_version_id = 1 + def __init__(self, node): + # Create an object attribute from the class attribute so it ends up + # in the pickled data in the .sconsign file. + self._version_id = self.current_version_id + def update(self, node): + try: + field_list = self.field_list + except AttributeError: + return + for f in field_list: + try: + delattr(self, f) + except AttributeError: + pass + try: + func = getattr(node, 'get_' + f) + except AttributeError: + pass + else: + setattr(self, f, func()) + def convert(self, node, val): + pass + def merge(self, other): + self.__dict__.update(other.__dict__) + def format(self, field_list=None, names=0): + if field_list is None: + try: + field_list = self.field_list + except AttributeError: + field_list = self.__dict__.keys() + field_list.sort() + fields = [] + for field in field_list: + try: + f = getattr(self, field) + except AttributeError: + f = None + f = str(f) + if names: + f = field + ': ' + f + fields.append(f) + return fields + +class BuildInfoBase: + """ + The generic base class for build information for a Node. + + This is what gets stored in a .sconsign file for each target file. + It contains a NodeInfo instance for this node (signature information + that's specific to the type of Node) and direct attributes for the + generic build stuff we have to track: sources, explicit dependencies, + implicit dependencies, and action information. + """ + current_version_id = 1 + def __init__(self, node): + # Create an object attribute from the class attribute so it ends up + # in the pickled data in the .sconsign file. + self._version_id = self.current_version_id + self.bsourcesigs = [] + self.bdependsigs = [] + self.bimplicitsigs = [] + self.bactsig = None + def merge(self, other): + self.__dict__.update(other.__dict__) + +class Node: + """The base Node class, for entities that we know how to + build, or use to build other Nodes. + """ + + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + memoizer_counters = [] + + class Attrs: + pass + + def __init__(self): + if __debug__: logInstanceCreation(self, 'Node.Node') + # Note that we no longer explicitly initialize a self.builder + # attribute to None here. That's because the self.builder + # attribute may be created on-the-fly later by a subclass (the + # canonical example being a builder to fetch a file from a + # source code system like CVS or Subversion). + + # Each list of children that we maintain is accompanied by a + # dictionary used to look up quickly whether a node is already + # present in the list. Empirical tests showed that it was + # fastest to maintain them as side-by-side Node attributes in + # this way, instead of wrapping up each list+dictionary pair in + # a class. (Of course, we could always still do that in the + # future if we had a good reason to...). + self.sources = [] # source files used to build node + self.sources_set = set() + self._specific_sources = False + self.depends = [] # explicit dependencies (from Depends) + self.depends_set = set() + self.ignore = [] # dependencies to ignore + self.ignore_set = set() + self.prerequisites = SCons.Util.UniqueList() + self.implicit = None # implicit (scanned) dependencies (None means not scanned yet) + self.waiting_parents = set() + self.waiting_s_e = set() + self.ref_count = 0 + self.wkids = None # Kids yet to walk, when it's an array + + self.env = None + self.state = no_state + self.precious = None + self.noclean = 0 + self.nocache = 0 + self.always_build = None + self.includes = None + self.attributes = self.Attrs() # Generic place to stick information about the Node. + self.side_effect = 0 # true iff this node is a side effect + self.side_effects = [] # the side effects of building this target + self.linked = 0 # is this node linked to the variant directory? + + self.clear_memoized_values() + + # Let the interface in which the build engine is embedded + # annotate this Node with its own info (like a description of + # what line in what file created the node, for example). + Annotate(self) + + def disambiguate(self, must_exist=None): + return self + + def get_suffix(self): + return '' + + memoizer_counters.append(SCons.Memoize.CountValue('get_build_env')) + + def get_build_env(self): + """Fetch the appropriate Environment to build this node. + """ + try: + return self._memo['get_build_env'] + except KeyError: + pass + result = self.get_executor().get_build_env() + self._memo['get_build_env'] = result + return result + + def get_build_scanner_path(self, scanner): + """Fetch the appropriate scanner path for this node.""" + return self.get_executor().get_build_scanner_path(scanner) + + def set_executor(self, executor): + """Set the action executor for this node.""" + self.executor = executor + + def get_executor(self, create=1): + """Fetch the action executor for this node. Create one if + there isn't already one, and requested to do so.""" + try: + executor = self.executor + except AttributeError: + if not create: + raise + try: + act = self.builder.action + except AttributeError: + executor = SCons.Executor.Null(targets=[self]) + else: + executor = SCons.Executor.Executor(act, + self.env or self.builder.env, + [self.builder.overrides], + [self], + self.sources) + self.executor = executor + return executor + + def executor_cleanup(self): + """Let the executor clean up any cached information.""" + try: + executor = self.get_executor(create=None) + except AttributeError: + pass + else: + executor.cleanup() + + def reset_executor(self): + "Remove cached executor; forces recompute when needed." + try: + delattr(self, 'executor') + except AttributeError: + pass + + def push_to_cache(self): + """Try to push a node into a cache + """ + pass + + def retrieve_from_cache(self): + """Try to retrieve the node's content from a cache + + This method is called from multiple threads in a parallel build, + so only do thread safe stuff here. Do thread unsafe stuff in + built(). + + Returns true iff the node was successfully retrieved. + """ + return 0 + + # + # Taskmaster interface subsystem + # + + def make_ready(self): + """Get a Node ready for evaluation. + + This is called before the Taskmaster decides if the Node is + up-to-date or not. Overriding this method allows for a Node + subclass to be disambiguated if necessary, or for an implicit + source builder to be attached. + """ + pass + + def prepare(self): + """Prepare for this Node to be built. + + This is called after the Taskmaster has decided that the Node + is out-of-date and must be rebuilt, but before actually calling + the method to build the Node. + + This default implementation checks that explicit or implicit + dependencies either exist or are derived, and initializes the + BuildInfo structure that will hold the information about how + this node is, uh, built. + + (The existence of source files is checked separately by the + Executor, which aggregates checks for all of the targets built + by a specific action.) + + Overriding this method allows for for a Node subclass to remove + the underlying file from the file system. Note that subclass + methods should call this base class method to get the child + check and the BuildInfo structure. + """ + for d in self.depends: + if d.missing(): + msg = "Explicit dependency `%s' not found, needed by target `%s'." + raise SCons.Errors.StopError, msg % (d, self) + if self.implicit is not None: + for i in self.implicit: + if i.missing(): + msg = "Implicit dependency `%s' not found, needed by target `%s'." + raise SCons.Errors.StopError, msg % (i, self) + self.binfo = self.get_binfo() + + def build(self, **kw): + """Actually build the node. + + This is called by the Taskmaster after it's decided that the + Node is out-of-date and must be rebuilt, and after the prepare() + method has gotten everything, uh, prepared. + + This method is called from multiple threads in a parallel build, + so only do thread safe stuff here. Do thread unsafe stuff + in built(). + + """ + try: + apply(self.get_executor(), (self,), kw) + except SCons.Errors.BuildError, e: + e.node = self + raise + + def built(self): + """Called just after this node is successfully built.""" + + # Clear the implicit dependency caches of any Nodes + # waiting for this Node to be built. + for parent in self.waiting_parents: + parent.implicit = None + + self.clear() + + self.ninfo.update(self) + + def visited(self): + """Called just after this node has been visited (with or + without a build).""" + try: + binfo = self.binfo + except AttributeError: + # Apparently this node doesn't need build info, so + # don't bother calculating or storing it. + pass + else: + self.ninfo.update(self) + self.store_info() + + # + # + # + + def add_to_waiting_s_e(self, node): + self.waiting_s_e.add(node) + + def add_to_waiting_parents(self, node): + """ + Returns the number of nodes added to our waiting parents list: + 1 if we add a unique waiting parent, 0 if not. (Note that the + returned values are intended to be used to increment a reference + count, so don't think you can "clean up" this function by using + True and False instead...) + """ + wp = self.waiting_parents + if node in wp: + return 0 + wp.add(node) + return 1 + + def postprocess(self): + """Clean up anything we don't need to hang onto after we've + been built.""" + self.executor_cleanup() + self.waiting_parents = set() + + def clear(self): + """Completely clear a Node of all its cached state (so that it + can be re-evaluated by interfaces that do continuous integration + builds). + """ + # The del_binfo() call here isn't necessary for normal execution, + # but is for interactive mode, where we might rebuild the same + # target and need to start from scratch. + self.del_binfo() + self.clear_memoized_values() + self.ninfo = self.new_ninfo() + self.executor_cleanup() + try: + delattr(self, '_calculated_sig') + except AttributeError: + pass + self.includes = None + + def clear_memoized_values(self): + self._memo = {} + + def builder_set(self, builder): + self.builder = builder + try: + del self.executor + except AttributeError: + pass + + def has_builder(self): + """Return whether this Node has a builder or not. + + In Boolean tests, this turns out to be a *lot* more efficient + than simply examining the builder attribute directly ("if + node.builder: ..."). When the builder attribute is examined + directly, it ends up calling __getattr__ for both the __len__ + and __nonzero__ attributes on instances of our Builder Proxy + class(es), generating a bazillion extra calls and slowing + things down immensely. + """ + try: + b = self.builder + except AttributeError: + # There was no explicit builder for this Node, so initialize + # the self.builder attribute to None now. + b = self.builder = None + return b is not None + + def set_explicit(self, is_explicit): + self.is_explicit = is_explicit + + def has_explicit_builder(self): + """Return whether this Node has an explicit builder + + This allows an internal Builder created by SCons to be marked + non-explicit, so that it can be overridden by an explicit + builder that the user supplies (the canonical example being + directories).""" + try: + return self.is_explicit + except AttributeError: + self.is_explicit = None + return self.is_explicit + + def get_builder(self, default_builder=None): + """Return the set builder, or a specified default value""" + try: + return self.builder + except AttributeError: + return default_builder + + multiple_side_effect_has_builder = has_builder + + def is_derived(self): + """ + Returns true iff this node is derived (i.e. built). + + This should return true only for nodes whose path should be in + the variant directory when duplicate=0 and should contribute their build + signatures when they are used as source files to other derived files. For + example: source with source builders are not derived in this sense, + and hence should not return true. + """ + return self.has_builder() or self.side_effect + + def alter_targets(self): + """Return a list of alternate targets for this Node. + """ + return [], None + + def get_found_includes(self, env, scanner, path): + """Return the scanned include lines (implicit dependencies) + found in this node. + + The default is no implicit dependencies. We expect this method + to be overridden by any subclass that can be scanned for + implicit dependencies. + """ + return [] + + def get_implicit_deps(self, env, scanner, path): + """Return a list of implicit dependencies for this node. + + This method exists to handle recursive invocation of the scanner + on the implicit dependencies returned by the scanner, if the + scanner's recursive flag says that we should. + """ + if not scanner: + return [] + + # Give the scanner a chance to select a more specific scanner + # for this Node. + #scanner = scanner.select(self) + + nodes = [self] + seen = {} + seen[self] = 1 + deps = [] + while nodes: + n = nodes.pop(0) + d = filter(lambda x, seen=seen: not seen.has_key(x), + n.get_found_includes(env, scanner, path)) + if d: + deps.extend(d) + for n in d: + seen[n] = 1 + nodes.extend(scanner.recurse_nodes(d)) + + return deps + + def get_env_scanner(self, env, kw={}): + return env.get_scanner(self.scanner_key()) + + def get_target_scanner(self): + return self.builder.target_scanner + + def get_source_scanner(self, node): + """Fetch the source scanner for the specified node + + NOTE: "self" is the target being built, "node" is + the source file for which we want to fetch the scanner. + + Implies self.has_builder() is true; again, expect to only be + called from locations where this is already verified. + + This function may be called very often; it attempts to cache + the scanner found to improve performance. + """ + scanner = None + try: + scanner = self.builder.source_scanner + except AttributeError: + pass + if not scanner: + # The builder didn't have an explicit scanner, so go look up + # a scanner from env['SCANNERS'] based on the node's scanner + # key (usually the file extension). + scanner = self.get_env_scanner(self.get_build_env()) + if scanner: + scanner = scanner.select(node) + return scanner + + def add_to_implicit(self, deps): + if not hasattr(self, 'implicit') or self.implicit is None: + self.implicit = [] + self.implicit_set = set() + self._children_reset() + self._add_child(self.implicit, self.implicit_set, deps) + + def scan(self): + """Scan this node's dependents for implicit dependencies.""" + # Don't bother scanning non-derived files, because we don't + # care what their dependencies are. + # Don't scan again, if we already have scanned. + if self.implicit is not None: + return + self.implicit = [] + self.implicit_set = set() + self._children_reset() + if not self.has_builder(): + return + + build_env = self.get_build_env() + executor = self.get_executor() + + # Here's where we implement --implicit-cache. + if implicit_cache and not implicit_deps_changed: + implicit = self.get_stored_implicit() + if implicit is not None: + # We now add the implicit dependencies returned from the + # stored .sconsign entry to have already been converted + # to Nodes for us. (We used to run them through a + # source_factory function here.) + + # Update all of the targets with them. This + # essentially short-circuits an N*M scan of the + # sources for each individual target, which is a hell + # of a lot more efficient. + for tgt in executor.get_all_targets(): + tgt.add_to_implicit(implicit) + + if implicit_deps_unchanged or self.is_up_to_date(): + return + # one of this node's sources has changed, + # so we must recalculate the implicit deps: + self.implicit = [] + self.implicit_set = set() + + # Have the executor scan the sources. + executor.scan_sources(self.builder.source_scanner) + + # If there's a target scanner, have the executor scan the target + # node itself and associated targets that might be built. + scanner = self.get_target_scanner() + if scanner: + executor.scan_targets(scanner) + + def scanner_key(self): + return None + + def select_scanner(self, scanner): + """Selects a scanner for this Node. + + This is a separate method so it can be overridden by Node + subclasses (specifically, Node.FS.Dir) that *must* use their + own Scanner and don't select one the Scanner.Selector that's + configured for the target. + """ + return scanner.select(self) + + def env_set(self, env, safe=0): + if safe and self.env: + return + self.env = env + + # + # SIGNATURE SUBSYSTEM + # + + NodeInfo = NodeInfoBase + BuildInfo = BuildInfoBase + + def new_ninfo(self): + ninfo = self.NodeInfo(self) + return ninfo + + def get_ninfo(self): + try: + return self.ninfo + except AttributeError: + self.ninfo = self.new_ninfo() + return self.ninfo + + def new_binfo(self): + binfo = self.BuildInfo(self) + return binfo + + def get_binfo(self): + """ + Fetch a node's build information. + + node - the node whose sources will be collected + cache - alternate node to use for the signature cache + returns - the build signature + + This no longer handles the recursive descent of the + node's children's signatures. We expect that they're + already built and updated by someone else, if that's + what's wanted. + """ + try: + return self.binfo + except AttributeError: + pass + + binfo = self.new_binfo() + self.binfo = binfo + + executor = self.get_executor() + ignore_set = self.ignore_set + + if self.has_builder(): + binfo.bact = str(executor) + binfo.bactsig = SCons.Util.MD5signature(executor.get_contents()) + + if self._specific_sources: + sources = [] + for s in self.sources: + if s not in ignore_set: + sources.append(s) + else: + sources = executor.get_unignored_sources(self, self.ignore) + seen = set() + bsources = [] + bsourcesigs = [] + for s in sources: + if not s in seen: + seen.add(s) + bsources.append(s) + bsourcesigs.append(s.get_ninfo()) + binfo.bsources = bsources + binfo.bsourcesigs = bsourcesigs + + depends = self.depends + dependsigs = [] + for d in depends: + if d not in ignore_set: + dependsigs.append(d.get_ninfo()) + binfo.bdepends = depends + binfo.bdependsigs = dependsigs + + implicit = self.implicit or [] + implicitsigs = [] + for i in implicit: + if i not in ignore_set: + implicitsigs.append(i.get_ninfo()) + binfo.bimplicit = implicit + binfo.bimplicitsigs = implicitsigs + + return binfo + + def del_binfo(self): + """Delete the build info from this node.""" + try: + delattr(self, 'binfo') + except AttributeError: + pass + + def get_csig(self): + try: + return self.ninfo.csig + except AttributeError: + ninfo = self.get_ninfo() + ninfo.csig = SCons.Util.MD5signature(self.get_contents()) + return self.ninfo.csig + + def get_cachedir_csig(self): + return self.get_csig() + + def store_info(self): + """Make the build signature permanent (that is, store it in the + .sconsign file or equivalent).""" + pass + + def do_not_store_info(self): + pass + + def get_stored_info(self): + return None + + def get_stored_implicit(self): + """Fetch the stored implicit dependencies""" + return None + + # + # + # + + def set_precious(self, precious = 1): + """Set the Node's precious value.""" + self.precious = precious + + def set_noclean(self, noclean = 1): + """Set the Node's noclean value.""" + # Make sure noclean is an integer so the --debug=stree + # output in Util.py can use it as an index. + self.noclean = noclean and 1 or 0 + + def set_nocache(self, nocache = 1): + """Set the Node's nocache value.""" + # Make sure nocache is an integer so the --debug=stree + # output in Util.py can use it as an index. + self.nocache = nocache and 1 or 0 + + def set_always_build(self, always_build = 1): + """Set the Node's always_build value.""" + self.always_build = always_build + + def exists(self): + """Does this node exists?""" + # All node exist by default: + return 1 + + def rexists(self): + """Does this node exist locally or in a repositiory?""" + # There are no repositories by default: + return self.exists() + + def missing(self): + return not self.is_derived() and \ + not self.linked and \ + not self.rexists() + + def remove(self): + """Remove this Node: no-op by default.""" + return None + + def add_dependency(self, depend): + """Adds dependencies.""" + try: + self._add_child(self.depends, self.depends_set, depend) + except TypeError, e: + e = e.args[0] + if SCons.Util.is_List(e): + s = map(str, e) + else: + s = str(e) + raise SCons.Errors.UserError("attempted to add a non-Node dependency to %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e))) + + def add_prerequisite(self, prerequisite): + """Adds prerequisites""" + self.prerequisites.extend(prerequisite) + self._children_reset() + + def add_ignore(self, depend): + """Adds dependencies to ignore.""" + try: + self._add_child(self.ignore, self.ignore_set, depend) + except TypeError, e: + e = e.args[0] + if SCons.Util.is_List(e): + s = map(str, e) + else: + s = str(e) + raise SCons.Errors.UserError("attempted to ignore a non-Node dependency of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e))) + + def add_source(self, source): + """Adds sources.""" + if self._specific_sources: + return + try: + self._add_child(self.sources, self.sources_set, source) + except TypeError, e: + e = e.args[0] + if SCons.Util.is_List(e): + s = map(str, e) + else: + s = str(e) + raise SCons.Errors.UserError("attempted to add a non-Node as source of %s:\n\t%s is a %s, not a Node" % (str(self), s, type(e))) + + def _add_child(self, collection, set, child): + """Adds 'child' to 'collection', first checking 'set' to see if it's + already present.""" + #if type(child) is not type([]): + # child = [child] + #for c in child: + # if not isinstance(c, Node): + # raise TypeError, c + added = None + for c in child: + if c not in set: + set.add(c) + collection.append(c) + added = 1 + if added: + self._children_reset() + + def set_specific_source(self, source): + self.add_source(source) + self._specific_sources = True + + def add_wkid(self, wkid): + """Add a node to the list of kids waiting to be evaluated""" + if self.wkids is not None: + self.wkids.append(wkid) + + def _children_reset(self): + self.clear_memoized_values() + # We need to let the Executor clear out any calculated + # build info that it's cached so we can re-calculate it. + self.executor_cleanup() + + memoizer_counters.append(SCons.Memoize.CountValue('_children_get')) + + def _children_get(self): + try: + return self._memo['children_get'] + except KeyError: + pass + + # The return list may contain duplicate Nodes, especially in + # source trees where there are a lot of repeated #includes + # of a tangle of .h files. Profiling shows, however, that + # eliminating the duplicates with a brute-force approach that + # preserves the order (that is, something like: + # + # u = [] + # for n in list: + # if n not in u: + # u.append(n)" + # + # takes more cycles than just letting the underlying methods + # hand back cached values if a Node's information is requested + # multiple times. (Other methods of removing duplicates, like + # using dictionary keys, lose the order, and the only ordered + # dictionary patterns I found all ended up using "not in" + # internally anyway...) + if self.ignore_set: + if self.implicit is None: + iter = chain(self.sources,self.depends) + else: + iter = chain(self.sources, self.depends, self.implicit) + + children = [] + for i in iter: + if i not in self.ignore_set: + children.append(i) + else: + if self.implicit is None: + children = self.sources + self.depends + else: + children = self.sources + self.depends + self.implicit + + self._memo['children_get'] = children + return children + + def all_children(self, scan=1): + """Return a list of all the node's direct children.""" + if scan: + self.scan() + + # The return list may contain duplicate Nodes, especially in + # source trees where there are a lot of repeated #includes + # of a tangle of .h files. Profiling shows, however, that + # eliminating the duplicates with a brute-force approach that + # preserves the order (that is, something like: + # + # u = [] + # for n in list: + # if n not in u: + # u.append(n)" + # + # takes more cycles than just letting the underlying methods + # hand back cached values if a Node's information is requested + # multiple times. (Other methods of removing duplicates, like + # using dictionary keys, lose the order, and the only ordered + # dictionary patterns I found all ended up using "not in" + # internally anyway...) + if self.implicit is None: + return self.sources + self.depends + else: + return self.sources + self.depends + self.implicit + + def children(self, scan=1): + """Return a list of the node's direct children, minus those + that are ignored by this node.""" + if scan: + self.scan() + return self._children_get() + + def set_state(self, state): + self.state = state + + def get_state(self): + return self.state + + def state_has_changed(self, target, prev_ni): + return (self.state != SCons.Node.up_to_date) + + def get_env(self): + env = self.env + if not env: + import SCons.Defaults + env = SCons.Defaults.DefaultEnvironment() + return env + + def changed_since_last_build(self, target, prev_ni): + """ + + Must be overridden in a specific subclass to return True if this + Node (a dependency) has changed since the last time it was used + to build the specified target. prev_ni is this Node's state (for + example, its file timestamp, length, maybe content signature) + as of the last time the target was built. + + Note that this method is called through the dependency, not the + target, because a dependency Node must be able to use its own + logic to decide if it changed. For example, File Nodes need to + obey if we're configured to use timestamps, but Python Value Nodes + never use timestamps and always use the content. If this method + were called through the target, then each Node's implementation + of this method would have to have more complicated logic to + handle all the different Node types on which it might depend. + """ + raise NotImplementedError + + def Decider(self, function): + SCons.Util.AddMethod(self, function, 'changed_since_last_build') + + def changed(self, node=None): + """ + Returns if the node is up-to-date with respect to the BuildInfo + stored last time it was built. The default behavior is to compare + it against our own previously stored BuildInfo, but the stored + BuildInfo from another Node (typically one in a Repository) + can be used instead. + + Note that we now *always* check every dependency. We used to + short-circuit the check by returning as soon as we detected + any difference, but we now rely on checking every dependency + to make sure that any necessary Node information (for example, + the content signature of an #included .h file) is updated. + """ + t = 0 + if t: Trace('changed(%s [%s], %s)' % (self, classname(self), node)) + if node is None: + node = self + + result = False + + bi = node.get_stored_info().binfo + then = bi.bsourcesigs + bi.bdependsigs + bi.bimplicitsigs + children = self.children() + + diff = len(children) - len(then) + if diff: + # The old and new dependency lists are different lengths. + # This always indicates that the Node must be rebuilt. + # We also extend the old dependency list with enough None + # entries to equal the new dependency list, for the benefit + # of the loop below that updates node information. + then.extend([None] * diff) + if t: Trace(': old %s new %s' % (len(then), len(children))) + result = True + + for child, prev_ni in izip(children, then): + if child.changed_since_last_build(self, prev_ni): + if t: Trace(': %s changed' % child) + result = True + + contents = self.get_executor().get_contents() + if self.has_builder(): + import SCons.Util + newsig = SCons.Util.MD5signature(contents) + if bi.bactsig != newsig: + if t: Trace(': bactsig %s != newsig %s' % (bi.bactsig, newsig)) + result = True + + if not result: + if t: Trace(': up to date') + + if t: Trace('\n') + + return result + + def is_up_to_date(self): + """Default check for whether the Node is current: unknown Node + subtypes are always out of date, so they will always get built.""" + return None + + def children_are_up_to_date(self): + """Alternate check for whether the Node is current: If all of + our children were up-to-date, then this Node was up-to-date, too. + + The SCons.Node.Alias and SCons.Node.Python.Value subclasses + rebind their current() method to this method.""" + # Allow the children to calculate their signatures. + self.binfo = self.get_binfo() + if self.always_build: + return None + state = 0 + for kid in self.children(None): + s = kid.get_state() + if s and (not state or s > state): + state = s + return (state == 0 or state == SCons.Node.up_to_date) + + def is_literal(self): + """Always pass the string representation of a Node to + the command interpreter literally.""" + return 1 + + def render_include_tree(self): + """ + Return a text representation, suitable for displaying to the + user, of the include tree for the sources of this node. + """ + if self.is_derived() and self.env: + env = self.get_build_env() + for s in self.sources: + scanner = self.get_source_scanner(s) + if scanner: + path = self.get_build_scanner_path(scanner) + else: + path = None + def f(node, env=env, scanner=scanner, path=path): + return node.get_found_includes(env, scanner, path) + return SCons.Util.render_tree(s, f, 1) + else: + return None + + def get_abspath(self): + """ + Return an absolute path to the Node. This will return simply + str(Node) by default, but for Node types that have a concept of + relative path, this might return something different. + """ + return str(self) + + def for_signature(self): + """ + Return a string representation of the Node that will always + be the same for this particular Node, no matter what. This + is by contrast to the __str__() method, which might, for + instance, return a relative path for a file Node. The purpose + of this method is to generate a value to be used in signature + calculation for the command line used to build a target, and + we use this method instead of str() to avoid unnecessary + rebuilds. This method does not need to return something that + would actually work in a command line; it can return any kind of + nonsense, so long as it does not change. + """ + return str(self) + + def get_string(self, for_signature): + """This is a convenience function designed primarily to be + used in command generators (i.e., CommandGeneratorActions or + Environment variables that are callable), which are called + with a for_signature argument that is nonzero if the command + generator is being called to generate a signature for the + command line, which determines if we should rebuild or not. + + Such command generators should use this method in preference + to str(Node) when converting a Node to a string, passing + in the for_signature parameter, such that we will call + Node.for_signature() or str(Node) properly, depending on whether + we are calculating a signature or actually constructing a + command line.""" + if for_signature: + return self.for_signature() + return str(self) + + def get_subst_proxy(self): + """ + This method is expected to return an object that will function + exactly like this Node, except that it implements any additional + special features that we would like to be in effect for + Environment variable substitution. The principle use is that + some Nodes would like to implement a __getattr__() method, + but putting that in the Node type itself has a tendency to kill + performance. We instead put it in a proxy and return it from + this method. It is legal for this method to return self + if no new functionality is needed for Environment substitution. + """ + return self + + def explain(self): + if not self.exists(): + return "building `%s' because it doesn't exist\n" % self + + if self.always_build: + return "rebuilding `%s' because AlwaysBuild() is specified\n" % self + + old = self.get_stored_info() + if old is None: + return None + + old = old.binfo + old.prepare_dependencies() + + try: + old_bkids = old.bsources + old.bdepends + old.bimplicit + old_bkidsigs = old.bsourcesigs + old.bdependsigs + old.bimplicitsigs + except AttributeError: + return "Cannot explain why `%s' is being rebuilt: No previous build information found\n" % self + + new = self.get_binfo() + + new_bkids = new.bsources + new.bdepends + new.bimplicit + new_bkidsigs = new.bsourcesigs + new.bdependsigs + new.bimplicitsigs + + osig = dict(izip(old_bkids, old_bkidsigs)) + nsig = dict(izip(new_bkids, new_bkidsigs)) + + # The sources and dependencies we'll want to report are all stored + # as relative paths to this target's directory, but we want to + # report them relative to the top-level SConstruct directory, + # so we only print them after running them through this lambda + # to turn them into the right relative Node and then return + # its string. + def stringify( s, E=self.dir.Entry ) : + if hasattr( s, 'dir' ) : + return str(E(s)) + return str(s) + + lines = [] + + removed = filter(lambda x, nk=new_bkids: not x in nk, old_bkids) + if removed: + removed = map(stringify, removed) + fmt = "`%s' is no longer a dependency\n" + lines.extend(map(lambda s, fmt=fmt: fmt % s, removed)) + + for k in new_bkids: + if not k in old_bkids: + lines.append("`%s' is a new dependency\n" % stringify(k)) + elif k.changed_since_last_build(self, osig[k]): + lines.append("`%s' changed\n" % stringify(k)) + + if len(lines) == 0 and old_bkids != new_bkids: + lines.append("the dependency order changed:\n" + + "%sold: %s\n" % (' '*15, map(stringify, old_bkids)) + + "%snew: %s\n" % (' '*15, map(stringify, new_bkids))) + + if len(lines) == 0: + def fmt_with_title(title, strlines): + lines = string.split(strlines, '\n') + sep = '\n' + ' '*(15 + len(title)) + return ' '*15 + title + string.join(lines, sep) + '\n' + if old.bactsig != new.bactsig: + if old.bact == new.bact: + lines.append("the contents of the build action changed\n" + + fmt_with_title('action: ', new.bact)) + else: + lines.append("the build action changed:\n" + + fmt_with_title('old: ', old.bact) + + fmt_with_title('new: ', new.bact)) + + if len(lines) == 0: + return "rebuilding `%s' for unknown reasons\n" % self + + preamble = "rebuilding `%s' because" % self + if len(lines) == 1: + return "%s %s" % (preamble, lines[0]) + else: + lines = ["%s:\n" % preamble] + lines + return string.join(lines, ' '*11) + +try: + [].extend(UserList.UserList([])) +except TypeError: + # Python 1.5.2 doesn't allow a list to be extended by list-like + # objects (such as UserList instances), so just punt and use + # real lists. + def NodeList(l): + return l +else: + class NodeList(UserList.UserList): + def __str__(self): + return str(map(str, self.data)) + +def get_children(node, parent): return node.children() +def ignore_cycle(node, stack): pass +def do_nothing(node, parent): pass + +class Walker: + """An iterator for walking a Node tree. + + This is depth-first, children are visited before the parent. + The Walker object can be initialized with any node, and + returns the next node on the descent with each next() call. + 'kids_func' is an optional function that will be called to + get the children of a node instead of calling 'children'. + 'cycle_func' is an optional function that will be called + when a cycle is detected. + + This class does not get caught in node cycles caused, for example, + by C header file include loops. + """ + def __init__(self, node, kids_func=get_children, + cycle_func=ignore_cycle, + eval_func=do_nothing): + self.kids_func = kids_func + self.cycle_func = cycle_func + self.eval_func = eval_func + node.wkids = copy.copy(kids_func(node, None)) + self.stack = [node] + self.history = {} # used to efficiently detect and avoid cycles + self.history[node] = None + + def next(self): + """Return the next node for this walk of the tree. + + This function is intentionally iterative, not recursive, + to sidestep any issues of stack size limitations. + """ + + while self.stack: + if self.stack[-1].wkids: + node = self.stack[-1].wkids.pop(0) + if not self.stack[-1].wkids: + self.stack[-1].wkids = None + if self.history.has_key(node): + self.cycle_func(node, self.stack) + else: + node.wkids = copy.copy(self.kids_func(node, self.stack[-1])) + self.stack.append(node) + self.history[node] = None + else: + node = self.stack.pop() + del self.history[node] + if node: + if self.stack: + parent = self.stack[-1] + else: + parent = None + self.eval_func(node, parent) + return node + return None + + def is_done(self): + return not self.stack + + +arg2nodes_lookups = [] + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Options/BoolOption.py b/src/engine/SCons/Options/BoolOption.py new file mode 100644 index 0000000..53cd532 --- /dev/null +++ b/src/engine/SCons/Options/BoolOption.py @@ -0,0 +1,50 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Options/BoolOption.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Options module hierarchy + +This is for backwards compatibility. The new equivalent is the Variables/ +class hierarchy. These will have deprecation warnings added (some day), +and will then be removed entirely (some day). +""" + +import SCons.Variables +import SCons.Warnings + +warned = False + +def BoolOption(*args, **kw): + global warned + if not warned: + msg = "The BoolOption() function is deprecated; use the BoolVariable() function instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg) + warned = True + return apply(SCons.Variables.BoolVariable, args, kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Options/EnumOption.py b/src/engine/SCons/Options/EnumOption.py new file mode 100644 index 0000000..6e10139 --- /dev/null +++ b/src/engine/SCons/Options/EnumOption.py @@ -0,0 +1,50 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Options/EnumOption.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Options module hierarchy + +This is for backwards compatibility. The new equivalent is the Variables/ +class hierarchy. These will have deprecation warnings added (some day), +and will then be removed entirely (some day). +""" + +import SCons.Variables +import SCons.Warnings + +warned = False + +def EnumOption(*args, **kw): + global warned + if not warned: + msg = "The EnumOption() function is deprecated; use the EnumVariable() function instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg) + warned = True + return apply(SCons.Variables.EnumVariable, args, kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Options/ListOption.py b/src/engine/SCons/Options/ListOption.py new file mode 100644 index 0000000..146e289 --- /dev/null +++ b/src/engine/SCons/Options/ListOption.py @@ -0,0 +1,50 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Options/ListOption.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Options module hierarchy + +This is for backwards compatibility. The new equivalent is the Variables/ +class hierarchy. These will have deprecation warnings added (some day), +and will then be removed entirely (some day). +""" + +import SCons.Variables +import SCons.Warnings + +warned = False + +def ListOption(*args, **kw): + global warned + if not warned: + msg = "The ListOption() function is deprecated; use the ListVariable() function instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg) + warned = True + return apply(SCons.Variables.ListVariable, args, kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Options/PackageOption.py b/src/engine/SCons/Options/PackageOption.py new file mode 100644 index 0000000..7d04053 --- /dev/null +++ b/src/engine/SCons/Options/PackageOption.py @@ -0,0 +1,50 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Options/PackageOption.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Options module hierarchy + +This is for backwards compatibility. The new equivalent is the Variables/ +class hierarchy. These will have deprecation warnings added (some day), +and will then be removed entirely (some day). +""" + +import SCons.Variables +import SCons.Warnings + +warned = False + +def PackageOption(*args, **kw): + global warned + if not warned: + msg = "The PackageOption() function is deprecated; use the PackageVariable() function instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg) + warned = True + return apply(SCons.Variables.PackageVariable, args, kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Options/PathOption.py b/src/engine/SCons/Options/PathOption.py new file mode 100644 index 0000000..91653d4 --- /dev/null +++ b/src/engine/SCons/Options/PathOption.py @@ -0,0 +1,76 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Options/PathOption.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Options module hierarchy + +This is for backwards compatibility. The new equivalent is the Variables/ +class hierarchy. These will have deprecation warnings added (some day), +and will then be removed entirely (some day). +""" + +import SCons.Variables +import SCons.Warnings + +warned = False + +class _PathOptionClass: + def warn(self): + global warned + if not warned: + msg = "The PathOption() function is deprecated; use the PathVariable() function instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg) + warned = True + + def __call__(self, *args, **kw): + self.warn() + return apply(SCons.Variables.PathVariable, args, kw) + + def PathAccept(self, *args, **kw): + self.warn() + return apply(SCons.Variables.PathVariable.PathAccept, args, kw) + + def PathIsDir(self, *args, **kw): + self.warn() + return apply(SCons.Variables.PathVariable.PathIsDir, args, kw) + + def PathIsDirCreate(self, *args, **kw): + self.warn() + return apply(SCons.Variables.PathVariable.PathIsDirCreate, args, kw) + + def PathIsFile(self, *args, **kw): + self.warn() + return apply(SCons.Variables.PathVariable.PathIsFile, args, kw) + + def PathExists(self, *args, **kw): + self.warn() + return apply(SCons.Variables.PathVariable.PathExists, args, kw) + +PathOption = _PathOptionClass() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Options/__init__.py b/src/engine/SCons/Options/__init__.py new file mode 100644 index 0000000..eb18dbf --- /dev/null +++ b/src/engine/SCons/Options/__init__.py @@ -0,0 +1,74 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Options/__init__.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Options module hierarchy + +This is for backwards compatibility. The new equivalent is the Variables/ +class hierarchy. These will have deprecation warnings added (some day), +and will then be removed entirely (some day). +""" + +import SCons.Variables +import SCons.Warnings + +from BoolOption import BoolOption # okay +from EnumOption import EnumOption # okay +from ListOption import ListOption # naja +from PackageOption import PackageOption # naja +from PathOption import PathOption # okay + +warned = False + +class Options(SCons.Variables.Variables): + def __init__(self, *args, **kw): + global warned + if not warned: + msg = "The Options class is deprecated; use the Variables class instead." + SCons.Warnings.warn(SCons.Warnings.DeprecatedOptionsWarning, msg) + warned = True + apply(SCons.Variables.Variables.__init__, + (self,) + args, + kw) + + def AddOptions(self, *args, **kw): + return apply(SCons.Variables.Variables.AddVariables, + (self,) + args, + kw) + + def UnknownOptions(self, *args, **kw): + return apply(SCons.Variables.Variables.UnknownVariables, + (self,) + args, + kw) + + def FormatOptionHelpText(self, *args, **kw): + return apply(SCons.Variables.Variables.FormatVariableHelpText, + (self,) + args, + kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/PathList.py b/src/engine/SCons/PathList.py new file mode 100644 index 0000000..fb54fe1 --- /dev/null +++ b/src/engine/SCons/PathList.py @@ -0,0 +1,232 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/PathList.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """SCons.PathList + +A module for handling lists of directory paths (the sort of things +that get set as CPPPATH, LIBPATH, etc.) with as much caching of data and +efficiency as we can while still keeping the evaluation delayed so that we +Do the Right Thing (almost) regardless of how the variable is specified. + +""" + +import os +import string + +import SCons.Memoize +import SCons.Node +import SCons.Util + +# +# Variables to specify the different types of entries in a PathList object: +# + +TYPE_STRING_NO_SUBST = 0 # string with no '$' +TYPE_STRING_SUBST = 1 # string containing '$' +TYPE_OBJECT = 2 # other object + +def node_conv(obj): + """ + This is the "string conversion" routine that we have our substitutions + use to return Nodes, not strings. This relies on the fact that an + EntryProxy object has a get() method that returns the underlying + Node that it wraps, which is a bit of architectural dependence + that we might need to break or modify in the future in response to + additional requirements. + """ + try: + get = obj.get + except AttributeError: + if isinstance(obj, SCons.Node.Node) or SCons.Util.is_Sequence( obj ): + result = obj + else: + result = str(obj) + else: + result = get() + return result + +class _PathList: + """ + An actual PathList object. + """ + def __init__(self, pathlist): + """ + Initializes a PathList object, canonicalizing the input and + pre-processing it for quicker substitution later. + + The stored representation of the PathList is a list of tuples + containing (type, value), where the "type" is one of the TYPE_* + variables defined above. We distinguish between: + + strings that contain no '$' and therefore need no + delayed-evaluation string substitution (we expect that there + will be many of these and that we therefore get a pretty + big win from avoiding string substitution) + + strings that contain '$' and therefore need substitution + (the hard case is things like '${TARGET.dir}/include', + which require re-evaluation for every target + source) + + other objects (which may be something like an EntryProxy + that needs a method called to return a Node) + + Pre-identifying the type of each element in the PathList up-front + and storing the type in the list of tuples is intended to reduce + the amount of calculation when we actually do the substitution + over and over for each target. + """ + if SCons.Util.is_String(pathlist): + pathlist = string.split(pathlist, os.pathsep) + elif not SCons.Util.is_Sequence(pathlist): + pathlist = [pathlist] + + pl = [] + for p in pathlist: + try: + index = string.find(p, '$') + except (AttributeError, TypeError): + type = TYPE_OBJECT + else: + if index == -1: + type = TYPE_STRING_NO_SUBST + else: + type = TYPE_STRING_SUBST + pl.append((type, p)) + + self.pathlist = tuple(pl) + + def __len__(self): return len(self.pathlist) + + def __getitem__(self, i): return self.pathlist[i] + + def subst_path(self, env, target, source): + """ + Performs construction variable substitution on a pre-digested + PathList for a specific target and source. + """ + result = [] + for type, value in self.pathlist: + if type == TYPE_STRING_SUBST: + value = env.subst(value, target=target, source=source, + conv=node_conv) + if SCons.Util.is_Sequence(value): + result.extend(value) + continue + + elif type == TYPE_OBJECT: + value = node_conv(value) + if value: + result.append(value) + return tuple(result) + + +class PathListCache: + """ + A class to handle caching of PathList lookups. + + This class gets instantiated once and then deleted from the namespace, + so it's used as a Singleton (although we don't enforce that in the + usual Pythonic ways). We could have just made the cache a dictionary + in the module namespace, but putting it in this class allows us to + use the same Memoizer pattern that we use elsewhere to count cache + hits and misses, which is very valuable. + + Lookup keys in the cache are computed by the _PathList_key() method. + Cache lookup should be quick, so we don't spend cycles canonicalizing + all forms of the same lookup key. For example, 'x:y' and ['x', + 'y'] logically represent the same list, but we don't bother to + split string representations and treat those two equivalently. + (Note, however, that we do, treat lists and tuples the same.) + + The main type of duplication we're trying to catch will come from + looking up the same path list from two different clones of the + same construction environment. That is, given + + env2 = env1.Clone() + + both env1 and env2 will have the same CPPPATH value, and we can + cheaply avoid re-parsing both values of CPPPATH by using the + common value from this cache. + """ + if SCons.Memoize.use_memoizer: + __metaclass__ = SCons.Memoize.Memoized_Metaclass + + memoizer_counters = [] + + def __init__(self): + self._memo = {} + + def _PathList_key(self, pathlist): + """ + Returns the key for memoization of PathLists. + + Note that we want this to be pretty quick, so we don't completely + canonicalize all forms of the same list. For example, + 'dir1:$ROOT/dir2' and ['$ROOT/dir1', 'dir'] may logically + represent the same list if you're executing from $ROOT, but + we're not going to bother splitting strings into path elements, + or massaging strings into Nodes, to identify that equivalence. + We just want to eliminate obvious redundancy from the normal + case of re-using exactly the same cloned value for a path. + """ + if SCons.Util.is_Sequence(pathlist): + pathlist = tuple(SCons.Util.flatten(pathlist)) + return pathlist + + memoizer_counters.append(SCons.Memoize.CountDict('PathList', _PathList_key)) + + def PathList(self, pathlist): + """ + Returns the cached _PathList object for the specified pathlist, + creating and caching a new object as necessary. + """ + pathlist = self._PathList_key(pathlist) + try: + memo_dict = self._memo['PathList'] + except KeyError: + memo_dict = {} + self._memo['PathList'] = memo_dict + else: + try: + return memo_dict[pathlist] + except KeyError: + pass + + result = _PathList(pathlist) + + memo_dict[pathlist] = result + + return result + +PathList = PathListCache().PathList + + +del PathListCache + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/PathListTests.py b/src/engine/SCons/PathListTests.py new file mode 100644 index 0000000..b6ec142 --- /dev/null +++ b/src/engine/SCons/PathListTests.py @@ -0,0 +1,170 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/PathListTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.PathList + + +class subst_pathTestCase(unittest.TestCase): + + def setUp(self): + + class FakeEnvironment: + def __init__(self, **kw): + self.kw = kw + def subst(self, s, target=None, source=None, conv=lambda x: x): + if s[0] == '$': + s = s[1:] + if s == 'target': + s = target + elif s == 'source': + s = source + else: + s = self.kw[s] + return s + + self.env = FakeEnvironment(AAA = 'aaa', NULL = '') + + def test_node(self): + """Test the subst_path() method on a Node + """ + + import SCons.Node + + class A: + pass + + n = SCons.Node.Node() + + pl = SCons.PathList.PathList((n,)) + + result = pl.subst_path(self.env, 'y', 'z') + + assert result == (n,), result + + def test_object(self): + """Test the subst_path() method on a non-Node object + """ + + class A: + def __str__(self): + return '' + + a = A() + + pl = SCons.PathList.PathList((a,)) + + result = pl.subst_path(self.env, 'y', 'z') + + assert result == ('',), result + + def test_object_get(self): + """Test the subst_path() method on an object with a get() method + """ + + class B: + def get(self): + return 'b' + + b = B() + + pl = SCons.PathList.PathList((b,)) + + result = pl.subst_path(self.env, 'y', 'z') + + assert result == ('b',), result + + def test_string(self): + """Test the subst_path() method on a non-substitution string + """ + + self.env.subst = lambda s, target, source, conv: 'NOT THIS STRING' + + pl = SCons.PathList.PathList(('x')) + + result = pl.subst_path(self.env, 'y', 'z') + + assert result == ('x',), result + + def test_subst(self): + """Test the subst_path() method on substitution strings + """ + + pl = SCons.PathList.PathList(('$AAA', '$NULL')) + + result = pl.subst_path(self.env, 'y', 'z') + + assert result == ('aaa',), result + + +class PathListCacheTestCase(unittest.TestCase): + + def test_no_PathListCache(self): + """Make sure the PathListCache class is not visible + """ + try: + SCons.PathList.PathListCache + except AttributeError: + pass + else: + self.fail("Found PathListCache unexpectedly\n") + + +class PathListTestCase(unittest.TestCase): + + def test_PathList(self): + """Test the PathList() entry point + """ + + x1 = SCons.PathList.PathList(('x',)) + x2 = SCons.PathList.PathList(['x',]) + + assert x1 is x2, (x1, x2) + + x3 = SCons.PathList.PathList('x') + + assert not x1 is x3, (x1, x3) + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + subst_pathTestCase, + PathListCacheTestCase, + PathListTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/PlatformTests.py b/src/engine/SCons/Platform/PlatformTests.py new file mode 100644 index 0000000..e1e19cd --- /dev/null +++ b/src/engine/SCons/Platform/PlatformTests.py @@ -0,0 +1,126 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/PlatformTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Platform +import UserDict + +class Environment(UserDict.UserDict): + def Detect(self, cmd): + return cmd + def AppendENVPath(self, key, value): + pass + +class PlatformTestCase(unittest.TestCase): + def test_Platform(self): + """Test the Platform() function""" + p = SCons.Platform.Platform('cygwin') + assert str(p) == 'cygwin', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '.exe', env + assert env['LIBSUFFIX'] == '.a', env + assert env['SHELL'] == 'sh', env + + p = SCons.Platform.Platform('os2') + assert str(p) == 'os2', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '.exe', env + assert env['LIBSUFFIX'] == '.lib', env + + p = SCons.Platform.Platform('posix') + assert str(p) == 'posix', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '', env + assert env['LIBSUFFIX'] == '.a', env + assert env['SHELL'] == 'sh', env + + p = SCons.Platform.Platform('irix') + assert str(p) == 'irix', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '', env + assert env['LIBSUFFIX'] == '.a', env + assert env['SHELL'] == 'sh', env + + p = SCons.Platform.Platform('aix') + assert str(p) == 'aix', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '', env + assert env['LIBSUFFIX'] == '.a', env + assert env['SHELL'] == 'sh', env + + p = SCons.Platform.Platform('sunos') + assert str(p) == 'sunos', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '', env + assert env['LIBSUFFIX'] == '.a', env + assert env['SHELL'] == 'sh', env + + p = SCons.Platform.Platform('hpux') + assert str(p) == 'hpux', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '', env + assert env['LIBSUFFIX'] == '.a', env + assert env['SHELL'] == 'sh', env + + p = SCons.Platform.Platform('win32') + assert str(p) == 'win32', p + env = Environment() + p(env) + assert env['PROGSUFFIX'] == '.exe', env + assert env['LIBSUFFIX'] == '.lib', env + assert str + + try: + p = SCons.Platform.Platform('_does_not_exist_') + except SCons.Errors.UserError: + pass + else: + raise + + env = Environment() + SCons.Platform.Platform()(env) + assert env != {}, env + + +if __name__ == "__main__": + suite = unittest.makeSuite(PlatformTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/__init__.py b/src/engine/SCons/Platform/__init__.py new file mode 100644 index 0000000..671e929 --- /dev/null +++ b/src/engine/SCons/Platform/__init__.py @@ -0,0 +1,236 @@ +"""SCons.Platform + +SCons platform selection. + +This looks for modules that define a callable object that can modify a +construction environment as appropriate for a given platform. + +Note that we take a more simplistic view of "platform" than Python does. +We're looking for a single string that determines a set of +tool-independent variables with which to initialize a construction +environment. Consequently, we'll examine both sys.platform and os.name +(and anything else that might come in to play) in order to return some +specification which is unique enough for our purposes. + +Note that because this subsysem just *selects* a callable that can +modify a construction environment, it's possible for people to define +their own "platform specification" in an arbitrary callable function. +No one needs to use or tie in to this subsystem in order to roll +their own platform definition. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/__init__.py 4577 2009/12/27 19:44:43 scons" + +import SCons.compat + +import imp +import os +import string +import sys +import tempfile + +import SCons.Errors +import SCons.Subst +import SCons.Tool + +def platform_default(): + """Return the platform string for our execution environment. + + The returned value should map to one of the SCons/Platform/*.py + files. Since we're architecture independent, though, we don't + care about the machine architecture. + """ + osname = os.name + if osname == 'java': + osname = os._osType + if osname == 'posix': + if sys.platform == 'cygwin': + return 'cygwin' + elif string.find(sys.platform, 'irix') != -1: + return 'irix' + elif string.find(sys.platform, 'sunos') != -1: + return 'sunos' + elif string.find(sys.platform, 'hp-ux') != -1: + return 'hpux' + elif string.find(sys.platform, 'aix') != -1: + return 'aix' + elif string.find(sys.platform, 'darwin') != -1: + return 'darwin' + else: + return 'posix' + elif os.name == 'os2': + return 'os2' + else: + return sys.platform + +def platform_module(name = platform_default()): + """Return the imported module for the platform. + + This looks for a module name that matches the specified argument. + If the name is unspecified, we fetch the appropriate default for + our execution environment. + """ + full_name = 'SCons.Platform.' + name + if not sys.modules.has_key(full_name): + if os.name == 'java': + eval(full_name) + else: + try: + file, path, desc = imp.find_module(name, + sys.modules['SCons.Platform'].__path__) + try: + mod = imp.load_module(full_name, file, path, desc) + finally: + if file: + file.close() + except ImportError: + try: + import zipimport + importer = zipimport.zipimporter( sys.modules['SCons.Platform'].__path__[0] ) + mod = importer.load_module(full_name) + except ImportError: + raise SCons.Errors.UserError, "No platform named '%s'" % name + setattr(SCons.Platform, name, mod) + return sys.modules[full_name] + +def DefaultToolList(platform, env): + """Select a default tool list for the specified platform. + """ + return SCons.Tool.tool_list(platform, env) + +class PlatformSpec: + def __init__(self, name): + self.name = name + + def __str__(self): + return self.name + +class TempFileMunge: + """A callable class. You can set an Environment variable to this, + then call it with a string argument, then it will perform temporary + file substitution on it. This is used to circumvent the long command + line limitation. + + Example usage: + env["TEMPFILE"] = TempFileMunge + env["LINKCOM"] = "${TEMPFILE('$LINK $TARGET $SOURCES')}" + + By default, the name of the temporary file used begins with a + prefix of '@'. This may be configred for other tool chains by + setting '$TEMPFILEPREFIX'. + + env["TEMPFILEPREFIX"] = '-@' # diab compiler + env["TEMPFILEPREFIX"] = '-via' # arm tool chain + """ + def __init__(self, cmd): + self.cmd = cmd + + def __call__(self, target, source, env, for_signature): + if for_signature: + # If we're being called for signature calculation, it's + # because we're being called by the string expansion in + # Subst.py, which has the logic to strip any $( $) that + # may be in the command line we squirreled away. So we + # just return the raw command line and let the upper + # string substitution layers do their thing. + return self.cmd + + # Now we're actually being called because someone is actually + # going to try to execute the command, so we have to do our + # own expansion. + cmd = env.subst_list(self.cmd, SCons.Subst.SUBST_CMD, target, source)[0] + try: + maxline = int(env.subst('$MAXLINELENGTH')) + except ValueError: + maxline = 2048 + + if (reduce(lambda x, y: x + len(y), cmd, 0) + len(cmd)) <= maxline: + return self.cmd + + # We do a normpath because mktemp() has what appears to be + # a bug in Windows that will use a forward slash as a path + # delimiter. Windows's link mistakes that for a command line + # switch and barfs. + # + # We use the .lnk suffix for the benefit of the Phar Lap + # linkloc linker, which likes to append an .lnk suffix if + # none is given. + (fd, tmp) = tempfile.mkstemp('.lnk', text=True) + native_tmp = SCons.Util.get_native_path(os.path.normpath(tmp)) + + if env['SHELL'] and env['SHELL'] == 'sh': + # The sh shell will try to escape the backslashes in the + # path, so unescape them. + native_tmp = string.replace(native_tmp, '\\', r'\\\\') + # In Cygwin, we want to use rm to delete the temporary + # file, because del does not exist in the sh shell. + rm = env.Detect('rm') or 'del' + else: + # Don't use 'rm' if the shell is not sh, because rm won't + # work with the Windows shells (cmd.exe or command.com) or + # Windows path names. + rm = 'del' + + prefix = env.subst('$TEMPFILEPREFIX') + if not prefix: + prefix = '@' + + args = map(SCons.Subst.quote_spaces, cmd[1:]) + os.write(fd, string.join(args, " ") + "\n") + os.close(fd) + # XXX Using the SCons.Action.print_actions value directly + # like this is bogus, but expedient. This class should + # really be rewritten as an Action that defines the + # __call__() and strfunction() methods and lets the + # normal action-execution logic handle whether or not to + # print/execute the action. The problem, though, is all + # of that is decided before we execute this method as + # part of expanding the $TEMPFILE construction variable. + # Consequently, refactoring this will have to wait until + # we get more flexible with allowing Actions to exist + # independently and get strung together arbitrarily like + # Ant tasks. In the meantime, it's going to be more + # user-friendly to not let obsession with architectural + # purity get in the way of just being helpful, so we'll + # reach into SCons.Action directly. + if SCons.Action.print_actions: + print("Using tempfile "+native_tmp+" for command line:\n"+ + str(cmd[0]) + " " + string.join(args," ")) + return [ cmd[0], prefix + native_tmp + '\n' + rm, native_tmp ] + +def Platform(name = platform_default()): + """Select a canned Platform specification. + """ + module = platform_module(name) + spec = PlatformSpec(name) + spec.__call__ = module.generate + return spec + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/__init__.xml b/src/engine/SCons/Platform/__init__.xml new file mode 100644 index 0000000..dc5a415 --- /dev/null +++ b/src/engine/SCons/Platform/__init__.xml @@ -0,0 +1,183 @@ + + + +A function that will be called to escape shell special characters in +command lines. The function should take one argument: the command line +string to escape; and should return the escaped command line. + + + + + +The prefix used for (static) library file names. +A default value is set for each platform +(posix, win32, os2, etc.), +but the value is overridden by individual tools +(ar, mslib, sgiar, sunar, tlib, etc.) +to reflect the names of the libraries they create. + + + + + +A list of all legal prefixes for library file names. +When searching for library dependencies, +SCons will look for files with these prefixes, +the base library name, +and suffixes in the &cv-LIBSUFFIXES; list. + + + + + +The suffix used for (static) library file names. +A default value is set for each platform +(posix, win32, os2, etc.), +but the value is overridden by individual tools +(ar, mslib, sgiar, sunar, tlib, etc.) +to reflect the names of the libraries they create. + + + + + +A list of all legal suffixes for library file names. +When searching for library dependencies, +SCons will look for files with prefixes, in the &cv-LIBPREFIXES; list, +the base library name, +and these suffixes. + + + + + +The prefix used for (static) object file names. + + + + + +The suffix used for (static) object file names. + + + + + +The name of the platform used to create the Environment. If no platform is +specified when the Environment is created, +&scons; +autodetects the platform. + + +env = Environment(tools = []) +if env['PLATFORM'] == 'cygwin': + Tool('mingw')(env) +else: + Tool('msvc')(env) + + + + + + + The name of the host operating system used to create the Environment. + If a platform is specified when creating the Environment, then + that Platform's logic will handle setting this value. + This value is immutable, and should not be changed by the user after + the Environment is initialized. + Currently only set for Win32. + + + + + + The name of the host hardware architecture used to create the Environment. + If a platform is specified when creating the Environment, then + that Platform's logic will handle setting this value. + This value is immutable, and should not be changed by the user after + the Environment is initialized. + Currently only set for Win32. + + + + + + The name of the target operating system for the compiled objects + created by this Environment. + This defaults to the value of HOST_OS, and the user can override it. + Currently only set for Win32. + + + + + + The name of the target hardware architecture for the compiled objects + created by this Environment. + This defaults to the value of HOST_ARCH, and the user can override it. + Currently only set for Win32. + + + + + + +The prefix used for executable file names. + + + + + +The suffix used for executable file names. + + + + + +A string naming the shell program that will be passed to the +&cv-SPAWN; +function. +See the +&cv-SPAWN; +construction variable for more information. + + + + + +The prefix used for shared library file names. + + + + + +The suffix used for shared library file names. + + + + + +The prefix used for shared object file names. + + + + + +The suffix used for shared object file names. + + + + + +The prefix for a temporary file used +to execute lines longer than $MAXLINELENGTH. +The default is '@'. +This may be set for toolchains that use other values, +such as '-@' for the diab compiler +or '-via' for ARM toolchain. + + diff --git a/src/engine/SCons/Platform/aix.py b/src/engine/SCons/Platform/aix.py new file mode 100644 index 0000000..8d6b6e6 --- /dev/null +++ b/src/engine/SCons/Platform/aix.py @@ -0,0 +1,70 @@ +"""engine.SCons.Platform.aix + +Platform-specific initialization for IBM AIX systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/aix.py 4577 2009/12/27 19:44:43 scons" + +import os +import string + +import posix + +def get_xlc(env, xlc=None, xlc_r=None, packages=[]): + # Use the AIX package installer tool lslpp to figure out where a + # given xl* compiler is installed and what version it is. + xlcPath = None + xlcVersion = None + + if xlc is None: + xlc = env.get('CC', 'xlc') + if xlc_r is None: + xlc_r = xlc + '_r' + for package in packages: + cmd = "lslpp -fc " + package + " 2>/dev/null | egrep '" + xlc + "([^-_a-zA-Z0-9].*)?$'" + line = os.popen(cmd).readline() + if line: + v, p = string.split(line, ':')[1:3] + xlcVersion = string.split(v)[1] + xlcPath = string.split(p)[0] + xlcPath = xlcPath[:xlcPath.rindex('/')] + break + return (xlcPath, xlc, xlc_r, xlcVersion) + +def generate(env): + posix.generate(env) + #Based on AIX 5.2: ARG_MAX=24576 - 3000 for environment expansion + env['MAXLINELENGTH'] = 21576 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/cygwin.py b/src/engine/SCons/Platform/cygwin.py new file mode 100644 index 0000000..cf6ecce --- /dev/null +++ b/src/engine/SCons/Platform/cygwin.py @@ -0,0 +1,55 @@ +"""SCons.Platform.cygwin + +Platform-specific initialization for Cygwin systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/cygwin.py 4577 2009/12/27 19:44:43 scons" + +import posix +from SCons.Platform import TempFileMunge + +def generate(env): + posix.generate(env) + + env['PROGPREFIX'] = '' + env['PROGSUFFIX'] = '.exe' + env['SHLIBPREFIX'] = '' + env['SHLIBSUFFIX'] = '.dll' + env['LIBPREFIXES'] = [ '$LIBPREFIX', '$SHLIBPREFIX' ] + env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ] + env['TEMPFILE'] = TempFileMunge + env['TEMPFILEPREFIX'] = '@' + env['MAXLINELENGTH'] = 2048 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/darwin.py b/src/engine/SCons/Platform/darwin.py new file mode 100644 index 0000000..35f8fc8 --- /dev/null +++ b/src/engine/SCons/Platform/darwin.py @@ -0,0 +1,46 @@ +"""engine.SCons.Platform.darwin + +Platform-specific initialization for Mac OS X systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/darwin.py 4577 2009/12/27 19:44:43 scons" + +import posix + +def generate(env): + posix.generate(env) + env['SHLIBSUFFIX'] = '.dylib' + env['ENV']['PATH'] = env['ENV']['PATH'] + ':/sw/bin' + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/hpux.py b/src/engine/SCons/Platform/hpux.py new file mode 100644 index 0000000..2d55556 --- /dev/null +++ b/src/engine/SCons/Platform/hpux.py @@ -0,0 +1,46 @@ +"""engine.SCons.Platform.hpux + +Platform-specific initialization for HP-UX systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/hpux.py 4577 2009/12/27 19:44:43 scons" + +import posix + +def generate(env): + posix.generate(env) + #Based on HP-UX11i: ARG_MAX=2048000 - 3000 for environment expansion + env['MAXLINELENGTH'] = 2045000 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/irix.py b/src/engine/SCons/Platform/irix.py new file mode 100644 index 0000000..c6a4990 --- /dev/null +++ b/src/engine/SCons/Platform/irix.py @@ -0,0 +1,44 @@ +"""SCons.Platform.irix + +Platform-specific initialization for SGI IRIX systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/irix.py 4577 2009/12/27 19:44:43 scons" + +import posix + +def generate(env): + posix.generate(env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/os2.py b/src/engine/SCons/Platform/os2.py new file mode 100644 index 0000000..930a5a9 --- /dev/null +++ b/src/engine/SCons/Platform/os2.py @@ -0,0 +1,58 @@ +"""SCons.Platform.os2 + +Platform-specific initialization for OS/2 systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/os2.py 4577 2009/12/27 19:44:43 scons" +import win32 + +def generate(env): + if not env.has_key('ENV'): + env['ENV'] = {} + env['OBJPREFIX'] = '' + env['OBJSUFFIX'] = '.obj' + env['SHOBJPREFIX'] = '$OBJPREFIX' + env['SHOBJSUFFIX'] = '$OBJSUFFIX' + env['PROGPREFIX'] = '' + env['PROGSUFFIX'] = '.exe' + env['LIBPREFIX'] = '' + env['LIBSUFFIX'] = '.lib' + env['SHLIBPREFIX'] = '' + env['SHLIBSUFFIX'] = '.dll' + env['LIBPREFIXES'] = '$LIBPREFIX' + env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ] + env['HOST_OS'] = 'os2' + env['HOST_ARCH'] = win32.get_architecture().arch + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/posix.py b/src/engine/SCons/Platform/posix.py new file mode 100644 index 0000000..50dfb69 --- /dev/null +++ b/src/engine/SCons/Platform/posix.py @@ -0,0 +1,264 @@ +"""SCons.Platform.posix + +Platform-specific initialization for POSIX (Linux, UNIX, etc.) systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/posix.py 4577 2009/12/27 19:44:43 scons" + +import errno +import os +import os.path +import string +import subprocess +import sys +import select + +import SCons.Util +from SCons.Platform import TempFileMunge + +exitvalmap = { + 2 : 127, + 13 : 126, +} + +def escape(arg): + "escape shell special characters" + slash = '\\' + special = '"$()' + + arg = string.replace(arg, slash, slash+slash) + for c in special: + arg = string.replace(arg, c, slash+c) + + return '"' + arg + '"' + +def exec_system(l, env): + stat = os.system(string.join(l)) + if stat & 0xff: + return stat | 0x80 + return stat >> 8 + +def exec_spawnvpe(l, env): + stat = os.spawnvpe(os.P_WAIT, l[0], l, env) + # os.spawnvpe() returns the actual exit code, not the encoding + # returned by os.waitpid() or os.system(). + return stat + +def exec_fork(l, env): + pid = os.fork() + if not pid: + # Child process. + exitval = 127 + try: + os.execvpe(l[0], l, env) + except OSError, e: + exitval = exitvalmap.get(e[0], e[0]) + sys.stderr.write("scons: %s: %s\n" % (l[0], e[1])) + os._exit(exitval) + else: + # Parent process. + pid, stat = os.waitpid(pid, 0) + if stat & 0xff: + return stat | 0x80 + return stat >> 8 + +def _get_env_command(sh, escape, cmd, args, env): + s = string.join(args) + if env: + l = ['env', '-'] + \ + map(lambda t, e=escape: e(t[0])+'='+e(t[1]), env.items()) + \ + [sh, '-c', escape(s)] + s = string.join(l) + return s + +def env_spawn(sh, escape, cmd, args, env): + return exec_system([_get_env_command( sh, escape, cmd, args, env)], env) + +def spawnvpe_spawn(sh, escape, cmd, args, env): + return exec_spawnvpe([sh, '-c', string.join(args)], env) + +def fork_spawn(sh, escape, cmd, args, env): + return exec_fork([sh, '-c', string.join(args)], env) + +def process_cmd_output(cmd_stdout, cmd_stderr, stdout, stderr): + stdout_eof = stderr_eof = 0 + while not (stdout_eof and stderr_eof): + try: + (i,o,e) = select.select([cmd_stdout, cmd_stderr], [], []) + if cmd_stdout in i: + str = cmd_stdout.read() + if len(str) == 0: + stdout_eof = 1 + elif stdout is not None: + stdout.write(str) + if cmd_stderr in i: + str = cmd_stderr.read() + if len(str) == 0: + #sys.__stderr__.write( "stderr_eof=1\n" ) + stderr_eof = 1 + else: + #sys.__stderr__.write( "str(stderr) = %s\n" % str ) + stderr.write(str) + except select.error, (_errno, _strerror): + if _errno != errno.EINTR: + raise + +def exec_popen3(l, env, stdout, stderr): + proc = subprocess.Popen(string.join(l), + stdout=stdout, + stderr=stderr, + shell=True) + stat = proc.wait() + if stat & 0xff: + return stat | 0x80 + return stat >> 8 + +def exec_piped_fork(l, env, stdout, stderr): + # spawn using fork / exec and providing a pipe for the command's + # stdout / stderr stream + if stdout != stderr: + (rFdOut, wFdOut) = os.pipe() + (rFdErr, wFdErr) = os.pipe() + else: + (rFdOut, wFdOut) = os.pipe() + rFdErr = rFdOut + wFdErr = wFdOut + # do the fork + pid = os.fork() + if not pid: + # Child process + os.close( rFdOut ) + if rFdOut != rFdErr: + os.close( rFdErr ) + os.dup2( wFdOut, 1 ) # is there some symbolic way to do that ? + os.dup2( wFdErr, 2 ) + os.close( wFdOut ) + if stdout != stderr: + os.close( wFdErr ) + exitval = 127 + try: + os.execvpe(l[0], l, env) + except OSError, e: + exitval = exitvalmap.get(e[0], e[0]) + stderr.write("scons: %s: %s\n" % (l[0], e[1])) + os._exit(exitval) + else: + # Parent process + pid, stat = os.waitpid(pid, 0) + os.close( wFdOut ) + if stdout != stderr: + os.close( wFdErr ) + childOut = os.fdopen( rFdOut ) + if stdout != stderr: + childErr = os.fdopen( rFdErr ) + else: + childErr = childOut + process_cmd_output(childOut, childErr, stdout, stderr) + os.close( rFdOut ) + if stdout != stderr: + os.close( rFdErr ) + if stat & 0xff: + return stat | 0x80 + return stat >> 8 + +def piped_env_spawn(sh, escape, cmd, args, env, stdout, stderr): + # spawn using Popen3 combined with the env command + # the command name and the command's stdout is written to stdout + # the command's stderr is written to stderr + return exec_popen3([_get_env_command(sh, escape, cmd, args, env)], + env, stdout, stderr) + +def piped_fork_spawn(sh, escape, cmd, args, env, stdout, stderr): + # spawn using fork / exec and providing a pipe for the command's + # stdout / stderr stream + return exec_piped_fork([sh, '-c', string.join(args)], + env, stdout, stderr) + + + +def generate(env): + # If os.spawnvpe() exists, we use it to spawn commands. Otherwise + # if the env utility exists, we use os.system() to spawn commands, + # finally we fall back on os.fork()/os.exec(). + # + # os.spawnvpe() is prefered because it is the most efficient. But + # for Python versions without it, os.system() is prefered because it + # is claimed that it works better with threads (i.e. -j) and is more + # efficient than forking Python. + # + # NB: Other people on the scons-users mailing list have claimed that + # os.fork()/os.exec() works better than os.system(). There may just + # not be a default that works best for all users. + + if os.__dict__.has_key('spawnvpe'): + spawn = spawnvpe_spawn + elif env.Detect('env'): + spawn = env_spawn + else: + spawn = fork_spawn + + if env.Detect('env'): + pspawn = piped_env_spawn + else: + pspawn = piped_fork_spawn + + if not env.has_key('ENV'): + env['ENV'] = {} + env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin' + env['OBJPREFIX'] = '' + env['OBJSUFFIX'] = '.o' + env['SHOBJPREFIX'] = '$OBJPREFIX' + env['SHOBJSUFFIX'] = '$OBJSUFFIX' + env['PROGPREFIX'] = '' + env['PROGSUFFIX'] = '' + env['LIBPREFIX'] = 'lib' + env['LIBSUFFIX'] = '.a' + env['SHLIBPREFIX'] = '$LIBPREFIX' + env['SHLIBSUFFIX'] = '.so' + env['LIBPREFIXES'] = [ '$LIBPREFIX' ] + env['LIBSUFFIXES'] = [ '$LIBSUFFIX', '$SHLIBSUFFIX' ] + env['PSPAWN'] = pspawn + env['SPAWN'] = spawn + env['SHELL'] = 'sh' + env['ESCAPE'] = escape + env['TEMPFILE'] = TempFileMunge + env['TEMPFILEPREFIX'] = '@' + #Based on LINUX: ARG_MAX=ARG_MAX=131072 - 3000 for environment expansion + #Note: specific platforms might rise or lower this value + env['MAXLINELENGTH'] = 128072 + + # This platform supports RPATH specifications. + env['__RPATH'] = '$_RPATH' + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/posix.xml b/src/engine/SCons/Platform/posix.xml new file mode 100644 index 0000000..6336202 --- /dev/null +++ b/src/engine/SCons/Platform/posix.xml @@ -0,0 +1,51 @@ + + + +A list of paths to search for shared libraries when running programs. +Currently only used in the GNU (gnulink), +IRIX (sgilink) and Sun (sunlink) linkers. +Ignored on platforms and toolchains that don't support it. +Note that the paths added to RPATH +are not transformed by +&scons; +in any way: if you want an absolute +path, you must make it absolute yourself. + + + + + +An automatically-generated construction variable +containing the rpath flags to be used when linking +a program with shared libraries. +The value of &cv-_RPATH; is created +by appending &cv-RPATHPREFIX; and &cv-RPATHSUFFIX; +to the beginning and end +of each directory in &cv-RPATH;. + + + + + +The prefix used to specify a directory to be searched for +shared libraries when running programs. +This will be appended to the beginning of each directory +in the &cv-RPATH; construction variable +when the &cv-_RPATH; variable is automatically generated. + + + + + +The suffix used to specify a directory to be searched for +shared libraries when running programs. +This will be appended to the end of each directory +in the &cv-RPATH; construction variable +when the &cv-_RPATH; variable is automatically generated. + + diff --git a/src/engine/SCons/Platform/sunos.py b/src/engine/SCons/Platform/sunos.py new file mode 100644 index 0000000..5604598 --- /dev/null +++ b/src/engine/SCons/Platform/sunos.py @@ -0,0 +1,50 @@ +"""engine.SCons.Platform.sunos + +Platform-specific initialization for Sun systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/sunos.py 4577 2009/12/27 19:44:43 scons" + +import posix + +def generate(env): + posix.generate(env) + # Based on sunSparc 8:32bit + # ARG_MAX=1048320 - 3000 for environment expansion + env['MAXLINELENGTH'] = 1045320 + env['PKGINFO'] = 'pkginfo' + env['PKGCHK'] = '/usr/sbin/pkgchk' + env['ENV']['PATH'] = env['ENV']['PATH'] + ':/opt/SUNWspro/bin:/usr/ccs/bin' + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/sunos.xml b/src/engine/SCons/Platform/sunos.xml new file mode 100644 index 0000000..72dd85f --- /dev/null +++ b/src/engine/SCons/Platform/sunos.xml @@ -0,0 +1,30 @@ + + + + +On Solaris systems, +the package-checking program that will +be used (along with &cv-PKGINFO;) +to look for installed versions of +the Sun PRO C++ compiler. +The default is +/usr/sbin/pgkchk. + + + + + +On Solaris systems, +the package information program that will +be used (along with &cv-PKGCHK;) +to look for installed versions of +the Sun PRO C++ compiler. +The default is +pkginfo. + + diff --git a/src/engine/SCons/Platform/win32.py b/src/engine/SCons/Platform/win32.py new file mode 100644 index 0000000..5672315 --- /dev/null +++ b/src/engine/SCons/Platform/win32.py @@ -0,0 +1,386 @@ +"""SCons.Platform.win32 + +Platform-specific initialization for Win32 systems. + +There normally shouldn't be any need to import this module directly. It +will usually be imported through the generic SCons.Platform.Platform() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Platform/win32.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string +import sys +import tempfile + +from SCons.Platform.posix import exitvalmap +from SCons.Platform import TempFileMunge +import SCons.Util + +try: + import msvcrt + import win32api + import win32con + + msvcrt.get_osfhandle + win32api.SetHandleInformation + win32con.HANDLE_FLAG_INHERIT +except ImportError: + parallel_msg = \ + "you do not seem to have the pywin32 extensions installed;\n" + \ + "\tparallel (-j) builds may not work reliably with open Python files." +except AttributeError: + parallel_msg = \ + "your pywin32 extensions do not support file handle operations;\n" + \ + "\tparallel (-j) builds may not work reliably with open Python files." +else: + parallel_msg = None + + import __builtin__ + + _builtin_file = __builtin__.file + _builtin_open = __builtin__.open + + def _scons_file(*args, **kw): + fp = apply(_builtin_file, args, kw) + win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()), + win32con.HANDLE_FLAG_INHERIT, + 0) + return fp + + def _scons_open(*args, **kw): + fp = apply(_builtin_open, args, kw) + win32api.SetHandleInformation(msvcrt.get_osfhandle(fp.fileno()), + win32con.HANDLE_FLAG_INHERIT, + 0) + return fp + + __builtin__.file = _scons_file + __builtin__.open = _scons_open + + + +# The upshot of all this is that, if you are using Python 1.5.2, +# you had better have cmd or command.com in your PATH when you run +# scons. + +def piped_spawn(sh, escape, cmd, args, env, stdout, stderr): + # There is no direct way to do that in python. What we do + # here should work for most cases: + # In case stdout (stderr) is not redirected to a file, + # we redirect it into a temporary file tmpFileStdout + # (tmpFileStderr) and copy the contents of this file + # to stdout (stderr) given in the argument + if not sh: + sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n") + return 127 + else: + # one temporary file for stdout and stderr + tmpFileStdout = os.path.normpath(tempfile.mktemp()) + tmpFileStderr = os.path.normpath(tempfile.mktemp()) + + # check if output is redirected + stdoutRedirected = 0 + stderrRedirected = 0 + for arg in args: + # are there more possibilities to redirect stdout ? + if (string.find( arg, ">", 0, 1 ) != -1 or + string.find( arg, "1>", 0, 2 ) != -1): + stdoutRedirected = 1 + # are there more possibilities to redirect stderr ? + if string.find( arg, "2>", 0, 2 ) != -1: + stderrRedirected = 1 + + # redirect output of non-redirected streams to our tempfiles + if stdoutRedirected == 0: + args.append(">" + str(tmpFileStdout)) + if stderrRedirected == 0: + args.append("2>" + str(tmpFileStderr)) + + # actually do the spawn + try: + args = [sh, '/C', escape(string.join(args)) ] + ret = os.spawnve(os.P_WAIT, sh, args, env) + except OSError, e: + # catch any error + try: + ret = exitvalmap[e[0]] + except KeyError: + sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e[0], cmd, e[1])) + if stderr is not None: + stderr.write("scons: %s: %s\n" % (cmd, e[1])) + # copy child output from tempfiles to our streams + # and do clean up stuff + if stdout is not None and stdoutRedirected == 0: + try: + stdout.write(open( tmpFileStdout, "r" ).read()) + os.remove( tmpFileStdout ) + except (IOError, OSError): + pass + + if stderr is not None and stderrRedirected == 0: + try: + stderr.write(open( tmpFileStderr, "r" ).read()) + os.remove( tmpFileStderr ) + except (IOError, OSError): + pass + return ret + +def exec_spawn(l, env): + try: + result = os.spawnve(os.P_WAIT, l[0], l, env) + except OSError, e: + try: + result = exitvalmap[e[0]] + sys.stderr.write("scons: %s: %s\n" % (l[0], e[1])) + except KeyError: + result = 127 + if len(l) > 2: + if len(l[2]) < 1000: + command = string.join(l[0:3]) + else: + command = l[0] + else: + command = l[0] + sys.stderr.write("scons: unknown OSError exception code %d - '%s': %s\n" % (e[0], command, e[1])) + return result + +def spawn(sh, escape, cmd, args, env): + if not sh: + sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n") + return 127 + return exec_spawn([sh, '/C', escape(string.join(args))], env) + +# Windows does not allow special characters in file names anyway, so no +# need for a complex escape function, we will just quote the arg, except +# that "cmd /c" requires that if an argument ends with a backslash it +# needs to be escaped so as not to interfere with closing double quote +# that we add. +def escape(x): + if x[-1] == '\\': + x = x + '\\' + return '"' + x + '"' + +# Get the windows system directory name +_system_root = None + +def get_system_root(): + global _system_root + if _system_root is not None: + return _system_root + + # A resonable default if we can't read the registry + val = os.environ.get('SystemRoot', "C:/WINDOWS") + + if SCons.Util.can_read_reg: + try: + # Look for Windows NT system root + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'Software\\Microsoft\\Windows NT\\CurrentVersion') + val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot') + except SCons.Util.RegError: + try: + # Okay, try the Windows 9x system root + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'Software\\Microsoft\\Windows\\CurrentVersion') + val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot') + except KeyboardInterrupt: + raise + except: + pass + _system_root = val + return val + +# Get the location of the program files directory +def get_program_files_dir(): + # Now see if we can look in the registry... + val = '' + if SCons.Util.can_read_reg: + try: + # Look for Windows Program Files directory + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'Software\\Microsoft\\Windows\\CurrentVersion') + val, tok = SCons.Util.RegQueryValueEx(k, 'ProgramFilesDir') + except SCons.Util.RegError: + val = '' + pass + + if val == '': + # A reasonable default if we can't read the registry + # (Actually, it's pretty reasonable even if we can :-) + val = os.path.join(os.path.dirname(get_system_root()),"Program Files") + + return val + + + +# Determine which windows CPU were running on. +class ArchDefinition: + """ + A class for defining architecture-specific settings and logic. + """ + def __init__(self, arch, synonyms=[]): + self.arch = arch + self.synonyms = synonyms + +SupportedArchitectureList = [ + ArchDefinition( + 'x86', + ['i386', 'i486', 'i586', 'i686'], + ), + + ArchDefinition( + 'x86_64', + ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'], + ), + + ArchDefinition( + 'ia64', + ['IA64'], + ), +] + +SupportedArchitectureMap = {} +for a in SupportedArchitectureList: + SupportedArchitectureMap[a.arch] = a + for s in a.synonyms: + SupportedArchitectureMap[s] = a + +def get_architecture(arch=None): + """Returns the definition for the specified architecture string. + + If no string is specified, the system default is returned (as defined + by the PROCESSOR_ARCHITEW6432 or PROCESSOR_ARCHITECTURE environment + variables). + """ + if arch is None: + arch = os.environ.get('PROCESSOR_ARCHITEW6432') + if not arch: + arch = os.environ.get('PROCESSOR_ARCHITECTURE') + return SupportedArchitectureMap.get(arch, ArchDefinition('', [''])) + +def generate(env): + # Attempt to find cmd.exe (for WinNT/2k/XP) or + # command.com for Win9x + cmd_interp = '' + # First see if we can look in the registry... + if SCons.Util.can_read_reg: + try: + # Look for Windows NT system root + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'Software\\Microsoft\\Windows NT\\CurrentVersion') + val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot') + cmd_interp = os.path.join(val, 'System32\\cmd.exe') + except SCons.Util.RegError: + try: + # Okay, try the Windows 9x system root + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'Software\\Microsoft\\Windows\\CurrentVersion') + val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot') + cmd_interp = os.path.join(val, 'command.com') + except KeyboardInterrupt: + raise + except: + pass + + # For the special case of not having access to the registry, we + # use a temporary path and pathext to attempt to find the command + # interpreter. If we fail, we try to find the interpreter through + # the env's PATH. The problem with that is that it might not + # contain an ENV and a PATH. + if not cmd_interp: + systemroot = get_system_root() + tmp_path = systemroot + os.pathsep + \ + os.path.join(systemroot,'System32') + tmp_pathext = '.com;.exe;.bat;.cmd' + if os.environ.has_key('PATHEXT'): + tmp_pathext = os.environ['PATHEXT'] + cmd_interp = SCons.Util.WhereIs('cmd', tmp_path, tmp_pathext) + if not cmd_interp: + cmd_interp = SCons.Util.WhereIs('command', tmp_path, tmp_pathext) + + if not cmd_interp: + cmd_interp = env.Detect('cmd') + if not cmd_interp: + cmd_interp = env.Detect('command') + + + if not env.has_key('ENV'): + env['ENV'] = {} + + # Import things from the external environment to the construction + # environment's ENV. This is a potential slippery slope, because we + # *don't* want to make builds dependent on the user's environment by + # default. We're doing this for SystemRoot, though, because it's + # needed for anything that uses sockets, and seldom changes, and + # for SystemDrive because it's related. + # + # Weigh the impact carefully before adding other variables to this list. + import_env = [ 'SystemDrive', 'SystemRoot', 'TEMP', 'TMP' ] + for var in import_env: + v = os.environ.get(var) + if v: + env['ENV'][var] = v + + if not env['ENV'].has_key('COMSPEC'): + v = os.environ.get("COMSPEC") + if v: + env['ENV']['COMSPEC'] = v + + env.AppendENVPath('PATH', get_system_root() + '\System32') + + env['ENV']['PATHEXT'] = '.COM;.EXE;.BAT;.CMD' + env['OBJPREFIX'] = '' + env['OBJSUFFIX'] = '.obj' + env['SHOBJPREFIX'] = '$OBJPREFIX' + env['SHOBJSUFFIX'] = '$OBJSUFFIX' + env['PROGPREFIX'] = '' + env['PROGSUFFIX'] = '.exe' + env['LIBPREFIX'] = '' + env['LIBSUFFIX'] = '.lib' + env['SHLIBPREFIX'] = '' + env['SHLIBSUFFIX'] = '.dll' + env['LIBPREFIXES'] = [ '$LIBPREFIX' ] + env['LIBSUFFIXES'] = [ '$LIBSUFFIX' ] + env['PSPAWN'] = piped_spawn + env['SPAWN'] = spawn + env['SHELL'] = cmd_interp + env['TEMPFILE'] = TempFileMunge + env['TEMPFILEPREFIX'] = '@' + env['MAXLINELENGTH'] = 2048 + env['ESCAPE'] = escape + + env['HOST_OS'] = 'win32' + env['HOST_ARCH'] = get_architecture().arch + + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Platform/win32.xml b/src/engine/SCons/Platform/win32.xml new file mode 100644 index 0000000..dc60c90 --- /dev/null +++ b/src/engine/SCons/Platform/win32.xml @@ -0,0 +1,14 @@ + + + +The maximum number of characters allowed on an external command line. +On Win32 systems, +link lines longer than this many characters +are linked via a temporary file name. + + diff --git a/src/engine/SCons/SConf.py b/src/engine/SCons/SConf.py new file mode 100644 index 0000000..0151cf7 --- /dev/null +++ b/src/engine/SCons/SConf.py @@ -0,0 +1,1038 @@ +"""SCons.SConf + +Autoconf-like configuration support. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/SConf.py 4577 2009/12/27 19:44:43 scons" + +import os +import re +import string +import StringIO +import sys +import traceback +import types + +import SCons.Action +import SCons.Builder +import SCons.Errors +import SCons.Job +import SCons.Node.FS +import SCons.Taskmaster +import SCons.Util +import SCons.Warnings +import SCons.Conftest + +from SCons.Debug import Trace + +# Turn off the Conftest error logging +SCons.Conftest.LogInputFiles = 0 +SCons.Conftest.LogErrorMessages = 0 + +# Set +build_type = None +build_types = ['clean', 'help'] + +def SetBuildType(type): + global build_type + build_type = type + +# to be set, if we are in dry-run mode +dryrun = 0 + +AUTO=0 # use SCons dependency scanning for up-to-date checks +FORCE=1 # force all tests to be rebuilt +CACHE=2 # force all tests to be taken from cache (raise an error, if necessary) +cache_mode = AUTO + +def SetCacheMode(mode): + """Set the Configure cache mode. mode must be one of "auto", "force", + or "cache".""" + global cache_mode + if mode == "auto": + cache_mode = AUTO + elif mode == "force": + cache_mode = FORCE + elif mode == "cache": + cache_mode = CACHE + else: + raise ValueError, "SCons.SConf.SetCacheMode: Unknown mode " + mode + +progress_display = SCons.Util.display # will be overwritten by SCons.Script +def SetProgressDisplay(display): + """Set the progress display to use (called from SCons.Script)""" + global progress_display + progress_display = display + +SConfFS = None + +_ac_build_counter = 0 # incremented, whenever TryBuild is called +_ac_config_logs = {} # all config.log files created in this build +_ac_config_hs = {} # all config.h files created in this build +sconf_global = None # current sconf object + +def _createConfigH(target, source, env): + t = open(str(target[0]), "w") + defname = re.sub('[^A-Za-z0-9_]', '_', string.upper(str(target[0]))) + t.write("""#ifndef %(DEFNAME)s_SEEN +#define %(DEFNAME)s_SEEN + +""" % {'DEFNAME' : defname}) + t.write(source[0].get_contents()) + t.write(""" +#endif /* %(DEFNAME)s_SEEN */ +""" % {'DEFNAME' : defname}) + t.close() + +def _stringConfigH(target, source, env): + return "scons: Configure: creating " + str(target[0]) + +def CreateConfigHBuilder(env): + """Called just before the building targets phase begins.""" + if len(_ac_config_hs) == 0: + return + action = SCons.Action.Action(_createConfigH, + _stringConfigH) + sconfigHBld = SCons.Builder.Builder(action=action) + env.Append( BUILDERS={'SConfigHBuilder':sconfigHBld} ) + for k in _ac_config_hs.keys(): + env.SConfigHBuilder(k, env.Value(_ac_config_hs[k])) + +class SConfWarning(SCons.Warnings.Warning): + pass +SCons.Warnings.enableWarningClass(SConfWarning) + +# some error definitions +class SConfError(SCons.Errors.UserError): + def __init__(self,msg): + SCons.Errors.UserError.__init__(self,msg) + +class ConfigureDryRunError(SConfError): + """Raised when a file or directory needs to be updated during a Configure + process, but the user requested a dry-run""" + def __init__(self,target): + if not isinstance(target, SCons.Node.FS.File): + msg = 'Cannot create configure directory "%s" within a dry-run.' % str(target) + else: + msg = 'Cannot update configure test "%s" within a dry-run.' % str(target) + SConfError.__init__(self,msg) + +class ConfigureCacheError(SConfError): + """Raised when a use explicitely requested the cache feature, but the test + is run the first time.""" + def __init__(self,target): + SConfError.__init__(self, '"%s" is not yet built and cache is forced.' % str(target)) + +# define actions for building text files +def _createSource( target, source, env ): + fd = open(str(target[0]), "w") + fd.write(source[0].get_contents()) + fd.close() +def _stringSource( target, source, env ): + return (str(target[0]) + ' <-\n |' + + string.replace( source[0].get_contents(), + '\n', "\n |" ) ) + +# python 2.2 introduces types.BooleanType +BooleanTypes = [types.IntType] +if hasattr(types, 'BooleanType'): BooleanTypes.append(types.BooleanType) + +class SConfBuildInfo(SCons.Node.FS.FileBuildInfo): + """ + Special build info for targets of configure tests. Additional members + are result (did the builder succeed last time?) and string, which + contains messages of the original build phase. + """ + result = None # -> 0/None -> no error, != 0 error + string = None # the stdout / stderr output when building the target + + def set_build_result(self, result, string): + self.result = result + self.string = string + + +class Streamer: + """ + 'Sniffer' for a file-like writable object. Similar to the unix tool tee. + """ + def __init__(self, orig): + self.orig = orig + self.s = StringIO.StringIO() + + def write(self, str): + if self.orig: + self.orig.write(str) + self.s.write(str) + + def writelines(self, lines): + for l in lines: + self.write(l + '\n') + + def getvalue(self): + """ + Return everything written to orig since the Streamer was created. + """ + return self.s.getvalue() + + def flush(self): + if self.orig: + self.orig.flush() + self.s.flush() + + +class SConfBuildTask(SCons.Taskmaster.AlwaysTask): + """ + This is almost the same as SCons.Script.BuildTask. Handles SConfErrors + correctly and knows about the current cache_mode. + """ + def display(self, message): + if sconf_global.logstream: + sconf_global.logstream.write("scons: Configure: " + message + "\n") + + def display_cached_string(self, bi): + """ + Logs the original builder messages, given the SConfBuildInfo instance + bi. + """ + if not isinstance(bi, SConfBuildInfo): + SCons.Warnings.warn(SConfWarning, + "The stored build information has an unexpected class: %s" % bi.__class__) + else: + self.display("The original builder output was:\n" + + string.replace(" |" + str(bi.string), + "\n", "\n |")) + + def failed(self): + # check, if the reason was a ConfigureDryRunError or a + # ConfigureCacheError and if yes, reraise the exception + exc_type = self.exc_info()[0] + if issubclass(exc_type, SConfError): + raise + elif issubclass(exc_type, SCons.Errors.BuildError): + # we ignore Build Errors (occurs, when a test doesn't pass) + # Clear the exception to prevent the contained traceback + # to build a reference cycle. + self.exc_clear() + else: + self.display('Caught exception while building "%s":\n' % + self.targets[0]) + try: + excepthook = sys.excepthook + except AttributeError: + # Earlier versions of Python don't have sys.excepthook... + def excepthook(type, value, tb): + traceback.print_tb(tb) + print type, value + apply(excepthook, self.exc_info()) + return SCons.Taskmaster.Task.failed(self) + + def collect_node_states(self): + # returns (is_up_to_date, cached_error, cachable) + # where is_up_to_date is 1, if the node(s) are up_to_date + # cached_error is 1, if the node(s) are up_to_date, but the + # build will fail + # cachable is 0, if some nodes are not in our cache + T = 0 + changed = False + cached_error = False + cachable = True + for t in self.targets: + if T: Trace('%s' % (t)) + bi = t.get_stored_info().binfo + if isinstance(bi, SConfBuildInfo): + if T: Trace(': SConfBuildInfo') + if cache_mode == CACHE: + t.set_state(SCons.Node.up_to_date) + if T: Trace(': set_state(up_to-date)') + else: + if T: Trace(': get_state() %s' % t.get_state()) + if T: Trace(': changed() %s' % t.changed()) + if (t.get_state() != SCons.Node.up_to_date and t.changed()): + changed = True + if T: Trace(': changed %s' % changed) + cached_error = cached_error or bi.result + else: + if T: Trace(': else') + # the node hasn't been built in a SConf context or doesn't + # exist + cachable = False + changed = ( t.get_state() != SCons.Node.up_to_date ) + if T: Trace(': changed %s' % changed) + if T: Trace('\n') + return (not changed, cached_error, cachable) + + def execute(self): + if not self.targets[0].has_builder(): + return + + sconf = sconf_global + + is_up_to_date, cached_error, cachable = self.collect_node_states() + + if cache_mode == CACHE and not cachable: + raise ConfigureCacheError(self.targets[0]) + elif cache_mode == FORCE: + is_up_to_date = 0 + + if cached_error and is_up_to_date: + self.display("Building \"%s\" failed in a previous run and all " + "its sources are up to date." % str(self.targets[0])) + binfo = self.targets[0].get_stored_info().binfo + self.display_cached_string(binfo) + raise SCons.Errors.BuildError # will be 'caught' in self.failed + elif is_up_to_date: + self.display("\"%s\" is up to date." % str(self.targets[0])) + binfo = self.targets[0].get_stored_info().binfo + self.display_cached_string(binfo) + elif dryrun: + raise ConfigureDryRunError(self.targets[0]) + else: + # note stdout and stderr are the same here + s = sys.stdout = sys.stderr = Streamer(sys.stdout) + try: + env = self.targets[0].get_build_env() + if cache_mode == FORCE: + # Set up the Decider() to force rebuilds by saying + # that every source has changed. Note that we still + # call the environment's underlying source decider so + # that the correct .sconsign info will get calculated + # and keep the build state consistent. + def force_build(dependency, target, prev_ni, + env_decider=env.decide_source): + env_decider(dependency, target, prev_ni) + return True + if env.decide_source.func_code is not force_build.func_code: + env.Decider(force_build) + env['PSTDOUT'] = env['PSTDERR'] = s + try: + sconf.cached = 0 + self.targets[0].build() + finally: + sys.stdout = sys.stderr = env['PSTDOUT'] = \ + env['PSTDERR'] = sconf.logstream + except KeyboardInterrupt: + raise + except SystemExit: + exc_value = sys.exc_info()[1] + raise SCons.Errors.ExplicitExit(self.targets[0],exc_value.code) + except Exception, e: + for t in self.targets: + binfo = t.get_binfo() + binfo.__class__ = SConfBuildInfo + binfo.set_build_result(1, s.getvalue()) + sconsign_entry = SCons.SConsign.SConsignEntry() + sconsign_entry.binfo = binfo + #sconsign_entry.ninfo = self.get_ninfo() + # We'd like to do this as follows: + # t.store_info(binfo) + # However, we need to store it as an SConfBuildInfo + # object, and store_info() will turn it into a + # regular FileNodeInfo if the target is itself a + # regular File. + sconsign = t.dir.sconsign() + sconsign.set_entry(t.name, sconsign_entry) + sconsign.merge() + raise e + else: + for t in self.targets: + binfo = t.get_binfo() + binfo.__class__ = SConfBuildInfo + binfo.set_build_result(0, s.getvalue()) + sconsign_entry = SCons.SConsign.SConsignEntry() + sconsign_entry.binfo = binfo + #sconsign_entry.ninfo = self.get_ninfo() + # We'd like to do this as follows: + # t.store_info(binfo) + # However, we need to store it as an SConfBuildInfo + # object, and store_info() will turn it into a + # regular FileNodeInfo if the target is itself a + # regular File. + sconsign = t.dir.sconsign() + sconsign.set_entry(t.name, sconsign_entry) + sconsign.merge() + +class SConfBase: + """This is simply a class to represent a configure context. After + creating a SConf object, you can call any tests. After finished with your + tests, be sure to call the Finish() method, which returns the modified + environment. + Some words about caching: In most cases, it is not necessary to cache + Test results explicitely. Instead, we use the scons dependency checking + mechanism. For example, if one wants to compile a test program + (SConf.TryLink), the compiler is only called, if the program dependencies + have changed. However, if the program could not be compiled in a former + SConf run, we need to explicitely cache this error. + """ + + def __init__(self, env, custom_tests = {}, conf_dir='$CONFIGUREDIR', + log_file='$CONFIGURELOG', config_h = None, _depth = 0): + """Constructor. Pass additional tests in the custom_tests-dictinary, + e.g. custom_tests={'CheckPrivate':MyPrivateTest}, where MyPrivateTest + defines a custom test. + Note also the conf_dir and log_file arguments (you may want to + build tests in the VariantDir, not in the SourceDir) + """ + global SConfFS + if not SConfFS: + SConfFS = SCons.Node.FS.default_fs or \ + SCons.Node.FS.FS(env.fs.pathTop) + if sconf_global is not None: + raise (SCons.Errors.UserError, + "Only one SConf object may be active at one time") + self.env = env + if log_file is not None: + log_file = SConfFS.File(env.subst(log_file)) + self.logfile = log_file + self.logstream = None + self.lastTarget = None + self.depth = _depth + self.cached = 0 # will be set, if all test results are cached + + # add default tests + default_tests = { + 'CheckCC' : CheckCC, + 'CheckCXX' : CheckCXX, + 'CheckSHCC' : CheckSHCC, + 'CheckSHCXX' : CheckSHCXX, + 'CheckFunc' : CheckFunc, + 'CheckType' : CheckType, + 'CheckTypeSize' : CheckTypeSize, + 'CheckDeclaration' : CheckDeclaration, + 'CheckHeader' : CheckHeader, + 'CheckCHeader' : CheckCHeader, + 'CheckCXXHeader' : CheckCXXHeader, + 'CheckLib' : CheckLib, + 'CheckLibWithHeader' : CheckLibWithHeader, + } + self.AddTests(default_tests) + self.AddTests(custom_tests) + self.confdir = SConfFS.Dir(env.subst(conf_dir)) + if config_h is not None: + config_h = SConfFS.File(config_h) + self.config_h = config_h + self._startup() + + def Finish(self): + """Call this method after finished with your tests: + env = sconf.Finish() + """ + self._shutdown() + return self.env + + def Define(self, name, value = None, comment = None): + """ + Define a pre processor symbol name, with the optional given value in the + current config header. + + If value is None (default), then #define name is written. If value is not + none, then #define name value is written. + + comment is a string which will be put as a C comment in the + header, to explain the meaning of the value (appropriate C comments /* and + */ will be put automatically.""" + lines = [] + if comment: + comment_str = "/* %s */" % comment + lines.append(comment_str) + + if value is not None: + define_str = "#define %s %s" % (name, value) + else: + define_str = "#define %s" % name + lines.append(define_str) + lines.append('') + + self.config_h_text = self.config_h_text + string.join(lines, '\n') + + def BuildNodes(self, nodes): + """ + Tries to build the given nodes immediately. Returns 1 on success, + 0 on error. + """ + if self.logstream is not None: + # override stdout / stderr to write in log file + oldStdout = sys.stdout + sys.stdout = self.logstream + oldStderr = sys.stderr + sys.stderr = self.logstream + + # the engine assumes the current path is the SConstruct directory ... + old_fs_dir = SConfFS.getcwd() + old_os_dir = os.getcwd() + SConfFS.chdir(SConfFS.Top, change_os_dir=1) + + # Because we take responsibility here for writing out our + # own .sconsign info (see SConfBuildTask.execute(), above), + # we override the store_info() method with a null place-holder + # so we really control how it gets written. + for n in nodes: + n.store_info = n.do_not_store_info + + ret = 1 + + try: + # ToDo: use user options for calc + save_max_drift = SConfFS.get_max_drift() + SConfFS.set_max_drift(0) + tm = SCons.Taskmaster.Taskmaster(nodes, SConfBuildTask) + # we don't want to build tests in parallel + jobs = SCons.Job.Jobs(1, tm ) + jobs.run() + for n in nodes: + state = n.get_state() + if (state != SCons.Node.executed and + state != SCons.Node.up_to_date): + # the node could not be built. we return 0 in this case + ret = 0 + finally: + SConfFS.set_max_drift(save_max_drift) + os.chdir(old_os_dir) + SConfFS.chdir(old_fs_dir, change_os_dir=0) + if self.logstream is not None: + # restore stdout / stderr + sys.stdout = oldStdout + sys.stderr = oldStderr + return ret + + def pspawn_wrapper(self, sh, escape, cmd, args, env): + """Wrapper function for handling piped spawns. + + This looks to the calling interface (in Action.py) like a "normal" + spawn, but associates the call with the PSPAWN variable from + the construction environment and with the streams to which we + want the output logged. This gets slid into the construction + environment as the SPAWN variable so Action.py doesn't have to + know or care whether it's spawning a piped command or not. + """ + return self.pspawn(sh, escape, cmd, args, env, self.logstream, self.logstream) + + + def TryBuild(self, builder, text = None, extension = ""): + """Low level TryBuild implementation. Normally you don't need to + call that - you can use TryCompile / TryLink / TryRun instead + """ + global _ac_build_counter + + # Make sure we have a PSPAWN value, and save the current + # SPAWN value. + try: + self.pspawn = self.env['PSPAWN'] + except KeyError: + raise SCons.Errors.UserError('Missing PSPAWN construction variable.') + try: + save_spawn = self.env['SPAWN'] + except KeyError: + raise SCons.Errors.UserError('Missing SPAWN construction variable.') + + nodesToBeBuilt = [] + + f = "conftest_" + str(_ac_build_counter) + pref = self.env.subst( builder.builder.prefix ) + suff = self.env.subst( builder.builder.suffix ) + target = self.confdir.File(pref + f + suff) + + try: + # Slide our wrapper into the construction environment as + # the SPAWN function. + self.env['SPAWN'] = self.pspawn_wrapper + sourcetext = self.env.Value(text) + + if text is not None: + textFile = self.confdir.File(f + extension) + textFileNode = self.env.SConfSourceBuilder(target=textFile, + source=sourcetext) + nodesToBeBuilt.extend(textFileNode) + source = textFileNode + else: + source = None + + nodes = builder(target = target, source = source) + if not SCons.Util.is_List(nodes): + nodes = [nodes] + nodesToBeBuilt.extend(nodes) + result = self.BuildNodes(nodesToBeBuilt) + + finally: + self.env['SPAWN'] = save_spawn + + _ac_build_counter = _ac_build_counter + 1 + if result: + self.lastTarget = nodes[0] + else: + self.lastTarget = None + + return result + + def TryAction(self, action, text = None, extension = ""): + """Tries to execute the given action with optional source file + contents and optional source file extension , + Returns the status (0 : failed, 1 : ok) and the contents of the + output file. + """ + builder = SCons.Builder.Builder(action=action) + self.env.Append( BUILDERS = {'SConfActionBuilder' : builder} ) + ok = self.TryBuild(self.env.SConfActionBuilder, text, extension) + del self.env['BUILDERS']['SConfActionBuilder'] + if ok: + outputStr = self.lastTarget.get_contents() + return (1, outputStr) + return (0, "") + + def TryCompile( self, text, extension): + """Compiles the program given in text to an env.Object, using extension + as file extension (e.g. '.c'). Returns 1, if compilation was + successful, 0 otherwise. The target is saved in self.lastTarget (for + further processing). + """ + return self.TryBuild(self.env.Object, text, extension) + + def TryLink( self, text, extension ): + """Compiles the program given in text to an executable env.Program, + using extension as file extension (e.g. '.c'). Returns 1, if + compilation was successful, 0 otherwise. The target is saved in + self.lastTarget (for further processing). + """ + return self.TryBuild(self.env.Program, text, extension ) + + def TryRun(self, text, extension ): + """Compiles and runs the program given in text, using extension + as file extension (e.g. '.c'). Returns (1, outputStr) on success, + (0, '') otherwise. The target (a file containing the program's stdout) + is saved in self.lastTarget (for further processing). + """ + ok = self.TryLink(text, extension) + if( ok ): + prog = self.lastTarget + pname = prog.path + output = self.confdir.File(os.path.basename(pname)+'.out') + node = self.env.Command(output, prog, [ [ pname, ">", "${TARGET}"] ]) + ok = self.BuildNodes(node) + if ok: + outputStr = output.get_contents() + return( 1, outputStr) + return (0, "") + + class TestWrapper: + """A wrapper around Tests (to ensure sanity)""" + def __init__(self, test, sconf): + self.test = test + self.sconf = sconf + def __call__(self, *args, **kw): + if not self.sconf.active: + raise (SCons.Errors.UserError, + "Test called after sconf.Finish()") + context = CheckContext(self.sconf) + ret = apply(self.test, (context,) + args, kw) + if self.sconf.config_h is not None: + self.sconf.config_h_text = self.sconf.config_h_text + context.config_h + context.Result("error: no result") + return ret + + def AddTest(self, test_name, test_instance): + """Adds test_class to this SConf instance. It can be called with + self.test_name(...)""" + setattr(self, test_name, SConfBase.TestWrapper(test_instance, self)) + + def AddTests(self, tests): + """Adds all the tests given in the tests dictionary to this SConf + instance + """ + for name in tests.keys(): + self.AddTest(name, tests[name]) + + def _createDir( self, node ): + dirName = str(node) + if dryrun: + if not os.path.isdir( dirName ): + raise ConfigureDryRunError(dirName) + else: + if not os.path.isdir( dirName ): + os.makedirs( dirName ) + node._exists = 1 + + def _startup(self): + """Private method. Set up logstream, and set the environment + variables necessary for a piped build + """ + global _ac_config_logs + global sconf_global + global SConfFS + + self.lastEnvFs = self.env.fs + self.env.fs = SConfFS + self._createDir(self.confdir) + self.confdir.up().add_ignore( [self.confdir] ) + + if self.logfile is not None and not dryrun: + # truncate logfile, if SConf.Configure is called for the first time + # in a build + if _ac_config_logs.has_key(self.logfile): + log_mode = "a" + else: + _ac_config_logs[self.logfile] = None + log_mode = "w" + fp = open(str(self.logfile), log_mode) + self.logstream = SCons.Util.Unbuffered(fp) + # logfile may stay in a build directory, so we tell + # the build system not to override it with a eventually + # existing file with the same name in the source directory + self.logfile.dir.add_ignore( [self.logfile] ) + + tb = traceback.extract_stack()[-3-self.depth] + old_fs_dir = SConfFS.getcwd() + SConfFS.chdir(SConfFS.Top, change_os_dir=0) + self.logstream.write('file %s,line %d:\n\tConfigure(confdir = %s)\n' % + (tb[0], tb[1], str(self.confdir)) ) + SConfFS.chdir(old_fs_dir) + else: + self.logstream = None + # we use a special builder to create source files from TEXT + action = SCons.Action.Action(_createSource, + _stringSource) + sconfSrcBld = SCons.Builder.Builder(action=action) + self.env.Append( BUILDERS={'SConfSourceBuilder':sconfSrcBld} ) + self.config_h_text = _ac_config_hs.get(self.config_h, "") + self.active = 1 + # only one SConf instance should be active at a time ... + sconf_global = self + + def _shutdown(self): + """Private method. Reset to non-piped spawn""" + global sconf_global, _ac_config_hs + + if not self.active: + raise SCons.Errors.UserError, "Finish may be called only once!" + if self.logstream is not None and not dryrun: + self.logstream.write("\n") + self.logstream.close() + self.logstream = None + # remove the SConfSourceBuilder from the environment + blds = self.env['BUILDERS'] + del blds['SConfSourceBuilder'] + self.env.Replace( BUILDERS=blds ) + self.active = 0 + sconf_global = None + if not self.config_h is None: + _ac_config_hs[self.config_h] = self.config_h_text + self.env.fs = self.lastEnvFs + +class CheckContext: + """Provides a context for configure tests. Defines how a test writes to the + screen and log file. + + A typical test is just a callable with an instance of CheckContext as + first argument: + + def CheckCustom(context, ...) + context.Message('Checking my weird test ... ') + ret = myWeirdTestFunction(...) + context.Result(ret) + + Often, myWeirdTestFunction will be one of + context.TryCompile/context.TryLink/context.TryRun. The results of + those are cached, for they are only rebuild, if the dependencies have + changed. + """ + + def __init__(self, sconf): + """Constructor. Pass the corresponding SConf instance.""" + self.sconf = sconf + self.did_show_result = 0 + + # for Conftest.py: + self.vardict = {} + self.havedict = {} + self.headerfilename = None + self.config_h = "" # config_h text will be stored here + # we don't regenerate the config.h file after each test. That means, + # that tests won't be able to include the config.h file, and so + # they can't do an #ifdef HAVE_XXX_H. This shouldn't be a major + # issue, though. If it turns out, that we need to include config.h + # in tests, we must ensure, that the dependencies are worked out + # correctly. Note that we can't use Conftest.py's support for config.h, + # cause we will need to specify a builder for the config.h file ... + + def Message(self, text): + """Inform about what we are doing right now, e.g. + 'Checking for SOMETHING ... ' + """ + self.Display(text) + self.sconf.cached = 1 + self.did_show_result = 0 + + def Result(self, res): + """Inform about the result of the test. res may be an integer or a + string. In case of an integer, the written text will be 'yes' or 'no'. + The result is only displayed when self.did_show_result is not set. + """ + if type(res) in BooleanTypes: + if res: + text = "yes" + else: + text = "no" + elif type(res) == types.StringType: + text = res + else: + raise TypeError, "Expected string, int or bool, got " + str(type(res)) + + if self.did_show_result == 0: + # Didn't show result yet, do it now. + self.Display(text + "\n") + self.did_show_result = 1 + + def TryBuild(self, *args, **kw): + return apply(self.sconf.TryBuild, args, kw) + + def TryAction(self, *args, **kw): + return apply(self.sconf.TryAction, args, kw) + + def TryCompile(self, *args, **kw): + return apply(self.sconf.TryCompile, args, kw) + + def TryLink(self, *args, **kw): + return apply(self.sconf.TryLink, args, kw) + + def TryRun(self, *args, **kw): + return apply(self.sconf.TryRun, args, kw) + + def __getattr__( self, attr ): + if( attr == 'env' ): + return self.sconf.env + elif( attr == 'lastTarget' ): + return self.sconf.lastTarget + else: + raise AttributeError, "CheckContext instance has no attribute '%s'" % attr + + #### Stuff used by Conftest.py (look there for explanations). + + def BuildProg(self, text, ext): + self.sconf.cached = 1 + # TODO: should use self.vardict for $CC, $CPPFLAGS, etc. + return not self.TryBuild(self.env.Program, text, ext) + + def CompileProg(self, text, ext): + self.sconf.cached = 1 + # TODO: should use self.vardict for $CC, $CPPFLAGS, etc. + return not self.TryBuild(self.env.Object, text, ext) + + def CompileSharedObject(self, text, ext): + self.sconf.cached = 1 + # TODO: should use self.vardict for $SHCC, $CPPFLAGS, etc. + return not self.TryBuild(self.env.SharedObject, text, ext) + + def RunProg(self, text, ext): + self.sconf.cached = 1 + # TODO: should use self.vardict for $CC, $CPPFLAGS, etc. + st, out = self.TryRun(text, ext) + return not st, out + + def AppendLIBS(self, lib_name_list): + oldLIBS = self.env.get( 'LIBS', [] ) + self.env.Append(LIBS = lib_name_list) + return oldLIBS + + def PrependLIBS(self, lib_name_list): + oldLIBS = self.env.get( 'LIBS', [] ) + self.env.Prepend(LIBS = lib_name_list) + return oldLIBS + + def SetLIBS(self, val): + oldLIBS = self.env.get( 'LIBS', [] ) + self.env.Replace(LIBS = val) + return oldLIBS + + def Display(self, msg): + if self.sconf.cached: + # We assume that Display is called twice for each test here + # once for the Checking for ... message and once for the result. + # The self.sconf.cached flag can only be set between those calls + msg = "(cached) " + msg + self.sconf.cached = 0 + progress_display(msg, append_newline=0) + self.Log("scons: Configure: " + msg + "\n") + + def Log(self, msg): + if self.sconf.logstream is not None: + self.sconf.logstream.write(msg) + + #### End of stuff used by Conftest.py. + + +def SConf(*args, **kw): + if kw.get(build_type, True): + kw['_depth'] = kw.get('_depth', 0) + 1 + for bt in build_types: + try: + del kw[bt] + except KeyError: + pass + return apply(SConfBase, args, kw) + else: + return SCons.Util.Null() + + +def CheckFunc(context, function_name, header = None, language = None): + res = SCons.Conftest.CheckFunc(context, function_name, header = header, language = language) + context.did_show_result = 1 + return not res + +def CheckType(context, type_name, includes = "", language = None): + res = SCons.Conftest.CheckType(context, type_name, + header = includes, language = language) + context.did_show_result = 1 + return not res + +def CheckTypeSize(context, type_name, includes = "", language = None, expect = None): + res = SCons.Conftest.CheckTypeSize(context, type_name, + header = includes, language = language, + expect = expect) + context.did_show_result = 1 + return res + +def CheckDeclaration(context, declaration, includes = "", language = None): + res = SCons.Conftest.CheckDeclaration(context, declaration, + includes = includes, + language = language) + context.did_show_result = 1 + return not res + +def createIncludesFromHeaders(headers, leaveLast, include_quotes = '""'): + # used by CheckHeader and CheckLibWithHeader to produce C - #include + # statements from the specified header (list) + if not SCons.Util.is_List(headers): + headers = [headers] + l = [] + if leaveLast: + lastHeader = headers[-1] + headers = headers[:-1] + else: + lastHeader = None + for s in headers: + l.append("#include %s%s%s\n" + % (include_quotes[0], s, include_quotes[1])) + return string.join(l, ''), lastHeader + +def CheckHeader(context, header, include_quotes = '<>', language = None): + """ + A test for a C or C++ header file. + """ + prog_prefix, hdr_to_check = \ + createIncludesFromHeaders(header, 1, include_quotes) + res = SCons.Conftest.CheckHeader(context, hdr_to_check, prog_prefix, + language = language, + include_quotes = include_quotes) + context.did_show_result = 1 + return not res + +def CheckCC(context): + res = SCons.Conftest.CheckCC(context) + context.did_show_result = 1 + return not res + +def CheckCXX(context): + res = SCons.Conftest.CheckCXX(context) + context.did_show_result = 1 + return not res + +def CheckSHCC(context): + res = SCons.Conftest.CheckSHCC(context) + context.did_show_result = 1 + return not res + +def CheckSHCXX(context): + res = SCons.Conftest.CheckSHCXX(context) + context.did_show_result = 1 + return not res + +# Bram: Make this function obsolete? CheckHeader() is more generic. + +def CheckCHeader(context, header, include_quotes = '""'): + """ + A test for a C header file. + """ + return CheckHeader(context, header, include_quotes, language = "C") + + +# Bram: Make this function obsolete? CheckHeader() is more generic. + +def CheckCXXHeader(context, header, include_quotes = '""'): + """ + A test for a C++ header file. + """ + return CheckHeader(context, header, include_quotes, language = "C++") + + +def CheckLib(context, library = None, symbol = "main", + header = None, language = None, autoadd = 1): + """ + A test for a library. See also CheckLibWithHeader. + Note that library may also be None to test whether the given symbol + compiles without flags. + """ + + if library == []: + library = [None] + + if not SCons.Util.is_List(library): + library = [library] + + # ToDo: accept path for the library + res = SCons.Conftest.CheckLib(context, library, symbol, header = header, + language = language, autoadd = autoadd) + context.did_show_result = 1 + return not res + +# XXX +# Bram: Can only include one header and can't use #ifdef HAVE_HEADER_H. + +def CheckLibWithHeader(context, libs, header, language, + call = None, autoadd = 1): + # ToDo: accept path for library. Support system header files. + """ + Another (more sophisticated) test for a library. + Checks, if library and header is available for language (may be 'C' + or 'CXX'). Call maybe be a valid expression _with_ a trailing ';'. + As in CheckLib, we support library=None, to test if the call compiles + without extra link flags. + """ + prog_prefix, dummy = \ + createIncludesFromHeaders(header, 0) + if libs == []: + libs = [None] + + if not SCons.Util.is_List(libs): + libs = [libs] + + res = SCons.Conftest.CheckLib(context, libs, None, prog_prefix, + call = call, language = language, autoadd = autoadd) + context.did_show_result = 1 + return not res + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/SConfTests.py b/src/engine/SCons/SConfTests.py new file mode 100644 index 0000000..71bdb57 --- /dev/null +++ b/src/engine/SCons/SConfTests.py @@ -0,0 +1,759 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/SConfTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import re +import string +import StringIO +import sys +from types import * +import unittest + +import TestCmd + +sys.stdout = StringIO.StringIO() + +if sys.platform == 'win32': + existing_lib = "msvcrt" +else: + existing_lib = "m" + +class SConfTestCase(unittest.TestCase): + + def setUp(self): + # we always want to start with a clean directory + self.save_cwd = os.getcwd() + self.test = TestCmd.TestCmd(workdir = '') + os.chdir(self.test.workpath('')) + + def tearDown(self): + self.test.cleanup() + import SCons.SConsign + SCons.SConsign.Reset() + os.chdir(self.save_cwd) + + def _resetSConfState(self): + # Ok, this is tricky, and i do not know, if everything is sane. + # We try to reset scons' state (including all global variables) + import SCons.SConsign + SCons.SConsign.write() # simulate normal scons-finish + for n in sys.modules.keys(): + if string.split(n, '.')[0] == 'SCons' and n[:12] != 'SCons.compat': + m = sys.modules[n] + if type(m) is ModuleType: + # if this is really a scons module, clear its namespace + del sys.modules[n] + m.__dict__.clear() + # we only use SCons.Environment and SCons.SConf for these tests. + import SCons.Environment + import SCons.SConf + self.Environment = SCons.Environment + self.SConf = SCons.SConf + # and we need a new environment, cause references may point to + # old modules (well, at least this is safe ...) + self.scons_env = self.Environment.Environment() + self.scons_env.AppendENVPath('PATH', os.environ['PATH']) + + # we want to do some autodetection here + # this stuff works with + # - cygwin on Windows (using cmd.exe, not bash) + # - posix + # - msvc on Windows (hopefully) + if (not self.scons_env.Detect( self.scons_env.subst('$CXX') ) or + not self.scons_env.Detect( self.scons_env.subst('$CC') ) or + not self.scons_env.Detect( self.scons_env.subst('$LINK') )): + raise Exception, "This test needs an installed compiler!" + if self.scons_env['CXX'] == 'g++': + global existing_lib + existing_lib = 'm' + + if sys.platform in ['cygwin', 'win32']: + # On Windows, SCons.Platform.win32 redefines the builtin + # file() and open() functions to close the file handles. + # This interferes with the unittest.py infrastructure in + # some way. Just sidestep the issue by restoring the + # original builtin functions whenever we have to reset + # all of our global state. + + import __builtin__ + import SCons.Platform.win32 + + __builtin__.file = SCons.Platform.win32._builtin_file + __builtin__.open = SCons.Platform.win32._builtin_open + + def _baseTryXXX(self, TryFunc): + # TryCompile and TryLink are much the same, so we can test them + # in one method, we pass the function as a string ('TryCompile', + # 'TryLink'), so we are aware of reloading modules. + + def checks(self, sconf, TryFuncString): + TryFunc = self.SConf.SConfBase.__dict__[TryFuncString] + res1 = TryFunc( sconf, "int main() { return 0; }\n", ".c" ) + res2 = TryFunc( sconf, + '#include "no_std_header.h"\nint main() {return 0; }\n', + '.c' ) + return (res1,res2) + + # 1. test initial behaviour (check ok / failed) + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + res = checks( self, sconf, TryFunc ) + assert res[0] and not res[1], res + finally: + sconf.Finish() + + # 2.1 test the error caching mechanism (no dependencies have changed) + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + res = checks( self, sconf, TryFunc ) + assert res[0] and not res[1], res + finally: + sconf.Finish() + # we should have exactly one one error cached + log = self.test.read( self.test.workpath('config.log') ) + expr = re.compile( ".*failed in a previous run and all", re.DOTALL ) + firstOcc = expr.match( log ) + assert firstOcc is not None, log + secondOcc = expr.match( log, firstOcc.end(0) ) + assert secondOcc is None, log + + # 2.2 test the error caching mechanism (dependencies have changed) + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + no_std_header_h = self.test.workpath('config.tests', 'no_std_header.h') + test_h = self.test.write( no_std_header_h, + "/* we are changing a dependency now */\n" ); + try: + res = checks( self, sconf, TryFunc ) + log = self.test.read( self.test.workpath('config.log') ) + assert res[0] and res[1], res + finally: + sconf.Finish() + + def test_TryBuild(self): + """Test SConf.TryBuild + """ + # 1 test that we can try a builder that returns a list of nodes + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + class MyBuilder: + def __init__(self): + self.prefix = '' + self.suffix = '' + def __call__(self, env, target, source): + class MyNode: + def __init__(self, name): + self.name = name + self.state = None + self.waiting_parents = set() + self.side_effects = [] + self.builder = None + self.prerequisites = [] + def disambiguate(self): + return self + def has_builder(self): + return 1 + def add_pre_action(self, *actions): + pass + def add_post_action(self, *actions): + pass + def children(self): + return [] + def get_state(self): + return self.state + def set_state(self, state): + self.state = state + def alter_targets(self): + return [], None + def depends_on(self, nodes): + return None + def postprocess(self): + pass + def clear(self): + pass + def is_up_to_date(self): + return None + def prepare(self): + pass + def push_to_cache(self): + pass + def retrieve_from_cache(self): + return 0 + def build(self, **kw): + return + def built(self): + pass + def get_stored_info(self): + pass + def do_not_store_info(self): + pass + def get_executor(self): + class Executor: + def __init__(self, targets): + self.targets = targets + def get_all_targets(self): + return self.targets + return Executor([self]) + return [MyNode('n1'), MyNode('n2')] + try: + self.scons_env.Append(BUILDERS = {'SConfActionBuilder' : MyBuilder()}) + sconf.TryBuild(self.scons_env.SConfActionBuilder) + finally: + sconf.Finish() + + def test_TryCompile(self): + """Test SConf.TryCompile + """ + self._baseTryXXX( "TryCompile" ) #self.SConf.SConf.TryCompile ) + + def test_TryLink(self): + """Test SConf.TryLink + """ + self._baseTryXXX( "TryLink" ) #self.SConf.SConf.TryLink ) + + def test_TryRun(self): + """Test SConf.TryRun + """ + def checks(sconf): + prog = """ +#include +int main() { + printf( "Hello" ); + return 0; +} +""" + res1 = sconf.TryRun( prog, ".c" ) + res2 = sconf.TryRun( "not a c program\n", ".c" ) + return (res1, res2) + + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + res = checks(sconf) + assert res[0][0] and res[0][1] == "Hello", res + assert not res[1][0] and res[1][1] == "", res + finally: + sconf.Finish() + log = self.test.read( self.test.workpath('config.log') ) + + # test the caching mechanism + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + res = checks(sconf) + assert res[0][0] and res[0][1] == "Hello", res + assert not res[1][0] and res[1][1] == "", res + finally: + sconf.Finish() + # we should have exactly one error cached + log = self.test.read( self.test.workpath('config.log') ) + expr = re.compile( ".*failed in a previous run and all", re.DOTALL ) + firstOcc = expr.match( log ) + assert firstOcc is not None, log + secondOcc = expr.match( log, firstOcc.end(0) ) + assert secondOcc is None, log + + + def test_TryAction(self): + """Test SConf.TryAction + """ + def actionOK(target, source, env): + open(str(target[0]), "w").write( "RUN OK\n" ) + return None + def actionFAIL(target, source, env): + return 1 + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + (ret, output) = sconf.TryAction(action=actionOK) + assert ret and output == "RUN OK" + os.linesep, (ret, output) + (ret, output) = sconf.TryAction(action=actionFAIL) + assert not ret and output == "", (ret, output) + finally: + sconf.Finish() + + def _test_check_compilers(self, comp, func, name): + """This is the implementation for CheckCC and CheckCXX tests.""" + from copy import deepcopy + + # Check that Check* works + r = func() + assert r, "could not find %s ?" % comp + + # Check that Check* does fail if comp is not available in env + oldcomp = deepcopy(self.scons_env[comp]) + del self.scons_env[comp] + r = func() + assert not r, "%s worked wo comp ?" % name + + # Check that Check* does fail if comp is set but empty + self.scons_env[comp] = '' + r = func() + assert not r, "%s worked with comp = '' ?" % name + + # Check that Check* does fail if comp is set to buggy executable + self.scons_env[comp] = 'thiscccompilerdoesnotexist' + r = func() + assert not r, "%s worked with comp = thiscompilerdoesnotexist ?" % name + + # Check that Check* does fail if CFLAGS is buggy + self.scons_env[comp] = oldcomp + self.scons_env['%sFLAGS' % comp] = '/WX qwertyuiop.c' + r = func() + assert not r, "%s worked with %sFLAGS = qwertyuiop ?" % (name, comp) + + def test_CheckCC(self): + """Test SConf.CheckCC() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + try: + self._test_check_compilers('CC', sconf.CheckCC, 'CheckCC') + except AssertionError: + sys.stderr.write(self.test.read('config.log')) + raise + finally: + sconf.Finish() + + def test_CheckSHCC(self): + """Test SConf.CheckSHCC() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + try: + self._test_check_compilers('SHCC', sconf.CheckSHCC, 'CheckSHCC') + except AssertionError: + sys.stderr.write(self.test.read('config.log')) + raise + finally: + sconf.Finish() + + def test_CheckCXX(self): + """Test SConf.CheckCXX() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + try: + self._test_check_compilers('CXX', sconf.CheckCXX, 'CheckCXX') + except AssertionError: + sys.stderr.write(self.test.read('config.log')) + raise + finally: + sconf.Finish() + + def test_CheckSHCXX(self): + """Test SConf.CheckSHCXX() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + try: + self._test_check_compilers('SHCXX', sconf.CheckSHCXX, 'CheckSHCXX') + except AssertionError: + sys.stderr.write(self.test.read('config.log')) + raise + finally: + sconf.Finish() + + + def test_CheckHeader(self): + """Test SConf.CheckHeader() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + # CheckHeader() + r = sconf.CheckHeader( "stdio.h", include_quotes="<>", language="C" ) + assert r, "did not find stdio.h" + r = sconf.CheckHeader( "HopefullyNoHeader.noh", language="C" ) + assert not r, "unexpectedly found HopefullyNoHeader.noh" + r = sconf.CheckHeader( "vector", include_quotes="<>", language="C++" ) + assert r, "did not find vector" + r = sconf.CheckHeader( "HopefullyNoHeader.noh", language="C++" ) + assert not r, "unexpectedly found HopefullyNoHeader.noh" + + finally: + sconf.Finish() + + def test_CheckCHeader(self): + """Test SConf.CheckCHeader() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + + try: + # CheckCHeader() + r = sconf.CheckCHeader( "stdio.h", include_quotes="<>" ) + assert r, "did not find stdio.h" + r = sconf.CheckCHeader( ["math.h", "stdio.h"], include_quotes="<>" ) + assert r, "did not find stdio.h, #include math.h first" + r = sconf.CheckCHeader( "HopefullyNoCHeader.noh" ) + assert not r, "unexpectedly found HopefullyNoCHeader.noh" + + finally: + sconf.Finish() + + def test_CheckCXXHeader(self): + """Test SConf.CheckCXXHeader() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + + try: + # CheckCXXHeader() + r = sconf.CheckCXXHeader( "vector", include_quotes="<>" ) + assert r, "did not find vector" + r = sconf.CheckCXXHeader( ["stdio.h", "vector"], include_quotes="<>" ) + assert r, "did not find vector, #include stdio.h first" + r = sconf.CheckCXXHeader( "HopefullyNoCXXHeader.noh" ) + assert not r, "unexpectedly found HopefullyNoCXXHeader.noh" + + finally: + sconf.Finish() + + def test_CheckLib(self): + """Test SConf.CheckLib() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + + try: + # CheckLib() + r = sconf.CheckLib( existing_lib, "main", autoadd=0 ) + assert r, "did not find %s" % existing_lib + r = sconf.CheckLib( "hopefullynolib", "main", autoadd=0 ) + assert not r, "unexpectedly found hopefullynolib" + + # CheckLib() with list of libs + r = sconf.CheckLib( [existing_lib], "main", autoadd=0 ) + assert r, "did not find %s" % existing_lib + r = sconf.CheckLib( ["hopefullynolib"], "main", autoadd=0 ) + assert not r, "unexpectedly found hopefullynolib" + # This is a check that a null list doesn't find functions + # that are in libraries that must be explicitly named. + # This works on POSIX systems where you have to -lm to + # get the math functions, but it fails on Visual Studio + # where you apparently get all those functions for free. + # Comment out this check until someone who understands + # Visual Studio better can come up with a corresponding + # test (if that ever really becomes necessary). + #r = sconf.CheckLib( [], "sin", autoadd=0 ) + #assert not r, "unexpectedly found nonexistent library" + r = sconf.CheckLib( [existing_lib,"hopefullynolib"], "main", autoadd=0 ) + assert r, "did not find %s,%s " % (existing_lib,r) + r = sconf.CheckLib( ["hopefullynolib",existing_lib], "main", autoadd=0 ) + assert r, "did not find %s " % existing_lib + + # CheckLib() with autoadd + def libs(env): + return env.get('LIBS', []) + + env = sconf.env.Clone() + + try: + r = sconf.CheckLib( existing_lib, "main", autoadd=1 ) + assert r, "did not find main in %s" % existing_lib + expect = libs(env) + [existing_lib] + got = libs(sconf.env) + assert got == expect, "LIBS: expected %s, got %s" % (expect, got) + + sconf.env = env.Clone() + r = sconf.CheckLib( existing_lib, "main", autoadd=0 ) + assert r, "did not find main in %s" % existing_lib + expect = libs(env) + got = libs(sconf.env) + assert got == expect, "before and after LIBS were not the same" + finally: + sconf.env = env + finally: + sconf.Finish() + + def test_CheckLibWithHeader(self): + """Test SConf.CheckLibWithHeader() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + + try: + # CheckLibWithHeader() + r = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=0 ) + assert r, "did not find %s" % existing_lib + r = sconf.CheckLibWithHeader( existing_lib, ["stdio.h", "math.h"], "C", autoadd=0 ) + assert r, "did not find %s, #include stdio.h first" % existing_lib + r = sconf.CheckLibWithHeader( "hopefullynolib", "math.h", "C", autoadd=0 ) + assert not r, "unexpectedly found hopefullynolib" + + # CheckLibWithHeader() with lists of libs + r = sconf.CheckLibWithHeader( [existing_lib], "math.h", "C", autoadd=0 ) + assert r, "did not find %s" % existing_lib + r = sconf.CheckLibWithHeader( [existing_lib], ["stdio.h", "math.h"], "C", autoadd=0 ) + assert r, "did not find %s, #include stdio.h first" % existing_lib + # This is a check that a null list doesn't find functions + # that are in libraries that must be explicitly named. + # This works on POSIX systems where you have to -lm to + # get the math functions, but it fails on Visual Studio + # where you apparently get all those functions for free. + # Comment out this check until someone who understands + # Visual Studio better can come up with a corresponding + # test (if that ever really becomes necessary). + #r = sconf.CheckLibWithHeader( [], "math.h", "C", call="sin(3);", autoadd=0 ) + #assert not r, "unexpectedly found non-existent library" + r = sconf.CheckLibWithHeader( ["hopefullynolib"], "math.h", "C", autoadd=0 ) + assert not r, "unexpectedly found hopefullynolib" + r = sconf.CheckLibWithHeader( ["hopefullynolib",existing_lib], ["stdio.h", "math.h"], "C", autoadd=0 ) + assert r, "did not find %s, #include stdio.h first" % existing_lib + r = sconf.CheckLibWithHeader( [existing_lib,"hopefullynolib"], ["stdio.h", "math.h"], "C", autoadd=0 ) + assert r, "did not find %s, #include stdio.h first" % existing_lib + + # CheckLibWithHeader with autoadd + def libs(env): + return env.get('LIBS', []) + + env = sconf.env.Clone() + + try: + r = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=1 ) + assert r, "did not find math.h with %s" % existing_lib + expect = libs(env) + [existing_lib] + got = libs(sconf.env) + assert got == expect, "LIBS: expected %s, got %s" % (expect, got) + + sconf.env = env.Clone() + r = sconf.CheckLibWithHeader( existing_lib, "math.h", "C", autoadd=0 ) + assert r, "did not find math.h with %s" % existing_lib + expect = libs(env) + got = libs(sconf.env) + assert got == expect, "before and after LIBS were not the same" + finally: + sconf.env = env + + finally: + sconf.Finish() + + def test_CheckFunc(self): + """Test SConf.CheckFunc() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + + try: + # CheckFunc() + r = sconf.CheckFunc('strcpy') + assert r, "did not find strcpy" + r = sconf.CheckFunc('strcpy', '/* header */ char strcpy();') + assert r, "did not find strcpy" + r = sconf.CheckFunc('hopefullynofunction') + assert not r, "unexpectedly found hopefullynofunction" + + finally: + sconf.Finish() + + def test_Define(self): + """Test SConf.Define() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log'), + config_h = self.test.workpath('config.h')) + try: + # XXX: we test the generated config.h string. This is not so good, + # ideally, we would like to test if the generated file included in + # a test program does what we want. + + # Test defining one symbol wo value + sconf.config_h_text = '' + sconf.Define('YOP') + assert sconf.config_h_text == '#define YOP\n' + + # Test defining one symbol with integer value + sconf.config_h_text = '' + sconf.Define('YOP', 1) + assert sconf.config_h_text == '#define YOP 1\n' + + # Test defining one symbol with string value + sconf.config_h_text = '' + sconf.Define('YOP', '"YIP"') + assert sconf.config_h_text == '#define YOP "YIP"\n' + + # Test defining one symbol with string value + sconf.config_h_text = '' + sconf.Define('YOP', "YIP") + assert sconf.config_h_text == '#define YOP YIP\n' + + finally: + sconf.Finish() + + def test_CheckTypeSize(self): + """Test SConf.CheckTypeSize() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + # CheckTypeSize() + + # In ANSI C, sizeof(char) == 1. + r = sconf.CheckTypeSize('char', expect = 1) + assert r == 1, "sizeof(char) != 1 ??" + r = sconf.CheckTypeSize('char', expect = 0) + assert r == 0, "sizeof(char) == 0 ??" + r = sconf.CheckTypeSize('char', expect = 2) + assert r == 0, "sizeof(char) == 2 ??" + r = sconf.CheckTypeSize('char') + assert r == 1, "sizeof(char) != 1 ??" + r = sconf.CheckTypeSize('const unsigned char') + assert r == 1, "sizeof(const unsigned char) != 1 ??" + + # Checking C++ + r = sconf.CheckTypeSize('const unsigned char', language = 'C++') + assert r == 1, "sizeof(const unsigned char) != 1 ??" + + # Checking Non-existing type + r = sconf.CheckTypeSize('thistypedefhasnotchancetosexist_scons') + assert r == 0, \ + "Checking size of thistypedefhasnotchancetosexist_scons succeeded ?" + + finally: + sconf.Finish() + + def test_CheckDeclaration(self): + """Test SConf.CheckDeclaration() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + # In ANSI C, malloc should be available in stdlib + r = sconf.CheckDeclaration('malloc', includes = "#include ") + assert r, "malloc not declared ??" + # For C++, __cplusplus should be declared + r = sconf.CheckDeclaration('__cplusplus', language = 'C++') + assert r, "__cplusplus not declared in C++ ??" + r = sconf.CheckDeclaration('__cplusplus', language = 'C') + assert not r, "__cplusplus declared in C ??" + finally: + sconf.Finish() + + def test_(self): + """Test SConf.CheckType() + """ + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + # CheckType() + r = sconf.CheckType('off_t', '#include \n') + assert r, "did not find off_t" + r = sconf.CheckType('hopefullynotypedef_not') + assert not r, "unexpectedly found hopefullynotypedef_not" + + finally: + sconf.Finish() + + def test_CustomChecks(self): + """Test Custom Checks + """ + def CheckCustom(test): + test.Message( "Checking UserTest ... " ) + prog = """ +#include + +int main() { + printf( "Hello" ); + return 0; +} +""" + (ret, output) = test.TryRun( prog, ".c" ) + test.Result( ret ) + assert ret and output == "Hello", (ret, output) + return ret + + + self._resetSConfState() + sconf = self.SConf.SConf(self.scons_env, + custom_tests={'CheckCustom': CheckCustom}, + conf_dir=self.test.workpath('config.tests'), + log_file=self.test.workpath('config.log')) + try: + ret = sconf.CheckCustom() + assert ret, ret + finally: + sconf.Finish() + + +if __name__ == "__main__": + suite = unittest.makeSuite(SConfTestCase, 'test_') + res = unittest.TextTestRunner().run(suite) + if not res.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/SConsign.py b/src/engine/SCons/SConsign.py new file mode 100644 index 0000000..2149f0d --- /dev/null +++ b/src/engine/SCons/SConsign.py @@ -0,0 +1,381 @@ +"""SCons.SConsign + +Writing and reading information to the .sconsign file or files. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/SConsign.py 4577 2009/12/27 19:44:43 scons" + +import cPickle +import os +import os.path + +import SCons.dblite +import SCons.Warnings + +def corrupt_dblite_warning(filename): + SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, + "Ignoring corrupt .sconsign file: %s"%filename) + +SCons.dblite.ignore_corrupt_dbfiles = 1 +SCons.dblite.corruption_warning = corrupt_dblite_warning + +#XXX Get rid of the global array so this becomes re-entrant. +sig_files = [] + +# Info for the database SConsign implementation (now the default): +# "DataBase" is a dictionary that maps top-level SConstruct directories +# to open database handles. +# "DB_Module" is the Python database module to create the handles. +# "DB_Name" is the base name of the database file (minus any +# extension the underlying DB module will add). +DataBase = {} +DB_Module = SCons.dblite +DB_Name = ".sconsign" +DB_sync_list = [] + +def Get_DataBase(dir): + global DataBase, DB_Module, DB_Name + top = dir.fs.Top + if not os.path.isabs(DB_Name) and top.repositories: + mode = "c" + for d in [top] + top.repositories: + if dir.is_under(d): + try: + return DataBase[d], mode + except KeyError: + path = d.entry_abspath(DB_Name) + try: db = DataBase[d] = DB_Module.open(path, mode) + except (IOError, OSError): pass + else: + if mode != "r": + DB_sync_list.append(db) + return db, mode + mode = "r" + try: + return DataBase[top], "c" + except KeyError: + db = DataBase[top] = DB_Module.open(DB_Name, "c") + DB_sync_list.append(db) + return db, "c" + except TypeError: + print "DataBase =", DataBase + raise + +def Reset(): + """Reset global state. Used by unit tests that end up using + SConsign multiple times to get a clean slate for each test.""" + global sig_files, DB_sync_list + sig_files = [] + DB_sync_list = [] + +normcase = os.path.normcase + +def write(): + global sig_files + for sig_file in sig_files: + sig_file.write(sync=0) + for db in DB_sync_list: + try: + syncmethod = db.sync + except AttributeError: + pass # Not all anydbm modules have sync() methods. + else: + syncmethod() + +class SConsignEntry: + """ + Wrapper class for the generic entry in a .sconsign file. + The Node subclass populates it with attributes as it pleases. + + XXX As coded below, we do expect a '.binfo' attribute to be added, + but we'll probably generalize this in the next refactorings. + """ + current_version_id = 1 + def __init__(self): + # Create an object attribute from the class attribute so it ends up + # in the pickled data in the .sconsign file. + _version_id = self.current_version_id + def convert_to_sconsign(self): + self.binfo.convert_to_sconsign() + def convert_from_sconsign(self, dir, name): + self.binfo.convert_from_sconsign(dir, name) + +class Base: + """ + This is the controlling class for the signatures for the collection of + entries associated with a specific directory. The actual directory + association will be maintained by a subclass that is specific to + the underlying storage method. This class provides a common set of + methods for fetching and storing the individual bits of information + that make up signature entry. + """ + def __init__(self): + self.entries = {} + self.dirty = False + self.to_be_merged = {} + + def get_entry(self, filename): + """ + Fetch the specified entry attribute. + """ + return self.entries[filename] + + def set_entry(self, filename, obj): + """ + Set the entry. + """ + self.entries[filename] = obj + self.dirty = True + + def do_not_set_entry(self, filename, obj): + pass + + def store_info(self, filename, node): + entry = node.get_stored_info() + entry.binfo.merge(node.get_binfo()) + self.to_be_merged[filename] = node + self.dirty = True + + def do_not_store_info(self, filename, node): + pass + + def merge(self): + for key, node in self.to_be_merged.items(): + entry = node.get_stored_info() + try: + ninfo = entry.ninfo + except AttributeError: + # This happens with SConf Nodes, because the configuration + # subsystem takes direct control over how the build decision + # is made and its information stored. + pass + else: + ninfo.merge(node.get_ninfo()) + self.entries[key] = entry + self.to_be_merged = {} + +class DB(Base): + """ + A Base subclass that reads and writes signature information + from a global .sconsign.db* file--the actual file suffix is + determined by the database module. + """ + def __init__(self, dir): + Base.__init__(self) + + self.dir = dir + + db, mode = Get_DataBase(dir) + + # Read using the path relative to the top of the Repository + # (self.dir.tpath) from which we're fetching the signature + # information. + path = normcase(dir.tpath) + try: + rawentries = db[path] + except KeyError: + pass + else: + try: + self.entries = cPickle.loads(rawentries) + if type(self.entries) is not type({}): + self.entries = {} + raise TypeError + except KeyboardInterrupt: + raise + except Exception, e: + SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, + "Ignoring corrupt sconsign entry : %s (%s)\n"%(self.dir.tpath, e)) + for key, entry in self.entries.items(): + entry.convert_from_sconsign(dir, key) + + if mode == "r": + # This directory is actually under a repository, which means + # likely they're reaching in directly for a dependency on + # a file there. Don't actually set any entry info, so we + # won't try to write to that .sconsign.dblite file. + self.set_entry = self.do_not_set_entry + self.store_info = self.do_not_store_info + + global sig_files + sig_files.append(self) + + def write(self, sync=1): + if not self.dirty: + return + + self.merge() + + db, mode = Get_DataBase(self.dir) + + # Write using the path relative to the top of the SConstruct + # directory (self.dir.path), not relative to the top of + # the Repository; we only write to our own .sconsign file, + # not to .sconsign files in Repositories. + path = normcase(self.dir.path) + for key, entry in self.entries.items(): + entry.convert_to_sconsign() + db[path] = cPickle.dumps(self.entries, 1) + + if sync: + try: + syncmethod = db.sync + except AttributeError: + # Not all anydbm modules have sync() methods. + pass + else: + syncmethod() + +class Dir(Base): + def __init__(self, fp=None, dir=None): + """ + fp - file pointer to read entries from + """ + Base.__init__(self) + + if not fp: + return + + self.entries = cPickle.load(fp) + if type(self.entries) is not type({}): + self.entries = {} + raise TypeError + + if dir: + for key, entry in self.entries.items(): + entry.convert_from_sconsign(dir, key) + +class DirFile(Dir): + """ + Encapsulates reading and writing a per-directory .sconsign file. + """ + def __init__(self, dir): + """ + dir - the directory for the file + """ + + self.dir = dir + self.sconsign = os.path.join(dir.path, '.sconsign') + + try: + fp = open(self.sconsign, 'rb') + except IOError: + fp = None + + try: + Dir.__init__(self, fp, dir) + except KeyboardInterrupt: + raise + except: + SCons.Warnings.warn(SCons.Warnings.CorruptSConsignWarning, + "Ignoring corrupt .sconsign file: %s"%self.sconsign) + + global sig_files + sig_files.append(self) + + def write(self, sync=1): + """ + Write the .sconsign file to disk. + + Try to write to a temporary file first, and rename it if we + succeed. If we can't write to the temporary file, it's + probably because the directory isn't writable (and if so, + how did we build anything in this directory, anyway?), so + try to write directly to the .sconsign file as a backup. + If we can't rename, try to copy the temporary contents back + to the .sconsign file. Either way, always try to remove + the temporary file at the end. + """ + if not self.dirty: + return + + self.merge() + + temp = os.path.join(self.dir.path, '.scons%d' % os.getpid()) + try: + file = open(temp, 'wb') + fname = temp + except IOError: + try: + file = open(self.sconsign, 'wb') + fname = self.sconsign + except IOError: + return + for key, entry in self.entries.items(): + entry.convert_to_sconsign() + cPickle.dump(self.entries, file, 1) + file.close() + if fname != self.sconsign: + try: + mode = os.stat(self.sconsign)[0] + os.chmod(self.sconsign, 0666) + os.unlink(self.sconsign) + except (IOError, OSError): + # Try to carry on in the face of either OSError + # (things like permission issues) or IOError (disk + # or network issues). If there's a really dangerous + # issue, it should get re-raised by the calls below. + pass + try: + os.rename(fname, self.sconsign) + except OSError: + # An OSError failure to rename may indicate something + # like the directory has no write permission, but + # the .sconsign file itself might still be writable, + # so try writing on top of it directly. An IOError + # here, or in any of the following calls, would get + # raised, indicating something like a potentially + # serious disk or network issue. + open(self.sconsign, 'wb').write(open(fname, 'rb').read()) + os.chmod(self.sconsign, mode) + try: + os.unlink(temp) + except (IOError, OSError): + pass + +ForDirectory = DB + +def File(name, dbm_module=None): + """ + Arrange for all signatures to be stored in a global .sconsign.db* + file. + """ + global ForDirectory, DB_Name, DB_Module + if name is None: + ForDirectory = DirFile + DB_Module = None + else: + ForDirectory = DB + DB_Name = name + if not dbm_module is None: + DB_Module = dbm_module + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/SConsignTests.py b/src/engine/SCons/SConsignTests.py new file mode 100644 index 0000000..1ebe31f --- /dev/null +++ b/src/engine/SCons/SConsignTests.py @@ -0,0 +1,397 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/SConsignTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import sys +import TestCmd +import unittest + +import SCons.dblite + +import SCons.SConsign + +class BuildInfo: + def merge(self, object): + pass + +class DummySConsignEntry: + def __init__(self, name): + self.name = name + self.binfo = BuildInfo() + def convert_to_sconsign(self): + self.c_to_s = 1 + def convert_from_sconsign(self, dir, name): + self.c_from_s = 1 + +class FS: + def __init__(self, top): + self.Top = top + self.Top.repositories = [] + +class DummyNode: + def __init__(self, path='not_a_valid_path', binfo=None): + self.path = path + self.tpath = path + self.fs = FS(self) + self.binfo = binfo + def get_stored_info(self): + return self.binfo + def get_binfo(self): + return self.binfo + +class SConsignTestCase(unittest.TestCase): + def setUp(self): + self.save_cwd = os.getcwd() + self.test = TestCmd.TestCmd(workdir = '') + os.chdir(self.test.workpath('')) + def tearDown(self): + self.test.cleanup() + SCons.SConsign.Reset() + os.chdir(self.save_cwd) + +class BaseTestCase(SConsignTestCase): + + def test_Base(self): + aaa = DummySConsignEntry('aaa') + bbb = DummySConsignEntry('bbb') + bbb.arg1 = 'bbb arg1' + ccc = DummySConsignEntry('ccc') + ccc.arg2 = 'ccc arg2' + + f = SCons.SConsign.Base() + f.set_entry('aaa', aaa) + f.set_entry('bbb', bbb) + + #f.merge() + + e = f.get_entry('aaa') + assert e == aaa, e + assert e.name == 'aaa', e.name + + e = f.get_entry('bbb') + assert e == bbb, e + assert e.name == 'bbb', e.name + assert e.arg1 == 'bbb arg1', e.arg1 + assert not hasattr(e, 'arg2'), e + + f.set_entry('bbb', ccc) + + e = f.get_entry('bbb') + assert e.name == 'ccc', e.name + assert not hasattr(e, 'arg1'), e + assert e.arg2 == 'ccc arg2', e.arg1 + + ddd = DummySConsignEntry('ddd') + eee = DummySConsignEntry('eee') + fff = DummySConsignEntry('fff') + fff.arg = 'fff arg' + + f = SCons.SConsign.Base() + f.set_entry('ddd', ddd) + f.set_entry('eee', eee) + + e = f.get_entry('ddd') + assert e == ddd, e + assert e.name == 'ddd', e.name + + e = f.get_entry('eee') + assert e == eee, e + assert e.name == 'eee', e.name + assert not hasattr(e, 'arg'), e + + f.set_entry('eee', fff) + + e = f.get_entry('eee') + assert e.name == 'fff', e.name + assert e.arg == 'fff arg', e.arg + + def test_store_info(self): + aaa = DummySConsignEntry('aaa') + bbb = DummySConsignEntry('bbb') + bbb.arg1 = 'bbb arg1' + ccc = DummySConsignEntry('ccc') + ccc.arg2 = 'ccc arg2' + + f = SCons.SConsign.Base() + f.store_info('aaa', DummyNode('aaa', aaa)) + f.store_info('bbb', DummyNode('bbb', bbb)) + + try: + e = f.get_entry('aaa') + except KeyError: + pass + else: + raise "unexpected entry %s" % e + + try: + e = f.get_entry('bbb') + except KeyError: + pass + else: + raise "unexpected entry %s" % e + + f.merge() + + e = f.get_entry('aaa') + assert e == aaa, "aaa = %s, e = %s" % (aaa, e) + assert e.name == 'aaa', e.name + + e = f.get_entry('bbb') + assert e == bbb, "bbb = %s, e = %s" % (bbb, e) + assert e.name == 'bbb', e.name + assert e.arg1 == 'bbb arg1', e.arg1 + assert not hasattr(e, 'arg2'), e + + f.store_info('bbb', DummyNode('bbb', ccc)) + + e = f.get_entry('bbb') + assert e == bbb, e + assert e.name == 'bbb', e.name + assert e.arg1 == 'bbb arg1', e.arg1 + assert not hasattr(e, 'arg2'), e + + f.merge() + + e = f.get_entry('bbb') + assert e.name == 'ccc', e.name + assert not hasattr(e, 'arg1'), e + assert e.arg2 == 'ccc arg2', e.arg1 + + ddd = DummySConsignEntry('ddd') + eee = DummySConsignEntry('eee') + fff = DummySConsignEntry('fff') + fff.arg = 'fff arg' + + f = SCons.SConsign.Base() + f.store_info('ddd', DummyNode('ddd', ddd)) + f.store_info('eee', DummyNode('eee', eee)) + + f.merge() + + e = f.get_entry('ddd') + assert e == ddd, e + assert e.name == 'ddd', e.name + + e = f.get_entry('eee') + assert e == eee, e + assert e.name == 'eee', e.name + assert not hasattr(e, 'arg'), e + + f.store_info('eee', DummyNode('eee', fff)) + + e = f.get_entry('eee') + assert e == eee, e + assert e.name == 'eee', e.name + assert not hasattr(e, 'arg'), e + + f.merge() + + e = f.get_entry('eee') + assert e.name == 'fff', e.name + assert e.arg == 'fff arg', e.arg + +class SConsignDBTestCase(SConsignTestCase): + + def test_SConsignDB(self): + save_DataBase = SCons.SConsign.DataBase + SCons.SConsign.DataBase = {} + try: + d1 = SCons.SConsign.DB(DummyNode('dir1')) + d1.set_entry('aaa', DummySConsignEntry('aaa name')) + d1.set_entry('bbb', DummySConsignEntry('bbb name')) + + aaa = d1.get_entry('aaa') + assert aaa.name == 'aaa name' + bbb = d1.get_entry('bbb') + assert bbb.name == 'bbb name' + + d2 = SCons.SConsign.DB(DummyNode('dir2')) + d2.set_entry('ccc', DummySConsignEntry('ccc name')) + d2.set_entry('ddd', DummySConsignEntry('ddd name')) + + ccc = d2.get_entry('ccc') + assert ccc.name == 'ccc name' + ddd = d2.get_entry('ddd') + assert ddd.name == 'ddd name' + + d31 = SCons.SConsign.DB(DummyNode('dir3/sub1')) + d31.set_entry('eee', DummySConsignEntry('eee name')) + d31.set_entry('fff', DummySConsignEntry('fff name')) + + eee = d31.get_entry('eee') + assert eee.name == 'eee name' + fff = d31.get_entry('fff') + assert fff.name == 'fff name' + + d32 = SCons.SConsign.DB(DummyNode('dir3%ssub2' % os.sep)) + d32.set_entry('ggg', DummySConsignEntry('ggg name')) + d32.set_entry('hhh', DummySConsignEntry('hhh name')) + + ggg = d32.get_entry('ggg') + assert ggg.name == 'ggg name' + hhh = d32.get_entry('hhh') + assert hhh.name == 'hhh name' + + finally: + + SCons.SConsign.DataBase = save_DataBase + +class SConsignDirFileTestCase(SConsignTestCase): + + def test_SConsignDirFile(self): + bi_foo = DummySConsignEntry('foo') + bi_bar = DummySConsignEntry('bar') + + f = SCons.SConsign.DirFile(DummyNode()) + f.set_entry('foo', bi_foo) + f.set_entry('bar', bi_bar) + + e = f.get_entry('foo') + assert e == bi_foo, e + assert e.name == 'foo', e.name + + e = f.get_entry('bar') + assert e == bi_bar, e + assert e.name == 'bar', e.name + assert not hasattr(e, 'arg'), e + + bbb = DummySConsignEntry('bbb') + bbb.arg = 'bbb arg' + + f.set_entry('bar', bbb) + + e = f.get_entry('bar') + assert e.name == 'bbb', e.name + assert e.arg == 'bbb arg', e.arg + + +class SConsignFileTestCase(SConsignTestCase): + + def test_SConsignFile(self): + test = self.test + file = test.workpath('sconsign_file') + + assert SCons.SConsign.DataBase == {}, SCons.SConsign.DataBase + assert SCons.SConsign.DB_Name == ".sconsign", SCons.SConsign.DB_Name + assert SCons.SConsign.DB_Module is SCons.dblite, SCons.SConsign.DB_Module + + SCons.SConsign.File(file) + + assert SCons.SConsign.DataBase == {}, SCons.SConsign.DataBase + assert SCons.SConsign.DB_Name is file, SCons.SConsign.DB_Name + assert SCons.SConsign.DB_Module is SCons.dblite, SCons.SConsign.DB_Module + + SCons.SConsign.File(None) + + assert SCons.SConsign.DataBase == {}, SCons.SConsign.DataBase + assert SCons.SConsign.DB_Name is file, SCons.SConsign.DB_Name + assert SCons.SConsign.DB_Module is None, SCons.SConsign.DB_Module + + class Fake_DBM: + def open(self, name, mode): + self.name = name + self.mode = mode + return self + def __getitem__(self, key): + pass + def __setitem__(self, key, value): + pass + + fake_dbm = Fake_DBM() + + SCons.SConsign.File(file, fake_dbm) + + assert SCons.SConsign.DataBase == {}, SCons.SConsign.DataBase + assert SCons.SConsign.DB_Name is file, SCons.SConsign.DB_Name + assert SCons.SConsign.DB_Module is fake_dbm, SCons.SConsign.DB_Module + assert not hasattr(fake_dbm, 'name'), fake_dbm + assert not hasattr(fake_dbm, 'mode'), fake_dbm + + SCons.SConsign.ForDirectory(DummyNode(test.workpath('dir'))) + + assert not SCons.SConsign.DataBase is None, SCons.SConsign.DataBase + assert fake_dbm.name == file, fake_dbm.name + assert fake_dbm.mode == "c", fake_dbm.mode + + +class writeTestCase(SConsignTestCase): + + def test_write(self): + + test = self.test + file = test.workpath('sconsign_file') + + class Fake_DBM: + def __getitem__(self, key): + return None + def __setitem__(self, key, value): + pass + def open(self, name, mode): + self.sync_count = 0 + return self + def sync(self): + self.sync_count = self.sync_count + 1 + + fake_dbm = Fake_DBM() + + SCons.SConsign.DataBase = {} + SCons.SConsign.File(file, fake_dbm) + + f = SCons.SConsign.DB(DummyNode()) + + bi_foo = DummySConsignEntry('foo') + bi_bar = DummySConsignEntry('bar') + f.set_entry('foo', bi_foo) + f.set_entry('bar', bi_bar) + + SCons.SConsign.write() + + assert bi_foo.c_to_s, bi_foo.c_to_s + assert bi_bar.c_to_s, bi_bar.c_to_s + + assert fake_dbm.sync_count == 1, fake_dbm.sync_count + + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + BaseTestCase, + SConsignDBTestCase, + SConsignDirFileTestCase, + SConsignFileTestCase, + writeTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/C.py b/src/engine/SCons/Scanner/C.py new file mode 100644 index 0000000..68ba74b --- /dev/null +++ b/src/engine/SCons/Scanner/C.py @@ -0,0 +1,132 @@ +"""SCons.Scanner.C + +This module implements the depenency scanner for C/C++ code. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/C.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner +import SCons.Util + +import SCons.cpp + +class SConsCPPScanner(SCons.cpp.PreProcessor): + """ + SCons-specific subclass of the cpp.py module's processing. + + We subclass this so that: 1) we can deal with files represented + by Nodes, not strings; 2) we can keep track of the files that are + missing. + """ + def __init__(self, *args, **kw): + apply(SCons.cpp.PreProcessor.__init__, (self,)+args, kw) + self.missing = [] + def initialize_result(self, fname): + self.result = SCons.Util.UniqueList([fname]) + def finalize_result(self, fname): + return self.result[1:] + def find_include_file(self, t): + keyword, quote, fname = t + result = SCons.Node.FS.find_file(fname, self.searchpath[quote]) + if not result: + self.missing.append((fname, self.current_file)) + return result + def read_file(self, file): + try: + fp = open(str(file.rfile())) + except EnvironmentError, e: + self.missing.append((file, self.current_file)) + return '' + else: + return fp.read() + +def dictify_CPPDEFINES(env): + cppdefines = env.get('CPPDEFINES', {}) + if cppdefines is None: + return {} + if SCons.Util.is_Sequence(cppdefines): + result = {} + for c in cppdefines: + if SCons.Util.is_Sequence(c): + result[c[0]] = c[1] + else: + result[c] = None + return result + if not SCons.Util.is_Dict(cppdefines): + return {cppdefines : None} + return cppdefines + +class SConsCPPScannerWrapper: + """ + The SCons wrapper around a cpp.py scanner. + + This is the actual glue between the calling conventions of generic + SCons scanners, and the (subclass of) cpp.py class that knows how + to look for #include lines with reasonably real C-preprocessor-like + evaluation of #if/#ifdef/#else/#elif lines. + """ + def __init__(self, name, variable): + self.name = name + self.path = SCons.Scanner.FindPathDirs(variable) + def __call__(self, node, env, path = ()): + cpp = SConsCPPScanner(current = node.get_dir(), + cpppath = path, + dict = dictify_CPPDEFINES(env)) + result = cpp(node) + for included, includer in cpp.missing: + fmt = "No dependency generated for file: %s (included from: %s) -- file not found" + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + fmt % (included, includer)) + return result + + def recurse_nodes(self, nodes): + return nodes + def select(self, node): + return self + +def CScanner(): + """Return a prototype Scanner instance for scanning source files + that use the C pre-processor""" + + # Here's how we would (or might) use the CPP scanner code above that + # knows how to evaluate #if/#ifdef/#else/#elif lines when searching + # for #includes. This is commented out for now until we add the + # right configurability to let users pick between the scanners. + #return SConsCPPScannerWrapper("CScanner", "CPPPATH") + + cs = SCons.Scanner.ClassicCPP("CScanner", + "$CPPSUFFIXES", + "CPPPATH", + '^[ \t]*#[ \t]*(?:include|import)[ \t]*(<|")([^>"]+)(>|")') + return cs + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/CTests.py b/src/engine/SCons/Scanner/CTests.py new file mode 100644 index 0000000..6d7f8a2 --- /dev/null +++ b/src/engine/SCons/Scanner/CTests.py @@ -0,0 +1,468 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/CTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import sys +import TestCmd +import unittest +import UserDict + +import SCons.Node.FS +import SCons.Warnings + +import SCons.Scanner.C + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('f1.cpp',""" +#include \"f1.h\" +#include + +int main() +{ + return 0; +} +""") + +test.write('f2.cpp',""" +#include \"d1/f1.h\" +#include +#include \"f1.h\" +#import + +int main() +{ + return 0; +} +""") + +test.write('f3.cpp',""" +#include \t "f1.h" + \t #include "f2.h" +# \t include "f3-test.h" + +#include \t + \t #include +# \t include + +// #include "never.h" + +const char* x = "#include " + +int main() +{ + return 0; +} +""") + + +# for Emacs -> " + +test.subdir('d1', ['d1', 'd2']) + +headers = ['f1.h','f2.h', 'f3-test.h', 'fi.h', 'fj.h', 'never.h', + 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h', 'd1/fi.h', 'd1/fj.h', + 'd1/d2/f1.h', 'd1/d2/f2.h', 'd1/d2/f3-test.h', + 'd1/d2/f4.h', 'd1/d2/fi.h', 'd1/d2/fj.h'] + +for h in headers: + test.write(h, " ") + +test.write('f2.h',""" +#include "fi.h" +""") + +test.write('f3-test.h',""" +#include +""") + + +test.subdir('include', 'subdir', ['subdir', 'include']) + +test.write('fa.cpp',""" +#include \"fa.h\" +#include + +int main() +{ + return 0; +} +""") + +test.write(['include', 'fa.h'], "\n") +test.write(['include', 'fb.h'], "\n") +test.write(['subdir', 'include', 'fa.h'], "\n") +test.write(['subdir', 'include', 'fb.h'], "\n") + + +test.subdir('repository', ['repository', 'include'], + ['repository', 'src' ]) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.h'], "\n") + +test.write(['work', 'src', 'fff.c'], """ +#include +#include + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'aaa.c'], """ +#include "bbb.h" + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'bbb.h'], "\n") + +test.write([ 'repository', 'src', 'ccc.c'], """ +#include "ddd.h" + +int main() +{ + return 0; +} +""") + +test.write([ 'repository', 'src', 'ddd.h'], "\n") + +test.write('f5.c', """\ +#include\"f5a.h\" +#include +""") + +test.write("f5a.h", "\n") +test.write("f5b.h", "\n") + +# define some helpers: + +class DummyEnvironment(UserDict.UserDict): + def __init__(self, **kw): + UserDict.UserDict.__init__(self) + self.data.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + return self.data + + def subst(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + + def subst_list(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return [self.data[strSubst[1:]]] + return [[strSubst]] + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase +else: + my_normpath = os.path.normpath + +def deps_match(self, deps, headers): + global my_normpath + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class CScannerTestCase1(unittest.TestCase): + def runTest(self): + """Find local files with no CPPPATH""" + env = DummyEnvironment(CPPPATH=[]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f1.cpp'), env, path) + headers = ['f1.h', 'f2.h'] + deps_match(self, deps, headers) + +class CScannerTestCase2(unittest.TestCase): + def runTest(self): + """Find a file in a CPPPATH directory""" + env = DummyEnvironment(CPPPATH=[test.workpath("d1")]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f1.cpp'), env, path) + headers = ['f1.h', 'd1/f2.h'] + deps_match(self, deps, headers) + +class CScannerTestCase3(unittest.TestCase): + def runTest(self): + """Find files in explicit subdirectories, ignore missing file""" + env = DummyEnvironment(CPPPATH=[test.workpath("d1")]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f2.cpp'), env, path) + headers = ['d1/f1.h', 'f1.h', 'd1/d2/f1.h'] + deps_match(self, deps, headers) + +class CScannerTestCase4(unittest.TestCase): + def runTest(self): + """Find files in explicit subdirectories""" + env = DummyEnvironment(CPPPATH=[test.workpath("d1"), test.workpath("d1/d2")]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f2.cpp'), env, path) + headers = ['d1/f1.h', 'f1.h', 'd1/d2/f1.h', 'd1/d2/f4.h'] + deps_match(self, deps, headers) + +class CScannerTestCase5(unittest.TestCase): + def runTest(self): + """Make sure files in repositories will get scanned""" + env = DummyEnvironment(CPPPATH=[]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + + n = env.File('f3.cpp') + def my_rexists(s=n): + s.rexists_called = 1 + return s.old_rexists() + setattr(n, 'old_rexists', n.rexists) + setattr(n, 'rexists', my_rexists) + + deps = s(n, env, path) + + # Make sure rexists() got called on the file node being + # scanned, essential for cooperation with VariantDir functionality. + assert n.rexists_called + + headers = ['f1.h', 'f2.h', 'f3-test.h', + 'd1/f1.h', 'd1/f2.h', 'd1/f3-test.h'] + deps_match(self, deps, headers) + +class CScannerTestCase6(unittest.TestCase): + def runTest(self): + """Find a same-named file in different directories when CPPPATH changes""" + env1 = DummyEnvironment(CPPPATH=[test.workpath("d1")]) + env2 = DummyEnvironment(CPPPATH=[test.workpath("d1/d2")]) + s = SCons.Scanner.C.CScanner() + path1 = s.path(env1) + path2 = s.path(env2) + deps1 = s(env1.File('f1.cpp'), env1, path1) + deps2 = s(env2.File('f1.cpp'), env2, path2) + headers1 = ['f1.h', 'd1/f2.h'] + headers2 = ['f1.h', 'd1/d2/f2.h'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class CScannerTestCase8(unittest.TestCase): + def runTest(self): + """Find files in a subdirectory relative to the current directory""" + env = DummyEnvironment(CPPPATH=["include"]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps1 = s(env.File('fa.cpp'), env, path) + env.fs.chdir(env.Dir('subdir')) + dir = env.fs.getcwd() + env.fs.chdir(env.Dir('')) + path = s.path(env, dir) + deps2 = s(env.File('#fa.cpp'), env, path) + headers1 = map(test.workpath, ['include/fa.h', 'include/fb.h']) + headers2 = ['include/fa.h', 'include/fb.h'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class CScannerTestCase9(unittest.TestCase): + def runTest(self): + """Generate a warning when we can't find a #included file""" + SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning) + class TestOut: + def __call__(self, x): + self.out = x + + to = TestOut() + to.out = None + SCons.Warnings._warningOut = to + test.write('fa.h','\n') + fs = SCons.Node.FS.FS(test.workpath('')) + env = DummyEnvironment(CPPPATH=[]) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(fs.File('fa.cpp'), env, path) + + # Did we catch the warning associated with not finding fb.h? + assert to.out + + deps_match(self, deps, [ 'fa.h' ]) + test.unlink('fa.h') + +class CScannerTestCase10(unittest.TestCase): + def runTest(self): + """Find files in the local directory when the scanned file is elsewhere""" + fs = SCons.Node.FS.FS(test.workpath('')) + fs.chdir(fs.Dir('include')) + env = DummyEnvironment(CPPPATH=[]) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + test.write('include/fa.cpp', test.read('fa.cpp')) + fs.chdir(fs.Dir('..')) + deps = s(fs.File('#include/fa.cpp'), env, path) + deps_match(self, deps, [ 'include/fa.h', 'include/fb.h' ]) + test.unlink('include/fa.cpp') + +class CScannerTestCase11(unittest.TestCase): + def runTest(self): + """Handle dependencies on a derived .h file in a non-existent directory""" + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.Repository(test.workpath('repository')) + + # Create a derived file in a directory that does not exist yet. + # This was a bug at one time. + f1=fs.File('include2/jjj.h') + f1.builder=1 + env = DummyEnvironment(CPPPATH=['include', 'include2']) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(fs.File('src/fff.c'), env, path) + deps_match(self, deps, [ test.workpath('repository/include/iii.h'), + 'include2/jjj.h' ]) + os.chdir(test.workpath('')) + +class CScannerTestCase12(unittest.TestCase): + def runTest(self): + """Find files in VariantDir() directories""" + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.VariantDir('build1', 'src', 1) + fs.VariantDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment(CPPPATH=[]) + env.fs = fs + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps1 = s(fs.File('build1/aaa.c'), env, path) + deps_match(self, deps1, [ 'build1/bbb.h' ]) + deps2 = s(fs.File('build2/aaa.c'), env, path) + deps_match(self, deps2, [ 'src/bbb.h' ]) + deps3 = s(fs.File('build1/ccc.c'), env, path) + deps_match(self, deps3, [ 'build1/ddd.h' ]) + deps4 = s(fs.File('build2/ccc.c'), env, path) + deps_match(self, deps4, [ test.workpath('repository/src/ddd.h') ]) + os.chdir(test.workpath('')) + +class CScannerTestCase13(unittest.TestCase): + def runTest(self): + """Find files in directories named in a substituted environment variable""" + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, test=test): + if arg == "$blah": + return test.workpath("d1") + else: + return arg + env = SubstEnvironment(CPPPATH=["$blah"]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f1.cpp'), env, path) + headers = ['f1.h', 'd1/f2.h'] + deps_match(self, deps, headers) + +class CScannerTestCase14(unittest.TestCase): + def runTest(self): + """Find files when there's no space between "#include" and the name""" + env = DummyEnvironment(CPPPATH=[]) + s = SCons.Scanner.C.CScanner() + path = s.path(env) + deps = s(env.File('f5.c'), env, path) + headers = ['f5a.h', 'f5b.h'] + deps_match(self, deps, headers) + +class CScannerTestCase15(unittest.TestCase): + def runTest(self): + """Verify scanner initialization with the suffixes in $CPPSUFFIXES""" + suffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", + ".h", ".H", ".hxx", ".hpp", ".hh", + ".F", ".fpp", ".FPP", + ".S", ".spp", ".SPP"] + env = DummyEnvironment(CPPSUFFIXES = suffixes) + s = SCons.Scanner.C.CScanner() + for suffix in suffixes: + assert suffix in s.get_skeys(env), "%s not in skeys" % suffix + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(CScannerTestCase1()) + suite.addTest(CScannerTestCase2()) + suite.addTest(CScannerTestCase3()) + suite.addTest(CScannerTestCase4()) + suite.addTest(CScannerTestCase5()) + suite.addTest(CScannerTestCase6()) + suite.addTest(CScannerTestCase8()) + suite.addTest(CScannerTestCase9()) + suite.addTest(CScannerTestCase10()) + suite.addTest(CScannerTestCase11()) + suite.addTest(CScannerTestCase12()) + suite.addTest(CScannerTestCase13()) + suite.addTest(CScannerTestCase14()) + suite.addTest(CScannerTestCase15()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/D.py b/src/engine/SCons/Scanner/D.py new file mode 100644 index 0000000..e0637c0 --- /dev/null +++ b/src/engine/SCons/Scanner/D.py @@ -0,0 +1,74 @@ +"""SCons.Scanner.D + +Scanner for the Digital Mars "D" programming language. + +Coded by Andy Friesen +17 Nov 2003 + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/D.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Scanner + +def DScanner(): + """Return a prototype Scanner instance for scanning D source files""" + ds = D() + return ds + +class D(SCons.Scanner.Classic): + def __init__ (self): + SCons.Scanner.Classic.__init__ (self, + name = "DScanner", + suffixes = '$DSUFFIXES', + path_variable = 'DPATH', + regex = 'import\s+(?:[a-zA-Z0-9_.]+)\s*(?:,\s*(?:[a-zA-Z0-9_.]+)\s*)*;') + + self.cre2 = re.compile ('(?:import\s)?\s*([a-zA-Z0-9_.]+)\s*(?:,|;)', re.M) + + def find_include(self, include, source_dir, path): + # translate dots (package separators) to slashes + inc = string.replace(include, '.', '/') + + i = SCons.Node.FS.find_file(inc + '.d', (source_dir,) + path) + if i is None: + i = SCons.Node.FS.find_file (inc + '.di', (source_dir,) + path) + return i, include + + def find_include_names(self, node): + includes = [] + for i in self.cre.findall(node.get_text_contents()): + includes = includes + self.cre2.findall(i) + return includes + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/Dir.py b/src/engine/SCons/Scanner/Dir.py new file mode 100644 index 0000000..eacd855 --- /dev/null +++ b/src/engine/SCons/Scanner/Dir.py @@ -0,0 +1,111 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/Dir.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner + +def only_dirs(nodes): + is_Dir = lambda n: isinstance(n.disambiguate(), SCons.Node.FS.Dir) + return filter(is_Dir, nodes) + +def DirScanner(**kw): + """Return a prototype Scanner instance for scanning + directories for on-disk files""" + kw['node_factory'] = SCons.Node.FS.Entry + kw['recursive'] = only_dirs + return apply(SCons.Scanner.Base, (scan_on_disk, "DirScanner"), kw) + +def DirEntryScanner(**kw): + """Return a prototype Scanner instance for "scanning" + directory Nodes for their in-memory entries""" + kw['node_factory'] = SCons.Node.FS.Entry + kw['recursive'] = None + return apply(SCons.Scanner.Base, (scan_in_memory, "DirEntryScanner"), kw) + +skip_entry = {} + +skip_entry_list = [ + '.', + '..', + '.sconsign', + # Used by the native dblite.py module. + '.sconsign.dblite', + # Used by dbm and dumbdbm. + '.sconsign.dir', + # Used by dbm. + '.sconsign.pag', + # Used by dumbdbm. + '.sconsign.dat', + '.sconsign.bak', + # Used by some dbm emulations using Berkeley DB. + '.sconsign.db', +] + +for skip in skip_entry_list: + skip_entry[skip] = 1 + skip_entry[SCons.Node.FS._my_normcase(skip)] = 1 + +do_not_scan = lambda k: not skip_entry.has_key(k) + +def scan_on_disk(node, env, path=()): + """ + Scans a directory for on-disk files and directories therein. + + Looking up the entries will add these to the in-memory Node tree + representation of the file system, so all we have to do is just + that and then call the in-memory scanning function. + """ + try: + flist = node.fs.listdir(node.abspath) + except (IOError, OSError): + return [] + e = node.Entry + for f in filter(do_not_scan, flist): + # Add ./ to the beginning of the file name so if it begins with a + # '#' we don't look it up relative to the top-level directory. + e('./' + f) + return scan_in_memory(node, env, path) + +def scan_in_memory(node, env, path=()): + """ + "Scans" a Node.FS.Dir for its in-memory entries. + """ + try: + entries = node.entries + except AttributeError: + # It's not a Node.FS.Dir (or doesn't look enough like one for + # our purposes), which can happen if a target list containing + # mixed Node types (Dirs and Files, for example) has a Dir as + # the first entry. + return [] + entry_list = filter(do_not_scan, entries.keys()) + entry_list.sort() + return map(lambda n, e=entries: e[n], entry_list) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/DirTests.py b/src/engine/SCons/Scanner/DirTests.py new file mode 100644 index 0000000..d244c5c --- /dev/null +++ b/src/engine/SCons/Scanner/DirTests.py @@ -0,0 +1,140 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/DirTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys +import types +import unittest + +import TestCmd +import SCons.Node.FS +import SCons.Scanner.Dir + +#class DummyNode: +# def __init__(self, name, fs): +# self.name = name +# self.abspath = test.workpath(name) +# self.fs = fs +# def __str__(self): +# return self.name +# def Entry(self, name): +# return self.fs.Entry(name) + +class DummyEnvironment: + def __init__(self, root): + self.fs = SCons.Node.FS.FS(root) + def Dir(self, name): + return self.fs.Dir(name) + def Entry(self, name): + return self.fs.Entry(name) + def File(self, name): + return self.fs.File(name) + def get_factory(self, factory): + return factory or self.fs.Entry + +class DirScannerTestBase(unittest.TestCase): + def setUp(self): + self.test = TestCmd.TestCmd(workdir = '') + + self.test.subdir('dir', ['dir', 'sub']) + + self.test.write(['dir', 'f1'], "dir/f1\n") + self.test.write(['dir', 'f2'], "dir/f2\n") + self.test.write(['dir', '.sconsign'], "dir/.sconsign\n") + self.test.write(['dir', '.sconsign.bak'], "dir/.sconsign.bak\n") + self.test.write(['dir', '.sconsign.dat'], "dir/.sconsign.dat\n") + self.test.write(['dir', '.sconsign.db'], "dir/.sconsign.db\n") + self.test.write(['dir', '.sconsign.dblite'], "dir/.sconsign.dblite\n") + self.test.write(['dir', '.sconsign.dir'], "dir/.sconsign.dir\n") + self.test.write(['dir', '.sconsign.pag'], "dir/.sconsign.pag\n") + self.test.write(['dir', 'sub', 'f3'], "dir/sub/f3\n") + self.test.write(['dir', 'sub', 'f4'], "dir/sub/f4\n") + self.test.write(['dir', 'sub', '.sconsign'], "dir/.sconsign\n") + self.test.write(['dir', 'sub', '.sconsign.bak'], "dir/.sconsign.bak\n") + self.test.write(['dir', 'sub', '.sconsign.dat'], "dir/.sconsign.dat\n") + self.test.write(['dir', 'sub', '.sconsign.dblite'], "dir/.sconsign.dblite\n") + self.test.write(['dir', 'sub', '.sconsign.dir'], "dir/.sconsign.dir\n") + self.test.write(['dir', 'sub', '.sconsign.pag'], "dir/.sconsign.pag\n") + +class DirScannerTestCase(DirScannerTestBase): + def runTest(self): + env = DummyEnvironment(self.test.workpath()) + + s = SCons.Scanner.Dir.DirScanner() + + expect = [ + os.path.join('dir', 'f1'), + os.path.join('dir', 'f2'), + os.path.join('dir', 'sub'), + ] + deps = s(env.Dir('dir'), env, ()) + sss = map(str, deps) + assert sss == expect, sss + + expect = [ + os.path.join('dir', 'sub', 'f3'), + os.path.join('dir', 'sub', 'f4'), + ] + deps = s(env.Dir('dir/sub'), env, ()) + sss = map(str, deps) + assert sss == expect, sss + +class DirEntryScannerTestCase(DirScannerTestBase): + def runTest(self): + env = DummyEnvironment(self.test.workpath()) + + s = SCons.Scanner.Dir.DirEntryScanner() + + deps = s(env.Dir('dir'), env, ()) + sss = map(str, deps) + assert sss == [], sss + + deps = s(env.Dir('dir/sub'), env, ()) + sss = map(str, deps) + assert sss == [], sss + + # Make sure we don't blow up if handed a non-Dir node. + deps = s(env.File('dir/f1'), env, ()) + sss = map(str, deps) + assert sss == [], sss + +def suite(): + suite = unittest.TestSuite() + suite.addTest(DirScannerTestCase()) + suite.addTest(DirEntryScannerTestCase()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/Fortran.py b/src/engine/SCons/Scanner/Fortran.py new file mode 100644 index 0000000..7c011a6 --- /dev/null +++ b/src/engine/SCons/Scanner/Fortran.py @@ -0,0 +1,320 @@ +"""SCons.Scanner.Fortran + +This module implements the dependency scanner for Fortran code. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/Fortran.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Node +import SCons.Node.FS +import SCons.Scanner +import SCons.Util +import SCons.Warnings + +class F90Scanner(SCons.Scanner.Classic): + """ + A Classic Scanner subclass for Fortran source files which takes + into account both USE and INCLUDE statements. This scanner will + work for both F77 and F90 (and beyond) compilers. + + Currently, this scanner assumes that the include files do not contain + USE statements. To enable the ability to deal with USE statements + in include files, add logic right after the module names are found + to loop over each include file, search for and locate each USE + statement, and append each module name to the list of dependencies. + Caching the search results in a common dictionary somewhere so that + the same include file is not searched multiple times would be a + smart thing to do. + """ + + def __init__(self, name, suffixes, path_variable, + use_regex, incl_regex, def_regex, *args, **kw): + + self.cre_use = re.compile(use_regex, re.M) + self.cre_incl = re.compile(incl_regex, re.M) + self.cre_def = re.compile(def_regex, re.M) + + def _scan(node, env, path, self=self): + node = node.rfile() + + if not node.exists(): + return [] + + return self.scan(node, env, path) + + kw['function'] = _scan + kw['path_function'] = SCons.Scanner.FindPathDirs(path_variable) + kw['recursive'] = 1 + kw['skeys'] = suffixes + kw['name'] = name + + apply(SCons.Scanner.Current.__init__, (self,) + args, kw) + + def scan(self, node, env, path=()): + + # cache the includes list in node so we only scan it once: + if node.includes != None: + mods_and_includes = node.includes + else: + # retrieve all included filenames + includes = self.cre_incl.findall(node.get_text_contents()) + # retrieve all USE'd module names + modules = self.cre_use.findall(node.get_text_contents()) + # retrieve all defined module names + defmodules = self.cre_def.findall(node.get_text_contents()) + + # Remove all USE'd module names that are defined in the same file + d = {} + for m in defmodules: + d[m] = 1 + modules = filter(lambda m, d=d: not d.has_key(m), modules) + #modules = self.undefinedModules(modules, defmodules) + + # Convert module name to a .mod filename + suffix = env.subst('$FORTRANMODSUFFIX') + modules = map(lambda x, s=suffix: string.lower(x) + s, modules) + # Remove unique items from the list + mods_and_includes = SCons.Util.unique(includes+modules) + node.includes = mods_and_includes + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the USE or INCLUDE line, which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally. + nodes = [] + source_dir = node.get_dir() + if callable(path): + path = path() + for dep in mods_and_includes: + n, i = self.find_include(dep, source_dir, path) + + if n is None: + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (referenced by: %s) -- file not found" % (i, node)) + else: + sortkey = self.sort_key(dep) + nodes.append((sortkey, n)) + + nodes.sort() + nodes = map(lambda pair: pair[1], nodes) + return nodes + +def FortranScan(path_variable="FORTRANPATH"): + """Return a prototype Scanner instance for scanning source files + for Fortran USE & INCLUDE statements""" + +# The USE statement regex matches the following: +# +# USE module_name +# USE :: module_name +# USE, INTRINSIC :: module_name +# USE, NON_INTRINSIC :: module_name +# +# Limitations +# +# -- While the regex can handle multiple USE statements on one line, +# it cannot properly handle them if they are commented out. +# In either of the following cases: +# +# ! USE mod_a ; USE mod_b [entire line is commented out] +# USE mod_a ! ; USE mod_b [in-line comment of second USE statement] +# +# the second module name (mod_b) will be picked up as a dependency +# even though it should be ignored. The only way I can see +# to rectify this would be to modify the scanner to eliminate +# the call to re.findall, read in the contents of the file, +# treating the comment character as an end-of-line character +# in addition to the normal linefeed, loop over each line, +# weeding out the comments, and looking for the USE statements. +# One advantage to this is that the regex passed to the scanner +# would no longer need to match a semicolon. +# +# -- I question whether or not we need to detect dependencies to +# INTRINSIC modules because these are built-in to the compiler. +# If we consider them a dependency, will SCons look for them, not +# find them, and kill the build? Or will we there be standard +# compiler-specific directories we will need to point to so the +# compiler and SCons can locate the proper object and mod files? + +# Here is a breakdown of the regex: +# +# (?i) : regex is case insensitive +# ^ : start of line +# (?: : group a collection of regex symbols without saving the match as a "group" +# ^|; : matches either the start of the line or a semicolon - semicolon +# ) : end the unsaved grouping +# \s* : any amount of white space +# USE : match the string USE, case insensitive +# (?: : group a collection of regex symbols without saving the match as a "group" +# \s+| : match one or more whitespace OR .... (the next entire grouped set of regex symbols) +# (?: : group a collection of regex symbols without saving the match as a "group" +# (?: : establish another unsaved grouping of regex symbols +# \s* : any amount of white space +# , : match a comma +# \s* : any amount of white space +# (?:NON_)? : optionally match the prefix NON_, case insensitive +# INTRINSIC : match the string INTRINSIC, case insensitive +# )? : optionally match the ", INTRINSIC/NON_INTRINSIC" grouped expression +# \s* : any amount of white space +# :: : match a double colon that must appear after the INTRINSIC/NON_INTRINSIC attribute +# ) : end the unsaved grouping +# ) : end the unsaved grouping +# \s* : match any amount of white space +# (\w+) : match the module name that is being USE'd +# +# + use_regex = "(?i)(?:^|;)\s*USE(?:\s+|(?:(?:\s*,\s*(?:NON_)?INTRINSIC)?\s*::))\s*(\w+)" + + +# The INCLUDE statement regex matches the following: +# +# INCLUDE 'some_Text' +# INCLUDE "some_Text" +# INCLUDE "some_Text" ; INCLUDE "some_Text" +# INCLUDE kind_"some_Text" +# INCLUDE kind_'some_Text" +# +# where some_Text can include any alphanumeric and/or special character +# as defined by the Fortran 2003 standard. +# +# Limitations: +# +# -- The Fortran standard dictates that a " or ' in the INCLUDE'd +# string must be represented as a "" or '', if the quotes that wrap +# the entire string are either a ' or ", respectively. While the +# regular expression below can detect the ' or " characters just fine, +# the scanning logic, presently is unable to detect them and reduce +# them to a single instance. This probably isn't an issue since, +# in practice, ' or " are not generally used in filenames. +# +# -- This regex will not properly deal with multiple INCLUDE statements +# when the entire line has been commented out, ala +# +# ! INCLUDE 'some_file' ; INCLUDE 'some_file' +# +# In such cases, it will properly ignore the first INCLUDE file, +# but will actually still pick up the second. Interestingly enough, +# the regex will properly deal with these cases: +# +# INCLUDE 'some_file' +# INCLUDE 'some_file' !; INCLUDE 'some_file' +# +# To get around the above limitation, the FORTRAN programmer could +# simply comment each INCLUDE statement separately, like this +# +# ! INCLUDE 'some_file' !; INCLUDE 'some_file' +# +# The way I see it, the only way to get around this limitation would +# be to modify the scanning logic to replace the calls to re.findall +# with a custom loop that processes each line separately, throwing +# away fully commented out lines before attempting to match against +# the INCLUDE syntax. +# +# Here is a breakdown of the regex: +# +# (?i) : regex is case insensitive +# (?: : begin a non-saving group that matches the following: +# ^ : either the start of the line +# | : or +# ['">]\s*; : a semicolon that follows a single quote, +# double quote or greater than symbol (with any +# amount of whitespace in between). This will +# allow the regex to match multiple INCLUDE +# statements per line (although it also requires +# the positive lookahead assertion that is +# used below). It will even properly deal with +# (i.e. ignore) cases in which the additional +# INCLUDES are part of an in-line comment, ala +# " INCLUDE 'someFile' ! ; INCLUDE 'someFile2' " +# ) : end of non-saving group +# \s* : any amount of white space +# INCLUDE : match the string INCLUDE, case insensitive +# \s+ : match one or more white space characters +# (?\w+_)? : match the optional "kind-param _" prefix allowed by the standard +# [<"'] : match the include delimiter - an apostrophe, double quote, or less than symbol +# (.+?) : match one or more characters that make up +# the included path and file name and save it +# in a group. The Fortran standard allows for +# any non-control character to be used. The dot +# operator will pick up any character, including +# control codes, but I can't conceive of anyone +# putting control codes in their file names. +# The question mark indicates it is non-greedy so +# that regex will match only up to the next quote, +# double quote, or greater than symbol +# (?=["'>]) : positive lookahead assertion to match the include +# delimiter - an apostrophe, double quote, or +# greater than symbol. This level of complexity +# is required so that the include delimiter is +# not consumed by the match, thus allowing the +# sub-regex discussed above to uniquely match a +# set of semicolon-separated INCLUDE statements +# (as allowed by the F2003 standard) + + include_regex = """(?i)(?:^|['">]\s*;)\s*INCLUDE\s+(?:\w+_)?[<"'](.+?)(?=["'>])""" + +# The MODULE statement regex finds module definitions by matching +# the following: +# +# MODULE module_name +# +# but *not* the following: +# +# MODULE PROCEDURE procedure_name +# +# Here is a breakdown of the regex: +# +# (?i) : regex is case insensitive +# ^\s* : any amount of white space +# MODULE : match the string MODULE, case insensitive +# \s+ : match one or more white space characters +# (?!PROCEDURE) : but *don't* match if the next word matches +# PROCEDURE (negative lookahead assertion), +# case insensitive +# (\w+) : match one or more alphanumeric characters +# that make up the defined module name and +# save it in a group + + def_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)""" + + scanner = F90Scanner("FortranScan", + "$FORTRANSUFFIXES", + path_variable, + use_regex, + include_regex, + def_regex) + return scanner + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/FortranTests.py b/src/engine/SCons/Scanner/FortranTests.py new file mode 100644 index 0000000..d3f3041 --- /dev/null +++ b/src/engine/SCons/Scanner/FortranTests.py @@ -0,0 +1,543 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/FortranTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import sys +import unittest + +import SCons.Scanner.Fortran +import SCons.Node.FS +import SCons.Warnings + +import TestCmd + +original = os.getcwd() + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('fff1.f',""" + PROGRAM FOO + INCLUDE 'f1.f' + include 'f2.f' + STOP + END +""") + +test.write('fff2.f',""" + PROGRAM FOO + INCLUDE 'f2.f' + include 'd1/f2.f' + INCLUDE 'd2/f2.f' + STOP + END +""") + +test.write('fff3.f',""" + PROGRAM FOO + INCLUDE 'f3.f' ; INCLUDE\t'd1/f3.f' + STOP + END +""") + + +# for Emacs -> " + +test.subdir('d1', ['d1', 'd2']) + +headers = ['fi.f', 'never.f', + 'd1/f1.f', 'd1/f2.f', 'd1/f3.f', 'd1/fi.f', + 'd1/d2/f1.f', 'd1/d2/f2.f', 'd1/d2/f3.f', + 'd1/d2/f4.f', 'd1/d2/fi.f'] + +for h in headers: + test.write(h, "\n") + + +test.subdir('include', 'subdir', ['subdir', 'include']) + +test.write('fff4.f',""" + PROGRAM FOO + INCLUDE 'f4.f' + STOP + END +""") + +test.write('include/f4.f', "\n") +test.write('subdir/include/f4.f', "\n") + +test.write('fff5.f',""" + PROGRAM FOO + INCLUDE 'f5.f' + INCLUDE 'not_there.f' + STOP + END +""") + +test.write('f5.f', "\n") + +test.subdir('repository', ['repository', 'include'], + [ 'repository', 'src' ]) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.f'], "\n") + +test.write(['work', 'src', 'fff.f'], """ + PROGRAM FOO + INCLUDE 'iii.f' + INCLUDE 'jjj.f' + STOP + END +""") + +test.write([ 'work', 'src', 'aaa.f'], """ + PROGRAM FOO + INCLUDE 'bbb.f' + STOP + END +""") + +test.write([ 'work', 'src', 'bbb.f'], "\n") + +test.write([ 'repository', 'src', 'ccc.f'], """ + PROGRAM FOO + INCLUDE 'ddd.f' + STOP + END +""") + +test.write([ 'repository', 'src', 'ddd.f'], "\n") + + +test.write('fff90a.f90',""" + PROGRAM FOO + +! Test comments - these includes should NOT be picked up +C INCLUDE 'fi.f' +# INCLUDE 'fi.f' + ! INCLUDE 'fi.f' + + INCLUDE 'f1.f' ! in-line comments are valid syntax + INCLUDE"fi.f" ! space is significant - this should be ignored + INCLUDE ! Absoft compiler allows greater than/less than delimiters +! +! Allow kind type parameters + INCLUDE kindType_"f3.f" + INCLUDE kind_Type_"f4.f" +! +! Test multiple statements per line - use various spacings between semicolons + incLUDE 'f5.f';include "f6.f" ; include ; include 'f8.f' ;include kindType_'f9.f' +! +! Test various USE statement syntaxes +! + USE Mod01 + use mod02 + use use + USE mOD03, ONLY : someVar + USE MOD04 ,only:someVar + USE Mod05 , ONLY: someVar ! in-line comment + USE Mod06,ONLY :someVar,someOtherVar + + USE mod07;USE mod08; USE mod09 ;USE mod10 ; USE mod11 ! Test various semicolon placements + use mod12 ;use mod13! Test comment at end of line + +! USE modi +! USE modia ; use modib ! Scanner regexp will only ignore the first - this is a deficiency in the regexp + ! USE modic ; ! use modid ! Scanner regexp should ignore both modules + USE mod14 !; USE modi ! Only ignore the second + USE mod15!;USE modi + USE mod16 ! ; USE modi + +! Test semicolon syntax - use various spacings + USE :: mod17 + USE::mod18 + USE ::mod19 ; USE:: mod20 + + use, non_intrinsic :: mod21, ONLY : someVar ; use,intrinsic:: mod22 + USE, NON_INTRINSIC::mod23 ; USE ,INTRINSIC ::mod24 + +USE mod25 ! Test USE statement at the beginning of line + + +; USE modi ! Scanner should ignore this since it isn't valid syntax + USEmodi ! No space in between USE and module name - ignore it + USE mod01 ! This one is a duplicate - there should only be one dependency to it. + + STOP + END +""") + +modules = ['mod01.mod', 'mod02.mod', 'mod03.mod', 'mod04.mod', 'mod05.mod', + 'mod06.mod', 'mod07.mod', 'mod08.mod', 'mod09.mod', 'mod10.mod', + 'mod11.mod', 'mod12.mod', 'mod13.mod', 'mod14.mod', 'mod15.mod', + 'mod16.mod', 'mod17.mod', 'mod18.mod', 'mod19.mod', 'mod20.mod', + 'mod21.mod', 'mod22.mod', 'mod23.mod', 'mod24.mod', 'mod25.mod'] + +for m in modules: + test.write(m, "\n") + +test.subdir('modules') +test.write(['modules', 'use.mod'], "\n") + +# define some helpers: + +class DummyEnvironment: + def __init__(self, listCppPath): + self.path = listCppPath + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + if not args: + return { 'FORTRANPATH': self.path, 'FORTRANMODSUFFIX' : ".mod" } + elif len(args) == 1 and args[0] == 'FORTRANPATH': + return self.path + else: + raise KeyError, "Dummy environment only has FORTRANPATH attribute." + + def has_key(self, key): + return self.Dictionary().has_key(key) + + def __getitem__(self,key): + return self.Dictionary()[key] + + def __setitem__(self,key,value): + self.Dictionary()[key] = value + + def __delitem__(self,key): + del self.Dictionary()[key] + + def subst(self, arg, target=None, source=None, conv=None): + if arg[0] == '$': + return self[arg[1:]] + return arg + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +def deps_match(self, deps, headers): + scanned = map(os.path.normpath, map(str, deps)) + expect = map(os.path.normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class FortranScannerTestCase1(unittest.TestCase): + def runTest(self): + test.write('f1.f', "\n") + test.write('f2.f', " INCLUDE 'fi.f'\n") + env = DummyEnvironment([]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['f1.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f1.f') + test.unlink('f2.f') + +class FortranScannerTestCase2(unittest.TestCase): + def runTest(self): + test.write('f1.f', "\n") + test.write('f2.f', " INCLUDE 'fi.f'\n") + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['f1.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f1.f') + test.unlink('f2.f') + +class FortranScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['d1/f1.f', 'd1/f2.f'] + deps_match(self, deps, headers) + +class FortranScannerTestCase4(unittest.TestCase): + def runTest(self): + test.write(['d1', 'f2.f'], " INCLUDE 'fi.f'\n") + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['d1/f1.f', 'd1/f2.f'] + deps_match(self, deps, headers) + test.write(['d1', 'f2.f'], "\n") + +class FortranScannerTestCase5(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'd1/f2.f'] + deps_match(self, deps, headers) + +class FortranScannerTestCase6(unittest.TestCase): + def runTest(self): + test.write('f2.f', "\n") + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f2.f') + +class FortranScannerTestCase7(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1/d2"), test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'd1/d2/f2.f'] + deps_match(self, deps, headers) + +class FortranScannerTestCase8(unittest.TestCase): + def runTest(self): + test.write('f2.f', "\n") + env = DummyEnvironment([test.workpath("d1/d2"), test.workpath("d1")]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff2.f'), env, path) + headers = ['d1/f2.f', 'd1/d2/f2.f', 'f2.f'] + deps_match(self, deps, headers) + test.unlink('f2.f') + +class FortranScannerTestCase9(unittest.TestCase): + def runTest(self): + test.write('f3.f', "\n") + env = DummyEnvironment([]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + + n = env.File('fff3.f') + def my_rexists(s=n): + s.rexists_called = 1 + return s.old_rexists() + setattr(n, 'old_rexists', n.rexists) + setattr(n, 'rexists', my_rexists) + + deps = s(n, env, path) + + # Make sure rexists() got called on the file node being + # scanned, essential for cooperation with VariantDir functionality. + assert n.rexists_called + + headers = ['d1/f3.f', 'f3.f'] + deps_match(self, deps, headers) + test.unlink('f3.f') + +class FortranScannerTestCase10(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(["include"]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps1 = s(env.File('fff4.f'), env, path) + env.fs.chdir(env.Dir('subdir')) + dir = env.fs.getcwd() + env.fs.chdir(env.Dir('')) + path = s.path(env, dir) + deps2 = s(env.File('#fff4.f'), env, path) + headers1 = map(test.workpath, ['include/f4.f']) + headers2 = ['include/f4.f'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class FortranScannerTestCase11(unittest.TestCase): + def runTest(self): + SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning) + class TestOut: + def __call__(self, x): + self.out = x + + to = TestOut() + to.out = None + SCons.Warnings._warningOut = to + env = DummyEnvironment([]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff5.f'), env, path) + + # Did we catch the warning from not finding not_there.f? + assert to.out + + deps_match(self, deps, [ 'f5.f' ]) + +class FortranScannerTestCase12(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + env.fs.chdir(env.Dir('include')) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + test.write('include/fff4.f', test.read('fff4.f')) + deps = s(env.File('#include/fff4.f'), env, path) + env.fs.chdir(env.Dir('')) + deps_match(self, deps, ['f4.f']) + test.unlink('include/fff4.f') + +class FortranScannerTestCase13(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.Repository(test.workpath('repository')) + + # Create a derived file in a directory that does not exist yet. + # This was a bug at one time. + f1=fs.File('include2/jjj.f') + f1.builder=1 + env = DummyEnvironment(['include','include2']) + env.fs = fs + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(fs.File('src/fff.f'), env, path) + deps_match(self, deps, [test.workpath('repository/include/iii.f'), 'include2/jjj.f']) + os.chdir(test.workpath('')) + +class FortranScannerTestCase14(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.VariantDir('build1', 'src', 1) + fs.VariantDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment([]) + env.fs = fs + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps1 = s(fs.File('build1/aaa.f'), env, path) + deps_match(self, deps1, [ 'build1/bbb.f' ]) + deps2 = s(fs.File('build2/aaa.f'), env, path) + deps_match(self, deps2, [ 'src/bbb.f' ]) + deps3 = s(fs.File('build1/ccc.f'), env, path) + deps_match(self, deps3, [ 'build1/ddd.f' ]) + deps4 = s(fs.File('build2/ccc.f'), env, path) + deps_match(self, deps4, [ test.workpath('repository/src/ddd.f') ]) + os.chdir(test.workpath('')) + +class FortranScannerTestCase15(unittest.TestCase): + def runTest(self): + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, test=test): + if arg == "$junk": + return test.workpath("d1") + else: + return arg + test.write(['d1', 'f2.f'], " INCLUDE 'fi.f'\n") + env = SubstEnvironment(["$junk"]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff1.f'), env, path) + headers = ['d1/f1.f', 'd1/f2.f'] + deps_match(self, deps, headers) + test.write(['d1', 'f2.f'], "\n") + +class FortranScannerTestCase16(unittest.TestCase): + def runTest(self): + test.write('f1.f', "\n") + test.write('f2.f', "\n") + test.write('f3.f', "\n") + test.write('f4.f', "\n") + test.write('f5.f', "\n") + test.write('f6.f', "\n") + test.write('f7.f', "\n") + test.write('f8.f', "\n") + test.write('f9.f', "\n") + test.write('f10.f', "\n") + env = DummyEnvironment([test.workpath('modules')]) + s = SCons.Scanner.Fortran.FortranScan() + path = s.path(env) + deps = s(env.File('fff90a.f90'), env, path) + headers = ['f1.f', 'f2.f', 'f3.f', 'f4.f', 'f5.f', 'f6.f', 'f7.f', 'f8.f', 'f9.f'] + modules = ['mod01.mod', 'mod02.mod', 'mod03.mod', 'mod04.mod', 'mod05.mod', + 'mod06.mod', 'mod07.mod', 'mod08.mod', 'mod09.mod', 'mod10.mod', + 'mod11.mod', 'mod12.mod', 'mod13.mod', 'mod14.mod', 'mod15.mod', + 'mod16.mod', 'mod17.mod', 'mod18.mod', 'mod19.mod', 'mod20.mod', + 'mod21.mod', 'mod22.mod', 'mod23.mod', 'mod24.mod', 'mod25.mod', 'modules/use.mod'] + deps_expected = headers + modules + deps_match(self, deps, deps_expected) + test.unlink('f1.f') + test.unlink('f2.f') + test.unlink('f3.f') + test.unlink('f4.f') + test.unlink('f5.f') + test.unlink('f6.f') + test.unlink('f7.f') + test.unlink('f8.f') + test.unlink('f9.f') + test.unlink('f10.f') + +def suite(): + suite = unittest.TestSuite() + suite.addTest(FortranScannerTestCase1()) + suite.addTest(FortranScannerTestCase2()) + suite.addTest(FortranScannerTestCase3()) + suite.addTest(FortranScannerTestCase4()) + suite.addTest(FortranScannerTestCase5()) + suite.addTest(FortranScannerTestCase6()) + suite.addTest(FortranScannerTestCase7()) + suite.addTest(FortranScannerTestCase8()) + suite.addTest(FortranScannerTestCase9()) + suite.addTest(FortranScannerTestCase10()) + suite.addTest(FortranScannerTestCase11()) + suite.addTest(FortranScannerTestCase12()) + suite.addTest(FortranScannerTestCase13()) + suite.addTest(FortranScannerTestCase14()) + suite.addTest(FortranScannerTestCase15()) + suite.addTest(FortranScannerTestCase16()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/IDL.py b/src/engine/SCons/Scanner/IDL.py new file mode 100644 index 0000000..d12f827 --- /dev/null +++ b/src/engine/SCons/Scanner/IDL.py @@ -0,0 +1,48 @@ +"""SCons.Scanner.IDL + +This module implements the depenency scanner for IDL (Interface +Definition Language) files. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/IDL.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner + +def IDLScan(): + """Return a prototype Scanner instance for scanning IDL source files""" + cs = SCons.Scanner.ClassicCPP("IDLScan", + "$IDLSUFFIXES", + "CPPPATH", + '^[ \t]*(?:#[ \t]*include|[ \t]*import)[ \t]+(<|")([^>"]+)(>|")') + return cs + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/IDLTests.py b/src/engine/SCons/Scanner/IDLTests.py new file mode 100644 index 0000000..9a53e38 --- /dev/null +++ b/src/engine/SCons/Scanner/IDLTests.py @@ -0,0 +1,453 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/IDLTests.py 4577 2009/12/27 19:44:43 scons" + +import TestCmd +import SCons.Scanner.IDL +import unittest +import sys +import os +import os.path +import SCons.Node.FS +import SCons.Warnings + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('t1.idl',''' +#include "f1.idl" +#include +import "f3.idl"; + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring("IBarObject Interface"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +''') + +test.write('t2.idl',""" +#include \"d1/f1.idl\" +#include +#include \"f1.idl\" +import ; + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring(\"IBarObject Interface\"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +""") + +test.write('t3.idl',""" +#include \t \"f1.idl\" + \t #include \"f2.idl\" +# \t include \"f3-test.idl\" + +#include \t + \t #include +# \t include + +import \t \"d1/f1.idl\" + \t import \"d1/f2.idl\" + +include \t \"never.idl\" + \t include \"never.idl\" + +// #include \"never.idl\" + +const char* x = \"#include \" + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring(\"IBarObject Interface\"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +""") + +test.subdir('d1', ['d1', 'd2']) + +headers = ['f1.idl','f2.idl', 'f3.idl', 'f3-test.idl', 'fi.idl', 'fj.idl', 'never.idl', + 'd1/f1.idl', 'd1/f2.idl', 'd1/f3-test.idl', 'd1/fi.idl', 'd1/fj.idl', + 'd1/d2/f1.idl', 'd1/d2/f2.idl', 'd1/d2/f3-test.idl', + 'd1/d2/f4.idl', 'd1/d2/fi.idl', 'd1/d2/fj.idl'] + +for h in headers: + test.write(h, " ") + +test.write('f2.idl',""" +#include "fi.idl" +""") + +test.write('f3-test.idl',""" +#include +""") + + +test.subdir('include', 'subdir', ['subdir', 'include']) + +test.write('t4.idl',""" +#include \"fa.idl\" +#include + +[ + object, + uuid(22995106-CE26-4561-AF1B-C71C6934B840), + dual, + helpstring(\"IBarObject Interface\"), + pointer_default(unique) +] +interface IBarObject : IDispatch +{ +}; +""") + +test.write(['include', 'fa.idl'], "\n") +test.write(['include', 'fb.idl'], "\n") +test.write(['subdir', 'include', 'fa.idl'], "\n") +test.write(['subdir', 'include', 'fb.idl'], "\n") + +test.subdir('repository', ['repository', 'include'], + ['repository', 'src' ]) +test.subdir('work', ['work', 'src']) + +test.write(['repository', 'include', 'iii.idl'], "\n") + +test.write(['work', 'src', 'fff.c'], """ +#include +#include + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'aaa.c'], """ +#include "bbb.idl" + +int main() +{ + return 0; +} +""") + +test.write([ 'work', 'src', 'bbb.idl'], "\n") + +test.write([ 'repository', 'src', 'ccc.c'], """ +#include "ddd.idl" + +int main() +{ + return 0; +} +""") + +test.write([ 'repository', 'src', 'ddd.idl'], "\n") + +# define some helpers: + +class DummyEnvironment: + def __init__(self, listCppPath): + self.path = listCppPath + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + if not args: + return { 'CPPPATH': self.path } + elif len(args) == 1 and args[0] == 'CPPPATH': + return self.path + else: + raise KeyError, "Dummy environment only has CPPPATH attribute." + + def subst(self, arg, target=None, source=None, conv=None): + return arg + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def has_key(self, key): + return self.Dictionary().has_key(key) + + def __getitem__(self,key): + return self.Dictionary()[key] + + def __setitem__(self,key,value): + self.Dictionary()[key] = value + + def __delitem__(self,key): + del self.Dictionary()[key] + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +global my_normpath +my_normpath = os.path.normpath + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase + +def deps_match(self, deps, headers): + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class IDLScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t1.idl'), env, path) + headers = ['f1.idl', 'f3.idl', 'f2.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t1.idl'), env, path) + headers = ['f1.idl', 'f3.idl', 'd1/f2.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t2.idl'), env, path) + headers = ['d1/f1.idl', 'f1.idl', 'd1/d2/f1.idl', 'f3.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase4(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([test.workpath("d1"), test.workpath("d1/d2")]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t2.idl'), env, path) + headers = ['d1/f1.idl', 'f1.idl', 'd1/d2/f1.idl', 'f3.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase5(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + + n = env.File('t3.idl') + def my_rexists(s=n): + s.rexists_called = 1 + return s.old_rexists() + setattr(n, 'old_rexists', n.rexists) + setattr(n, 'rexists', my_rexists) + + deps = s(n, env, path) + + # Make sure rexists() got called on the file node being + # scanned, essential for cooperation with VariantDir functionality. + assert n.rexists_called + + headers = ['d1/f1.idl', 'd1/f2.idl', + 'f1.idl', 'f2.idl', 'f3-test.idl', + 'd1/f1.idl', 'd1/f2.idl', 'd1/f3-test.idl'] + deps_match(self, deps, headers) + +class IDLScannerTestCase6(unittest.TestCase): + def runTest(self): + env1 = DummyEnvironment([test.workpath("d1")]) + env2 = DummyEnvironment([test.workpath("d1/d2")]) + s = SCons.Scanner.IDL.IDLScan() + path1 = s.path(env1) + path2 = s.path(env2) + deps1 = s(env1.File('t1.idl'), env1, path1) + deps2 = s(env2.File('t1.idl'), env2, path2) + headers1 = ['f1.idl', 'f3.idl', 'd1/f2.idl'] + headers2 = ['f1.idl', 'f3.idl', 'd1/d2/f2.idl'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class IDLScannerTestCase7(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(["include"]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps1 = s(env.File('t4.idl'), env, path) + env.fs.chdir(env.Dir('subdir')) + dir = env.fs.getcwd() + env.fs.chdir(env.Dir('')) + path = s.path(env, dir) + deps2 = s(env.File('#t4.idl'), env, path) + headers1 = map(test.workpath, ['include/fa.idl', 'include/fb.idl']) + headers2 = ['include/fa.idl', 'include/fb.idl'] + deps_match(self, deps1, headers1) + deps_match(self, deps2, headers2) + +class IDLScannerTestCase8(unittest.TestCase): + def runTest(self): + SCons.Warnings.enableWarningClass(SCons.Warnings.DependencyWarning) + class TestOut: + def __call__(self, x): + self.out = x + + to = TestOut() + to.out = None + SCons.Warnings._warningOut = to + test.write('fa.idl','\n') + env = DummyEnvironment([]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t4.idl'), env, path) + + # Did we catch the warning associated with not finding fb.idl? + assert to.out + + deps_match(self, deps, [ 'fa.idl' ]) + test.unlink('fa.idl') + +class IDLScannerTestCase9(unittest.TestCase): + def runTest(self): + env = DummyEnvironment([]) + env.fs.chdir(env.Dir('include')) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + test.write('include/t4.idl', test.read('t4.idl')) + deps = s(env.File('#include/t4.idl'), env, path) + env.fs.chdir(env.Dir('')) + deps_match(self, deps, [ 'fa.idl', 'fb.idl' ]) + test.unlink('include/t4.idl') + +class IDLScannerTestCase10(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.Repository(test.workpath('repository')) + + # Create a derived file in a directory that does not exist yet. + # This was a bug at one time. + env = DummyEnvironment(['include', 'include2']) + env.fs = fs + f1 = fs.File('include2/jjj.idl') + f1.builder = 1 + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(fs.File('src/fff.c'), env, path) + deps_match(self, deps, [ test.workpath('repository/include/iii.idl'), + 'include2/jjj.idl' ]) + os.chdir(test.workpath('')) + +class IDLScannerTestCase11(unittest.TestCase): + def runTest(self): + os.chdir(test.workpath('work')) + fs = SCons.Node.FS.FS(test.workpath('work')) + fs.VariantDir('build1', 'src', 1) + fs.VariantDir('build2', 'src', 0) + fs.Repository(test.workpath('repository')) + env = DummyEnvironment([]) + env.fs = fs + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps1 = s(fs.File('build1/aaa.c'), env, path) + deps_match(self, deps1, [ 'build1/bbb.idl' ]) + deps2 = s(fs.File('build2/aaa.c'), env, path) + deps_match(self, deps2, [ 'src/bbb.idl' ]) + deps3 = s(fs.File('build1/ccc.c'), env, path) + deps_match(self, deps3, [ 'build1/ddd.idl' ]) + deps4 = s(fs.File('build2/ccc.c'), env, path) + deps_match(self, deps4, [ test.workpath('repository/src/ddd.idl') ]) + os.chdir(test.workpath('')) + +class IDLScannerTestCase12(unittest.TestCase): + def runTest(self): + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, test=test): + if arg == "$blah": + return test.workpath("d1") + else: + return arg + env = SubstEnvironment(["$blah"]) + s = SCons.Scanner.IDL.IDLScan() + path = s.path(env) + deps = s(env.File('t1.idl'), env, path) + headers = ['f1.idl', 'f3.idl', 'd1/f2.idl'] + deps_match(self, deps, headers) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(IDLScannerTestCase1()) + suite.addTest(IDLScannerTestCase2()) + suite.addTest(IDLScannerTestCase3()) + suite.addTest(IDLScannerTestCase4()) + suite.addTest(IDLScannerTestCase5()) + suite.addTest(IDLScannerTestCase6()) + suite.addTest(IDLScannerTestCase7()) + suite.addTest(IDLScannerTestCase8()) + suite.addTest(IDLScannerTestCase9()) + suite.addTest(IDLScannerTestCase10()) + suite.addTest(IDLScannerTestCase11()) + suite.addTest(IDLScannerTestCase12()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/LaTeX.py b/src/engine/SCons/Scanner/LaTeX.py new file mode 100644 index 0000000..0aca69d --- /dev/null +++ b/src/engine/SCons/Scanner/LaTeX.py @@ -0,0 +1,345 @@ +"""SCons.Scanner.LaTeX + +This module implements the dependency scanner for LaTeX code. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/LaTeX.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import re + +import SCons.Scanner +import SCons.Util + +# list of graphics file extensions for TeX and LaTeX +TexGraphics = ['.eps', '.ps'] +LatexGraphics = ['.pdf', '.png', '.jpg', '.gif', '.tif'] + +# Used as a return value of modify_env_var if the variable is not set. +class _Null: + pass +_null = _Null + +# The user specifies the paths in env[variable], similar to other builders. +# They may be relative and must be converted to absolute, as expected +# by LaTeX and Co. The environment may already have some paths in +# env['ENV'][var]. These paths are honored, but the env[var] paths have +# higher precedence. All changes are un-done on exit. +def modify_env_var(env, var, abspath): + try: + save = env['ENV'][var] + except KeyError: + save = _null + env.PrependENVPath(var, abspath) + try: + if SCons.Util.is_List(env[var]): + #TODO(1.5) + #env.PrependENVPath(var, [os.path.abspath(str(p)) for p in env[var]]) + env.PrependENVPath(var, map(lambda p: os.path.abspath(str(p)), env[var])) + else: + # Split at os.pathsep to convert into absolute path + #TODO(1.5) env.PrependENVPath(var, [os.path.abspath(p) for p in str(env[var]).split(os.pathsep)]) + env.PrependENVPath(var, map(lambda p: os.path.abspath(p), string.split(str(env[var]), os.pathsep))) + except KeyError: + pass + + # Convert into a string explicitly to append ":" (without which it won't search system + # paths as well). The problem is that env.AppendENVPath(var, ":") + # does not work, refuses to append ":" (os.pathsep). + + if SCons.Util.is_List(env['ENV'][var]): + # TODO(1.5) + #env['ENV'][var] = os.pathsep.join(env['ENV'][var]) + env['ENV'][var] = string.join(env['ENV'][var], os.pathsep) + # Append the trailing os.pathsep character here to catch the case with no env[var] + env['ENV'][var] = env['ENV'][var] + os.pathsep + + return save + +class FindENVPathDirs: + """A class to bind a specific *PATH variable name to a function that + will return all of the *path directories.""" + def __init__(self, variable): + self.variable = variable + def __call__(self, env, dir=None, target=None, source=None, argument=None): + import SCons.PathList + try: + path = env['ENV'][self.variable] + except KeyError: + return () + + dir = dir or env.fs._cwd + path = SCons.PathList.PathList(path).subst_path(env, target, source) + return tuple(dir.Rfindalldirs(path)) + + + +def LaTeXScanner(): + """Return a prototype Scanner instance for scanning LaTeX source files + when built with latex. + """ + ds = LaTeX(name = "LaTeXScanner", + suffixes = '$LATEXSUFFIXES', + # in the search order, see below in LaTeX class docstring + graphics_extensions = TexGraphics, + recursive = 0) + return ds + +def PDFLaTeXScanner(): + """Return a prototype Scanner instance for scanning LaTeX source files + when built with pdflatex. + """ + ds = LaTeX(name = "PDFLaTeXScanner", + suffixes = '$LATEXSUFFIXES', + # in the search order, see below in LaTeX class docstring + graphics_extensions = LatexGraphics, + recursive = 0) + return ds + +class LaTeX(SCons.Scanner.Base): + """Class for scanning LaTeX files for included files. + + Unlike most scanners, which use regular expressions that just + return the included file name, this returns a tuple consisting + of the keyword for the inclusion ("include", "includegraphics", + "input", or "bibliography"), and then the file name itself. + Based on a quick look at LaTeX documentation, it seems that we + should append .tex suffix for the "include" keywords, append .tex if + there is no extension for the "input" keyword, and need to add .bib + for the "bibliography" keyword that does not accept extensions by itself. + + Finally, if there is no extension for an "includegraphics" keyword + latex will append .ps or .eps to find the file, while pdftex may use .pdf, + .jpg, .tif, .mps, or .png. + + The actual subset and search order may be altered by + DeclareGraphicsExtensions command. This complication is ignored. + The default order corresponds to experimentation with teTeX + $ latex --version + pdfeTeX 3.141592-1.21a-2.2 (Web2C 7.5.4) + kpathsea version 3.5.4 + The order is: + ['.eps', '.ps'] for latex + ['.png', '.pdf', '.jpg', '.tif']. + + Another difference is that the search path is determined by the type + of the file being searched: + env['TEXINPUTS'] for "input" and "include" keywords + env['TEXINPUTS'] for "includegraphics" keyword + env['TEXINPUTS'] for "lstinputlisting" keyword + env['BIBINPUTS'] for "bibliography" keyword + env['BSTINPUTS'] for "bibliographystyle" keyword + + FIXME: also look for the class or style in document[class|style]{} + FIXME: also look for the argument of bibliographystyle{} + """ + keyword_paths = {'include': 'TEXINPUTS', + 'input': 'TEXINPUTS', + 'includegraphics': 'TEXINPUTS', + 'bibliography': 'BIBINPUTS', + 'bibliographystyle': 'BSTINPUTS', + 'usepackage': 'TEXINPUTS', + 'lstinputlisting': 'TEXINPUTS'} + env_variables = SCons.Util.unique(keyword_paths.values()) + + def __init__(self, name, suffixes, graphics_extensions, *args, **kw): + + # We have to include \n with the % we exclude from the first part + # part of the regex because the expression is compiled with re.M. + # Without the \n, the ^ could match the beginning of a *previous* + # line followed by one or more newline characters (i.e. blank + # lines), interfering with a match on the next line. + regex = r'^[^%\n]*\\(include|includegraphics(?:\[[^\]]+\])?|lstinputlisting(?:\[[^\]]+\])?|input|bibliography|usepackage){([^}]*)}' + self.cre = re.compile(regex, re.M) + self.graphics_extensions = graphics_extensions + + def _scan(node, env, path=(), self=self): + node = node.rfile() + if not node.exists(): + return [] + return self.scan(node, path) + + class FindMultiPathDirs: + """The stock FindPathDirs function has the wrong granularity: + it is called once per target, while we need the path that depends + on what kind of included files is being searched. This wrapper + hides multiple instances of FindPathDirs, one per the LaTeX path + variable in the environment. When invoked, the function calculates + and returns all the required paths as a dictionary (converted into + a tuple to become hashable). Then the scan function converts it + back and uses a dictionary of tuples rather than a single tuple + of paths. + """ + def __init__(self, dictionary): + self.dictionary = {} + for k,n in dictionary.items(): + self.dictionary[k] = ( SCons.Scanner.FindPathDirs(n), + FindENVPathDirs(n) ) + + def __call__(self, env, dir=None, target=None, source=None, + argument=None): + di = {} + for k,(c,cENV) in self.dictionary.items(): + di[k] = ( c(env, dir=None, target=None, source=None, + argument=None) , + cENV(env, dir=None, target=None, source=None, + argument=None) ) + # To prevent "dict is not hashable error" + return tuple(di.items()) + + class LaTeXScanCheck: + """Skip all but LaTeX source files, i.e., do not scan *.eps, + *.pdf, *.jpg, etc. + """ + def __init__(self, suffixes): + self.suffixes = suffixes + def __call__(self, node, env): + current = not node.has_builder() or node.is_up_to_date() + scannable = node.get_suffix() in env.subst_list(self.suffixes)[0] + # Returning false means that the file is not scanned. + return scannable and current + + kw['function'] = _scan + kw['path_function'] = FindMultiPathDirs(LaTeX.keyword_paths) + kw['recursive'] = 1 + kw['skeys'] = suffixes + kw['scan_check'] = LaTeXScanCheck(suffixes) + kw['name'] = name + + apply(SCons.Scanner.Base.__init__, (self,) + args, kw) + + def _latex_names(self, include): + filename = include[1] + if include[0] == 'input': + base, ext = os.path.splitext( filename ) + if ext == "": + return [filename + '.tex'] + if (include[0] == 'include'): + return [filename + '.tex'] + if include[0] == 'bibliography': + base, ext = os.path.splitext( filename ) + if ext == "": + return [filename + '.bib'] + if include[0] == 'usepackage': + base, ext = os.path.splitext( filename ) + if ext == "": + return [filename + '.sty'] + if include[0] == 'includegraphics': + base, ext = os.path.splitext( filename ) + if ext == "": + #TODO(1.5) return [filename + e for e in self.graphics_extensions] + #return map(lambda e, f=filename: f+e, self.graphics_extensions + TexGraphics) + # use the line above to find dependency for PDF builder when only .eps figure is present + # Since it will be found if the user tell scons how to make the pdf figure leave it out for now. + return map(lambda e, f=filename: f+e, self.graphics_extensions) + return [filename] + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(str(include)) + + def find_include(self, include, source_dir, path): + try: + sub_path = path[include[0]] + except (IndexError, KeyError): + sub_path = () + try_names = self._latex_names(include) + for n in try_names: + # see if we find it using the path in env[var] + i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path[0]) + if i: + return i, include + # see if we find it using the path in env['ENV'][var] + i = SCons.Node.FS.find_file(n, (source_dir,) + sub_path[1]) + if i: + return i, include + return i, include + + def scan(self, node, path=()): + # Modify the default scan function to allow for the regular + # expression to return a comma separated list of file names + # as can be the case with the bibliography keyword. + + # Cache the includes list in node so we only scan it once: + path_dict = dict(list(path)) + noopt_cre = re.compile('\[.*$') + if node.includes != None: + includes = node.includes + else: + includes = self.cre.findall(node.get_text_contents()) + # 1. Split comma-separated lines, e.g. + # ('bibliography', 'phys,comp') + # should become two entries + # ('bibliography', 'phys') + # ('bibliography', 'comp') + # 2. Remove the options, e.g., such as + # ('includegraphics[clip,width=0.7\\linewidth]', 'picture.eps') + # should become + # ('includegraphics', 'picture.eps') + split_includes = [] + for include in includes: + inc_type = noopt_cre.sub('', include[0]) + inc_list = string.split(include[1],',') + for j in range(len(inc_list)): + split_includes.append( (inc_type, inc_list[j]) ) + # + includes = split_includes + node.includes = includes + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the \include, \input, etc. line. + # TODO: what about the comment in the original Classic scanner: + # """which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally.""" + nodes = [] + source_dir = node.get_dir() + for include in includes: + # + # Handle multiple filenames in include[1] + # + n, i = self.find_include(include, source_dir, path_dict) + if n is None: + # Do not bother with 'usepackage' warnings, as they most + # likely refer to system-level files + if include[0] != 'usepackage': + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) + else: + sortkey = self.sort_key(n) + nodes.append((sortkey, n)) + # + nodes.sort() + nodes = map(lambda pair: pair[1], nodes) + return nodes + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/LaTeXTests.py b/src/engine/SCons/Scanner/LaTeXTests.py new file mode 100644 index 0000000..012ad4b --- /dev/null +++ b/src/engine/SCons/Scanner/LaTeXTests.py @@ -0,0 +1,162 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/LaTeXTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys +import types +import unittest +import UserDict + +import TestCmd +import SCons.Node.FS +import SCons.Scanner.LaTeX + +test = TestCmd.TestCmd(workdir = '') + +test.write('test1.latex',""" +\include{inc1} +\input{inc2} +include{incNO} +%\include{incNO} +xyzzy \include{inc6} +""") + +test.write('test2.latex',""" +\include{inc1} +\include{inc3} +""") + +test.write('test3.latex',""" +\includegraphics{inc4.eps} +\includegraphics[width=60mm]{inc5.xyz} +""") + +test.subdir('subdir') + +test.write('inc1.tex',"\n") +test.write('inc2.tex',"\n") +test.write(['subdir', 'inc3.tex'], "\n") +test.write(['subdir', 'inc4.eps'], "\n") +test.write('inc5.xyz', "\n") +test.write('inc6.tex', "\n") +test.write('incNO.tex', "\n") + +# define some helpers: +# copied from CTest.py +class DummyEnvironment(UserDict.UserDict): + def __init__(self, **kw): + UserDict.UserDict.__init__(self) + self.data.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + return self.data + + def subst(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + + def subst_list(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return [self.data[strSubst[1:]]] + return [[strSubst]] + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase +else: + my_normpath = os.path.normpath + +def deps_match(self, deps, headers): + global my_normpath + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + + +class LaTeXScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LATEXSUFFIXES = [".tex", ".ltx", ".latex"]) + s = SCons.Scanner.LaTeX.LaTeXScanner() + path = s.path(env) + deps = s(env.File('test1.latex'), env, path) + headers = ['inc1.tex', 'inc2.tex', 'inc6.tex'] + deps_match(self, deps, headers) + +class LaTeXScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(TEXINPUTS=[test.workpath("subdir")],LATEXSUFFIXES = [".tex", ".ltx", ".latex"]) + s = SCons.Scanner.LaTeX.LaTeXScanner() + path = s.path(env) + deps = s(env.File('test2.latex'), env, path) + headers = ['inc1.tex', 'subdir/inc3.tex'] + deps_match(self, deps, headers) + +class LaTeXScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(TEXINPUTS=[test.workpath("subdir")],LATEXSUFFIXES = [".tex", ".ltx", ".latex"]) + s = SCons.Scanner.LaTeX.LaTeXScanner() + path = s.path(env) + deps = s(env.File('test3.latex'), env, path) + files = ['inc5.xyz', 'subdir/inc4.eps'] + deps_match(self, deps, files) + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(LaTeXScannerTestCase1()) + suite.addTest(LaTeXScannerTestCase2()) + suite.addTest(LaTeXScannerTestCase3()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/Prog.py b/src/engine/SCons/Scanner/Prog.py new file mode 100644 index 0000000..bdc11c0 --- /dev/null +++ b/src/engine/SCons/Scanner/Prog.py @@ -0,0 +1,103 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/Prog.py 4577 2009/12/27 19:44:43 scons" + +import string + +import SCons.Node +import SCons.Node.FS +import SCons.Scanner +import SCons.Util + +# global, set by --debug=findlibs +print_find_libs = None + +def ProgramScanner(**kw): + """Return a prototype Scanner instance for scanning executable + files for static-lib dependencies""" + kw['path_function'] = SCons.Scanner.FindPathDirs('LIBPATH') + ps = apply(SCons.Scanner.Base, [scan, "ProgramScanner"], kw) + return ps + +def scan(node, env, libpath = ()): + """ + This scanner scans program files for static-library + dependencies. It will search the LIBPATH environment variable + for libraries specified in the LIBS variable, returning any + files it finds as dependencies. + """ + try: + libs = env['LIBS'] + except KeyError: + # There are no LIBS in this environment, so just return a null list: + return [] + if SCons.Util.is_String(libs): + libs = string.split(libs) + else: + libs = SCons.Util.flatten(libs) + + try: + prefix = env['LIBPREFIXES'] + if not SCons.Util.is_List(prefix): + prefix = [ prefix ] + except KeyError: + prefix = [ '' ] + + try: + suffix = env['LIBSUFFIXES'] + if not SCons.Util.is_List(suffix): + suffix = [ suffix ] + except KeyError: + suffix = [ '' ] + + pairs = [] + for suf in map(env.subst, suffix): + for pref in map(env.subst, prefix): + pairs.append((pref, suf)) + + result = [] + + if callable(libpath): + libpath = libpath() + + find_file = SCons.Node.FS.find_file + adjustixes = SCons.Util.adjustixes + for lib in libs: + if SCons.Util.is_String(lib): + lib = env.subst(lib) + for pref, suf in pairs: + l = adjustixes(lib, pref, suf) + l = find_file(l, libpath, verbose=print_find_libs) + if l: + result.append(l) + else: + result.append(lib) + + return result + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/ProgTests.py b/src/engine/SCons/Scanner/ProgTests.py new file mode 100644 index 0000000..2a5761e --- /dev/null +++ b/src/engine/SCons/Scanner/ProgTests.py @@ -0,0 +1,262 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/ProgTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys +import types +import unittest + +import TestCmd +import SCons.Node.FS +import SCons.Scanner.Prog + +test = TestCmd.TestCmd(workdir = '') + +test.subdir('d1', ['d1', 'd2'], 'dir', ['dir', 'sub']) + +libs = [ 'l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib', + 'dir/libfoo.a', 'dir/sub/libbar.a', 'dir/libxyz.other'] + +for h in libs: + test.write(h, "\n") + +# define some helpers: + +class DummyEnvironment: + def __init__(self, **kw): + self._dict = {'LIBSUFFIXES' : '.lib'} + self._dict.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + if not args: + return self._dict + elif len(args) == 1: + return self._dict[args[0]] + else: + return map(lambda x, s=self: s._dict[x], args) + + def has_key(self, key): + return self.Dictionary().has_key(key) + + def __getitem__(self,key): + return self.Dictionary()[key] + + def __setitem__(self,key,value): + self.Dictionary()[key] = value + + def __delitem__(self,key): + del self.Dictionary()[key] + + def subst(self, s, target=None, source=None, conv=None): + try: + if s[0] == '$': + return self._dict[s[1:]] + except IndexError: + return '' + return s + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(test.workpath(filename)) + + def File(self, filename): + return self.fs.File(test.workpath(filename)) + +class DummyNode: + def __init__(self, name): + self.name = name + def rexists(self): + return 1 + def __str__(self): + return self.name + +def deps_match(deps, libs): + deps=map(str, deps) + deps.sort() + libs.sort() + return map(os.path.normpath, deps) == map(os.path.normpath, libs) + +# define some tests: + +class ProgramScannerTestCase1(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=[ 'l1', 'l2', 'l3' ]) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['l1.lib']), map(str, deps) + + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS='l1') + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['l1.lib']), map(str, deps) + + f1 = env.fs.File(test.workpath('f1')) + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=[f1]) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps[0] is f1, deps + + f2 = env.fs.File(test.workpath('f1')) + env = DummyEnvironment(LIBPATH=[ test.workpath("") ], + LIBS=f2) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps[0] is f2, deps + + +class ProgramScannerTestCase2(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=map(test.workpath, + ["", "d1", "d1/d2" ]), + LIBS=[ 'l1', 'l2', 'l3' ]) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['l1.lib', 'd1/l2.lib', 'd1/d2/l3.lib' ]), map(str, deps) + +class ProgramScannerTestCase3(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[test.workpath("d1/d2"), + test.workpath("d1")], + LIBS=string.split('l2 l3')) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['d1/l2.lib', 'd1/d2/l3.lib']), map(str, deps) + +class ProgramScannerTestCase5(unittest.TestCase): + def runTest(self): + class SubstEnvironment(DummyEnvironment): + def subst(self, arg, target=None, source=None, conv=None, path=test.workpath("d1")): + if arg == "$blah": + return test.workpath("d1") + else: + return arg + env = SubstEnvironment(LIBPATH=[ "$blah" ], + LIBS=string.split('l2 l3')) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, [ 'd1/l2.lib' ]), map(str, deps) + +class ProgramScannerTestCase6(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=['foo', 'sub/libbar', 'xyz.other'], + LIBPREFIXES=['lib'], + LIBSUFFIXES=['.a']) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['dir/libfoo.a', 'dir/sub/libbar.a', 'dir/libxyz.other']), map(str, deps) + +class ProgramScannerTestCase7(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=['foo', '$LIBBAR', '$XYZ'], + LIBPREFIXES=['lib'], + LIBSUFFIXES=['.a'], + LIBBAR='sub/libbar', + XYZ='xyz.other') + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['dir/libfoo.a', 'dir/sub/libbar.a', 'dir/libxyz.other']), map(str, deps) + +class ProgramScannerTestCase8(unittest.TestCase): + def runTest(self): + + n1 = DummyNode('n1') + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=[n1], + LIBPREFIXES=['p1-', 'p2-'], + LIBSUFFIXES=['.1', '2']) + s = SCons.Scanner.Prog.ProgramScanner(node_class = DummyNode) + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps == [n1], deps + + n2 = DummyNode('n2') + env = DummyEnvironment(LIBPATH=[ test.workpath("dir") ], + LIBS=[n1, [n2]], + LIBPREFIXES=['p1-', 'p2-'], + LIBSUFFIXES=['.1', '2']) + s = SCons.Scanner.Prog.ProgramScanner(node_class = DummyNode) + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps == [n1, n2], deps + +def suite(): + suite = unittest.TestSuite() + suite.addTest(ProgramScannerTestCase1()) + suite.addTest(ProgramScannerTestCase2()) + suite.addTest(ProgramScannerTestCase3()) + suite.addTest(ProgramScannerTestCase5()) + suite.addTest(ProgramScannerTestCase6()) + suite.addTest(ProgramScannerTestCase7()) + suite.addTest(ProgramScannerTestCase8()) + if hasattr(types, 'UnicodeType'): + code = """if 1: + class ProgramScannerTestCase4(unittest.TestCase): + def runTest(self): + env = DummyEnvironment(LIBPATH=[test.workpath("d1/d2"), + test.workpath("d1")], + LIBS=string.split(u'l2 l3')) + s = SCons.Scanner.Prog.ProgramScanner() + path = s.path(env) + deps = s(DummyNode('dummy'), env, path) + assert deps_match(deps, ['d1/l2.lib', 'd1/d2/l3.lib']), map(str, deps) + suite.addTest(ProgramScannerTestCase4()) + \n""" + exec code + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/RC.py b/src/engine/SCons/Scanner/RC.py new file mode 100644 index 0000000..7d73125 --- /dev/null +++ b/src/engine/SCons/Scanner/RC.py @@ -0,0 +1,55 @@ +"""SCons.Scanner.RC + +This module implements the depenency scanner for RC (Interface +Definition Language) files. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/RC.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Node.FS +import SCons.Scanner +import re + +def RCScan(): + """Return a prototype Scanner instance for scanning RC source files""" + + res_re= r'^(?:\s*#\s*(?:include)|' \ + '.*?\s+(?:ICON|BITMAP|CURSOR|HTML|FONT|MESSAGETABLE|TYPELIB|REGISTRY|D3DFX)' \ + '\s*.*?)' \ + '\s*(<|"| )([^>"\s]+)(?:[>" ])*$' + resScanner = SCons.Scanner.ClassicCPP( "ResourceScanner", + "$RCSUFFIXES", + "CPPPATH", + res_re ) + + return resScanner + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/RCTests.py b/src/engine/SCons/Scanner/RCTests.py new file mode 100644 index 0000000..ebf298b --- /dev/null +++ b/src/engine/SCons/Scanner/RCTests.py @@ -0,0 +1,168 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/RCTests.py 4577 2009/12/27 19:44:43 scons" + +import TestCmd +import SCons.Scanner.RC +import unittest +import sys +import os +import os.path +import SCons.Node.FS +import SCons.Warnings +import UserDict + +test = TestCmd.TestCmd(workdir = '') + +os.chdir(test.workpath('')) + +# create some source files and headers: + +test.write('t1.rc',''' +#include "t1.h" +''') + +test.write('t2.rc',""" +#include "t1.h" +ICO_TEST ICON DISCARDABLE "abc.ico" +BMP_TEST BITMAP DISCARDABLE "def.bmp" +cursor1 CURSOR "bullseye.cur" +ID_RESPONSE_ERROR_PAGE HTML "responseerrorpage.htm" +5 FONT "cmroman.fnt" +1 MESSAGETABLE "MSG00409.bin" +1 MESSAGETABLE MSG00410.bin +1 TYPELIB "testtypelib.tlb" +TEST_REGIS REGISTRY MOVEABLE PURE "testregis.rgs" +TEST_D3DFX D3DFX DISCARDABLE "testEffect.fx" + +""") + + +# Create dummy include files +headers = ['t1.h', + 'abc.ico','def.bmp','bullseye.cur','responseerrorpage.htm','cmroman.fnt', + 'testEffect.fx', + 'MSG00409.bin','MSG00410.bin','testtypelib.tlb','testregis.rgs'] + +for h in headers: + test.write(h, " ") + + +# define some helpers: + +class DummyEnvironment(UserDict.UserDict): + def __init__(self,**kw): + UserDict.UserDict.__init__(self) + self.data.update(kw) + self.fs = SCons.Node.FS.FS(test.workpath('')) + + def Dictionary(self, *args): + return self.data + + def subst(self, arg, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + + def has_key(self, key): + return self.Dictionary().has_key(key) + + def get_calculator(self): + return None + + def get_factory(self, factory): + return factory or self.fs.File + + def Dir(self, filename): + return self.fs.Dir(filename) + + def File(self, filename): + return self.fs.File(filename) + +global my_normpath +my_normpath = os.path.normpath + +if os.path.normcase('foo') == os.path.normcase('FOO'): + my_normpath = os.path.normcase + +def deps_match(self, deps, headers): + scanned = map(my_normpath, map(str, deps)) + expect = map(my_normpath, headers) + scanned.sort() + expect.sort() + self.failUnless(scanned == expect, "expect %s != scanned %s" % (expect, scanned)) + +# define some tests: + +class RCScannerTestCase1(unittest.TestCase): + def runTest(self): + path = [] + env = DummyEnvironment(RCSUFFIXES=['.rc','.rc2'], + CPPPATH=path) + s = SCons.Scanner.RC.RCScan() + deps = s(env.File('t1.rc'), env, path) + headers = ['t1.h'] + deps_match(self, deps, headers) + +class RCScannerTestCase2(unittest.TestCase): + def runTest(self): + path = [] + env = DummyEnvironment(RCSUFFIXES=['.rc','.rc2'], + CPPPATH=path) + s = SCons.Scanner.RC.RCScan() + deps = s(env.File('t2.rc'), env, path) + headers = ['MSG00410.bin', + 'abc.ico','bullseye.cur', + 'cmroman.fnt','def.bmp', + 'MSG00409.bin', + 'responseerrorpage.htm', + 't1.h', + 'testEffect.fx', + 'testregis.rgs','testtypelib.tlb'] + deps_match(self, deps, headers) + + + +def suite(): + suite = unittest.TestSuite() + suite.addTest(RCScannerTestCase1()) + suite.addTest(RCScannerTestCase2()) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/ScannerTests.py b/src/engine/SCons/Scanner/ScannerTests.py new file mode 100644 index 0000000..ae6cb79 --- /dev/null +++ b/src/engine/SCons/Scanner/ScannerTests.py @@ -0,0 +1,611 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/ScannerTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest +import UserDict + +import SCons.Scanner + +class DummyFS: + def File(self, name): + return DummyNode(name) + +class DummyEnvironment(UserDict.UserDict): + def __init__(self, dict=None, **kw): + UserDict.UserDict.__init__(self, dict) + self.data.update(kw) + self.fs = DummyFS() + def subst(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return self.data[strSubst[1:]] + return strSubst + def subst_list(self, strSubst, target=None, source=None, conv=None): + if strSubst[0] == '$': + return [self.data[strSubst[1:]]] + return [[strSubst]] + def subst_path(self, path, target=None, source=None, conv=None): + if type(path) != type([]): + path = [path] + return map(self.subst, path) + def get_factory(self, factory): + return factory or self.fs.File + +class DummyNode: + def __init__(self, name, search_result=()): + self.name = name + self.search_result = tuple(search_result) + def rexists(self): + return 1 + def __str__(self): + return self.name + def Rfindalldirs(self, pathlist): + return self.search_result + pathlist + +class FindPathDirsTestCase(unittest.TestCase): + def test_FindPathDirs(self): + """Test the FindPathDirs callable class""" + + env = DummyEnvironment(LIBPATH = [ 'foo' ]) + env.fs = DummyFS() + env.fs._cwd = DummyNode('cwd') + + dir = DummyNode('dir', ['xxx']) + fpd = SCons.Scanner.FindPathDirs('LIBPATH') + result = fpd(env) + assert str(result) == "('foo',)", result + result = fpd(env, dir) + assert str(result) == "('xxx', 'foo')", result + +class ScannerTestCase(unittest.TestCase): + + def test_creation(self): + """Test creation of Scanner objects""" + def func(self): + pass + s = SCons.Scanner.Base(func) + assert isinstance(s, SCons.Scanner.Base), s + s = SCons.Scanner.Base({}) + assert isinstance(s, SCons.Scanner.Base), s + + s = SCons.Scanner.Base(func, name='fooscan') + assert str(s) == 'fooscan', str(s) + s = SCons.Scanner.Base({}, name='barscan') + assert str(s) == 'barscan', str(s) + + s = SCons.Scanner.Base(func, name='fooscan', argument=9) + assert str(s) == 'fooscan', str(s) + assert s.argument == 9, s.argument + s = SCons.Scanner.Base({}, name='fooscan', argument=888) + assert str(s) == 'fooscan', str(s) + assert s.argument == 888, s.argument + + +class BaseTestCase(unittest.TestCase): + + class skey_node: + def __init__(self, key): + self.key = key + def scanner_key(self): + return self.key + def rexists(self): + return 1 + + def func(self, filename, env, target, *args): + self.filename = filename + self.env = env + self.target = target + + if len(args) > 0: + self.arg = args[0] + + return self.deps + + def test(self, scanner, env, filename, deps, *args): + self.deps = deps + path = scanner.path(env) + scanned = scanner(filename, env, path) + scanned_strs = map(lambda x: str(x), scanned) + + self.failUnless(self.filename == filename, "the filename was passed incorrectly") + self.failUnless(self.env == env, "the environment was passed incorrectly") + self.failUnless(scanned_strs == deps, "the dependencies were returned incorrectly") + for d in scanned: + self.failUnless(type(d) != type(""), "got a string in the dependencies") + + if len(args) > 0: + self.failUnless(self.arg == args[0], "the argument was passed incorrectly") + else: + self.failIf(hasattr(self, "arg"), "an argument was given when it shouldn't have been") + + def test___call__dict(self): + """Test calling Scanner.Base objects with a dictionary""" + called = [] + def s1func(node, env, path, called=called): + called.append('s1func') + called.append(node) + return [] + def s2func(node, env, path, called=called): + called.append('s2func') + called.append(node) + return [] + s1 = SCons.Scanner.Base(s1func) + s2 = SCons.Scanner.Base(s2func) + selector = SCons.Scanner.Base({'.x' : s1, '.y' : s2}) + nx = self.skey_node('.x') + env = DummyEnvironment() + selector(nx, env, []) + assert called == ['s1func', nx], called + del called[:] + ny = self.skey_node('.y') + selector(ny, env, []) + assert called == ['s2func', ny], called + + def test_path(self): + """Test the Scanner.Base path() method""" + def pf(env, cwd, target, source, argument=None): + return "pf: %s %s %s %s %s" % \ + (env.VARIABLE, cwd, target[0], source[0], argument) + + env = DummyEnvironment() + env.VARIABLE = 'v1' + target = DummyNode('target') + source = DummyNode('source') + + s = SCons.Scanner.Base(self.func, path_function=pf) + p = s.path(env, 'here', [target], [source]) + assert p == "pf: v1 here target source None", p + + s = SCons.Scanner.Base(self.func, path_function=pf, argument="xyz") + p = s.path(env, 'here', [target], [source]) + assert p == "pf: v1 here target source xyz", p + + def test_positional(self): + """Test the Scanner.Base class using positional arguments""" + s = SCons.Scanner.Base(self.func, "Pos") + env = DummyEnvironment() + env.VARIABLE = "var1" + self.test(s, env, DummyNode('f1.cpp'), ['f1.h', 'f1.hpp']) + + env = DummyEnvironment() + env.VARIABLE = "i1" + self.test(s, env, DummyNode('i1.cpp'), ['i1.h', 'i1.hpp']) + + def test_keywords(self): + """Test the Scanner.Base class using keyword arguments""" + s = SCons.Scanner.Base(function = self.func, name = "Key") + env = DummyEnvironment() + env.VARIABLE = "var2" + self.test(s, env, DummyNode('f2.cpp'), ['f2.h', 'f2.hpp']) + + env = DummyEnvironment() + env.VARIABLE = "i2" + + self.test(s, env, DummyNode('i2.cpp'), ['i2.h', 'i2.hpp']) + + def test_pos_opt(self): + """Test the Scanner.Base class using both position and optional arguments""" + arg = "this is the argument" + s = SCons.Scanner.Base(self.func, "PosArg", arg) + env = DummyEnvironment() + env.VARIABLE = "var3" + self.test(s, env, DummyNode('f3.cpp'), ['f3.h', 'f3.hpp'], arg) + + env = DummyEnvironment() + env.VARIABLE = "i3" + self.test(s, env, DummyNode('i3.cpp'), ['i3.h', 'i3.hpp'], arg) + + def test_key_opt(self): + """Test the Scanner.Base class using both keyword and optional arguments""" + arg = "this is another argument" + s = SCons.Scanner.Base(function = self.func, name = "KeyArg", + argument = arg) + env = DummyEnvironment() + env.VARIABLE = "var4" + self.test(s, env, DummyNode('f4.cpp'), ['f4.h', 'f4.hpp'], arg) + + env = DummyEnvironment() + env.VARIABLE = "i4" + self.test(s, env, DummyNode('i4.cpp'), ['i4.h', 'i4.hpp'], arg) + + def test___cmp__(self): + """Test the Scanner.Base class __cmp__() method""" + s = SCons.Scanner.Base(self.func, "Cmp") + assert cmp(s, None) + + def test_hash(self): + """Test the Scanner.Base class __hash__() method""" + s = SCons.Scanner.Base(self.func, "Hash") + dict = {} + dict[s] = 777 + i = hash(id(s)) + h = hash(dict.keys()[0]) + self.failUnless(h == i, + "hash Scanner base class expected %s, got %s" % (i, h)) + + def test_scan_check(self): + """Test the Scanner.Base class scan_check() method""" + def my_scan(filename, env, target, *args): + return [] + def check(node, env, s=self): + s.checked[str(node)] = 1 + return 1 + env = DummyEnvironment() + s = SCons.Scanner.Base(my_scan, "Check", scan_check = check) + self.checked = {} + path = s.path(env) + scanned = s(DummyNode('x'), env, path) + self.failUnless(self.checked['x'] == 1, + "did not call check function") + + def test_recursive(self): + """Test the Scanner.Base class recursive flag""" + nodes = [1, 2, 3, 4] + + s = SCons.Scanner.Base(function = self.func) + n = s.recurse_nodes(nodes) + self.failUnless(n == [], + "default behavior returned nodes: %s" % n) + + s = SCons.Scanner.Base(function = self.func, recursive = None) + n = s.recurse_nodes(nodes) + self.failUnless(n == [], + "recursive = None returned nodes: %s" % n) + + s = SCons.Scanner.Base(function = self.func, recursive = 1) + n = s.recurse_nodes(nodes) + self.failUnless(n == n, + "recursive = 1 didn't return all nodes: %s" % n) + + def odd_only(nodes): + return filter(lambda n: n % 2, nodes) + s = SCons.Scanner.Base(function = self.func, recursive = odd_only) + n = s.recurse_nodes(nodes) + self.failUnless(n == [1, 3], + "recursive = 1 didn't return all nodes: %s" % n) + + def test_get_skeys(self): + """Test the Scanner.Base get_skeys() method""" + s = SCons.Scanner.Base(function = self.func) + sk = s.get_skeys() + self.failUnless(sk == [], + "did not initialize to expected []") + + s = SCons.Scanner.Base(function = self.func, skeys = ['.1', '.2']) + sk = s.get_skeys() + self.failUnless(sk == ['.1', '.2'], + "sk was %s, not ['.1', '.2']") + + s = SCons.Scanner.Base(function = self.func, skeys = '$LIST') + env = DummyEnvironment(LIST = ['.3', '.4']) + sk = s.get_skeys(env) + self.failUnless(sk == ['.3', '.4'], + "sk was %s, not ['.3', '.4']") + + def test_select(self): + """Test the Scanner.Base select() method""" + scanner = SCons.Scanner.Base(function = self.func) + s = scanner.select('.x') + assert s is scanner, s + + selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.x')) + assert s == 1, s + s = selector.select(self.skey_node('.y')) + assert s == 2, s + s = selector.select(self.skey_node('.z')) + assert s is None, s + + def test_add_scanner(self): + """Test the Scanner.Base add_scanner() method""" + selector = SCons.Scanner.Base({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.z')) + assert s is None, s + selector.add_scanner('.z', 3) + s = selector.select(self.skey_node('.z')) + assert s == 3, s + + def test___str__(self): + """Test the Scanner.Base __str__() method""" + scanner = SCons.Scanner.Base(function = self.func) + s = str(scanner) + assert s == 'NONE', s + scanner = SCons.Scanner.Base(function = self.func, name = 'xyzzy') + s = str(scanner) + assert s == 'xyzzy', s + +class SelectorTestCase(unittest.TestCase): + class skey_node: + def __init__(self, key): + self.key = key + def scanner_key(self): + return self.key + def rexists(self): + return 1 + + def test___init__(self): + """Test creation of Scanner.Selector object""" + s = SCons.Scanner.Selector({}) + assert isinstance(s, SCons.Scanner.Selector), s + assert s.dict == {}, s.dict + + def test___call__(self): + """Test calling Scanner.Selector objects""" + called = [] + def s1func(node, env, path, called=called): + called.append('s1func') + called.append(node) + return [] + def s2func(node, env, path, called=called): + called.append('s2func') + called.append(node) + return [] + s1 = SCons.Scanner.Base(s1func) + s2 = SCons.Scanner.Base(s2func) + selector = SCons.Scanner.Selector({'.x' : s1, '.y' : s2}) + nx = self.skey_node('.x') + env = DummyEnvironment() + selector(nx, env, []) + assert called == ['s1func', nx], called + del called[:] + ny = self.skey_node('.y') + selector(ny, env, []) + assert called == ['s2func', ny], called + + def test_select(self): + """Test the Scanner.Selector select() method""" + selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.x')) + assert s == 1, s + s = selector.select(self.skey_node('.y')) + assert s == 2, s + s = selector.select(self.skey_node('.z')) + assert s is None, s + + def test_add_scanner(self): + """Test the Scanner.Selector add_scanner() method""" + selector = SCons.Scanner.Selector({'.x' : 1, '.y' : 2}) + s = selector.select(self.skey_node('.z')) + assert s is None, s + selector.add_scanner('.z', 3) + s = selector.select(self.skey_node('.z')) + assert s == 3, s + +class CurrentTestCase(unittest.TestCase): + def test_class(self): + """Test the Scanner.Current class""" + class MyNode: + def __init__(self): + self.called_has_builder = None + self.called_is_up_to_date = None + self.func_called = None + def rexists(self): + return 1 + class HasNoBuilder(MyNode): + def has_builder(self): + self.called_has_builder = 1 + return None + class IsNotCurrent(MyNode): + def has_builder(self): + self.called_has_builder = 1 + return 1 + def is_up_to_date(self): + self.called_is_up_to_date = 1 + return None + class IsCurrent(MyNode): + def has_builder(self): + self.called_has_builder = 1 + return 1 + def is_up_to_date(self): + self.called_is_up_to_date = 1 + return 1 + def func(node, env, path): + node.func_called = 1 + return [] + env = DummyEnvironment() + s = SCons.Scanner.Current(func) + path = s.path(env) + hnb = HasNoBuilder() + s(hnb, env, path) + self.failUnless(hnb.called_has_builder, "did not call has_builder()") + self.failUnless(not hnb.called_is_up_to_date, "did call is_up_to_date()") + self.failUnless(hnb.func_called, "did not call func()") + inc = IsNotCurrent() + s(inc, env, path) + self.failUnless(inc.called_has_builder, "did not call has_builder()") + self.failUnless(inc.called_is_up_to_date, "did not call is_up_to_date()") + self.failUnless(not inc.func_called, "did call func()") + ic = IsCurrent() + s(ic, env, path) + self.failUnless(ic.called_has_builder, "did not call has_builder()") + self.failUnless(ic.called_is_up_to_date, "did not call is_up_to_date()") + self.failUnless(ic.func_called, "did not call func()") + +class ClassicTestCase(unittest.TestCase): + def test_find_include(self): + """Test the Scanner.Classic find_include() method""" + env = DummyEnvironment() + s = SCons.Scanner.Classic("t", ['.suf'], 'MYPATH', '^my_inc (\S+)') + + def _find_file(filename, paths): + return paths[0]+'/'+filename + + save = SCons.Node.FS.find_file + SCons.Node.FS.find_file = _find_file + + try: + n, i = s.find_include('aaa', 'foo', ('path',)) + assert n == 'foo/aaa', n + assert i == 'aaa', i + + finally: + SCons.Node.FS.find_file = save + + def test_name(self): + """Test setting the Scanner.Classic name""" + s = SCons.Scanner.Classic("my_name", ['.s'], 'MYPATH', '^my_inc (\S+)') + assert s.name == "my_name", s.name + + def test_scan(self): + """Test the Scanner.Classic scan() method""" + class MyNode: + def __init__(self, name): + self.name = name + self._rfile = self + self.includes = None + def rfile(self): + return self._rfile + def exists(self): + return self._exists + def get_contents(self): + return self._contents + def get_text_contents(self): + return self._contents + def get_dir(self): + return self._dir + + class MyScanner(SCons.Scanner.Classic): + def find_include(self, include, source_dir, path): + return include, include + + env = DummyEnvironment() + s = MyScanner("t", ['.suf'], 'MYPATH', '^my_inc (\S+)') + + # This set of tests is intended to test the scanning operation + # of the Classic scanner. + + # Note that caching has been added for not just the includes + # but the entire scan call. The caching is based on the + # arguments, so we will fiddle with the path parameter to + # defeat this caching for the purposes of these tests. + + # If the node doesn't exist, scanning turns up nothing. + n1 = MyNode("n1") + n1._exists = None + ret = s.function(n1, env) + assert ret == [], ret + + # Verify that it finds includes from the contents. + n = MyNode("n") + n._exists = 1 + n._dir = MyNode("n._dir") + n._contents = 'my_inc abc\n' + ret = s.function(n, env, ('foo',)) + assert ret == ['abc'], ret + + # Verify that it uses the cached include info. + n._contents = 'my_inc def\n' + ret = s.function(n, env, ('foo2',)) + assert ret == ['abc'], ret + + # Verify that if we wipe the cache, it uses the new contents. + n.includes = None + ret = s.function(n, env, ('foo3',)) + assert ret == ['def'], ret + + # We no longer cache overall scan results, which would be returned + # if individual results are de-cached. If we ever restore that + # functionality, this test goes back here. + #ret = s.function(n, env, ('foo2',)) + #assert ret == ['abc'], 'caching inactive; got: %s'%ret + + # Verify that it sorts what it finds. + n.includes = ['xyz', 'uvw'] + ret = s.function(n, env, ('foo4',)) + assert ret == ['uvw', 'xyz'], ret + + # Verify that we use the rfile() node. + nr = MyNode("nr") + nr._exists = 1 + nr._dir = MyNode("nr._dir") + nr.includes = ['jkl', 'mno'] + n._rfile = nr + ret = s.function(n, env, ('foo5',)) + assert ret == ['jkl', 'mno'], ret + + + +class ClassicCPPTestCase(unittest.TestCase): + def test_find_include(self): + """Test the Scanner.ClassicCPP find_include() method""" + env = DummyEnvironment() + s = SCons.Scanner.ClassicCPP("Test", [], None, "") + + def _find_file(filename, paths): + return paths[0]+'/'+filename + + save = SCons.Node.FS.find_file + SCons.Node.FS.find_file = _find_file + + try: + n, i = s.find_include(('"', 'aaa'), 'foo', ('path',)) + assert n == 'foo/aaa', n + assert i == 'aaa', i + + n, i = s.find_include(('<', 'bbb'), 'foo', ('path',)) + assert n == 'path/bbb', n + assert i == 'bbb', i + + # TODO(1.5): remove when 2.2 is minimal; replace ccc + # variable in find_include() call below with in-line u'ccc'. + try: + ccc = eval("u'ccc'") + except SyntaxError: + ccc = 'ccc' + + n, i = s.find_include(('<', ccc), 'foo', ('path',)) + assert n == 'path/ccc', n + assert i == 'ccc', i + + finally: + SCons.Node.FS.find_file = save + +def suite(): + suite = unittest.TestSuite() + tclasses = [ + FindPathDirsTestCase, + ScannerTestCase, + BaseTestCase, + SelectorTestCase, + CurrentTestCase, + ClassicTestCase, + ClassicCPPTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + return suite + +if __name__ == "__main__": + runner = unittest.TextTestRunner() + result = runner.run(suite()) + if not result.wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Scanner/__init__.py b/src/engine/SCons/Scanner/__init__.py new file mode 100644 index 0000000..76cd536 --- /dev/null +++ b/src/engine/SCons/Scanner/__init__.py @@ -0,0 +1,415 @@ +"""SCons.Scanner + +The Scanner package for the SCons software construction utility. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Scanner/__init__.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Node.FS +import SCons.Util + + +class _Null: + pass + +# This is used instead of None as a default argument value so None can be +# used as an actual argument value. +_null = _Null + +def Scanner(function, *args, **kw): + """ + Public interface factory function for creating different types + of Scanners based on the different types of "functions" that may + be supplied. + + TODO: Deprecate this some day. We've moved the functionality + inside the Base class and really don't need this factory function + any more. It was, however, used by some of our Tool modules, so + the call probably ended up in various people's custom modules + patterned on SCons code. + """ + if SCons.Util.is_Dict(function): + return apply(Selector, (function,) + args, kw) + else: + return apply(Base, (function,) + args, kw) + + + +class FindPathDirs: + """A class to bind a specific *PATH variable name to a function that + will return all of the *path directories.""" + def __init__(self, variable): + self.variable = variable + def __call__(self, env, dir=None, target=None, source=None, argument=None): + import SCons.PathList + try: + path = env[self.variable] + except KeyError: + return () + + dir = dir or env.fs._cwd + path = SCons.PathList.PathList(path).subst_path(env, target, source) + return tuple(dir.Rfindalldirs(path)) + + + +class Base: + """ + The base class for dependency scanners. This implements + straightforward, single-pass scanning of a single file. + """ + + def __init__(self, + function, + name = "NONE", + argument = _null, + skeys = _null, + path_function = None, + node_class = SCons.Node.FS.Entry, + node_factory = None, + scan_check = None, + recursive = None): + """ + Construct a new scanner object given a scanner function. + + 'function' - a scanner function taking two or three + arguments and returning a list of strings. + + 'name' - a name for identifying this scanner object. + + 'argument' - an optional argument that, if specified, will be + passed to both the scanner function and the path_function. + + 'skeys' - an optional list argument that can be used to determine + which scanner should be used for a given Node. In the case of File + nodes, for example, the 'skeys' would be file suffixes. + + 'path_function' - a function that takes four or five arguments + (a construction environment, Node for the directory containing + the SConscript file that defined the primary target, list of + target nodes, list of source nodes, and optional argument for + this instance) and returns a tuple of the directories that can + be searched for implicit dependency files. May also return a + callable() which is called with no args and returns the tuple + (supporting Bindable class). + + 'node_class' - the class of Nodes which this scan will return. + If node_class is None, then this scanner will not enforce any + Node conversion and will return the raw results from the + underlying scanner function. + + 'node_factory' - the factory function to be called to translate + the raw results returned by the scanner function into the + expected node_class objects. + + 'scan_check' - a function to be called to first check whether + this node really needs to be scanned. + + 'recursive' - specifies that this scanner should be invoked + recursively on all of the implicit dependencies it returns + (the canonical example being #include lines in C source files). + May be a callable, which will be called to filter the list + of nodes found to select a subset for recursive scanning + (the canonical example being only recursively scanning + subdirectories within a directory). + + The scanner function's first argument will be a Node that should + be scanned for dependencies, the second argument will be an + Environment object, the third argument will be the tuple of paths + returned by the path_function, and the fourth argument will be + the value passed into 'argument', and the returned list should + contain the Nodes for all the direct dependencies of the file. + + Examples: + + s = Scanner(my_scanner_function) + + s = Scanner(function = my_scanner_function) + + s = Scanner(function = my_scanner_function, argument = 'foo') + + """ + + # Note: this class could easily work with scanner functions that take + # something other than a filename as an argument (e.g. a database + # node) and a dependencies list that aren't file names. All that + # would need to be changed is the documentation. + + self.function = function + self.path_function = path_function + self.name = name + self.argument = argument + + if skeys is _null: + if SCons.Util.is_Dict(function): + skeys = function.keys() + else: + skeys = [] + self.skeys = skeys + + self.node_class = node_class + self.node_factory = node_factory + self.scan_check = scan_check + if callable(recursive): + self.recurse_nodes = recursive + elif recursive: + self.recurse_nodes = self._recurse_all_nodes + else: + self.recurse_nodes = self._recurse_no_nodes + + def path(self, env, dir=None, target=None, source=None): + if not self.path_function: + return () + if not self.argument is _null: + return self.path_function(env, dir, target, source, self.argument) + else: + return self.path_function(env, dir, target, source) + + def __call__(self, node, env, path = ()): + """ + This method scans a single object. 'node' is the node + that will be passed to the scanner function, and 'env' is the + environment that will be passed to the scanner function. A list of + direct dependency nodes for the specified node will be returned. + """ + if self.scan_check and not self.scan_check(node, env): + return [] + + self = self.select(node) + + if not self.argument is _null: + list = self.function(node, env, path, self.argument) + else: + list = self.function(node, env, path) + + kw = {} + if hasattr(node, 'dir'): + kw['directory'] = node.dir + node_factory = env.get_factory(self.node_factory) + nodes = [] + for l in list: + if self.node_class and not isinstance(l, self.node_class): + l = apply(node_factory, (l,), kw) + nodes.append(l) + return nodes + + def __cmp__(self, other): + try: + return cmp(self.__dict__, other.__dict__) + except AttributeError: + # other probably doesn't have a __dict__ + return cmp(self.__dict__, other) + + def __hash__(self): + return id(self) + + def __str__(self): + return self.name + + def add_skey(self, skey): + """Add a skey to the list of skeys""" + self.skeys.append(skey) + + def get_skeys(self, env=None): + if env and SCons.Util.is_String(self.skeys): + return env.subst_list(self.skeys)[0] + return self.skeys + + def select(self, node): + if SCons.Util.is_Dict(self.function): + key = node.scanner_key() + try: + return self.function[key] + except KeyError: + return None + else: + return self + + def _recurse_all_nodes(self, nodes): + return nodes + + def _recurse_no_nodes(self, nodes): + return [] + + recurse_nodes = _recurse_no_nodes + + def add_scanner(self, skey, scanner): + self.function[skey] = scanner + self.add_skey(skey) + + +class Selector(Base): + """ + A class for selecting a more specific scanner based on the + scanner_key() (suffix) for a specific Node. + + TODO: This functionality has been moved into the inner workings of + the Base class, and this class will be deprecated at some point. + (It was never exposed directly as part of the public interface, + although it is used by the Scanner() factory function that was + used by various Tool modules and therefore was likely a template + for custom modules that may be out there.) + """ + def __init__(self, dict, *args, **kw): + apply(Base.__init__, (self, None,)+args, kw) + self.dict = dict + self.skeys = dict.keys() + + def __call__(self, node, env, path = ()): + return self.select(node)(node, env, path) + + def select(self, node): + try: + return self.dict[node.scanner_key()] + except KeyError: + return None + + def add_scanner(self, skey, scanner): + self.dict[skey] = scanner + self.add_skey(skey) + + +class Current(Base): + """ + A class for scanning files that are source files (have no builder) + or are derived files and are current (which implies that they exist, + either locally or in a repository). + """ + + def __init__(self, *args, **kw): + def current_check(node, env): + return not node.has_builder() or node.is_up_to_date() + kw['scan_check'] = current_check + apply(Base.__init__, (self,) + args, kw) + +class Classic(Current): + """ + A Scanner subclass to contain the common logic for classic CPP-style + include scanning, but which can be customized to use different + regular expressions to find the includes. + + Note that in order for this to work "out of the box" (without + overriding the find_include() and sort_key() methods), the regular + expression passed to the constructor must return the name of the + include file in group 0. + """ + + def __init__(self, name, suffixes, path_variable, regex, *args, **kw): + + self.cre = re.compile(regex, re.M) + + def _scan(node, env, path=(), self=self): + node = node.rfile() + if not node.exists(): + return [] + return self.scan(node, path) + + kw['function'] = _scan + kw['path_function'] = FindPathDirs(path_variable) + kw['recursive'] = 1 + kw['skeys'] = suffixes + kw['name'] = name + + apply(Current.__init__, (self,) + args, kw) + + def find_include(self, include, source_dir, path): + n = SCons.Node.FS.find_file(include, (source_dir,) + tuple(path)) + return n, include + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(include) + + def find_include_names(self, node): + return self.cre.findall(node.get_text_contents()) + + def scan(self, node, path=()): + + # cache the includes list in node so we only scan it once: + if node.includes is not None: + includes = node.includes + else: + includes = self.find_include_names (node) + # Intern the names of the include files. Saves some memory + # if the same header is included many times. + node.includes = map(SCons.Util.silent_intern, includes) + + # This is a hand-coded DSU (decorate-sort-undecorate, or + # Schwartzian transform) pattern. The sort key is the raw name + # of the file as specifed on the #include line (including the + # " or <, since that may affect what file is found), which lets + # us keep the sort order constant regardless of whether the file + # is actually found in a Repository or locally. + nodes = [] + source_dir = node.get_dir() + if callable(path): + path = path() + for include in includes: + n, i = self.find_include(include, source_dir, path) + + if n is None: + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "No dependency generated for file: %s (included from: %s) -- file not found" % (i, node)) + else: + sortkey = self.sort_key(include) + nodes.append((sortkey, n)) + + nodes.sort() + nodes = map(lambda pair: pair[1], nodes) + return nodes + +class ClassicCPP(Classic): + """ + A Classic Scanner subclass which takes into account the type of + bracketing used to include the file, and uses classic CPP rules + for searching for the files based on the bracketing. + + Note that in order for this to work, the regular expression passed + to the constructor must return the leading bracket in group 0, and + the contained filename in group 1. + """ + def find_include(self, include, source_dir, path): + if include[0] == '"': + paths = (source_dir,) + tuple(path) + else: + paths = tuple(path) + (source_dir,) + + n = SCons.Node.FS.find_file(include[1], paths) + + i = SCons.Util.silent_intern(include[1]) + return n, i + + def sort_key(self, include): + return SCons.Node.FS._my_normcase(string.join(include)) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/Interactive.py b/src/engine/SCons/Script/Interactive.py new file mode 100644 index 0000000..26711d8 --- /dev/null +++ b/src/engine/SCons/Script/Interactive.py @@ -0,0 +1,386 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/Interactive.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +SCons interactive mode +""" + +# TODO: +# +# This has the potential to grow into something with a really big life +# of its own, which might or might not be a good thing. Nevertheless, +# here are some enhancements that will probably be requested some day +# and are worth keeping in mind (assuming this takes off): +# +# - A command to re-read / re-load the SConscript files. This may +# involve allowing people to specify command-line options (e.g. -f, +# -I, --no-site-dir) that affect how the SConscript files are read. +# +# - Additional command-line options on the "build" command. +# +# Of the supported options that seemed to make sense (after a quick +# pass through the list), the ones that seemed likely enough to be +# used are listed in the man page and have explicit test scripts. +# +# These had code changed in Script/Main.py to support them, but didn't +# seem likely to be used regularly, so had no test scripts added: +# +# build --diskcheck=* +# build --implicit-cache=* +# build --implicit-deps-changed=* +# build --implicit-deps-unchanged=* +# +# These look like they should "just work" with no changes to the +# existing code, but like those above, look unlikely to be used and +# therefore had no test scripts added: +# +# build --random +# +# These I'm not sure about. They might be useful for individual +# "build" commands, and may even work, but they seem unlikely enough +# that we'll wait until they're requested before spending any time on +# writing test scripts for them, or investigating whether they work. +# +# build -q [??? is there a useful analog to the exit status?] +# build --duplicate= +# build --profile= +# build --max-drift= +# build --warn=* +# build --Y +# +# - Most of the SCons command-line options that the "build" command +# supports should be settable as default options that apply to all +# subsequent "build" commands. Maybe a "set {option}" command that +# maps to "SetOption('{option}')". +# +# - Need something in the 'help' command that prints the -h output. +# +# - A command to run the configure subsystem separately (must see how +# this interacts with the new automake model). +# +# - Command-line completion of target names; maybe even of SCons options? +# Completion is something that's supported by the Python cmd module, +# so this should be doable without too much trouble. +# + +import cmd +import copy +import os +import re +import shlex +import string +import sys + +try: + import readline +except ImportError: + pass + +class SConsInteractiveCmd(cmd.Cmd): + """\ + build [TARGETS] Build the specified TARGETS and their dependencies. + 'b' is a synonym. + clean [TARGETS] Clean (remove) the specified TARGETS and their + dependencies. 'c' is a synonym. + exit Exit SCons interactive mode. + help [COMMAND] Prints help for the specified COMMAND. 'h' and + '?' are synonyms. + shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and '!' + are synonyms. + version Prints SCons version information. + """ + + synonyms = { + 'b' : 'build', + 'c' : 'clean', + 'h' : 'help', + 'scons' : 'build', + 'sh' : 'shell', + } + + def __init__(self, **kw): + cmd.Cmd.__init__(self) + for key, val in kw.items(): + setattr(self, key, val) + + if sys.platform == 'win32': + self.shell_variable = 'COMSPEC' + else: + self.shell_variable = 'SHELL' + + def default(self, argv): + print "*** Unknown command: %s" % argv[0] + + def onecmd(self, line): + line = string.strip(line) + if not line: + print self.lastcmd + return self.emptyline() + self.lastcmd = line + if line[0] == '!': + line = 'shell ' + line[1:] + elif line[0] == '?': + line = 'help ' + line[1:] + if os.sep == '\\': + line = string.replace(line, '\\', '\\\\') + argv = shlex.split(line) + argv[0] = self.synonyms.get(argv[0], argv[0]) + if not argv[0]: + return self.default(line) + else: + try: + func = getattr(self, 'do_' + argv[0]) + except AttributeError: + return self.default(argv) + return func(argv) + + def do_build(self, argv): + """\ + build [TARGETS] Build the specified TARGETS and their + dependencies. 'b' is a synonym. + """ + import SCons.Node + import SCons.SConsign + import SCons.Script.Main + + options = copy.deepcopy(self.options) + + options, targets = self.parser.parse_args(argv[1:], values=options) + + SCons.Script.COMMAND_LINE_TARGETS = targets + + if targets: + SCons.Script.BUILD_TARGETS = targets + else: + # If the user didn't specify any targets on the command line, + # use the list of default targets. + SCons.Script.BUILD_TARGETS = SCons.Script._build_plus_default + + nodes = SCons.Script.Main._build_targets(self.fs, + options, + targets, + self.target_top) + + if not nodes: + return + + # Call each of the Node's alter_targets() methods, which may + # provide additional targets that ended up as part of the build + # (the canonical example being a VariantDir() when we're building + # from a source directory) and which we therefore need their + # state cleared, too. + x = [] + for n in nodes: + x.extend(n.alter_targets()[0]) + nodes.extend(x) + + # Clean up so that we can perform the next build correctly. + # + # We do this by walking over all the children of the targets, + # and clearing their state. + # + # We currently have to re-scan each node to find their + # children, because built nodes have already been partially + # cleared and don't remember their children. (In scons + # 0.96.1 and earlier, this wasn't the case, and we didn't + # have to re-scan the nodes.) + # + # Because we have to re-scan each node, we can't clear the + # nodes as we walk over them, because we may end up rescanning + # a cleared node as we scan a later node. Therefore, only + # store the list of nodes that need to be cleared as we walk + # the tree, and clear them in a separate pass. + # + # XXX: Someone more familiar with the inner workings of scons + # may be able to point out a more efficient way to do this. + + SCons.Script.Main.progress_display("scons: Clearing cached node information ...") + + seen_nodes = {} + + def get_unseen_children(node, parent, seen_nodes=seen_nodes): + def is_unseen(node, seen_nodes=seen_nodes): + return not seen_nodes.has_key(node) + return filter(is_unseen, node.children(scan=1)) + + def add_to_seen_nodes(node, parent, seen_nodes=seen_nodes): + seen_nodes[node] = 1 + + # If this file is in a VariantDir and has a + # corresponding source file in the source tree, remember the + # node in the source tree, too. This is needed in + # particular to clear cached implicit dependencies on the + # source file, since the scanner will scan it if the + # VariantDir was created with duplicate=0. + try: + rfile_method = node.rfile + except AttributeError: + return + else: + rfile = rfile_method() + if rfile != node: + seen_nodes[rfile] = 1 + + for node in nodes: + walker = SCons.Node.Walker(node, + kids_func=get_unseen_children, + eval_func=add_to_seen_nodes) + n = walker.next() + while n: + n = walker.next() + + for node in seen_nodes.keys(): + # Call node.clear() to clear most of the state + node.clear() + # node.clear() doesn't reset node.state, so call + # node.set_state() to reset it manually + node.set_state(SCons.Node.no_state) + node.implicit = None + + # Debug: Uncomment to verify that all Taskmaster reference + # counts have been reset to zero. + #if node.ref_count != 0: + # from SCons.Debug import Trace + # Trace('node %s, ref_count %s !!!\n' % (node, node.ref_count)) + + SCons.SConsign.Reset() + SCons.Script.Main.progress_display("scons: done clearing node information.") + + def do_clean(self, argv): + """\ + clean [TARGETS] Clean (remove) the specified TARGETS + and their dependencies. 'c' is a synonym. + """ + return self.do_build(['build', '--clean'] + argv[1:]) + + def do_EOF(self, argv): + print + self.do_exit(argv) + + def _do_one_help(self, arg): + try: + # If help_() exists, then call it. + func = getattr(self, 'help_' + arg) + except AttributeError: + try: + func = getattr(self, 'do_' + arg) + except AttributeError: + doc = None + else: + doc = self._doc_to_help(func) + if doc: + sys.stdout.write(doc + '\n') + sys.stdout.flush() + else: + doc = self.strip_initial_spaces(func()) + if doc: + sys.stdout.write(doc + '\n') + sys.stdout.flush() + + def _doc_to_help(self, obj): + doc = obj.__doc__ + if doc is None: + return '' + return self._strip_initial_spaces(doc) + + def _strip_initial_spaces(self, s): + #lines = s.split('\n') + lines = string.split(s, '\n') + spaces = re.match(' *', lines[0]).group(0) + #def strip_spaces(l): + # if l.startswith(spaces): + # l = l[len(spaces):] + # return l + #return '\n'.join([ strip_spaces(l) for l in lines ]) + def strip_spaces(l, spaces=spaces): + if l[:len(spaces)] == spaces: + l = l[len(spaces):] + return l + lines = map(strip_spaces, lines) + return string.join(lines, '\n') + + def do_exit(self, argv): + """\ + exit Exit SCons interactive mode. + """ + sys.exit(0) + + def do_help(self, argv): + """\ + help [COMMAND] Prints help for the specified COMMAND. 'h' + and '?' are synonyms. + """ + if argv[1:]: + for arg in argv[1:]: + if self._do_one_help(arg): + break + else: + # If bare 'help' is called, print this class's doc + # string (if it has one). + doc = self._doc_to_help(self.__class__) + if doc: + sys.stdout.write(doc + '\n') + sys.stdout.flush() + + def do_shell(self, argv): + """\ + shell [COMMANDLINE] Execute COMMANDLINE in a subshell. 'sh' and + '!' are synonyms. + """ + import subprocess + argv = argv[1:] + if not argv: + argv = os.environ[self.shell_variable] + try: + # Per "[Python-Dev] subprocess insufficiently platform-independent?" + # http://mail.python.org/pipermail/python-dev/2008-August/081979.html "+ + # Doing the right thing with an argument list currently + # requires different shell= values on Windows and Linux. + p = subprocess.Popen(argv, shell=(sys.platform=='win32')) + except EnvironmentError, e: + sys.stderr.write('scons: %s: %s\n' % (argv[0], e.strerror)) + else: + p.wait() + + def do_version(self, argv): + """\ + version Prints SCons version information. + """ + sys.stdout.write(self.parser.version + '\n') + +def interact(fs, parser, options, targets, target_top): + c = SConsInteractiveCmd(prompt = 'scons>>> ', + fs = fs, + parser = parser, + options = options, + targets = targets, + target_top = target_top) + c.cmdloop() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/Main.py b/src/engine/SCons/Script/Main.py new file mode 100644 index 0000000..c7cbae8 --- /dev/null +++ b/src/engine/SCons/Script/Main.py @@ -0,0 +1,1360 @@ +"""SCons.Script + +This file implements the main() function used by the scons script. + +Architecturally, this *is* the scons script, and will likely only be +called from the external "scons" wrapper. Consequently, anything here +should not be, or be considered, part of the build engine. If it's +something that we expect other software to want to use, it should go in +some other module. If it's specific to the "scons" script invocation, +it goes here. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/Main.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string +import sys +import time +import traceback + +# Strip the script directory from sys.path() so on case-insensitive +# (Windows) systems Python doesn't think that the "scons" script is the +# "SCons" package. Replace it with our own version directory so, if +# if they're there, we pick up the right version of the build engine +# modules. +#sys.path = [os.path.join(sys.prefix, +# 'lib', +# 'scons-%d' % SCons.__version__)] + sys.path[1:] + +import SCons.CacheDir +import SCons.Debug +import SCons.Defaults +import SCons.Environment +import SCons.Errors +import SCons.Job +import SCons.Node +import SCons.Node.FS +import SCons.SConf +import SCons.Script +import SCons.Taskmaster +import SCons.Util +import SCons.Warnings + +import SCons.Script.Interactive + +def fetch_win32_parallel_msg(): + # A subsidiary function that exists solely to isolate this import + # so we don't have to pull it in on all platforms, and so that an + # in-line "import" statement in the _main() function below doesn't + # cause warnings about local names shadowing use of the 'SCons' + # globl in nest scopes and UnboundLocalErrors and the like in some + # versions (2.1) of Python. + import SCons.Platform.win32 + return SCons.Platform.win32.parallel_msg + +# + +class SConsPrintHelpException(Exception): + pass + +display = SCons.Util.display +progress_display = SCons.Util.DisplayEngine() + +first_command_start = None +last_command_end = None + +class Progressor: + prev = '' + count = 0 + target_string = '$TARGET' + + def __init__(self, obj, interval=1, file=None, overwrite=False): + if file is None: + file = sys.stdout + + self.obj = obj + self.file = file + self.interval = interval + self.overwrite = overwrite + + if callable(obj): + self.func = obj + elif SCons.Util.is_List(obj): + self.func = self.spinner + elif string.find(obj, self.target_string) != -1: + self.func = self.replace_string + else: + self.func = self.string + + def write(self, s): + self.file.write(s) + self.file.flush() + self.prev = s + + def erase_previous(self): + if self.prev: + length = len(self.prev) + if self.prev[-1] in ('\n', '\r'): + length = length - 1 + self.write(' ' * length + '\r') + self.prev = '' + + def spinner(self, node): + self.write(self.obj[self.count % len(self.obj)]) + + def string(self, node): + self.write(self.obj) + + def replace_string(self, node): + self.write(string.replace(self.obj, self.target_string, str(node))) + + def __call__(self, node): + self.count = self.count + 1 + if (self.count % self.interval) == 0: + if self.overwrite: + self.erase_previous() + self.func(node) + +ProgressObject = SCons.Util.Null() + +def Progress(*args, **kw): + global ProgressObject + ProgressObject = apply(Progressor, args, kw) + +# Task control. +# + +_BuildFailures = [] + +def GetBuildFailures(): + return _BuildFailures + +class BuildTask(SCons.Taskmaster.OutOfDateTask): + """An SCons build task.""" + progress = ProgressObject + + def display(self, message): + display('scons: ' + message) + + def prepare(self): + self.progress(self.targets[0]) + return SCons.Taskmaster.OutOfDateTask.prepare(self) + + def needs_execute(self): + if SCons.Taskmaster.OutOfDateTask.needs_execute(self): + return True + if self.top and self.targets[0].has_builder(): + display("scons: `%s' is up to date." % str(self.node)) + return False + + def execute(self): + if print_time: + start_time = time.time() + global first_command_start + if first_command_start is None: + first_command_start = start_time + SCons.Taskmaster.OutOfDateTask.execute(self) + if print_time: + global cumulative_command_time + global last_command_end + finish_time = time.time() + last_command_end = finish_time + cumulative_command_time = cumulative_command_time+finish_time-start_time + sys.stdout.write("Command execution time: %f seconds\n"%(finish_time-start_time)) + + def do_failed(self, status=2): + _BuildFailures.append(self.exception[1]) + global exit_status + global this_build_status + if self.options.ignore_errors: + SCons.Taskmaster.OutOfDateTask.executed(self) + elif self.options.keep_going: + SCons.Taskmaster.OutOfDateTask.fail_continue(self) + exit_status = status + this_build_status = status + else: + SCons.Taskmaster.OutOfDateTask.fail_stop(self) + exit_status = status + this_build_status = status + + def executed(self): + t = self.targets[0] + if self.top and not t.has_builder() and not t.side_effect: + if not t.exists(): + def classname(obj): + return string.split(str(obj.__class__), '.')[-1] + if classname(t) in ('File', 'Dir', 'Entry'): + errstr="Do not know how to make %s target `%s' (%s)." % (classname(t), t, t.abspath) + else: # Alias or Python or ... + errstr="Do not know how to make %s target `%s'." % (classname(t), t) + sys.stderr.write("scons: *** " + errstr) + if not self.options.keep_going: + sys.stderr.write(" Stop.") + sys.stderr.write("\n") + try: + raise SCons.Errors.BuildError(t, errstr) + except KeyboardInterrupt: + raise + except: + self.exception_set() + self.do_failed() + else: + print "scons: Nothing to be done for `%s'." % t + SCons.Taskmaster.OutOfDateTask.executed(self) + else: + SCons.Taskmaster.OutOfDateTask.executed(self) + + def failed(self): + # Handle the failure of a build task. The primary purpose here + # is to display the various types of Errors and Exceptions + # appropriately. + exc_info = self.exc_info() + try: + t, e, tb = exc_info + except ValueError: + t, e = exc_info + tb = None + + if t is None: + # The Taskmaster didn't record an exception for this Task; + # see if the sys module has one. + try: + t, e, tb = sys.exc_info()[:] + except ValueError: + t, e = exc_info + tb = None + + # Deprecated string exceptions will have their string stored + # in the first entry of the tuple. + if e is None: + e = t + + buildError = SCons.Errors.convert_to_BuildError(e) + if not buildError.node: + buildError.node = self.node + + node = buildError.node + if not SCons.Util.is_List(node): + node = [ node ] + nodename = string.join(map(str, node), ', ') + + errfmt = "scons: *** [%s] %s\n" + sys.stderr.write(errfmt % (nodename, buildError)) + + if (buildError.exc_info[2] and buildError.exc_info[1] and + # TODO(1.5) + #not isinstance( + # buildError.exc_info[1], + # (EnvironmentError, SCons.Errors.StopError, SCons.Errors.UserError))): + not isinstance(buildError.exc_info[1], EnvironmentError) and + not isinstance(buildError.exc_info[1], SCons.Errors.StopError) and + not isinstance(buildError.exc_info[1], SCons.Errors.UserError)): + type, value, trace = buildError.exc_info + traceback.print_exception(type, value, trace) + elif tb and print_stacktrace: + sys.stderr.write("scons: internal stack trace:\n") + traceback.print_tb(tb, file=sys.stderr) + + self.exception = (e, buildError, tb) # type, value, traceback + self.do_failed(buildError.exitstatus) + + self.exc_clear() + + def postprocess(self): + if self.top: + t = self.targets[0] + for tp in self.options.tree_printers: + tp.display(t) + if self.options.debug_includes: + tree = t.render_include_tree() + if tree: + print + print tree + SCons.Taskmaster.OutOfDateTask.postprocess(self) + + def make_ready(self): + """Make a task ready for execution""" + SCons.Taskmaster.OutOfDateTask.make_ready(self) + if self.out_of_date and self.options.debug_explain: + explanation = self.out_of_date[0].explain() + if explanation: + sys.stdout.write("scons: " + explanation) + +class CleanTask(SCons.Taskmaster.AlwaysTask): + """An SCons clean task.""" + def fs_delete(self, path, pathstr, remove=1): + try: + if os.path.lexists(path): + if os.path.isfile(path) or os.path.islink(path): + if remove: os.unlink(path) + display("Removed " + pathstr) + elif os.path.isdir(path) and not os.path.islink(path): + # delete everything in the dir + entries = os.listdir(path) + # Sort for deterministic output (os.listdir() Can + # return entries in a random order). + entries.sort() + for e in entries: + p = os.path.join(path, e) + s = os.path.join(pathstr, e) + if os.path.isfile(p): + if remove: os.unlink(p) + display("Removed " + s) + else: + self.fs_delete(p, s, remove) + # then delete dir itself + if remove: os.rmdir(path) + display("Removed directory " + pathstr) + else: + errstr = "Path '%s' exists but isn't a file or directory." + raise SCons.Errors.UserError(errstr % (pathstr)) + except SCons.Errors.UserError, e: + print e + except (IOError, OSError), e: + print "scons: Could not remove '%s':" % pathstr, e.strerror + + def show(self): + target = self.targets[0] + if (target.has_builder() or target.side_effect) and not target.noclean: + for t in self.targets: + if not t.isdir(): + display("Removed " + str(t)) + if SCons.Environment.CleanTargets.has_key(target): + files = SCons.Environment.CleanTargets[target] + for f in files: + self.fs_delete(f.abspath, str(f), 0) + + def remove(self): + target = self.targets[0] + if (target.has_builder() or target.side_effect) and not target.noclean: + for t in self.targets: + try: + removed = t.remove() + except OSError, e: + # An OSError may indicate something like a permissions + # issue, an IOError would indicate something like + # the file not existing. In either case, print a + # message and keep going to try to remove as many + # targets aa possible. + print "scons: Could not remove '%s':" % str(t), e.strerror + else: + if removed: + display("Removed " + str(t)) + if SCons.Environment.CleanTargets.has_key(target): + files = SCons.Environment.CleanTargets[target] + for f in files: + self.fs_delete(f.abspath, str(f)) + + execute = remove + + # We want the Taskmaster to update the Node states (and therefore + # handle reference counts, etc.), but we don't want to call + # back to the Node's post-build methods, which would do things + # we don't want, like store .sconsign information. + executed = SCons.Taskmaster.Task.executed_without_callbacks + + # Have the taskmaster arrange to "execute" all of the targets, because + # we'll figure out ourselves (in remove() or show() above) whether + # anything really needs to be done. + make_ready = SCons.Taskmaster.Task.make_ready_all + + def prepare(self): + pass + +class QuestionTask(SCons.Taskmaster.AlwaysTask): + """An SCons task for the -q (question) option.""" + def prepare(self): + pass + + def execute(self): + if self.targets[0].get_state() != SCons.Node.up_to_date or \ + (self.top and not self.targets[0].exists()): + global exit_status + global this_build_status + exit_status = 1 + this_build_status = 1 + self.tm.stop() + + def executed(self): + pass + + +class TreePrinter: + def __init__(self, derived=False, prune=False, status=False): + self.derived = derived + self.prune = prune + self.status = status + def get_all_children(self, node): + return node.all_children() + def get_derived_children(self, node): + children = node.all_children(None) + return filter(lambda x: x.has_builder(), children) + def display(self, t): + if self.derived: + func = self.get_derived_children + else: + func = self.get_all_children + s = self.status and 2 or 0 + SCons.Util.print_tree(t, func, prune=self.prune, showtags=s) + + +def python_version_string(): + return string.split(sys.version)[0] + +def python_version_unsupported(version=sys.version_info): + return version < (1, 5, 2) + +def python_version_deprecated(version=sys.version_info): + return version < (2, 4, 0) + + +# Global variables + +print_objects = 0 +print_memoizer = 0 +print_stacktrace = 0 +print_time = 0 +sconscript_time = 0 +cumulative_command_time = 0 +exit_status = 0 # final exit status, assume success by default +this_build_status = 0 # "exit status" of an individual build +num_jobs = None +delayed_warnings = [] + +class FakeOptionParser: + """ + A do-nothing option parser, used for the initial OptionsParser variable. + + During normal SCons operation, the OptionsParser is created right + away by the main() function. Certain tests scripts however, can + introspect on different Tool modules, the initialization of which + can try to add a new, local option to an otherwise uninitialized + OptionsParser object. This allows that introspection to happen + without blowing up. + + """ + class FakeOptionValues: + def __getattr__(self, attr): + return None + values = FakeOptionValues() + def add_local_option(self, *args, **kw): + pass + +OptionsParser = FakeOptionParser() + +def AddOption(*args, **kw): + if not kw.has_key('default'): + kw['default'] = None + result = apply(OptionsParser.add_local_option, args, kw) + return result + +def GetOption(name): + return getattr(OptionsParser.values, name) + +def SetOption(name, value): + return OptionsParser.values.set_option(name, value) + +# +class Stats: + def __init__(self): + self.stats = [] + self.labels = [] + self.append = self.do_nothing + self.print_stats = self.do_nothing + def enable(self, outfp): + self.outfp = outfp + self.append = self.do_append + self.print_stats = self.do_print + def do_nothing(self, *args, **kw): + pass + +class CountStats(Stats): + def do_append(self, label): + self.labels.append(label) + self.stats.append(SCons.Debug.fetchLoggedInstances()) + def do_print(self): + stats_table = {} + for s in self.stats: + for n in map(lambda t: t[0], s): + stats_table[n] = [0, 0, 0, 0] + i = 0 + for s in self.stats: + for n, c in s: + stats_table[n][i] = c + i = i + 1 + keys = stats_table.keys() + keys.sort() + self.outfp.write("Object counts:\n") + pre = [" "] + post = [" %s\n"] + l = len(self.stats) + fmt1 = string.join(pre + [' %7s']*l + post, '') + fmt2 = string.join(pre + [' %7d']*l + post, '') + labels = self.labels[:l] + labels.append(("", "Class")) + self.outfp.write(fmt1 % tuple(map(lambda x: x[0], labels))) + self.outfp.write(fmt1 % tuple(map(lambda x: x[1], labels))) + for k in keys: + r = stats_table[k][:l] + [k] + self.outfp.write(fmt2 % tuple(r)) + +count_stats = CountStats() + +class MemStats(Stats): + def do_append(self, label): + self.labels.append(label) + self.stats.append(SCons.Debug.memory()) + def do_print(self): + fmt = 'Memory %-32s %12d\n' + for label, stats in map(None, self.labels, self.stats): + self.outfp.write(fmt % (label, stats)) + +memory_stats = MemStats() + +# utility functions + +def _scons_syntax_error(e): + """Handle syntax errors. Print out a message and show where the error + occurred. + """ + etype, value, tb = sys.exc_info() + lines = traceback.format_exception_only(etype, value) + for line in lines: + sys.stderr.write(line+'\n') + sys.exit(2) + +def find_deepest_user_frame(tb): + """ + Find the deepest stack frame that is not part of SCons. + + Input is a "pre-processed" stack trace in the form + returned by traceback.extract_tb() or traceback.extract_stack() + """ + + tb.reverse() + + # find the deepest traceback frame that is not part + # of SCons: + for frame in tb: + filename = frame[0] + if string.find(filename, os.sep+'SCons'+os.sep) == -1: + return frame + return tb[0] + +def _scons_user_error(e): + """Handle user errors. Print out a message and a description of the + error, along with the line number and routine where it occured. + The file and line number will be the deepest stack frame that is + not part of SCons itself. + """ + global print_stacktrace + etype, value, tb = sys.exc_info() + if print_stacktrace: + traceback.print_exception(etype, value, tb) + filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb)) + sys.stderr.write("\nscons: *** %s\n" % value) + sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) + sys.exit(2) + +def _scons_user_warning(e): + """Handle user warnings. Print out a message and a description of + the warning, along with the line number and routine where it occured. + The file and line number will be the deepest stack frame that is + not part of SCons itself. + """ + etype, value, tb = sys.exc_info() + filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_tb(tb)) + sys.stderr.write("\nscons: warning: %s\n" % e) + sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) + +def _scons_internal_warning(e): + """Slightly different from _scons_user_warning in that we use the + *current call stack* rather than sys.exc_info() to get our stack trace. + This is used by the warnings framework to print warnings.""" + filename, lineno, routine, dummy = find_deepest_user_frame(traceback.extract_stack()) + sys.stderr.write("\nscons: warning: %s\n" % e[0]) + sys.stderr.write('File "%s", line %d, in %s\n' % (filename, lineno, routine)) + +def _scons_internal_error(): + """Handle all errors but user errors. Print out a message telling + the user what to do in this case and print a normal trace. + """ + print 'internal error' + traceback.print_exc() + sys.exit(2) + +def _SConstruct_exists(dirname='', repositories=[], filelist=None): + """This function checks that an SConstruct file exists in a directory. + If so, it returns the path of the file. By default, it checks the + current directory. + """ + if not filelist: + filelist = ['SConstruct', 'Sconstruct', 'sconstruct'] + for file in filelist: + sfile = os.path.join(dirname, file) + if os.path.isfile(sfile): + return sfile + if not os.path.isabs(sfile): + for rep in repositories: + if os.path.isfile(os.path.join(rep, sfile)): + return sfile + return None + +def _set_debug_values(options): + global print_memoizer, print_objects, print_stacktrace, print_time + + debug_values = options.debug + + if "count" in debug_values: + # All of the object counts are within "if __debug__:" blocks, + # which get stripped when running optimized (with python -O or + # from compiled *.pyo files). Provide a warning if __debug__ is + # stripped, so it doesn't just look like --debug=count is broken. + enable_count = False + if __debug__: enable_count = True + if enable_count: + count_stats.enable(sys.stdout) + else: + msg = "--debug=count is not supported when running SCons\n" + \ + "\twith the python -O option or optimized (.pyo) modules." + SCons.Warnings.warn(SCons.Warnings.NoObjectCountWarning, msg) + if "dtree" in debug_values: + options.tree_printers.append(TreePrinter(derived=True)) + options.debug_explain = ("explain" in debug_values) + if "findlibs" in debug_values: + SCons.Scanner.Prog.print_find_libs = "findlibs" + options.debug_includes = ("includes" in debug_values) + print_memoizer = ("memoizer" in debug_values) + if "memory" in debug_values: + memory_stats.enable(sys.stdout) + print_objects = ("objects" in debug_values) + if "presub" in debug_values: + SCons.Action.print_actions_presub = 1 + if "stacktrace" in debug_values: + print_stacktrace = 1 + if "stree" in debug_values: + options.tree_printers.append(TreePrinter(status=True)) + if "time" in debug_values: + print_time = 1 + if "tree" in debug_values: + options.tree_printers.append(TreePrinter()) + +def _create_path(plist): + path = '.' + for d in plist: + if os.path.isabs(d): + path = d + else: + path = path + '/' + d + return path + +def _load_site_scons_dir(topdir, site_dir_name=None): + """Load the site_scons dir under topdir. + Adds site_scons to sys.path, imports site_scons/site_init.py, + and adds site_scons/site_tools to default toolpath.""" + if site_dir_name: + err_if_not_found = True # user specified: err if missing + else: + site_dir_name = "site_scons" + err_if_not_found = False + + site_dir = os.path.join(topdir.path, site_dir_name) + if not os.path.exists(site_dir): + if err_if_not_found: + raise SCons.Errors.UserError, "site dir %s not found."%site_dir + return + + site_init_filename = "site_init.py" + site_init_modname = "site_init" + site_tools_dirname = "site_tools" + sys.path = [os.path.abspath(site_dir)] + sys.path + site_init_file = os.path.join(site_dir, site_init_filename) + site_tools_dir = os.path.join(site_dir, site_tools_dirname) + if os.path.exists(site_init_file): + import imp + # TODO(2.4): turn this into try:-except:-finally: + try: + try: + fp, pathname, description = imp.find_module(site_init_modname, + [site_dir]) + # Load the file into SCons.Script namespace. This is + # opaque and clever; m is the module object for the + # SCons.Script module, and the exec ... in call executes a + # file (or string containing code) in the context of the + # module's dictionary, so anything that code defines ends + # up adding to that module. This is really short, but all + # the error checking makes it longer. + try: + m = sys.modules['SCons.Script'] + except Exception, e: + fmt = 'cannot import site_init.py: missing SCons.Script module %s' + raise SCons.Errors.InternalError, fmt % repr(e) + try: + # This is the magic. + exec fp in m.__dict__ + except KeyboardInterrupt: + raise + except Exception, e: + fmt = '*** Error loading site_init file %s:\n' + sys.stderr.write(fmt % repr(site_init_file)) + raise + except KeyboardInterrupt: + raise + except ImportError, e: + fmt = '*** cannot import site init file %s:\n' + sys.stderr.write(fmt % repr(site_init_file)) + raise + finally: + if fp: + fp.close() + if os.path.exists(site_tools_dir): + SCons.Tool.DefaultToolpath.append(os.path.abspath(site_tools_dir)) + +def version_string(label, module): + version = module.__version__ + build = module.__build__ + if build: + if build[0] != '.': + build = '.' + build + version = version + build + fmt = "\t%s: v%s, %s, by %s on %s\n" + return fmt % (label, + version, + module.__date__, + module.__developer__, + module.__buildsys__) + +def _main(parser): + global exit_status + global this_build_status + + options = parser.values + + # Here's where everything really happens. + + # First order of business: set up default warnings and then + # handle the user's warning options, so that we can issue (or + # suppress) appropriate warnings about anything that might happen, + # as configured by the user. + + default_warnings = [ SCons.Warnings.CorruptSConsignWarning, + SCons.Warnings.DeprecatedWarning, + SCons.Warnings.DuplicateEnvironmentWarning, + SCons.Warnings.FutureReservedVariableWarning, + SCons.Warnings.LinkWarning, + SCons.Warnings.MissingSConscriptWarning, + SCons.Warnings.NoMD5ModuleWarning, + SCons.Warnings.NoMetaclassSupportWarning, + SCons.Warnings.NoObjectCountWarning, + SCons.Warnings.NoParallelSupportWarning, + SCons.Warnings.MisleadingKeywordsWarning, + SCons.Warnings.ReservedVariableWarning, + SCons.Warnings.StackSizeWarning, + SCons.Warnings.VisualVersionMismatch, + SCons.Warnings.VisualCMissingWarning, + ] + + for warning in default_warnings: + SCons.Warnings.enableWarningClass(warning) + SCons.Warnings._warningOut = _scons_internal_warning + SCons.Warnings.process_warn_strings(options.warn) + + # Now that we have the warnings configuration set up, we can actually + # issue (or suppress) any warnings about warning-worthy things that + # occurred while the command-line options were getting parsed. + try: + dw = options.delayed_warnings + except AttributeError: + pass + else: + delayed_warnings.extend(dw) + for warning_type, message in delayed_warnings: + SCons.Warnings.warn(warning_type, message) + + if options.diskcheck: + SCons.Node.FS.set_diskcheck(options.diskcheck) + + # Next, we want to create the FS object that represents the outside + # world's file system, as that's central to a lot of initialization. + # To do this, however, we need to be in the directory from which we + # want to start everything, which means first handling any relevant + # options that might cause us to chdir somewhere (-C, -D, -U, -u). + if options.directory: + script_dir = os.path.abspath(_create_path(options.directory)) + else: + script_dir = os.getcwd() + + target_top = None + if options.climb_up: + target_top = '.' # directory to prepend to targets + while script_dir and not _SConstruct_exists(script_dir, + options.repository, + options.file): + script_dir, last_part = os.path.split(script_dir) + if last_part: + target_top = os.path.join(last_part, target_top) + else: + script_dir = '' + + if script_dir and script_dir != os.getcwd(): + display("scons: Entering directory `%s'" % script_dir) + try: + os.chdir(script_dir) + except OSError: + sys.stderr.write("Could not change directory to %s\n" % script_dir) + + # Now that we're in the top-level SConstruct directory, go ahead + # and initialize the FS object that represents the file system, + # and make it the build engine default. + fs = SCons.Node.FS.get_default_fs() + + for rep in options.repository: + fs.Repository(rep) + + # Now that we have the FS object, the next order of business is to + # check for an SConstruct file (or other specified config file). + # If there isn't one, we can bail before doing any more work. + scripts = [] + if options.file: + scripts.extend(options.file) + if not scripts: + sfile = _SConstruct_exists(repositories=options.repository, + filelist=options.file) + if sfile: + scripts.append(sfile) + + if not scripts: + if options.help: + # There's no SConstruct, but they specified -h. + # Give them the options usage now, before we fail + # trying to read a non-existent SConstruct file. + raise SConsPrintHelpException + raise SCons.Errors.UserError, "No SConstruct file found." + + if scripts[0] == "-": + d = fs.getcwd() + else: + d = fs.File(scripts[0]).dir + fs.set_SConstruct_dir(d) + + _set_debug_values(options) + SCons.Node.implicit_cache = options.implicit_cache + SCons.Node.implicit_deps_changed = options.implicit_deps_changed + SCons.Node.implicit_deps_unchanged = options.implicit_deps_unchanged + + if options.no_exec: + SCons.SConf.dryrun = 1 + SCons.Action.execute_actions = None + if options.question: + SCons.SConf.dryrun = 1 + if options.clean: + SCons.SConf.SetBuildType('clean') + if options.help: + SCons.SConf.SetBuildType('help') + SCons.SConf.SetCacheMode(options.config) + SCons.SConf.SetProgressDisplay(progress_display) + + if options.no_progress or options.silent: + progress_display.set_mode(0) + + if options.site_dir: + _load_site_scons_dir(d, options.site_dir) + elif not options.no_site_dir: + _load_site_scons_dir(d) + + if options.include_dir: + sys.path = options.include_dir + sys.path + + # That should cover (most of) the options. Next, set up the variables + # that hold command-line arguments, so the SConscript files that we + # read and execute have access to them. + targets = [] + xmit_args = [] + for a in parser.largs: + if a[:1] == '-': + continue + if '=' in a: + xmit_args.append(a) + else: + targets.append(a) + SCons.Script._Add_Targets(targets + parser.rargs) + SCons.Script._Add_Arguments(xmit_args) + + # If stdout is not a tty, replace it with a wrapper object to call flush + # after every write. + # + # Tty devices automatically flush after every newline, so the replacement + # isn't necessary. Furthermore, if we replace sys.stdout, the readline + # module will no longer work. This affects the behavior during + # --interactive mode. --interactive should only be used when stdin and + # stdout refer to a tty. + if not hasattr(sys.stdout, 'isatty') or not sys.stdout.isatty(): + sys.stdout = SCons.Util.Unbuffered(sys.stdout) + if not hasattr(sys.stderr, 'isatty') or not sys.stderr.isatty(): + sys.stderr = SCons.Util.Unbuffered(sys.stderr) + + memory_stats.append('before reading SConscript files:') + count_stats.append(('pre-', 'read')) + + # And here's where we (finally) read the SConscript files. + + progress_display("scons: Reading SConscript files ...") + + start_time = time.time() + try: + for script in scripts: + SCons.Script._SConscript._SConscript(fs, script) + except SCons.Errors.StopError, e: + # We had problems reading an SConscript file, such as it + # couldn't be copied in to the VariantDir. Since we're just + # reading SConscript files and haven't started building + # things yet, stop regardless of whether they used -i or -k + # or anything else. + sys.stderr.write("scons: *** %s Stop.\n" % e) + exit_status = 2 + sys.exit(exit_status) + global sconscript_time + sconscript_time = time.time() - start_time + + progress_display("scons: done reading SConscript files.") + + memory_stats.append('after reading SConscript files:') + count_stats.append(('post-', 'read')) + + # Re-{enable,disable} warnings in case they disabled some in + # the SConscript file. + # + # We delay enabling the PythonVersionWarning class until here so that, + # if they explicity disabled it in either in the command line or in + # $SCONSFLAGS, or in the SConscript file, then the search through + # the list of deprecated warning classes will find that disabling + # first and not issue the warning. + SCons.Warnings.enableWarningClass(SCons.Warnings.PythonVersionWarning) + SCons.Warnings.process_warn_strings(options.warn) + + # Now that we've read the SConscript files, we can check for the + # warning about deprecated Python versions--delayed until here + # in case they disabled the warning in the SConscript files. + if python_version_deprecated(): + msg = "Support for pre-2.4 Python (%s) is deprecated.\n" + \ + " If this will cause hardship, contact dev@scons.tigris.org." + SCons.Warnings.warn(SCons.Warnings.PythonVersionWarning, + msg % python_version_string()) + + if not options.help: + SCons.SConf.CreateConfigHBuilder(SCons.Defaults.DefaultEnvironment()) + + # Now re-parse the command-line options (any to the left of a '--' + # argument, that is) with any user-defined command-line options that + # the SConscript files may have added to the parser object. This will + # emit the appropriate error message and exit if any unknown option + # was specified on the command line. + + parser.preserve_unknown_options = False + parser.parse_args(parser.largs, options) + + if options.help: + help_text = SCons.Script.help_text + if help_text is None: + # They specified -h, but there was no Help() inside the + # SConscript files. Give them the options usage. + raise SConsPrintHelpException + else: + print help_text + print "Use scons -H for help about command-line options." + exit_status = 0 + return + + # Change directory to the top-level SConstruct directory, then tell + # the Node.FS subsystem that we're all done reading the SConscript + # files and calling Repository() and VariantDir() and changing + # directories and the like, so it can go ahead and start memoizing + # the string values of file system nodes. + + fs.chdir(fs.Top) + + SCons.Node.FS.save_strings(1) + + # Now that we've read the SConscripts we can set the options + # that are SConscript settable: + SCons.Node.implicit_cache = options.implicit_cache + SCons.Node.FS.set_duplicate(options.duplicate) + fs.set_max_drift(options.max_drift) + + SCons.Job.explicit_stack_size = options.stack_size + + if options.md5_chunksize: + SCons.Node.FS.File.md5_chunksize = options.md5_chunksize + + platform = SCons.Platform.platform_module() + + if options.interactive: + SCons.Script.Interactive.interact(fs, OptionsParser, options, + targets, target_top) + + else: + + # Build the targets + nodes = _build_targets(fs, options, targets, target_top) + if not nodes: + exit_status = 2 + +def _build_targets(fs, options, targets, target_top): + + global this_build_status + this_build_status = 0 + + progress_display.set_mode(not (options.no_progress or options.silent)) + display.set_mode(not options.silent) + SCons.Action.print_actions = not options.silent + SCons.Action.execute_actions = not options.no_exec + SCons.Node.FS.do_store_info = not options.no_exec + SCons.SConf.dryrun = options.no_exec + + if options.diskcheck: + SCons.Node.FS.set_diskcheck(options.diskcheck) + + SCons.CacheDir.cache_enabled = not options.cache_disable + SCons.CacheDir.cache_debug = options.cache_debug + SCons.CacheDir.cache_force = options.cache_force + SCons.CacheDir.cache_show = options.cache_show + + if options.no_exec: + CleanTask.execute = CleanTask.show + else: + CleanTask.execute = CleanTask.remove + + lookup_top = None + if targets or SCons.Script.BUILD_TARGETS != SCons.Script._build_plus_default: + # They specified targets on the command line or modified + # BUILD_TARGETS in the SConscript file(s), so if they used -u, + # -U or -D, we have to look up targets relative to the top, + # but we build whatever they specified. + if target_top: + lookup_top = fs.Dir(target_top) + target_top = None + + targets = SCons.Script.BUILD_TARGETS + else: + # There are no targets specified on the command line, + # so if they used -u, -U or -D, we may have to restrict + # what actually gets built. + d = None + if target_top: + if options.climb_up == 1: + # -u, local directory and below + target_top = fs.Dir(target_top) + lookup_top = target_top + elif options.climb_up == 2: + # -D, all Default() targets + target_top = None + lookup_top = None + elif options.climb_up == 3: + # -U, local SConscript Default() targets + target_top = fs.Dir(target_top) + def check_dir(x, target_top=target_top): + if hasattr(x, 'cwd') and not x.cwd is None: + cwd = x.cwd.srcnode() + return cwd == target_top + else: + # x doesn't have a cwd, so it's either not a target, + # or not a file, so go ahead and keep it as a default + # target and let the engine sort it out: + return 1 + d = filter(check_dir, SCons.Script.DEFAULT_TARGETS) + SCons.Script.DEFAULT_TARGETS[:] = d + target_top = None + lookup_top = None + + targets = SCons.Script._Get_Default_Targets(d, fs) + + if not targets: + sys.stderr.write("scons: *** No targets specified and no Default() targets found. Stop.\n") + return None + + def Entry(x, ltop=lookup_top, ttop=target_top, fs=fs): + if isinstance(x, SCons.Node.Node): + node = x + else: + node = None + # Why would ltop be None? Unfortunately this happens. + if ltop is None: ltop = '' + # Curdir becomes important when SCons is called with -u, -C, + # or similar option that changes directory, and so the paths + # of targets given on the command line need to be adjusted. + curdir = os.path.join(os.getcwd(), str(ltop)) + for lookup in SCons.Node.arg2nodes_lookups: + node = lookup(x, curdir=curdir) + if node is not None: + break + if node is None: + node = fs.Entry(x, directory=ltop, create=1) + if ttop and not node.is_under(ttop): + if isinstance(node, SCons.Node.FS.Dir) and ttop.is_under(node): + node = ttop + else: + node = None + return node + + nodes = filter(None, map(Entry, targets)) + + task_class = BuildTask # default action is to build targets + opening_message = "Building targets ..." + closing_message = "done building targets." + if options.keep_going: + failure_message = "done building targets (errors occurred during build)." + else: + failure_message = "building terminated because of errors." + if options.question: + task_class = QuestionTask + try: + if options.clean: + task_class = CleanTask + opening_message = "Cleaning targets ..." + closing_message = "done cleaning targets." + if options.keep_going: + failure_message = "done cleaning targets (errors occurred during clean)." + else: + failure_message = "cleaning terminated because of errors." + except AttributeError: + pass + + task_class.progress = ProgressObject + + if options.random: + def order(dependencies): + """Randomize the dependencies.""" + import random + # This is cribbed from the implementation of + # random.shuffle() in Python 2.X. + d = dependencies + for i in xrange(len(d)-1, 0, -1): + j = int(random.random() * (i+1)) + d[i], d[j] = d[j], d[i] + return d + else: + def order(dependencies): + """Leave the order of dependencies alone.""" + return dependencies + + if options.taskmastertrace_file == '-': + tmtrace = sys.stdout + elif options.taskmastertrace_file: + tmtrace = open(options.taskmastertrace_file, 'wb') + else: + tmtrace = None + taskmaster = SCons.Taskmaster.Taskmaster(nodes, task_class, order, tmtrace) + + # Let the BuildTask objects get at the options to respond to the + # various print_* settings, tree_printer list, etc. + BuildTask.options = options + + global num_jobs + num_jobs = options.num_jobs + jobs = SCons.Job.Jobs(num_jobs, taskmaster) + if num_jobs > 1: + msg = None + if jobs.num_jobs == 1: + msg = "parallel builds are unsupported by this version of Python;\n" + \ + "\tignoring -j or num_jobs option.\n" + elif sys.platform == 'win32': + msg = fetch_win32_parallel_msg() + if msg: + SCons.Warnings.warn(SCons.Warnings.NoParallelSupportWarning, msg) + + memory_stats.append('before building targets:') + count_stats.append(('pre-', 'build')) + + def jobs_postfunc( + jobs=jobs, + options=options, + closing_message=closing_message, + failure_message=failure_message + ): + if jobs.were_interrupted(): + if not options.no_progress and not options.silent: + sys.stderr.write("scons: Build interrupted.\n") + global exit_status + global this_build_status + exit_status = 2 + this_build_status = 2 + + if this_build_status: + progress_display("scons: " + failure_message) + else: + progress_display("scons: " + closing_message) + if not options.no_exec: + if jobs.were_interrupted(): + progress_display("scons: writing .sconsign file.") + SCons.SConsign.write() + + progress_display("scons: " + opening_message) + jobs.run(postfunc = jobs_postfunc) + + memory_stats.append('after building targets:') + count_stats.append(('post-', 'build')) + + return nodes + +def _exec_main(parser, values): + sconsflags = os.environ.get('SCONSFLAGS', '') + all_args = string.split(sconsflags) + sys.argv[1:] + + options, args = parser.parse_args(all_args, values) + + if type(options.debug) == type([]) and "pdb" in options.debug: + import pdb + pdb.Pdb().runcall(_main, parser) + elif options.profile_file: + try: + from cProfile import Profile + except ImportError, e: + from profile import Profile + + # Some versions of Python 2.4 shipped a profiler that had the + # wrong 'c_exception' entry in its dispatch table. Make sure + # we have the right one. (This may put an unnecessary entry + # in the table in earlier versions of Python, but its presence + # shouldn't hurt anything). + try: + dispatch = Profile.dispatch + except AttributeError: + pass + else: + dispatch['c_exception'] = Profile.trace_dispatch_return + + prof = Profile() + try: + prof.runcall(_main, parser) + except SConsPrintHelpException, e: + prof.dump_stats(options.profile_file) + raise e + except SystemExit: + pass + prof.dump_stats(options.profile_file) + else: + _main(parser) + +def main(): + global OptionsParser + global exit_status + global first_command_start + + # Check up front for a Python version we do not support. We + # delay the check for deprecated Python versions until later, + # after the SConscript files have been read, in case they + # disable that warning. + if python_version_unsupported(): + msg = "scons: *** SCons version %s does not run under Python version %s.\n" + sys.stderr.write(msg % (SCons.__version__, python_version_string())) + sys.exit(1) + + parts = ["SCons by Steven Knight et al.:\n"] + try: + import __main__ + parts.append(version_string("script", __main__)) + except (ImportError, AttributeError): + # On Windows there is no scons.py, so there is no + # __main__.__version__, hence there is no script version. + pass + parts.append(version_string("engine", SCons)) + parts.append("Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation") + version = string.join(parts, '') + + import SConsOptions + parser = SConsOptions.Parser(version) + values = SConsOptions.SConsValues(parser.get_default_values()) + + OptionsParser = parser + + try: + _exec_main(parser, values) + except SystemExit, s: + if s: + exit_status = s + except KeyboardInterrupt: + print("scons: Build interrupted.") + sys.exit(2) + except SyntaxError, e: + _scons_syntax_error(e) + except SCons.Errors.InternalError: + _scons_internal_error() + except SCons.Errors.UserError, e: + _scons_user_error(e) + except SConsPrintHelpException: + parser.print_help() + exit_status = 0 + except SCons.Errors.BuildError, e: + exit_status = e.exitstatus + except: + # An exception here is likely a builtin Python exception Python + # code in an SConscript file. Show them precisely what the + # problem was and where it happened. + SCons.Script._SConscript.SConscript_exception() + sys.exit(2) + + memory_stats.print_stats() + count_stats.print_stats() + + if print_objects: + SCons.Debug.listLoggedInstances('*') + #SCons.Debug.dumpLoggedInstances('*') + + if print_memoizer: + SCons.Memoize.Dump("Memoizer (memory cache) hits and misses:") + + # Dump any development debug info that may have been enabled. + # These are purely for internal debugging during development, so + # there's no need to control them with --debug= options; they're + # controlled by changing the source code. + SCons.Debug.dump_caller_counts() + SCons.Taskmaster.dump_stats() + + if print_time: + total_time = time.time() - SCons.Script.start_time + if num_jobs == 1: + ct = cumulative_command_time + else: + if last_command_end is None or first_command_start is None: + ct = 0.0 + else: + ct = last_command_end - first_command_start + scons_time = total_time - sconscript_time - ct + print "Total build time: %f seconds"%total_time + print "Total SConscript file execution time: %f seconds"%sconscript_time + print "Total SCons execution time: %f seconds"%scons_time + print "Total command execution time: %f seconds"%ct + + sys.exit(exit_status) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/MainTests.py b/src/engine/SCons/Script/MainTests.py new file mode 100644 index 0000000..5dbd4db --- /dev/null +++ b/src/engine/SCons/Script/MainTests.py @@ -0,0 +1,53 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/MainTests.py 4577 2009/12/27 19:44:43 scons" + +import unittest +import SCons.Errors +import SCons.Script.Main + +# Unit tests of various classes within SCons.Script.Main.py. +# +# Most of the tests of this functionality are actually end-to-end scripts +# in the test/ hierarchy. +# +# This module is for specific bits of functionality that we can test +# more effectively here, instead of in an end-to-end test that would +# have to reach into SCons.Script.Main for various classes or other bits +# of private functionality. + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/SConsOptions.py b/src/engine/SCons/Script/SConsOptions.py new file mode 100644 index 0000000..585e5ef --- /dev/null +++ b/src/engine/SCons/Script/SConsOptions.py @@ -0,0 +1,944 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/SConsOptions.py 4577 2009/12/27 19:44:43 scons" + +import optparse +import re +import string +import sys +import textwrap + +try: + no_hyphen_re = re.compile(r'(\s+|(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') +except re.error: + # Pre-2.0 Python versions don't have the (?<= negative + # look-behind assertion. + no_hyphen_re = re.compile(r'(\s+|-*\w{2,}-(?=\w{2,}))') + +try: + from gettext import gettext +except ImportError: + def gettext(message): + return message +_ = gettext + +import SCons.Node.FS +import SCons.Warnings + +OptionValueError = optparse.OptionValueError +SUPPRESS_HELP = optparse.SUPPRESS_HELP + +diskcheck_all = SCons.Node.FS.diskcheck_types() + +def diskcheck_convert(value): + if value is None: + return [] + if not SCons.Util.is_List(value): + value = string.split(value, ',') + result = [] + for v in map(string.lower, value): + if v == 'all': + result = diskcheck_all + elif v == 'none': + result = [] + elif v in diskcheck_all: + result.append(v) + else: + raise ValueError, v + return result + +class SConsValues(optparse.Values): + """ + Holder class for uniform access to SCons options, regardless + of whether or not they can be set on the command line or in the + SConscript files (using the SetOption() function). + + A SCons option value can originate three different ways: + + 1) set on the command line; + 2) set in an SConscript file; + 3) the default setting (from the the op.add_option() + calls in the Parser() function, below). + + The command line always overrides a value set in a SConscript file, + which in turn always overrides default settings. Because we want + to support user-specified options in the SConscript file itself, + though, we may not know about all of the options when the command + line is first parsed, so we can't make all the necessary precedence + decisions at the time the option is configured. + + The solution implemented in this class is to keep these different sets + of settings separate (command line, SConscript file, and default) + and to override the __getattr__() method to check them in turn. + This should allow the rest of the code to just fetch values as + attributes of an instance of this class, without having to worry + about where they came from. + + Note that not all command line options are settable from SConscript + files, and the ones that are must be explicitly added to the + "settable" list in this class, and optionally validated and coerced + in the set_option() method. + """ + + def __init__(self, defaults): + self.__dict__['__defaults__'] = defaults + self.__dict__['__SConscript_settings__'] = {} + + def __getattr__(self, attr): + """ + Fetches an options value, checking first for explicit settings + from the command line (which are direct attributes), then the + SConscript file settings, then the default values. + """ + try: + return self.__dict__[attr] + except KeyError: + try: + return self.__dict__['__SConscript_settings__'][attr] + except KeyError: + return getattr(self.__dict__['__defaults__'], attr) + + settable = [ + 'clean', + 'diskcheck', + 'duplicate', + 'help', + 'implicit_cache', + 'max_drift', + 'md5_chunksize', + 'no_exec', + 'num_jobs', + 'random', + 'stack_size', + 'warn', + ] + + def set_option(self, name, value): + """ + Sets an option from an SConscript file. + """ + if not name in self.settable: + raise SCons.Errors.UserError, "This option is not settable from a SConscript file: %s"%name + + if name == 'num_jobs': + try: + value = int(value) + if value < 1: + raise ValueError + except ValueError: + raise SCons.Errors.UserError, "A positive integer is required: %s"%repr(value) + elif name == 'max_drift': + try: + value = int(value) + except ValueError: + raise SCons.Errors.UserError, "An integer is required: %s"%repr(value) + elif name == 'duplicate': + try: + value = str(value) + except ValueError: + raise SCons.Errors.UserError, "A string is required: %s"%repr(value) + if not value in SCons.Node.FS.Valid_Duplicates: + raise SCons.Errors.UserError, "Not a valid duplication style: %s" % value + # Set the duplicate style right away so it can affect linking + # of SConscript files. + SCons.Node.FS.set_duplicate(value) + elif name == 'diskcheck': + try: + value = diskcheck_convert(value) + except ValueError, v: + raise SCons.Errors.UserError, "Not a valid diskcheck value: %s"%v + if not self.__dict__.has_key('diskcheck'): + # No --diskcheck= option was specified on the command line. + # Set this right away so it can affect the rest of the + # file/Node lookups while processing the SConscript files. + SCons.Node.FS.set_diskcheck(value) + elif name == 'stack_size': + try: + value = int(value) + except ValueError: + raise SCons.Errors.UserError, "An integer is required: %s"%repr(value) + elif name == 'md5_chunksize': + try: + value = int(value) + except ValueError: + raise SCons.Errors.UserError, "An integer is required: %s"%repr(value) + elif name == 'warn': + if SCons.Util.is_String(value): + value = [value] + value = self.__SConscript_settings__.get(name, []) + value + SCons.Warnings.process_warn_strings(value) + + self.__SConscript_settings__[name] = value + +class SConsOption(optparse.Option): + def convert_value(self, opt, value): + if value is not None: + if self.nargs in (1, '?'): + return self.check_value(opt, value) + else: + return tuple(map(lambda v, o=opt, s=self: s.check_value(o, v), value)) + + def process(self, opt, value, values, parser): + + # First, convert the value(s) to the right type. Howl if any + # value(s) are bogus. + value = self.convert_value(opt, value) + + # And then take whatever action is expected of us. + # This is a separate method to make life easier for + # subclasses to add new actions. + return self.take_action( + self.action, self.dest, opt, value, values, parser) + + def _check_nargs_optional(self): + if self.nargs == '?' and self._short_opts: + fmt = "option %s: nargs='?' is incompatible with short options" + raise SCons.Errors.UserError, fmt % self._short_opts[0] + + try: + _orig_CONST_ACTIONS = optparse.Option.CONST_ACTIONS + + _orig_CHECK_METHODS = optparse.Option.CHECK_METHODS + + except AttributeError: + # optparse.Option had no CONST_ACTIONS before Python 2.5. + + _orig_CONST_ACTIONS = ("store_const",) + + def _check_const(self): + if self.action not in self.CONST_ACTIONS and self.const is not None: + raise OptionError( + "'const' must not be supplied for action %r" % self.action, + self) + + # optparse.Option collects its list of unbound check functions + # up front. This sucks because it means we can't just override + # the _check_const() function like a normal method, we have to + # actually replace it in the list. This seems to be the most + # straightforward way to do that. + + _orig_CHECK_METHODS = [optparse.Option._check_action, + optparse.Option._check_type, + optparse.Option._check_choice, + optparse.Option._check_dest, + _check_const, + optparse.Option._check_nargs, + optparse.Option._check_callback] + + CHECK_METHODS = _orig_CHECK_METHODS + [_check_nargs_optional] + + CONST_ACTIONS = _orig_CONST_ACTIONS + optparse.Option.TYPED_ACTIONS + +class SConsOptionGroup(optparse.OptionGroup): + """ + A subclass for SCons-specific option groups. + + The only difference between this and the base class is that we print + the group's help text flush left, underneath their own title but + lined up with the normal "SCons Options". + """ + def format_help(self, formatter): + """ + Format an option group's help text, outdenting the title so it's + flush with the "SCons Options" title we print at the top. + """ + formatter.dedent() + result = formatter.format_heading(self.title) + formatter.indent() + result = result + optparse.OptionContainer.format_help(self, formatter) + return result + +class SConsOptionParser(optparse.OptionParser): + preserve_unknown_options = False + + def error(self, msg): + self.print_usage(sys.stderr) + sys.stderr.write("SCons error: %s\n" % msg) + sys.exit(2) + + def _process_long_opt(self, rargs, values): + """ + SCons-specific processing of long options. + + This is copied directly from the normal + optparse._process_long_opt() method, except that, if configured + to do so, we catch the exception thrown when an unknown option + is encountered and just stick it back on the "leftover" arguments + for later (re-)processing. + """ + arg = rargs.pop(0) + + # Value explicitly attached to arg? Pretend it's the next + # argument. + if "=" in arg: + (opt, next_arg) = string.split(arg, "=", 1) + rargs.insert(0, next_arg) + had_explicit_value = True + else: + opt = arg + had_explicit_value = False + + try: + opt = self._match_long_opt(opt) + except optparse.BadOptionError: + if self.preserve_unknown_options: + # SCons-specific: if requested, add unknown options to + # the "leftover arguments" list for later processing. + self.largs.append(arg) + if had_explicit_value: + # The unknown option will be re-processed later, + # so undo the insertion of the explicit value. + rargs.pop(0) + return + raise + + option = self._long_opt[opt] + if option.takes_value(): + nargs = option.nargs + if nargs == '?': + if had_explicit_value: + value = rargs.pop(0) + else: + value = option.const + elif len(rargs) < nargs: + if nargs == 1: + self.error(_("%s option requires an argument") % opt) + else: + self.error(_("%s option requires %d arguments") + % (opt, nargs)) + elif nargs == 1: + value = rargs.pop(0) + else: + value = tuple(rargs[0:nargs]) + del rargs[0:nargs] + + elif had_explicit_value: + self.error(_("%s option does not take a value") % opt) + + else: + value = None + + option.process(opt, value, values, self) + + def add_local_option(self, *args, **kw): + """ + Adds a local option to the parser. + + This is initiated by a SetOption() call to add a user-defined + command-line option. We add the option to a separate option + group for the local options, creating the group if necessary. + """ + try: + group = self.local_option_group + except AttributeError: + group = SConsOptionGroup(self, 'Local Options') + group = self.add_option_group(group) + self.local_option_group = group + + result = apply(group.add_option, args, kw) + + if result: + # The option was added succesfully. We now have to add the + # default value to our object that holds the default values + # (so that an attempt to fetch the option's attribute will + # yield the default value when not overridden) and then + # we re-parse the leftover command-line options, so that + # any value overridden on the command line is immediately + # available if the user turns around and does a GetOption() + # right away. + setattr(self.values.__defaults__, result.dest, result.default) + self.parse_args(self.largs, self.values) + + return result + +class SConsIndentedHelpFormatter(optparse.IndentedHelpFormatter): + def format_usage(self, usage): + return "usage: %s\n" % usage + + def format_heading(self, heading): + """ + This translates any heading of "options" or "Options" into + "SCons Options." Unfortunately, we have to do this here, + because those titles are hard-coded in the optparse calls. + """ + if heading == 'options': + # The versions of optparse.py shipped with Pythons 2.3 and + # 2.4 pass this in uncapitalized; override that so we get + # consistent output on all versions. + heading = "Options" + if heading == 'Options': + heading = "SCons Options" + return optparse.IndentedHelpFormatter.format_heading(self, heading) + + def format_option(self, option): + """ + A copy of the normal optparse.IndentedHelpFormatter.format_option() + method. This has been snarfed so we can modify text wrapping to + out liking: + + -- add our own regular expression that doesn't break on hyphens + (so things like --no-print-directory don't get broken); + + -- wrap the list of options themselves when it's too long + (the wrapper.fill(opts) call below); + + -- set the subsequent_indent when wrapping the help_text. + """ + # The help for each option consists of two parts: + # * the opt strings and metavars + # eg. ("-x", or "-fFILENAME, --file=FILENAME") + # * the user-supplied help string + # eg. ("turn on expert mode", "read data from FILENAME") + # + # If possible, we write both of these on the same line: + # -x turn on expert mode + # + # But if the opt string list is too long, we put the help + # string on a second line, indented to the same column it would + # start in if it fit on the first line. + # -fFILENAME, --file=FILENAME + # read data from FILENAME + result = [] + + try: + opts = self.option_strings[option] + except AttributeError: + # The Python 2.3 version of optparse attaches this to + # to the option argument, not to this object. + opts = option.option_strings + + opt_width = self.help_position - self.current_indent - 2 + if len(opts) > opt_width: + wrapper = textwrap.TextWrapper(width=self.width, + initial_indent = ' ', + subsequent_indent = ' ') + wrapper.wordsep_re = no_hyphen_re + opts = wrapper.fill(opts) + '\n' + indent_first = self.help_position + else: # start help on same line as opts + opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) + indent_first = 0 + result.append(opts) + if option.help: + + try: + expand_default = self.expand_default + except AttributeError: + # The HelpFormatter base class in the Python 2.3 version + # of optparse has no expand_default() method. + help_text = option.help + else: + help_text = expand_default(option) + + # SCons: indent every line of the help text but the first. + wrapper = textwrap.TextWrapper(width=self.help_width, + subsequent_indent = ' ') + wrapper.wordsep_re = no_hyphen_re + help_lines = wrapper.wrap(help_text) + result.append("%*s%s\n" % (indent_first, "", help_lines[0])) + for line in help_lines[1:]: + result.append("%*s%s\n" % (self.help_position, "", line)) + elif opts[-1] != "\n": + result.append("\n") + return string.join(result, "") + + # For consistent help output across Python versions, we provide a + # subclass copy of format_option_strings() and these two variables. + # This is necessary (?) for Python2.3, which otherwise concatenates + # a short option with its metavar. + _short_opt_fmt = "%s %s" + _long_opt_fmt = "%s=%s" + + def format_option_strings(self, option): + """Return a comma-separated list of option strings & metavariables.""" + if option.takes_value(): + metavar = option.metavar or string.upper(option.dest) + short_opts = [] + for sopt in option._short_opts: + short_opts.append(self._short_opt_fmt % (sopt, metavar)) + long_opts = [] + for lopt in option._long_opts: + long_opts.append(self._long_opt_fmt % (lopt, metavar)) + else: + short_opts = option._short_opts + long_opts = option._long_opts + + if self.short_first: + opts = short_opts + long_opts + else: + opts = long_opts + short_opts + + return string.join(opts, ", ") + +def Parser(version): + """ + Returns an options parser object initialized with the standard + SCons options. + """ + + formatter = SConsIndentedHelpFormatter(max_help_position=30) + + op = SConsOptionParser(option_class=SConsOption, + add_help_option=False, + formatter=formatter, + usage="usage: scons [OPTION] [TARGET] ...",) + + op.preserve_unknown_options = True + op.version = version + + # Add the options to the parser we just created. + # + # These are in the order we want them to show up in the -H help + # text, basically alphabetical. Each op.add_option() call below + # should have a consistent format: + # + # op.add_option("-L", "--long-option-name", + # nargs=1, type="string", + # dest="long_option_name", default='foo', + # action="callback", callback=opt_long_option, + # help="help text goes here", + # metavar="VAR") + # + # Even though the optparse module constructs reasonable default + # destination names from the long option names, we're going to be + # explicit about each one for easier readability and so this code + # will at least show up when grepping the source for option attribute + # names, or otherwise browsing the source code. + + # options ignored for compatibility + def opt_ignore(option, opt, value, parser): + sys.stderr.write("Warning: ignoring %s option\n" % opt) + op.add_option("-b", "-d", "-e", "-m", "-S", "-t", "-w", + "--environment-overrides", + "--no-keep-going", + "--no-print-directory", + "--print-directory", + "--stop", + "--touch", + action="callback", callback=opt_ignore, + help="Ignored for compatibility.") + + op.add_option('-c', '--clean', '--remove', + dest="clean", default=False, + action="store_true", + help="Remove specified targets and dependencies.") + + op.add_option('-C', '--directory', + nargs=1, type="string", + dest="directory", default=[], + action="append", + help="Change to DIR before doing anything.", + metavar="DIR") + + op.add_option('--cache-debug', + nargs=1, + dest="cache_debug", default=None, + action="store", + help="Print CacheDir debug info to FILE.", + metavar="FILE") + + op.add_option('--cache-disable', '--no-cache', + dest='cache_disable', default=False, + action="store_true", + help="Do not retrieve built targets from CacheDir.") + + op.add_option('--cache-force', '--cache-populate', + dest='cache_force', default=False, + action="store_true", + help="Copy already-built targets into the CacheDir.") + + op.add_option('--cache-show', + dest='cache_show', default=False, + action="store_true", + help="Print build actions for files from CacheDir.") + + config_options = ["auto", "force" ,"cache"] + + def opt_config(option, opt, value, parser, c_options=config_options): + if not value in c_options: + raise OptionValueError("Warning: %s is not a valid config type" % value) + setattr(parser.values, option.dest, value) + opt_config_help = "Controls Configure subsystem: %s." \ + % string.join(config_options, ", ") + op.add_option('--config', + nargs=1, type="string", + dest="config", default="auto", + action="callback", callback=opt_config, + help = opt_config_help, + metavar="MODE") + + op.add_option('-D', + dest="climb_up", default=None, + action="store_const", const=2, + help="Search up directory tree for SConstruct, " + "build all Default() targets.") + + deprecated_debug_options = { + "dtree" : '; please use --tree=derived instead', + "nomemoizer" : ' and has no effect', + "stree" : '; please use --tree=all,status instead', + "tree" : '; please use --tree=all instead', + } + + debug_options = ["count", "explain", "findlibs", + "includes", "memoizer", "memory", "objects", + "pdb", "presub", "stacktrace", + "time"] + deprecated_debug_options.keys() + + def opt_debug(option, opt, value, parser, + debug_options=debug_options, + deprecated_debug_options=deprecated_debug_options): + if value in debug_options: + parser.values.debug.append(value) + if value in deprecated_debug_options.keys(): + try: + parser.values.delayed_warnings + except AttributeError: + parser.values.delayed_warnings = [] + msg = deprecated_debug_options[value] + w = "The --debug=%s option is deprecated%s." % (value, msg) + t = (SCons.Warnings.DeprecatedWarning, w) + parser.values.delayed_warnings.append(t) + else: + raise OptionValueError("Warning: %s is not a valid debug type" % value) + opt_debug_help = "Print various types of debugging information: %s." \ + % string.join(debug_options, ", ") + op.add_option('--debug', + nargs=1, type="string", + dest="debug", default=[], + action="callback", callback=opt_debug, + help=opt_debug_help, + metavar="TYPE") + + def opt_diskcheck(option, opt, value, parser): + try: + diskcheck_value = diskcheck_convert(value) + except ValueError, e: + raise OptionValueError("Warning: `%s' is not a valid diskcheck type" % e) + setattr(parser.values, option.dest, diskcheck_value) + + op.add_option('--diskcheck', + nargs=1, type="string", + dest='diskcheck', default=None, + action="callback", callback=opt_diskcheck, + help="Enable specific on-disk checks.", + metavar="TYPE") + + def opt_duplicate(option, opt, value, parser): + if not value in SCons.Node.FS.Valid_Duplicates: + raise OptionValueError("`%s' is not a valid duplication style." % value) + setattr(parser.values, option.dest, value) + # Set the duplicate style right away so it can affect linking + # of SConscript files. + SCons.Node.FS.set_duplicate(value) + + opt_duplicate_help = "Set the preferred duplication methods. Must be one of " \ + + string.join(SCons.Node.FS.Valid_Duplicates, ", ") + + op.add_option('--duplicate', + nargs=1, type="string", + dest="duplicate", default='hard-soft-copy', + action="callback", callback=opt_duplicate, + help=opt_duplicate_help) + + op.add_option('-f', '--file', '--makefile', '--sconstruct', + nargs=1, type="string", + dest="file", default=[], + action="append", + help="Read FILE as the top-level SConstruct file.") + + op.add_option('-h', '--help', + dest="help", default=False, + action="store_true", + help="Print defined help message, or this one.") + + op.add_option("-H", "--help-options", + action="help", + help="Print this message and exit.") + + op.add_option('-i', '--ignore-errors', + dest='ignore_errors', default=False, + action="store_true", + help="Ignore errors from build actions.") + + op.add_option('-I', '--include-dir', + nargs=1, + dest='include_dir', default=[], + action="append", + help="Search DIR for imported Python modules.", + metavar="DIR") + + op.add_option('--implicit-cache', + dest='implicit_cache', default=False, + action="store_true", + help="Cache implicit dependencies") + + def opt_implicit_deps(option, opt, value, parser): + setattr(parser.values, 'implicit_cache', True) + setattr(parser.values, option.dest, True) + + op.add_option('--implicit-deps-changed', + dest="implicit_deps_changed", default=False, + action="callback", callback=opt_implicit_deps, + help="Ignore cached implicit dependencies.") + + op.add_option('--implicit-deps-unchanged', + dest="implicit_deps_unchanged", default=False, + action="callback", callback=opt_implicit_deps, + help="Ignore changes in implicit dependencies.") + + op.add_option('--interact', '--interactive', + dest='interactive', default=False, + action="store_true", + help="Run in interactive mode.") + + op.add_option('-j', '--jobs', + nargs=1, type="int", + dest="num_jobs", default=1, + action="store", + help="Allow N jobs at once.", + metavar="N") + + op.add_option('-k', '--keep-going', + dest='keep_going', default=False, + action="store_true", + help="Keep going when a target can't be made.") + + op.add_option('--max-drift', + nargs=1, type="int", + dest='max_drift', default=SCons.Node.FS.default_max_drift, + action="store", + help="Set maximum system clock drift to N seconds.", + metavar="N") + + op.add_option('--md5-chunksize', + nargs=1, type="int", + dest='md5_chunksize', default=SCons.Node.FS.File.md5_chunksize, + action="store", + help="Set chunk-size for MD5 signature computation to N kilobytes.", + metavar="N") + + op.add_option('-n', '--no-exec', '--just-print', '--dry-run', '--recon', + dest='no_exec', default=False, + action="store_true", + help="Don't build; just print commands.") + + op.add_option('--no-site-dir', + dest='no_site_dir', default=False, + action="store_true", + help="Don't search or use the usual site_scons dir.") + + op.add_option('--profile', + nargs=1, + dest="profile_file", default=None, + action="store", + help="Profile SCons and put results in FILE.", + metavar="FILE") + + op.add_option('-q', '--question', + dest="question", default=False, + action="store_true", + help="Don't build; exit status says if up to date.") + + op.add_option('-Q', + dest='no_progress', default=False, + action="store_true", + help="Suppress \"Reading/Building\" progress messages.") + + op.add_option('--random', + dest="random", default=False, + action="store_true", + help="Build dependencies in random order.") + + op.add_option('-s', '--silent', '--quiet', + dest="silent", default=False, + action="store_true", + help="Don't print commands.") + + op.add_option('--site-dir', + nargs=1, + dest='site_dir', default=None, + action="store", + help="Use DIR instead of the usual site_scons dir.", + metavar="DIR") + + op.add_option('--stack-size', + nargs=1, type="int", + dest='stack_size', + action="store", + help="Set the stack size of the threads used to run jobs to N kilobytes.", + metavar="N") + + op.add_option('--taskmastertrace', + nargs=1, + dest="taskmastertrace_file", default=None, + action="store", + help="Trace Node evaluation to FILE.", + metavar="FILE") + + tree_options = ["all", "derived", "prune", "status"] + + def opt_tree(option, opt, value, parser, tree_options=tree_options): + import Main + tp = Main.TreePrinter() + for o in string.split(value, ','): + if o == 'all': + tp.derived = False + elif o == 'derived': + tp.derived = True + elif o == 'prune': + tp.prune = True + elif o == 'status': + tp.status = True + else: + raise OptionValueError("Warning: %s is not a valid --tree option" % o) + parser.values.tree_printers.append(tp) + + opt_tree_help = "Print a dependency tree in various formats: %s." \ + % string.join(tree_options, ", ") + + op.add_option('--tree', + nargs=1, type="string", + dest="tree_printers", default=[], + action="callback", callback=opt_tree, + help=opt_tree_help, + metavar="OPTIONS") + + op.add_option('-u', '--up', '--search-up', + dest="climb_up", default=0, + action="store_const", const=1, + help="Search up directory tree for SConstruct, " + "build targets at or below current directory.") + + op.add_option('-U', + dest="climb_up", default=0, + action="store_const", const=3, + help="Search up directory tree for SConstruct, " + "build Default() targets from local SConscript.") + + def opt_version(option, opt, value, parser): + sys.stdout.write(parser.version + '\n') + sys.exit(0) + op.add_option("-v", "--version", + action="callback", callback=opt_version, + help="Print the SCons version number and exit.") + + def opt_warn(option, opt, value, parser, tree_options=tree_options): + if SCons.Util.is_String(value): + value = string.split(value, ',') + parser.values.warn.extend(value) + + op.add_option('--warn', '--warning', + nargs=1, type="string", + dest="warn", default=[], + action="callback", callback=opt_warn, + help="Enable or disable warnings.", + metavar="WARNING-SPEC") + + op.add_option('-Y', '--repository', '--srcdir', + nargs=1, + dest="repository", default=[], + action="append", + help="Search REPOSITORY for source and target files.") + + # Options from Make and Cons classic that we do not yet support, + # but which we may support someday and whose (potential) meanings + # we don't want to change. These all get a "the -X option is not + # yet implemented" message and don't show up in the help output. + + def opt_not_yet(option, opt, value, parser): + msg = "Warning: the %s option is not yet implemented\n" % opt + sys.stderr.write(msg) + + op.add_option('-l', '--load-average', '--max-load', + nargs=1, type="int", + dest="load_average", default=0, + action="callback", callback=opt_not_yet, + # action="store", + # help="Don't start multiple jobs unless load is below " + # "LOAD-AVERAGE." + help=SUPPRESS_HELP) + op.add_option('--list-actions', + dest="list_actions", + action="callback", callback=opt_not_yet, + # help="Don't build; list files and build actions." + help=SUPPRESS_HELP) + op.add_option('--list-derived', + dest="list_derived", + action="callback", callback=opt_not_yet, + # help="Don't build; list files that would be built." + help=SUPPRESS_HELP) + op.add_option('--list-where', + dest="list_where", + action="callback", callback=opt_not_yet, + # help="Don't build; list files and where defined." + help=SUPPRESS_HELP) + op.add_option('-o', '--old-file', '--assume-old', + nargs=1, type="string", + dest="old_file", default=[], + action="callback", callback=opt_not_yet, + # action="append", + # help = "Consider FILE to be old; don't rebuild it." + help=SUPPRESS_HELP) + op.add_option('--override', + nargs=1, type="string", + action="callback", callback=opt_not_yet, + dest="override", + # help="Override variables as specified in FILE." + help=SUPPRESS_HELP) + op.add_option('-p', + action="callback", callback=opt_not_yet, + dest="p", + # help="Print internal environments/objects." + help=SUPPRESS_HELP) + op.add_option('-r', '-R', '--no-builtin-rules', '--no-builtin-variables', + action="callback", callback=opt_not_yet, + dest="no_builtin_rules", + # help="Clear default environments and variables." + help=SUPPRESS_HELP) + op.add_option('--write-filenames', + nargs=1, type="string", + dest="write_filenames", + action="callback", callback=opt_not_yet, + # help="Write all filenames examined into FILE." + help=SUPPRESS_HELP) + op.add_option('-W', '--new-file', '--assume-new', '--what-if', + nargs=1, type="string", + dest="new_file", + action="callback", callback=opt_not_yet, + # help="Consider FILE to be changed." + help=SUPPRESS_HELP) + op.add_option('--warn-undefined-variables', + dest="warn_undefined_variables", + action="callback", callback=opt_not_yet, + # help="Warn when an undefined variable is referenced." + help=SUPPRESS_HELP) + + return op + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/SConscript.py b/src/engine/SCons/Script/SConscript.py new file mode 100644 index 0000000..31c10ad --- /dev/null +++ b/src/engine/SCons/Script/SConscript.py @@ -0,0 +1,642 @@ +"""SCons.Script.SConscript + +This module defines the Python API provided to SConscript and SConstruct +files. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/SConscript.py 4577 2009/12/27 19:44:43 scons" + +import SCons +import SCons.Action +import SCons.Builder +import SCons.Defaults +import SCons.Environment +import SCons.Errors +import SCons.Node +import SCons.Node.Alias +import SCons.Node.FS +import SCons.Platform +import SCons.SConf +import SCons.Script.Main +import SCons.Tool +import SCons.Util + +import os +import os.path +import re +import string +import sys +import traceback +import types +import UserList + +# The following variables used to live in this module. Some +# SConscript files out there may have referred to them directly as +# SCons.Script.SConscript.*. This is now supported by some special +# handling towards the bottom of the SConscript.__init__.py module. +#Arguments = {} +#ArgList = [] +#BuildTargets = TargetList() +#CommandLineTargets = [] +#DefaultTargets = [] + +class SConscriptReturn(Exception): + pass + +launch_dir = os.path.abspath(os.curdir) + +GlobalDict = None + +# global exports set by Export(): +global_exports = {} + +# chdir flag +sconscript_chdir = 1 + +def get_calling_namespaces(): + """Return the locals and globals for the function that called + into this module in the current call stack.""" + try: 1/0 + except ZeroDivisionError: + # Don't start iterating with the current stack-frame to + # prevent creating reference cycles (f_back is safe). + frame = sys.exc_info()[2].tb_frame.f_back + + # Find the first frame that *isn't* from this file. This means + # that we expect all of the SCons frames that implement an Export() + # or SConscript() call to be in this file, so that we can identify + # the first non-Script.SConscript frame as the user's local calling + # environment, and the locals and globals dictionaries from that + # frame as the calling namespaces. See the comment below preceding + # the DefaultEnvironmentCall block for even more explanation. + while frame.f_globals.get("__name__") == __name__: + frame = frame.f_back + + return frame.f_locals, frame.f_globals + + +def compute_exports(exports): + """Compute a dictionary of exports given one of the parameters + to the Export() function or the exports argument to SConscript().""" + + loc, glob = get_calling_namespaces() + + retval = {} + try: + for export in exports: + if SCons.Util.is_Dict(export): + retval.update(export) + else: + try: + retval[export] = loc[export] + except KeyError: + retval[export] = glob[export] + except KeyError, x: + raise SCons.Errors.UserError, "Export of non-existent variable '%s'"%x + + return retval + +class Frame: + """A frame on the SConstruct/SConscript call stack""" + def __init__(self, fs, exports, sconscript): + self.globals = BuildDefaultGlobals() + self.retval = None + self.prev_dir = fs.getcwd() + self.exports = compute_exports(exports) # exports from the calling SConscript + # make sure the sconscript attr is a Node. + if isinstance(sconscript, SCons.Node.Node): + self.sconscript = sconscript + elif sconscript == '-': + self.sconscript = None + else: + self.sconscript = fs.File(str(sconscript)) + +# the SConstruct/SConscript call stack: +call_stack = [] + +# For documentation on the methods in this file, see the scons man-page + +def Return(*vars, **kw): + retval = [] + try: + fvars = SCons.Util.flatten(vars) + for var in fvars: + for v in string.split(var): + retval.append(call_stack[-1].globals[v]) + except KeyError, x: + raise SCons.Errors.UserError, "Return of non-existent variable '%s'"%x + + if len(retval) == 1: + call_stack[-1].retval = retval[0] + else: + call_stack[-1].retval = tuple(retval) + + stop = kw.get('stop', True) + + if stop: + raise SConscriptReturn + + +stack_bottom = '% Stack boTTom %' # hard to define a variable w/this name :) + +def _SConscript(fs, *files, **kw): + top = fs.Top + sd = fs.SConstruct_dir.rdir() + exports = kw.get('exports', []) + + # evaluate each SConscript file + results = [] + for fn in files: + call_stack.append(Frame(fs, exports, fn)) + old_sys_path = sys.path + try: + SCons.Script.sconscript_reading = SCons.Script.sconscript_reading + 1 + if fn == "-": + exec sys.stdin in call_stack[-1].globals + else: + if isinstance(fn, SCons.Node.Node): + f = fn + else: + f = fs.File(str(fn)) + _file_ = None + + # Change directory to the top of the source + # tree to make sure the os's cwd and the cwd of + # fs match so we can open the SConscript. + fs.chdir(top, change_os_dir=1) + if f.rexists(): + actual = f.rfile() + _file_ = open(actual.get_abspath(), "r") + elif f.srcnode().rexists(): + actual = f.srcnode().rfile() + _file_ = open(actual.get_abspath(), "r") + elif f.has_src_builder(): + # The SConscript file apparently exists in a source + # code management system. Build it, but then clear + # the builder so that it doesn't get built *again* + # during the actual build phase. + f.build() + f.built() + f.builder_set(None) + if f.exists(): + _file_ = open(f.get_abspath(), "r") + if _file_: + # Chdir to the SConscript directory. Use a path + # name relative to the SConstruct file so that if + # we're using the -f option, we're essentially + # creating a parallel SConscript directory structure + # in our local directory tree. + # + # XXX This is broken for multiple-repository cases + # where the SConstruct and SConscript files might be + # in different Repositories. For now, cross that + # bridge when someone comes to it. + try: + src_dir = kw['src_dir'] + except KeyError: + ldir = fs.Dir(f.dir.get_path(sd)) + else: + ldir = fs.Dir(src_dir) + if not ldir.is_under(f.dir): + # They specified a source directory, but + # it's above the SConscript directory. + # Do the sensible thing and just use the + # SConcript directory. + ldir = fs.Dir(f.dir.get_path(sd)) + try: + fs.chdir(ldir, change_os_dir=sconscript_chdir) + except OSError: + # There was no local directory, so we should be + # able to chdir to the Repository directory. + # Note that we do this directly, not through + # fs.chdir(), because we still need to + # interpret the stuff within the SConscript file + # relative to where we are logically. + fs.chdir(ldir, change_os_dir=0) + os.chdir(actual.dir.get_abspath()) + + # Append the SConscript directory to the beginning + # of sys.path so Python modules in the SConscript + # directory can be easily imported. + sys.path = [ f.dir.get_abspath() ] + sys.path + + # This is the magic line that actually reads up + # and executes the stuff in the SConscript file. + # The locals for this frame contain the special + # bottom-of-the-stack marker so that any + # exceptions that occur when processing this + # SConscript can base the printed frames at this + # level and not show SCons internals as well. + call_stack[-1].globals.update({stack_bottom:1}) + old_file = call_stack[-1].globals.get('__file__') + try: + del call_stack[-1].globals['__file__'] + except KeyError: + pass + try: + try: + exec _file_ in call_stack[-1].globals + except SConscriptReturn: + pass + finally: + if old_file is not None: + call_stack[-1].globals.update({__file__:old_file}) + else: + SCons.Warnings.warn(SCons.Warnings.MissingSConscriptWarning, + "Ignoring missing SConscript '%s'" % f.path) + + finally: + SCons.Script.sconscript_reading = SCons.Script.sconscript_reading - 1 + sys.path = old_sys_path + frame = call_stack.pop() + try: + fs.chdir(frame.prev_dir, change_os_dir=sconscript_chdir) + except OSError: + # There was no local directory, so chdir to the + # Repository directory. Like above, we do this + # directly. + fs.chdir(frame.prev_dir, change_os_dir=0) + rdir = frame.prev_dir.rdir() + rdir._create() # Make sure there's a directory there. + try: + os.chdir(rdir.get_abspath()) + except OSError, e: + # We still couldn't chdir there, so raise the error, + # but only if actions are being executed. + # + # If the -n option was used, the directory would *not* + # have been created and we should just carry on and + # let things muddle through. This isn't guaranteed + # to work if the SConscript files are reading things + # from disk (for example), but it should work well + # enough for most configurations. + if SCons.Action.execute_actions: + raise e + + results.append(frame.retval) + + # if we only have one script, don't return a tuple + if len(results) == 1: + return results[0] + else: + return tuple(results) + +def SConscript_exception(file=sys.stderr): + """Print an exception stack trace just for the SConscript file(s). + This will show users who have Python errors where the problem is, + without cluttering the output with all of the internal calls leading + up to where we exec the SConscript.""" + exc_type, exc_value, exc_tb = sys.exc_info() + tb = exc_tb + while tb and not tb.tb_frame.f_locals.has_key(stack_bottom): + tb = tb.tb_next + if not tb: + # We did not find our exec statement, so this was actually a bug + # in SCons itself. Show the whole stack. + tb = exc_tb + stack = traceback.extract_tb(tb) + try: + type = exc_type.__name__ + except AttributeError: + type = str(exc_type) + if type[:11] == "exceptions.": + type = type[11:] + file.write('%s: %s:\n' % (type, exc_value)) + for fname, line, func, text in stack: + file.write(' File "%s", line %d:\n' % (fname, line)) + file.write(' %s\n' % text) + +def annotate(node): + """Annotate a node with the stack frame describing the + SConscript file and line number that created it.""" + tb = sys.exc_info()[2] + while tb and not tb.tb_frame.f_locals.has_key(stack_bottom): + tb = tb.tb_next + if not tb: + # We did not find any exec of an SConscript file: what?! + raise SCons.Errors.InternalError, "could not find SConscript stack frame" + node.creator = traceback.extract_stack(tb)[0] + +# The following line would cause each Node to be annotated using the +# above function. Unfortunately, this is a *huge* performance hit, so +# leave this disabled until we find a more efficient mechanism. +#SCons.Node.Annotate = annotate + +class SConsEnvironment(SCons.Environment.Base): + """An Environment subclass that contains all of the methods that + are particular to the wrapper SCons interface and which aren't + (or shouldn't be) part of the build engine itself. + + Note that not all of the methods of this class have corresponding + global functions, there are some private methods. + """ + + # + # Private methods of an SConsEnvironment. + # + def _exceeds_version(self, major, minor, v_major, v_minor): + """Return 1 if 'major' and 'minor' are greater than the version + in 'v_major' and 'v_minor', and 0 otherwise.""" + return (major > v_major or (major == v_major and minor > v_minor)) + + def _get_major_minor_revision(self, version_string): + """Split a version string into major, minor and (optionally) + revision parts. + + This is complicated by the fact that a version string can be + something like 3.2b1.""" + version = string.split(string.split(version_string, ' ')[0], '.') + v_major = int(version[0]) + v_minor = int(re.match('\d+', version[1]).group()) + if len(version) >= 3: + v_revision = int(re.match('\d+', version[2]).group()) + else: + v_revision = 0 + return v_major, v_minor, v_revision + + def _get_SConscript_filenames(self, ls, kw): + """ + Convert the parameters passed to # SConscript() calls into a list + of files and export variables. If the parameters are invalid, + throws SCons.Errors.UserError. Returns a tuple (l, e) where l + is a list of SConscript filenames and e is a list of exports. + """ + exports = [] + + if len(ls) == 0: + try: + dirs = kw["dirs"] + except KeyError: + raise SCons.Errors.UserError, \ + "Invalid SConscript usage - no parameters" + + if not SCons.Util.is_List(dirs): + dirs = [ dirs ] + dirs = map(str, dirs) + + name = kw.get('name', 'SConscript') + + files = map(lambda n, name = name: os.path.join(n, name), dirs) + + elif len(ls) == 1: + + files = ls[0] + + elif len(ls) == 2: + + files = ls[0] + exports = self.Split(ls[1]) + + else: + + raise SCons.Errors.UserError, \ + "Invalid SConscript() usage - too many arguments" + + if not SCons.Util.is_List(files): + files = [ files ] + + if kw.get('exports'): + exports.extend(self.Split(kw['exports'])) + + variant_dir = kw.get('variant_dir') or kw.get('build_dir') + if variant_dir: + if len(files) != 1: + raise SCons.Errors.UserError, \ + "Invalid SConscript() usage - can only specify one SConscript with a variant_dir" + duplicate = kw.get('duplicate', 1) + src_dir = kw.get('src_dir') + if not src_dir: + src_dir, fname = os.path.split(str(files[0])) + files = [os.path.join(str(variant_dir), fname)] + else: + if not isinstance(src_dir, SCons.Node.Node): + src_dir = self.fs.Dir(src_dir) + fn = files[0] + if not isinstance(fn, SCons.Node.Node): + fn = self.fs.File(fn) + if fn.is_under(src_dir): + # Get path relative to the source directory. + fname = fn.get_path(src_dir) + files = [os.path.join(str(variant_dir), fname)] + else: + files = [fn.abspath] + kw['src_dir'] = variant_dir + self.fs.VariantDir(variant_dir, src_dir, duplicate) + + return (files, exports) + + # + # Public methods of an SConsEnvironment. These get + # entry points in the global name space so they can be called + # as global functions. + # + + def Configure(self, *args, **kw): + if not SCons.Script.sconscript_reading: + raise SCons.Errors.UserError, "Calling Configure from Builders is not supported." + kw['_depth'] = kw.get('_depth', 0) + 1 + return apply(SCons.Environment.Base.Configure, (self,)+args, kw) + + def Default(self, *targets): + SCons.Script._Set_Default_Targets(self, targets) + + def EnsureSConsVersion(self, major, minor, revision=0): + """Exit abnormally if the SCons version is not late enough.""" + scons_ver = self._get_major_minor_revision(SCons.__version__) + if scons_ver < (major, minor, revision): + if revision: + scons_ver_string = '%d.%d.%d' % (major, minor, revision) + else: + scons_ver_string = '%d.%d' % (major, minor) + print "SCons %s or greater required, but you have SCons %s" % \ + (scons_ver_string, SCons.__version__) + sys.exit(2) + + def EnsurePythonVersion(self, major, minor): + """Exit abnormally if the Python version is not late enough.""" + try: + v_major, v_minor, v_micro, release, serial = sys.version_info + python_ver = (v_major, v_minor) + except AttributeError: + python_ver = self._get_major_minor_revision(sys.version)[:2] + if python_ver < (major, minor): + v = string.split(sys.version, " ", 1)[0] + print "Python %d.%d or greater required, but you have Python %s" %(major,minor,v) + sys.exit(2) + + def Exit(self, value=0): + sys.exit(value) + + def Export(self, *vars, **kw): + for var in vars: + global_exports.update(compute_exports(self.Split(var))) + global_exports.update(kw) + + def GetLaunchDir(self): + global launch_dir + return launch_dir + + def GetOption(self, name): + name = self.subst(name) + return SCons.Script.Main.GetOption(name) + + def Help(self, text): + text = self.subst(text, raw=1) + SCons.Script.HelpFunction(text) + + def Import(self, *vars): + try: + frame = call_stack[-1] + globals = frame.globals + exports = frame.exports + for var in vars: + var = self.Split(var) + for v in var: + if v == '*': + globals.update(global_exports) + globals.update(exports) + else: + if exports.has_key(v): + globals[v] = exports[v] + else: + globals[v] = global_exports[v] + except KeyError,x: + raise SCons.Errors.UserError, "Import of non-existent variable '%s'"%x + + def SConscript(self, *ls, **kw): + def subst_element(x, subst=self.subst): + if SCons.Util.is_List(x): + x = map(subst, x) + else: + x = subst(x) + return x + ls = map(subst_element, ls) + subst_kw = {} + for key, val in kw.items(): + if SCons.Util.is_String(val): + val = self.subst(val) + elif SCons.Util.is_List(val): + result = [] + for v in val: + if SCons.Util.is_String(v): + v = self.subst(v) + result.append(v) + val = result + subst_kw[key] = val + + files, exports = self._get_SConscript_filenames(ls, subst_kw) + subst_kw['exports'] = exports + return apply(_SConscript, [self.fs,] + files, subst_kw) + + def SConscriptChdir(self, flag): + global sconscript_chdir + sconscript_chdir = flag + + def SetOption(self, name, value): + name = self.subst(name) + SCons.Script.Main.SetOption(name, value) + +# +# +# +SCons.Environment.Environment = SConsEnvironment + +def Configure(*args, **kw): + if not SCons.Script.sconscript_reading: + raise SCons.Errors.UserError, "Calling Configure from Builders is not supported." + kw['_depth'] = 1 + return apply(SCons.SConf.SConf, args, kw) + +# It's very important that the DefaultEnvironmentCall() class stay in this +# file, with the get_calling_namespaces() function, the compute_exports() +# function, the Frame class and the SConsEnvironment.Export() method. +# These things make up the calling stack leading up to the actual global +# Export() or SConscript() call that the user issued. We want to allow +# users to export local variables that they define, like so: +# +# def func(): +# x = 1 +# Export('x') +# +# To support this, the get_calling_namespaces() function assumes that +# the *first* stack frame that's not from this file is the local frame +# for the Export() or SConscript() call. + +_DefaultEnvironmentProxy = None + +def get_DefaultEnvironmentProxy(): + global _DefaultEnvironmentProxy + if not _DefaultEnvironmentProxy: + default_env = SCons.Defaults.DefaultEnvironment() + _DefaultEnvironmentProxy = SCons.Environment.NoSubstitutionProxy(default_env) + return _DefaultEnvironmentProxy + +class DefaultEnvironmentCall: + """A class that implements "global function" calls of + Environment methods by fetching the specified method from the + DefaultEnvironment's class. Note that this uses an intermediate + proxy class instead of calling the DefaultEnvironment method + directly so that the proxy can override the subst() method and + thereby prevent expansion of construction variables (since from + the user's point of view this was called as a global function, + with no associated construction environment).""" + def __init__(self, method_name, subst=0): + self.method_name = method_name + if subst: + self.factory = SCons.Defaults.DefaultEnvironment + else: + self.factory = get_DefaultEnvironmentProxy + def __call__(self, *args, **kw): + env = self.factory() + method = getattr(env, self.method_name) + return apply(method, args, kw) + + +def BuildDefaultGlobals(): + """ + Create a dictionary containing all the default globals for + SConstruct and SConscript files. + """ + + global GlobalDict + if GlobalDict is None: + GlobalDict = {} + + import SCons.Script + d = SCons.Script.__dict__ + def not_a_module(m, d=d, mtype=type(SCons.Script)): + return type(d[m]) != mtype + for m in filter(not_a_module, dir(SCons.Script)): + GlobalDict[m] = d[m] + + return GlobalDict.copy() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/SConscriptTests.py b/src/engine/SCons/Script/SConscriptTests.py new file mode 100644 index 0000000..0958dc9 --- /dev/null +++ b/src/engine/SCons/Script/SConscriptTests.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/SConscriptTests.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Script.SConscript + +# all of the SConscript.py tests are in test/SConscript.py + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Script/__init__.py b/src/engine/SCons/Script/__init__.py new file mode 100644 index 0000000..1e535ab --- /dev/null +++ b/src/engine/SCons/Script/__init__.py @@ -0,0 +1,414 @@ +"""SCons.Script + +This file implements the main() function used by the scons script. + +Architecturally, this *is* the scons script, and will likely only be +called from the external "scons" wrapper. Consequently, anything here +should not be, or be considered, part of the build engine. If it's +something that we expect other software to want to use, it should go in +some other module. If it's specific to the "scons" script invocation, +it goes here. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Script/__init__.py 4577 2009/12/27 19:44:43 scons" + +import time +start_time = time.time() + +import os +import string +import sys +import UserList + +# Special chicken-and-egg handling of the "--debug=memoizer" flag: +# +# SCons.Memoize contains a metaclass implementation that affects how +# the other classes are instantiated. The Memoizer may add shim methods +# to classes that have methods that cache computed values in order to +# count and report the hits and misses. +# +# If we wait to enable the Memoization until after we've parsed the +# command line options normally, it will be too late, because the Memoizer +# will have already analyzed the classes that it's Memoizing and decided +# to not add the shims. So we use a special-case, up-front check for +# the "--debug=memoizer" flag and enable Memoizer before we import any +# of the other modules that use it. + +_args = sys.argv + string.split(os.environ.get('SCONSFLAGS', '')) +if "--debug=memoizer" in _args: + import SCons.Memoize + import SCons.Warnings + try: + SCons.Memoize.EnableMemoization() + except SCons.Warnings.Warning: + # Some warning was thrown (inability to --debug=memoizer on + # Python 1.5.2 because it doesn't have metaclasses). Arrange + # for it to be displayed or not after warnings are configured. + import Main + exc_type, exc_value, tb = sys.exc_info() + Main.delayed_warnings.append((exc_type, exc_value)) +del _args + +import SCons.Action +import SCons.Builder +import SCons.Environment +import SCons.Node.FS +import SCons.Options +import SCons.Platform +import SCons.Scanner +import SCons.SConf +import SCons.Subst +import SCons.Tool +import SCons.Util +import SCons.Variables +import SCons.Defaults + +import Main + +main = Main.main + +# The following are global class definitions and variables that used to +# live directly in this module back before 0.96.90, when it contained +# a lot of code. Some SConscript files in widely-distributed packages +# (Blender is the specific example) actually reached into SCons.Script +# directly to use some of these. Rather than break those SConscript +# files, we're going to propagate these names into the SCons.Script +# namespace here. +# +# Some of these are commented out because it's *really* unlikely anyone +# used them, but we're going to leave the comment here to try to make +# it obvious what to do if the situation arises. +BuildTask = Main.BuildTask +CleanTask = Main.CleanTask +QuestionTask = Main.QuestionTask +#PrintHelp = Main.PrintHelp +#SConscriptSettableOptions = Main.SConscriptSettableOptions + +AddOption = Main.AddOption +GetOption = Main.GetOption +SetOption = Main.SetOption +Progress = Main.Progress +GetBuildFailures = Main.GetBuildFailures + +#keep_going_on_error = Main.keep_going_on_error +#print_dtree = Main.print_dtree +#print_explanations = Main.print_explanations +#print_includes = Main.print_includes +#print_objects = Main.print_objects +#print_time = Main.print_time +#print_tree = Main.print_tree +#memory_stats = Main.memory_stats +#ignore_errors = Main.ignore_errors +#sconscript_time = Main.sconscript_time +#command_time = Main.command_time +#exit_status = Main.exit_status +#profiling = Main.profiling +#repositories = Main.repositories + +# +import SConscript +_SConscript = SConscript + +call_stack = _SConscript.call_stack + +# +Action = SCons.Action.Action +AddMethod = SCons.Util.AddMethod +AllowSubstExceptions = SCons.Subst.SetAllowableExceptions +Builder = SCons.Builder.Builder +Configure = _SConscript.Configure +Environment = SCons.Environment.Environment +#OptParser = SCons.SConsOptions.OptParser +FindPathDirs = SCons.Scanner.FindPathDirs +Platform = SCons.Platform.Platform +Return = _SConscript.Return +Scanner = SCons.Scanner.Base +Tool = SCons.Tool.Tool +WhereIs = SCons.Util.WhereIs + +# +BoolVariable = SCons.Variables.BoolVariable +EnumVariable = SCons.Variables.EnumVariable +ListVariable = SCons.Variables.ListVariable +PackageVariable = SCons.Variables.PackageVariable +PathVariable = SCons.Variables.PathVariable + +# Deprecated names that will go away some day. +BoolOption = SCons.Options.BoolOption +EnumOption = SCons.Options.EnumOption +ListOption = SCons.Options.ListOption +PackageOption = SCons.Options.PackageOption +PathOption = SCons.Options.PathOption + +# Action factories. +Chmod = SCons.Defaults.Chmod +Copy = SCons.Defaults.Copy +Delete = SCons.Defaults.Delete +Mkdir = SCons.Defaults.Mkdir +Move = SCons.Defaults.Move +Touch = SCons.Defaults.Touch + +# Pre-made, public scanners. +CScanner = SCons.Tool.CScanner +DScanner = SCons.Tool.DScanner +DirScanner = SCons.Defaults.DirScanner +ProgramScanner = SCons.Tool.ProgramScanner +SourceFileScanner = SCons.Tool.SourceFileScanner + +# Functions we might still convert to Environment methods. +CScan = SCons.Defaults.CScan +DefaultEnvironment = SCons.Defaults.DefaultEnvironment + +# Other variables we provide. +class TargetList(UserList.UserList): + def _do_nothing(self, *args, **kw): + pass + def _add_Default(self, list): + self.extend(list) + def _clear(self): + del self[:] + +ARGUMENTS = {} +ARGLIST = [] +BUILD_TARGETS = TargetList() +COMMAND_LINE_TARGETS = [] +DEFAULT_TARGETS = [] + +# BUILD_TARGETS can be modified in the SConscript files. If so, we +# want to treat the modified BUILD_TARGETS list as if they specified +# targets on the command line. To do that, though, we need to know if +# BUILD_TARGETS was modified through "official" APIs or by hand. We do +# this by updating two lists in parallel, the documented BUILD_TARGETS +# list, above, and this internal _build_plus_default targets list which +# should only have "official" API changes. Then Script/Main.py can +# compare these two afterwards to figure out if the user added their +# own targets to BUILD_TARGETS. +_build_plus_default = TargetList() + +def _Add_Arguments(alist): + for arg in alist: + a, b = string.split(arg, '=', 1) + ARGUMENTS[a] = b + ARGLIST.append((a, b)) + +def _Add_Targets(tlist): + if tlist: + COMMAND_LINE_TARGETS.extend(tlist) + BUILD_TARGETS.extend(tlist) + BUILD_TARGETS._add_Default = BUILD_TARGETS._do_nothing + BUILD_TARGETS._clear = BUILD_TARGETS._do_nothing + _build_plus_default.extend(tlist) + _build_plus_default._add_Default = _build_plus_default._do_nothing + _build_plus_default._clear = _build_plus_default._do_nothing + +def _Set_Default_Targets_Has_Been_Called(d, fs): + return DEFAULT_TARGETS + +def _Set_Default_Targets_Has_Not_Been_Called(d, fs): + if d is None: + d = [fs.Dir('.')] + return d + +_Get_Default_Targets = _Set_Default_Targets_Has_Not_Been_Called + +def _Set_Default_Targets(env, tlist): + global DEFAULT_TARGETS + global _Get_Default_Targets + _Get_Default_Targets = _Set_Default_Targets_Has_Been_Called + for t in tlist: + if t is None: + # Delete the elements from the list in-place, don't + # reassign an empty list to DEFAULT_TARGETS, so that the + # variables will still point to the same object we point to. + del DEFAULT_TARGETS[:] + BUILD_TARGETS._clear() + _build_plus_default._clear() + elif isinstance(t, SCons.Node.Node): + DEFAULT_TARGETS.append(t) + BUILD_TARGETS._add_Default([t]) + _build_plus_default._add_Default([t]) + else: + nodes = env.arg2nodes(t, env.fs.Entry) + DEFAULT_TARGETS.extend(nodes) + BUILD_TARGETS._add_Default(nodes) + _build_plus_default._add_Default(nodes) + +# +help_text = None + +def HelpFunction(text): + global help_text + if SCons.Script.help_text is None: + SCons.Script.help_text = text + else: + help_text = help_text + text + +# +# Will be non-zero if we are reading an SConscript file. +sconscript_reading = 0 + +# +def Variables(files=[], args=ARGUMENTS): + return SCons.Variables.Variables(files, args) + +def Options(files=[], args=ARGUMENTS): + return SCons.Options.Options(files, args) + +# The list of global functions to add to the SConscript name space +# that end up calling corresponding methods or Builders in the +# DefaultEnvironment(). +GlobalDefaultEnvironmentFunctions = [ + # Methods from the SConsEnvironment class, above. + 'Default', + 'EnsurePythonVersion', + 'EnsureSConsVersion', + 'Exit', + 'Export', + 'GetLaunchDir', + 'Help', + 'Import', + #'SConscript', is handled separately, below. + 'SConscriptChdir', + + # Methods from the Environment.Base class. + 'AddPostAction', + 'AddPreAction', + 'Alias', + 'AlwaysBuild', + 'BuildDir', + 'CacheDir', + 'Clean', + #The Command() method is handled separately, below. + 'Decider', + 'Depends', + 'Dir', + 'NoClean', + 'NoCache', + 'Entry', + 'Execute', + 'File', + 'FindFile', + 'FindInstalledFiles', + 'FindSourceFiles', + 'Flatten', + 'GetBuildPath', + 'Glob', + 'Ignore', + 'Install', + 'InstallAs', + 'Literal', + 'Local', + 'ParseDepends', + 'Precious', + 'Repository', + 'Requires', + 'SConsignFile', + 'SideEffect', + 'SourceCode', + 'SourceSignatures', + 'Split', + 'Tag', + 'TargetSignatures', + 'Value', + 'VariantDir', +] + +GlobalDefaultBuilders = [ + # Supported builders. + 'CFile', + 'CXXFile', + 'DVI', + 'Jar', + 'Java', + 'JavaH', + 'Library', + 'M4', + 'MSVSProject', + 'Object', + 'PCH', + 'PDF', + 'PostScript', + 'Program', + 'RES', + 'RMIC', + 'SharedLibrary', + 'SharedObject', + 'StaticLibrary', + 'StaticObject', + 'Tar', + 'TypeLibrary', + 'Zip', + 'Package', +] + +for name in GlobalDefaultEnvironmentFunctions + GlobalDefaultBuilders: + exec "%s = _SConscript.DefaultEnvironmentCall(%s)" % (name, repr(name)) +del name + +# There are a handful of variables that used to live in the +# Script/SConscript.py module that some SConscript files out there were +# accessing directly as SCons.Script.SConscript.*. The problem is that +# "SConscript" in this namespace is no longer a module, it's a global +# function call--or more precisely, an object that implements a global +# function call through the default Environment. Nevertheless, we can +# maintain backwards compatibility for SConscripts that were reaching in +# this way by hanging some attributes off the "SConscript" object here. +SConscript = _SConscript.DefaultEnvironmentCall('SConscript') + +# Make SConscript look enough like the module it used to be so +# that pychecker doesn't barf. +SConscript.__name__ = 'SConscript' + +SConscript.Arguments = ARGUMENTS +SConscript.ArgList = ARGLIST +SConscript.BuildTargets = BUILD_TARGETS +SConscript.CommandLineTargets = COMMAND_LINE_TARGETS +SConscript.DefaultTargets = DEFAULT_TARGETS + +# The global Command() function must be handled differently than the +# global functions for other construction environment methods because +# we want people to be able to use Actions that must expand $TARGET +# and $SOURCE later, when (and if) the Action is invoked to build +# the target(s). We do this with the subst=1 argument, which creates +# a DefaultEnvironmentCall instance that wraps up a normal default +# construction environment that performs variable substitution, not a +# proxy that doesn't. +# +# There's a flaw here, though, because any other $-variables on a command +# line will *also* be expanded, each to a null string, but that should +# only be a problem in the unusual case where someone was passing a '$' +# on a command line and *expected* the $ to get through to the shell +# because they were calling Command() and not env.Command()... This is +# unlikely enough that we're going to leave this as is and cross that +# bridge if someone actually comes to it. +Command = _SConscript.DefaultEnvironmentCall('Command', subst=1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Sig.py b/src/engine/SCons/Sig.py new file mode 100644 index 0000000..f42b473 --- /dev/null +++ b/src/engine/SCons/Sig.py @@ -0,0 +1,63 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Sig.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Place-holder for the old SCons.Sig module hierarchy + +This is no longer used, but code out there (such as the NSIS module on +the SCons wiki) may try to import SCons.Sig. If so, we generate a warning +that points them to the line that caused the import, and don't die. + +If someone actually tried to use the sub-modules or functions within +the package (for example, SCons.Sig.MD5.signature()), then they'll still +get an AttributeError, but at least they'll know where to start looking. +""" + +import SCons.Util +import SCons.Warnings + +msg = 'The SCons.Sig module no longer exists.\n' \ + ' Remove the following "import SCons.Sig" line to eliminate this warning:' + +SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, msg) + +default_calc = None +default_module = None + +class MD5Null(SCons.Util.Null): + def __repr__(self): + return "MD5Null()" + +class TimeStampNull(SCons.Util.Null): + def __repr__(self): + return "TimeStampNull()" + +MD5 = MD5Null() +TimeStamp = TimeStampNull() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Subst.py b/src/engine/SCons/Subst.py new file mode 100644 index 0000000..fe644a5 --- /dev/null +++ b/src/engine/SCons/Subst.py @@ -0,0 +1,911 @@ +"""SCons.Subst + +SCons string substitution. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Subst.py 4577 2009/12/27 19:44:43 scons" + +import re +import string +import types +import UserList +import UserString + +import SCons.Errors + +from SCons.Util import is_String, is_Sequence + +# Indexed by the SUBST_* constants below. +_strconv = [SCons.Util.to_String_for_subst, + SCons.Util.to_String_for_subst, + SCons.Util.to_String_for_signature] + + + +AllowableExceptions = (IndexError, NameError) + +def SetAllowableExceptions(*excepts): + global AllowableExceptions + AllowableExceptions = filter(None, excepts) + +def raise_exception(exception, target, s): + name = exception.__class__.__name__ + msg = "%s `%s' trying to evaluate `%s'" % (name, exception, s) + if target: + raise SCons.Errors.BuildError, (target[0], msg) + else: + raise SCons.Errors.UserError, msg + + + +class Literal: + """A wrapper for a string. If you use this object wrapped + around a string, then it will be interpreted as literal. + When passed to the command interpreter, all special + characters will be escaped.""" + def __init__(self, lstr): + self.lstr = lstr + + def __str__(self): + return self.lstr + + def escape(self, escape_func): + return escape_func(self.lstr) + + def for_signature(self): + return self.lstr + + def is_literal(self): + return 1 + +class SpecialAttrWrapper: + """This is a wrapper for what we call a 'Node special attribute.' + This is any of the attributes of a Node that we can reference from + Environment variable substitution, such as $TARGET.abspath or + $SOURCES[1].filebase. We implement the same methods as Literal + so we can handle special characters, plus a for_signature method, + such that we can return some canonical string during signature + calculation to avoid unnecessary rebuilds.""" + + def __init__(self, lstr, for_signature=None): + """The for_signature parameter, if supplied, will be the + canonical string we return from for_signature(). Else + we will simply return lstr.""" + self.lstr = lstr + if for_signature: + self.forsig = for_signature + else: + self.forsig = lstr + + def __str__(self): + return self.lstr + + def escape(self, escape_func): + return escape_func(self.lstr) + + def for_signature(self): + return self.forsig + + def is_literal(self): + return 1 + +def quote_spaces(arg): + """Generic function for putting double quotes around any string that + has white space in it.""" + if ' ' in arg or '\t' in arg: + return '"%s"' % arg + else: + return str(arg) + +class CmdStringHolder(UserString.UserString): + """This is a special class used to hold strings generated by + scons_subst() and scons_subst_list(). It defines a special method + escape(). When passed a function with an escape algorithm for a + particular platform, it will return the contained string with the + proper escape sequences inserted. + """ + def __init__(self, cmd, literal=None): + UserString.UserString.__init__(self, cmd) + self.literal = literal + + def is_literal(self): + return self.literal + + def escape(self, escape_func, quote_func=quote_spaces): + """Escape the string with the supplied function. The + function is expected to take an arbitrary string, then + return it with all special characters escaped and ready + for passing to the command interpreter. + + After calling this function, the next call to str() will + return the escaped string. + """ + + if self.is_literal(): + return escape_func(self.data) + elif ' ' in self.data or '\t' in self.data: + return quote_func(self.data) + else: + return self.data + +def escape_list(list, escape_func): + """Escape a list of arguments by running the specified escape_func + on every object in the list that has an escape() method.""" + def escape(obj, escape_func=escape_func): + try: + e = obj.escape + except AttributeError: + return obj + else: + return e(escape_func) + return map(escape, list) + +class NLWrapper: + """A wrapper class that delays turning a list of sources or targets + into a NodeList until it's needed. The specified function supplied + when the object is initialized is responsible for turning raw nodes + into proxies that implement the special attributes like .abspath, + .source, etc. This way, we avoid creating those proxies just + "in case" someone is going to use $TARGET or the like, and only + go through the trouble if we really have to. + + In practice, this might be a wash performance-wise, but it's a little + cleaner conceptually... + """ + + def __init__(self, list, func): + self.list = list + self.func = func + def _return_nodelist(self): + return self.nodelist + def _gen_nodelist(self): + list = self.list + if list is None: + list = [] + elif not is_Sequence(list): + list = [list] + # The map(self.func) call is what actually turns + # a list into appropriate proxies. + self.nodelist = SCons.Util.NodeList(map(self.func, list)) + self._create_nodelist = self._return_nodelist + return self.nodelist + _create_nodelist = _gen_nodelist + + +class Targets_or_Sources(UserList.UserList): + """A class that implements $TARGETS or $SOURCES expansions by in turn + wrapping a NLWrapper. This class handles the different methods used + to access the list, calling the NLWrapper to create proxies on demand. + + Note that we subclass UserList.UserList purely so that the + is_Sequence() function will identify an object of this class as + a list during variable expansion. We're not really using any + UserList.UserList methods in practice. + """ + def __init__(self, nl): + self.nl = nl + def __getattr__(self, attr): + nl = self.nl._create_nodelist() + return getattr(nl, attr) + def __getitem__(self, i): + nl = self.nl._create_nodelist() + return nl[i] + def __getslice__(self, i, j): + nl = self.nl._create_nodelist() + i = max(i, 0); j = max(j, 0) + return nl[i:j] + def __str__(self): + nl = self.nl._create_nodelist() + return str(nl) + def __repr__(self): + nl = self.nl._create_nodelist() + return repr(nl) + +class Target_or_Source: + """A class that implements $TARGET or $SOURCE expansions by in turn + wrapping a NLWrapper. This class handles the different methods used + to access an individual proxy Node, calling the NLWrapper to create + a proxy on demand. + """ + def __init__(self, nl): + self.nl = nl + def __getattr__(self, attr): + nl = self.nl._create_nodelist() + try: + nl0 = nl[0] + except IndexError: + # If there is nothing in the list, then we have no attributes to + # pass through, so raise AttributeError for everything. + raise AttributeError, "NodeList has no attribute: %s" % attr + return getattr(nl0, attr) + def __str__(self): + nl = self.nl._create_nodelist() + if nl: + return str(nl[0]) + return '' + def __repr__(self): + nl = self.nl._create_nodelist() + if nl: + return repr(nl[0]) + return '' + +class NullNodeList(SCons.Util.NullSeq): + def __call__(self, *args, **kwargs): return '' + def __str__(self): return '' + # TODO(1.5): unneeded after new-style classes introduce iterators + def __getitem__(self, i): + raise IndexError + +NullNodesList = NullNodeList() + +def subst_dict(target, source): + """Create a dictionary for substitution of special + construction variables. + + This translates the following special arguments: + + target - the target (object or array of objects), + used to generate the TARGET and TARGETS + construction variables + + source - the source (object or array of objects), + used to generate the SOURCES and SOURCE + construction variables + """ + dict = {} + + if target: + def get_tgt_subst_proxy(thing): + try: + subst_proxy = thing.get_subst_proxy() + except AttributeError: + subst_proxy = thing # probably a string, just return it + return subst_proxy + tnl = NLWrapper(target, get_tgt_subst_proxy) + dict['TARGETS'] = Targets_or_Sources(tnl) + dict['TARGET'] = Target_or_Source(tnl) + + # This is a total cheat, but hopefully this dictionary goes + # away soon anyway. We just let these expand to $TARGETS + # because that's "good enough" for the use of ToolSurrogates + # (see test/ToolSurrogate.py) to generate documentation. + dict['CHANGED_TARGETS'] = '$TARGETS' + dict['UNCHANGED_TARGETS'] = '$TARGETS' + else: + dict['TARGETS'] = NullNodesList + dict['TARGET'] = NullNodesList + + if source: + def get_src_subst_proxy(node): + try: + rfile = node.rfile + except AttributeError: + pass + else: + node = rfile() + try: + return node.get_subst_proxy() + except AttributeError: + return node # probably a String, just return it + snl = NLWrapper(source, get_src_subst_proxy) + dict['SOURCES'] = Targets_or_Sources(snl) + dict['SOURCE'] = Target_or_Source(snl) + + # This is a total cheat, but hopefully this dictionary goes + # away soon anyway. We just let these expand to $TARGETS + # because that's "good enough" for the use of ToolSurrogates + # (see test/ToolSurrogate.py) to generate documentation. + dict['CHANGED_SOURCES'] = '$SOURCES' + dict['UNCHANGED_SOURCES'] = '$SOURCES' + else: + dict['SOURCES'] = NullNodesList + dict['SOURCE'] = NullNodesList + + return dict + +# Constants for the "mode" parameter to scons_subst_list() and +# scons_subst(). SUBST_RAW gives the raw command line. SUBST_CMD +# gives a command line suitable for passing to a shell. SUBST_SIG +# gives a command line appropriate for calculating the signature +# of a command line...if this changes, we should rebuild. +SUBST_CMD = 0 +SUBST_RAW = 1 +SUBST_SIG = 2 + +_rm = re.compile(r'\$[()]') +_remove = re.compile(r'\$\([^\$]*(\$[^\)][^\$]*)*\$\)') + +# Indexed by the SUBST_* constants above. +_regex_remove = [ _rm, None, _remove ] + +def _rm_list(list): + #return [ l for l in list if not l in ('$(', '$)') ] + return filter(lambda l: not l in ('$(', '$)'), list) + +def _remove_list(list): + result = [] + do_append = result.append + for l in list: + if l == '$(': + do_append = lambda x: None + elif l == '$)': + do_append = result.append + else: + do_append(l) + return result + +# Indexed by the SUBST_* constants above. +_list_remove = [ _rm_list, None, _remove_list ] + +# Regular expressions for splitting strings and handling substitutions, +# for use by the scons_subst() and scons_subst_list() functions: +# +# The first expression compiled matches all of the $-introduced tokens +# that we need to process in some way, and is used for substitutions. +# The expressions it matches are: +# +# "$$" +# "$(" +# "$)" +# "$variable" [must begin with alphabetic or underscore] +# "${any stuff}" +# +# The second expression compiled is used for splitting strings into tokens +# to be processed, and it matches all of the tokens listed above, plus +# the following that affect how arguments do or don't get joined together: +# +# " " [white space] +# "non-white-space" [without any dollar signs] +# "$" [single dollar sign] +# +_dollar_exps_str = r'\$[\$\(\)]|\$[_a-zA-Z][\.\w]*|\${[^}]*}' +_dollar_exps = re.compile(r'(%s)' % _dollar_exps_str) +_separate_args = re.compile(r'(%s|\s+|[^\s\$]+|\$)' % _dollar_exps_str) + +# This regular expression is used to replace strings of multiple white +# space characters in the string result from the scons_subst() function. +_space_sep = re.compile(r'[\t ]+(?![^{]*})') + +def scons_subst(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): + """Expand a string or list containing construction variable + substitutions. + + This is the work-horse function for substitutions in file names + and the like. The companion scons_subst_list() function (below) + handles separating command lines into lists of arguments, so see + that function if that's what you're looking for. + """ + if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0: + return strSubst + + class StringSubber: + """A class to construct the results of a scons_subst() call. + + This binds a specific construction environment, mode, target and + source with two methods (substitute() and expand()) that handle + the expansion. + """ + def __init__(self, env, mode, conv, gvars): + self.env = env + self.mode = mode + self.conv = conv + self.gvars = gvars + + def expand(self, s, lvars): + """Expand a single "token" as necessary, returning an + appropriate string containing the expansion. + + This handles expanding different types of things (strings, + lists, callables) appropriately. It calls the wrapper + substitute() method to re-expand things as necessary, so that + the results of expansions of side-by-side strings still get + re-evaluated separately, not smushed together. + """ + if is_String(s): + try: + s0, s1 = s[:2] + except (IndexError, ValueError): + return s + if s0 != '$': + return s + if s1 == '$': + return '$' + elif s1 in '()': + return s + else: + key = s[1:] + if key[0] == '{' or string.find(key, '.') >= 0: + if key[0] == '{': + key = key[1:-1] + try: + s = eval(key, self.gvars, lvars) + except KeyboardInterrupt: + raise + except Exception, e: + if e.__class__ in AllowableExceptions: + return '' + raise_exception(e, lvars['TARGETS'], s) + else: + if lvars.has_key(key): + s = lvars[key] + elif self.gvars.has_key(key): + s = self.gvars[key] + elif not NameError in AllowableExceptions: + raise_exception(NameError(key), lvars['TARGETS'], s) + else: + return '' + + # Before re-expanding the result, handle + # recursive expansion by copying the local + # variable dictionary and overwriting a null + # string for the value of the variable name + # we just expanded. + # + # This could potentially be optimized by only + # copying lvars when s contains more expansions, + # but lvars is usually supposed to be pretty + # small, and deeply nested variable expansions + # are probably more the exception than the norm, + # so it should be tolerable for now. + lv = lvars.copy() + var = string.split(key, '.')[0] + lv[var] = '' + return self.substitute(s, lv) + elif is_Sequence(s): + def func(l, conv=self.conv, substitute=self.substitute, lvars=lvars): + return conv(substitute(l, lvars)) + return map(func, s) + elif callable(s): + try: + s = s(target=lvars['TARGETS'], + source=lvars['SOURCES'], + env=self.env, + for_signature=(self.mode != SUBST_CMD)) + except TypeError: + # This probably indicates that it's a callable + # object that doesn't match our calling arguments + # (like an Action). + if self.mode == SUBST_RAW: + return s + s = self.conv(s) + return self.substitute(s, lvars) + elif s is None: + return '' + else: + return s + + def substitute(self, args, lvars): + """Substitute expansions in an argument or list of arguments. + + This serves as a wrapper for splitting up a string into + separate tokens. + """ + if is_String(args) and not isinstance(args, CmdStringHolder): + args = str(args) # In case it's a UserString. + try: + def sub_match(match, conv=self.conv, expand=self.expand, lvars=lvars): + return conv(expand(match.group(1), lvars)) + result = _dollar_exps.sub(sub_match, args) + except TypeError: + # If the internal conversion routine doesn't return + # strings (it could be overridden to return Nodes, for + # example), then the 1.5.2 re module will throw this + # exception. Back off to a slower, general-purpose + # algorithm that works for all data types. + args = _separate_args.findall(args) + result = [] + for a in args: + result.append(self.conv(self.expand(a, lvars))) + if len(result) == 1: + result = result[0] + else: + result = string.join(map(str, result), '') + return result + else: + return self.expand(args, lvars) + + if conv is None: + conv = _strconv[mode] + + # Doing this every time is a bit of a waste, since the Executor + # has typically already populated the OverrideEnvironment with + # $TARGET/$SOURCE variables. We're keeping this (for now), though, + # because it supports existing behavior that allows us to call + # an Action directly with an arbitrary target+source pair, which + # we use in Tool/tex.py to handle calling $BIBTEX when necessary. + # If we dropped that behavior (or found another way to cover it), + # we could get rid of this call completely and just rely on the + # Executor setting the variables. + if not lvars.has_key('TARGET'): + d = subst_dict(target, source) + if d: + lvars = lvars.copy() + lvars.update(d) + + # We're (most likely) going to eval() things. If Python doesn't + # find a __builtins__ value in the global dictionary used for eval(), + # it copies the current global values for you. Avoid this by + # setting it explicitly and then deleting, so we don't pollute the + # construction environment Dictionary(ies) that are typically used + # for expansion. + gvars['__builtins__'] = __builtins__ + + ss = StringSubber(env, mode, conv, gvars) + result = ss.substitute(strSubst, lvars) + + try: + del gvars['__builtins__'] + except KeyError: + pass + + if is_String(result): + # Remove $(-$) pairs and any stuff in between, + # if that's appropriate. + remove = _regex_remove[mode] + if remove: + result = remove.sub('', result) + if mode != SUBST_RAW: + # Compress strings of white space characters into + # a single space. + result = string.strip(_space_sep.sub(' ', result)) + elif is_Sequence(result): + remove = _list_remove[mode] + if remove: + result = remove(result) + + return result + +#Subst_List_Strings = {} + +def scons_subst_list(strSubst, env, mode=SUBST_RAW, target=None, source=None, gvars={}, lvars={}, conv=None): + """Substitute construction variables in a string (or list or other + object) and separate the arguments into a command list. + + The companion scons_subst() function (above) handles basic + substitutions within strings, so see that function instead + if that's what you're looking for. + """ +# try: +# Subst_List_Strings[strSubst] = Subst_List_Strings[strSubst] + 1 +# except KeyError: +# Subst_List_Strings[strSubst] = 1 +# import SCons.Debug +# SCons.Debug.caller_trace(1) + class ListSubber(UserList.UserList): + """A class to construct the results of a scons_subst_list() call. + + Like StringSubber, this class binds a specific construction + environment, mode, target and source with two methods + (substitute() and expand()) that handle the expansion. + + In addition, however, this class is used to track the state of + the result(s) we're gathering so we can do the appropriate thing + whenever we have to append another word to the result--start a new + line, start a new word, append to the current word, etc. We do + this by setting the "append" attribute to the right method so + that our wrapper methods only need ever call ListSubber.append(), + and the rest of the object takes care of doing the right thing + internally. + """ + def __init__(self, env, mode, conv, gvars): + UserList.UserList.__init__(self, []) + self.env = env + self.mode = mode + self.conv = conv + self.gvars = gvars + + if self.mode == SUBST_RAW: + self.add_strip = lambda x, s=self: s.append(x) + else: + self.add_strip = lambda x, s=self: None + self.in_strip = None + self.next_line() + + def expand(self, s, lvars, within_list): + """Expand a single "token" as necessary, appending the + expansion to the current result. + + This handles expanding different types of things (strings, + lists, callables) appropriately. It calls the wrapper + substitute() method to re-expand things as necessary, so that + the results of expansions of side-by-side strings still get + re-evaluated separately, not smushed together. + """ + + if is_String(s): + try: + s0, s1 = s[:2] + except (IndexError, ValueError): + self.append(s) + return + if s0 != '$': + self.append(s) + return + if s1 == '$': + self.append('$') + elif s1 == '(': + self.open_strip('$(') + elif s1 == ')': + self.close_strip('$)') + else: + key = s[1:] + if key[0] == '{' or string.find(key, '.') >= 0: + if key[0] == '{': + key = key[1:-1] + try: + s = eval(key, self.gvars, lvars) + except KeyboardInterrupt: + raise + except Exception, e: + if e.__class__ in AllowableExceptions: + return + raise_exception(e, lvars['TARGETS'], s) + else: + if lvars.has_key(key): + s = lvars[key] + elif self.gvars.has_key(key): + s = self.gvars[key] + elif not NameError in AllowableExceptions: + raise_exception(NameError(), lvars['TARGETS'], s) + else: + return + + # Before re-expanding the result, handle + # recursive expansion by copying the local + # variable dictionary and overwriting a null + # string for the value of the variable name + # we just expanded. + lv = lvars.copy() + var = string.split(key, '.')[0] + lv[var] = '' + self.substitute(s, lv, 0) + self.this_word() + elif is_Sequence(s): + for a in s: + self.substitute(a, lvars, 1) + self.next_word() + elif callable(s): + try: + s = s(target=lvars['TARGETS'], + source=lvars['SOURCES'], + env=self.env, + for_signature=(self.mode != SUBST_CMD)) + except TypeError: + # This probably indicates that it's a callable + # object that doesn't match our calling arguments + # (like an Action). + if self.mode == SUBST_RAW: + self.append(s) + return + s = self.conv(s) + self.substitute(s, lvars, within_list) + elif s is None: + self.this_word() + else: + self.append(s) + + def substitute(self, args, lvars, within_list): + """Substitute expansions in an argument or list of arguments. + + This serves as a wrapper for splitting up a string into + separate tokens. + """ + + if is_String(args) and not isinstance(args, CmdStringHolder): + args = str(args) # In case it's a UserString. + args = _separate_args.findall(args) + for a in args: + if a[0] in ' \t\n\r\f\v': + if '\n' in a: + self.next_line() + elif within_list: + self.append(a) + else: + self.next_word() + else: + self.expand(a, lvars, within_list) + else: + self.expand(args, lvars, within_list) + + def next_line(self): + """Arrange for the next word to start a new line. This + is like starting a new word, except that we have to append + another line to the result.""" + UserList.UserList.append(self, []) + self.next_word() + + def this_word(self): + """Arrange for the next word to append to the end of the + current last word in the result.""" + self.append = self.add_to_current_word + + def next_word(self): + """Arrange for the next word to start a new word.""" + self.append = self.add_new_word + + def add_to_current_word(self, x): + """Append the string x to the end of the current last word + in the result. If that is not possible, then just add + it as a new word. Make sure the entire concatenated string + inherits the object attributes of x (in particular, the + escape function) by wrapping it as CmdStringHolder.""" + + if not self.in_strip or self.mode != SUBST_SIG: + try: + current_word = self[-1][-1] + except IndexError: + self.add_new_word(x) + else: + # All right, this is a hack and it should probably + # be refactored out of existence in the future. + # The issue is that we want to smoosh words together + # and make one file name that gets escaped if + # we're expanding something like foo$EXTENSION, + # but we don't want to smoosh them together if + # it's something like >$TARGET, because then we'll + # treat the '>' like it's part of the file name. + # So for now, just hard-code looking for the special + # command-line redirection characters... + try: + last_char = str(current_word)[-1] + except IndexError: + last_char = '\0' + if last_char in '<>|': + self.add_new_word(x) + else: + y = current_word + x + + # We used to treat a word appended to a literal + # as a literal itself, but this caused problems + # with interpreting quotes around space-separated + # targets on command lines. Removing this makes + # none of the "substantive" end-to-end tests fail, + # so we'll take this out but leave it commented + # for now in case there's a problem not covered + # by the test cases and we need to resurrect this. + #literal1 = self.literal(self[-1][-1]) + #literal2 = self.literal(x) + y = self.conv(y) + if is_String(y): + #y = CmdStringHolder(y, literal1 or literal2) + y = CmdStringHolder(y, None) + self[-1][-1] = y + + def add_new_word(self, x): + if not self.in_strip or self.mode != SUBST_SIG: + literal = self.literal(x) + x = self.conv(x) + if is_String(x): + x = CmdStringHolder(x, literal) + self[-1].append(x) + self.append = self.add_to_current_word + + def literal(self, x): + try: + l = x.is_literal + except AttributeError: + return None + else: + return l() + + def open_strip(self, x): + """Handle the "open strip" $( token.""" + self.add_strip(x) + self.in_strip = 1 + + def close_strip(self, x): + """Handle the "close strip" $) token.""" + self.add_strip(x) + self.in_strip = None + + if conv is None: + conv = _strconv[mode] + + # Doing this every time is a bit of a waste, since the Executor + # has typically already populated the OverrideEnvironment with + # $TARGET/$SOURCE variables. We're keeping this (for now), though, + # because it supports existing behavior that allows us to call + # an Action directly with an arbitrary target+source pair, which + # we use in Tool/tex.py to handle calling $BIBTEX when necessary. + # If we dropped that behavior (or found another way to cover it), + # we could get rid of this call completely and just rely on the + # Executor setting the variables. + if not lvars.has_key('TARGET'): + d = subst_dict(target, source) + if d: + lvars = lvars.copy() + lvars.update(d) + + # We're (most likely) going to eval() things. If Python doesn't + # find a __builtins__ value in the global dictionary used for eval(), + # it copies the current global values for you. Avoid this by + # setting it explicitly and then deleting, so we don't pollute the + # construction environment Dictionary(ies) that are typically used + # for expansion. + gvars['__builtins__'] = __builtins__ + + ls = ListSubber(env, mode, conv, gvars) + ls.substitute(strSubst, lvars, 0) + + try: + del gvars['__builtins__'] + except KeyError: + pass + + return ls.data + +def scons_subst_once(strSubst, env, key): + """Perform single (non-recursive) substitution of a single + construction variable keyword. + + This is used when setting a variable when copying or overriding values + in an Environment. We want to capture (expand) the old value before + we override it, so people can do things like: + + env2 = env.Clone(CCFLAGS = '$CCFLAGS -g') + + We do this with some straightforward, brute-force code here... + """ + if type(strSubst) == types.StringType and string.find(strSubst, '$') < 0: + return strSubst + + matchlist = ['$' + key, '${' + key + '}'] + val = env.get(key, '') + def sub_match(match, val=val, matchlist=matchlist): + a = match.group(1) + if a in matchlist: + a = val + if is_Sequence(a): + return string.join(map(str, a)) + else: + return str(a) + + if is_Sequence(strSubst): + result = [] + for arg in strSubst: + if is_String(arg): + if arg in matchlist: + arg = val + if is_Sequence(arg): + result.extend(arg) + else: + result.append(arg) + else: + result.append(_dollar_exps.sub(sub_match, arg)) + else: + result.append(arg) + return result + elif is_String(strSubst): + return _dollar_exps.sub(sub_match, strSubst) + else: + return strSubst + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/SubstTests.py b/src/engine/SCons/SubstTests.py new file mode 100644 index 0000000..5e83eaf --- /dev/null +++ b/src/engine/SCons/SubstTests.py @@ -0,0 +1,1242 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/SubstTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string +import StringIO +import sys +import types +import unittest + +from UserDict import UserDict + +import SCons.Errors + +from SCons.Subst import * + +class DummyNode: + """Simple node work-alike.""" + def __init__(self, name): + self.name = os.path.normpath(name) + def __str__(self): + return self.name + def is_literal(self): + return 1 + def rfile(self): + return self + def get_subst_proxy(self): + return self + +class DummyEnv: + def __init__(self, dict={}): + self.dict = dict + + def Dictionary(self, key = None): + if not key: + return self.dict + return self.dict[key] + + def __getitem__(self, key): + return self.dict[key] + + def get(self, key, default): + return self.dict.get(key, default) + + def sig_dict(self): + dict = self.dict.copy() + dict["TARGETS"] = 'tsig' + dict["SOURCES"] = 'ssig' + return dict + +def cs(target=None, source=None, env=None, for_signature=None): + return 'cs' + +def cl(target=None, source=None, env=None, for_signature=None): + return ['cl'] + +def CmdGen1(target, source, env, for_signature): + # Nifty trick...since Environment references are interpolated, + # instantiate an instance of a callable class with this one, + # which will then get evaluated. + assert str(target) == 't', target + assert str(source) == 's', source + return "${CMDGEN2('foo', %d)}" % for_signature + +class CmdGen2: + def __init__(self, mystr, forsig): + self.mystr = mystr + self.expect_for_signature = forsig + + def __call__(self, target, source, env, for_signature): + assert str(target) == 't', target + assert str(source) == 's', source + assert for_signature == self.expect_for_signature, for_signature + return [ self.mystr, env.Dictionary('BAR') ] + +if os.sep == '/': + def cvt(str): + return str +else: + def cvt(str): + return string.replace(str, '/', os.sep) + +class SubstTestCase(unittest.TestCase): + class MyNode(DummyNode): + """Simple node work-alike with some extra stuff for testing.""" + def __init__(self, name): + DummyNode.__init__(self, name) + class Attribute: + pass + self.attribute = Attribute() + self.attribute.attr1 = 'attr$1-' + os.path.basename(name) + self.attribute.attr2 = 'attr$2-' + os.path.basename(name) + def get_stuff(self, extra): + return self.name + extra + foo = 1 + + class TestLiteral: + def __init__(self, literal): + self.literal = literal + def __str__(self): + return self.literal + def is_literal(self): + return 1 + + class TestCallable: + def __init__(self, value): + self.value = value + def __call__(self): + pass + def __str__(self): + return self.value + + def function_foo(arg): + pass + + target = [ MyNode("./foo/bar.exe"), + MyNode("/bar/baz with spaces.obj"), + MyNode("../foo/baz.obj") ] + source = [ MyNode("./foo/blah with spaces.cpp"), + MyNode("/bar/ack.cpp"), + MyNode("../foo/ack.c") ] + + callable_object_1 = TestCallable('callable-1') + callable_object_2 = TestCallable('callable-2') + + def _defines(defs): + l = [] + for d in defs: + if SCons.Util.is_List(d) or type(d) is types.TupleType: + l.append(str(d[0]) + '=' + str(d[1])) + else: + l.append(str(d)) + return l + + loc = { + 'xxx' : None, + 'NEWLINE' : 'before\nafter', + + 'null' : '', + 'zero' : 0, + 'one' : 1, + 'BAZ' : 'baz', + 'ONE' : '$TWO', + 'TWO' : '$THREE', + 'THREE' : 'four', + + 'AAA' : 'a', + 'BBB' : 'b', + 'CCC' : 'c', + + 'DO' : DummyNode('do something'), + 'FOO' : DummyNode('foo.in'), + 'BAR' : DummyNode('bar with spaces.out'), + 'CRAZY' : DummyNode('crazy\nfile.in'), + + # $XXX$HHH should expand to GGGIII, not BADNEWS. + 'XXX' : '$FFF', + 'FFF' : 'GGG', + 'HHH' : 'III', + 'FFFIII' : 'BADNEWS', + + 'LITERAL' : TestLiteral("$XXX"), + + # Test that we can expand to and return a function. + #'FUNCTION' : function_foo, + + 'CMDGEN1' : CmdGen1, + 'CMDGEN2' : CmdGen2, + + 'LITERALS' : [ Literal('foo\nwith\nnewlines'), + Literal('bar\nwith\nnewlines') ], + + 'NOTHING' : "", + 'NONE' : None, + + # Test various combinations of strings, lists and functions. + 'N' : None, + 'X' : 'x', + 'Y' : '$X', + 'R' : '$R', + 'S' : 'x y', + 'LS' : ['x y'], + 'L' : ['x', 'y'], + 'TS' : ('x y'), + 'T' : ('x', 'y'), + 'CS' : cs, + 'CL' : cl, + 'US' : UserString.UserString('us'), + + # Test function calls within ${}. + 'FUNCCALL' : '${FUNC1("$AAA $FUNC2 $BBB")}', + 'FUNC1' : lambda x: x, + 'FUNC2' : lambda target, source, env, for_signature: ['x$CCC'], + + # Various tests refactored from ActionTests.py. + 'LIST' : [["This", "is", "$(", "$a", "$)", "test"]], + + # Test recursion. + 'RECURSE' : 'foo $RECURSE bar', + 'RRR' : 'foo $SSS bar', + 'SSS' : '$RRR', + + # Test callables that don't match the calling arguments. + 'CALLABLE1' : callable_object_1, + 'CALLABLE2' : callable_object_2, + + '_defines' : _defines, + 'DEFS' : [ ('Q1', '"q1"'), ('Q2', '"$AAA"') ], + } + + def basic_comparisons(self, function, convert): + env = DummyEnv(self.loc) + cases = self.basic_cases[:] + kwargs = {'target' : self.target, 'source' : self.source, + 'gvars' : env.Dictionary()} + + failed = 0 + while cases: + input, expect = cases[:2] + expect = convert(expect) + try: + result = apply(function, (input, env), kwargs) + except Exception, e: + fmt = " input %s generated %s (%s)" + print fmt % (repr(input), e.__class__.__name__, repr(e)) + failed = failed + 1 + else: + if result != expect: + if failed == 0: print + print " input %s => %s did not match %s" % (repr(input), repr(result), repr(expect)) + failed = failed + 1 + del cases[:2] + fmt = "%d %s() cases failed" + assert failed == 0, fmt % (failed, function.__name__) + +class scons_subst_TestCase(SubstTestCase): + + # Basic tests of substitution functionality. + basic_cases = [ + # Basics: strings without expansions are left alone, and + # the simplest possible expansion to a null-string value. + "test", "test", + "$null", "", + + # Test expansion of integer values. + "test $zero", "test 0", + "test $one", "test 1", + + # Test multiple re-expansion of values. + "test $ONE", "test four", + + # Test a whole bunch of $TARGET[S] and $SOURCE[S] expansions. + "test $TARGETS $SOURCES", + "test foo/bar.exe /bar/baz with spaces.obj ../foo/baz.obj foo/blah with spaces.cpp /bar/ack.cpp ../foo/ack.c", + + "test ${TARGETS[:]} ${SOURCES[0]}", + "test foo/bar.exe /bar/baz with spaces.obj ../foo/baz.obj foo/blah with spaces.cpp", + + "test ${TARGETS[1:]}v", + "test /bar/baz with spaces.obj ../foo/baz.objv", + + "test $TARGET", + "test foo/bar.exe", + + "test $TARGET$NO_SUCH_VAR[0]", + "test foo/bar.exe[0]", + + "test $TARGETS.foo", + "test 1 1 1", + + "test ${SOURCES[0:2].foo}", + "test 1 1", + + "test $SOURCE.foo", + "test 1", + + "test ${TARGET.get_stuff('blah')}", + "test foo/bar.exeblah", + + "test ${SOURCES.get_stuff('blah')}", + "test foo/blah with spaces.cppblah /bar/ack.cppblah ../foo/ack.cblah", + + "test ${SOURCES[0:2].get_stuff('blah')}", + "test foo/blah with spaces.cppblah /bar/ack.cppblah", + + "test ${SOURCES[0:2].get_stuff('blah')}", + "test foo/blah with spaces.cppblah /bar/ack.cppblah", + + "test ${SOURCES.attribute.attr1}", + "test attr$1-blah with spaces.cpp attr$1-ack.cpp attr$1-ack.c", + + "test ${SOURCES.attribute.attr2}", + "test attr$2-blah with spaces.cpp attr$2-ack.cpp attr$2-ack.c", + + # Test adjacent expansions. + "foo$BAZ", + "foobaz", + + "foo${BAZ}", + "foobaz", + + # Test that adjacent expansions don't get re-interpreted + # together. The correct disambiguated expansion should be: + # $XXX$HHH => ${FFF}III => GGGIII + # not: + # $XXX$HHH => ${FFFIII} => BADNEWS + "$XXX$HHH", "GGGIII", + + # Test double-dollar-sign behavior. + "$$FFF$HHH", "$FFFIII", + + # Test that a Literal will stop dollar-sign substitution. + "$XXX $LITERAL $FFF", "GGG $XXX GGG", + + # Test that we don't blow up even if they subscript + # something in ways they "can't." + "${FFF[0]}", "G", + "${FFF[7]}", "", + "${NOTHING[1]}", "", + + # Test various combinations of strings and lists. + #None, '', + '', '', + 'x', 'x', + 'x y', 'x y', + '$N', '', + '$X', 'x', + '$Y', 'x', + '$R', '', + '$S', 'x y', + '$LS', 'x y', + '$L', 'x y', + '$TS', 'x y', + '$T', 'x y', + '$S z', 'x y z', + '$LS z', 'x y z', + '$L z', 'x y z', + '$TS z', 'x y z', + '$T z', 'x y z', + #cs, 'cs', + #cl, 'cl', + '$CS', 'cs', + '$CL', 'cl', + + # Various uses of UserString. + UserString.UserString('x'), 'x', + UserString.UserString('$X'), 'x', + UserString.UserString('$US'), 'us', + '$US', 'us', + + # Test function calls within ${}. + '$FUNCCALL', 'a xc b', + + # Bug reported by Christoph Wiedemann. + cvt('$xxx/bin'), '/bin', + + # Tests callables that don't match our calling arguments. + '$CALLABLE1', 'callable-1', + + # Test handling of quotes. + 'aaa "bbb ccc" ddd', 'aaa "bbb ccc" ddd', + ] + + def test_scons_subst(self): + """Test scons_subst(): basic substitution""" + return self.basic_comparisons(scons_subst, cvt) + + subst_cases = [ + "test $xxx", + "test ", + "test", + "test", + + "test $($xxx$)", + "test $($)", + "test", + "test", + + "test $( $xxx $)", + "test $( $)", + "test", + "test", + + "$AAA ${AAA}A $BBBB $BBB", + "a aA b", + "a aA b", + "a aA b", + + "$RECURSE", + "foo bar", + "foo bar", + "foo bar", + + "$RRR", + "foo bar", + "foo bar", + "foo bar", + + # Verify what happens with no target or source nodes. + "$TARGET $SOURCES", + " ", + "", + "", + + "$TARGETS $SOURCE", + " ", + "", + "", + + # Various tests refactored from ActionTests.py. + "${LIST}", + "This is $( $) test", + "This is test", + "This is test", + + ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1], + ["|", "$(", "a", "|", "b", "$)", "|", "c", "1"], + ["|", "a", "|", "b", "|", "c", "1"], + ["|", "|", "c", "1"], + ] + + def test_subst_env(self): + """Test scons_subst(): expansion dictionary""" + # The expansion dictionary no longer comes from the construction + # environment automatically. + env = DummyEnv(self.loc) + s = scons_subst('$AAA', env) + assert s == '', s + + def test_subst_SUBST_modes(self): + """Test scons_subst(): SUBST_* modes""" + env = DummyEnv(self.loc) + subst_cases = self.subst_cases[:] + + gvars = env.Dictionary() + + failed = 0 + while subst_cases: + input, eraw, ecmd, esig = subst_cases[:4] + result = scons_subst(input, env, mode=SUBST_RAW, gvars=gvars) + if result != eraw: + if failed == 0: print + print " input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw)) + failed = failed + 1 + result = scons_subst(input, env, mode=SUBST_CMD, gvars=gvars) + if result != ecmd: + if failed == 0: print + print " input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd)) + failed = failed + 1 + result = scons_subst(input, env, mode=SUBST_SIG, gvars=gvars) + if result != esig: + if failed == 0: print + print " input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig)) + failed = failed + 1 + del subst_cases[:4] + assert failed == 0, "%d subst() mode cases failed" % failed + + def test_subst_target_source(self): + """Test scons_subst(): target= and source= arguments""" + env = DummyEnv(self.loc) + t1 = self.MyNode('t1') + t2 = self.MyNode('t2') + s1 = self.MyNode('s1') + s2 = self.MyNode('s2') + result = scons_subst("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2]) + assert result == "t1 s1 s2", result + result = scons_subst("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2], + gvars={}) + assert result == "t1 s1 s2", result + + result = scons_subst("$TARGET $SOURCES", env, target=[], source=[]) + assert result == " ", result + result = scons_subst("$TARGETS $SOURCE", env, target=[], source=[]) + assert result == " ", result + + def test_subst_callable_expansion(self): + """Test scons_subst(): expanding a callable""" + env = DummyEnv(self.loc) + gvars = env.Dictionary() + newcom = scons_subst("test $CMDGEN1 $SOURCES $TARGETS", env, + target=self.MyNode('t'), source=self.MyNode('s'), + gvars=gvars) + assert newcom == "test foo bar with spaces.out s t", newcom + + def test_subst_attribute_errors(self): + """Test scons_subst(): handling attribute errors""" + env = DummyEnv(self.loc) + try: + class Foo: + pass + scons_subst('${foo.bar}', env, gvars={'foo':Foo()}) + except SCons.Errors.UserError, e: + expect = [ + "AttributeError `bar' trying to evaluate `${foo.bar}'", + "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'", + "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'", + ] + assert str(e) in expect, e + else: + raise AssertionError, "did not catch expected UserError" + + def test_subst_syntax_errors(self): + """Test scons_subst(): handling syntax errors""" + env = DummyEnv(self.loc) + try: + scons_subst('$foo.bar.3.0', env) + except SCons.Errors.UserError, e: + expect = [ + # Python 1.5 + "SyntaxError `invalid syntax' trying to evaluate `$foo.bar.3.0'", + # Python 2.2, 2.3, 2.4 + "SyntaxError `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'", + # Python 2.5 + "SyntaxError `invalid syntax (, line 1)' trying to evaluate `$foo.bar.3.0'", + ] + assert str(e) in expect, e + else: + raise AssertionError, "did not catch expected UserError" + + def test_subst_type_errors(self): + """Test scons_subst(): handling type errors""" + env = DummyEnv(self.loc) + try: + scons_subst("${NONE[2]}", env, gvars={'NONE':None}) + except SCons.Errors.UserError, e: + expect = [ + # Python 1.5, 2.2, 2.3, 2.4 + "TypeError `unsubscriptable object' trying to evaluate `${NONE[2]}'", + # Python 2.5 and later + "TypeError `'NoneType' object is unsubscriptable' trying to evaluate `${NONE[2]}'", + ] + assert str(e) in expect, e + else: + raise AssertionError, "did not catch expected UserError" + + try: + def func(a, b, c): + pass + scons_subst("${func(1)}", env, gvars={'func':func}) + except SCons.Errors.UserError, e: + expect = [ + # Python 1.5 + "TypeError `not enough arguments; expected 3, got 1' trying to evaluate `${func(1)}'", + # Python 2.2, 2.3, 2.4, 2.5 + "TypeError `func() takes exactly 3 arguments (1 given)' trying to evaluate `${func(1)}'" + ] + assert str(e) in expect, repr(str(e)) + else: + raise AssertionError, "did not catch expected UserError" + + def test_subst_raw_function(self): + """Test scons_subst(): fetch function with SUBST_RAW plus conv""" + # Test that the combination of SUBST_RAW plus a pass-through + # conversion routine allows us to fetch a function through the + # dictionary. CommandAction uses this to allow delayed evaluation + # of $SPAWN variables. + env = DummyEnv(self.loc) + gvars = env.Dictionary() + x = lambda x: x + r = scons_subst("$CALLABLE1", env, mode=SUBST_RAW, conv=x, gvars=gvars) + assert r is self.callable_object_1, repr(r) + r = scons_subst("$CALLABLE1", env, mode=SUBST_RAW, gvars=gvars) + assert r == 'callable-1', repr(r) + + # Test how we handle overriding the internal conversion routines. + def s(obj): + return obj + + n1 = self.MyNode('n1') + env = DummyEnv({'NODE' : n1}) + gvars = env.Dictionary() + node = scons_subst("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars) + assert node is n1, node + node = scons_subst("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars) + assert node is n1, node + node = scons_subst("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars) + assert node is n1, node + + #def test_subst_function_return(self): + # """Test scons_subst(): returning a function""" + # env = DummyEnv({'FUNCTION' : foo}) + # gvars = env.Dictionary() + # func = scons_subst("$FUNCTION", env, mode=SUBST_RAW, call=None, gvars=gvars) + # assert func is function_foo, func + # func = scons_subst("$FUNCTION", env, mode=SUBST_CMD, call=None, gvars=gvars) + # assert func is function_foo, func + # func = scons_subst("$FUNCTION", env, mode=SUBST_SIG, call=None, gvars=gvars) + # assert func is function_foo, func + + def test_subst_overriding_gvars(self): + """Test scons_subst(): supplying an overriding gvars dictionary""" + env = DummyEnv({'XXX' : 'xxx'}) + result = scons_subst('$XXX', env, gvars=env.Dictionary()) + assert result == 'xxx', result + result = scons_subst('$XXX', env, gvars={'XXX' : 'yyy'}) + assert result == 'yyy', result + +class CLVar_TestCase(unittest.TestCase): + def test_CLVar(self): + """Test scons_subst() and scons_subst_list() with CLVar objects""" + + loc = {} + loc['FOO'] = 'foo' + loc['BAR'] = SCons.Util.CLVar('bar') + loc['CALL'] = lambda target, source, env, for_signature: 'call' + env = DummyEnv(loc) + + cmd = SCons.Util.CLVar("test $FOO $BAR $CALL test") + + newcmd = scons_subst(cmd, env, gvars=env.Dictionary()) + assert newcmd == ['test', 'foo', 'bar', 'call', 'test'], newcmd + + cmd_list = scons_subst_list(cmd, env, gvars=env.Dictionary()) + assert len(cmd_list) == 1, cmd_list + assert cmd_list[0][0] == "test", cmd_list[0][0] + assert cmd_list[0][1] == "foo", cmd_list[0][1] + assert cmd_list[0][2] == "bar", cmd_list[0][2] + assert cmd_list[0][3] == "call", cmd_list[0][3] + assert cmd_list[0][4] == "test", cmd_list[0][4] + +class scons_subst_list_TestCase(SubstTestCase): + + basic_cases = [ + "$TARGETS", + [ + ["foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"], + ], + + "$SOURCES $NEWLINE $TARGETS", + [ + ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.c", "before"], + ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj"], + ], + + "$SOURCES$NEWLINE", + [ + ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"], + ["after"], + ], + + "foo$FFF", + [ + ["fooGGG"], + ], + + "foo${FFF}", + [ + ["fooGGG"], + ], + + "test ${SOURCES.attribute.attr1}", + [ + ["test", "attr$1-blah with spaces.cpp", "attr$1-ack.cpp", "attr$1-ack.c"], + ], + + "test ${SOURCES.attribute.attr2}", + [ + ["test", "attr$2-blah with spaces.cpp", "attr$2-ack.cpp", "attr$2-ack.c"], + ], + + "$DO --in=$FOO --out=$BAR", + [ + ["do something", "--in=foo.in", "--out=bar with spaces.out"], + ], + + # This test is now fixed, and works like it should. + "$DO --in=$CRAZY --out=$BAR", + [ + ["do something", "--in=crazy\nfile.in", "--out=bar with spaces.out"], + ], + + # Try passing a list to scons_subst_list(). + [ "$SOURCES$NEWLINE", "$TARGETS", "This is a test"], + [ + ["foo/blah with spaces.cpp", "/bar/ack.cpp", "../foo/ack.cbefore"], + ["after", "foo/bar.exe", "/bar/baz with spaces.obj", "../foo/baz.obj", "This is a test"], + ], + + # Test against a former bug in scons_subst_list(). + "$XXX$HHH", + [ + ["GGGIII"], + ], + + # Test double-dollar-sign behavior. + "$$FFF$HHH", + [ + ["$FFFIII"], + ], + + # Test various combinations of strings, lists and functions. + None, [[]], + [None], [[]], + '', [[]], + [''], [[]], + 'x', [['x']], + ['x'], [['x']], + 'x y', [['x', 'y']], + ['x y'], [['x y']], + ['x', 'y'], [['x', 'y']], + '$N', [[]], + ['$N'], [[]], + '$X', [['x']], + ['$X'], [['x']], + '$Y', [['x']], + ['$Y'], [['x']], + #'$R', [[]], + #['$R'], [[]], + '$S', [['x', 'y']], + '$S z', [['x', 'y', 'z']], + ['$S'], [['x', 'y']], + ['$S z'], [['x', 'y z']], # XXX - IS THIS BEST? + ['$S', 'z'], [['x', 'y', 'z']], + '$LS', [['x y']], + '$LS z', [['x y', 'z']], + ['$LS'], [['x y']], + ['$LS z'], [['x y z']], + ['$LS', 'z'], [['x y', 'z']], + '$L', [['x', 'y']], + '$L z', [['x', 'y', 'z']], + ['$L'], [['x', 'y']], + ['$L z'], [['x', 'y z']], # XXX - IS THIS BEST? + ['$L', 'z'], [['x', 'y', 'z']], + cs, [['cs']], + [cs], [['cs']], + cl, [['cl']], + [cl], [['cl']], + '$CS', [['cs']], + ['$CS'], [['cs']], + '$CL', [['cl']], + ['$CL'], [['cl']], + + # Various uses of UserString. + UserString.UserString('x'), [['x']], + [UserString.UserString('x')], [['x']], + UserString.UserString('$X'), [['x']], + [UserString.UserString('$X')], [['x']], + UserString.UserString('$US'), [['us']], + [UserString.UserString('$US')], [['us']], + '$US', [['us']], + ['$US'], [['us']], + + # Test function calls within ${}. + '$FUNCCALL', [['a', 'xc', 'b']], + + # Test handling of newlines in white space. + 'foo\nbar', [['foo'], ['bar']], + 'foo\n\nbar', [['foo'], ['bar']], + 'foo \n \n bar', [['foo'], ['bar']], + 'foo \nmiddle\n bar', [['foo'], ['middle'], ['bar']], + + # Bug reported by Christoph Wiedemann. + cvt('$xxx/bin'), [['/bin']], + + # Test variables smooshed together with different prefixes. + 'foo$AAA', [['fooa']], + '<$AAA', [['<', 'a']], + '>$AAA', [['>', 'a']], + '|$AAA', [['|', 'a']], + + # Test callables that don't match our calling arguments. + '$CALLABLE2', [['callable-2']], + + # Test handling of quotes. + # XXX Find a way to handle this in the future. + #'aaa "bbb ccc" ddd', [['aaa', 'bbb ccc', 'ddd']], + + '${_defines(DEFS)}', [['Q1="q1"', 'Q2="a"']], + ] + + def test_scons_subst_list(self): + """Test scons_subst_list(): basic substitution""" + def convert_lists(expect): + return map(lambda l: map(cvt, l), expect) + return self.basic_comparisons(scons_subst_list, convert_lists) + + subst_list_cases = [ + "test $xxx", + [["test"]], + [["test"]], + [["test"]], + + "test $($xxx$)", + [["test", "$($)"]], + [["test"]], + [["test"]], + + "test $( $xxx $)", + [["test", "$(", "$)"]], + [["test"]], + [["test"]], + + "$AAA ${AAA}A $BBBB $BBB", + [["a", "aA", "b"]], + [["a", "aA", "b"]], + [["a", "aA", "b"]], + + "$RECURSE", + [["foo", "bar"]], + [["foo", "bar"]], + [["foo", "bar"]], + + "$RRR", + [["foo", "bar"]], + [["foo", "bar"]], + [["foo", "bar"]], + + # Verify what happens with no target or source nodes. + "$TARGET $SOURCES", + [[]], + [[]], + [[]], + + "$TARGETS $SOURCE", + [[]], + [[]], + [[]], + + # Various test refactored from ActionTests.py + "${LIST}", + [['This', 'is', '$(', '$)', 'test']], + [['This', 'is', 'test']], + [['This', 'is', 'test']], + + ["|", "$(", "$AAA", "|", "$BBB", "$)", "|", "$CCC", 1], + [["|", "$(", "a", "|", "b", "$)", "|", "c", "1"]], + [["|", "a", "|", "b", "|", "c", "1"]], + [["|", "|", "c", "1"]], + ] + + def test_subst_env(self): + """Test scons_subst_list(): expansion dictionary""" + # The expansion dictionary no longer comes from the construction + # environment automatically. + env = DummyEnv() + s = scons_subst_list('$AAA', env) + assert s == [[]], s + + def test_subst_target_source(self): + """Test scons_subst_list(): target= and source= arguments""" + env = DummyEnv(self.loc) + gvars = env.Dictionary() + t1 = self.MyNode('t1') + t2 = self.MyNode('t2') + s1 = self.MyNode('s1') + s2 = self.MyNode('s2') + result = scons_subst_list("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2], + gvars=gvars) + assert result == [['t1', 's1', 's2']], result + result = scons_subst_list("$TARGET $SOURCES", env, + target=[t1, t2], + source=[s1, s2], + gvars={}) + assert result == [['t1', 's1', 's2']], result + + # Test interpolating a callable. + _t = DummyNode('t') + _s = DummyNode('s') + cmd_list = scons_subst_list("testing $CMDGEN1 $TARGETS $SOURCES", + env, target=_t, source=_s, + gvars=gvars) + assert cmd_list == [['testing', 'foo', 'bar with spaces.out', 't', 's']], cmd_list + + def test_subst_escape(self): + """Test scons_subst_list(): escape functionality""" + env = DummyEnv(self.loc) + gvars = env.Dictionary() + def escape_func(foo): + return '**' + foo + '**' + cmd_list = scons_subst_list("abc $LITERALS xyz", env, gvars=gvars) + assert cmd_list == [['abc', + 'foo\nwith\nnewlines', + 'bar\nwith\nnewlines', + 'xyz']], cmd_list + c = cmd_list[0][0].escape(escape_func) + assert c == 'abc', c + c = cmd_list[0][1].escape(escape_func) + assert c == '**foo\nwith\nnewlines**', c + c = cmd_list[0][2].escape(escape_func) + assert c == '**bar\nwith\nnewlines**', c + c = cmd_list[0][3].escape(escape_func) + assert c == 'xyz', c + + # We used to treat literals smooshed together like the whole + # thing was literal and escape it as a unit. The commented-out + # asserts below are in case we ever have to find a way to + # resurrect that functionality in some way. + cmd_list = scons_subst_list("abc${LITERALS}xyz", env, gvars=gvars) + c = cmd_list[0][0].escape(escape_func) + #assert c == '**abcfoo\nwith\nnewlines**', c + assert c == 'abcfoo\nwith\nnewlines', c + c = cmd_list[0][1].escape(escape_func) + #assert c == '**bar\nwith\nnewlinesxyz**', c + assert c == 'bar\nwith\nnewlinesxyz', c + + _t = DummyNode('t') + + cmd_list = scons_subst_list('echo "target: $TARGET"', env, + target=_t, gvars=gvars) + c = cmd_list[0][0].escape(escape_func) + assert c == 'echo', c + c = cmd_list[0][1].escape(escape_func) + assert c == '"target:', c + c = cmd_list[0][2].escape(escape_func) + assert c == 't"', c + + def test_subst_SUBST_modes(self): + """Test scons_subst_list(): SUBST_* modes""" + env = DummyEnv(self.loc) + subst_list_cases = self.subst_list_cases[:] + gvars = env.Dictionary() + + r = scons_subst_list("$TARGET $SOURCES", env, mode=SUBST_RAW, gvars=gvars) + assert r == [[]], r + + failed = 0 + while subst_list_cases: + input, eraw, ecmd, esig = subst_list_cases[:4] + result = scons_subst_list(input, env, mode=SUBST_RAW, gvars=gvars) + if result != eraw: + if failed == 0: print + print " input %s => RAW %s did not match %s" % (repr(input), repr(result), repr(eraw)) + failed = failed + 1 + result = scons_subst_list(input, env, mode=SUBST_CMD, gvars=gvars) + if result != ecmd: + if failed == 0: print + print " input %s => CMD %s did not match %s" % (repr(input), repr(result), repr(ecmd)) + failed = failed + 1 + result = scons_subst_list(input, env, mode=SUBST_SIG, gvars=gvars) + if result != esig: + if failed == 0: print + print " input %s => SIG %s did not match %s" % (repr(input), repr(result), repr(esig)) + failed = failed + 1 + del subst_list_cases[:4] + assert failed == 0, "%d subst() mode cases failed" % failed + + def test_subst_attribute_errors(self): + """Test scons_subst_list(): handling attribute errors""" + env = DummyEnv() + try: + class Foo: + pass + scons_subst_list('${foo.bar}', env, gvars={'foo':Foo()}) + except SCons.Errors.UserError, e: + expect = [ + "AttributeError `bar' trying to evaluate `${foo.bar}'", + "AttributeError `Foo instance has no attribute 'bar'' trying to evaluate `${foo.bar}'", + "AttributeError `'Foo' instance has no attribute 'bar'' trying to evaluate `${foo.bar}'", + ] + assert str(e) in expect, e + else: + raise AssertionError, "did not catch expected UserError" + + def test_subst_syntax_errors(self): + """Test scons_subst_list(): handling syntax errors""" + env = DummyEnv() + try: + scons_subst_list('$foo.bar.3.0', env) + except SCons.Errors.UserError, e: + expect = [ + "SyntaxError `invalid syntax' trying to evaluate `$foo.bar.3.0'", + "SyntaxError `invalid syntax (line 1)' trying to evaluate `$foo.bar.3.0'", + "SyntaxError `invalid syntax (, line 1)' trying to evaluate `$foo.bar.3.0'", + ] + assert str(e) in expect, e + else: + raise AssertionError, "did not catch expected SyntaxError" + + def test_subst_raw_function(self): + """Test scons_subst_list(): fetch function with SUBST_RAW plus conv""" + # Test that the combination of SUBST_RAW plus a pass-through + # conversion routine allows us to fetch a function through the + # dictionary. + env = DummyEnv(self.loc) + gvars = env.Dictionary() + x = lambda x: x + r = scons_subst_list("$CALLABLE2", env, mode=SUBST_RAW, conv=x, gvars=gvars) + assert r == [[self.callable_object_2]], repr(r) + r = scons_subst_list("$CALLABLE2", env, mode=SUBST_RAW, gvars=gvars) + assert r == [['callable-2']], repr(r) + + def test_subst_list_overriding_gvars(self): + """Test scons_subst_list(): overriding conv()""" + env = DummyEnv() + def s(obj): + return obj + + n1 = self.MyNode('n1') + env = DummyEnv({'NODE' : n1}) + gvars=env.Dictionary() + node = scons_subst_list("$NODE", env, mode=SUBST_RAW, conv=s, gvars=gvars) + assert node == [[n1]], node + node = scons_subst_list("$NODE", env, mode=SUBST_CMD, conv=s, gvars=gvars) + assert node == [[n1]], node + node = scons_subst_list("$NODE", env, mode=SUBST_SIG, conv=s, gvars=gvars) + assert node == [[n1]], node + + def test_subst_list_overriding_gvars(self): + """Test scons_subst_list(): supplying an overriding gvars dictionary""" + env = DummyEnv({'XXX' : 'xxx'}) + result = scons_subst_list('$XXX', env, gvars=env.Dictionary()) + assert result == [['xxx']], result + result = scons_subst_list('$XXX', env, gvars={'XXX' : 'yyy'}) + assert result == [['yyy']], result + +class scons_subst_once_TestCase(unittest.TestCase): + + loc = { + 'CCFLAGS' : '-DFOO', + 'ONE' : 1, + 'RECURSE' : 'r $RECURSE r', + 'LIST' : ['a', 'b', 'c'], + } + + basic_cases = [ + '$CCFLAGS -DBAR', + 'OTHER_KEY', + '$CCFLAGS -DBAR', + + '$CCFLAGS -DBAR', + 'CCFLAGS', + '-DFOO -DBAR', + + 'x $ONE y', + 'ONE', + 'x 1 y', + + 'x $RECURSE y', + 'RECURSE', + 'x r $RECURSE r y', + + '$LIST', + 'LIST', + 'a b c', + + ['$LIST'], + 'LIST', + ['a', 'b', 'c'], + + ['x', '$LIST', 'y'], + 'LIST', + ['x', 'a', 'b', 'c', 'y'], + + ['x', 'x $LIST y', 'y'], + 'LIST', + ['x', 'x a b c y', 'y'], + + ['x', 'x $CCFLAGS y', 'y'], + 'LIST', + ['x', 'x $CCFLAGS y', 'y'], + + ['x', 'x $RECURSE y', 'y'], + 'LIST', + ['x', 'x $RECURSE y', 'y'], + ] + + def test_subst_once(self): + """Test the scons_subst_once() function""" + env = DummyEnv(self.loc) + cases = self.basic_cases[:] + + failed = 0 + while cases: + input, key, expect = cases[:3] + result = scons_subst_once(input, env, key) + if result != expect: + if failed == 0: print + print " input %s (%s) => %s did not match %s" % (repr(input), repr(key), repr(result), repr(expect)) + failed = failed + 1 + del cases[:3] + assert failed == 0, "%d subst() cases failed" % failed + +class quote_spaces_TestCase(unittest.TestCase): + def test_quote_spaces(self): + """Test the quote_spaces() method...""" + q = quote_spaces('x') + assert q == 'x', q + + q = quote_spaces('x x') + assert q == '"x x"', q + + q = quote_spaces('x\tx') + assert q == '"x\tx"', q + + class Node: + def __init__(self, name, children=[]): + self.children = children + self.name = name + def __str__(self): + return self.name + def exists(self): + return 1 + def rexists(self): + return 1 + def has_builder(self): + return 1 + def has_explicit_builder(self): + return 1 + def side_effect(self): + return 1 + def precious(self): + return 1 + def always_build(self): + return 1 + def current(self): + return 1 + +class LiteralTestCase(unittest.TestCase): + def test_Literal(self): + """Test the Literal() function.""" + input_list = [ '$FOO', Literal('$BAR') ] + gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' } + + def escape_func(cmd): + return '**' + cmd + '**' + + cmd_list = scons_subst_list(input_list, None, gvars=gvars) + cmd_list = escape_list(cmd_list[0], escape_func) + assert cmd_list == ['BAZ', '**$BAR**'], cmd_list + +class SpecialAttrWrapperTestCase(unittest.TestCase): + def test_SpecialAttrWrapper(self): + """Test the SpecialAttrWrapper() function.""" + input_list = [ '$FOO', SpecialAttrWrapper('$BAR', 'BLEH') ] + gvars = { 'FOO' : 'BAZ', 'BAR' : 'BLAT' } + + def escape_func(cmd): + return '**' + cmd + '**' + + cmd_list = scons_subst_list(input_list, None, gvars=gvars) + cmd_list = escape_list(cmd_list[0], escape_func) + assert cmd_list == ['BAZ', '**$BAR**'], cmd_list + + cmd_list = scons_subst_list(input_list, None, mode=SUBST_SIG, gvars=gvars) + cmd_list = escape_list(cmd_list[0], escape_func) + assert cmd_list == ['BAZ', '**BLEH**'], cmd_list + +class subst_dict_TestCase(unittest.TestCase): + def test_subst_dict(self): + """Test substituting dictionary values in an Action + """ + t = DummyNode('t') + s = DummyNode('s') + d = subst_dict(target=t, source=s) + assert str(d['TARGETS'][0]) == 't', d['TARGETS'] + assert str(d['TARGET']) == 't', d['TARGET'] + assert str(d['SOURCES'][0]) == 's', d['SOURCES'] + assert str(d['SOURCE']) == 's', d['SOURCE'] + + t1 = DummyNode('t1') + t2 = DummyNode('t2') + s1 = DummyNode('s1') + s2 = DummyNode('s2') + d = subst_dict(target=[t1, t2], source=[s1, s2]) + TARGETS = map(lambda x: str(x), d['TARGETS']) + TARGETS.sort() + assert TARGETS == ['t1', 't2'], d['TARGETS'] + assert str(d['TARGET']) == 't1', d['TARGET'] + SOURCES = map(lambda x: str(x), d['SOURCES']) + SOURCES.sort() + assert SOURCES == ['s1', 's2'], d['SOURCES'] + assert str(d['SOURCE']) == 's1', d['SOURCE'] + + class V: + # Fake Value node with no rfile() method. + def __init__(self, name): + self.name = name + def __str__(self): + return 'v-'+self.name + def get_subst_proxy(self): + return self + + class N(V): + def rfile(self): + return self.__class__('rstr-' + self.name) + + t3 = N('t3') + t4 = DummyNode('t4') + t5 = V('t5') + s3 = DummyNode('s3') + s4 = N('s4') + s5 = V('s5') + d = subst_dict(target=[t3, t4, t5], source=[s3, s4, s5]) + TARGETS = map(lambda x: str(x), d['TARGETS']) + TARGETS.sort() + assert TARGETS == ['t4', 'v-t3', 'v-t5'], TARGETS + SOURCES = map(lambda x: str(x), d['SOURCES']) + SOURCES.sort() + assert SOURCES == ['s3', 'v-rstr-s4', 'v-s5'], SOURCES + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ + CLVar_TestCase, + LiteralTestCase, + SpecialAttrWrapperTestCase, + quote_spaces_TestCase, + scons_subst_TestCase, + scons_subst_list_TestCase, + scons_subst_once_TestCase, + subst_dict_TestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Taskmaster.py b/src/engine/SCons/Taskmaster.py new file mode 100644 index 0000000..1da11bd --- /dev/null +++ b/src/engine/SCons/Taskmaster.py @@ -0,0 +1,1030 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = """ +Generic Taskmaster module for the SCons build engine. + +This module contains the primary interface(s) between a wrapping user +interface and the SCons build engine. There are two key classes here: + + Taskmaster + This is the main engine for walking the dependency graph and + calling things to decide what does or doesn't need to be built. + + Task + This is the base class for allowing a wrapping interface to + decide what does or doesn't actually need to be done. The + intention is for a wrapping interface to subclass this as + appropriate for different types of behavior it may need. + + The canonical example is the SCons native Python interface, + which has Task subclasses that handle its specific behavior, + like printing "`foo' is up to date" when a top-level target + doesn't need to be built, and handling the -c option by removing + targets as its "build" action. There is also a separate subclass + for suppressing this output when the -q option is used. + + The Taskmaster instantiates a Task object for each (set of) + target(s) that it decides need to be evaluated and/or built. +""" + +__revision__ = "src/engine/SCons/Taskmaster.py 4577 2009/12/27 19:44:43 scons" + +from itertools import chain +import operator +import string +import sys +import traceback + +import SCons.Errors +import SCons.Node +import SCons.Warnings + +StateString = SCons.Node.StateString +NODE_NO_STATE = SCons.Node.no_state +NODE_PENDING = SCons.Node.pending +NODE_EXECUTING = SCons.Node.executing +NODE_UP_TO_DATE = SCons.Node.up_to_date +NODE_EXECUTED = SCons.Node.executed +NODE_FAILED = SCons.Node.failed + + +# A subsystem for recording stats about how different Nodes are handled by +# the main Taskmaster loop. There's no external control here (no need for +# a --debug= option); enable it by changing the value of CollectStats. + +CollectStats = None + +class Stats: + """ + A simple class for holding statistics about the disposition of a + Node by the Taskmaster. If we're collecting statistics, each Node + processed by the Taskmaster gets one of these attached, in which case + the Taskmaster records its decision each time it processes the Node. + (Ideally, that's just once per Node.) + """ + def __init__(self): + """ + Instantiates a Taskmaster.Stats object, initializing all + appropriate counters to zero. + """ + self.considered = 0 + self.already_handled = 0 + self.problem = 0 + self.child_failed = 0 + self.not_built = 0 + self.side_effects = 0 + self.build = 0 + +StatsNodes = [] + +fmt = "%(considered)3d "\ + "%(already_handled)3d " \ + "%(problem)3d " \ + "%(child_failed)3d " \ + "%(not_built)3d " \ + "%(side_effects)3d " \ + "%(build)3d " + +def dump_stats(): + StatsNodes.sort(lambda a, b: cmp(str(a), str(b))) + for n in StatsNodes: + print (fmt % n.stats.__dict__) + str(n) + + + +class Task: + """ + Default SCons build engine task. + + This controls the interaction of the actual building of node + and the rest of the engine. + + This is expected to handle all of the normally-customizable + aspects of controlling a build, so any given application + *should* be able to do what it wants by sub-classing this + class and overriding methods as appropriate. If an application + needs to customze something by sub-classing Taskmaster (or + some other build engine class), we should first try to migrate + that functionality into this class. + + Note that it's generally a good idea for sub-classes to call + these methods explicitly to update state, etc., rather than + roll their own interaction with Taskmaster from scratch. + """ + def __init__(self, tm, targets, top, node): + self.tm = tm + self.targets = targets + self.top = top + self.node = node + self.exc_clear() + + def trace_message(self, method, node, description='node'): + fmt = '%-20s %s %s\n' + return fmt % (method + ':', description, self.tm.trace_node(node)) + + def display(self, message): + """ + Hook to allow the calling interface to display a message. + + This hook gets called as part of preparing a task for execution + (that is, a Node to be built). As part of figuring out what Node + should be built next, the actually target list may be altered, + along with a message describing the alteration. The calling + interface can subclass Task and provide a concrete implementation + of this method to see those messages. + """ + pass + + def prepare(self): + """ + Called just before the task is executed. + + This is mainly intended to give the target Nodes a chance to + unlink underlying files and make all necessary directories before + the Action is actually called to build the targets. + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.prepare()', self.node)) + + # Now that it's the appropriate time, give the TaskMaster a + # chance to raise any exceptions it encountered while preparing + # this task. + self.exception_raise() + + if self.tm.message: + self.display(self.tm.message) + self.tm.message = None + + # Let the targets take care of any necessary preparations. + # This includes verifying that all of the necessary sources + # and dependencies exist, removing the target file(s), etc. + # + # As of April 2008, the get_executor().prepare() method makes + # sure that all of the aggregate sources necessary to build this + # Task's target(s) exist in one up-front check. The individual + # target t.prepare() methods check that each target's explicit + # or implicit dependencies exists, and also initialize the + # .sconsign info. + executor = self.targets[0].get_executor() + executor.prepare() + for t in executor.get_action_targets(): + t.prepare() + for s in t.side_effects: + s.prepare() + + def get_target(self): + """Fetch the target being built or updated by this task. + """ + return self.node + + def needs_execute(self): + # TODO(deprecate): "return True" is the old default behavior; + # change it to NotImplementedError (after running through the + # Deprecation Cycle) so the desired behavior is explicitly + # determined by which concrete subclass is used. + #raise NotImplementedError + msg = ('Direct use of the Taskmaster.Task class will be deprecated\n' + + '\tin a future release.') + SCons.Warnings.warn(SCons.Warnings.TaskmasterNeedsExecuteWarning, msg) + return True + + def execute(self): + """ + Called to execute the task. + + This method is called from multiple threads in a parallel build, + so only do thread safe stuff here. Do thread unsafe stuff in + prepare(), executed() or failed(). + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.execute()', self.node)) + + try: + everything_was_cached = 1 + for t in self.targets: + if t.retrieve_from_cache(): + # Call the .built() method without calling the + # .push_to_cache() method, since we just got the + # target from the cache and don't need to push + # it back there. + t.set_state(NODE_EXECUTED) + t.built() + else: + everything_was_cached = 0 + break + if not everything_was_cached: + self.targets[0].build() + except SystemExit: + exc_value = sys.exc_info()[1] + raise SCons.Errors.ExplicitExit(self.targets[0], exc_value.code) + except SCons.Errors.UserError: + raise + except SCons.Errors.BuildError: + raise + except Exception, e: + buildError = SCons.Errors.convert_to_BuildError(e) + buildError.node = self.targets[0] + buildError.exc_info = sys.exc_info() + raise buildError + + def executed_without_callbacks(self): + """ + Called when the task has been successfully executed + and the Taskmaster instance doesn't want to call + the Node's callback methods. + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.executed_without_callbacks()', + self.node)) + + for t in self.targets: + if t.get_state() == NODE_EXECUTING: + for side_effect in t.side_effects: + side_effect.set_state(NODE_NO_STATE) + t.set_state(NODE_EXECUTED) + + def executed_with_callbacks(self): + """ + Called when the task has been successfully executed and + the Taskmaster instance wants to call the Node's callback + methods. + + This may have been a do-nothing operation (to preserve build + order), so we must check the node's state before deciding whether + it was "built", in which case we call the appropriate Node method. + In any event, we always call "visited()", which will handle any + post-visit actions that must take place regardless of whether + or not the target was an actual built target or a source Node. + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.executed_with_callbacks()', + self.node)) + + for t in self.targets: + if t.get_state() == NODE_EXECUTING: + for side_effect in t.side_effects: + side_effect.set_state(NODE_NO_STATE) + t.set_state(NODE_EXECUTED) + t.push_to_cache() + t.built() + t.visited() + + executed = executed_with_callbacks + + def failed(self): + """ + Default action when a task fails: stop the build. + + Note: Although this function is normally invoked on nodes in + the executing state, it might also be invoked on up-to-date + nodes when using Configure(). + """ + self.fail_stop() + + def fail_stop(self): + """ + Explicit stop-the-build failure. + + This sets failure status on the target nodes and all of + their dependent parent nodes. + + Note: Although this function is normally invoked on nodes in + the executing state, it might also be invoked on up-to-date + nodes when using Configure(). + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.failed_stop()', self.node)) + + # Invoke will_not_build() to clean-up the pending children + # list. + self.tm.will_not_build(self.targets, lambda n: n.set_state(NODE_FAILED)) + + # Tell the taskmaster to not start any new tasks + self.tm.stop() + + # We're stopping because of a build failure, but give the + # calling Task class a chance to postprocess() the top-level + # target under which the build failure occurred. + self.targets = [self.tm.current_top] + self.top = 1 + + def fail_continue(self): + """ + Explicit continue-the-build failure. + + This sets failure status on the target nodes and all of + their dependent parent nodes. + + Note: Although this function is normally invoked on nodes in + the executing state, it might also be invoked on up-to-date + nodes when using Configure(). + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.failed_continue()', self.node)) + + self.tm.will_not_build(self.targets, lambda n: n.set_state(NODE_FAILED)) + + def make_ready_all(self): + """ + Marks all targets in a task ready for execution. + + This is used when the interface needs every target Node to be + visited--the canonical example being the "scons -c" option. + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.make_ready_all()', self.node)) + + self.out_of_date = self.targets[:] + for t in self.targets: + t.disambiguate().set_state(NODE_EXECUTING) + for s in t.side_effects: + s.set_state(NODE_EXECUTING) + + def make_ready_current(self): + """ + Marks all targets in a task ready for execution if any target + is not current. + + This is the default behavior for building only what's necessary. + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.make_ready_current()', + self.node)) + + self.out_of_date = [] + needs_executing = False + for t in self.targets: + try: + t.disambiguate().make_ready() + is_up_to_date = not t.has_builder() or \ + (not t.always_build and t.is_up_to_date()) + except EnvironmentError, e: + raise SCons.Errors.BuildError(node=t, errstr=e.strerror, filename=e.filename) + + if not is_up_to_date: + self.out_of_date.append(t) + needs_executing = True + + if needs_executing: + for t in self.targets: + t.set_state(NODE_EXECUTING) + for s in t.side_effects: + s.set_state(NODE_EXECUTING) + else: + for t in self.targets: + # We must invoke visited() to ensure that the node + # information has been computed before allowing the + # parent nodes to execute. (That could occur in a + # parallel build...) + t.visited() + t.set_state(NODE_UP_TO_DATE) + + make_ready = make_ready_current + + def postprocess(self): + """ + Post-processes a task after it's been executed. + + This examines all the targets just built (or not, we don't care + if the build was successful, or even if there was no build + because everything was up-to-date) to see if they have any + waiting parent Nodes, or Nodes waiting on a common side effect, + that can be put back on the candidates list. + """ + T = self.tm.trace + if T: T.write(self.trace_message('Task.postprocess()', self.node)) + + # We may have built multiple targets, some of which may have + # common parents waiting for this build. Count up how many + # targets each parent was waiting for so we can subtract the + # values later, and so we *don't* put waiting side-effect Nodes + # back on the candidates list if the Node is also a waiting + # parent. + + targets = set(self.targets) + + pending_children = self.tm.pending_children + parents = {} + for t in targets: + # A node can only be in the pending_children set if it has + # some waiting_parents. + if t.waiting_parents: + if T: T.write(self.trace_message('Task.postprocess()', + t, + 'removing')) + pending_children.discard(t) + for p in t.waiting_parents: + parents[p] = parents.get(p, 0) + 1 + + for t in targets: + for s in t.side_effects: + if s.get_state() == NODE_EXECUTING: + s.set_state(NODE_NO_STATE) + for p in s.waiting_parents: + parents[p] = parents.get(p, 0) + 1 + for p in s.waiting_s_e: + if p.ref_count == 0: + self.tm.candidates.append(p) + + for p, subtract in parents.items(): + p.ref_count = p.ref_count - subtract + if T: T.write(self.trace_message('Task.postprocess()', + p, + 'adjusted parent ref count')) + if p.ref_count == 0: + self.tm.candidates.append(p) + + for t in targets: + t.postprocess() + + # Exception handling subsystem. + # + # Exceptions that occur while walking the DAG or examining Nodes + # must be raised, but must be raised at an appropriate time and in + # a controlled manner so we can, if necessary, recover gracefully, + # possibly write out signature information for Nodes we've updated, + # etc. This is done by having the Taskmaster tell us about the + # exception, and letting + + def exc_info(self): + """ + Returns info about a recorded exception. + """ + return self.exception + + def exc_clear(self): + """ + Clears any recorded exception. + + This also changes the "exception_raise" attribute to point + to the appropriate do-nothing method. + """ + self.exception = (None, None, None) + self.exception_raise = self._no_exception_to_raise + + def exception_set(self, exception=None): + """ + Records an exception to be raised at the appropriate time. + + This also changes the "exception_raise" attribute to point + to the method that will, in fact + """ + if not exception: + exception = sys.exc_info() + self.exception = exception + self.exception_raise = self._exception_raise + + def _no_exception_to_raise(self): + pass + + def _exception_raise(self): + """ + Raises a pending exception that was recorded while getting a + Task ready for execution. + """ + exc = self.exc_info()[:] + try: + exc_type, exc_value, exc_traceback = exc + except ValueError: + exc_type, exc_value = exc + exc_traceback = None + raise exc_type, exc_value, exc_traceback + +class AlwaysTask(Task): + def needs_execute(self): + """ + Always returns True (indicating this Task should always + be executed). + + Subclasses that need this behavior (as opposed to the default + of only executing Nodes that are out of date w.r.t. their + dependencies) can use this as follows: + + class MyTaskSubclass(SCons.Taskmaster.Task): + needs_execute = SCons.Taskmaster.Task.execute_always + """ + return True + +class OutOfDateTask(Task): + def needs_execute(self): + """ + Returns True (indicating this Task should be executed) if this + Task's target state indicates it needs executing, which has + already been determined by an earlier up-to-date check. + """ + return self.targets[0].get_state() == SCons.Node.executing + + +def find_cycle(stack, visited): + if stack[-1] in visited: + return None + visited.add(stack[-1]) + for n in stack[-1].waiting_parents: + stack.append(n) + if stack[0] == stack[-1]: + return stack + if find_cycle(stack, visited): + return stack + stack.pop() + return None + + +class Taskmaster: + """ + The Taskmaster for walking the dependency DAG. + """ + + def __init__(self, targets=[], tasker=None, order=None, trace=None): + self.original_top = targets + self.top_targets_left = targets[:] + self.top_targets_left.reverse() + self.candidates = [] + if tasker is None: + tasker = OutOfDateTask + self.tasker = tasker + if not order: + order = lambda l: l + self.order = order + self.message = None + self.trace = trace + self.next_candidate = self.find_next_candidate + self.pending_children = set() + + def find_next_candidate(self): + """ + Returns the next candidate Node for (potential) evaluation. + + The candidate list (really a stack) initially consists of all of + the top-level (command line) targets provided when the Taskmaster + was initialized. While we walk the DAG, visiting Nodes, all the + children that haven't finished processing get pushed on to the + candidate list. Each child can then be popped and examined in + turn for whether *their* children are all up-to-date, in which + case a Task will be created for their actual evaluation and + potential building. + + Here is where we also allow candidate Nodes to alter the list of + Nodes that should be examined. This is used, for example, when + invoking SCons in a source directory. A source directory Node can + return its corresponding build directory Node, essentially saying, + "Hey, you really need to build this thing over here instead." + """ + try: + return self.candidates.pop() + except IndexError: + pass + try: + node = self.top_targets_left.pop() + except IndexError: + return None + self.current_top = node + alt, message = node.alter_targets() + if alt: + self.message = message + self.candidates.append(node) + self.candidates.extend(self.order(alt)) + node = self.candidates.pop() + return node + + def no_next_candidate(self): + """ + Stops Taskmaster processing by not returning a next candidate. + + Note that we have to clean-up the Taskmaster candidate list + because the cycle detection depends on the fact all nodes have + been processed somehow. + """ + while self.candidates: + candidates = self.candidates + self.candidates = [] + self.will_not_build(candidates) + return None + + def _validate_pending_children(self): + """ + Validate the content of the pending_children set. Assert if an + internal error is found. + + This function is used strictly for debugging the taskmaster by + checking that no invariants are violated. It is not used in + normal operation. + + The pending_children set is used to detect cycles in the + dependency graph. We call a "pending child" a child that is + found in the "pending" state when checking the dependencies of + its parent node. + + A pending child can occur when the Taskmaster completes a loop + through a cycle. For example, lets imagine a graph made of + three node (A, B and C) making a cycle. The evaluation starts + at node A. The taskmaster first consider whether node A's + child B is up-to-date. Then, recursively, node B needs to + check whether node C is up-to-date. This leaves us with a + dependency graph looking like: + + Next candidate \ + \ + Node A (Pending) --> Node B(Pending) --> Node C (NoState) + ^ | + | | + +-------------------------------------+ + + Now, when the Taskmaster examines the Node C's child Node A, + it finds that Node A is in the "pending" state. Therefore, + Node A is a pending child of node C. + + Pending children indicate that the Taskmaster has potentially + loop back through a cycle. We say potentially because it could + also occur when a DAG is evaluated in parallel. For example, + consider the following graph: + + + Node A (Pending) --> Node B(Pending) --> Node C (Pending) --> ... + | ^ + | | + +----------> Node D (NoState) --------+ + / + Next candidate / + + The Taskmaster first evaluates the nodes A, B, and C and + starts building some children of node C. Assuming, that the + maximum parallel level has not been reached, the Taskmaster + will examine Node D. It will find that Node C is a pending + child of Node D. + + In summary, evaluating a graph with a cycle will always + involve a pending child at one point. A pending child might + indicate either a cycle or a diamond-shaped DAG. Only a + fraction of the nodes ends-up being a "pending child" of + another node. This keeps the pending_children set small in + practice. + + We can differentiate between the two cases if we wait until + the end of the build. At this point, all the pending children + nodes due to a diamond-shaped DAG will have been properly + built (or will have failed to build). But, the pending + children involved in a cycle will still be in the pending + state. + + The taskmaster removes nodes from the pending_children set as + soon as a pending_children node moves out of the pending + state. This also helps to keep the pending_children set small. + """ + + for n in self.pending_children: + assert n.state in (NODE_PENDING, NODE_EXECUTING), \ + (str(n), StateString[n.state]) + assert len(n.waiting_parents) != 0, (str(n), len(n.waiting_parents)) + for p in n.waiting_parents: + assert p.ref_count > 0, (str(n), str(p), p.ref_count) + + + def trace_message(self, message): + return 'Taskmaster: %s\n' % message + + def trace_node(self, node): + return '<%-10s %-3s %s>' % (StateString[node.get_state()], + node.ref_count, + repr(str(node))) + + def _find_next_ready_node(self): + """ + Finds the next node that is ready to be built. + + This is *the* main guts of the DAG walk. We loop through the + list of candidates, looking for something that has no un-built + children (i.e., that is a leaf Node or has dependencies that are + all leaf Nodes or up-to-date). Candidate Nodes are re-scanned + (both the target Node itself and its sources, which are always + scanned in the context of a given target) to discover implicit + dependencies. A Node that must wait for some children to be + built will be put back on the candidates list after the children + have finished building. A Node that has been put back on the + candidates list in this way may have itself (or its sources) + re-scanned, in order to handle generated header files (e.g.) and + the implicit dependencies therein. + + Note that this method does not do any signature calculation or + up-to-date check itself. All of that is handled by the Task + class. This is purely concerned with the dependency graph walk. + """ + + self.ready_exc = None + + T = self.trace + if T: T.write('\n' + self.trace_message('Looking for a node to evaluate')) + + while 1: + node = self.next_candidate() + if node is None: + if T: T.write(self.trace_message('No candidate anymore.') + '\n') + return None + + node = node.disambiguate() + state = node.get_state() + + # For debugging only: + # + # try: + # self._validate_pending_children() + # except: + # self.ready_exc = sys.exc_info() + # return node + + if CollectStats: + if not hasattr(node, 'stats'): + node.stats = Stats() + StatsNodes.append(node) + S = node.stats + S.considered = S.considered + 1 + else: + S = None + + if T: T.write(self.trace_message(' Considering node %s and its children:' % self.trace_node(node))) + + if state == NODE_NO_STATE: + # Mark this node as being on the execution stack: + node.set_state(NODE_PENDING) + elif state > NODE_PENDING: + # Skip this node if it has already been evaluated: + if S: S.already_handled = S.already_handled + 1 + if T: T.write(self.trace_message(' already handled (executed)')) + continue + + executor = node.get_executor() + + try: + children = executor.get_all_children() + except SystemExit: + exc_value = sys.exc_info()[1] + e = SCons.Errors.ExplicitExit(node, exc_value.code) + self.ready_exc = (SCons.Errors.ExplicitExit, e) + if T: T.write(self.trace_message(' SystemExit')) + return node + except Exception, e: + # We had a problem just trying to figure out the + # children (like a child couldn't be linked in to a + # VariantDir, or a Scanner threw something). Arrange to + # raise the exception when the Task is "executed." + self.ready_exc = sys.exc_info() + if S: S.problem = S.problem + 1 + if T: T.write(self.trace_message(' exception %s while scanning children.\n' % e)) + return node + + children_not_visited = [] + children_pending = set() + children_not_ready = [] + children_failed = False + + for child in chain(executor.get_all_prerequisites(), children): + childstate = child.get_state() + + if T: T.write(self.trace_message(' ' + self.trace_node(child))) + + if childstate == NODE_NO_STATE: + children_not_visited.append(child) + elif childstate == NODE_PENDING: + children_pending.add(child) + elif childstate == NODE_FAILED: + children_failed = True + + if childstate <= NODE_EXECUTING: + children_not_ready.append(child) + + + # These nodes have not even been visited yet. Add + # them to the list so that on some next pass we can + # take a stab at evaluating them (or their children). + children_not_visited.reverse() + self.candidates.extend(self.order(children_not_visited)) + #if T and children_not_visited: + # T.write(self.trace_message(' adding to candidates: %s' % map(str, children_not_visited))) + # T.write(self.trace_message(' candidates now: %s\n' % map(str, self.candidates))) + + # Skip this node if any of its children have failed. + # + # This catches the case where we're descending a top-level + # target and one of our children failed while trying to be + # built by a *previous* descent of an earlier top-level + # target. + # + # It can also occur if a node is reused in multiple + # targets. One first descends though the one of the + # target, the next time occurs through the other target. + # + # Note that we can only have failed_children if the + # --keep-going flag was used, because without it the build + # will stop before diving in the other branch. + # + # Note that even if one of the children fails, we still + # added the other children to the list of candidate nodes + # to keep on building (--keep-going). + if children_failed: + for n in executor.get_action_targets(): + n.set_state(NODE_FAILED) + + if S: S.child_failed = S.child_failed + 1 + if T: T.write(self.trace_message('****** %s\n' % self.trace_node(node))) + continue + + if children_not_ready: + for child in children_not_ready: + # We're waiting on one or more derived targets + # that have not yet finished building. + if S: S.not_built = S.not_built + 1 + + # Add this node to the waiting parents lists of + # anything we're waiting on, with a reference + # count so we can be put back on the list for + # re-evaluation when they've all finished. + node.ref_count = node.ref_count + child.add_to_waiting_parents(node) + if T: T.write(self.trace_message(' adjusted ref count: %s, child %s' % + (self.trace_node(node), repr(str(child))))) + + if T: + for pc in children_pending: + T.write(self.trace_message(' adding %s to the pending children set\n' % + self.trace_node(pc))) + self.pending_children = self.pending_children | children_pending + + continue + + # Skip this node if it has side-effects that are + # currently being built: + wait_side_effects = False + for se in executor.get_action_side_effects(): + if se.get_state() == NODE_EXECUTING: + se.add_to_waiting_s_e(node) + wait_side_effects = True + + if wait_side_effects: + if S: S.side_effects = S.side_effects + 1 + continue + + # The default when we've gotten through all of the checks above: + # this node is ready to be built. + if S: S.build = S.build + 1 + if T: T.write(self.trace_message('Evaluating %s\n' % + self.trace_node(node))) + + # For debugging only: + # + # try: + # self._validate_pending_children() + # except: + # self.ready_exc = sys.exc_info() + # return node + + return node + + return None + + def next_task(self): + """ + Returns the next task to be executed. + + This simply asks for the next Node to be evaluated, and then wraps + it in the specific Task subclass with which we were initialized. + """ + node = self._find_next_ready_node() + + if node is None: + return None + + tlist = node.get_executor().get_all_targets() + + task = self.tasker(self, tlist, node in self.original_top, node) + try: + task.make_ready() + except: + # We had a problem just trying to get this task ready (like + # a child couldn't be linked in to a VariantDir when deciding + # whether this node is current). Arrange to raise the + # exception when the Task is "executed." + self.ready_exc = sys.exc_info() + + if self.ready_exc: + task.exception_set(self.ready_exc) + + self.ready_exc = None + + return task + + def will_not_build(self, nodes, node_func=lambda n: None): + """ + Perform clean-up about nodes that will never be built. Invokes + a user defined function on all of these nodes (including all + of their parents). + """ + + T = self.trace + + pending_children = self.pending_children + + to_visit = set(nodes) + pending_children = pending_children - to_visit + + if T: + for n in nodes: + T.write(self.trace_message(' removing node %s from the pending children set\n' % + self.trace_node(n))) + try: + while 1: + try: + node = to_visit.pop() + except AttributeError: + # Python 1.5.2 + if len(to_visit): + node = to_visit[0] + to_visit.remove(node) + else: + break + + node_func(node) + + # Prune recursion by flushing the waiting children + # list immediately. + parents = node.waiting_parents + node.waiting_parents = set() + + to_visit = to_visit | parents + pending_children = pending_children - parents + + for p in parents: + p.ref_count = p.ref_count - 1 + if T: T.write(self.trace_message(' removing parent %s from the pending children set\n' % + self.trace_node(p))) + except KeyError: + # The container to_visit has been emptied. + pass + + # We have the stick back the pending_children list into the + # task master because the python 1.5.2 compatibility does not + # allow us to use in-place updates + self.pending_children = pending_children + + def stop(self): + """ + Stops the current build completely. + """ + self.next_candidate = self.no_next_candidate + + def cleanup(self): + """ + Check for dependency cycles. + """ + if not self.pending_children: + return + + # TODO(1.5) + #nclist = [ (n, find_cycle([n], set())) for n in self.pending_children ] + nclist = map(lambda n: (n, find_cycle([n], set())), self.pending_children) + + # TODO(1.5) + #genuine_cycles = [ + # node for node, cycle in nclist + # if cycle or node.get_state() != NODE_EXECUTED + #] + genuine_cycles = filter(lambda t: t[1] or t[0].get_state() != NODE_EXECUTED, nclist) + if not genuine_cycles: + # All of the "cycles" found were single nodes in EXECUTED state, + # which is to say, they really weren't cycles. Just return. + return + + desc = 'Found dependency cycle(s):\n' + for node, cycle in nclist: + if cycle: + desc = desc + " " + string.join(map(str, cycle), " -> ") + "\n" + else: + desc = desc + \ + " Internal Error: no cycle found for node %s (%s) in state %s\n" % \ + (node, repr(node), StateString[node.get_state()]) + + raise SCons.Errors.UserError, desc + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/TaskmasterTests.py b/src/engine/SCons/TaskmasterTests.py new file mode 100644 index 0000000..cef256c --- /dev/null +++ b/src/engine/SCons/TaskmasterTests.py @@ -0,0 +1,1138 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/TaskmasterTests.py 4577 2009/12/27 19:44:43 scons" + +import copy +import sys +import unittest + +import SCons.Taskmaster +import SCons.Errors + + +built_text = None +cache_text = [] +visited_nodes = [] +executed = None +scan_called = 0 + +class Node: + def __init__(self, name, kids = [], scans = []): + self.name = name + self.kids = kids + self.scans = scans + self.cached = 0 + self.scanned = 0 + self.scanner = None + self.targets = [self] + self.prerequisites = [] + class Builder: + def targets(self, node): + return node.targets + self.builder = Builder() + self.bsig = None + self.csig = None + self.state = SCons.Node.no_state + self.prepared = None + self.ref_count = 0 + self.waiting_parents = set() + self.waiting_s_e = set() + self.side_effect = 0 + self.side_effects = [] + self.alttargets = [] + self.postprocessed = None + self._bsig_val = None + self._current_val = 0 + self.always_build = None + + def disambiguate(self): + return self + + def push_to_cache(self): + pass + + def retrieve_from_cache(self): + global cache_text + if self.cached: + cache_text.append(self.name + " retrieved") + return self.cached + + def make_ready(self): + pass + + def prepare(self): + self.prepared = 1 + + def build(self): + global built_text + built_text = self.name + " built" + + def built(self): + global built_text + if not self.cached: + built_text = built_text + " really" + + def has_builder(self): + return not self.builder is None + + def is_derived(self): + return self.has_builder or self.side_effect + + def alter_targets(self): + return self.alttargets, None + + def visited(self): + global visited_nodes + visited_nodes.append(self.name) + + def children(self): + if not self.scanned: + self.scan() + self.scanned = 1 + return self.kids + + def scan(self): + global scan_called + scan_called = scan_called + 1 + self.kids = self.kids + self.scans + self.scans = [] + + def scanner_key(self): + return self.name + + def add_to_waiting_parents(self, node): + wp = self.waiting_parents + if node in wp: + return 0 + wp.add(node) + return 1 + + def get_state(self): + return self.state + + def set_state(self, state): + self.state = state + + def set_bsig(self, bsig): + self.bsig = bsig + + def set_csig(self, csig): + self.csig = csig + + def store_csig(self): + pass + + def store_bsig(self): + pass + + def is_pseudo_derived(self): + pass + + def is_up_to_date(self): + return self._current_val + + def depends_on(self, nodes): + for node in nodes: + if node in self.kids: + return 1 + return 0 + + def __str__(self): + return self.name + + def postprocess(self): + self.postprocessed = 1 + self.waiting_parents = set() + + def get_executor(self): + if not hasattr(self, 'executor'): + class Executor: + def prepare(self): + pass + def get_action_targets(self): + return self.targets + def get_all_targets(self): + return self.targets + def get_all_children(self): + result = [] + for node in self.targets: + result.extend(node.children()) + return result + def get_all_prerequisites(self): + return [] + def get_action_side_effects(self): + return [] + self.executor = Executor() + self.executor.targets = self.targets + return self.executor + +class OtherError(Exception): + pass + +class MyException(Exception): + pass + + +class TaskmasterTestCase(unittest.TestCase): + + def test_next_task(self): + """Test fetching the next task + """ + global built_text + + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1, n1]) + t = tm.next_task() + t.prepare() + t.execute() + t = tm.next_task() + assert t is None + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + + tm = SCons.Taskmaster.Taskmaster([n3]) + + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n1 built", built_text + t.executed() + t.postprocess() + + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n2 built", built_text + t.executed() + t.postprocess() + + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n3 built", built_text + t.executed() + t.postprocess() + + assert tm.next_task() is None + + built_text = "up to date: " + top_node = n3 + + class MyTask(SCons.Taskmaster.Task): + def execute(self): + global built_text + if self.targets[0].get_state() == SCons.Node.up_to_date: + if self.top: + built_text = self.targets[0].name + " up-to-date top" + else: + built_text = self.targets[0].name + " up-to-date" + else: + self.targets[0].build() + + n1.set_state(SCons.Node.no_state) + n1._current_val = 1 + n2.set_state(SCons.Node.no_state) + n2._current_val = 1 + n3.set_state(SCons.Node.no_state) + n3._current_val = 1 + tm = SCons.Taskmaster.Taskmaster(targets = [n3], tasker = MyTask) + + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n1 up-to-date", built_text + t.executed() + t.postprocess() + + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n2 up-to-date", built_text + t.executed() + t.postprocess() + + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n3 up-to-date top", built_text + t.executed() + t.postprocess() + + assert tm.next_task() is None + + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + n4 = Node("n4") + n5 = Node("n5", [n3, n4]) + tm = SCons.Taskmaster.Taskmaster([n5]) + + t1 = tm.next_task() + assert t1.get_target() == n1 + + t2 = tm.next_task() + assert t2.get_target() == n2 + + t4 = tm.next_task() + assert t4.get_target() == n4 + t4.executed() + t4.postprocess() + + t1.executed() + t1.postprocess() + t2.executed() + t2.postprocess() + t3 = tm.next_task() + assert t3.get_target() == n3 + + t3.executed() + t3.postprocess() + t5 = tm.next_task() + assert t5.get_target() == n5, t5.get_target() + t5.executed() + t5.postprocess() + + assert tm.next_task() is None + + + n4 = Node("n4") + n4.set_state(SCons.Node.executed) + tm = SCons.Taskmaster.Taskmaster([n4]) + assert tm.next_task() is None + + n1 = Node("n1") + n2 = Node("n2", [n1]) + tm = SCons.Taskmaster.Taskmaster([n2,n2]) + t = tm.next_task() + t.executed() + t.postprocess() + t = tm.next_task() + assert tm.next_task() is None + + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1], [n2]) + tm = SCons.Taskmaster.Taskmaster([n3]) + t = tm.next_task() + target = t.get_target() + assert target == n1, target + t.executed() + t.postprocess() + t = tm.next_task() + target = t.get_target() + assert target == n2, target + t.executed() + t.postprocess() + t = tm.next_task() + target = t.get_target() + assert target == n3, target + t.executed() + t.postprocess() + assert tm.next_task() is None + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + n4 = Node("n4", [n3]) + n5 = Node("n5", [n3]) + global scan_called + scan_called = 0 + tm = SCons.Taskmaster.Taskmaster([n4]) + t = tm.next_task() + assert t.get_target() == n1 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n2 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n3 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n4 + t.executed() + t.postprocess() + assert tm.next_task() is None + assert scan_called == 4, scan_called + + tm = SCons.Taskmaster.Taskmaster([n5]) + t = tm.next_task() + assert t.get_target() == n5, t.get_target() + t.executed() + assert tm.next_task() is None + assert scan_called == 5, scan_called + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3") + n4 = Node("n4", [n1,n2,n3]) + n5 = Node("n5", [n4]) + n3.side_effect = 1 + n1.side_effects = n2.side_effects = n3.side_effects = [n4] + tm = SCons.Taskmaster.Taskmaster([n1,n2,n3,n4,n5]) + t = tm.next_task() + assert t.get_target() == n1 + assert n4.state == SCons.Node.executing, n4.state + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n2 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n3 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n4 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n5 + assert not tm.next_task() + t.executed() + t.postprocess() + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3") + n4 = Node("n4", [n1,n2,n3]) + def reverse(dependencies): + dependencies.reverse() + return dependencies + tm = SCons.Taskmaster.Taskmaster([n4], order=reverse) + t = tm.next_task() + assert t.get_target() == n3, t.get_target() + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n2, t.get_target() + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n1, t.get_target() + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n4, t.get_target() + t.executed() + t.postprocess() + + n5 = Node("n5") + n6 = Node("n6") + n7 = Node("n7") + n6.alttargets = [n7] + + tm = SCons.Taskmaster.Taskmaster([n5]) + t = tm.next_task() + assert t.get_target() == n5 + t.executed() + t.postprocess() + + tm = SCons.Taskmaster.Taskmaster([n6]) + t = tm.next_task() + assert t.get_target() == n7 + t.executed() + t.postprocess() + t = tm.next_task() + assert t.get_target() == n6 + t.executed() + t.postprocess() + + n1 = Node("n1") + n2 = Node("n2", [n1]) + n1.set_state(SCons.Node.failed) + tm = SCons.Taskmaster.Taskmaster([n2]) + assert tm.next_task() is None + + n1 = Node("n1") + n2 = Node("n2") + n1.targets = [n1, n2] + n1._current_val = 1 + tm = SCons.Taskmaster.Taskmaster([n1]) + t = tm.next_task() + t.executed() + t.postprocess() + + s = n1.get_state() + assert s == SCons.Node.executed, s + s = n2.get_state() + assert s == SCons.Node.executed, s + + + def test_make_ready_out_of_date(self): + """Test the Task.make_ready() method's list of out-of-date Nodes + """ + ood = [] + def TaskGen(tm, targets, top, node, ood=ood): + class MyTask(SCons.Taskmaster.Task): + def make_ready(self): + SCons.Taskmaster.Task.make_ready(self) + self.ood.extend(self.out_of_date) + t = MyTask(tm, targets, top, node) + t.ood = ood + return t + + n1 = Node("n1") + c2 = Node("c2") + c2._current_val = 1 + n3 = Node("n3") + c4 = Node("c4") + c4._current_val = 1 + a5 = Node("a5") + a5._current_val = 1 + a5.always_build = 1 + tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4, a5], + tasker = TaskGen) + + del ood[:] + t = tm.next_task() + assert ood == [n1], ood + + del ood[:] + t = tm.next_task() + assert ood == [], ood + + del ood[:] + t = tm.next_task() + assert ood == [n3], ood + + del ood[:] + t = tm.next_task() + assert ood == [], ood + + del ood[:] + t = tm.next_task() + assert ood == [a5], ood + + def test_make_ready_exception(self): + """Test handling exceptions from Task.make_ready() + """ + class MyTask(SCons.Taskmaster.Task): + def make_ready(self): + raise MyException, "from make_ready()" + + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster(targets = [n1], tasker = MyTask) + t = tm.next_task() + exc_type, exc_value, exc_tb = t.exception + assert exc_type == MyException, repr(exc_type) + assert str(exc_value) == "from make_ready()", exc_value + + + def test_make_ready_all(self): + """Test the make_ready_all() method""" + class MyTask(SCons.Taskmaster.Task): + make_ready = SCons.Taskmaster.Task.make_ready_all + + n1 = Node("n1") + c2 = Node("c2") + c2._current_val = 1 + n3 = Node("n3") + c4 = Node("c4") + c4._current_val = 1 + + tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4]) + + t = tm.next_task() + target = t.get_target() + assert target is n1, target + assert target.state == SCons.Node.executing, target.state + t = tm.next_task() + target = t.get_target() + assert target is c2, target + assert target.state == SCons.Node.up_to_date, target.state + t = tm.next_task() + target = t.get_target() + assert target is n3, target + assert target.state == SCons.Node.executing, target.state + t = tm.next_task() + target = t.get_target() + assert target is c4, target + assert target.state == SCons.Node.up_to_date, target.state + t = tm.next_task() + assert t is None + + n1 = Node("n1") + c2 = Node("c2") + n3 = Node("n3") + c4 = Node("c4") + + tm = SCons.Taskmaster.Taskmaster(targets = [n1, c2, n3, c4], + tasker = MyTask) + + t = tm.next_task() + target = t.get_target() + assert target is n1, target + assert target.state == SCons.Node.executing, target.state + t = tm.next_task() + target = t.get_target() + assert target is c2, target + assert target.state == SCons.Node.executing, target.state + t = tm.next_task() + target = t.get_target() + assert target is n3, target + assert target.state == SCons.Node.executing, target.state + t = tm.next_task() + target = t.get_target() + assert target is c4, target + assert target.state == SCons.Node.executing, target.state + t = tm.next_task() + assert t is None + + + def test_children_errors(self): + """Test errors when fetching the children of a node. + """ + class StopNode(Node): + def children(self): + raise SCons.Errors.StopError, "stop!" + class ExitNode(Node): + def children(self): + sys.exit(77) + + n1 = StopNode("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + t = tm.next_task() + exc_type, exc_value, exc_tb = t.exception + assert exc_type == SCons.Errors.StopError, repr(exc_type) + assert str(exc_value) == "stop!", exc_value + + n2 = ExitNode("n2") + tm = SCons.Taskmaster.Taskmaster([n2]) + t = tm.next_task() + exc_type, exc_value = t.exception + assert exc_type == SCons.Errors.ExplicitExit, repr(exc_type) + assert exc_value.node == n2, exc_value.node + assert exc_value.status == 77, exc_value.status + + def test_cycle_detection(self): + """Test detecting dependency cycles + """ + n1 = Node("n1") + n2 = Node("n2", [n1]) + n3 = Node("n3", [n2]) + n1.kids = [n3] + + tm = SCons.Taskmaster.Taskmaster([n3]) + try: + t = tm.next_task() + except SCons.Errors.UserError, e: + assert str(e) == "Dependency cycle: n3 -> n1 -> n2 -> n3", str(e) + else: + assert 'Did not catch expected UserError' + + def test_next_top_level_candidate(self): + """Test the next_top_level_candidate() method + """ + n1 = Node("n1") + n2 = Node("n2", [n1]) + n3 = Node("n3", [n2]) + + tm = SCons.Taskmaster.Taskmaster([n3]) + t = tm.next_task() + assert t.targets == [n1], t.targets + t.fail_stop() + assert t.targets == [n3], map(str, t.targets) + assert t.top == 1, t.top + + def test_stop(self): + """Test the stop() method + + Both default and overridden in a subclass. + """ + global built_text + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + + tm = SCons.Taskmaster.Taskmaster([n3]) + t = tm.next_task() + t.prepare() + t.execute() + assert built_text == "n1 built", built_text + t.executed() + t.postprocess() + assert built_text == "n1 built really", built_text + + tm.stop() + assert tm.next_task() is None + + class MyTM(SCons.Taskmaster.Taskmaster): + def stop(self): + global built_text + built_text = "MyTM.stop()" + SCons.Taskmaster.Taskmaster.stop(self) + + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + + built_text = None + tm = MyTM([n3]) + tm.next_task().execute() + assert built_text == "n1 built" + + tm.stop() + assert built_text == "MyTM.stop()" + assert tm.next_task() is None + + def test_executed(self): + """Test when a task has been executed + """ + global built_text + global visited_nodes + + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + t = tm.next_task() + built_text = "xxx" + visited_nodes = [] + n1.set_state(SCons.Node.executing) + + t.executed() + + s = n1.get_state() + assert s == SCons.Node.executed, s + assert built_text == "xxx really", built_text + assert visited_nodes == ['n1'], visited_nodes + + n2 = Node("n2") + tm = SCons.Taskmaster.Taskmaster([n2]) + t = tm.next_task() + built_text = "should_not_change" + visited_nodes = [] + n2.set_state(None) + + t.executed() + + s = n2.get_state() + assert s is None, s + assert built_text == "should_not_change", built_text + assert visited_nodes == ['n2'], visited_nodes + + n3 = Node("n3") + n4 = Node("n4") + n3.targets = [n3, n4] + tm = SCons.Taskmaster.Taskmaster([n3]) + t = tm.next_task() + visited_nodes = [] + n3.set_state(SCons.Node.up_to_date) + n4.set_state(SCons.Node.executing) + + t.executed() + + s = n3.get_state() + assert s == SCons.Node.up_to_date, s + s = n4.get_state() + assert s == SCons.Node.executed, s + assert visited_nodes == ['n3', 'n4'], visited_nodes + + def test_prepare(self): + """Test preparation of multiple Nodes for a task + """ + n1 = Node("n1") + n2 = Node("n2") + tm = SCons.Taskmaster.Taskmaster([n1, n2]) + t = tm.next_task() + # This next line is moderately bogus. We're just reaching + # in and setting the targets for this task to an array. The + # "right" way to do this would be to have the next_task() call + # set it up by having something that approximates a real Builder + # return this list--but that's more work than is probably + # warranted right now. + n1.get_executor().targets = [n1, n2] + t.prepare() + assert n1.prepared + assert n2.prepared + + n3 = Node("n3") + n4 = Node("n4") + tm = SCons.Taskmaster.Taskmaster([n3, n4]) + t = tm.next_task() + # More bogus reaching in and setting the targets. + n3.set_state(SCons.Node.up_to_date) + n3.get_executor().targets = [n3, n4] + t.prepare() + assert n3.prepared + assert n4.prepared + + # If the Node has had an exception recorded while it was getting + # prepared, then prepare() should raise that exception. + class MyException(Exception): + pass + + built_text = None + n5 = Node("n5") + tm = SCons.Taskmaster.Taskmaster([n5]) + t = tm.next_task() + t.exception_set((MyException, "exception value")) + exc_caught = None + try: + t.prepare() + except MyException, e: + exc_caught = 1 + except: + pass + assert exc_caught, "did not catch expected MyException" + assert str(e) == "exception value", e + assert built_text is None, built_text + + # Regression test, make sure we prepare not only + # all targets, but their side effects as well. + n6 = Node("n6") + n7 = Node("n7") + n8 = Node("n8") + n9 = Node("n9") + n10 = Node("n10") + + n6.side_effects = [ n8 ] + n7.side_effects = [ n9, n10 ] + + tm = SCons.Taskmaster.Taskmaster([n6, n7]) + t = tm.next_task() + # More bogus reaching in and setting the targets. + n6.get_executor().targets = [n6, n7] + t.prepare() + assert n6.prepared + assert n7.prepared + assert n8.prepared + assert n9.prepared + assert n10.prepared + + # Make sure we call an Executor's prepare() method. + class ExceptionExecutor: + def prepare(self): + raise Exception, "Executor.prepare() exception" + def get_all_targets(self): + return self.nodes + def get_all_children(self): + result = [] + for node in self.nodes: + result.extend(node.children()) + return result + def get_all_prerequisites(self): + return [] + def get_action_side_effects(self): + return [] + + n11 = Node("n11") + n11.executor = ExceptionExecutor() + n11.executor.nodes = [n11] + tm = SCons.Taskmaster.Taskmaster([n11]) + t = tm.next_task() + try: + t.prepare() + except Exception, e: + assert str(e) == "Executor.prepare() exception", e + else: + raise AssertionError, "did not catch expected exception" + + def test_execute(self): + """Test executing a task + """ + global built_text + global cache_text + + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + t = tm.next_task() + t.execute() + assert built_text == "n1 built", built_text + + def raise_UserError(): + raise SCons.Errors.UserError + n2 = Node("n2") + n2.build = raise_UserError + tm = SCons.Taskmaster.Taskmaster([n2]) + t = tm.next_task() + try: + t.execute() + except SCons.Errors.UserError: + pass + else: + raise TestFailed, "did not catch expected UserError" + + def raise_BuildError(): + raise SCons.Errors.BuildError + n3 = Node("n3") + n3.build = raise_BuildError + tm = SCons.Taskmaster.Taskmaster([n3]) + t = tm.next_task() + try: + t.execute() + except SCons.Errors.BuildError: + pass + else: + raise TestFailed, "did not catch expected BuildError" + + # On a generic (non-BuildError) exception from a Builder, + # the target should throw a BuildError exception with the + # args set to the exception value, instance, and traceback. + def raise_OtherError(): + raise OtherError + n4 = Node("n4") + n4.build = raise_OtherError + tm = SCons.Taskmaster.Taskmaster([n4]) + t = tm.next_task() + try: + t.execute() + except SCons.Errors.BuildError, e: + assert e.node == n4, e.node + assert e.errstr == "OtherError : ", e.errstr + assert len(e.exc_info) == 3, e.exc_info + exc_traceback = sys.exc_info()[2] + assert type(e.exc_info[2]) == type(exc_traceback), e.exc_info[2] + else: + raise TestFailed, "did not catch expected BuildError" + + built_text = None + cache_text = [] + n5 = Node("n5") + n6 = Node("n6") + n6.cached = 1 + tm = SCons.Taskmaster.Taskmaster([n5]) + t = tm.next_task() + # This next line is moderately bogus. We're just reaching + # in and setting the targets for this task to an array. The + # "right" way to do this would be to have the next_task() call + # set it up by having something that approximates a real Builder + # return this list--but that's more work than is probably + # warranted right now. + t.targets = [n5, n6] + t.execute() + assert built_text == "n5 built", built_text + assert cache_text == [], cache_text + + built_text = None + cache_text = [] + n7 = Node("n7") + n8 = Node("n8") + n7.cached = 1 + n8.cached = 1 + tm = SCons.Taskmaster.Taskmaster([n7]) + t = tm.next_task() + # This next line is moderately bogus. We're just reaching + # in and setting the targets for this task to an array. The + # "right" way to do this would be to have the next_task() call + # set it up by having something that approximates a real Builder + # return this list--but that's more work than is probably + # warranted right now. + t.targets = [n7, n8] + t.execute() + assert built_text is None, built_text + assert cache_text == ["n7 retrieved", "n8 retrieved"], cache_text + + def test_exception(self): + """Test generic Taskmaster exception handling + + """ + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + t = tm.next_task() + + t.exception_set((1, 2)) + exc_type, exc_value = t.exception + assert exc_type == 1, exc_type + assert exc_value == 2, exc_value + + t.exception_set(3) + assert t.exception == 3 + + try: 1/0 + except: pass + t.exception_set(None) + exc_type, exc_value, exc_tb = t.exception + assert exc_type is ZeroDivisionError, exc_type + exception_values = [ + "integer division or modulo", + "integer division or modulo by zero", + ] + assert str(exc_value) in exception_values, exc_value + + class Exception1(Exception): + pass + + t.exception_set((Exception1, None)) + try: + t.exception_raise() + except: + exc_type, exc_value = sys.exc_info()[:2] + assert exc_type == Exception1, exc_type + assert str(exc_value) == '', exc_value + else: + assert 0, "did not catch expected exception" + + class Exception2(Exception): + pass + + t.exception_set((Exception2, "xyzzy")) + try: + t.exception_raise() + except: + exc_type, exc_value = sys.exc_info()[:2] + assert exc_type == Exception2, exc_type + assert str(exc_value) == "xyzzy", exc_value + else: + assert 0, "did not catch expected exception" + + class Exception3(Exception): + pass + + try: + 1/0 + except: + tb = sys.exc_info()[2] + t.exception_set((Exception3, "arg", tb)) + try: + t.exception_raise() + except: + exc_type, exc_value, exc_tb = sys.exc_info() + assert exc_type == Exception3, exc_type + assert str(exc_value) == "arg", exc_value + import traceback + x = traceback.extract_tb(tb)[-1] + y = traceback.extract_tb(exc_tb)[-1] + assert x == y, "x = %s, y = %s" % (x, y) + else: + assert 0, "did not catch expected exception" + + def test_postprocess(self): + """Test postprocessing targets to give them a chance to clean up + """ + n1 = Node("n1") + tm = SCons.Taskmaster.Taskmaster([n1]) + + t = tm.next_task() + assert not n1.postprocessed + t.postprocess() + assert n1.postprocessed + + n2 = Node("n2") + n3 = Node("n3") + tm = SCons.Taskmaster.Taskmaster([n2, n3]) + + assert not n2.postprocessed + assert not n3.postprocessed + t = tm.next_task() + t.postprocess() + assert n2.postprocessed + assert not n3.postprocessed + t = tm.next_task() + t.postprocess() + assert n2.postprocessed + assert n3.postprocessed + + def test_trace(self): + """Test Taskmaster tracing + """ + import StringIO + + trace = StringIO.StringIO() + n1 = Node("n1") + n2 = Node("n2") + n3 = Node("n3", [n1, n2]) + tm = SCons.Taskmaster.Taskmaster([n1, n1, n3], trace=trace) + t = tm.next_task() + t.prepare() + t.execute() + t.postprocess() + n1.set_state(SCons.Node.executed) + t = tm.next_task() + t.prepare() + t.execute() + t.postprocess() + n2.set_state(SCons.Node.executed) + t = tm.next_task() + t.prepare() + t.execute() + t.postprocess() + t = tm.next_task() + assert t is None + + value = trace.getvalue() + expect = """\ + +Taskmaster: Looking for a node to evaluate +Taskmaster: Considering node and its children: +Taskmaster: Evaluating + +Task.make_ready_current(): node +Task.prepare(): node +Task.execute(): node +Task.postprocess(): node + +Taskmaster: Looking for a node to evaluate +Taskmaster: Considering node and its children: +Taskmaster: already handled (executed) +Taskmaster: Considering node and its children: +Taskmaster: +Taskmaster: +Taskmaster: adjusted ref count: , child 'n2' +Taskmaster: Considering node and its children: +Taskmaster: Evaluating + +Task.make_ready_current(): node +Task.prepare(): node +Task.execute(): node +Task.postprocess(): node +Task.postprocess(): removing +Task.postprocess(): adjusted parent ref count + +Taskmaster: Looking for a node to evaluate +Taskmaster: Considering node and its children: +Taskmaster: +Taskmaster: +Taskmaster: Evaluating + +Task.make_ready_current(): node +Task.prepare(): node +Task.execute(): node +Task.postprocess(): node + +Taskmaster: Looking for a node to evaluate +Taskmaster: No candidate anymore. + +""" + assert value == expect, value + + + +if __name__ == "__main__": + suite = unittest.makeSuite(TaskmasterTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/386asm.py b/src/engine/SCons/Tool/386asm.py new file mode 100644 index 0000000..7b026e4 --- /dev/null +++ b/src/engine/SCons/Tool/386asm.py @@ -0,0 +1,61 @@ +"""SCons.Tool.386asm + +Tool specification for the 386ASM assembler for the Phar Lap ETS embedded +operating system. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/386asm.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.PharLapCommon import addPharLapPaths +import SCons.Util + +as_module = __import__('as', globals(), locals(), []) + +def generate(env): + """Add Builders and construction variables for ar to an Environment.""" + as_module.generate(env) + + env['AS'] = '386asm' + env['ASFLAGS'] = SCons.Util.CLVar('') + env['ASPPFLAGS'] = '$ASFLAGS' + env['ASCOM'] = '$AS $ASFLAGS $SOURCES -o $TARGET' + env['ASPPCOM'] = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $SOURCES -o $TARGET' + + addPharLapPaths(env) + +def exists(env): + return env.Detect('386asm') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/386asm.xml b/src/engine/SCons/Tool/386asm.xml new file mode 100644 index 0000000..ed48820 --- /dev/null +++ b/src/engine/SCons/Tool/386asm.xml @@ -0,0 +1,25 @@ + + + +Sets construction variables for the 386ASM assembler +for the Phar Lap ETS embedded operating system. + + +AS +ASFLAGS +ASPPFLAGS +ASCOM +ASPPCOM + + +CC +CPPFLAGS +_CPPDEFFLAGS +_CPPINCFLAGS + + diff --git a/src/engine/SCons/Tool/BitKeeper.py b/src/engine/SCons/Tool/BitKeeper.py new file mode 100644 index 0000000..6830ca9 --- /dev/null +++ b/src/engine/SCons/Tool/BitKeeper.py @@ -0,0 +1,65 @@ +"""SCons.Tool.BitKeeper.py + +Tool-specific initialization for the BitKeeper source code control +system. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/BitKeeper.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Util + +def generate(env): + """Add a Builder factory function and construction variables for + BitKeeper to an Environment.""" + + def BitKeeperFactory(env=env): + """ """ + act = SCons.Action.Action("$BITKEEPERCOM", "$BITKEEPERCOMSTR") + return SCons.Builder.Builder(action = act, env = env) + + #setattr(env, 'BitKeeper', BitKeeperFactory) + env.BitKeeper = BitKeeperFactory + + env['BITKEEPER'] = 'bk' + env['BITKEEPERGET'] = '$BITKEEPER get' + env['BITKEEPERGETFLAGS'] = SCons.Util.CLVar('') + env['BITKEEPERCOM'] = '$BITKEEPERGET $BITKEEPERGETFLAGS $TARGET' + +def exists(env): + return env.Detect('bk') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/BitKeeper.xml b/src/engine/SCons/Tool/BitKeeper.xml new file mode 100644 index 0000000..4eb7a32 --- /dev/null +++ b/src/engine/SCons/Tool/BitKeeper.xml @@ -0,0 +1,58 @@ + + + +Sets construction variables for the BitKeeper +source code control system. + + +BITKEEPER +BITKEEPERGET +BITKEEPERGETFLAGS +BITKEEPERCOM + + +BITKEEPERCOMSTR + + + + + +The BitKeeper executable. + + + + + +The command line for +fetching source files using BitKeeper. + + + + + +The string displayed when fetching +a source file using BitKeeper. +If this is not set, then &cv-link-BITKEEPERCOM; +(the command line) is displayed. + + + + + +The command (&cv-link-BITKEEPER;) and subcommand +for fetching source files using BitKeeper. + + + + + +Options that are passed to the BitKeeper +get +subcommand. + + diff --git a/src/engine/SCons/Tool/CVS.py b/src/engine/SCons/Tool/CVS.py new file mode 100644 index 0000000..f2334c8 --- /dev/null +++ b/src/engine/SCons/Tool/CVS.py @@ -0,0 +1,73 @@ +"""SCons.Tool.CVS.py + +Tool-specific initialization for CVS. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/CVS.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Util + +def generate(env): + """Add a Builder factory function and construction variables for + CVS to an Environment.""" + + def CVSFactory(repos, module='', env=env): + """ """ + # fail if repos is not an absolute path name? + if module != '': + # Don't use os.path.join() because the name we fetch might + # be across a network and must use POSIX slashes as separators. + module = module + '/' + env['CVSCOM'] = '$CVS $CVSFLAGS co $CVSCOFLAGS -d ${TARGET.dir} $CVSMODULE${TARGET.posix}' + act = SCons.Action.Action('$CVSCOM', '$CVSCOMSTR') + return SCons.Builder.Builder(action = act, + env = env, + CVSREPOSITORY = repos, + CVSMODULE = module) + + #setattr(env, 'CVS', CVSFactory) + env.CVS = CVSFactory + + env['CVS'] = 'cvs' + env['CVSFLAGS'] = SCons.Util.CLVar('-d $CVSREPOSITORY') + env['CVSCOFLAGS'] = SCons.Util.CLVar('') + env['CVSCOM'] = '$CVS $CVSFLAGS co $CVSCOFLAGS ${TARGET.posix}' + +def exists(env): + return env.Detect('cvs') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/CVS.xml b/src/engine/SCons/Tool/CVS.xml new file mode 100644 index 0000000..fdb5360 --- /dev/null +++ b/src/engine/SCons/Tool/CVS.xml @@ -0,0 +1,66 @@ + + + +Sets construction variables for the CVS source code +management system. + + +CVS +CVSCOM +CVSFLAGS +CVSCOFLAGS + + +CVSCOMSTR + + + + + +The CVS executable. + + + + + +Options that are passed to the CVS checkout subcommand. + + + + + +The command line used to +fetch source files from a CVS repository. + + + + + +The string displayed when fetching +a source file from a CVS repository. +If this is not set, then &cv-link-CVSCOM; +(the command line) is displayed. + + + + + +General options that are passed to CVS. +By default, this is set to +-d $CVSREPOSITORY +to specify from where the files must be fetched. + + + + + +The path to the CVS repository. +This is referenced in the default +&cv-link-CVSFLAGS; value. + + diff --git a/src/engine/SCons/Tool/FortranCommon.py b/src/engine/SCons/Tool/FortranCommon.py new file mode 100644 index 0000000..784b3fa --- /dev/null +++ b/src/engine/SCons/Tool/FortranCommon.py @@ -0,0 +1,247 @@ +"""SCons.Tool.FortranCommon + +Stuff for processing Fortran, common to all fortran dialects. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/FortranCommon.py 4577 2009/12/27 19:44:43 scons" + +import re +import string +import os.path + +import SCons.Action +import SCons.Defaults +import SCons.Scanner.Fortran +import SCons.Tool +import SCons.Util + +def isfortran(env, source): + """Return 1 if any of code in source has fortran files in it, 0 + otherwise.""" + try: + fsuffixes = env['FORTRANSUFFIXES'] + except KeyError: + # If no FORTRANSUFFIXES, no fortran tool, so there is no need to look + # for fortran sources. + return 0 + + if not source: + # Source might be None for unusual cases like SConf. + return 0 + for s in source: + if s.sources: + ext = os.path.splitext(str(s.sources[0]))[1] + if ext in fsuffixes: + return 1 + return 0 + +def _fortranEmitter(target, source, env): + node = source[0].rfile() + if not node.exists() and not node.is_derived(): + print "Could not locate " + str(node.name) + return ([], []) + mod_regex = """(?i)^\s*MODULE\s+(?!PROCEDURE)(\w+)""" + cre = re.compile(mod_regex,re.M) + # Retrieve all USE'd module names + modules = cre.findall(node.get_text_contents()) + # Remove unique items from the list + modules = SCons.Util.unique(modules) + # Convert module name to a .mod filename + suffix = env.subst('$FORTRANMODSUFFIX', target=target, source=source) + moddir = env.subst('$FORTRANMODDIR', target=target, source=source) + modules = map(lambda x, s=suffix: string.lower(x) + s, modules) + for m in modules: + target.append(env.fs.File(m, moddir)) + return (target, source) + +def FortranEmitter(target, source, env): + target, source = _fortranEmitter(target, source, env) + return SCons.Defaults.StaticObjectEmitter(target, source, env) + +def ShFortranEmitter(target, source, env): + target, source = _fortranEmitter(target, source, env) + return SCons.Defaults.SharedObjectEmitter(target, source, env) + +def ComputeFortranSuffixes(suffixes, ppsuffixes): + """suffixes are fortran source files, and ppsuffixes the ones to be + pre-processed. Both should be sequences, not strings.""" + assert len(suffixes) > 0 + s = suffixes[0] + sup = string.upper(s) + upper_suffixes = map(string.upper, suffixes) + if SCons.Util.case_sensitive_suffixes(s, sup): + ppsuffixes.extend(upper_suffixes) + else: + suffixes.extend(upper_suffixes) + +def CreateDialectActions(dialect): + """Create dialect specific actions.""" + CompAction = SCons.Action.Action('$%sCOM ' % dialect, '$%sCOMSTR' % dialect) + CompPPAction = SCons.Action.Action('$%sPPCOM ' % dialect, '$%sPPCOMSTR' % dialect) + ShCompAction = SCons.Action.Action('$SH%sCOM ' % dialect, '$SH%sCOMSTR' % dialect) + ShCompPPAction = SCons.Action.Action('$SH%sPPCOM ' % dialect, '$SH%sPPCOMSTR' % dialect) + + return CompAction, CompPPAction, ShCompAction, ShCompPPAction + +def DialectAddToEnv(env, dialect, suffixes, ppsuffixes, support_module = 0): + """Add dialect specific construction variables.""" + ComputeFortranSuffixes(suffixes, ppsuffixes) + + fscan = SCons.Scanner.Fortran.FortranScan("%sPATH" % dialect) + + for suffix in suffixes + ppsuffixes: + SCons.Tool.SourceFileScanner.add_scanner(suffix, fscan) + + env.AppendUnique(FORTRANSUFFIXES = suffixes + ppsuffixes) + + compaction, compppaction, shcompaction, shcompppaction = \ + CreateDialectActions(dialect) + + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in suffixes: + static_obj.add_action(suffix, compaction) + shared_obj.add_action(suffix, shcompaction) + static_obj.add_emitter(suffix, FortranEmitter) + shared_obj.add_emitter(suffix, ShFortranEmitter) + + for suffix in ppsuffixes: + static_obj.add_action(suffix, compppaction) + shared_obj.add_action(suffix, shcompppaction) + static_obj.add_emitter(suffix, FortranEmitter) + shared_obj.add_emitter(suffix, ShFortranEmitter) + + if not env.has_key('%sFLAGS' % dialect): + env['%sFLAGS' % dialect] = SCons.Util.CLVar('') + + if not env.has_key('SH%sFLAGS' % dialect): + env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS' % dialect) + + # If a tool does not define fortran prefix/suffix for include path, use C ones + if not env.has_key('INC%sPREFIX' % dialect): + env['INC%sPREFIX' % dialect] = '$INCPREFIX' + + if not env.has_key('INC%sSUFFIX' % dialect): + env['INC%sSUFFIX' % dialect] = '$INCSUFFIX' + + env['_%sINCFLAGS' % dialect] = '$( ${_concat(INC%sPREFIX, %sPATH, INC%sSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' % (dialect, dialect, dialect) + + if support_module == 1: + env['%sCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) + env['%sPPCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) + env['SH%sCOM' % dialect] = '$SH%s -o $TARGET -c $SH%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) + env['SH%sPPCOM' % dialect] = '$SH%s -o $TARGET -c $SH%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect) + else: + env['%sCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect) + env['%sPPCOM' % dialect] = '$%s -o $TARGET -c $%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect) + env['SH%sCOM' % dialect] = '$SH%s -o $TARGET -c $SH%sFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect) + env['SH%sPPCOM' % dialect] = '$SH%s -o $TARGET -c $SH%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect) + +def add_fortran_to_env(env): + """Add Builders and construction variables for Fortran to an Environment.""" + try: + FortranSuffixes = env['FORTRANFILESUFFIXES'] + except KeyError: + FortranSuffixes = ['.f', '.for', '.ftn'] + + #print "Adding %s to fortran suffixes" % FortranSuffixes + try: + FortranPPSuffixes = env['FORTRANPPFILESUFFIXES'] + except KeyError: + FortranPPSuffixes = ['.fpp', '.FPP'] + + DialectAddToEnv(env, "FORTRAN", FortranSuffixes, + FortranPPSuffixes, support_module = 1) + + env['FORTRANMODPREFIX'] = '' # like $LIBPREFIX + env['FORTRANMODSUFFIX'] = '.mod' # like $LIBSUFFIX + + env['FORTRANMODDIR'] = '' # where the compiler should place .mod files + env['FORTRANMODDIRPREFIX'] = '' # some prefix to $FORTRANMODDIR - similar to $INCPREFIX + env['FORTRANMODDIRSUFFIX'] = '' # some suffix to $FORTRANMODDIR - similar to $INCSUFFIX + env['_FORTRANMODFLAG'] = '$( ${_concat(FORTRANMODDIRPREFIX, FORTRANMODDIR, FORTRANMODDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' + +def add_f77_to_env(env): + """Add Builders and construction variables for f77 to an Environment.""" + try: + F77Suffixes = env['F77FILESUFFIXES'] + except KeyError: + F77Suffixes = ['.f77'] + + #print "Adding %s to f77 suffixes" % F77Suffixes + try: + F77PPSuffixes = env['F77PPFILESUFFIXES'] + except KeyError: + F77PPSuffixes = [] + + DialectAddToEnv(env, "F77", F77Suffixes, F77PPSuffixes) + +def add_f90_to_env(env): + """Add Builders and construction variables for f90 to an Environment.""" + try: + F90Suffixes = env['F90FILESUFFIXES'] + except KeyError: + F90Suffixes = ['.f90'] + + #print "Adding %s to f90 suffixes" % F90Suffixes + try: + F90PPSuffixes = env['F90PPFILESUFFIXES'] + except KeyError: + F90PPSuffixes = [] + + DialectAddToEnv(env, "F90", F90Suffixes, F90PPSuffixes, + support_module = 1) + +def add_f95_to_env(env): + """Add Builders and construction variables for f95 to an Environment.""" + try: + F95Suffixes = env['F95FILESUFFIXES'] + except KeyError: + F95Suffixes = ['.f95'] + + #print "Adding %s to f95 suffixes" % F95Suffixes + try: + F95PPSuffixes = env['F95PPFILESUFFIXES'] + except KeyError: + F95PPSuffixes = [] + + DialectAddToEnv(env, "F95", F95Suffixes, F95PPSuffixes, + support_module = 1) + +def add_all_to_env(env): + """Add builders and construction variables for all supported fortran + dialects.""" + add_fortran_to_env(env) + add_f77_to_env(env) + add_f90_to_env(env) + add_f95_to_env(env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/JavaCommon.py b/src/engine/SCons/Tool/JavaCommon.py new file mode 100644 index 0000000..44a6091 --- /dev/null +++ b/src/engine/SCons/Tool/JavaCommon.py @@ -0,0 +1,324 @@ +"""SCons.Tool.JavaCommon + +Stuff for processing Java. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/JavaCommon.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import re +import string + +java_parsing = 1 + +default_java_version = '1.4' + +if java_parsing: + # Parse Java files for class names. + # + # This is a really cool parser from Charles Crain + # that finds appropriate class names in Java source. + + # A regular expression that will find, in a java file: + # newlines; + # double-backslashes; + # a single-line comment "//"; + # single or double quotes preceeded by a backslash; + # single quotes, double quotes, open or close braces, semi-colons, + # periods, open or close parentheses; + # floating-point numbers; + # any alphanumeric token (keyword, class name, specifier); + # any alphanumeric token surrounded by angle brackets (generics); + # the multi-line comment begin and end tokens /* and */; + # array declarations "[]". + _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"\{\}\;\.\(\)]|' + + r'\d*\.\d*|[A-Za-z_][\w\$\.]*|<[A-Za-z_]\w+>|' + + r'/\*|\*/|\[\])') + + class OuterState: + """The initial state for parsing a Java file for classes, + interfaces, and anonymous inner classes.""" + def __init__(self, version=default_java_version): + + if not version in ('1.1', '1.2', '1.3','1.4', '1.5', '1.6', + '5', '6'): + msg = "Java version %s not supported" % version + raise NotImplementedError, msg + + self.version = version + self.listClasses = [] + self.listOutputs = [] + self.stackBrackets = [] + self.brackets = 0 + self.nextAnon = 1 + self.localClasses = [] + self.stackAnonClassBrackets = [] + self.anonStacksStack = [[0]] + self.package = None + + def trace(self): + pass + + def __getClassState(self): + try: + return self.classState + except AttributeError: + ret = ClassState(self) + self.classState = ret + return ret + + def __getPackageState(self): + try: + return self.packageState + except AttributeError: + ret = PackageState(self) + self.packageState = ret + return ret + + def __getAnonClassState(self): + try: + return self.anonState + except AttributeError: + self.outer_state = self + ret = SkipState(1, AnonClassState(self)) + self.anonState = ret + return ret + + def __getSkipState(self): + try: + return self.skipState + except AttributeError: + ret = SkipState(1, self) + self.skipState = ret + return ret + + def __getAnonStack(self): + return self.anonStacksStack[-1] + + def openBracket(self): + self.brackets = self.brackets + 1 + + def closeBracket(self): + self.brackets = self.brackets - 1 + if len(self.stackBrackets) and \ + self.brackets == self.stackBrackets[-1]: + self.listOutputs.append(string.join(self.listClasses, '$')) + self.localClasses.pop() + self.listClasses.pop() + self.anonStacksStack.pop() + self.stackBrackets.pop() + if len(self.stackAnonClassBrackets) and \ + self.brackets == self.stackAnonClassBrackets[-1]: + self.__getAnonStack().pop() + self.stackAnonClassBrackets.pop() + + def parseToken(self, token): + if token[:2] == '//': + return IgnoreState('\n', self) + elif token == '/*': + return IgnoreState('*/', self) + elif token == '{': + self.openBracket() + elif token == '}': + self.closeBracket() + elif token in [ '"', "'" ]: + return IgnoreState(token, self) + elif token == "new": + # anonymous inner class + if len(self.listClasses) > 0: + return self.__getAnonClassState() + return self.__getSkipState() # Skip the class name + elif token in ['class', 'interface', 'enum']: + if len(self.listClasses) == 0: + self.nextAnon = 1 + self.stackBrackets.append(self.brackets) + return self.__getClassState() + elif token == 'package': + return self.__getPackageState() + elif token == '.': + # Skip the attribute, it might be named "class", in which + # case we don't want to treat the following token as + # an inner class name... + return self.__getSkipState() + return self + + def addAnonClass(self): + """Add an anonymous inner class""" + if self.version in ('1.1', '1.2', '1.3', '1.4'): + clazz = self.listClasses[0] + self.listOutputs.append('%s$%d' % (clazz, self.nextAnon)) + elif self.version in ('1.5', '1.6', '5', '6'): + self.stackAnonClassBrackets.append(self.brackets) + className = [] + className.extend(self.listClasses) + self.__getAnonStack()[-1] = self.__getAnonStack()[-1] + 1 + for anon in self.__getAnonStack(): + className.append(str(anon)) + self.listOutputs.append(string.join(className, '$')) + + self.nextAnon = self.nextAnon + 1 + self.__getAnonStack().append(0) + + def setPackage(self, package): + self.package = package + + class AnonClassState: + """A state that looks for anonymous inner classes.""" + def __init__(self, old_state): + # outer_state is always an instance of OuterState + self.outer_state = old_state.outer_state + self.old_state = old_state + self.brace_level = 0 + def parseToken(self, token): + # This is an anonymous class if and only if the next + # non-whitespace token is a bracket. Everything between + # braces should be parsed as normal java code. + if token[:2] == '//': + return IgnoreState('\n', self) + elif token == '/*': + return IgnoreState('*/', self) + elif token == '\n': + return self + elif token[0] == '<' and token[-1] == '>': + return self + elif token == '(': + self.brace_level = self.brace_level + 1 + return self + if self.brace_level > 0: + if token == 'new': + # look further for anonymous inner class + return SkipState(1, AnonClassState(self)) + elif token in [ '"', "'" ]: + return IgnoreState(token, self) + elif token == ')': + self.brace_level = self.brace_level - 1 + return self + if token == '{': + self.outer_state.addAnonClass() + return self.old_state.parseToken(token) + + class SkipState: + """A state that will skip a specified number of tokens before + reverting to the previous state.""" + def __init__(self, tokens_to_skip, old_state): + self.tokens_to_skip = tokens_to_skip + self.old_state = old_state + def parseToken(self, token): + self.tokens_to_skip = self.tokens_to_skip - 1 + if self.tokens_to_skip < 1: + return self.old_state + return self + + class ClassState: + """A state we go into when we hit a class or interface keyword.""" + def __init__(self, outer_state): + # outer_state is always an instance of OuterState + self.outer_state = outer_state + def parseToken(self, token): + # the next non-whitespace token should be the name of the class + if token == '\n': + return self + # If that's an inner class which is declared in a method, it + # requires an index prepended to the class-name, e.g. + # 'Foo$1Inner' (Tigris Issue 2087) + if self.outer_state.localClasses and \ + self.outer_state.stackBrackets[-1] > \ + self.outer_state.stackBrackets[-2]+1: + locals = self.outer_state.localClasses[-1] + try: + idx = locals[token] + locals[token] = locals[token]+1 + except KeyError: + locals[token] = 1 + token = str(locals[token]) + token + self.outer_state.localClasses.append({}) + self.outer_state.listClasses.append(token) + self.outer_state.anonStacksStack.append([0]) + return self.outer_state + + class IgnoreState: + """A state that will ignore all tokens until it gets to a + specified token.""" + def __init__(self, ignore_until, old_state): + self.ignore_until = ignore_until + self.old_state = old_state + def parseToken(self, token): + if self.ignore_until == token: + return self.old_state + return self + + class PackageState: + """The state we enter when we encounter the package keyword. + We assume the next token will be the package name.""" + def __init__(self, outer_state): + # outer_state is always an instance of OuterState + self.outer_state = outer_state + def parseToken(self, token): + self.outer_state.setPackage(token) + return self.outer_state + + def parse_java_file(fn, version=default_java_version): + return parse_java(open(fn, 'r').read(), version) + + def parse_java(contents, version=default_java_version, trace=None): + """Parse a .java file and return a double of package directory, + plus a list of .class files that compiling that .java file will + produce""" + package = None + initial = OuterState(version) + currstate = initial + for token in _reToken.findall(contents): + # The regex produces a bunch of groups, but only one will + # have anything in it. + currstate = currstate.parseToken(token) + if trace: trace(token, currstate) + if initial.package: + package = string.replace(initial.package, '.', os.sep) + return (package, initial.listOutputs) + +else: + # Don't actually parse Java files for class names. + # + # We might make this a configurable option in the future if + # Java-file parsing takes too long (although it shouldn't relative + # to how long the Java compiler itself seems to take...). + + def parse_java_file(fn): + """ "Parse" a .java file. + + This actually just splits the file name, so the assumption here + is that the file name matches the public class name, and that + the path to the file is the same as the package name. + """ + return os.path.split(file) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/JavaCommonTests.py b/src/engine/SCons/Tool/JavaCommonTests.py new file mode 100644 index 0000000..da44639 --- /dev/null +++ b/src/engine/SCons/Tool/JavaCommonTests.py @@ -0,0 +1,580 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/JavaCommonTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import sys +import unittest + +import SCons.Tool.JavaCommon + + +# Adding trace=trace to any of the parse_jave() calls below will cause +# the parser to spit out trace messages of the tokens it sees and the +# attendant transitions. + +def trace(token, newstate): + from SCons.Debug import Trace + statename = newstate.__class__.__name__ + Trace('token = %s, state = %s\n' % (repr(token), statename)) + +class parse_javaTestCase(unittest.TestCase): + + def test_bare_bones(self): + """Test a bare-bones class""" + + input = """\ +package com.sub.bar; + +public class Foo +{ + + public static void main(String[] args) + { + + /* This tests a former bug where strings would eat later code. */ + String hello1 = new String("Hello, world!"); + + } + +} +""" + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir == os.path.join('com', 'sub', 'bar'), pkg_dir + assert classes == ['Foo'], classes + + + + def test_dollar_sign(self): + """Test class names with $ in them""" + + input = """\ +public class BadDep { + public void new$rand () {} +} +""" + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir is None, pkg_dir + assert classes == ['BadDep'], classes + + + + def test_inner_classes(self): + """Test parsing various forms of inner classes""" + + input = """\ +class Empty { +} + +interface Listener { + public void execute(); +} + +public +class +Test implements Listener { + class Inner { + void go() { + use(new Listener() { + public void execute() { + System.out.println("In Inner"); + } + }); + } + String s1 = "class A"; + String s2 = "new Listener() { }"; + /* class B */ + /* new Listener() { } */ + } + + class Inner2 { + Inner2() { Listener l = new Listener(); } + } + + /* Make sure this class doesn't get interpreted as an inner class of the previous one, when "new" is used in the previous class. */ + class Inner3 { + + } + + public static void main(String[] args) { + new Test().run(); + } + + void run() { + use(new Listener() { + public void execute() { + use(new Listener( ) { + public void execute() { + System.out.println("Inside execute()"); + } + }); + } + }); + + new Inner().go(); + } + + void use(Listener l) { + l.execute(); + } +} + +class Private { + void run() { + new Listener() { + public void execute() { + } + }; + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4') + assert pkg_dir is None, pkg_dir + expect = [ + 'Empty', + 'Listener', + 'Test$1', + 'Test$Inner', + 'Test$Inner2', + 'Test$Inner3', + 'Test$2', + 'Test$3', + 'Test', + 'Private$1', + 'Private', + ] + assert classes == expect, classes + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5') + assert pkg_dir is None, pkg_dir + expect = [ + 'Empty', + 'Listener', + 'Test$Inner$1', + 'Test$Inner', + 'Test$Inner2', + 'Test$Inner3', + 'Test$1', + 'Test$1$1', + 'Test', + 'Private$1', + 'Private', + ] + assert classes == expect, (expect, classes) + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '5') + assert pkg_dir is None, pkg_dir + expect = [ + 'Empty', + 'Listener', + 'Test$Inner$1', + 'Test$Inner', + 'Test$Inner2', + 'Test$Inner3', + 'Test$1', + 'Test$1$1', + 'Test', + 'Private$1', + 'Private', + ] + assert classes == expect, (expect, classes) + + + + def test_comments(self): + """Test a class with comments""" + + input = """\ +package com.sub.foo; + +import java.rmi.Naming; +import java.rmi.RemoteException; +import java.rmi.RMISecurityManager; +import java.rmi.server.UnicastRemoteObject; + +public class Example1 extends UnicastRemoteObject implements Hello { + + public Example1() throws RemoteException { + super(); + } + + public String sayHello() { + return "Hello World!"; + } + + public static void main(String args[]) { + if (System.getSecurityManager() == null) { + System.setSecurityManager(new RMISecurityManager()); + } + // a comment + try { + Example1 obj = new Example1(); + + Naming.rebind("//myhost/HelloServer", obj); + + System.out.println("HelloServer bound in registry"); + } catch (Exception e) { + System.out.println("Example1 err: " + e.getMessage()); + e.printStackTrace(); + } + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir == os.path.join('com', 'sub', 'foo'), pkg_dir + assert classes == ['Example1'], classes + + + def test_arrays(self): + """Test arrays of class instances""" + + input = """\ +public class Test { + MyClass abc = new MyClass(); + MyClass xyz = new MyClass(); + MyClass _array[] = new MyClass[] { + abc, + xyz + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir is None, pkg_dir + assert classes == ['Test'], classes + + + + def test_backslash(self): + """Test backslash handling""" + + input = """\ +public class MyTabs +{ + private class MyInternal + { + } + private final static String PATH = "images\\\\"; +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir is None, pkg_dir + assert classes == ['MyTabs$MyInternal', 'MyTabs'], classes + + + def test_enum(self): + """Test the Java 1.5 enum keyword""" + + input = """\ +package p; +public enum a {} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir == 'p', pkg_dir + assert classes == ['a'], classes + + + def test_anon_classes(self): + """Test anonymous classes""" + + input = """\ +public abstract class TestClass +{ + public void completed() + { + new Thread() + { + }.start(); + + new Thread() + { + }.start(); + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir is None, pkg_dir + assert classes == ['TestClass$1', 'TestClass$2', 'TestClass'], classes + + + def test_closing_bracket(self): + """Test finding a closing bracket instead of an anonymous class""" + + input = """\ +class TestSCons { + public static void main(String[] args) { + Foo[] fooArray = new Foo[] { new Foo() }; + } +} + +class Foo { } +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir is None, pkg_dir + assert classes == ['TestSCons', 'Foo'], classes + + + def test_dot_class_attributes(self): + """Test handling ".class" attributes""" + + input = """\ +public class Test extends Object +{ + static { + Class c = Object[].class; + Object[] s = new Object[] {}; + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert classes == ['Test'], classes + + input = """\ +public class A { + public class B { + public void F(Object[] o) { + F(new Object[] {Object[].class}); + } + public void G(Object[] o) { + F(new Object[] {}); + } + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input) + assert pkg_dir is None, pkg_dir + assert classes == ['A$B', 'A'], classes + + def test_anonymous_classes_with_parentheses(self): + """Test finding anonymous classes marked by parentheses""" + + input = """\ +import java.io.File; + +public class Foo { + public static void main(String[] args) { + File f = new File( + new File("a") { + public String toString() { + return "b"; + } + } to String() + ) { + public String toString() { + return "c"; + } + }; + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4') + assert classes == ['Foo$1', 'Foo$2', 'Foo'], classes + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5') + assert classes == ['Foo$1', 'Foo$1$1', 'Foo'], classes + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6') + assert classes == ['Foo$1', 'Foo$1$1', 'Foo'], classes + + + + def test_nested_anonymous_inner_classes(self): + """Test finding nested anonymous inner classes""" + + input = """\ +// import java.util.*; + +public class NestedExample +{ + public NestedExample() + { + Thread t = new Thread() { + public void start() + { + Thread t = new Thread() { + public void start() + { + try {Thread.sleep(200);} + catch (Exception e) {} + } + }; + while (true) + { + try {Thread.sleep(200);} + catch (Exception e) {} + } + } + }; + } + + + public static void main(String argv[]) + { + NestedExample e = new NestedExample(); + } +} +""" + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4') + expect = [ 'NestedExample$1', 'NestedExample$2', 'NestedExample' ] + assert expect == classes, (expect, classes) + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5') + expect = [ 'NestedExample$1', 'NestedExample$1$1', 'NestedExample' ] + assert expect == classes, (expect, classes) + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6') + expect = [ 'NestedExample$1', 'NestedExample$1$1', 'NestedExample' ] + assert expect == classes, (expect, classes) + + def test_private_inner_class_instantiation(self): + """Test anonymous inner class generated by private instantiation""" + + input = """\ +class test +{ + test() + { + super(); + new inner(); + } + + static class inner + { + private inner() {} + } +} +""" + + # This is what we *should* generate, apparently due to the + # private instantiation of the inner class, but don't today. + #expect = [ 'test$1', 'test$inner', 'test' ] + + # What our parser currently generates, which doesn't match + # what the Java compiler actually generates. + expect = [ 'test$inner', 'test' ] + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4') + assert expect == classes, (expect, classes) + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5') + assert expect == classes, (expect, classes) + + def test_floating_point_numbers(self): + """Test floating-point numbers in the input stream""" + input = """ +// Broken.java +class Broken +{ + /** + * Detected. + */ + Object anonymousInnerOK = new Runnable() { public void run () {} }; + + /** + * Detected. + */ + class InnerOK { InnerOK () { } } + + { + System.out.println("a number: " + 1000.0 + ""); + } + + /** + * Not detected. + */ + Object anonymousInnerBAD = new Runnable() { public void run () {} }; + + /** + * Not detected. + */ + class InnerBAD { InnerBAD () { } } +} +""" + + expect = ['Broken$1', 'Broken$InnerOK', 'Broken$2', 'Broken$InnerBAD', 'Broken'] + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4') + assert expect == classes, (expect, classes) + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5') + assert expect == classes, (expect, classes) + + + def test_genercis(self): + """Test that generics don't interfere with detecting anonymous classes""" + + input = """\ +import java.util.Date; +import java.util.Comparator; + +public class Foo +{ + public void foo() + { + Comparator comp = new Comparator() + { + static final long serialVersionUID = 1L; + public int compare(Date lhs, Date rhs) + { + return 0; + } + }; + } +} +""" + + expect = [ 'Foo$1', 'Foo' ] + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.6') + assert expect == classes, (expect, classes) + + pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6') + assert expect == classes, (expect, classes) + + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ parse_javaTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/MSCommon/__init__.py b/src/engine/SCons/Tool/MSCommon/__init__.py new file mode 100644 index 0000000..78a6030 --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/__init__.py @@ -0,0 +1,56 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/__init__.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +Common functions for Microsoft Visual Studio and Visual C/C++. +""" + +import copy +import os +import re +import subprocess + +import SCons.Errors +import SCons.Platform.win32 +import SCons.Util + +from SCons.Tool.MSCommon.sdk import mssdk_exists, \ + mssdk_setup_env + +from SCons.Tool.MSCommon.vc import msvc_exists, \ + msvc_setup_env, \ + msvc_setup_env_once + +from SCons.Tool.MSCommon.vs import get_default_version, \ + get_vs_by_version, \ + merge_default_version, \ + msvs_exists, \ + query_versions + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/MSCommon/arch.py b/src/engine/SCons/Tool/MSCommon/arch.py new file mode 100644 index 0000000..3cf2fa0 --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/arch.py @@ -0,0 +1,61 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/arch.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Module to define supported Windows chip architectures. +""" + +import os + +class ArchDefinition: + """ + A class for defining architecture-specific settings and logic. + """ + def __init__(self, arch, synonyms=[]): + self.arch = arch + self.synonyms = synonyms + +SupportedArchitectureList = [ + ArchitectureDefinition( + 'x86', + ['i386', 'i486', 'i586', 'i686'], + ), + + ArchitectureDefinition( + 'x86_64', + ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'], + ), + + ArchitectureDefinition( + 'ia64', + ['IA64'], + ), +] + +SupportedArchitectureMap = {} +for a in SupportedArchitectureList: + SupportedArchitectureMap[a.arch] = a + for s in a.synonyms: + SupportedArchitectureMap[s] = a + diff --git a/src/engine/SCons/Tool/MSCommon/common.py b/src/engine/SCons/Tool/MSCommon/common.py new file mode 100644 index 0000000..8fc49cc --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/common.py @@ -0,0 +1,195 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/common.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +Common helper functions for working with the Microsoft tool chain. +""" + +import copy +import os +import subprocess +import re + +import SCons.Util + + +logfile = os.environ.get('SCONS_MSCOMMON_DEBUG') +if logfile == '-': + def debug(x): + print x +elif logfile: + try: + import logging + except ImportError: + debug = lambda x: open(logfile, 'a').write(x + '\n') + else: + logging.basicConfig(filename=logfile, level=logging.DEBUG) + debug = logging.debug +else: + debug = lambda x: None + + +_is_win64 = None + +def is_win64(): + """Return true if running on windows 64 bits. + + Works whether python itself runs in 64 bits or 32 bits.""" + # Unfortunately, python does not provide a useful way to determine + # if the underlying Windows OS is 32-bit or 64-bit. Worse, whether + # the Python itself is 32-bit or 64-bit affects what it returns, + # so nothing in sys.* or os.* help. So we go to the registry to + # look directly for a clue from Windows, caching the result to + # avoid repeated registry calls. + global _is_win64 + if _is_win64 is None: + _is_win64 = has_reg(r"Software\Wow6432Node") + return _is_win64 + + +def read_reg(value): + return SCons.Util.RegGetValue(SCons.Util.HKEY_LOCAL_MACHINE, value)[0] + +def has_reg(value): + """Return True if the given key exists in HKEY_LOCAL_MACHINE, False + otherwise.""" + try: + SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, value) + ret = True + except WindowsError: + ret = False + return ret + +# Functions for fetching environment variable settings from batch files. + +def normalize_env(env, keys): + """Given a dictionary representing a shell environment, add the variables + from os.environ needed for the processing of .bat files; the keys are + controlled by the keys argument. + + It also makes sure the environment values are correctly encoded. + + Note: the environment is copied""" + normenv = {} + if env: + for k in env.keys(): + normenv[k] = copy.deepcopy(env[k]).encode('mbcs') + + for k in keys: + if os.environ.has_key(k): + normenv[k] = os.environ[k].encode('mbcs') + + return normenv + +def get_output(vcbat, args = None, env = None): + """Parse the output of given bat file, with given args.""" + if args: + debug("Calling '%s %s'" % (vcbat, args)) + popen = subprocess.Popen('"%s" %s & set' % (vcbat, args), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env) + else: + debug("Calling '%s'" % vcbat) + popen = subprocess.Popen('"%s" & set' % vcbat, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=env) + + # Use the .stdout and .stderr attributes directly because the + # .communicate() method uses the threading module on Windows + # and won't work under Pythons not built with threading. + stdout = popen.stdout.read() + if popen.wait() != 0: + raise IOError(popen.stderr.read().decode("mbcs")) + + output = stdout.decode("mbcs") + return output + +def parse_output(output, keep = ("INCLUDE", "LIB", "LIBPATH", "PATH")): + # dkeep is a dict associating key: path_list, where key is one item from + # keep, and pat_list the associated list of paths + + # TODO(1.5): replace with the following list comprehension: + #dkeep = dict([(i, []) for i in keep]) + dkeep = dict(map(lambda i: (i, []), keep)) + + # rdk will keep the regex to match the .bat file output line starts + rdk = {} + for i in keep: + rdk[i] = re.compile('%s=(.*)' % i, re.I) + + def add_env(rmatch, key, dkeep=dkeep): + plist = rmatch.group(1).split(os.pathsep) + for p in plist: + # Do not add empty paths (when a var ends with ;) + if p: + p = p.encode('mbcs') + # XXX: For some reason, VC98 .bat file adds "" around the PATH + # values, and it screws up the environment later, so we strip + # it. + p = p.strip('"') + dkeep[key].append(p) + + for line in output.splitlines(): + for k,v in rdk.items(): + m = v.match(line) + if m: + add_env(m, k) + + return dkeep + +# TODO(sgk): unused +def output_to_dict(output): + """Given an output string, parse it to find env variables. + + Return a dict where keys are variables names, and values their content""" + envlinem = re.compile(r'^([a-zA-z0-9]+)=([\S\s]*)$') + parsedenv = {} + for line in output.splitlines(): + m = envlinem.match(line) + if m: + parsedenv[m.group(1)] = m.group(2) + return parsedenv + +# TODO(sgk): unused +def get_new(l1, l2): + """Given two list l1 and l2, return the items in l2 which are not in l1. + Order is maintained.""" + + # We don't try to be smart: lists are small, and this is not the bottleneck + # is any case + new = [] + for i in l2: + if i not in l1: + new.append(i) + + return new + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/MSCommon/netframework.py b/src/engine/SCons/Tool/MSCommon/netframework.py new file mode 100644 index 0000000..3a38abd --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/netframework.py @@ -0,0 +1,84 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/netframework.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +""" + +import os +import re +import string + +from common import read_reg, debug + +# Original value recorded by dcournapeau +_FRAMEWORKDIR_HKEY_ROOT = r'Software\Microsoft\.NETFramework\InstallRoot' +# On SGK's system +_FRAMEWORKDIR_HKEY_ROOT = r'Software\Microsoft\Microsoft SDKs\.NETFramework\v2.0\InstallationFolder' + +def find_framework_root(): + # XXX: find it from environment (FrameworkDir) + try: + froot = read_reg(_FRAMEWORKDIR_HKEY_ROOT) + debug("Found framework install root in registry: %s" % froot) + except WindowsError, e: + debug("Could not read reg key %s" % _FRAMEWORKDIR_HKEY_ROOT) + return None + + if not os.path.exists(froot): + debug("%s not found on fs" % froot) + return None + + return froot + +def query_versions(): + froot = find_framework_root() + if froot: + contents = os.listdir(froot) + + l = re.compile('v[0-9]+.*') + versions = filter(lambda e, l=l: l.match(e), contents) + + def versrt(a,b): + # since version numbers aren't really floats... + aa = a[1:] + bb = b[1:] + aal = string.split(aa, '.') + bbl = string.split(bb, '.') + # sequence comparison in python is lexicographical + # which is exactly what we want. + # Note we sort backwards so the highest version is first. + return cmp(bbl,aal) + + versions.sort(versrt) + else: + versions = [] + + return versions + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/MSCommon/sdk.py b/src/engine/SCons/Tool/MSCommon/sdk.py new file mode 100644 index 0000000..88af6aa --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/sdk.py @@ -0,0 +1,321 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/sdk.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Module to detect the Platform/Windows SDK + +PSDK 2003 R1 is the earliest version detected. +""" + +import os + +import SCons.Errors +import SCons.Util + +import common + +debug = common.debug + +# SDK Checks. This is of course a mess as everything else on MS platforms. Here +# is what we do to detect the SDK: +# +# For Windows SDK >= 6.0: just look into the registry entries: +# HKLM\Software\Microsoft\Microsoft SDKs\Windows +# All the keys in there are the available versions. +# +# For Platform SDK before 6.0 (2003 server R1 and R2, etc...), there does not +# seem to be any sane registry key, so the precise location is hardcoded. +# +# For versions below 2003R1, it seems the PSDK is included with Visual Studio? +# +# Also, per the following: +# http://benjamin.smedbergs.us/blog/tag/atl/ +# VC++ Professional comes with the SDK, VC++ Express does not. + +# Location of the SDK (checked for 6.1 only) +_CURINSTALLED_SDK_HKEY_ROOT = \ + r"Software\Microsoft\Microsoft SDKs\Windows\CurrentInstallFolder" + + +class SDKDefinition: + """ + An abstract base class for trying to find installed SDK directories. + """ + def __init__(self, version, **kw): + self.version = version + self.__dict__.update(kw) + + def find_sdk_dir(self): + """Try to find the MS SDK from the registry. + + Return None if failed or the directory does not exist. + """ + if not SCons.Util.can_read_reg: + debug('find_sdk_dir(): can not read registry') + return None + + hkey = self.HKEY_FMT % self.hkey_data + + try: + sdk_dir = common.read_reg(hkey) + except WindowsError, e: + debug('find_sdk_dir(): no SDK registry key %s' % repr(hkey)) + return None + + if not os.path.exists(sdk_dir): + debug('find_sdk_dir(): %s not on file system' % sdk_dir) + return None + + ftc = os.path.join(sdk_dir, self.sanity_check_file) + if not os.path.exists(ftc): + debug("find_sdk_dir(): sanity check %s not found" % ftc) + return None + + return sdk_dir + + def get_sdk_dir(self): + """Return the MSSSDK given the version string.""" + try: + return self._sdk_dir + except AttributeError: + sdk_dir = self.find_sdk_dir() + self._sdk_dir = sdk_dir + return sdk_dir + +class WindowsSDK(SDKDefinition): + """ + A subclass for trying to find installed Windows SDK directories. + """ + HKEY_FMT = r'Software\Microsoft\Microsoft SDKs\Windows\v%s\InstallationFolder' + def __init__(self, *args, **kw): + apply(SDKDefinition.__init__, (self,)+args, kw) + self.hkey_data = self.version + +class PlatformSDK(SDKDefinition): + """ + A subclass for trying to find installed Platform SDK directories. + """ + HKEY_FMT = r'Software\Microsoft\MicrosoftSDK\InstalledSDKS\%s\Install Dir' + def __init__(self, *args, **kw): + apply(SDKDefinition.__init__, (self,)+args, kw) + self.hkey_data = self.uuid + +# The list of support SDKs which we know how to detect. +# +# The first SDK found in the list is the one used by default if there +# are multiple SDKs installed. Barring good reasons to the contrary, +# this means we should list SDKs with from most recent to oldest. +# +# If you update this list, update the documentation in Tool/mssdk.xml. +SupportedSDKList = [ + WindowsSDK('6.1', + sanity_check_file=r'bin\SetEnv.Cmd', + include_subdir='include', + lib_subdir={ + 'x86' : ['lib'], + 'x86_64' : [r'lib\x64'], + 'ia64' : [r'lib\ia64'], + }, + ), + + WindowsSDK('6.0A', + sanity_check_file=r'include\windows.h', + include_subdir='include', + lib_subdir={ + 'x86' : ['lib'], + 'x86_64' : [r'lib\x64'], + 'ia64' : [r'lib\ia64'], + }, + ), + + WindowsSDK('6.0', + sanity_check_file=r'bin\gacutil.exe', + include_subdir='include', + lib_subdir='lib', + ), + + PlatformSDK('2003R2', + sanity_check_file=r'SetEnv.Cmd', + uuid="D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1" + ), + + PlatformSDK('2003R1', + sanity_check_file=r'SetEnv.Cmd', + uuid="8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3", + ), +] + +SupportedSDKMap = {} +for sdk in SupportedSDKList: + SupportedSDKMap[sdk.version] = sdk + + +# Finding installed SDKs isn't cheap, because it goes not only to the +# registry but also to the disk to sanity-check that there is, in fact, +# an SDK installed there and that the registry entry isn't just stale. +# Find this information once, when requested, and cache it. + +InstalledSDKList = None +InstalledSDKMap = None + +def get_installed_sdks(): + global InstalledSDKList + global InstalledSDKMap + if InstalledSDKList is None: + InstalledSDKList = [] + InstalledSDKMap = {} + for sdk in SupportedSDKList: + debug('trying to find SDK %s' % sdk.version) + if sdk.get_sdk_dir(): + debug('found SDK %s' % sdk.version) + InstalledSDKList.append(sdk) + InstalledSDKMap[sdk.version] = sdk + return InstalledSDKList + + +# We may be asked to update multiple construction environments with +# SDK information. When doing this, we check on-disk for whether +# the SDK has 'mfc' and 'atl' subdirectories. Since going to disk +# is expensive, cache results by directory. + +SDKEnvironmentUpdates = {} + +def set_sdk_by_directory(env, sdk_dir): + global SDKEnvironmentUpdates + try: + env_tuple_list = SDKEnvironmentUpdates[sdk_dir] + except KeyError: + env_tuple_list = [] + SDKEnvironmentUpdates[sdk_dir] = env_tuple_list + + include_path = os.path.join(sdk_dir, 'include') + mfc_path = os.path.join(include_path, 'mfc') + atl_path = os.path.join(include_path, 'atl') + + if os.path.exists(mfc_path): + env_tuple_list.append(('INCLUDE', mfc_path)) + if os.path.exists(atl_path): + env_tuple_list.append(('INCLUDE', atl_path)) + env_tuple_list.append(('INCLUDE', include_path)) + + env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib'))) + env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib'))) + env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin'))) + + for variable, directory in env_tuple_list: + env.PrependENVPath(variable, directory) + + +# TODO(sgk): currently unused; remove? +def get_cur_sdk_dir_from_reg(): + """Try to find the platform sdk directory from the registry. + + Return None if failed or the directory does not exist""" + if not SCons.Util.can_read_reg: + debug('SCons cannot read registry') + return None + + try: + val = common.read_reg(_CURINSTALLED_SDK_HKEY_ROOT) + debug("Found current sdk dir in registry: %s" % val) + except WindowsError, e: + debug("Did not find current sdk in registry") + return None + + if not os.path.exists(val): + debug("Current sdk dir %s not on fs" % val) + return None + + return val + +def get_sdk_by_version(mssdk): + if not SupportedSDKMap.has_key(mssdk): + msg = "SDK version %s is not supported" % repr(mssdk) + raise SCons.Errors.UserError, msg + get_installed_sdks() + return InstalledSDKMap.get(mssdk) + +def get_default_sdk(): + """Set up the default Platform/Windows SDK.""" + get_installed_sdks() + if not InstalledSDKList: + return None + return InstalledSDKList[0] + +def mssdk_setup_env(env): + debug('msvs_setup_env()') + if env.has_key('MSSDK_DIR'): + sdk_dir = env['MSSDK_DIR'] + if sdk_dir is None: + return + sdk_dir = env.subst(sdk_dir) + elif env.has_key('MSSDK_VERSION'): + sdk_version = env['MSSDK_VERSION'] + if sdk_version is None: + msg = "SDK version %s is not installed" % repr(mssdk) + raise SCons.Errors.UserError, msg + sdk_version = env.subst(sdk_version) + mssdk = get_sdk_by_version(sdk_version) + sdk_dir = mssdk.get_sdk_dir() + elif env.has_key('MSVS_VERSION'): + msvs_version = env['MSVS_VERSION'] + debug('Getting MSVS_VERSION from env:%s'%msvs_version) + if msvs_version is None: + return + msvs_version = env.subst(msvs_version) + import vs + msvs = vs.get_vs_by_version(msvs_version) + debug('msvs is :%s'%msvs) + if not msvs: + return + sdk_version = msvs.sdk_version + if not sdk_version: + return + mssdk = get_sdk_by_version(sdk_version) + if not mssdk: + mssdk = get_default_sdk() + if not mssdk: + return + sdk_dir = mssdk.get_sdk_dir() + else: + mssdk = get_default_sdk() + if not mssdk: + return + sdk_dir = mssdk.get_sdk_dir() + + set_sdk_by_directory(env, sdk_dir) + + #print "No MSVS_VERSION: this is likely to be a bug" + +def mssdk_exists(version=None): + sdks = get_installed_sdks() + if version is None: + return len(sdks) > 0 + return sdks.has_key(version) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/MSCommon/vc.py b/src/engine/SCons/Tool/MSCommon/vc.py new file mode 100644 index 0000000..7d34374 --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/vc.py @@ -0,0 +1,367 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +# TODO: +# * supported arch for versions: for old versions of batch file without +# argument, giving bogus argument cannot be detected, so we have to hardcode +# this here +# * print warning when msvc version specified but not found +# * find out why warning do not print +# * test on 64 bits XP + VS 2005 (and VS 6 if possible) +# * SDK +# * Assembly +__revision__ = "src/engine/SCons/Tool/MSCommon/vc.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Module for Visual C/C++ detection and configuration. +""" +import SCons.compat + +import os +import platform + +import SCons.Warnings + +import common + +debug = common.debug + +class VisualCException(Exception): + pass + +class UnsupportedVersion(VisualCException): + pass + +class UnsupportedArch(VisualCException): + pass + +class MissingConfiguration(VisualCException): + pass + +class NoVersionFound(VisualCException): + pass + +class BatchFileExecutionError(VisualCException): + pass + +# Dict to 'canonalize' the arch +_ARCH_TO_CANONICAL = { + "x86": "x86", + "amd64": "amd64", + "i386": "x86", + "emt64": "amd64", + "x86_64": "amd64", + "itanium": "ia64", + "ia64": "ia64", +} + +# Given a (host, target) tuple, return the argument for the bat file. Both host +# and targets should be canonalized. +_HOST_TARGET_ARCH_TO_BAT_ARCH = { + ("x86", "x86"): "x86", + ("x86", "amd64"): "x86_amd64", + ("amd64", "amd64"): "amd64", + ("amd64", "x86"): "x86", + ("x86", "ia64"): "x86_ia64" +} + +def get_host_target(env): + host_platform = env.get('HOST_ARCH') + if not host_platform: + host_platform = platform.machine() + # TODO(2.5): the native Python platform.machine() function returns + # '' on all Python versions before 2.6, after which it also uses + # PROCESSOR_ARCHITECTURE. + if not host_platform: + host_platform = os.environ.get('PROCESSOR_ARCHITECTURE', '') + target_platform = env.get('TARGET_ARCH') + if not target_platform: + target_platform = host_platform + + try: + host = _ARCH_TO_CANONICAL[host_platform] + except KeyError, e: + msg = "Unrecognized host architecture %s" + raise ValueError(msg % repr(host_platform)) + + try: + target = _ARCH_TO_CANONICAL[target_platform] + except KeyError, e: + raise ValueError("Unrecognized target architecture %s" % target_platform) + + return (host, target) + +_VCVER = ["10.0", "9.0", "8.0", "7.1", "7.0", "6.0"] + +_VCVER_TO_PRODUCT_DIR = { + '10.0': [ + r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'], + '9.0': [ + r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir', + r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'], + '8.0': [ + r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir', + r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'], + '7.1': [ + r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'], + '7.0': [ + r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'], + '6.0': [ + r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'] +} + +def msvc_version_to_maj_min(msvc_version): + t = msvc_version.split(".") + if not len(t) == 2: + raise ValueError("Unrecognized version %s" % msvc_version) + try: + maj = int(t[0]) + min = int(t[1]) + return maj, min + except ValueError, e: + raise ValueError("Unrecognized version %s" % msvc_version) + +def is_host_target_supported(host_target, msvc_version): + """Return True if the given (host, target) tuple is supported given the + msvc version. + + Parameters + ---------- + host_target: tuple + tuple of (canonalized) host-target, e.g. ("x86", "amd64") for cross + compilation from 32 bits windows to 64 bits. + msvc_version: str + msvc version (major.minor, e.g. 10.0) + + Note + ---- + This only check whether a given version *may* support the given (host, + target), not that the toolchain is actually present on the machine. + """ + # We assume that any Visual Studio version supports x86 as a target + if host_target[1] != "x86": + maj, min = msvc_version_to_maj_min(msvc_version) + if maj < 8: + return False + + return True + +def find_vc_pdir(msvc_version): + """Try to find the product directory for the given + version. + + Note + ---- + If for some reason the requested version could not be found, an + exception which inherits from VisualCException will be raised.""" + root = 'Software\\' + if common.is_win64(): + root = root + 'Wow6432Node\\' + try: + hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version] + except KeyError: + debug("Unknown version of MSVC: %s" % msvc_version) + raise UnsupportedVersion("Unknown version %s" % msvc_version) + + for key in hkeys: + key = root + key + try: + comps = common.read_reg(key) + except WindowsError, e: + debug('find_vc_dir(): no VC registry key %s' % repr(key)) + else: + debug('find_vc_dir(): found VC in registry: %s' % comps) + if os.path.exists(comps): + return comps + else: + debug('find_vc_dir(): reg says dir is %s, but it does not exist. (ignoring)'\ + % comps) + raise MissingConfiguration("registry dir %s not found on the filesystem" % comps) + return None + +def find_batch_file(msvc_version): + pdir = find_vc_pdir(msvc_version) + if pdir is None: + raise NoVersionFound("No version of Visual Studio found") + + vernum = float(msvc_version) + if 7 <= vernum < 8: + pdir = os.path.join(pdir, os.pardir, "Common7", "Tools") + batfilename = os.path.join(pdir, "vsvars32.bat") + elif vernum < 7: + pdir = os.path.join(pdir, "Bin") + batfilename = os.path.join(pdir, "vcvars32.bat") + else: # >= 8 + batfilename = os.path.join(pdir, "vcvarsall.bat") + + if os.path.exists(batfilename): + return batfilename + else: + debug("Not found: %s" % batfilename) + return None + +__INSTALLED_VCS_RUN = None + +def cached_get_installed_vcs(): + global __INSTALLED_VCS_RUN + + if __INSTALLED_VCS_RUN is None: + ret = get_installed_vcs() + __INSTALLED_VCS_RUN = ret + + return __INSTALLED_VCS_RUN + +def get_installed_vcs(): + installed_versions = [] + for ver in _VCVER: + debug('trying to find VC %s' % ver) + try: + if find_vc_pdir(ver): + debug('found VC %s' % ver) + installed_versions.append(ver) + else: + debug('find_vc_pdir return None for ver %s' % ver) + except VisualCException, e: + debug('did not find VC %s: caught exception %s' % (ver, str(e))) + return installed_versions + +def reset_installed_vcs(): + """Make it try again to find VC. This is just for the tests.""" + __INSTALLED_VCS_RUN = None + +def script_env(script, args=None): + stdout = common.get_output(script, args) + # Stupid batch files do not set return code: we take a look at the + # beginning of the output for an error message instead + olines = stdout.splitlines() + if olines[0].startswith("The specified configuration type is missing"): + raise BatchFileExecutionError("\n".join(olines[:2])) + + return common.parse_output(stdout) + +def get_default_version(env): + debug('get_default_version()') + + msvc_version = env.get('MSVC_VERSION') + msvs_version = env.get('MSVS_VERSION') + + if msvs_version and not msvc_version: + SCons.Warnings.warn( + SCons.Warnings.DeprecatedWarning, + "MSVS_VERSION is deprecated: please use MSVC_VERSION instead ") + return msvs_version + elif msvc_version and msvs_version: + if not msvc_version == msvs_version: + SCons.Warnings.warn( + SCons.Warnings.VisualVersionMismatch, + "Requested msvc version (%s) and msvs version (%s) do " \ + "not match: please use MSVC_VERSION only to request a " \ + "visual studio version, MSVS_VERSION is deprecated" \ + % (msvc_version, msvs_version)) + return msvs_version + if not msvc_version: + installed_vcs = cached_get_installed_vcs() + debug('installed_vcs:%s' % installed_vcs) + if not installed_vcs: + msg = 'No installed VCs' + debug('msv %s\n' % repr(msg)) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg) + return None + msvc_version = installed_vcs[0] + debug('msvc_setup_env: using default installed MSVC version %s\n' % repr(msvc_version)) + + return msvc_version + +def msvc_setup_env_once(env): + try: + has_run = env["MSVC_SETUP_RUN"] + except KeyError: + has_run = False + + if not has_run: + msvc_setup_env(env) + env["MSVC_SETUP_RUN"] = True + +def msvc_setup_env(env): + debug('msvc_setup_env()') + + version = get_default_version(env) + if version is None: + warn_msg = "No version of Visual Studio compiler found - C/C++ " \ + "compilers most likely not set correctly" + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + return None + debug('msvc_setup_env: using specified MSVC version %s\n' % repr(version)) + + # XXX: we set-up both MSVS version for backward + # compatibility with the msvs tool + env['MSVC_VERSION'] = version + env['MSVS_VERSION'] = version + env['MSVS'] = {} + + try: + script = find_batch_file(version) + except VisualCException, e: + msg = str(e) + debug('Caught exception while looking for batch file (%s)' % msg) + warn_msg = "VC version %s not installed. " + \ + "C/C++ compilers are most likely not set correctly.\n" + \ + " Installed versions are: %s" + warn_msg = warn_msg % (version, cached_get_installed_vcs()) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + return None + + use_script = env.get('MSVC_USE_SCRIPT', True) + if SCons.Util.is_String(use_script): + debug('use_script 1 %s\n' % repr(use_script)) + d = script_env(use_script) + elif use_script: + host_platform, target_platform = get_host_target(env) + host_target = (host_platform, target_platform) + if not is_host_target_supported(host_target, version): + warn_msg = "host, target = %s not supported for MSVC version %s" % \ + (host_target, version) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[host_target] + debug('use_script 2 %s, args:%s\n' % (repr(script), arg)) + try: + d = script_env(script, args=arg) + except BatchFileExecutionError, e: + msg = "MSVC error while executing %s with args %s (error was %s)" % \ + (script, arg, str(e)) + raise SCons.Errors.UserError(msg) + else: + debug('MSVC_USE_SCRIPT set to False') + warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \ + "set correctly." + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg) + return None + + for k, v in d.items(): + env.PrependENVPath(k, v, delete_existing=True) + +def msvc_exists(version=None): + vcs = cached_get_installed_vcs() + if version is None: + return len(vcs) > 0 + return version in vcs + diff --git a/src/engine/SCons/Tool/MSCommon/vc.py.bak b/src/engine/SCons/Tool/MSCommon/vc.py.bak new file mode 100644 index 0000000..e59092f --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/vc.py.bak @@ -0,0 +1,394 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/vc.py.bak 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Module for Visual C/C++ detection and configuration. +""" + +import os +import platform + +import SCons.Warnings + +import common + +debug = common.debug + +class VisualC: + """ + An base class for finding installed versions of Visual C/C++. + """ + def __init__(self, version, **kw): + self.version = version + self.__dict__.update(kw) + self._cache = {} + + def vcbin_arch(self): + if common.is_win64(): + result = { + 'x86_64' : ['amd64', r'BIN\x86_amd64'], + 'ia64' : [r'BIN\ia64'], + }.get(target_arch, []) + else: + result = { + 'x86_64' : ['x86_amd64'], + 'ia64' : ['x86_ia64'], + }.get(target_arch, []) + # TODO(1.5) + #return ';'.join(result) + return string.join(result, ';') + + # Support for searching for an appropriate .bat file. + # The map is indexed by (target_architecture, host_architecture). + # Entries where the host_architecture is None specify the + # cross-platform "default" .bat file if there isn't sn entry + # specific to the current host architecture. + + batch_file_map = { + ('x86_64', 'x86_64') : [ + r'bin\amd64\vcvarsamd64.bat', + r'bin\x86_amd64\vcvarsx86_amd64.bat', + r'bin\vcvarsx86_amd64.bat', + ], + ('x86_64', 'x86') : [ + r'bin\x86_amd64\vcvarsx86_amd64.bat', + ], + ('ia64', 'ia64') : [ + r'bin\ia64\vcvarsia64.bat', + r'bin\x86_ia64\vcvarsx86_ia64.bat', + ], + ('ia64', None) : [ + r'bin\x86_ia64\vcvarsx86_ia64.bat', + ], + ('x86', None) : [ + r'bin\vcvars32.bat', + ], + } + + def find_batch_file(self, target_architecture, host_architecture): + key = (target_architecture, host_architecture) + potential_batch_files = self.batch_file_map.get(key) + if not potential_batch_files: + key = (target_architecture, None) + potential_batch_files = self.batch_file_map.get(key) + if potential_batch_files: + product_dir = self.get_vc_dir() + for batch_file in potential_batch_files: + bf = os.path.join(product_dir, batch_file) + if os.path.isfile(bf): + return bf + return None + + def find_vc_dir(self): + root = 'Software\\' + if common.is_win64(): + root = root + 'Wow6432Node\\' + for key in self.hkeys: + key = root + key + try: + comps = common.read_reg(key) + except WindowsError, e: + debug('find_vc_dir(): no VC registry key %s' % repr(key)) + else: + debug('find_vc_dir(): found VC in registry: %s' % comps) + if os.path.exists(comps): + return comps + else: + debug('find_vc_dir(): reg says dir is %s, but it does not exist. (ignoring)'\ + % comps) + return None + return None + + # + + def get_batch_file(self, target_architecture, host_architecture): + try: + return self._cache['batch_file'] + except KeyError: + batch_file = self.find_batch_file(target_architecture, host_architecture) + self._cache['batch_file'] = batch_file + return batch_file + + def get_vc_dir(self): + try: + return self._cache['vc_dir'] + except KeyError: + vc_dir = self.find_vc_dir() + self._cache['vc_dir'] = vc_dir + return vc_dir + + def reset(self): + self._cache={} + + +# The list of supported Visual C/C++ versions we know how to detect. +# +# The first VC found in the list is the one used by default if there +# are multiple VC installed. Barring good reasons to the contrary, +# this means we should list VC with from most recent to oldest. +# +# If you update this list, update the documentation in Tool/vc.xml. +SupportedVCList = [ + VisualC('9.0', + hkeys=[ + r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir', + r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir', + ], + default_install=r'Microsoft Visual Studio 9.0\VC', + common_tools_var='VS90COMNTOOLS', + vc_subdir=r'\VC', + batch_file_base='vcvars', + supported_arch=['x86', 'x86_64', 'ia64'], + atlmc_include_subdir = [r'ATLMFC\INCLUDE'], + atlmfc_lib_subdir = { + 'x86' : r'ATLMFC\LIB', + 'x86_64' : r'ATLMFC\LIB\amd64', + 'ia64' : r'ATLMFC\LIB\ia64', + }, + crt_lib_subdir = { + 'x86_64' : r'LIB\amd64', + 'ia64' : r'LIB\ia64', + }, + ), + VisualC('8.0', + hkeys=[ + r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir', + r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir', + ], + default_install=r'%s\Microsoft Visual Studio 8\VC', + common_tools_var='VS80COMNTOOLS', + vc_subdir=r'\VC', + batch_file_base='vcvars', + supported_arch=['x86', 'x86_64', 'ia64'], + atlmc_include_subdir = [r'ATLMFC\INCLUDE'], + atlmfc_lib_subdir = { + 'x86' : r'ATLMFC\LIB', + 'x86_64' : r'ATLMFC\LIB\amd64', + 'ia64' : r'ATLMFC\LIB\ia64', + }, + crt_lib_subdir = { + 'x86_64' : r'LIB\amd64', + 'ia64' : r'LIB\ia64', + }, + ), + VisualC('7.1', + hkeys=[ + r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir', + ], + default_install=r'%s\Microsoft Visual Studio 7.1.NET 2003\VC7', + common_tools_var='VS71COMNTOOLS', + vc_subdir=r'\VC7', + batch_file_base='vcvars', + supported_arch=['x86'], + atlmc_include_subdir = [r'ATLMFC\INCLUDE'], + atlmfc_lib_subdir = { + 'x86' : r'ATLMFC\LIB', + }, + ), + VisualC('7.0', + hkeys=[ + r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir', + ], + default_install=r'%s\Microsoft Visual Studio .NET\VC7', + common_tools_var='VS70COMNTOOLS', + vc_subdir=r'\VC7', + batch_file_base='vcvars', + supported_arch=['x86'], + atlmc_include_subdir = [r'ATLMFC\INCLUDE'], + atlmfc_lib_subdir = { + 'x86' : r'ATLMFC\LIB', + }, + ), + VisualC('6.0', + hkeys=[ + r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir', + ], + default_install=r'%s\Microsoft Visual Studio\VC98', + common_tools_var='VS60COMNTOOLS', + vc_subdir=r'\VC98', + batch_file_base='vcvars', + supported_arch=['x86'], + atlmc_include_subdir = [r'ATL\INCLUDE', r'MFC\INCLUDE'], + atlmfc_lib_subdir = { + 'x86' : r'MFC\LIB', + }, + ), +] + +SupportedVCMap = {} +for vc in SupportedVCList: + SupportedVCMap[vc.version] = vc + + +# Finding installed versions of Visual C/C++ isn't cheap, because it goes +# not only to the registry but also to the disk to sanity-check that there +# is, in fact, something installed there and that the registry entry isn't +# just stale. Find this information once, when requested, and cache it. + +InstalledVCList = None +InstalledVCMap = None + +def get_installed_vcs(): + global InstalledVCList + global InstalledVCMap + if InstalledVCList is None: + InstalledVCList = [] + InstalledVCMap = {} + for vc in SupportedVCList: + debug('trying to find VC %s' % vc.version) + if vc.get_vc_dir(): + debug('found VC %s' % vc.version) + InstalledVCList.append(vc) + InstalledVCMap[vc.version] = vc + return InstalledVCList + + +def set_vc_by_version(env, msvc): + if not SupportedVCMap.has_key(msvc): + msg = "VC version %s is not supported" % repr(msvc) + raise SCons.Errors.UserError, msg + get_installed_vcs() + vc = InstalledVCMap.get(msvc) + if not vc: + msg = "VC version %s is not installed" % repr(msvc) + raise SCons.Errors.UserError, msg + set_vc_by_directory(env, vc.get_vc_dir()) + +# New stuff + +def script_env(script, args=None): + stdout = common.get_output(script, args) + return common.parse_output(stdout) + +def get_default_version(env): + debug('get_default_version()') + + msvc_version = env.get('MSVC_VERSION') + if not msvc_version: + installed_vcs = get_installed_vcs() + debug('InstalledVCMap:%s'%InstalledVCMap) + if not installed_vcs: + msg = 'No installed VCs' + debug('msv %s\n' % repr(msg)) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg) + return None + msvc = installed_vcs[0] + msvc_version = msvc.version + debug('msvc_setup_env: using default installed MSVC version %s\n' % repr(msvc_version)) + + return msvc_version + +# Dict to 'canonalize' the arch +_ARCH_TO_CANONICAL = { + "x86": "x86", + "amd64": "amd64", + "i386": "x86", + "emt64": "amd64", + "x86_64": "amd64" +} + +# Given a (host, target) tuple, return the argument for the bat file. Both host +# and targets should be canonalized. +_HOST_TARGET_ARCH_TO_BAT_ARCH = { + ("x86", "x86"): "x86", + ("x86", "amd64"): "x86_amd64", + ("amd64", "amd64"): "amd64", + ("amd64", "x86"): "x86" +} + +def get_host_target(env): + host_platform = env.get('HOST_ARCH') + if not host_platform: + #host_platform = get_default_host_platform() + host_platform = platform.machine() + target_platform = env.get('TARGET_ARCH') + if not target_platform: + target_platform = host_platform + + return (_ARCH_TO_CANONICAL[host_platform], + _ARCH_TO_CANONICAL[target_platform]) + +def msvc_setup_env_once(env): + try: + has_run = env["MSVC_SETUP_RUN"] + except KeyError: + has_run = False + + if not has_run: + msvc_setup_env(env) + env["MSVC_SETUP_RUN"] = False + +def msvc_setup_env(env): + debug('msvc_setup_env()') + + version = get_default_version(env) + host_platform, target_platform = get_host_target(env) + debug('msvc_setup_env: using specified MSVC version %s\n' % repr(version)) + env['MSVC_VERSION'] = version + + msvc = InstalledVCMap.get(version) + if not msvc: + msg = 'VC version %s not installed' % version + debug('msv %s\n' % repr(msg)) + SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg) + return None + + use_script = env.get('MSVC_USE_SCRIPT', True) + if SCons.Util.is_String(use_script): + debug('use_script 1 %s\n' % repr(use_script)) + d = script_env(use_script) + elif use_script: + # XXX: this is VS 2008 specific, fix this + script = os.path.join(msvc.find_vc_dir(), "vcvarsall.bat") + + arg = _HOST_TARGET_ARCH_TO_BAT_ARCH[(host_platform, target_platform)] + debug('use_script 2 %s, args:%s\n' % (repr(script), arg)) + d = script_env(script, args=arg) + else: + debug('msvc.get_default_env()\n') + d = msvc.get_default_env() + + for k, v in d.items(): + env.PrependENVPath(k, v, delete_existing=True) + +def msvc_exists(version=None): + vcs = get_installed_vcs() + if version is None: + return len(vcs) > 0 + return InstalledVCMap.has_key(version) + + +def reset_installed_vcs(): + global InstalledVCList + global InstalledVCMap + InstalledVCList = None + InstalledVCMap = None + for vc in SupportedVCList: + vc.reset() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/MSCommon/vs.py b/src/engine/SCons/Tool/MSCommon/vs.py new file mode 100644 index 0000000..69003ef --- /dev/null +++ b/src/engine/SCons/Tool/MSCommon/vs.py @@ -0,0 +1,497 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/MSCommon/vs.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """Module to detect Visual Studio and/or Visual C/C++ +""" + +import os + +import SCons.Errors +import SCons.Util + +from common import debug, \ + get_output, \ + is_win64, \ + normalize_env, \ + parse_output, \ + read_reg + +import SCons.Tool.MSCommon.vc + +class VisualStudio: + """ + An abstract base class for trying to find installed versions of + Visual Studio. + """ + def __init__(self, version, **kw): + self.version = version + kw['vc_version'] = kw.get('vc_version', version) + kw['sdk_version'] = kw.get('sdk_version', version) + self.__dict__.update(kw) + self._cache = {} + + # + + def find_batch_file(self): + vs_dir = self.get_vs_dir() + if not vs_dir: + debug('find_executable(): no vs_dir') + return None + batch_file = os.path.join(vs_dir, self.batch_file_path) + batch_file = os.path.normpath(batch_file) + if not os.path.isfile(batch_file): + debug('find_batch_file(): %s not on file system' % batch_file) + return None + return batch_file + + def find_vs_dir_by_vc(self): + SCons.Tool.MSCommon.vc.get_installed_vcs() + dir = SCons.Tool.MSCommon.vc.find_vc_pdir(self.vc_version) + if not dir: + debug('find_vs_dir(): no installed VC %s' % self.vc_version) + return None + return dir + + def find_vs_dir_by_reg(self): + root = 'Software\\' + + if is_win64(): + root = root + 'Wow6432Node\\' + for key in self.hkeys: + if key=='use_dir': + return self.find_vs_dir_by_vc() + key = root + key + try: + comps = read_reg(key) + except WindowsError, e: + debug('find_vs_dir_by_reg(): no VS registry key %s' % repr(key)) + else: + debug('find_vs_dir_by_reg(): found VS in registry: %s' % comps) + return comps + return None + + def find_vs_dir(self): + """ Can use registry or location of VC to find vs dir + First try to find by registry, and if that fails find via VC dir + """ + + + if True: + vs_dir=self.find_vs_dir_by_reg() + return vs_dir + else: + return self.find_vs_dir_by_vc() + + def find_executable(self): + vs_dir = self.get_vs_dir() + if not vs_dir: + debug('find_executable(): no vs_dir (%s)'%vs_dir) + return None + executable = os.path.join(vs_dir, self.executable_path) + executable = os.path.normpath(executable) + if not os.path.isfile(executable): + debug('find_executable(): %s not on file system' % executable) + return None + return executable + + # + + def get_batch_file(self): + try: + return self._cache['batch_file'] + except KeyError: + batch_file = self.find_batch_file() + self._cache['batch_file'] = batch_file + return batch_file + + def get_executable(self): + try: + debug('get_executable using cache:%s'%self._cache['executable']) + return self._cache['executable'] + except KeyError: + executable = self.find_executable() + self._cache['executable'] = executable + debug('get_executable not in cache:%s'%executable) + return executable + + def get_vs_dir(self): + try: + return self._cache['vs_dir'] + except KeyError: + vs_dir = self.find_vs_dir() + self._cache['vs_dir'] = vs_dir + return vs_dir + + def get_supported_arch(self): + try: + return self._cache['supported_arch'] + except KeyError: + # RDEVE: for the time being use hardcoded lists + # supported_arch = self.find_supported_arch() + self._cache['supported_arch'] = self.supported_arch + return self.supported_arch + + def reset(self): + self._cache = {} + +# The list of supported Visual Studio versions we know how to detect. +# +# How to look for .bat file ? +# - VS 2008 Express (x86): +# * from registry key productdir, gives the full path to vsvarsall.bat. In +# HKEY_LOCAL_MACHINE): +# Software\Microsoft\VCEpress\9.0\Setup\VC\productdir +# * from environmnent variable VS90COMNTOOLS: the path is then ..\..\VC +# relatively to the path given by the variable. +# +# - VS 2008 Express (WoW6432: 32 bits on windows x64): +# Software\Wow6432Node\Microsoft\VCEpress\9.0\Setup\VC\productdir +# +# - VS 2005 Express (x86): +# * from registry key productdir, gives the full path to vsvarsall.bat. In +# HKEY_LOCAL_MACHINE): +# Software\Microsoft\VCEpress\8.0\Setup\VC\productdir +# * from environmnent variable VS80COMNTOOLS: the path is then ..\..\VC +# relatively to the path given by the variable. +# +# - VS 2005 Express (WoW6432: 32 bits on windows x64): does not seem to have a +# productdir ? +# +# - VS 2003 .Net (pro edition ? x86): +# * from registry key productdir. The path is then ..\Common7\Tools\ +# relatively to the key. The key is in HKEY_LOCAL_MACHINE): +# Software\Microsoft\VisualStudio\7.1\Setup\VC\productdir +# * from environmnent variable VS71COMNTOOLS: the path is the full path to +# vsvars32.bat +# +# - VS 98 (VS 6): +# * from registry key productdir. The path is then Bin +# relatively to the key. The key is in HKEY_LOCAL_MACHINE): +# Software\Microsoft\VisualStudio\6.0\Setup\VC98\productdir +# +# The first version found in the list is the one used by default if +# there are multiple versions installed. Barring good reasons to +# the contrary, this means we should list versions from most recent +# to oldest. Pro versions get listed before Express versions on the +# assumption that, by default, you'd rather use the version you paid +# good money for in preference to whatever Microsoft makes available +# for free. +# +# If you update this list, update the documentation in Tool/msvs.xml. + +SupportedVSList = [ + # Visual Studio 2010 + # TODO: find the settings, perhaps from someone with a CTP copy? + #VisualStudio('TBD', + # hkey_root=r'TBD', + # common_tools_var='TBD', + # executable_path=r'TBD', + # default_dirname='TBD', + #), + + # Visual Studio 2008 + # The batch file we look for is in the VC directory, + # so the devenv.com executable is up in ..\..\Common7\IDE. + VisualStudio('9.0', + sdk_version='6.1', + hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'], + common_tools_var='VS90COMNTOOLS', + executable_path=r'Common7\IDE\devenv.com', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio 9', + supported_arch=['x86', 'amd64'], + ), + + # Visual C++ 2008 Express Edition + # The batch file we look for is in the VC directory, + # so the VCExpress.exe executable is up in ..\..\Common7\IDE. + VisualStudio('9.0Exp', + vc_version='9.0', + sdk_version='6.1', + hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'], + common_tools_var='VS90COMNTOOLS', + executable_path=r'Common7\IDE\VCExpress.exe', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio 9', + supported_arch=['x86'], + ), + + # Visual Studio 2005 + # The batch file we look for is in the VC directory, + # so the devenv.com executable is up in ..\..\Common7\IDE. + VisualStudio('8.0', + sdk_version='6.0A', + hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'], + common_tools_var='VS80COMNTOOLS', + executable_path=r'Common7\IDE\devenv.com', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio 8', + supported_arch=['x86', 'amd64'], + ), + + # Visual C++ 2005 Express Edition + # The batch file we look for is in the VC directory, + # so the VCExpress.exe executable is up in ..\..\Common7\IDE. + VisualStudio('8.0Exp', + vc_version='8.0', + sdk_version='6.0A', + hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'], + common_tools_var='VS80COMNTOOLS', + executable_path=r'Common7\IDE\VCExpress.exe', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio 8', + supported_arch=['x86'], + ), + + # Visual Studio .NET 2003 + # The batch file we look for is in the Common7\Tools directory, + # so the devenv.com executable is next door in ..\IDE. + VisualStudio('7.1', + sdk_version='6.0', + hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'], + common_tools_var='VS71COMNTOOLS', + executable_path=r'Common7\IDE\devenv.com', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio .NET 2003', + supported_arch=['x86'], + ), + + # Visual Studio .NET + # The batch file we look for is in the Common7\Tools directory, + # so the devenv.com executable is next door in ..\IDE. + VisualStudio('7.0', + sdk_version='2003R2', + hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'], + common_tools_var='VS70COMNTOOLS', + executable_path=r'IDE\devenv.com', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio .NET', + supported_arch=['x86'], + ), + + # Visual Studio 6.0 + VisualStudio('6.0', + sdk_version='2003R1', + hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir', + 'use_dir'], + common_tools_var='VS60COMNTOOLS', + executable_path=r'Common\MSDev98\Bin\MSDEV.COM', + batch_file_path=r'Common7\Tools\vsvars32.bat', + default_dirname='Microsoft Visual Studio', + supported_arch=['x86'], + ), +] + +SupportedVSMap = {} +for vs in SupportedVSList: + SupportedVSMap[vs.version] = vs + + +# Finding installed versions of Visual Studio isn't cheap, because it +# goes not only to the registry but also to the disk to sanity-check +# that there is, in fact, a Visual Studio directory there and that the +# registry entry isn't just stale. Find this information once, when +# requested, and cache it. + +InstalledVSList = None +InstalledVSMap = None + +def get_installed_visual_studios(): + global InstalledVSList + global InstalledVSMap + if InstalledVSList is None: + InstalledVSList = [] + InstalledVSMap = {} + for vs in SupportedVSList: + debug('trying to find VS %s' % vs.version) + if vs.get_executable(): + debug('found VS %s' % vs.version) + InstalledVSList.append(vs) + InstalledVSMap[vs.version] = vs + return InstalledVSList + +def reset_installed_visual_studios(): + global InstalledVSList + global InstalledVSMap + InstalledVSList = None + InstalledVSMap = None + for vs in SupportedVSList: + vs.reset() + + # Need to clear installed VC's as well as they are used in finding + # installed VS's + SCons.Tool.MSCommon.vc.reset_installed_vcs() + + +# We may be asked to update multiple construction environments with +# SDK information. When doing this, we check on-disk for whether +# the SDK has 'mfc' and 'atl' subdirectories. Since going to disk +# is expensive, cache results by directory. + +#SDKEnvironmentUpdates = {} +# +#def set_sdk_by_directory(env, sdk_dir): +# global SDKEnvironmentUpdates +# try: +# env_tuple_list = SDKEnvironmentUpdates[sdk_dir] +# except KeyError: +# env_tuple_list = [] +# SDKEnvironmentUpdates[sdk_dir] = env_tuple_list +# +# include_path = os.path.join(sdk_dir, 'include') +# mfc_path = os.path.join(include_path, 'mfc') +# atl_path = os.path.join(include_path, 'atl') +# +# if os.path.exists(mfc_path): +# env_tuple_list.append(('INCLUDE', mfc_path)) +# if os.path.exists(atl_path): +# env_tuple_list.append(('INCLUDE', atl_path)) +# env_tuple_list.append(('INCLUDE', include_path)) +# +# env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib'))) +# env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib'))) +# env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin'))) +# +# for variable, directory in env_tuple_list: +# env.PrependENVPath(variable, directory) + +def msvs_exists(): + return (len(get_installed_visual_studios()) > 0) + +def get_vs_by_version(msvs): + global InstalledVSMap + global SupportedVSMap + + if not SupportedVSMap.has_key(msvs): + msg = "Visual Studio version %s is not supported" % repr(msvs) + raise SCons.Errors.UserError, msg + get_installed_visual_studios() + vs = InstalledVSMap.get(msvs) + debug('InstalledVSMap:%s'%InstalledVSMap) + # Some check like this would let us provide a useful error message + # if they try to set a Visual Studio version that's not installed. + # However, we also want to be able to run tests (like the unit + # tests) on systems that don't, or won't ever, have it installed. + # It might be worth resurrecting this, with some configurable + # setting that the tests can use to bypass the check. + #if not vs: + # msg = "Visual Studio version %s is not installed" % repr(msvs) + # raise SCons.Errors.UserError, msg + return vs + +def get_default_version(env): + """Returns the default version string to use for MSVS. + + If no version was requested by the user through the MSVS environment + variable, query all the available the visual studios through + query_versions, and take the highest one. + + Return + ------ + version: str + the default version. + """ + if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']): + # TODO(1.5): + #versions = [vs.version for vs in get_installed_visual_studios()] + versions = map(lambda vs: vs.version, get_installed_visual_studios()) + env['MSVS'] = {'VERSIONS' : versions} + else: + versions = env['MSVS'].get('VERSIONS', []) + + if not env.has_key('MSVS_VERSION'): + if versions: + env['MSVS_VERSION'] = versions[0] #use highest version by default + else: + env['MSVS_VERSION'] = SupportedVSList[0].version + + env['MSVS']['VERSION'] = env['MSVS_VERSION'] + + return env['MSVS_VERSION'] + +def get_default_arch(env): + """Return the default arch to use for MSVS + + if no version was requested by the user through the MSVS_ARCH environment + variable, select x86 + + Return + ------ + arch: str + """ + arch = env.get('MSVS_ARCH', 'x86') + + msvs = InstalledVSMap.get(env['MSVS_VERSION']) + + if not msvs: + arch = 'x86' + elif not arch in msvs.get_supported_arch(): + fmt = "Visual Studio version %s does not support architecture %s" + raise SCons.Errors.UserError, fmt % (env['MSVS_VERSION'], arch) + + return arch + +def merge_default_version(env): + version = get_default_version(env) + arch = get_default_arch(env) + +def msvs_setup_env(env): + batfilename = msvs.get_batch_file() + msvs = get_vs_by_version(version) + if msvs is None: + return + + # XXX: I think this is broken. This will silently set a bogus tool instead + # of failing, but there is no other way with the current scons tool + # framework + if batfilename is not None: + + vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE') + + msvs_list = get_installed_visual_studios() + # TODO(1.5): + #vscommonvarnames = [ vs.common_tools_var for vs in msvs_list ] + vscommonvarnames = map(lambda vs: vs.common_tools_var, msvs_list) + nenv = normalize_env(env['ENV'], vscommonvarnames + ['COMSPEC']) + output = get_output(batfilename, arch, env=nenv) + vars = parse_output(output, vars) + + for k, v in vars.items(): + env.PrependENVPath(k, v, delete_existing=1) + +def query_versions(): + """Query the system to get available versions of VS. A version is + considered when a batfile is found.""" + msvs_list = get_installed_visual_studios() + # TODO(1.5) + #versions = [ msvs.version for msvs in msvs_list ] + versions = map(lambda msvs: msvs.version, msvs_list) + return versions + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/Perforce.py b/src/engine/SCons/Tool/Perforce.py new file mode 100644 index 0000000..441b2cb --- /dev/null +++ b/src/engine/SCons/Tool/Perforce.py @@ -0,0 +1,104 @@ +"""SCons.Tool.Perforce.py + +Tool-specific initialization for Perforce Source Code Management system. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/Perforce.py 4577 2009/12/27 19:44:43 scons" + +import os + +import SCons.Action +import SCons.Builder +import SCons.Node.FS +import SCons.Util + +# This function should maybe be moved to SCons.Util? +from SCons.Tool.PharLapCommon import addPathIfNotExists + + + +# Variables that we want to import from the base OS environment. +_import_env = [ 'P4PORT', 'P4CLIENT', 'P4USER', 'USER', 'USERNAME', 'P4PASSWD', + 'P4CHARSET', 'P4LANGUAGE', 'SystemRoot' ] + +PerforceAction = SCons.Action.Action('$P4COM', '$P4COMSTR') + +def generate(env): + """Add a Builder factory function and construction variables for + Perforce to an Environment.""" + + def PerforceFactory(env=env): + """ """ + return SCons.Builder.Builder(action = PerforceAction, env = env) + + #setattr(env, 'Perforce', PerforceFactory) + env.Perforce = PerforceFactory + + env['P4'] = 'p4' + env['P4FLAGS'] = SCons.Util.CLVar('') + env['P4COM'] = '$P4 $P4FLAGS sync $TARGET' + try: + environ = env['ENV'] + except KeyError: + environ = {} + env['ENV'] = environ + + # Perforce seems to use the PWD environment variable rather than + # calling getcwd() for itself, which is odd. If no PWD variable + # is present, p4 WILL call getcwd, but this seems to cause problems + # with good ol' Windows's tilde-mangling for long file names. + environ['PWD'] = env.Dir('#').get_abspath() + + for var in _import_env: + v = os.environ.get(var) + if v: + environ[var] = v + + if SCons.Util.can_read_reg: + # If we can read the registry, add the path to Perforce to our environment. + try: + k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE, + 'Software\\Perforce\\environment') + val, tok = SCons.Util.RegQueryValueEx(k, 'P4INSTROOT') + addPathIfNotExists(environ, 'PATH', val) + except SCons.Util.RegError: + # Can't detect where Perforce is, hope the user has it set in the + # PATH. + pass + +def exists(env): + return env.Detect('p4') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/Perforce.xml b/src/engine/SCons/Tool/Perforce.xml new file mode 100644 index 0000000..7bfcdc0 --- /dev/null +++ b/src/engine/SCons/Tool/Perforce.xml @@ -0,0 +1,47 @@ + + + +Sets construction variables for interacting with the +Perforce source code management system. + + +P4 +P4FLAGS +P4COM + + +P4COMSTR + + + + + +The Perforce executable. + + + + + +The command line used to +fetch source files from Perforce. + + + + + +The string displayed when +fetching a source file from Perforce. +If this is not set, then &cv-link-P4COM; (the command line) is displayed. + + + + + +General options that are passed to Perforce. + + diff --git a/src/engine/SCons/Tool/PharLapCommon.py b/src/engine/SCons/Tool/PharLapCommon.py new file mode 100644 index 0000000..dd5ddd1 --- /dev/null +++ b/src/engine/SCons/Tool/PharLapCommon.py @@ -0,0 +1,138 @@ +"""SCons.Tool.PharLapCommon + +This module contains common code used by all Tools for the +Phar Lap ETS tool chain. Right now, this is linkloc and +386asm. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/PharLapCommon.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import SCons.Errors +import SCons.Util +import re +import string + +def getPharLapPath(): + """Reads the registry to find the installed path of the Phar Lap ETS + development kit. + + Raises UserError if no installed version of Phar Lap can + be found.""" + + if not SCons.Util.can_read_reg: + raise SCons.Errors.InternalError, "No Windows registry module was found" + try: + k=SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, + 'SOFTWARE\\Pharlap\\ETS') + val, type = SCons.Util.RegQueryValueEx(k, 'BaseDir') + + # The following is a hack...there is (not surprisingly) + # an odd issue in the Phar Lap plug in that inserts + # a bunch of junk data after the phar lap path in the + # registry. We must trim it. + idx=val.find('\0') + if idx >= 0: + val = val[:idx] + + return os.path.normpath(val) + except SCons.Util.RegError: + raise SCons.Errors.UserError, "Cannot find Phar Lap ETS path in the registry. Is it installed properly?" + +REGEX_ETS_VER = re.compile(r'#define\s+ETS_VER\s+([0-9]+)') + +def getPharLapVersion(): + """Returns the version of the installed ETS Tool Suite as a + decimal number. This version comes from the ETS_VER #define in + the embkern.h header. For example, '#define ETS_VER 1010' (which + is what Phar Lap 10.1 defines) would cause this method to return + 1010. Phar Lap 9.1 does not have such a #define, but this method + will return 910 as a default. + + Raises UserError if no installed version of Phar Lap can + be found.""" + + include_path = os.path.join(getPharLapPath(), os.path.normpath("include/embkern.h")) + if not os.path.exists(include_path): + raise SCons.Errors.UserError, "Cannot find embkern.h in ETS include directory.\nIs Phar Lap ETS installed properly?" + mo = REGEX_ETS_VER.search(open(include_path, 'r').read()) + if mo: + return int(mo.group(1)) + # Default return for Phar Lap 9.1 + return 910 + +def addPathIfNotExists(env_dict, key, path, sep=os.pathsep): + """This function will take 'key' out of the dictionary + 'env_dict', then add the path 'path' to that key if it is not + already there. This treats the value of env_dict[key] as if it + has a similar format to the PATH variable...a list of paths + separated by tokens. The 'path' will get added to the list if it + is not already there.""" + try: + is_list = 1 + paths = env_dict[key] + if not SCons.Util.is_List(env_dict[key]): + paths = string.split(paths, sep) + is_list = 0 + if not os.path.normcase(path) in map(os.path.normcase, paths): + paths = [ path ] + paths + if is_list: + env_dict[key] = paths + else: + env_dict[key] = string.join(paths, sep) + except KeyError: + env_dict[key] = path + +def addPharLapPaths(env): + """This function adds the path to the Phar Lap binaries, includes, + and libraries, if they are not already there.""" + ph_path = getPharLapPath() + + try: + env_dict = env['ENV'] + except KeyError: + env_dict = {} + env['ENV'] = env_dict + addPathIfNotExists(env_dict, 'PATH', + os.path.join(ph_path, 'bin')) + addPathIfNotExists(env_dict, 'INCLUDE', + os.path.join(ph_path, 'include')) + addPathIfNotExists(env_dict, 'LIB', + os.path.join(ph_path, 'lib')) + addPathIfNotExists(env_dict, 'LIB', + os.path.join(ph_path, os.path.normpath('lib/vclib'))) + + env['PHARLAP_PATH'] = getPharLapPath() + env['PHARLAP_VERSION'] = str(getPharLapVersion()) + + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/PharLapCommonTests.py b/src/engine/SCons/Tool/PharLapCommonTests.py new file mode 100644 index 0000000..99e7524 --- /dev/null +++ b/src/engine/SCons/Tool/PharLapCommonTests.py @@ -0,0 +1,68 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/PharLapCommonTests.py 4577 2009/12/27 19:44:43 scons" + +import unittest +import os.path +import os +import sys + +import SCons.Errors +from SCons.Tool.PharLapCommon import * + +class PharLapCommonTestCase(unittest.TestCase): + def test_addPathIfNotExists(self): + """Test the addPathIfNotExists() function""" + env_dict = { 'FOO' : os.path.normpath('/foo/bar') + os.pathsep + \ + os.path.normpath('/baz/blat'), + 'BAR' : os.path.normpath('/foo/bar') + os.pathsep + \ + os.path.normpath('/baz/blat'), + 'BLAT' : [ os.path.normpath('/foo/bar'), + os.path.normpath('/baz/blat') ] } + addPathIfNotExists(env_dict, 'FOO', os.path.normpath('/foo/bar')) + addPathIfNotExists(env_dict, 'BAR', os.path.normpath('/bar/foo')) + addPathIfNotExists(env_dict, 'BAZ', os.path.normpath('/foo/baz')) + addPathIfNotExists(env_dict, 'BLAT', os.path.normpath('/baz/blat')) + addPathIfNotExists(env_dict, 'BLAT', os.path.normpath('/baz/foo')) + + assert env_dict['FOO'] == os.path.normpath('/foo/bar') + os.pathsep + \ + os.path.normpath('/baz/blat'), env_dict['FOO'] + assert env_dict['BAR'] == os.path.normpath('/bar/foo') + os.pathsep + \ + os.path.normpath('/foo/bar') + os.pathsep + \ + os.path.normpath('/baz/blat'), env_dict['BAR'] + assert env_dict['BAZ'] == os.path.normpath('/foo/baz'), env_dict['BAZ'] + assert env_dict['BLAT'] == [ os.path.normpath('/baz/foo'), + os.path.normpath('/foo/bar'), + os.path.normpath('/baz/blat') ], env_dict['BLAT' ] + +if __name__ == "__main__": + suite = unittest.makeSuite(PharLapCommonTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/RCS.py b/src/engine/SCons/Tool/RCS.py new file mode 100644 index 0000000..7605f73 --- /dev/null +++ b/src/engine/SCons/Tool/RCS.py @@ -0,0 +1,64 @@ +"""SCons.Tool.RCS.py + +Tool-specific initialization for RCS. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/RCS.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Util + +def generate(env): + """Add a Builder factory function and construction variables for + RCS to an Environment.""" + + def RCSFactory(env=env): + """ """ + act = SCons.Action.Action('$RCS_COCOM', '$RCS_COCOMSTR') + return SCons.Builder.Builder(action = act, env = env) + + #setattr(env, 'RCS', RCSFactory) + env.RCS = RCSFactory + + env['RCS'] = 'rcs' + env['RCS_CO'] = 'co' + env['RCS_COFLAGS'] = SCons.Util.CLVar('') + env['RCS_COCOM'] = '$RCS_CO $RCS_COFLAGS $TARGET' + +def exists(env): + return env.Detect('rcs') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/RCS.xml b/src/engine/SCons/Tool/RCS.xml new file mode 100644 index 0000000..52d8165 --- /dev/null +++ b/src/engine/SCons/Tool/RCS.xml @@ -0,0 +1,61 @@ + + + +Sets construction variables for the interaction +with the Revision Control System. + + +RCS +RCS_CO +RCS_COFLAGS +RCS_COCOM + + +RCS_COCOMSTR + + + + + +The RCS executable. +Note that this variable is not actually used +for the command to fetch source files from RCS; +see the +&cv-link-RCS_CO; +construction variable, below. + + + + + +The RCS "checkout" executable, +used to fetch source files from RCS. + + + + + +The command line used to +fetch (checkout) source files from RCS. + + + + + +The string displayed when fetching +a source file from RCS. +If this is not set, then &cv-link-RCS_COCOM; +(the command line) is displayed. + + + + + +Options that are passed to the &cv-link-RCS_CO; command. + + diff --git a/src/engine/SCons/Tool/SCCS.py b/src/engine/SCons/Tool/SCCS.py new file mode 100644 index 0000000..acbd9aa --- /dev/null +++ b/src/engine/SCons/Tool/SCCS.py @@ -0,0 +1,64 @@ +"""SCons.Tool.SCCS.py + +Tool-specific initialization for SCCS. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/SCCS.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Util + +def generate(env): + """Add a Builder factory function and construction variables for + SCCS to an Environment.""" + + def SCCSFactory(env=env): + """ """ + act = SCons.Action.Action('$SCCSCOM', '$SCCSCOMSTR') + return SCons.Builder.Builder(action = act, env = env) + + #setattr(env, 'SCCS', SCCSFactory) + env.SCCS = SCCSFactory + + env['SCCS'] = 'sccs' + env['SCCSFLAGS'] = SCons.Util.CLVar('') + env['SCCSGETFLAGS'] = SCons.Util.CLVar('') + env['SCCSCOM'] = '$SCCS $SCCSFLAGS get $SCCSGETFLAGS $TARGET' + +def exists(env): + return env.Detect('sccs') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/SCCS.xml b/src/engine/SCons/Tool/SCCS.xml new file mode 100644 index 0000000..83ccec7 --- /dev/null +++ b/src/engine/SCons/Tool/SCCS.xml @@ -0,0 +1,58 @@ + + + +Sets construction variables for interacting with the +Source Code Control System. + + +SCCS +SCCSFLAGS +SCCSGETFLAGS +SCCSCOM + + +SCCSCOMSTR + + + + + +The SCCS executable. + + + + + +The command line used to +fetch source files from SCCS. + + + + + +The string displayed when fetching +a source file from a CVS repository. +If this is not set, then &cv-link-SCCSCOM; +(the command line) is displayed. + + + + + +General options that are passed to SCCS. + + + + + +Options that are passed specifically to the SCCS "get" subcommand. +This can be set, for example, to + +to check out editable files from SCCS. + + diff --git a/src/engine/SCons/Tool/Subversion.py b/src/engine/SCons/Tool/Subversion.py new file mode 100644 index 0000000..55906bb --- /dev/null +++ b/src/engine/SCons/Tool/Subversion.py @@ -0,0 +1,71 @@ +"""SCons.Tool.Subversion.py + +Tool-specific initialization for Subversion. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/Subversion.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import SCons.Action +import SCons.Builder +import SCons.Util + +def generate(env): + """Add a Builder factory function and construction variables for + Subversion to an Environment.""" + + def SubversionFactory(repos, module='', env=env): + """ """ + # fail if repos is not an absolute path name? + if module != '': + module = os.path.join(module, '') + act = SCons.Action.Action('$SVNCOM', '$SVNCOMSTR') + return SCons.Builder.Builder(action = act, + env = env, + SVNREPOSITORY = repos, + SVNMODULE = module) + + #setattr(env, 'Subversion', SubversionFactory) + env.Subversion = SubversionFactory + + env['SVN'] = 'svn' + env['SVNFLAGS'] = SCons.Util.CLVar('') + env['SVNCOM'] = '$SVN $SVNFLAGS cat $SVNREPOSITORY/$SVNMODULE$TARGET > $TARGET' + +def exists(env): + return env.Detect('svn') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/Subversion.xml b/src/engine/SCons/Tool/Subversion.xml new file mode 100644 index 0000000..40ca399 --- /dev/null +++ b/src/engine/SCons/Tool/Subversion.xml @@ -0,0 +1,47 @@ + + + + + + + + diff --git a/src/engine/SCons/Tool/ToolTests.py b/src/engine/SCons/Tool/ToolTests.py new file mode 100644 index 0000000..0b6463e --- /dev/null +++ b/src/engine/SCons/Tool/ToolTests.py @@ -0,0 +1,84 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ToolTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Tool + +class ToolTestCase(unittest.TestCase): + def test_Tool(self): + """Test the Tool() function""" + class Environment: + def __init__(self): + self.dict = {} + def Detect(self, progs): + if not SCons.Util.is_List(progs): + progs = [ progs ] + return progs[0] + def Append(self, **kw): + self.dict.update(kw) + def __getitem__(self, key): + return self.dict[key] + def __setitem__(self, key, val): + self.dict[key] = val + def has_key(self, key): + return self.dict.has_key(key) + env = Environment() + env['BUILDERS'] = {} + env['ENV'] = {} + env['PLATFORM'] = 'test' + t = SCons.Tool.Tool('g++') + t(env) + assert (env['CXX'] == 'c++' or env['CXX'] == 'g++'), env['CXX'] + assert env['INCPREFIX'] == '-I', env['INCPREFIX'] + assert env['TOOLS'] == ['g++'], env['TOOLS'] + + try: + SCons.Tool.Tool() + except TypeError: + pass + else: + raise + + try: + p = SCons.Tool.Tool('_does_not_exist_') + except SCons.Errors.EnvironmentError: + pass + else: + raise + + +if __name__ == "__main__": + suite = unittest.makeSuite(ToolTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/__init__.py b/src/engine/SCons/Tool/__init__.py new file mode 100644 index 0000000..a0eb2e8 --- /dev/null +++ b/src/engine/SCons/Tool/__init__.py @@ -0,0 +1,675 @@ +"""SCons.Tool + +SCons tool selection. + +This looks for modules that define a callable object that can modify +a construction environment as appropriate for a given tool (or tool +chain). + +Note that because this subsystem just *selects* a callable that can +modify a construction environment, it's possible for people to define +their own "tool specification" in an arbitrary callable function. No +one needs to use or tie in to this subsystem in order to roll their own +tool definition. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/__init__.py 4577 2009/12/27 19:44:43 scons" + +import imp +import sys + +import SCons.Builder +import SCons.Errors +import SCons.Node.FS +import SCons.Scanner +import SCons.Scanner.C +import SCons.Scanner.D +import SCons.Scanner.LaTeX +import SCons.Scanner.Prog + +DefaultToolpath=[] + +CScanner = SCons.Scanner.C.CScanner() +DScanner = SCons.Scanner.D.DScanner() +LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner() +PDFLaTeXScanner = SCons.Scanner.LaTeX.PDFLaTeXScanner() +ProgramScanner = SCons.Scanner.Prog.ProgramScanner() +SourceFileScanner = SCons.Scanner.Base({}, name='SourceFileScanner') + +CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc", + ".h", ".H", ".hxx", ".hpp", ".hh", + ".F", ".fpp", ".FPP", + ".m", ".mm", + ".S", ".spp", ".SPP"] + +DSuffixes = ['.d'] + +IDLSuffixes = [".idl", ".IDL"] + +LaTeXSuffixes = [".tex", ".ltx", ".latex"] + +for suffix in CSuffixes: + SourceFileScanner.add_scanner(suffix, CScanner) + +for suffix in DSuffixes: + SourceFileScanner.add_scanner(suffix, DScanner) + +# FIXME: what should be done here? Two scanners scan the same extensions, +# but look for different files, e.g., "picture.eps" vs. "picture.pdf". +# The builders for DVI and PDF explicitly reference their scanners +# I think that means this is not needed??? +for suffix in LaTeXSuffixes: + SourceFileScanner.add_scanner(suffix, LaTeXScanner) + SourceFileScanner.add_scanner(suffix, PDFLaTeXScanner) + +class Tool: + def __init__(self, name, toolpath=[], **kw): + self.name = name + self.toolpath = toolpath + DefaultToolpath + # remember these so we can merge them into the call + self.init_kw = kw + + module = self._tool_module() + self.generate = module.generate + self.exists = module.exists + if hasattr(module, 'options'): + self.options = module.options + + def _tool_module(self): + # TODO: Interchange zipimport with normal initilization for better error reporting + oldpythonpath = sys.path + sys.path = self.toolpath + sys.path + + try: + try: + file, path, desc = imp.find_module(self.name, self.toolpath) + try: + return imp.load_module(self.name, file, path, desc) + finally: + if file: + file.close() + except ImportError, e: + if str(e)!="No module named %s"%self.name: + raise SCons.Errors.EnvironmentError, e + try: + import zipimport + except ImportError: + pass + else: + for aPath in self.toolpath: + try: + importer = zipimport.zipimporter(aPath) + return importer.load_module(self.name) + except ImportError, e: + pass + finally: + sys.path = oldpythonpath + + full_name = 'SCons.Tool.' + self.name + try: + return sys.modules[full_name] + except KeyError: + try: + smpath = sys.modules['SCons.Tool'].__path__ + try: + file, path, desc = imp.find_module(self.name, smpath) + module = imp.load_module(full_name, file, path, desc) + setattr(SCons.Tool, self.name, module) + if file: + file.close() + return module + except ImportError, e: + if str(e)!="No module named %s"%self.name: + raise SCons.Errors.EnvironmentError, e + try: + import zipimport + importer = zipimport.zipimporter( sys.modules['SCons.Tool'].__path__[0] ) + module = importer.load_module(full_name) + setattr(SCons.Tool, self.name, module) + return module + except ImportError, e: + m = "No tool named '%s': %s" % (self.name, e) + raise SCons.Errors.EnvironmentError, m + except ImportError, e: + m = "No tool named '%s': %s" % (self.name, e) + raise SCons.Errors.EnvironmentError, m + + def __call__(self, env, *args, **kw): + if self.init_kw is not None: + # Merge call kws into init kws; + # but don't bash self.init_kw. + if kw is not None: + call_kw = kw + kw = self.init_kw.copy() + kw.update(call_kw) + else: + kw = self.init_kw + env.Append(TOOLS = [ self.name ]) + if hasattr(self, 'options'): + import SCons.Variables + if not env.has_key('options'): + from SCons.Script import ARGUMENTS + env['options']=SCons.Variables.Variables(args=ARGUMENTS) + opts=env['options'] + + self.options(opts) + opts.Update(env) + + apply(self.generate, ( env, ) + args, kw) + + def __str__(self): + return self.name + +########################################################################## +# Create common executable program / library / object builders + +def createProgBuilder(env): + """This is a utility function that creates the Program + Builder in an Environment if it is not there already. + + If it is already there, we return the existing one. + """ + + try: + program = env['BUILDERS']['Program'] + except KeyError: + import SCons.Defaults + program = SCons.Builder.Builder(action = SCons.Defaults.LinkAction, + emitter = '$PROGEMITTER', + prefix = '$PROGPREFIX', + suffix = '$PROGSUFFIX', + src_suffix = '$OBJSUFFIX', + src_builder = 'Object', + target_scanner = ProgramScanner) + env['BUILDERS']['Program'] = program + + return program + +def createStaticLibBuilder(env): + """This is a utility function that creates the StaticLibrary + Builder in an Environment if it is not there already. + + If it is already there, we return the existing one. + """ + + try: + static_lib = env['BUILDERS']['StaticLibrary'] + except KeyError: + action_list = [ SCons.Action.Action("$ARCOM", "$ARCOMSTR") ] + if env.Detect('ranlib'): + ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR") + action_list.append(ranlib_action) + + static_lib = SCons.Builder.Builder(action = action_list, + emitter = '$LIBEMITTER', + prefix = '$LIBPREFIX', + suffix = '$LIBSUFFIX', + src_suffix = '$OBJSUFFIX', + src_builder = 'StaticObject') + env['BUILDERS']['StaticLibrary'] = static_lib + env['BUILDERS']['Library'] = static_lib + + return static_lib + +def createSharedLibBuilder(env): + """This is a utility function that creates the SharedLibrary + Builder in an Environment if it is not there already. + + If it is already there, we return the existing one. + """ + + try: + shared_lib = env['BUILDERS']['SharedLibrary'] + except KeyError: + import SCons.Defaults + action_list = [ SCons.Defaults.SharedCheck, + SCons.Defaults.ShLinkAction ] + shared_lib = SCons.Builder.Builder(action = action_list, + emitter = "$SHLIBEMITTER", + prefix = '$SHLIBPREFIX', + suffix = '$SHLIBSUFFIX', + target_scanner = ProgramScanner, + src_suffix = '$SHOBJSUFFIX', + src_builder = 'SharedObject') + env['BUILDERS']['SharedLibrary'] = shared_lib + + return shared_lib + +def createLoadableModuleBuilder(env): + """This is a utility function that creates the LoadableModule + Builder in an Environment if it is not there already. + + If it is already there, we return the existing one. + """ + + try: + ld_module = env['BUILDERS']['LoadableModule'] + except KeyError: + import SCons.Defaults + action_list = [ SCons.Defaults.SharedCheck, + SCons.Defaults.LdModuleLinkAction ] + ld_module = SCons.Builder.Builder(action = action_list, + emitter = "$LDMODULEEMITTER", + prefix = '$LDMODULEPREFIX', + suffix = '$LDMODULESUFFIX', + target_scanner = ProgramScanner, + src_suffix = '$SHOBJSUFFIX', + src_builder = 'SharedObject') + env['BUILDERS']['LoadableModule'] = ld_module + + return ld_module + +def createObjBuilders(env): + """This is a utility function that creates the StaticObject + and SharedObject Builders in an Environment if they + are not there already. + + If they are there already, we return the existing ones. + + This is a separate function because soooo many Tools + use this functionality. + + The return is a 2-tuple of (StaticObject, SharedObject) + """ + + + try: + static_obj = env['BUILDERS']['StaticObject'] + except KeyError: + static_obj = SCons.Builder.Builder(action = {}, + emitter = {}, + prefix = '$OBJPREFIX', + suffix = '$OBJSUFFIX', + src_builder = ['CFile', 'CXXFile'], + source_scanner = SourceFileScanner, + single_source = 1) + env['BUILDERS']['StaticObject'] = static_obj + env['BUILDERS']['Object'] = static_obj + + try: + shared_obj = env['BUILDERS']['SharedObject'] + except KeyError: + shared_obj = SCons.Builder.Builder(action = {}, + emitter = {}, + prefix = '$SHOBJPREFIX', + suffix = '$SHOBJSUFFIX', + src_builder = ['CFile', 'CXXFile'], + source_scanner = SourceFileScanner, + single_source = 1) + env['BUILDERS']['SharedObject'] = shared_obj + + return (static_obj, shared_obj) + +def createCFileBuilders(env): + """This is a utility function that creates the CFile/CXXFile + Builders in an Environment if they + are not there already. + + If they are there already, we return the existing ones. + + This is a separate function because soooo many Tools + use this functionality. + + The return is a 2-tuple of (CFile, CXXFile) + """ + + try: + c_file = env['BUILDERS']['CFile'] + except KeyError: + c_file = SCons.Builder.Builder(action = {}, + emitter = {}, + suffix = {None:'$CFILESUFFIX'}) + env['BUILDERS']['CFile'] = c_file + + env.SetDefault(CFILESUFFIX = '.c') + + try: + cxx_file = env['BUILDERS']['CXXFile'] + except KeyError: + cxx_file = SCons.Builder.Builder(action = {}, + emitter = {}, + suffix = {None:'$CXXFILESUFFIX'}) + env['BUILDERS']['CXXFile'] = cxx_file + env.SetDefault(CXXFILESUFFIX = '.cc') + + return (c_file, cxx_file) + +########################################################################## +# Create common Java builders + +def CreateJarBuilder(env): + try: + java_jar = env['BUILDERS']['Jar'] + except KeyError: + fs = SCons.Node.FS.get_default_fs() + jar_com = SCons.Action.Action('$JARCOM', '$JARCOMSTR') + java_jar = SCons.Builder.Builder(action = jar_com, + suffix = '$JARSUFFIX', + src_suffix = '$JAVACLASSSUFIX', + src_builder = 'JavaClassFile', + source_factory = fs.Entry) + env['BUILDERS']['Jar'] = java_jar + return java_jar + +def CreateJavaHBuilder(env): + try: + java_javah = env['BUILDERS']['JavaH'] + except KeyError: + fs = SCons.Node.FS.get_default_fs() + java_javah_com = SCons.Action.Action('$JAVAHCOM', '$JAVAHCOMSTR') + java_javah = SCons.Builder.Builder(action = java_javah_com, + src_suffix = '$JAVACLASSSUFFIX', + target_factory = fs.Entry, + source_factory = fs.File, + src_builder = 'JavaClassFile') + env['BUILDERS']['JavaH'] = java_javah + return java_javah + +def CreateJavaClassFileBuilder(env): + try: + java_class_file = env['BUILDERS']['JavaClassFile'] + except KeyError: + fs = SCons.Node.FS.get_default_fs() + javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR') + java_class_file = SCons.Builder.Builder(action = javac_com, + emitter = {}, + #suffix = '$JAVACLASSSUFFIX', + src_suffix = '$JAVASUFFIX', + src_builder = ['JavaFile'], + target_factory = fs.Entry, + source_factory = fs.File) + env['BUILDERS']['JavaClassFile'] = java_class_file + return java_class_file + +def CreateJavaClassDirBuilder(env): + try: + java_class_dir = env['BUILDERS']['JavaClassDir'] + except KeyError: + fs = SCons.Node.FS.get_default_fs() + javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR') + java_class_dir = SCons.Builder.Builder(action = javac_com, + emitter = {}, + target_factory = fs.Dir, + source_factory = fs.Dir) + env['BUILDERS']['JavaClassDir'] = java_class_dir + return java_class_dir + +def CreateJavaFileBuilder(env): + try: + java_file = env['BUILDERS']['JavaFile'] + except KeyError: + java_file = SCons.Builder.Builder(action = {}, + emitter = {}, + suffix = {None:'$JAVASUFFIX'}) + env['BUILDERS']['JavaFile'] = java_file + env['JAVASUFFIX'] = '.java' + return java_file + +class ToolInitializerMethod: + """ + This is added to a construction environment in place of a + method(s) normally called for a Builder (env.Object, env.StaticObject, + etc.). When called, it has its associated ToolInitializer + object search the specified list of tools and apply the first + one that exists to the construction environment. It then calls + whatever builder was (presumably) added to the construction + environment in place of this particular instance. + """ + def __init__(self, name, initializer): + """ + Note: we store the tool name as __name__ so it can be used by + the class that attaches this to a construction environment. + """ + self.__name__ = name + self.initializer = initializer + + def get_builder(self, env): + """ + Returns the appropriate real Builder for this method name + after having the associated ToolInitializer object apply + the appropriate Tool module. + """ + builder = getattr(env, self.__name__) + + self.initializer.apply_tools(env) + + builder = getattr(env, self.__name__) + if builder is self: + # There was no Builder added, which means no valid Tool + # for this name was found (or possibly there's a mismatch + # between the name we were called by and the Builder name + # added by the Tool module). + return None + + self.initializer.remove_methods(env) + + return builder + + def __call__(self, env, *args, **kw): + """ + """ + builder = self.get_builder(env) + if builder is None: + return [], [] + return apply(builder, args, kw) + +class ToolInitializer: + """ + A class for delayed initialization of Tools modules. + + Instances of this class associate a list of Tool modules with + a list of Builder method names that will be added by those Tool + modules. As part of instantiating this object for a particular + construction environment, we also add the appropriate + ToolInitializerMethod objects for the various Builder methods + that we want to use to delay Tool searches until necessary. + """ + def __init__(self, env, tools, names): + if not SCons.Util.is_List(tools): + tools = [tools] + if not SCons.Util.is_List(names): + names = [names] + self.env = env + self.tools = tools + self.names = names + self.methods = {} + for name in names: + method = ToolInitializerMethod(name, self) + self.methods[name] = method + env.AddMethod(method) + + def remove_methods(self, env): + """ + Removes the methods that were added by the tool initialization + so we no longer copy and re-bind them when the construction + environment gets cloned. + """ + for method in self.methods.values(): + env.RemoveMethod(method) + + def apply_tools(self, env): + """ + Searches the list of associated Tool modules for one that + exists, and applies that to the construction environment. + """ + for t in self.tools: + tool = SCons.Tool.Tool(t) + if tool.exists(env): + env.Tool(tool) + return + + # If we fall through here, there was no tool module found. + # This is where we can put an informative error message + # about the inability to find the tool. We'll start doing + # this as we cut over more pre-defined Builder+Tools to use + # the ToolInitializer class. + +def Initializers(env): + ToolInitializer(env, ['install'], ['_InternalInstall', '_InternalInstallAs']) + def Install(self, *args, **kw): + return apply(self._InternalInstall, args, kw) + def InstallAs(self, *args, **kw): + return apply(self._InternalInstallAs, args, kw) + env.AddMethod(Install) + env.AddMethod(InstallAs) + +def FindTool(tools, env): + for tool in tools: + t = Tool(tool) + if t.exists(env): + return tool + return None + +def FindAllTools(tools, env): + def ToolExists(tool, env=env): + return Tool(tool).exists(env) + return filter (ToolExists, tools) + +def tool_list(platform, env): + + other_plat_tools=[] + # XXX this logic about what tool to prefer on which platform + # should be moved into either the platform files or + # the tool files themselves. + # The search orders here are described in the man page. If you + # change these search orders, update the man page as well. + if str(platform) == 'win32': + "prefer Microsoft tools on Windows" + linkers = ['mslink', 'gnulink', 'ilink', 'linkloc', 'ilink32' ] + c_compilers = ['msvc', 'mingw', 'gcc', 'intelc', 'icl', 'icc', 'cc', 'bcc32' ] + cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'c++', 'bcc32' ] + assemblers = ['masm', 'nasm', 'gas', '386asm' ] + fortran_compilers = ['gfortran', 'g77', 'ifl', 'cvf', 'f95', 'f90', 'fortran'] + ars = ['mslib', 'ar', 'tlib'] + other_plat_tools=['msvs','midl'] + elif str(platform) == 'os2': + "prefer IBM tools on OS/2" + linkers = ['ilink', 'gnulink', ]#'mslink'] + c_compilers = ['icc', 'gcc',]# 'msvc', 'cc'] + cxx_compilers = ['icc', 'g++',]# 'msvc', 'c++'] + assemblers = ['nasm',]# 'masm', 'gas'] + fortran_compilers = ['ifl', 'g77'] + ars = ['ar',]# 'mslib'] + elif str(platform) == 'irix': + "prefer MIPSPro on IRIX" + linkers = ['sgilink', 'gnulink'] + c_compilers = ['sgicc', 'gcc', 'cc'] + cxx_compilers = ['sgic++', 'g++', 'c++'] + assemblers = ['as', 'gas'] + fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran'] + ars = ['sgiar'] + elif str(platform) == 'sunos': + "prefer Forte tools on SunOS" + linkers = ['sunlink', 'gnulink'] + c_compilers = ['suncc', 'gcc', 'cc'] + cxx_compilers = ['sunc++', 'g++', 'c++'] + assemblers = ['as', 'gas'] + fortran_compilers = ['sunf95', 'sunf90', 'sunf77', 'f95', 'f90', 'f77', + 'gfortran', 'g77', 'fortran'] + ars = ['sunar'] + elif str(platform) == 'hpux': + "prefer aCC tools on HP-UX" + linkers = ['hplink', 'gnulink'] + c_compilers = ['hpcc', 'gcc', 'cc'] + cxx_compilers = ['hpc++', 'g++', 'c++'] + assemblers = ['as', 'gas'] + fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran'] + ars = ['ar'] + elif str(platform) == 'aix': + "prefer AIX Visual Age tools on AIX" + linkers = ['aixlink', 'gnulink'] + c_compilers = ['aixcc', 'gcc', 'cc'] + cxx_compilers = ['aixc++', 'g++', 'c++'] + assemblers = ['as', 'gas'] + fortran_compilers = ['f95', 'f90', 'aixf77', 'g77', 'fortran'] + ars = ['ar'] + elif str(platform) == 'darwin': + "prefer GNU tools on Mac OS X, except for some linkers and IBM tools" + linkers = ['applelink', 'gnulink'] + c_compilers = ['gcc', 'cc'] + cxx_compilers = ['g++', 'c++'] + assemblers = ['as'] + fortran_compilers = ['gfortran', 'f95', 'f90', 'g77'] + ars = ['ar'] + else: + "prefer GNU tools on all other platforms" + linkers = ['gnulink', 'mslink', 'ilink'] + c_compilers = ['gcc', 'msvc', 'intelc', 'icc', 'cc'] + cxx_compilers = ['g++', 'msvc', 'intelc', 'icc', 'c++'] + assemblers = ['gas', 'nasm', 'masm'] + fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77'] + ars = ['ar', 'mslib'] + + c_compiler = FindTool(c_compilers, env) or c_compilers[0] + + # XXX this logic about what tool provides what should somehow be + # moved into the tool files themselves. + if c_compiler and c_compiler == 'mingw': + # MinGW contains a linker, C compiler, C++ compiler, + # Fortran compiler, archiver and assembler: + cxx_compiler = None + linker = None + assembler = None + fortran_compiler = None + ar = None + else: + # Don't use g++ if the C compiler has built-in C++ support: + if c_compiler in ('msvc', 'intelc', 'icc'): + cxx_compiler = None + else: + cxx_compiler = FindTool(cxx_compilers, env) or cxx_compilers[0] + linker = FindTool(linkers, env) or linkers[0] + assembler = FindTool(assemblers, env) or assemblers[0] + fortran_compiler = FindTool(fortran_compilers, env) or fortran_compilers[0] + ar = FindTool(ars, env) or ars[0] + + other_tools = FindAllTools(['BitKeeper', 'CVS', + 'dmd', + 'filesystem', + 'dvipdf', 'dvips', 'gs', + 'jar', 'javac', 'javah', + 'latex', 'lex', + 'm4', #'midl', 'msvs', + 'pdflatex', 'pdftex', 'Perforce', + 'RCS', 'rmic', 'rpcgen', + 'SCCS', + # 'Subversion', + 'swig', + 'tar', 'tex', + 'yacc', 'zip', 'rpm', 'wix']+other_plat_tools, + env) + + tools = ([linker, c_compiler, cxx_compiler, + fortran_compiler, assembler, ar] + + other_tools) + + return filter(lambda x: x, tools) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/__init__.xml b/src/engine/SCons/Tool/__init__.xml new file mode 100644 index 0000000..fa3e37a --- /dev/null +++ b/src/engine/SCons/Tool/__init__.xml @@ -0,0 +1,367 @@ + + + +Builds a C source file given a lex (.l) +or yacc (.y) input file. +The suffix specified by the &cv-link-CFILESUFFIX; construction variable +(.c by default) +is automatically added to the target +if it is not already present. +Example: + + +# builds foo.c +env.CFile(target = 'foo.c', source = 'foo.l') +# builds bar.c +env.CFile(target = 'bar', source = 'bar.y') + + + + + + +Builds a C++ source file given a lex (.ll) +or yacc (.yy) +input file. +The suffix specified by the &cv-link-CXXFILESUFFIX; construction variable +(.cc by default) +is automatically added to the target +if it is not already present. +Example: + + +# builds foo.cc +env.CXXFile(target = 'foo.cc', source = 'foo.ll') +# builds bar.cc +env.CXXFile(target = 'bar', source = 'bar.yy') + + + + + + +A synonym for the +&b-StaticLibrary; +builder method. + + + + + +On most systems, +this is the same as +&b-SharedLibrary;. +On Mac OS X (Darwin) platforms, +this creates a loadable module bundle. + + + + + +A synonym for the +&b-StaticObject; +builder method. + + + + + +Builds an executable given one or more object files +or C, C++, D, or Fortran source files. +If any C, C++, D or Fortran source files are specified, +then they will be automatically +compiled to object files using the +&b-Object; +builder method; +see that builder method's description for +a list of legal source file suffixes +and how they are interpreted. +The target executable file prefix +(specified by the &cv-link-PROGPREFIX; construction variable; nothing by default) +and suffix +(specified by the &cv-link-PROGSUFFIX; construction variable; +by default, .exe on Windows systems, +nothing on POSIX systems) +are automatically added to the target if not already present. +Example: + + +env.Program(target = 'foo', source = ['foo.o', 'bar.c', 'baz.f']) + + + + + + +Builds a shared library +(.so on a POSIX system, +.dll on Windows) +given one or more object files +or C, C++, D or Fortran source files. +If any source files are given, +then they will be automatically +compiled to object files. +The static library prefix and suffix (if any) +are automatically added to the target. +The target library file prefix +(specified by the &cv-link-SHLIBPREFIX; construction variable; +by default, lib on POSIX systems, +nothing on Windows systems) +and suffix +(specified by the &cv-link-SHLIBSUFFIX; construction variable; +by default, .dll on Windows systems, +.so on POSIX systems) +are automatically added to the target if not already present. +Example: + + +env.SharedLibrary(target = 'bar', source = ['bar.c', 'foo.o']) + + +On Windows systems, the +&b-SharedLibrary; +builder method will always build an import +(.lib) library +in addition to the shared (.dll) library, +adding a .lib library with the same basename +if there is not already a .lib file explicitly +listed in the targets. + +Any object files listed in the +source +must have been built for a shared library +(that is, using the +&b-SharedObject; +builder method). +&scons; +will raise an error if there is any mismatch. + +On some platforms, there is a distinction between a shared library +(loaded automatically by the system to resolve external references) +and a loadable module (explicitly loaded by user action). +For maximum portability, use the &b-LoadableModule; builder for the latter. + +On Windows systems, specifying +register=1 +will cause the .dll to be +registered after it is built using REGSVR32. +The command that is run +("regsvr32" by default) is determined by &cv-link-REGSVR; construction +variable, and the flags passed are determined by &cv-link-REGSVRFLAGS;. By +default, &cv-link-REGSVRFLAGS; includes the option, +to prevent dialogs from popping +up and requiring user attention when it is run. If you change +&cv-link-REGSVRFLAGS;, be sure to include the option. +For example, + + +env.SharedLibrary(target = 'bar', + source = ['bar.cxx', 'foo.obj'], + register=1) + + +will register bar.dll as a COM object +when it is done linking it. + + + + + +Builds an object file for +inclusion in a shared library. +Source files must have one of the same set of extensions +specified above for the +&b-StaticObject; +builder method. +On some platforms building a shared object requires additional +compiler option +(e.g. for gcc) +in addition to those needed to build a +normal (static) object, but on some platforms there is no difference between a +shared object and a normal (static) one. When there is a difference, SCons +will only allow shared objects to be linked into a shared library, and will +use a different suffix for shared objects. On platforms where there is no +difference, SCons will allow both normal (static) +and shared objects to be linked into a +shared library, and will use the same suffix for shared and normal +(static) objects. +The target object file prefix +(specified by the &cv-link-SHOBJPREFIX; construction variable; +by default, the same as &cv-link-OBJPREFIX;) +and suffix +(specified by the &cv-link-SHOBJSUFFIX; construction variable) +are automatically added to the target if not already present. +Examples: + + +env.SharedObject(target = 'ddd', source = 'ddd.c') +env.SharedObject(target = 'eee.o', source = 'eee.cpp') +env.SharedObject(target = 'fff.obj', source = 'fff.for') + + +Note that the source files will be scanned +according to the suffix mappings in the +SourceFileScanner +object. +See the section "Scanner Objects," +below, for a more information. + + + + + +Builds a static library given one or more object files +or C, C++, D or Fortran source files. +If any source files are given, +then they will be automatically +compiled to object files. +The static library prefix and suffix (if any) +are automatically added to the target. +The target library file prefix +(specified by the &cv-link-LIBPREFIX; construction variable; +by default, lib on POSIX systems, +nothing on Windows systems) +and suffix +(specified by the &cv-link-LIBSUFFIX; construction variable; +by default, .lib on Windows systems, +.a on POSIX systems) +are automatically added to the target if not already present. +Example: + + +env.StaticLibrary(target = 'bar', source = ['bar.c', 'foo.o']) + + +Any object files listed in the +source +must have been built for a static library +(that is, using the +&b-StaticObject; +builder method). +&scons; +will raise an error if there is any mismatch. + + + + + +Builds a static object file +from one or more C, C++, D, or Fortran source files. +Source files must have one of the following extensions: + + + .asm assembly language file + .ASM assembly language file + .c C file + .C Windows: C file + POSIX: C++ file + .cc C++ file + .cpp C++ file + .cxx C++ file + .cxx C++ file + .c++ C++ file + .C++ C++ file + .d D file + .f Fortran file + .F Windows: Fortran file + POSIX: Fortran file + C pre-processor + .for Fortran file + .FOR Fortran file + .fpp Fortran file + C pre-processor + .FPP Fortran file + C pre-processor + .m Object C file + .mm Object C++ file + .s assembly language file + .S Windows: assembly language file + ARM: CodeSourcery Sourcery Lite + .sx assembly language file + C pre-processor + POSIX: assembly language file + C pre-processor + .spp assembly language file + C pre-processor + .SPP assembly language file + C pre-processor + + +The target object file prefix +(specified by the &cv-link-OBJPREFIX; construction variable; nothing by default) +and suffix +(specified by the &cv-link-OBJSUFFIX; construction variable; +.obj on Windows systems, +.o on POSIX systems) +are automatically added to the target if not already present. +Examples: + + +env.StaticObject(target = 'aaa', source = 'aaa.c') +env.StaticObject(target = 'bbb.o', source = 'bbb.c++') +env.StaticObject(target = 'ccc.obj', source = 'ccc.f') + + +Note that the source files will be scanned +according to the suffix mappings in +SourceFileScanner +object. +See the section "Scanner Objects," +below, for a more information. + + + + + +The version number of the C compiler. +This may or may not be set, +depending on the specific C compiler being used. + + + + + +The suffix for C source files. +This is used by the internal CFile builder +when generating C files from Lex (.l) or YACC (.y) input files. +The default suffix, of course, is +.c +(lower case). +On case-insensitive systems (like Windows), +SCons also treats +.C +(upper case) files +as C files. + + + + + +The version number of the C++ compiler. +This may or may not be set, +depending on the specific C++ compiler being used. + + + + + +The suffix for C++ source files. +This is used by the internal CXXFile builder +when generating C++ files from Lex (.ll) or YACC (.yy) input files. +The default suffix is +.cc. +SCons also treats files with the suffixes +.cpp, +.cxx, +.c++, +and +.C++ +as C++ files, +and files with +.mm +suffixes as Objective C++ files. +On case-sensitive systems (Linux, UNIX, and other POSIX-alikes), +SCons also treats +.C +(upper case) files +as C++ files. + + diff --git a/src/engine/SCons/Tool/aixc++.py b/src/engine/SCons/Tool/aixc++.py new file mode 100644 index 0000000..c141cec --- /dev/null +++ b/src/engine/SCons/Tool/aixc++.py @@ -0,0 +1,82 @@ +"""SCons.Tool.aixc++ + +Tool-specific initialization for IBM xlC / Visual Age C++ compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/aixc++.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import SCons.Platform.aix + +cplusplus = __import__('c++', globals(), locals(), []) + +packages = ['vacpp.cmp.core', 'vacpp.cmp.batch', 'vacpp.cmp.C', 'ibmcxx.cmp'] + +def get_xlc(env): + xlc = env.get('CXX', 'xlC') + xlc_r = env.get('SHCXX', 'xlC_r') + return SCons.Platform.aix.get_xlc(env, xlc, xlc_r, packages) + +def smart_cxxflags(source, target, env, for_signature): + build_dir = env.GetBuildPath() + if build_dir: + return '-qtempinc=' + os.path.join(build_dir, 'tempinc') + return '' + +def generate(env): + """Add Builders and construction variables for xlC / Visual Age + suite to an Environment.""" + path, _cxx, _shcxx, version = get_xlc(env) + if path: + _cxx = os.path.join(path, _cxx) + _shcxx = os.path.join(path, _shcxx) + + cplusplus.generate(env) + + env['CXX'] = _cxx + env['SHCXX'] = _shcxx + env['CXXVERSION'] = version + env['SHOBJSUFFIX'] = '.pic.o' + +def exists(env): + path, _cxx, _shcxx, version = get_xlc(env) + if path and _cxx: + xlc = os.path.join(path, _cxx) + if os.path.exists(xlc): + return xlc + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/aixc++.xml b/src/engine/SCons/Tool/aixc++.xml new file mode 100644 index 0000000..d4fcdaf --- /dev/null +++ b/src/engine/SCons/Tool/aixc++.xml @@ -0,0 +1,19 @@ + + + +Sets construction variables for the IMB xlc / Visual Age C++ compiler. + + +CXX +SHCXX +CXXVERSION +SHOBJSUFFIX + + + + diff --git a/src/engine/SCons/Tool/aixcc.py b/src/engine/SCons/Tool/aixcc.py new file mode 100644 index 0000000..23ef705 --- /dev/null +++ b/src/engine/SCons/Tool/aixcc.py @@ -0,0 +1,74 @@ +"""SCons.Tool.aixcc + +Tool-specific initialization for IBM xlc / Visual Age C compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/aixcc.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import SCons.Platform.aix + +import cc + +packages = ['vac.C', 'ibmcxx.cmp'] + +def get_xlc(env): + xlc = env.get('CC', 'xlc') + xlc_r = env.get('SHCC', 'xlc_r') + return SCons.Platform.aix.get_xlc(env, xlc, xlc_r, packages) + +def generate(env): + """Add Builders and construction variables for xlc / Visual Age + suite to an Environment.""" + path, _cc, _shcc, version = get_xlc(env) + if path: + _cc = os.path.join(path, _cc) + _shcc = os.path.join(path, _shcc) + + cc.generate(env) + + env['CC'] = _cc + env['SHCC'] = _shcc + env['CCVERSION'] = version + +def exists(env): + path, _cc, _shcc, version = get_xlc(env) + if path and _cc: + xlc = os.path.join(path, _cc) + if os.path.exists(xlc): + return xlc + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/aixcc.xml b/src/engine/SCons/Tool/aixcc.xml new file mode 100644 index 0000000..df1c4f3 --- /dev/null +++ b/src/engine/SCons/Tool/aixcc.xml @@ -0,0 +1,18 @@ + + + +Sets construction variables for the IBM xlc / Visual Age C compiler. + + +CC +SHCC +CCVERSION + + + + diff --git a/src/engine/SCons/Tool/aixf77.py b/src/engine/SCons/Tool/aixf77.py new file mode 100644 index 0000000..5c0a3a7 --- /dev/null +++ b/src/engine/SCons/Tool/aixf77.py @@ -0,0 +1,80 @@ +"""engine.SCons.Tool.aixf77 + +Tool-specific initialization for IBM Visual Age f77 Fortran compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/aixf77.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +#import SCons.Platform.aix + +import f77 + +# It would be good to look for the AIX F77 package the same way we're now +# looking for the C and C++ packages. This should be as easy as supplying +# the correct package names in the following list and uncommenting the +# SCons.Platform.aix_get_xlc() call the in the function below. +packages = [] + +def get_xlf77(env): + xlf77 = env.get('F77', 'xlf77') + xlf77_r = env.get('SHF77', 'xlf77_r') + #return SCons.Platform.aix.get_xlc(env, xlf77, xlf77_r, packages) + return (None, xlf77, xlf77_r, None) + +def generate(env): + """ + Add Builders and construction variables for the Visual Age FORTRAN + compiler to an Environment. + """ + path, _f77, _shf77, version = get_xlf77(env) + if path: + _f77 = os.path.join(path, _f77) + _shf77 = os.path.join(path, _shf77) + + f77.generate(env) + + env['F77'] = _f77 + env['SHF77'] = _shf77 + +def exists(env): + path, _f77, _shf77, version = get_xlf77(env) + if path and _f77: + xlf77 = os.path.join(path, _f77) + if os.path.exists(xlf77): + return xlf77 + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/aixf77.xml b/src/engine/SCons/Tool/aixf77.xml new file mode 100644 index 0000000..aa428c4 --- /dev/null +++ b/src/engine/SCons/Tool/aixf77.xml @@ -0,0 +1,17 @@ + + + +Sets construction variables for the IBM Visual Age f77 Fortran compiler. + + +F77 +SHF77 + + + + diff --git a/src/engine/SCons/Tool/aixlink.py b/src/engine/SCons/Tool/aixlink.py new file mode 100644 index 0000000..2c036ff --- /dev/null +++ b/src/engine/SCons/Tool/aixlink.py @@ -0,0 +1,76 @@ +"""SCons.Tool.aixlink + +Tool-specific initialization for the IBM Visual Age linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/aixlink.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path + +import SCons.Util + +import aixcc +import link + +cplusplus = __import__('c++', globals(), locals(), []) + +def smart_linkflags(source, target, env, for_signature): + if cplusplus.iscplusplus(source): + build_dir = env.subst('$BUILDDIR', target=target, source=source) + if build_dir: + return '-qtempinc=' + os.path.join(build_dir, 'tempinc') + return '' + +def generate(env): + """ + Add Builders and construction variables for Visual Age linker to + an Environment. + """ + link.generate(env) + + env['SMARTLINKFLAGS'] = smart_linkflags + env['LINKFLAGS'] = SCons.Util.CLVar('$SMARTLINKFLAGS') + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -qmkshrobj -qsuppress=1501-218') + env['SHLIBSUFFIX'] = '.a' + +def exists(env): + path, _cc, _shcc, version = aixcc.get_xlc(env) + if path and _cc: + xlc = os.path.join(path, _cc) + if os.path.exists(xlc): + return xlc + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/aixlink.xml b/src/engine/SCons/Tool/aixlink.xml new file mode 100644 index 0000000..b2ef46e --- /dev/null +++ b/src/engine/SCons/Tool/aixlink.xml @@ -0,0 +1,19 @@ + + + +Sets construction variables for the IBM Visual Age linker. + + + +LINKFLAGS +SHLINKFLAGS +SHLIBSUFFIX + + + + diff --git a/src/engine/SCons/Tool/applelink.py b/src/engine/SCons/Tool/applelink.py new file mode 100644 index 0000000..4e3b802 --- /dev/null +++ b/src/engine/SCons/Tool/applelink.py @@ -0,0 +1,71 @@ +"""SCons.Tool.applelink + +Tool-specific initialization for the Apple gnu-like linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/applelink.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +# Even though the Mac is based on the GNU toolchain, it doesn't understand +# the -rpath option, so we use the "link" tool instead of "gnulink". +import link + +def generate(env): + """Add Builders and construction variables for applelink to an + Environment.""" + link.generate(env) + + env['FRAMEWORKPATHPREFIX'] = '-F' + env['_FRAMEWORKPATH'] = '${_concat(FRAMEWORKPATHPREFIX, FRAMEWORKPATH, "", __env__)}' + env['_FRAMEWORKS'] = '${_concat("-framework ", FRAMEWORKS, "", __env__)}' + env['LINKCOM'] = env['LINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -dynamiclib') + env['SHLINKCOM'] = env['SHLINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS' + + # override the default for loadable modules, which are different + # on OS X than dynamic shared libs. echoing what XCode does for + # pre/suffixes: + env['LDMODULEPREFIX'] = '' + env['LDMODULESUFFIX'] = '' + env['LDMODULEFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -bundle') + env['LDMODULECOM'] = '$LDMODULE -o ${TARGET} $LDMODULEFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS' + + + +def exists(env): + return env['PLATFORM'] == 'darwin' + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/applelink.xml b/src/engine/SCons/Tool/applelink.xml new file mode 100644 index 0000000..29c8183 --- /dev/null +++ b/src/engine/SCons/Tool/applelink.xml @@ -0,0 +1,114 @@ + + + +Sets construction variables for the Apple linker +(similar to the GNU linker). + + +FRAMEWORKPATHPREFIX +_FRAMEWORKPATH +_FRAMEWORKS +LINKCOM +SHLINKFLAGS +SHLINKCOM +LDMODULEPREFIX +LDMODULESUFFIX +LDMODULEFLAGS +LDMODULECOM + + +FRAMEWORKSFLAGS + + + +"> + +On Mac OS X with gcc, +general user-supplied frameworks options to be added at +the end of a command +line building a loadable module. +(This has been largely superceded by +the &cv-link-FRAMEWORKPATH;, &cv-link-FRAMEWORKPATHPREFIX;, +&cv-link-FRAMEWORKPREFIX; and &cv-link-FRAMEWORKS; variables +described above.) + + + + + +On Mac OS X with gcc, a list of the framework names to be linked into a +program or shared library or bundle. +The default value is the empty list. +For example: + + + env.AppendUnique(FRAMEWORKS=Split('System Cocoa SystemConfiguration')) + + + + + + + +On Mac OS X with gcc, +the prefix to be used for linking in frameworks +(see &cv-link-FRAMEWORKS;). +The default value is +. + + + + + +On Mac OS X with gcc, +an automatically-generated construction variable +containing the linker command-line options +for linking with FRAMEWORKS. + + + + + +On Mac OS X with gcc, +a list containing the paths to search for frameworks. +Used by the compiler to find framework-style includes like +#include <Fmwk/Header.h>. +Used by the linker to find user-specified frameworks when linking (see +&cv-link-FRAMEWORKS;). +For example: + + + env.AppendUnique(FRAMEWORKPATH='#myframeworkdir') + + +will add + + + ... -Fmyframeworkdir + + +to the compiler and linker command lines. + + + + + +On Mac OS X with gcc, the prefix to be used for the FRAMEWORKPATH entries. +(see &cv-link-FRAMEWORKPATH;). +The default value is +. + + + + + +On Mac OS X with gcc, an automatically-generated construction variable +containing the linker command-line options corresponding to +&cv-link-FRAMEWORKPATH;. + + diff --git a/src/engine/SCons/Tool/ar.py b/src/engine/SCons/Tool/ar.py new file mode 100644 index 0000000..cdffcdf --- /dev/null +++ b/src/engine/SCons/Tool/ar.py @@ -0,0 +1,63 @@ +"""SCons.Tool.ar + +Tool-specific initialization for ar (library archive). + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ar.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + + +def generate(env): + """Add Builders and construction variables for ar to an Environment.""" + SCons.Tool.createStaticLibBuilder(env) + + env['AR'] = 'ar' + env['ARFLAGS'] = SCons.Util.CLVar('rc') + env['ARCOM'] = '$AR $ARFLAGS $TARGET $SOURCES' + env['LIBPREFIX'] = 'lib' + env['LIBSUFFIX'] = '.a' + + if env.Detect('ranlib'): + env['RANLIB'] = 'ranlib' + env['RANLIBFLAGS'] = SCons.Util.CLVar('') + env['RANLIBCOM'] = '$RANLIB $RANLIBFLAGS $TARGET' + +def exists(env): + return env.Detect('ar') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/ar.xml b/src/engine/SCons/Tool/ar.xml new file mode 100644 index 0000000..a359823 --- /dev/null +++ b/src/engine/SCons/Tool/ar.xml @@ -0,0 +1,82 @@ + + + +Sets construction variables for the &ar; library archiver. + + +AR +ARFLAGS +ARCOM +LIBPREFIX +LIBSUFFIX +RANLIB +RANLIBFLAGS +RANLIBCOM + + + + + + + +The static library archiver. + + + + + +The command line used to generate a static library from object files. + + + + + +The string displayed when an object file +is generated from an assembly-language source file. +If this is not set, then &cv-link-ARCOM; (the command line) is displayed. + + +env = Environment(ARCOMSTR = "Archiving $TARGET") + + + + + + +General options passed to the static library archiver. + + + + + +The archive indexer. + + + + + +The command line used to index a static library archive. + + + + + +The string displayed when a static library archive is indexed. +If this is not set, then &cv-link-RANLIBCOM; (the command line) is displayed. + + +env = Environment(RANLIBCOMSTR = "Indexing $TARGET") + + + + + + +General options passed to the archive indexer. + + diff --git a/src/engine/SCons/Tool/as.py b/src/engine/SCons/Tool/as.py new file mode 100644 index 0000000..2e180ce --- /dev/null +++ b/src/engine/SCons/Tool/as.py @@ -0,0 +1,78 @@ +"""SCons.Tool.as + +Tool-specific initialization for as, the generic Posix assembler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/as.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +assemblers = ['as'] + +ASSuffixes = ['.s', '.asm', '.ASM'] +ASPPSuffixes = ['.spp', '.SPP', '.sx'] +if SCons.Util.case_sensitive_suffixes('.s', '.S'): + ASPPSuffixes.extend(['.S']) +else: + ASSuffixes.extend(['.S']) + +def generate(env): + """Add Builders and construction variables for as to an Environment.""" + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in ASSuffixes: + static_obj.add_action(suffix, SCons.Defaults.ASAction) + shared_obj.add_action(suffix, SCons.Defaults.ASAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + for suffix in ASPPSuffixes: + static_obj.add_action(suffix, SCons.Defaults.ASPPAction) + shared_obj.add_action(suffix, SCons.Defaults.ASPPAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + env['AS'] = env.Detect(assemblers) or 'as' + env['ASFLAGS'] = SCons.Util.CLVar('') + env['ASCOM'] = '$AS $ASFLAGS -o $TARGET $SOURCES' + env['ASPPFLAGS'] = '$ASFLAGS' + env['ASPPCOM'] = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES' + +def exists(env): + return env.Detect(assemblers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/as.xml b/src/engine/SCons/Tool/as.xml new file mode 100644 index 0000000..94af6b6 --- /dev/null +++ b/src/engine/SCons/Tool/as.xml @@ -0,0 +1,88 @@ + + + +Sets construction variables for the &as; assembler. + + +AS +ASFLAGS +ASCOM +ASPPFLAGS +ASPPCOM + + +CC +CPPFLAGS +_CPPDEFFLAGS +_CPPINCFLAGS + + + + + +The assembler. + + + + + +The command line used to generate an object file +from an assembly-language source file. + + + + + +The string displayed when an object file +is generated from an assembly-language source file. +If this is not set, then &cv-link-ASCOM; (the command line) is displayed. + + +env = Environment(ASCOMSTR = "Assembling $TARGET") + + + + + + +General options passed to the assembler. + + + + + +The command line used to assemble an assembly-language +source file into an object file +after first running the file through the C preprocessor. +Any options specified +in the &cv-link-ASFLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. + + + + + +The string displayed when an object file +is generated from an assembly-language source file +after first running the file through the C preprocessor. +If this is not set, then &cv-link-ASPPCOM; (the command line) is displayed. + + +env = Environment(ASPPCOMSTR = "Assembling $TARGET") + + + + + + +General options when an assembling an assembly-language +source file into an object file +after first running the file through the C preprocessor. +The default is to use the value of &cv-link-ASFLAGS;. + + diff --git a/src/engine/SCons/Tool/bcc32.py b/src/engine/SCons/Tool/bcc32.py new file mode 100644 index 0000000..71daa2c --- /dev/null +++ b/src/engine/SCons/Tool/bcc32.py @@ -0,0 +1,82 @@ +"""SCons.Tool.bcc32 + +XXX + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/bcc32.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +def findIt(program, env): + # First search in the SCons path and then the OS path: + borwin = env.WhereIs(program) or SCons.Util.WhereIs(program) + if borwin: + dir = os.path.dirname(borwin) + env.PrependENVPath('PATH', dir) + return borwin + +def generate(env): + findIt('bcc32', env) + """Add Builders and construction variables for bcc to an + Environment.""" + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + for suffix in ['.c', '.cpp']: + static_obj.add_action(suffix, SCons.Defaults.CAction) + shared_obj.add_action(suffix, SCons.Defaults.ShCAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + env['CC'] = 'bcc32' + env['CCFLAGS'] = SCons.Util.CLVar('') + env['CFLAGS'] = SCons.Util.CLVar('') + env['CCCOM'] = '$CC -q $CFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o$TARGET $SOURCES' + env['SHCC'] = '$CC' + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS') + env['SHCCCOM'] = '$SHCC -WD $SHCFLAGS $SHCCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o$TARGET $SOURCES' + env['CPPDEFPREFIX'] = '-D' + env['CPPDEFSUFFIX'] = '' + env['INCPREFIX'] = '-I' + env['INCSUFFIX'] = '' + env['SHOBJSUFFIX'] = '.dll' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0 + env['CFILESUFFIX'] = '.cpp' + +def exists(env): + return findIt('bcc32', env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/bcc32.xml b/src/engine/SCons/Tool/bcc32.xml new file mode 100644 index 0000000..c7f3155 --- /dev/null +++ b/src/engine/SCons/Tool/bcc32.xml @@ -0,0 +1,32 @@ + + + +Sets construction variables for the bcc32 compiler. + + +CC +CCFLAGS +CFLAGS +CCCOM +SHCC +SHCCFLAGS +SHCFLAGS +SHCCCOM +CPPDEFPREFIX +CPPDEFSUFFIX +INCPREFIX +INCSUFFIX +SHOBJSUFFIX + +CFILESUFFIX + + +_CPPDEFFLAGS +_CPPINCFLAGS + + diff --git a/src/engine/SCons/Tool/c++.py b/src/engine/SCons/Tool/c++.py new file mode 100644 index 0000000..ad7fa13 --- /dev/null +++ b/src/engine/SCons/Tool/c++.py @@ -0,0 +1,99 @@ +"""SCons.Tool.c++ + +Tool-specific initialization for generic Posix C++ compilers. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/c++.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import SCons.Tool +import SCons.Defaults +import SCons.Util + +compilers = ['CC', 'c++'] + +CXXSuffixes = ['.cpp', '.cc', '.cxx', '.c++', '.C++', '.mm'] +if SCons.Util.case_sensitive_suffixes('.c', '.C'): + CXXSuffixes.append('.C') + +def iscplusplus(source): + if not source: + # Source might be None for unusual cases like SConf. + return 0 + for s in source: + if s.sources: + ext = os.path.splitext(str(s.sources[0]))[1] + if ext in CXXSuffixes: + return 1 + return 0 + +def generate(env): + """ + Add Builders and construction variables for Visual Age C++ compilers + to an Environment. + """ + import SCons.Tool + import SCons.Tool.cc + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in CXXSuffixes: + static_obj.add_action(suffix, SCons.Defaults.CXXAction) + shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + SCons.Tool.cc.add_common_cc_variables(env) + + env['CXX'] = 'c++' + env['CXXFLAGS'] = SCons.Util.CLVar('') + env['CXXCOM'] = '$CXX -o $TARGET -c $CXXFLAGS $CCFLAGS $_CCCOMCOM $SOURCES' + env['SHCXX'] = '$CXX' + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS') + env['SHCXXCOM'] = '$SHCXX -o $TARGET -c $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES' + + env['CPPDEFPREFIX'] = '-D' + env['CPPDEFSUFFIX'] = '' + env['INCPREFIX'] = '-I' + env['INCSUFFIX'] = '' + env['SHOBJSUFFIX'] = '.os' + env['OBJSUFFIX'] = '.o' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0 + + env['CXXFILESUFFIX'] = '.cc' + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/c++.xml b/src/engine/SCons/Tool/c++.xml new file mode 100644 index 0000000..d9dfd57 --- /dev/null +++ b/src/engine/SCons/Tool/c++.xml @@ -0,0 +1,102 @@ + + + +Sets construction variables for generic POSIX C++ compilers. + + +CXX +CXXFLAGS +CXXCOM +SHCXX +SHCXXFLAGS +SHCXXCOM +CPPDEFPREFIX +CPPDEFSUFFIX +INCPREFIX +INCSUFFIX +SHOBJSUFFIX +OBJSUFFIX + +CXXFILESUFFIX + + +CXXCOMSTR + + + + + +The C++ compiler. + + + + + +The command line used to compile a C++ source file to an object file. +Any options specified in the &cv-link-CXXFLAGS; and +&cv-link-CPPFLAGS; construction variables +are included on this command line. + + + + + +The string displayed when a C++ source file +is compiled to a (static) object file. +If this is not set, then &cv-link-CXXCOM; (the command line) is displayed. + + +env = Environment(CXXCOMSTR = "Compiling static object $TARGET") + + + + + + +General options that are passed to the C++ compiler. +By default, this includes the value of &cv-link-CCFLAGS;, +so that setting &cv-CCFLAGS; affects both C and C++ compilation. +If you want to add C++-specific flags, +you must set or override the value of &cv-link-CXXFLAGS;. + + + + + +The C++ compiler used for generating shared-library objects. + + + + + +The command line used to compile a C++ source file +to a shared-library object file. +Any options specified in the &cv-link-SHCXXFLAGS; and +&cv-link-CPPFLAGS; construction variables +are included on this command line. + + + + + +The string displayed when a C++ source file +is compiled to a shared object file. +If this is not set, then &cv-link-SHCXXCOM; (the command line) is displayed. + + +env = Environment(SHCXXCOMSTR = "Compiling shared object $TARGET") + + + + + + +Options that are passed to the C++ compiler +to generate shared-library objects. + + diff --git a/src/engine/SCons/Tool/cc.py b/src/engine/SCons/Tool/cc.py new file mode 100644 index 0000000..0ba3856 --- /dev/null +++ b/src/engine/SCons/Tool/cc.py @@ -0,0 +1,114 @@ +"""SCons.Tool.cc + +Tool-specific initialization for generic Posix C compilers. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/cc.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Tool +import SCons.Defaults +import SCons.Util + +CSuffixes = ['.c', '.m'] +if not SCons.Util.case_sensitive_suffixes('.c', '.C'): + CSuffixes.append('.C') + +def add_common_cc_variables(env): + """ + Add underlying common "C compiler" variables that + are used by multiple tools (specifically, c++). + """ + if not env.has_key('_CCCOMCOM'): + env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS' + # It's a hack to test for darwin here, but the alternative + # of creating an applecc.py to contain this seems overkill. + # Maybe someday the Apple platform will require more setup and + # this logic will be moved. + env['FRAMEWORKS'] = SCons.Util.CLVar('') + env['FRAMEWORKPATH'] = SCons.Util.CLVar('') + if env['PLATFORM'] == 'darwin': + env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH' + + if not env.has_key('CCFLAGS'): + env['CCFLAGS'] = SCons.Util.CLVar('') + + if not env.has_key('SHCCFLAGS'): + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + +def generate(env): + """ + Add Builders and construction variables for C compilers to an Environment. + """ + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in CSuffixes: + static_obj.add_action(suffix, SCons.Defaults.CAction) + shared_obj.add_action(suffix, SCons.Defaults.ShCAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) +#<<<<<<< .working +# +# env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS' +# # It's a hack to test for darwin here, but the alternative of creating +# # an applecc.py to contain this seems overkill. Maybe someday the Apple +# # platform will require more setup and this logic will be moved. +# env['FRAMEWORKS'] = SCons.Util.CLVar('') +# env['FRAMEWORKPATH'] = SCons.Util.CLVar('') +# if env['PLATFORM'] == 'darwin': +# env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH' +#======= +#>>>>>>> .merge-right.r1907 + + add_common_cc_variables(env) + + env['CC'] = 'cc' + env['CFLAGS'] = SCons.Util.CLVar('') + env['CCCOM'] = '$CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCCOMCOM $SOURCES' + env['SHCC'] = '$CC' + env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS') + env['SHCCCOM'] = '$SHCC -o $TARGET -c $SHCFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES' + + env['CPPDEFPREFIX'] = '-D' + env['CPPDEFSUFFIX'] = '' + env['INCPREFIX'] = '-I' + env['INCSUFFIX'] = '' + env['SHOBJSUFFIX'] = '.os' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0 + + env['CFILESUFFIX'] = '.c' + +def exists(env): + return env.Detect('cc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/cc.xml b/src/engine/SCons/Tool/cc.xml new file mode 100644 index 0000000..fbfcb06 --- /dev/null +++ b/src/engine/SCons/Tool/cc.xml @@ -0,0 +1,161 @@ + + + +Sets construction variables for generic POSIX C copmilers. + + + +FRAMEWORKS +FRAMEWORKPATH +CC +CFLAGS +CCFLAGS +CCCOM +SHCC +SHCFLAGS +SHCCFLAGS +SHCCCOM +CPPDEFPREFIX +CPPDEFSUFFIX +INCPREFIX +INCSUFFIX +SHOBJSUFFIX + +CFILESUFFIX + + +PLATFORM + + + + + +The C compiler. + + + + + +The command line used to compile a C source file to a (static) object +file. Any options specified in the &cv-link-CFLAGS;, &cv-link-CCFLAGS; and +&cv-link-CPPFLAGS; construction variables are included on this command +line. + + + + + +The string displayed when a C source file +is compiled to a (static) object file. +If this is not set, then &cv-link-CCCOM; (the command line) is displayed. + + +env = Environment(CCCOMSTR = "Compiling static object $TARGET") + + + + + + +General options that are passed to the C and C++ compilers. + + + + + +General options that are passed to the C compiler (C only; not C++). + + + + + +User-specified C preprocessor options. +These will be included in any command that uses the C preprocessor, +including not just compilation of C and C++ source files +via the &cv-link-CCCOM;, +&cv-link-SHCCCOM;, +&cv-link-CXXCOM; and +&cv-link-SHCXXCOM; command lines, +but also the &cv-link-FORTRANPPCOM;, +&cv-link-SHFORTRANPPCOM;, +&cv-link-F77PPCOM; and +&cv-link-SHF77PPCOM; command lines +used to compile a Fortran source file, +and the &cv-link-ASPPCOM; command line +used to assemble an assembly language source file, +after first running each file through the C preprocessor. +Note that this variable does +not +contain + +(or similar) include search path options +that scons generates automatically from &cv-link-CPPPATH;. +See &cv-link-_CPPINCFLAGS;, below, +for the variable that expands to those options. + + + + + +The list of suffixes of files that will be scanned +for C preprocessor implicit dependencies +(#include lines). +The default list is: + + +[".c", ".C", ".cxx", ".cpp", ".c++", ".cc", + ".h", ".H", ".hxx", ".hpp", ".hh", + ".F", ".fpp", ".FPP", + ".m", ".mm", + ".S", ".spp", ".SPP"] + + + + + + +The C compiler used for generating shared-library objects. + + + + + +The command line used to compile a C source file +to a shared-library object file. +Any options specified in the &cv-link-SHCFLAGS;, +&cv-link-SHCCFLAGS; and +&cv-link-CPPFLAGS; construction variables +are included on this command line. + + + + + +The string displayed when a C source file +is compiled to a shared object file. +If this is not set, then &cv-link-SHCCCOM; (the command line) is displayed. + + +env = Environment(SHCCCOMSTR = "Compiling shared object $TARGET") + + + + + + +Options that are passed to the C and C++ compilers +to generate shared-library objects. + + + + + +Options that are passed to the C compiler (only; not C++) +to generate shared-library objects. + + diff --git a/src/engine/SCons/Tool/cvf.py b/src/engine/SCons/Tool/cvf.py new file mode 100644 index 0000000..1d7332b --- /dev/null +++ b/src/engine/SCons/Tool/cvf.py @@ -0,0 +1,58 @@ +"""engine.SCons.Tool.cvf + +Tool-specific initialization for the Compaq Visual Fortran compiler. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/cvf.py 4577 2009/12/27 19:44:43 scons" + +import fortran + +compilers = ['f90'] + +def generate(env): + """Add Builders and construction variables for compaq visual fortran to an Environment.""" + + fortran.generate(env) + + env['FORTRAN'] = 'f90' + env['FORTRANCOM'] = '$FORTRAN $FORTRANFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}' + env['FORTRANPPCOM'] = '$FORTRAN $FORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}' + env['SHFORTRANCOM'] = '$SHFORTRAN $SHFORTRANFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}' + env['SHFORTRANPPCOM'] = '$SHFORTRAN $SHFORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}' + env['OBJSUFFIX'] = '.obj' + env['FORTRANMODDIR'] = '${TARGET.dir}' + env['FORTRANMODDIRPREFIX'] = '/module:' + env['FORTRANMODDIRSUFFIX'] = '' + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/cvf.xml b/src/engine/SCons/Tool/cvf.xml new file mode 100644 index 0000000..6c27e6b --- /dev/null +++ b/src/engine/SCons/Tool/cvf.xml @@ -0,0 +1,30 @@ + + + +Sets construction variables for the Compaq Visual Fortran compiler. + + +FORTRAN +FORTRANCOM +FORTRANPPCOM +SHFORTRANCOM +SHFORTRANPPCOM +OBJSUFFIX +FORTRANMODDIR +FORTRANMODDIRPREFIX +FORTRANMODDIRSUFFIX + + +FORTRANFLAGS +SHFORTRANFLAGS +_FORTRANMODFLAG +_FORTRANINCFLAGS +CPPFLAGS +_CPPDEFFLAGS + + diff --git a/src/engine/SCons/Tool/default.py b/src/engine/SCons/Tool/default.py new file mode 100644 index 0000000..0640d8c --- /dev/null +++ b/src/engine/SCons/Tool/default.py @@ -0,0 +1,50 @@ +"""SCons.Tool.default + +Initialization with a default tool list. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/default.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Tool + +def generate(env): + """Add default tools.""" + for t in SCons.Tool.tool_list(env['PLATFORM'], env): + SCons.Tool.Tool(t)(env) + +def exists(env): + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/default.xml b/src/engine/SCons/Tool/default.xml new file mode 100644 index 0000000..5c36b41 --- /dev/null +++ b/src/engine/SCons/Tool/default.xml @@ -0,0 +1,12 @@ + + + +Sets variables by calling a default list of Tool modules +for the platform on which SCons is running. + + diff --git a/src/engine/SCons/Tool/dmd.py b/src/engine/SCons/Tool/dmd.py new file mode 100644 index 0000000..0c74877 --- /dev/null +++ b/src/engine/SCons/Tool/dmd.py @@ -0,0 +1,224 @@ +"""SCons.Tool.dmd + +Tool-specific initialization for the Digital Mars D compiler. +(http://digitalmars.com/d) + +Coded by Andy Friesen (andy@ikagames.com) +15 November 2003 + +There are a number of problems with this script at this point in time. +The one that irritates me the most is the Windows linker setup. The D +linker doesn't have a way to add lib paths on the commandline, as far +as I can see. You have to specify paths relative to the SConscript or +use absolute paths. To hack around it, add '#/blah'. This will link +blah.lib from the directory where SConstruct resides. + +Compiler variables: + DC - The name of the D compiler to use. Defaults to dmd or gdmd, + whichever is found. + DPATH - List of paths to search for import modules. + DVERSIONS - List of version tags to enable when compiling. + DDEBUG - List of debug tags to enable when compiling. + +Linker related variables: + LIBS - List of library files to link in. + DLINK - Name of the linker to use. Defaults to dmd or gdmd. + DLINKFLAGS - List of linker flags. + +Lib tool variables: + DLIB - Name of the lib tool to use. Defaults to lib. + DLIBFLAGS - List of flags to pass to the lib tool. + LIBS - Same as for the linker. (libraries to pull into the .lib) +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/dmd.py 4577 2009/12/27 19:44:43 scons" + +import os +import string + +import SCons.Action +import SCons.Builder +import SCons.Defaults +import SCons.Scanner.D +import SCons.Tool + +# Adapted from c++.py +def isD(source): + if not source: + return 0 + + for s in source: + if s.sources: + ext = os.path.splitext(str(s.sources[0]))[1] + if ext == '.d': + return 1 + return 0 + +smart_link = {} + +smart_lib = {} + +def generate(env): + global smart_link + global smart_lib + + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + DAction = SCons.Action.Action('$DCOM', '$DCOMSTR') + + static_obj.add_action('.d', DAction) + shared_obj.add_action('.d', DAction) + static_obj.add_emitter('.d', SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter('.d', SCons.Defaults.SharedObjectEmitter) + + dc = env.Detect(['dmd', 'gdmd']) + env['DC'] = dc + env['DCOM'] = '$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -c -of$TARGET $SOURCES' + env['_DINCFLAGS'] = '$( ${_concat(DINCPREFIX, DPATH, DINCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' + env['_DVERFLAGS'] = '$( ${_concat(DVERPREFIX, DVERSIONS, DVERSUFFIX, __env__)} $)' + env['_DDEBUGFLAGS'] = '$( ${_concat(DDEBUGPREFIX, DDEBUG, DDEBUGSUFFIX, __env__)} $)' + env['_DFLAGS'] = '$( ${_concat(DFLAGPREFIX, DFLAGS, DFLAGSUFFIX, __env__)} $)' + + env['DPATH'] = ['#/'] + env['DFLAGS'] = [] + env['DVERSIONS'] = [] + env['DDEBUG'] = [] + + if dc: + # Add the path to the standard library. + # This is merely for the convenience of the dependency scanner. + dmd_path = env.WhereIs(dc) + if dmd_path: + x = string.rindex(dmd_path, dc) + phobosDir = dmd_path[:x] + '/../src/phobos' + if os.path.isdir(phobosDir): + env.Append(DPATH = [phobosDir]) + + env['DINCPREFIX'] = '-I' + env['DINCSUFFIX'] = '' + env['DVERPREFIX'] = '-version=' + env['DVERSUFFIX'] = '' + env['DDEBUGPREFIX'] = '-debug=' + env['DDEBUGSUFFIX'] = '' + env['DFLAGPREFIX'] = '-' + env['DFLAGSUFFIX'] = '' + env['DFILESUFFIX'] = '.d' + + # Need to use the Digital Mars linker/lib on windows. + # *nix can just use GNU link. + if env['PLATFORM'] == 'win32': + env['DLINK'] = '$DC' + env['DLINKCOM'] = '$DLINK -of$TARGET $SOURCES $DFLAGS $DLINKFLAGS $_DLINKLIBFLAGS' + env['DLIB'] = 'lib' + env['DLIBCOM'] = '$DLIB $_DLIBFLAGS -c $TARGET $SOURCES $_DLINKLIBFLAGS' + + env['_DLINKLIBFLAGS'] = '$( ${_concat(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' + env['_DLIBFLAGS'] = '$( ${_concat(DLIBFLAGPREFIX, DLIBFLAGS, DLIBFLAGSUFFIX, __env__)} $)' + env['DLINKFLAGS'] = [] + env['DLIBLINKPREFIX'] = '' + env['DLIBLINKSUFFIX'] = '.lib' + env['DLIBFLAGPREFIX'] = '-' + env['DLIBFLAGSUFFIX'] = '' + env['DLINKFLAGPREFIX'] = '-' + env['DLINKFLAGSUFFIX'] = '' + + SCons.Tool.createStaticLibBuilder(env) + + # Basically, we hijack the link and ar builders with our own. + # these builders check for the presence of D source, and swap out + # the system's defaults for the Digital Mars tools. If there's no D + # source, then we silently return the previous settings. + linkcom = env.get('LINKCOM') + try: + env['SMART_LINKCOM'] = smart_link[linkcom] + except KeyError: + def _smartLink(source, target, env, for_signature, + defaultLinker=linkcom): + if isD(source): + # XXX I'm not sure how to add a $DLINKCOMSTR variable + # so that it works with this _smartLink() logic, + # and I don't have a D compiler/linker to try it out, + # so we'll leave it alone for now. + return '$DLINKCOM' + else: + return defaultLinker + env['SMART_LINKCOM'] = smart_link[linkcom] = _smartLink + + arcom = env.get('ARCOM') + try: + env['SMART_ARCOM'] = smart_lib[arcom] + except KeyError: + def _smartLib(source, target, env, for_signature, + defaultLib=arcom): + if isD(source): + # XXX I'm not sure how to add a $DLIBCOMSTR variable + # so that it works with this _smartLib() logic, and + # I don't have a D compiler/archiver to try it out, + # so we'll leave it alone for now. + return '$DLIBCOM' + else: + return defaultLib + env['SMART_ARCOM'] = smart_lib[arcom] = _smartLib + + # It is worth noting that the final space in these strings is + # absolutely pivotal. SCons sees these as actions and not generators + # if it is not there. (very bad) + env['ARCOM'] = '$SMART_ARCOM ' + env['LINKCOM'] = '$SMART_LINKCOM ' + else: # assuming linux + linkcom = env.get('LINKCOM') + try: + env['SMART_LINKCOM'] = smart_link[linkcom] + except KeyError: + def _smartLink(source, target, env, for_signature, + defaultLinker=linkcom, dc=dc): + if isD(source): + try: + libs = env['LIBS'] + except KeyError: + libs = [] + if 'phobos' not in libs and 'gphobos' not in libs: + if dc is 'dmd': + env.Append(LIBS = ['phobos']) + elif dc is 'gdmd': + env.Append(LIBS = ['gphobos']) + if 'pthread' not in libs: + env.Append(LIBS = ['pthread']) + if 'm' not in libs: + env.Append(LIBS = ['m']) + return defaultLinker + env['SMART_LINKCOM'] = smart_link[linkcom] = _smartLink + + env['LINKCOM'] = '$SMART_LINKCOM ' + +def exists(env): + return env.Detect(['dmd', 'gdmd']) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/dmd.xml b/src/engine/SCons/Tool/dmd.xml new file mode 100644 index 0000000..a12b25c --- /dev/null +++ b/src/engine/SCons/Tool/dmd.xml @@ -0,0 +1,53 @@ + + + +Sets construction variables for D language compilers +(the Digital Mars D compiler, or GDC). + + + + + + + diff --git a/src/engine/SCons/Tool/dvi.py b/src/engine/SCons/Tool/dvi.py new file mode 100644 index 0000000..5c859c7 --- /dev/null +++ b/src/engine/SCons/Tool/dvi.py @@ -0,0 +1,64 @@ +"""SCons.Tool.dvi + +Common DVI Builder definition for various other Tool modules that use it. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/dvi.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Builder +import SCons.Tool + +DVIBuilder = None + +def generate(env): + try: + env['BUILDERS']['DVI'] + except KeyError: + global DVIBuilder + + if DVIBuilder is None: + # The suffix is hard-coded to '.dvi', not configurable via a + # construction variable like $DVISUFFIX, because the output + # file name is hard-coded within TeX. + DVIBuilder = SCons.Builder.Builder(action = {}, + source_scanner = SCons.Tool.LaTeXScanner, + suffix = '.dvi', + emitter = {}, + source_ext_match = None) + + env['BUILDERS']['DVI'] = DVIBuilder + +def exists(env): + # This only puts a skeleton Builder in place, so if someone + # references this Tool directly, it's always "available." + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/dvi.xml b/src/engine/SCons/Tool/dvi.xml new file mode 100644 index 0000000..b0a871f --- /dev/null +++ b/src/engine/SCons/Tool/dvi.xml @@ -0,0 +1,67 @@ + + + +Attaches the &b-DVI; builder to the +construction environment. + + + + + + + + + +Builds a .dvi file +from a .tex, +.ltx or .latex input file. +If the source file suffix is .tex, +&scons; +will examine the contents of the file; +if the string +\documentclass +or +\documentstyle +is found, the file is assumed to be a LaTeX file and +the target is built by invoking the &cv-link-LATEXCOM; command line; +otherwise, the &cv-link-TEXCOM; command line is used. +If the file is a LaTeX file, +the +&b-DVI; +builder method will also examine the contents +of the +.aux +file and invoke the &cv-link-BIBTEX; command line +if the string +bibdata +is found, +start &cv-link-MAKEINDEX; to generate an index if a +.ind +file is found +and will examine the contents +.log +file and re-run the &cv-link-LATEXCOM; command +if the log file says it is necessary. + +The suffix .dvi +(hard-coded within TeX itself) +is automatically added to the target +if it is not already present. +Examples: + + +# builds from aaa.tex +env.DVI(target = 'aaa.dvi', source = 'aaa.tex') +# builds bbb.dvi +env.DVI(target = 'bbb', source = 'bbb.ltx') +# builds from ccc.latex +env.DVI(target = 'ccc.dvi', source = 'ccc.latex') + + + + diff --git a/src/engine/SCons/Tool/dvipdf.py b/src/engine/SCons/Tool/dvipdf.py new file mode 100644 index 0000000..67137a8 --- /dev/null +++ b/src/engine/SCons/Tool/dvipdf.py @@ -0,0 +1,125 @@ +"""SCons.Tool.dvipdf + +Tool-specific initialization for dvipdf. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/dvipdf.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Defaults +import SCons.Tool.pdf +import SCons.Tool.tex +import SCons.Util + +_null = SCons.Scanner.LaTeX._null + +def DviPdfPsFunction(XXXDviAction, target = None, source= None, env=None): + """A builder for DVI files that sets the TEXPICTS environment + variable before running dvi2ps or dvipdf.""" + + try: + abspath = source[0].attributes.path + except AttributeError : + abspath = '' + + saved_env = SCons.Scanner.LaTeX.modify_env_var(env, 'TEXPICTS', abspath) + + result = XXXDviAction(target, source, env) + + if saved_env is _null: + try: + del env['ENV']['TEXPICTS'] + except KeyError: + pass # was never set + else: + env['ENV']['TEXPICTS'] = saved_env + + return result + +def DviPdfFunction(target = None, source= None, env=None): + result = DviPdfPsFunction(PDFAction,target,source,env) + return result + +def DviPdfStrFunction(target = None, source= None, env=None): + """A strfunction for dvipdf that returns the appropriate + command string for the no_exec options.""" + if env.GetOption("no_exec"): + result = env.subst('$DVIPDFCOM',0,target,source) + else: + result = '' + return result + +PDFAction = None +DVIPDFAction = None + +def PDFEmitter(target, source, env): + """Strips any .aux or .log files from the input source list. + These are created by the TeX Builder that in all likelihood was + used to generate the .dvi file we're using as input, and we only + care about the .dvi file. + """ + def strip_suffixes(n): + return not SCons.Util.splitext(str(n))[1] in ['.aux', '.log'] + source = filter(strip_suffixes, source) + return (target, source) + +def generate(env): + """Add Builders and construction variables for dvipdf to an Environment.""" + global PDFAction + if PDFAction is None: + PDFAction = SCons.Action.Action('$DVIPDFCOM', '$DVIPDFCOMSTR') + + global DVIPDFAction + if DVIPDFAction is None: + DVIPDFAction = SCons.Action.Action(DviPdfFunction, strfunction = DviPdfStrFunction) + + import pdf + pdf.generate(env) + + bld = env['BUILDERS']['PDF'] + bld.add_action('.dvi', DVIPDFAction) + bld.add_emitter('.dvi', PDFEmitter) + + env['DVIPDF'] = 'dvipdf' + env['DVIPDFFLAGS'] = SCons.Util.CLVar('') + env['DVIPDFCOM'] = 'cd ${TARGET.dir} && $DVIPDF $DVIPDFFLAGS ${SOURCE.file} ${TARGET.file}' + + # Deprecated synonym. + env['PDFCOM'] = ['$DVIPDFCOM'] + +def exists(env): + return env.Detect('dvipdf') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/dvipdf.xml b/src/engine/SCons/Tool/dvipdf.xml new file mode 100644 index 0000000..c3c2a36 --- /dev/null +++ b/src/engine/SCons/Tool/dvipdf.xml @@ -0,0 +1,51 @@ + + + +Sets construction variables for the dvipdf utility. + + +DVIPDF +DVIPDFFLAGS +DVIPDFCOM + + +DVIPDFCOMSTR + + + + + +The TeX DVI file to PDF file converter. + + + + + +General options passed to the TeX DVI file to PDF file converter. + + + + + +The command line used to convert TeX DVI files into a PDF file. + + + + + +The string displayed when a TeX DVI file +is converted into a PDF file. +If this is not set, then &cv-link-DVIPDFCOM; (the command line) is displayed. + + + + + +A deprecated synonym for &cv-link-DVIPDFCOM;. + + diff --git a/src/engine/SCons/Tool/dvips.py b/src/engine/SCons/Tool/dvips.py new file mode 100644 index 0000000..da61891 --- /dev/null +++ b/src/engine/SCons/Tool/dvips.py @@ -0,0 +1,94 @@ +"""SCons.Tool.dvips + +Tool-specific initialization for dvips. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/dvips.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Tool.dvipdf +import SCons.Util + +def DviPsFunction(target = None, source= None, env=None): + result = SCons.Tool.dvipdf.DviPdfPsFunction(PSAction,target,source,env) + return result + +def DviPsStrFunction(target = None, source= None, env=None): + """A strfunction for dvipdf that returns the appropriate + command string for the no_exec options.""" + if env.GetOption("no_exec"): + result = env.subst('$PSCOM',0,target,source) + else: + result = '' + return result + +PSAction = None +DVIPSAction = None +PSBuilder = None + +def generate(env): + """Add Builders and construction variables for dvips to an Environment.""" + global PSAction + if PSAction is None: + PSAction = SCons.Action.Action('$PSCOM', '$PSCOMSTR') + + global DVIPSAction + if DVIPSAction is None: + DVIPSAction = SCons.Action.Action(DviPsFunction, strfunction = DviPsStrFunction) + + global PSBuilder + if PSBuilder is None: + PSBuilder = SCons.Builder.Builder(action = PSAction, + prefix = '$PSPREFIX', + suffix = '$PSSUFFIX', + src_suffix = '.dvi', + src_builder = 'DVI', + single_source=True) + + env['BUILDERS']['PostScript'] = PSBuilder + + env['DVIPS'] = 'dvips' + env['DVIPSFLAGS'] = SCons.Util.CLVar('') + # I'm not quite sure I got the directories and filenames right for variant_dir + # We need to be in the correct directory for the sake of latex \includegraphics eps included files. + env['PSCOM'] = 'cd ${TARGET.dir} && $DVIPS $DVIPSFLAGS -o ${TARGET.file} ${SOURCE.file}' + env['PSPREFIX'] = '' + env['PSSUFFIX'] = '.ps' + +def exists(env): + return env.Detect('dvips') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/dvips.xml b/src/engine/SCons/Tool/dvips.xml new file mode 100644 index 0000000..b7ead77 --- /dev/null +++ b/src/engine/SCons/Tool/dvips.xml @@ -0,0 +1,81 @@ + + + +Sets construction variables for the dvips utility. + + +DVIPS +DVIPSFLAGS +PSCOM +PSPREFIX +PSSUFFIX + + +PSCOMSTR + + + + + +Builds a .ps file +from a .dvi input file +(or, by extension, a .tex, +.ltx, +or +.latex input file). +The suffix specified by the &cv-link-PSSUFFIX; construction variable +(.ps by default) +is added automatically to the target +if it is not already present. Example: + + +# builds from aaa.tex +env.PostScript(target = 'aaa.ps', source = 'aaa.tex') +# builds bbb.ps from bbb.dvi +env.PostScript(target = 'bbb', source = 'bbb.dvi') + + + + + + +The TeX DVI file to PostScript converter. + + + + + +General options passed to the TeX DVI file to PostScript converter. + + + + + +The command line used to convert TeX DVI files into a PostScript file. + + + + + +The string displayed when a TeX DVI file +is converted into a PostScript file. +If this is not set, then &cv-link-PSCOM; (the command line) is displayed. + + + + + +The prefix used for PostScript file names. + + + + + +The prefix used for PostScript file names. + + diff --git a/src/engine/SCons/Tool/f77.py b/src/engine/SCons/Tool/f77.py new file mode 100644 index 0000000..3fe3c3f --- /dev/null +++ b/src/engine/SCons/Tool/f77.py @@ -0,0 +1,62 @@ +"""engine.SCons.Tool.f77 + +Tool-specific initialization for the generic Posix f77 Fortran compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/f77.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Scanner.Fortran +import SCons.Tool +import SCons.Util +from SCons.Tool.FortranCommon import add_all_to_env, add_f77_to_env + +compilers = ['f77'] + +def generate(env): + add_all_to_env(env) + add_f77_to_env(env) + + fcomp = env.Detect(compilers) or 'f77' + env['F77'] = fcomp + env['SHF77'] = fcomp + + env['FORTRAN'] = fcomp + env['SHFORTRAN'] = fcomp + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/f77.xml b/src/engine/SCons/Tool/f77.xml new file mode 100644 index 0000000..c947905 --- /dev/null +++ b/src/engine/SCons/Tool/f77.xml @@ -0,0 +1,265 @@ + + + +Set construction variables for generic POSIX Fortran 77 compilers. + + +F77 +F77FLAGS +F77COM +F77PPCOM +F77FILESUFFIXES +F77PPFILESUFFIXES +FORTRAN +FORTRANFLAGS +FORTRANCOM +SHF77 +SHF77FLAGS +SHF77COM +SHF77PPCOM +SHFORTRAN +SHFORTRANFLAGS +SHFORTRANCOM +SHFORTRANPPCOM +_F77INCFLAGS + + +F77COMSTR +F77PPCOMSTR +FORTRANCOMSTR +FORTRANPPCOMSTR +SHF77COMSTR +SHF77PPCOMSTR +SHFORTRANCOMSTR +SHFORTRANPPCOMSTR + + + + + +The Fortran 77 compiler. +You should normally set the &cv-link-FORTRAN; variable, +which specifies the default Fortran compiler +for all Fortran versions. +You only need to set &cv-link-F77; if you need to use a specific compiler +or compiler version for Fortran 77 files. + + + + + +The command line used to compile a Fortran 77 source file to an object file. +You only need to set &cv-link-F77COM; if you need to use a specific +command line for Fortran 77 files. +You should normally set the &cv-link-FORTRANCOM; variable, +which specifies the default command line +for all Fortran versions. + + + + + +The list of file extensions for which the F77 dialect will be used. By +default, this is ['.f77'] + + + + + +The list of file extensions for which the compilation + preprocessor pass for +F77 dialect will be used. By default, this is empty + + + + + +The string displayed when a Fortran 77 source file +is compiled to an object file. +If this is not set, then &cv-link-F77COM; or &cv-link-FORTRANCOM; +(the command line) is displayed. + + + + + +General user-specified options that are passed to the Fortran 77 compiler. +Note that this variable does +not +contain + +(or similar) include search path options +that scons generates automatically from &cv-link-F77PATH;. +See +&cv-link-_F77INCFLAGS; +below, +for the variable that expands to those options. +You only need to set &cv-link-F77FLAGS; if you need to define specific +user options for Fortran 77 files. +You should normally set the &cv-link-FORTRANFLAGS; variable, +which specifies the user-specified options +passed to the default Fortran compiler +for all Fortran versions. + + + + + +An automatically-generated construction variable +containing the Fortran 77 compiler command-line options +for specifying directories to be searched for include files. +The value of &cv-link-_F77INCFLAGS; is created +by appending &cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +to the beginning and end +of each directory in &cv-link-F77PATH;. + + + + + +The list of directories that the Fortran 77 compiler will search for include +directories. The implicit dependency scanner will search these +directories for include files. Don't explicitly put include directory +arguments in &cv-link-F77FLAGS; because the result will be non-portable +and the directories will not be searched by the dependency scanner. Note: +directory names in &cv-link-F77PATH; will be looked-up relative to the SConscript +directory when they are used in a command. To force +&scons; +to look-up a directory relative to the root of the source tree use #: +You only need to set &cv-link-F77PATH; if you need to define a specific +include path for Fortran 77 files. +You should normally set the &cv-link-FORTRANPATH; variable, +which specifies the include path +for the default Fortran compiler +for all Fortran versions. + + +env = Environment(F77PATH='#/include') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +include = Dir('include') +env = Environment(F77PATH=include) + + +The directory list will be added to command lines +through the automatically-generated +&cv-link-_F77INCFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-link-F77PATH;. +Any command lines you define that need +the F77PATH directory list should +include &cv-link-_F77INCFLAGS;: + + +env = Environment(F77COM="my_compiler $_F77INCFLAGS -c -o $TARGET $SOURCE") + + + + + + +The command line used to compile a Fortran 77 source file to an object file +after first running the file through the C preprocessor. +Any options specified in the &cv-link-F77FLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. +You only need to set &cv-link-F77PPCOM; if you need to use a specific +C-preprocessor command line for Fortran 77 files. +You should normally set the &cv-link-FORTRANPPCOM; variable, +which specifies the default C-preprocessor command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 77 source file +is compiled to an object file +after first running the file through the C preprocessor. +If this is not set, then &cv-link-F77PPCOM; or &cv-link-FORTRANPPCOM; +(the command line) is displayed. + + + + + +The Fortran 77 compiler used for generating shared-library objects. +You should normally set the &cv-link-SHFORTRAN; variable, +which specifies the default Fortran compiler +for all Fortran versions. +You only need to set &cv-link-SHF77; if you need to use a specific compiler +or compiler version for Fortran 77 files. + + + + + +The command line used to compile a Fortran 77 source file +to a shared-library object file. +You only need to set &cv-link-SHF77COM; if you need to use a specific +command line for Fortran 77 files. +You should normally set the &cv-link-SHFORTRANCOM; variable, +which specifies the default command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 77 source file +is compiled to a shared-library object file. +If this is not set, then &cv-link-SHF77COM; or &cv-link-SHFORTRANCOM; +(the command line) is displayed. + + + + + +Options that are passed to the Fortran 77 compiler +to generated shared-library objects. +You only need to set &cv-link-SHF77FLAGS; if you need to define specific +user options for Fortran 77 files. +You should normally set the &cv-link-SHFORTRANFLAGS; variable, +which specifies the user-specified options +passed to the default Fortran compiler +for all Fortran versions. + + + + + +The command line used to compile a Fortran 77 source file to a +shared-library object file +after first running the file through the C preprocessor. +Any options specified in the &cv-link-SHF77FLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. +You only need to set &cv-link-SHF77PPCOM; if you need to use a specific +C-preprocessor command line for Fortran 77 files. +You should normally set the &cv-link-SHFORTRANPPCOM; variable, +which specifies the default C-preprocessor command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 77 source file +is compiled to a shared-library object file +after first running the file through the C preprocessor. +If this is not set, then &cv-link-SHF77PPCOM; or &cv-link-SHFORTRANPPCOM; +(the command line) is displayed. + + diff --git a/src/engine/SCons/Tool/f90.py b/src/engine/SCons/Tool/f90.py new file mode 100644 index 0000000..b53014e --- /dev/null +++ b/src/engine/SCons/Tool/f90.py @@ -0,0 +1,62 @@ +"""engine.SCons.Tool.f90 + +Tool-specific initialization for the generic Posix f90 Fortran compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/f90.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Scanner.Fortran +import SCons.Tool +import SCons.Util +from SCons.Tool.FortranCommon import add_all_to_env, add_f90_to_env + +compilers = ['f90'] + +def generate(env): + add_all_to_env(env) + add_f90_to_env(env) + + fc = env.Detect(compilers) or 'f90' + env['F90'] = fc + env['SHF90'] = fc + + env['FORTRAN'] = fc + env['SHFORTRAN'] = fc + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/f90.xml b/src/engine/SCons/Tool/f90.xml new file mode 100644 index 0000000..665333d --- /dev/null +++ b/src/engine/SCons/Tool/f90.xml @@ -0,0 +1,251 @@ + + + +Set construction variables for generic POSIX Fortran 90 compilers. + + +F90 +F90FLAGS +F90COM +F90PPCOM +SHF90 +SHF90FLAGS +SHF90COM +SHF90PPCOM +_F90INCFLAGS + + +F90COMSTR +F90PPCOMSTR +SHF90COMSTR +SHF90PPCOMSTR + + + + + +The Fortran 90 compiler. +You should normally set the &cv-link-FORTRAN; variable, +which specifies the default Fortran compiler +for all Fortran versions. +You only need to set &cv-link-F90; if you need to use a specific compiler +or compiler version for Fortran 90 files. + + + + + +The command line used to compile a Fortran 90 source file to an object file. +You only need to set &cv-link-F90COM; if you need to use a specific +command line for Fortran 90 files. +You should normally set the &cv-link-FORTRANCOM; variable, +which specifies the default command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 90 source file +is compiled to an object file. +If this is not set, then &cv-link-F90COM; or &cv-link-FORTRANCOM; +(the command line) is displayed. + + + + + +The list of file extensions for which the F90 dialect will be used. By +default, this is ['.f90'] + + + + + +The list of file extensions for which the compilation + preprocessor pass for +F90 dialect will be used. By default, this is empty + + + + + +General user-specified options that are passed to the Fortran 90 compiler. +Note that this variable does +not +contain + +(or similar) include search path options +that scons generates automatically from &cv-link-F90PATH;. +See +&cv-link-_F90INCFLAGS; +below, +for the variable that expands to those options. +You only need to set &cv-link-F90FLAGS; if you need to define specific +user options for Fortran 90 files. +You should normally set the &cv-link-FORTRANFLAGS; variable, +which specifies the user-specified options +passed to the default Fortran compiler +for all Fortran versions. + + + + + +An automatically-generated construction variable +containing the Fortran 90 compiler command-line options +for specifying directories to be searched for include files. +The value of &cv-link-_F90INCFLAGS; is created +by appending &cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +to the beginning and end +of each directory in &cv-link-F90PATH;. + + + + + +The list of directories that the Fortran 90 compiler will search for include +directories. The implicit dependency scanner will search these +directories for include files. Don't explicitly put include directory +arguments in &cv-link-F90FLAGS; because the result will be non-portable +and the directories will not be searched by the dependency scanner. Note: +directory names in &cv-link-F90PATH; will be looked-up relative to the SConscript +directory when they are used in a command. To force +&scons; +to look-up a directory relative to the root of the source tree use #: +You only need to set &cv-link-F90PATH; if you need to define a specific +include path for Fortran 90 files. +You should normally set the &cv-link-FORTRANPATH; variable, +which specifies the include path +for the default Fortran compiler +for all Fortran versions. + + +env = Environment(F90PATH='#/include') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +include = Dir('include') +env = Environment(F90PATH=include) + + +The directory list will be added to command lines +through the automatically-generated +&cv-link-_F90INCFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-link-F90PATH;. +Any command lines you define that need +the F90PATH directory list should +include &cv-link-_F90INCFLAGS;: + + +env = Environment(F90COM="my_compiler $_F90INCFLAGS -c -o $TARGET $SOURCE") + + + + + + +The command line used to compile a Fortran 90 source file to an object file +after first running the file through the C preprocessor. +Any options specified in the &cv-link-F90FLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. +You only need to set &cv-link-F90PPCOM; if you need to use a specific +C-preprocessor command line for Fortran 90 files. +You should normally set the &cv-link-FORTRANPPCOM; variable, +which specifies the default C-preprocessor command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 90 source file +is compiled after first running the file through the C preprocessor. +If this is not set, then &cv-link-F90PPCOM; or &cv-link-FORTRANPPCOM; +(the command line) is displayed. + + + + + +The Fortran 90 compiler used for generating shared-library objects. +You should normally set the &cv-link-SHFORTRAN; variable, +which specifies the default Fortran compiler +for all Fortran versions. +You only need to set &cv-link-SHF90; if you need to use a specific compiler +or compiler version for Fortran 90 files. + + + + + +The command line used to compile a Fortran 90 source file +to a shared-library object file. +You only need to set &cv-link-SHF90COM; if you need to use a specific +command line for Fortran 90 files. +You should normally set the &cv-link-SHFORTRANCOM; variable, +which specifies the default command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 90 source file +is compiled to a shared-library object file. +If this is not set, then &cv-link-SHF90COM; or &cv-link-SHFORTRANCOM; +(the command line) is displayed. + + + + + +Options that are passed to the Fortran 90 compiler +to generated shared-library objects. +You only need to set &cv-link-SHF90FLAGS; if you need to define specific +user options for Fortran 90 files. +You should normally set the &cv-link-SHFORTRANFLAGS; variable, +which specifies the user-specified options +passed to the default Fortran compiler +for all Fortran versions. + + + + + +The command line used to compile a Fortran 90 source file to a +shared-library object file +after first running the file through the C preprocessor. +Any options specified in the &cv-link-SHF90FLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. +You only need to set &cv-link-SHF90PPCOM; if you need to use a specific +C-preprocessor command line for Fortran 90 files. +You should normally set the &cv-link-SHFORTRANPPCOM; variable, +which specifies the default C-preprocessor command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 90 source file +is compiled to a shared-library object file +after first running the file through the C preprocessor. +If this is not set, then &cv-link-SHF90PPCOM; or &cv-link-SHFORTRANPPCOM; +(the command line) is displayed. + + diff --git a/src/engine/SCons/Tool/f95.py b/src/engine/SCons/Tool/f95.py new file mode 100644 index 0000000..72e8443 --- /dev/null +++ b/src/engine/SCons/Tool/f95.py @@ -0,0 +1,63 @@ +"""engine.SCons.Tool.f95 + +Tool-specific initialization for the generic Posix f95 Fortran compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/f95.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util +import fortran +from SCons.Tool.FortranCommon import add_all_to_env, add_f95_to_env + +compilers = ['f95'] + +def generate(env): + add_all_to_env(env) + add_f95_to_env(env) + + fcomp = env.Detect(compilers) or 'f95' + env['F95'] = fcomp + env['SHF95'] = fcomp + + env['FORTRAN'] = fcomp + env['SHFORTRAN'] = fcomp + + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/f95.xml b/src/engine/SCons/Tool/f95.xml new file mode 100644 index 0000000..2418cb9 --- /dev/null +++ b/src/engine/SCons/Tool/f95.xml @@ -0,0 +1,252 @@ + + + +Set construction variables for generic POSIX Fortran 95 compilers. + + +F95 +F95FLAGS +F95COM +F95PPCOM +SHF95 +SHF95FLAGS +SHF95COM +SHF95PPCOM +_F95INCFLAGS + + +F95COMSTR +F95PPCOMSTR +SHF95COMSTR +SHF95PPCOMSTR + + + + + +The Fortran 95 compiler. +You should normally set the &cv-link-FORTRAN; variable, +which specifies the default Fortran compiler +for all Fortran versions. +You only need to set &cv-link-F95; if you need to use a specific compiler +or compiler version for Fortran 95 files. + + + + + +The command line used to compile a Fortran 95 source file to an object file. +You only need to set &cv-link-F95COM; if you need to use a specific +command line for Fortran 95 files. +You should normally set the &cv-link-FORTRANCOM; variable, +which specifies the default command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 95 source file +is compiled to an object file. +If this is not set, then &cv-link-F95COM; or &cv-link-FORTRANCOM; +(the command line) is displayed. + + + + + +The list of file extensions for which the F95 dialect will be used. By +default, this is ['.f95'] + + + + + +The list of file extensions for which the compilation + preprocessor pass for +F95 dialect will be used. By default, this is empty + + + + + +General user-specified options that are passed to the Fortran 95 compiler. +Note that this variable does +not +contain + +(or similar) include search path options +that scons generates automatically from &cv-link-F95PATH;. +See +&cv-link-_F95INCFLAGS; +below, +for the variable that expands to those options. +You only need to set &cv-link-F95FLAGS; if you need to define specific +user options for Fortran 95 files. +You should normally set the &cv-link-FORTRANFLAGS; variable, +which specifies the user-specified options +passed to the default Fortran compiler +for all Fortran versions. + + + + + +An automatically-generated construction variable +containing the Fortran 95 compiler command-line options +for specifying directories to be searched for include files. +The value of &cv-link-_F95INCFLAGS; is created +by appending &cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +to the beginning and end +of each directory in &cv-link-F95PATH;. + + + + + +The list of directories that the Fortran 95 compiler will search for include +directories. The implicit dependency scanner will search these +directories for include files. Don't explicitly put include directory +arguments in &cv-link-F95FLAGS; because the result will be non-portable +and the directories will not be searched by the dependency scanner. Note: +directory names in &cv-link-F95PATH; will be looked-up relative to the SConscript +directory when they are used in a command. To force +&scons; +to look-up a directory relative to the root of the source tree use #: +You only need to set &cv-link-F95PATH; if you need to define a specific +include path for Fortran 95 files. +You should normally set the &cv-link-FORTRANPATH; variable, +which specifies the include path +for the default Fortran compiler +for all Fortran versions. + + +env = Environment(F95PATH='#/include') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +include = Dir('include') +env = Environment(F95PATH=include) + + +The directory list will be added to command lines +through the automatically-generated +&cv-link-_F95INCFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-link-F95PATH;. +Any command lines you define that need +the F95PATH directory list should +include &cv-link-_F95INCFLAGS;: + + +env = Environment(F95COM="my_compiler $_F95INCFLAGS -c -o $TARGET $SOURCE") + + + + + + +The command line used to compile a Fortran 95 source file to an object file +after first running the file through the C preprocessor. +Any options specified in the &cv-link-F95FLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. +You only need to set &cv-link-F95PPCOM; if you need to use a specific +C-preprocessor command line for Fortran 95 files. +You should normally set the &cv-link-FORTRANPPCOM; variable, +which specifies the default C-preprocessor command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 95 source file +is compiled to an object file +after first running the file through the C preprocessor. +If this is not set, then &cv-link-F95PPCOM; or &cv-link-FORTRANPPCOM; +(the command line) is displayed. + + + + + +The Fortran 95 compiler used for generating shared-library objects. +You should normally set the &cv-link-SHFORTRAN; variable, +which specifies the default Fortran compiler +for all Fortran versions. +You only need to set &cv-link-SHF95; if you need to use a specific compiler +or compiler version for Fortran 95 files. + + + + + +The command line used to compile a Fortran 95 source file +to a shared-library object file. +You only need to set &cv-link-SHF95COM; if you need to use a specific +command line for Fortran 95 files. +You should normally set the &cv-link-SHFORTRANCOM; variable, +which specifies the default command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 95 source file +is compiled to a shared-library object file. +If this is not set, then &cv-link-SHF95COM; or &cv-link-SHFORTRANCOM; +(the command line) is displayed. + + + + + +Options that are passed to the Fortran 95 compiler +to generated shared-library objects. +You only need to set &cv-link-SHF95FLAGS; if you need to define specific +user options for Fortran 95 files. +You should normally set the &cv-link-SHFORTRANFLAGS; variable, +which specifies the user-specified options +passed to the default Fortran compiler +for all Fortran versions. + + + + + +The command line used to compile a Fortran 95 source file to a +shared-library object file +after first running the file through the C preprocessor. +Any options specified in the &cv-link-SHF95FLAGS; and &cv-link-CPPFLAGS; construction variables +are included on this command line. +You only need to set &cv-link-SHF95PPCOM; if you need to use a specific +C-preprocessor command line for Fortran 95 files. +You should normally set the &cv-link-SHFORTRANPPCOM; variable, +which specifies the default C-preprocessor command line +for all Fortran versions. + + + + + +The string displayed when a Fortran 95 source file +is compiled to a shared-library object file +after first running the file through the C preprocessor. +If this is not set, then &cv-link-SHF95PPCOM; or &cv-link-SHFORTRANPPCOM; +(the command line) is displayed. + + diff --git a/src/engine/SCons/Tool/filesystem.py b/src/engine/SCons/Tool/filesystem.py new file mode 100644 index 0000000..bd4c803 --- /dev/null +++ b/src/engine/SCons/Tool/filesystem.py @@ -0,0 +1,98 @@ +"""SCons.Tool.filesystem + +Tool-specific initialization for the filesystem tools. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/filesystem.py 4577 2009/12/27 19:44:43 scons" + +import SCons +from SCons.Tool.install import copyFunc + +copyToBuilder, copyAsBuilder = None, None + +def copyto_emitter(target, source, env): + """ changes the path of the source to be under the target (which + are assumed to be directories. + """ + n_target = [] + + for t in target: + n_target = n_target + map( lambda s, t=t: t.File( str( s ) ), source ) + + return (n_target, source) + +def copy_action_func(target, source, env): + assert( len(target) == len(source) ), "\ntarget: %s\nsource: %s" %(map(str, target),map(str, source)) + + for t, s in zip(target, source): + if copyFunc(t.get_path(), s.get_path(), env): + return 1 + + return 0 + +def copy_action_str(target, source, env): + return env.subst_target_source(env['COPYSTR'], 0, target, source) + +copy_action = SCons.Action.Action( copy_action_func, copy_action_str ) + +def generate(env): + try: + env['BUILDERS']['CopyTo'] + env['BUILDERS']['CopyAs'] + except KeyError, e: + global copyToBuilder + if copyToBuilder is None: + copyToBuilder = SCons.Builder.Builder( + action = copy_action, + target_factory = env.fs.Dir, + source_factory = env.fs.Entry, + multi = 1, + emitter = [ copyto_emitter, ] ) + + global copyAsBuilder + if copyAsBuilder is None: + copyAsBuilder = SCons.Builder.Builder( + action = copy_action, + target_factory = env.fs.Entry, + source_factory = env.fs.Entry ) + + env['BUILDERS']['CopyTo'] = copyToBuilder + env['BUILDERS']['CopyAs'] = copyAsBuilder + + env['COPYSTR'] = 'Copy file(s): "$SOURCES" to "$TARGETS"' + +def exists(env): + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/fortran.py b/src/engine/SCons/Tool/fortran.py new file mode 100644 index 0000000..b88c1a9 --- /dev/null +++ b/src/engine/SCons/Tool/fortran.py @@ -0,0 +1,63 @@ +"""SCons.Tool.fortran + +Tool-specific initialization for a generic Posix f77/f90 Fortran compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/fortran.py 4577 2009/12/27 19:44:43 scons" + +import re +import string + +import SCons.Action +import SCons.Defaults +import SCons.Scanner.Fortran +import SCons.Tool +import SCons.Util +from SCons.Tool.FortranCommon import add_all_to_env, add_fortran_to_env + +compilers = ['f95', 'f90', 'f77'] + +def generate(env): + add_all_to_env(env) + add_fortran_to_env(env) + + fc = env.Detect(compilers) or 'f77' + env['SHFORTRAN'] = fc + env['FORTRAN'] = fc + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/fortran.xml b/src/engine/SCons/Tool/fortran.xml new file mode 100644 index 0000000..1800559 --- /dev/null +++ b/src/engine/SCons/Tool/fortran.xml @@ -0,0 +1,302 @@ + + + +Set construction variables for generic POSIX Fortran compilers. + + +FORTRAN +FORTRANFLAGS +FORTRANCOM +SHFORTRAN +SHFORTRANFLAGS +SHFORTRANCOM +SHFORTRANPPCOM + + +FORTRANCOMSTR +FORTRANPPCOMSTR +SHFORTRANCOMSTR +SHFORTRANPPCOMSTR + + + + + +The default Fortran compiler +for all versions of Fortran. + + + + + +The command line used to compile a Fortran source file to an object file. +By default, any options specified +in the &cv-link-FORTRANFLAGS;, +&cv-link-CPPFLAGS;, +&cv-link-_CPPDEFFLAGS;, +&cv-link-_FORTRANMODFLAG;, and +&cv-link-_FORTRANINCFLAGS; construction variables +are included on this command line. + + + + + +The string displayed when a Fortran source file +is compiled to an object file. +If this is not set, then &cv-link-FORTRANCOM; +(the command line) is displayed. + + + + + +The list of file extensions for which the FORTRAN dialect will be used. By +default, this is ['.f', '.for', '.ftn'] + + + + + +The list of file extensions for which the compilation + preprocessor pass for +FORTRAN dialect will be used. By default, this is ['.fpp', '.FPP'] + + + + + +General user-specified options that are passed to the Fortran compiler. +Note that this variable does +not +contain + +(or similar) include or module search path options +that scons generates automatically from &cv-link-FORTRANPATH;. +See +&cv-link-_FORTRANINCFLAGS; and &cv-link-_FORTRANMODFLAG;, +below, +for the variables that expand those options. + + + + + +An automatically-generated construction variable +containing the Fortran compiler command-line options +for specifying directories to be searched for include +files and module files. +The value of &cv-link-_FORTRANINCFLAGS; is created +by prepending/appending &cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +to the beginning and end +of each directory in &cv-link-FORTRANPATH;. + + + + + +Directory location where the Fortran compiler should place +any module files it generates. This variable is empty, by default. Some +Fortran compilers will internally append this directory in the search path +for module files, as well. + + + + + +The prefix used to specify a module directory on the Fortran compiler command +line. +This will be appended to the beginning of the directory +in the &cv-link-FORTRANMODDIR; construction variables +when the &cv-link-_FORTRANMODFLAG; variables is automatically generated. + + + + + +The suffix used to specify a module directory on the Fortran compiler command +line. +This will be appended to the beginning of the directory +in the &cv-link-FORTRANMODDIR; construction variables +when the &cv-link-_FORTRANMODFLAG; variables is automatically generated. + + + + + +An automatically-generated construction variable +containing the Fortran compiler command-line option +for specifying the directory location where the Fortran +compiler should place any module files that happen to get +generated during compilation. +The value of &cv-link-_FORTRANMODFLAG; is created +by prepending/appending &cv-link-FORTRANMODDIRPREFIX; and +&cv-link-FORTRANMODDIRSUFFIX; +to the beginning and end of the directory in &cv-link-FORTRANMODDIR;. + + + + + +The module file prefix used by the Fortran compiler. SCons assumes that +the Fortran compiler follows the quasi-standard naming convention for +module files of +module_name.mod. +As a result, this variable is left empty, by default. For situations in +which the compiler does not necessarily follow the normal convention, +the user may use this variable. Its value will be appended to every +module file name as scons attempts to resolve dependencies. + + + + + +The module file suffix used by the Fortran compiler. SCons assumes that +the Fortran compiler follows the quasi-standard naming convention for +module files of +module_name.mod. +As a result, this variable is set to ".mod", by default. For situations +in which the compiler does not necessarily follow the normal convention, +the user may use this variable. Its value will be appended to every +module file name as scons attempts to resolve dependencies. + + + + + +The list of directories that the Fortran compiler will search for +include files and (for some compilers) module files. The Fortran implicit +dependency scanner will search these directories for include files (but +not module files since they are autogenerated and, as such, may not +actually exist at the time the scan takes place). Don't explicitly put +include directory arguments in FORTRANFLAGS because the result will be +non-portable and the directories will not be searched by the dependency +scanner. Note: directory names in FORTRANPATH will be looked-up relative +to the SConscript directory when they are used in a command. To force +&scons; +to look-up a directory relative to the root of the source tree use #: + + +env = Environment(FORTRANPATH='#/include') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +include = Dir('include') +env = Environment(FORTRANPATH=include) + + +The directory list will be added to command lines +through the automatically-generated +&cv-link-_FORTRANINCFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-link-INCPREFIX; and &cv-link-INCSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-link-FORTRANPATH;. +Any command lines you define that need +the FORTRANPATH directory list should +include &cv-link-_FORTRANINCFLAGS;: + + +env = Environment(FORTRANCOM="my_compiler $_FORTRANINCFLAGS -c -o $TARGET $SOURCE") + + + + + + +The command line used to compile a Fortran source file to an object file +after first running the file through the C preprocessor. +By default, any options specified in the &cv-link-FORTRANFLAGS;, +&cv-link-CPPFLAGS;, +&cv-link-_CPPDEFFLAGS;, +&cv-link-_FORTRANMODFLAG;, and +&cv-link-_FORTRANINCFLAGS; +construction variables are included on this command line. + + + + + +The string displayed when a Fortran source file +is compiled to an object file +after first running the file throught the C preprocessor. +If this is not set, then &cv-link-FORTRANPPCOM; +(the command line) is displayed. + + + + + +The list of suffixes of files that will be scanned +for Fortran implicit dependencies +(INCLUDE lines and USE statements). +The default list is: + + +[".f", ".F", ".for", ".FOR", ".ftn", ".FTN", ".fpp", ".FPP", +".f77", ".F77", ".f90", ".F90", ".f95", ".F95"] + + + + + + +The default Fortran compiler used for generating shared-library objects. + + + + + +The command line used to compile a Fortran source file +to a shared-library object file. + + + + + +The string displayed when a Fortran source file +is compiled to a shared-library object file. +If this is not set, then &cv-link-SHFORTRANCOM; +(the command line) is displayed. + + + + + +Options that are passed to the Fortran compiler +to generate shared-library objects. + + + + + +The command line used to compile a Fortran source file to a +shared-library object file +after first running the file through the C preprocessor. +Any options specified +in the &cv-link-SHFORTRANFLAGS; and +&cv-link-CPPFLAGS; construction variables +are included on this command line. + + + + + +The string displayed when a Fortran source file +is compiled to a shared-library object file +after first running the file throught the C preprocessor. +If this is not set, then &cv-link-SHFORTRANPPCOM; +(the command line) is displayed. + + diff --git a/src/engine/SCons/Tool/g++.py b/src/engine/SCons/Tool/g++.py new file mode 100644 index 0000000..1ffb254 --- /dev/null +++ b/src/engine/SCons/Tool/g++.py @@ -0,0 +1,90 @@ +"""SCons.Tool.g++ + +Tool-specific initialization for g++. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/g++.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import re +import subprocess + +import SCons.Tool +import SCons.Util + +cplusplus = __import__('c++', globals(), locals(), []) + +compilers = ['g++'] + +def generate(env): + """Add Builders and construction variables for g++ to an Environment.""" + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + cplusplus.generate(env) + + env['CXX'] = env.Detect(compilers) + + # platform specific settings + if env['PLATFORM'] == 'aix': + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -mminimal-toc') + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + env['SHOBJSUFFIX'] = '$OBJSUFFIX' + elif env['PLATFORM'] == 'hpux': + env['SHOBJSUFFIX'] = '.pic.o' + elif env['PLATFORM'] == 'sunos': + env['SHOBJSUFFIX'] = '.pic.o' + # determine compiler version + if env['CXX']: + #pipe = SCons.Action._subproc(env, [env['CXX'], '-dumpversion'], + pipe = SCons.Action._subproc(env, [env['CXX'], '--version'], + stdin = 'devnull', + stderr = 'devnull', + stdout = subprocess.PIPE) + if pipe.wait() != 0: return + # -dumpversion was added in GCC 3.0. As long as we're supporting + # GCC versions older than that, we should use --version and a + # regular expression. + #line = pipe.stdout.read().strip() + #if line: + # env['CXXVERSION'] = line + line = pipe.stdout.readline() + match = re.search(r'[0-9]+(\.[0-9]+)+', line) + if match: + env['CXXVERSION'] = match.group(0) + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/g++.xml b/src/engine/SCons/Tool/g++.xml new file mode 100644 index 0000000..37bd2f8 --- /dev/null +++ b/src/engine/SCons/Tool/g++.xml @@ -0,0 +1,18 @@ + + + +Set construction variables for the &gXX; C++ compiler. + + +CXX +SHCXXFLAGS + +SHOBJSUFFIX +CXXVERSION + + diff --git a/src/engine/SCons/Tool/g77.py b/src/engine/SCons/Tool/g77.py new file mode 100644 index 0000000..2607b76 --- /dev/null +++ b/src/engine/SCons/Tool/g77.py @@ -0,0 +1,73 @@ +"""engine.SCons.Tool.g77 + +Tool-specific initialization for g77. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/g77.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util +from SCons.Tool.FortranCommon import add_all_to_env, add_f77_to_env + +compilers = ['g77', 'f77'] + +def generate(env): + """Add Builders and construction variables for g77 to an Environment.""" + add_all_to_env(env) + add_f77_to_env(env) + + fcomp = env.Detect(compilers) or 'g77' + if env['PLATFORM'] in ['cygwin', 'win32']: + env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS') + env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS') + else: + env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -fPIC') + env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS -fPIC') + + env['FORTRAN'] = fcomp + env['SHFORTRAN'] = '$FORTRAN' + + env['F77'] = fcomp + env['SHF77'] = '$F77' + + env['INCFORTRANPREFIX'] = "-I" + env['INCFORTRANSUFFIX'] = "" + + env['INCF77PREFIX'] = "-I" + env['INCF77SUFFIX'] = "" + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/g77.xml b/src/engine/SCons/Tool/g77.xml new file mode 100644 index 0000000..b694580 --- /dev/null +++ b/src/engine/SCons/Tool/g77.xml @@ -0,0 +1,13 @@ + + + +Set construction variables for the &g77; Fortran compiler. +Calls the &t-f77; Tool module +to set variables. + + diff --git a/src/engine/SCons/Tool/gas.py b/src/engine/SCons/Tool/gas.py new file mode 100644 index 0000000..7ac4d71 --- /dev/null +++ b/src/engine/SCons/Tool/gas.py @@ -0,0 +1,53 @@ +"""SCons.Tool.gas + +Tool-specific initialization for as, the Gnu assembler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/gas.py 4577 2009/12/27 19:44:43 scons" + +as_module = __import__('as', globals(), locals(), []) + +assemblers = ['as', 'gas'] + +def generate(env): + """Add Builders and construction variables for as to an Environment.""" + as_module.generate(env) + + env['AS'] = env.Detect(assemblers) or 'as' + +def exists(env): + return env.Detect(assemblers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/gas.xml b/src/engine/SCons/Tool/gas.xml new file mode 100644 index 0000000..6b60b33 --- /dev/null +++ b/src/engine/SCons/Tool/gas.xml @@ -0,0 +1,15 @@ + + + +Sets construction variables for the &gas; assembler. +Calls the &t-as; module. + + +AS + + diff --git a/src/engine/SCons/Tool/gcc.py b/src/engine/SCons/Tool/gcc.py new file mode 100644 index 0000000..58597a6 --- /dev/null +++ b/src/engine/SCons/Tool/gcc.py @@ -0,0 +1,80 @@ +"""SCons.Tool.gcc + +Tool-specific initialization for gcc. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/gcc.py 4577 2009/12/27 19:44:43 scons" + +import cc +import os +import re +import subprocess + +import SCons.Util + +compilers = ['gcc', 'cc'] + +def generate(env): + """Add Builders and construction variables for gcc to an Environment.""" + cc.generate(env) + + env['CC'] = env.Detect(compilers) or 'gcc' + if env['PLATFORM'] in ['cygwin', 'win32']: + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + else: + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC') + # determine compiler version + if env['CC']: + #pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'], + pipe = SCons.Action._subproc(env, [env['CC'], '--version'], + stdin = 'devnull', + stderr = 'devnull', + stdout = subprocess.PIPE) + if pipe.wait() != 0: return + # -dumpversion was added in GCC 3.0. As long as we're supporting + # GCC versions older than that, we should use --version and a + # regular expression. + #line = pipe.stdout.read().strip() + #if line: + # env['CCVERSION'] = line + line = pipe.stdout.readline() + match = re.search(r'[0-9]+(\.[0-9]+)+', line) + if match: + env['CCVERSION'] = match.group(0) + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/gcc.xml b/src/engine/SCons/Tool/gcc.xml new file mode 100644 index 0000000..ac78a98 --- /dev/null +++ b/src/engine/SCons/Tool/gcc.xml @@ -0,0 +1,16 @@ + + + +Set construction variables for the &gcc; C compiler. + + +CC +SHCCFLAGS +CCVERSION + + diff --git a/src/engine/SCons/Tool/gfortran.py b/src/engine/SCons/Tool/gfortran.py new file mode 100644 index 0000000..79e8817 --- /dev/null +++ b/src/engine/SCons/Tool/gfortran.py @@ -0,0 +1,64 @@ +"""SCons.Tool.gfortran + +Tool-specific initialization for gfortran, the GNU Fortran 95/Fortran +2003 compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/gfortran.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +import fortran + +def generate(env): + """Add Builders and construction variables for gfortran to an + Environment.""" + fortran.generate(env) + + for dialect in ['F77', 'F90', 'FORTRAN', 'F95']: + env['%s' % dialect] = 'gfortran' + env['SH%s' % dialect] = '$%s' % dialect + if env['PLATFORM'] in ['cygwin', 'win32']: + env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS' % dialect) + else: + env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS -fPIC' % dialect) + + env['INC%sPREFIX' % dialect] = "-I" + env['INC%sSUFFIX' % dialect] = "" + +def exists(env): + return env.Detect('gfortran') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/gfortran.xml b/src/engine/SCons/Tool/gfortran.xml new file mode 100644 index 0000000..cf6bb9a --- /dev/null +++ b/src/engine/SCons/Tool/gfortran.xml @@ -0,0 +1,25 @@ + + + +Sets construction variables for the GNU F95/F2003 GNU compiler. + + +FORTRAN +F77 +F90 +F95 +SHFORTRAN +SHF77 +SHF90 +SHF95 +SHFORTRANFLAGS +SHF77FLAGS +SHF90FLAGS +SHF95FLAGS + + diff --git a/src/engine/SCons/Tool/gnulink.py b/src/engine/SCons/Tool/gnulink.py new file mode 100644 index 0000000..9a12f82 --- /dev/null +++ b/src/engine/SCons/Tool/gnulink.py @@ -0,0 +1,63 @@ +"""SCons.Tool.gnulink + +Tool-specific initialization for the gnu linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/gnulink.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +import link + +linkers = ['g++', 'gcc'] + +def generate(env): + """Add Builders and construction variables for gnulink to an Environment.""" + link.generate(env) + + if env['PLATFORM'] == 'hpux': + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared -fPIC') + + # __RPATH is set to $_RPATH in the platform specification if that + # platform supports it. + env.Append(LINKFLAGS=['$__RPATH']) + env['RPATHPREFIX'] = '-Wl,-rpath=' + env['RPATHSUFFIX'] = '' + env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}' + +def exists(env): + return env.Detect(linkers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/gnulink.xml b/src/engine/SCons/Tool/gnulink.xml new file mode 100644 index 0000000..0f499de --- /dev/null +++ b/src/engine/SCons/Tool/gnulink.xml @@ -0,0 +1,16 @@ + + + +Set construction variables for GNU linker/loader. + + +SHLINKFLAGS +RPATHPREFIX +RPATHSUFFIX + + diff --git a/src/engine/SCons/Tool/gs.py b/src/engine/SCons/Tool/gs.py new file mode 100644 index 0000000..2c0db92 --- /dev/null +++ b/src/engine/SCons/Tool/gs.py @@ -0,0 +1,81 @@ +"""SCons.Tool.gs + +Tool-specific initialization for Ghostscript. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/gs.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Platform +import SCons.Util + +# Ghostscript goes by different names on different platforms... +platform = SCons.Platform.platform_default() + +if platform == 'os2': + gs = 'gsos2' +elif platform == 'win32': + gs = 'gswin32c' +else: + gs = 'gs' + +GhostscriptAction = None + +def generate(env): + """Add Builders and construction variables for Ghostscript to an + Environment.""" + + global GhostscriptAction + if GhostscriptAction is None: + GhostscriptAction = SCons.Action.Action('$GSCOM', '$GSCOMSTR') + + import pdf + pdf.generate(env) + + bld = env['BUILDERS']['PDF'] + bld.add_action('.ps', GhostscriptAction) + + env['GS'] = gs + env['GSFLAGS'] = SCons.Util.CLVar('-dNOPAUSE -dBATCH -sDEVICE=pdfwrite') + env['GSCOM'] = '$GS $GSFLAGS -sOutputFile=$TARGET $SOURCES' + + +def exists(env): + if env.has_key('PS2PDF'): + return env.Detect(env['PS2PDF']) + else: + return env.Detect(gs) or SCons.Util.WhereIs(gs) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/gs.xml b/src/engine/SCons/Tool/gs.xml new file mode 100644 index 0000000..e1817d5 --- /dev/null +++ b/src/engine/SCons/Tool/gs.xml @@ -0,0 +1,47 @@ + + + +Set construction variables for Ghostscript. + + +GS +GSFLAGS +GSCOM + + +GSCOMSTR + + + + + +The Ghostscript program used to convert PostScript to PDF files. + + + + + +The Ghostscript command line used to convert PostScript to PDF files. + + + + + +The string displayed when +Ghostscript is used to convert +a PostScript file to a PDF file. +If this is not set, then &cv-link-GSCOM; (the command line) is displayed. + + + + + +General options passed to the Ghostscript program +when converting PostScript to PDF files. + + diff --git a/src/engine/SCons/Tool/hpc++.py b/src/engine/SCons/Tool/hpc++.py new file mode 100644 index 0000000..cd7e041 --- /dev/null +++ b/src/engine/SCons/Tool/hpc++.py @@ -0,0 +1,85 @@ +"""SCons.Tool.hpc++ + +Tool-specific initialization for c++ on HP/UX. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/hpc++.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string + +import SCons.Util + +cplusplus = __import__('c++', globals(), locals(), []) + +acc = None + +# search for the acc compiler and linker front end + +try: + dirs = os.listdir('/opt') +except (IOError, OSError): + # Not being able to read the directory because it doesn't exist + # (IOError) or isn't readable (OSError) is okay. + dirs = [] + +for dir in dirs: + cc = '/opt/' + dir + '/bin/aCC' + if os.path.exists(cc): + acc = cc + break + + +def generate(env): + """Add Builders and construction variables for g++ to an Environment.""" + cplusplus.generate(env) + + if acc: + env['CXX'] = acc or 'aCC' + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS +Z') + # determine version of aCC + line = os.popen(acc + ' -V 2>&1').readline().rstrip() + if string.find(line, 'aCC: HP ANSI C++') == 0: + env['CXXVERSION'] = string.split(line)[-1] + + if env['PLATFORM'] == 'cygwin': + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS') + else: + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS +Z') + +def exists(env): + return acc + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/hpc++.xml b/src/engine/SCons/Tool/hpc++.xml new file mode 100644 index 0000000..3829ae7 --- /dev/null +++ b/src/engine/SCons/Tool/hpc++.xml @@ -0,0 +1,11 @@ + + + +Set construction variables for the compilers aCC on HP/UX systems. + + diff --git a/src/engine/SCons/Tool/hpcc.py b/src/engine/SCons/Tool/hpcc.py new file mode 100644 index 0000000..122e3aa --- /dev/null +++ b/src/engine/SCons/Tool/hpcc.py @@ -0,0 +1,53 @@ +"""SCons.Tool.hpcc + +Tool-specific initialization for HP aCC and cc. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/hpcc.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +import cc + +def generate(env): + """Add Builders and construction variables for aCC & cc to an Environment.""" + cc.generate(env) + + env['CXX'] = 'aCC' + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS +Z') + +def exists(env): + return env.Detect('aCC') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/hpcc.xml b/src/engine/SCons/Tool/hpcc.xml new file mode 100644 index 0000000..f103384 --- /dev/null +++ b/src/engine/SCons/Tool/hpcc.xml @@ -0,0 +1,18 @@ + + + +Set construction variables for the +aCC on HP/UX systems. +Calls the &t-cXX; tool for additional variables. + + +CXX +SHCXXFLAGS +CXXVERSION + + diff --git a/src/engine/SCons/Tool/hplink.py b/src/engine/SCons/Tool/hplink.py new file mode 100644 index 0000000..32c59a5 --- /dev/null +++ b/src/engine/SCons/Tool/hplink.py @@ -0,0 +1,77 @@ +"""SCons.Tool.hplink + +Tool-specific initialization for the HP linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/hplink.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path + +import SCons.Util + +import link + +ccLinker = None + +# search for the acc compiler and linker front end + +try: + dirs = os.listdir('/opt') +except (IOError, OSError): + # Not being able to read the directory because it doesn't exist + # (IOError) or isn't readable (OSError) is okay. + dirs = [] + +for dir in dirs: + linker = '/opt/' + dir + '/bin/aCC' + if os.path.exists(linker): + ccLinker = linker + break + +def generate(env): + """ + Add Builders and construction variables for Visual Age linker to + an Environment. + """ + link.generate(env) + + env['LINKFLAGS'] = SCons.Util.CLVar('-Wl,+s -Wl,+vnocompatwarnings') + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -b') + env['SHLIBSUFFIX'] = '.sl' + +def exists(env): + return ccLinker + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/hplink.xml b/src/engine/SCons/Tool/hplink.xml new file mode 100644 index 0000000..d4c29fb --- /dev/null +++ b/src/engine/SCons/Tool/hplink.xml @@ -0,0 +1,16 @@ + + + +Sets construction variables for the linker on HP/UX systems. + + +LINKFLAGS +SHLINKFLAGS +SHLIBSUFFIX + + diff --git a/src/engine/SCons/Tool/icc.py b/src/engine/SCons/Tool/icc.py new file mode 100644 index 0000000..78f8606 --- /dev/null +++ b/src/engine/SCons/Tool/icc.py @@ -0,0 +1,59 @@ +"""engine.SCons.Tool.icc + +Tool-specific initialization for the OS/2 icc compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/icc.py 4577 2009/12/27 19:44:43 scons" + +import cc + +def generate(env): + """Add Builders and construction variables for the OS/2 to an Environment.""" + cc.generate(env) + + env['CC'] = 'icc' + env['CCCOM'] = '$CC $CFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET' + env['CXXCOM'] = '$CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET' + env['CPPDEFPREFIX'] = '/D' + env['CPPDEFSUFFIX'] = '' + env['INCPREFIX'] = '/I' + env['INCSUFFIX'] = '' + env['CFILESUFFIX'] = '.c' + env['CXXFILESUFFIX'] = '.cc' + +def exists(env): + return env.Detect('icc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/icc.xml b/src/engine/SCons/Tool/icc.xml new file mode 100644 index 0000000..6b03105 --- /dev/null +++ b/src/engine/SCons/Tool/icc.xml @@ -0,0 +1,30 @@ + + + +Sets construction variables for the +icc compiler on OS/2 systems. + + +CC +CCCOM +CXXCOM +CPPDEFPREFIX +CPPDEFSUFFIX +INCPREFIX +INCSUFFIX +CFILESUFFIX +CXXFILESUFFIX + + +CFLAGS +CCFLAGS +CPPFLAGS +_CPPDEFFLAGS +_CPPINCFLAGS + + diff --git a/src/engine/SCons/Tool/icl.py b/src/engine/SCons/Tool/icl.py new file mode 100644 index 0000000..3a43da7 --- /dev/null +++ b/src/engine/SCons/Tool/icl.py @@ -0,0 +1,52 @@ +"""engine.SCons.Tool.icl + +Tool-specific initialization for the Intel C/C++ compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/icl.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Tool.intelc + +# This has been completely superceded by intelc.py, which can +# handle both Windows and Linux versions. + +def generate(*args, **kw): + """Add Builders and construction variables for icl to an Environment.""" + return apply(SCons.Tool.intelc.generate, args, kw) + +def exists(*args, **kw): + return apply(SCons.Tool.intelc.exists, args, kw) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/icl.xml b/src/engine/SCons/Tool/icl.xml new file mode 100644 index 0000000..06dc7af --- /dev/null +++ b/src/engine/SCons/Tool/icl.xml @@ -0,0 +1,12 @@ + + + +Sets construction variables for the Intel C/C++ compiler. +Calls the &t-intelc; Tool module to set its variables. + + diff --git a/src/engine/SCons/Tool/ifl.py b/src/engine/SCons/Tool/ifl.py new file mode 100644 index 0000000..8fe8c3a --- /dev/null +++ b/src/engine/SCons/Tool/ifl.py @@ -0,0 +1,72 @@ +"""SCons.Tool.ifl + +Tool-specific initialization for the Intel Fortran compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ifl.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +from SCons.Scanner.Fortran import FortranScan +from FortranCommon import add_all_to_env + +def generate(env): + """Add Builders and construction variables for ifl to an Environment.""" + fscan = FortranScan("FORTRANPATH") + SCons.Tool.SourceFileScanner.add_scanner('.i', fscan) + SCons.Tool.SourceFileScanner.add_scanner('.i90', fscan) + + if not env.has_key('FORTRANFILESUFFIXES'): + env['FORTRANFILESUFFIXES'] = ['.i'] + else: + env['FORTRANFILESUFFIXES'].append('.i') + + if not env.has_key('F90FILESUFFIXES'): + env['F90FILESUFFIXES'] = ['.i90'] + else: + env['F90FILESUFFIXES'].append('.i90') + + add_all_to_env(env) + + env['FORTRAN'] = 'ifl' + env['SHFORTRAN'] = '$FORTRAN' + env['FORTRANCOM'] = '$FORTRAN $FORTRANFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET' + env['FORTRANPPCOM'] = '$FORTRAN $FORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET' + env['SHFORTRANCOM'] = '$SHFORTRAN $SHFORTRANFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET' + env['SHFORTRANPPCOM'] = '$SHFORTRAN $SHFORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET' + +def exists(env): + return env.Detect('ifl') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/ifl.xml b/src/engine/SCons/Tool/ifl.xml new file mode 100644 index 0000000..3456d36 --- /dev/null +++ b/src/engine/SCons/Tool/ifl.xml @@ -0,0 +1,24 @@ + + + +Sets construction variables for the Intel Fortran compiler. + + +FORTRAN +FORTRANCOM +FORTRANPPCOM +SHFORTRANCOM +SHFORTRANPPCOM + + +FORTRANFLAGS +_FORTRANINCFLAGS +CPPFLAGS +_CPPDEFFLAGS + + diff --git a/src/engine/SCons/Tool/ifort.py b/src/engine/SCons/Tool/ifort.py new file mode 100644 index 0000000..250d875 --- /dev/null +++ b/src/engine/SCons/Tool/ifort.py @@ -0,0 +1,90 @@ +"""SCons.Tool.ifort + +Tool-specific initialization for newer versions of the Intel Fortran Compiler +for Linux/Windows (and possibly Mac OS X). + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ifort.py 4577 2009/12/27 19:44:43 scons" + +import string + +import SCons.Defaults +from SCons.Scanner.Fortran import FortranScan +from FortranCommon import add_all_to_env + +def generate(env): + """Add Builders and construction variables for ifort to an Environment.""" + # ifort supports Fortran 90 and Fortran 95 + # Additionally, ifort recognizes more file extensions. + fscan = FortranScan("FORTRANPATH") + SCons.Tool.SourceFileScanner.add_scanner('.i', fscan) + SCons.Tool.SourceFileScanner.add_scanner('.i90', fscan) + + if not env.has_key('FORTRANFILESUFFIXES'): + env['FORTRANFILESUFFIXES'] = ['.i'] + else: + env['FORTRANFILESUFFIXES'].append('.i') + + if not env.has_key('F90FILESUFFIXES'): + env['F90FILESUFFIXES'] = ['.i90'] + else: + env['F90FILESUFFIXES'].append('.i90') + + add_all_to_env(env) + + fc = 'ifort' + + for dialect in ['F77', 'F90', 'FORTRAN', 'F95']: + env['%s' % dialect] = fc + env['SH%s' % dialect] = '$%s' % dialect + if env['PLATFORM'] == 'posix': + env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS -fPIC' % dialect) + + if env['PLATFORM'] == 'win32': + # On Windows, the ifort compiler specifies the object on the + # command line with -object:, not -o. Massage the necessary + # command-line construction variables. + for dialect in ['F77', 'F90', 'FORTRAN', 'F95']: + for var in ['%sCOM' % dialect, '%sPPCOM' % dialect, + 'SH%sCOM' % dialect, 'SH%sPPCOM' % dialect]: + env[var] = string.replace(env[var], '-o $TARGET', '-object:$TARGET') + env['FORTRANMODDIRPREFIX'] = "/module:" + else: + env['FORTRANMODDIRPREFIX'] = "-module " + +def exists(env): + return env.Detect('ifort') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/ifort.xml b/src/engine/SCons/Tool/ifort.xml new file mode 100644 index 0000000..fc348ab --- /dev/null +++ b/src/engine/SCons/Tool/ifort.xml @@ -0,0 +1,26 @@ + + + +Sets construction variables for newer versions +of the Intel Fortran compiler for Linux. + + +FORTRAN +F77 +F90 +F95 +SHFORTRAN +SHF77 +SHF90 +SHF95 +SHFORTRANFLAGS +SHF77FLAGS +SHF90FLAGS +SHF95FLAGS + + diff --git a/src/engine/SCons/Tool/ilink.py b/src/engine/SCons/Tool/ilink.py new file mode 100644 index 0000000..c8d74f7 --- /dev/null +++ b/src/engine/SCons/Tool/ilink.py @@ -0,0 +1,59 @@ +"""SCons.Tool.ilink + +Tool-specific initialization for the OS/2 ilink linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ilink.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +def generate(env): + """Add Builders and construction variables for ilink to an Environment.""" + SCons.Tool.createProgBuilder(env) + + env['LINK'] = 'ilink' + env['LINKFLAGS'] = SCons.Util.CLVar('') + env['LINKCOM'] = '$LINK $LINKFLAGS /O:$TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + env['LIBDIRPREFIX']='/LIBPATH:' + env['LIBDIRSUFFIX']='' + env['LIBLINKPREFIX']='' + env['LIBLINKSUFFIX']='$LIBSUFFIX' + +def exists(env): + return env.Detect('ilink') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/ilink.xml b/src/engine/SCons/Tool/ilink.xml new file mode 100644 index 0000000..a5f25a6 --- /dev/null +++ b/src/engine/SCons/Tool/ilink.xml @@ -0,0 +1,23 @@ + + + +Sets construction variables for the +ilink linker on OS/2 systems. + + +LINK +LINKFLAGS +LINKCOM +LIBDIRPREFIX +LIBDIRSUFFIX +LIBLINKPREFIX +LIBLINKSUFFIX + + + + diff --git a/src/engine/SCons/Tool/ilink32.py b/src/engine/SCons/Tool/ilink32.py new file mode 100644 index 0000000..f0fd9f8 --- /dev/null +++ b/src/engine/SCons/Tool/ilink32.py @@ -0,0 +1,60 @@ +"""SCons.Tool.ilink32 + +XXX + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ilink32.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Tool +import SCons.Tool.bcc32 +import SCons.Util + +def generate(env): + """Add Builders and construction variables for Borland ilink to an + Environment.""" + SCons.Tool.createSharedLibBuilder(env) + SCons.Tool.createProgBuilder(env) + + env['LINK'] = '$CC' + env['LINKFLAGS'] = SCons.Util.CLVar('') + env['LINKCOM'] = '$LINK -q $LINKFLAGS -e$TARGET $SOURCES $LIBS' + env['LIBDIRPREFIX']='' + env['LIBDIRSUFFIX']='' + env['LIBLINKPREFIX']='' + env['LIBLINKSUFFIX']='$LIBSUFFIX' + + +def exists(env): + # Uses bcc32 to do linking as it generally knows where the standard + # LIBS are and set up the linking correctly + return SCons.Tool.bcc32.findIt('bcc32', env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/ilink32.xml b/src/engine/SCons/Tool/ilink32.xml new file mode 100644 index 0000000..0528487 --- /dev/null +++ b/src/engine/SCons/Tool/ilink32.xml @@ -0,0 +1,23 @@ + + + +Sets construction variables for the Borland +ilink32 linker. + + +LINK +LINKFLAGS +LINKCOM +LIBDIRPREFIX +LIBDIRSUFFIX +LIBLINKPREFIX +LIBLINKSUFFIX + + + + diff --git a/src/engine/SCons/Tool/install.py b/src/engine/SCons/Tool/install.py new file mode 100644 index 0000000..4c2ac2e --- /dev/null +++ b/src/engine/SCons/Tool/install.py @@ -0,0 +1,229 @@ +"""SCons.Tool.install + +Tool-specific initialization for the install tool. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/install.py 4577 2009/12/27 19:44:43 scons" + +import os +import shutil +import stat + +import SCons.Action +from SCons.Util import make_path_relative + +# +# We keep track of *all* installed files. +_INSTALLED_FILES = [] +_UNIQUE_INSTALLED_FILES = None + +# +# Functions doing the actual work of the Install Builder. +# +def copyFunc(dest, source, env): + """Install a source file or directory into a destination by copying, + (including copying permission/mode bits).""" + + if os.path.isdir(source): + if os.path.exists(dest): + if not os.path.isdir(dest): + raise SCons.Errors.UserError, "cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source)) + else: + parent = os.path.split(dest)[0] + if not os.path.exists(parent): + os.makedirs(parent) + shutil.copytree(source, dest) + else: + shutil.copy2(source, dest) + st = os.stat(source) + os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE) + + return 0 + +def installFunc(target, source, env): + """Install a source file into a target using the function specified + as the INSTALL construction variable.""" + try: + install = env['INSTALL'] + except KeyError: + raise SCons.Errors.UserError('Missing INSTALL construction variable.') + + assert len(target)==len(source), \ + "Installing source %s into target %s: target and source lists must have same length."%(map(str, source), map(str, target)) + for t,s in zip(target,source): + if install(t.get_path(),s.get_path(),env): + return 1 + + return 0 + +def stringFunc(target, source, env): + installstr = env.get('INSTALLSTR') + if installstr: + return env.subst_target_source(installstr, 0, target, source) + target = str(target[0]) + source = str(source[0]) + if os.path.isdir(source): + type = 'directory' + else: + type = 'file' + return 'Install %s: "%s" as "%s"' % (type, source, target) + +# +# Emitter functions +# +def add_targets_to_INSTALLED_FILES(target, source, env): + """ an emitter that adds all target files to the list stored in the + _INSTALLED_FILES global variable. This way all installed files of one + scons call will be collected. + """ + global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES + _INSTALLED_FILES.extend(target) + _UNIQUE_INSTALLED_FILES = None + return (target, source) + +class DESTDIR_factory: + """ a node factory, where all files will be relative to the dir supplied + in the constructor. + """ + def __init__(self, env, dir): + self.env = env + self.dir = env.arg2nodes( dir, env.fs.Dir )[0] + + def Entry(self, name): + name = make_path_relative(name) + return self.dir.Entry(name) + + def Dir(self, name): + name = make_path_relative(name) + return self.dir.Dir(name) + +# +# The Builder Definition +# +install_action = SCons.Action.Action(installFunc, stringFunc) +installas_action = SCons.Action.Action(installFunc, stringFunc) + +BaseInstallBuilder = None + +def InstallBuilderWrapper(env, target=None, source=None, dir=None, **kw): + if target and dir: + import SCons.Errors + raise SCons.Errors.UserError, "Both target and dir defined for Install(), only one may be defined." + if not dir: + dir=target + + import SCons.Script + install_sandbox = SCons.Script.GetOption('install_sandbox') + if install_sandbox: + target_factory = DESTDIR_factory(env, install_sandbox) + else: + target_factory = env.fs + + try: + dnodes = env.arg2nodes(dir, target_factory.Dir) + except TypeError: + raise SCons.Errors.UserError, "Target `%s' of Install() is a file, but should be a directory. Perhaps you have the Install() arguments backwards?" % str(dir) + sources = env.arg2nodes(source, env.fs.Entry) + tgt = [] + for dnode in dnodes: + for src in sources: + # Prepend './' so the lookup doesn't interpret an initial + # '#' on the file name portion as meaning the Node should + # be relative to the top-level SConstruct directory. + target = env.fs.Entry('.'+os.sep+src.name, dnode) + #tgt.extend(BaseInstallBuilder(env, target, src, **kw)) + tgt.extend(apply(BaseInstallBuilder, (env, target, src), kw)) + return tgt + +def InstallAsBuilderWrapper(env, target=None, source=None, **kw): + result = [] + for src, tgt in map(lambda x, y: (x, y), source, target): + #result.extend(BaseInstallBuilder(env, tgt, src, **kw)) + result.extend(apply(BaseInstallBuilder, (env, tgt, src), kw)) + return result + +added = None + +def generate(env): + + from SCons.Script import AddOption, GetOption + global added + if not added: + added = 1 + AddOption('--install-sandbox', + dest='install_sandbox', + type="string", + action="store", + help='A directory under which all installed files will be placed.') + + global BaseInstallBuilder + if BaseInstallBuilder is None: + install_sandbox = GetOption('install_sandbox') + if install_sandbox: + target_factory = DESTDIR_factory(env, install_sandbox) + else: + target_factory = env.fs + + BaseInstallBuilder = SCons.Builder.Builder( + action = install_action, + target_factory = target_factory.Entry, + source_factory = env.fs.Entry, + multi = 1, + emitter = [ add_targets_to_INSTALLED_FILES, ], + name = 'InstallBuilder') + + env['BUILDERS']['_InternalInstall'] = InstallBuilderWrapper + env['BUILDERS']['_InternalInstallAs'] = InstallAsBuilderWrapper + + # We'd like to initialize this doing something like the following, + # but there isn't yet support for a ${SOURCE.type} expansion that + # will print "file" or "directory" depending on what's being + # installed. For now we punt by not initializing it, and letting + # the stringFunc() that we put in the action fall back to the + # hand-crafted default string if it's not set. + # + #try: + # env['INSTALLSTR'] + #except KeyError: + # env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"' + + try: + env['INSTALL'] + except KeyError: + env['INSTALL'] = copyFunc + +def exists(env): + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/install.xml b/src/engine/SCons/Tool/install.xml new file mode 100644 index 0000000..c9eea00 --- /dev/null +++ b/src/engine/SCons/Tool/install.xml @@ -0,0 +1,52 @@ + + + + +Sets construction variables for file +and directory installation. + + +INSTALL +INSTALLSTR + + + + + +Installs one or more source files or directories +in the specified target, +which must be a directory. +The names of the specified source files or directories +remain the same within the destination directory. + + +env.Install('/usr/local/bin', source = ['foo', 'bar']) + + + + + + +Installs one or more source files or directories +to specific names, +allowing changing a file or directory name +as part of the installation. +It is an error if the +target +and +source +arguments list different numbers of files or directories. + + +env.InstallAs(target = '/usr/local/bin/foo', + source = 'foo_debug') +env.InstallAs(target = ['../lib/libfoo.a', '../lib/libbar.a'], + source = ['libFOO.a', 'libBAR.a']) + + + diff --git a/src/engine/SCons/Tool/intelc.py b/src/engine/SCons/Tool/intelc.py new file mode 100644 index 0000000..7add013 --- /dev/null +++ b/src/engine/SCons/Tool/intelc.py @@ -0,0 +1,490 @@ +"""SCons.Tool.icl + +Tool-specific initialization for the Intel C/C++ compiler. +Supports Linux and Windows compilers, v7 and up. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/intelc.py 4577 2009/12/27 19:44:43 scons" + +import math, sys, os.path, glob, string, re + +is_windows = sys.platform == 'win32' +is_win64 = is_windows and (os.environ['PROCESSOR_ARCHITECTURE'] == 'AMD64' or + (os.environ.has_key('PROCESSOR_ARCHITEW6432') and + os.environ['PROCESSOR_ARCHITEW6432'] == 'AMD64')) +is_linux = sys.platform == 'linux2' +is_mac = sys.platform == 'darwin' + +if is_windows: + import SCons.Tool.msvc +elif is_linux: + import SCons.Tool.gcc +elif is_mac: + import SCons.Tool.gcc +import SCons.Util +import SCons.Warnings + +# Exceptions for this tool +class IntelCError(SCons.Errors.InternalError): + pass +class MissingRegistryError(IntelCError): # missing registry entry + pass +class MissingDirError(IntelCError): # dir not found + pass +class NoRegistryModuleError(IntelCError): # can't read registry at all + pass + +def uniquify(s): + """Return a sequence containing only one copy of each unique element from input sequence s. + Does not preserve order. + Input sequence must be hashable (i.e. must be usable as a dictionary key).""" + u = {} + for x in s: + u[x] = 1 + return u.keys() + +def linux_ver_normalize(vstr): + """Normalize a Linux compiler version number. + Intel changed from "80" to "9.0" in 2005, so we assume if the number + is greater than 60 it's an old-style number and otherwise new-style. + Always returns an old-style float like 80 or 90 for compatibility with Windows. + Shades of Y2K!""" + # Check for version number like 9.1.026: return 91.026 + m = re.match(r'([0-9]+)\.([0-9]+)\.([0-9]+)', vstr) + if m: + vmaj,vmin,build = m.groups() + return float(vmaj) * 10 + float(vmin) + float(build) / 1000.; + else: + f = float(vstr) + if is_windows: + return f + else: + if f < 60: return f * 10.0 + else: return f + +def check_abi(abi): + """Check for valid ABI (application binary interface) name, + and map into canonical one""" + if not abi: + return None + abi = abi.lower() + # valid_abis maps input name to canonical name + if is_windows: + valid_abis = {'ia32' : 'ia32', + 'x86' : 'ia32', + 'ia64' : 'ia64', + 'em64t' : 'em64t', + 'amd64' : 'em64t'} + if is_linux: + valid_abis = {'ia32' : 'ia32', + 'x86' : 'ia32', + 'x86_64' : 'x86_64', + 'em64t' : 'x86_64', + 'amd64' : 'x86_64'} + if is_mac: + valid_abis = {'ia32' : 'ia32', + 'x86' : 'ia32', + 'x86_64' : 'x86_64', + 'em64t' : 'x86_64'} + try: + abi = valid_abis[abi] + except KeyError: + raise SCons.Errors.UserError, \ + "Intel compiler: Invalid ABI %s, valid values are %s"% \ + (abi, valid_abis.keys()) + return abi + +def vercmp(a, b): + """Compare strings as floats, + but Intel changed Linux naming convention at 9.0""" + return cmp(linux_ver_normalize(b), linux_ver_normalize(a)) + +def get_version_from_list(v, vlist): + """See if we can match v (string) in vlist (list of strings) + Linux has to match in a fuzzy way.""" + if is_windows: + # Simple case, just find it in the list + if v in vlist: return v + else: return None + else: + # Fuzzy match: normalize version number first, but still return + # original non-normalized form. + fuzz = 0.001 + for vi in vlist: + if math.fabs(linux_ver_normalize(vi) - linux_ver_normalize(v)) < fuzz: + return vi + # Not found + return None + +def get_intel_registry_value(valuename, version=None, abi=None): + """ + Return a value from the Intel compiler registry tree. (Windows only) + """ + # Open the key: + if is_win64: + K = 'Software\\Wow6432Node\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper() + else: + K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper() + try: + k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K) + except SCons.Util.RegError: + raise MissingRegistryError, \ + "%s was not found in the registry, for Intel compiler version %s, abi='%s'"%(K, version,abi) + + # Get the value: + try: + v = SCons.Util.RegQueryValueEx(k, valuename)[0] + return v # or v.encode('iso-8859-1', 'replace') to remove unicode? + except SCons.Util.RegError: + raise MissingRegistryError, \ + "%s\\%s was not found in the registry."%(K, valuename) + + +def get_all_compiler_versions(): + """Returns a sorted list of strings, like "70" or "80" or "9.0" + with most recent compiler version first. + """ + versions=[] + if is_windows: + if is_win64: + keyname = 'Software\\WoW6432Node\\Intel\\Compilers\\C++' + else: + keyname = 'Software\\Intel\\Compilers\\C++' + try: + k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, + keyname) + except WindowsError: + return [] + i = 0 + versions = [] + try: + while i < 100: + subkey = SCons.Util.RegEnumKey(k, i) # raises EnvironmentError + # Check that this refers to an existing dir. + # This is not 100% perfect but should catch common + # installation issues like when the compiler was installed + # and then the install directory deleted or moved (rather + # than uninstalling properly), so the registry values + # are still there. + ok = False + for try_abi in ('IA32', 'IA32e', 'IA64', 'EM64T'): + try: + d = get_intel_registry_value('ProductDir', subkey, try_abi) + except MissingRegistryError: + continue # not found in reg, keep going + if os.path.exists(d): ok = True + if ok: + versions.append(subkey) + else: + try: + # Registry points to nonexistent dir. Ignore this + # version. + value = get_intel_registry_value('ProductDir', subkey, 'IA32') + except MissingRegistryError, e: + + # Registry key is left dangling (potentially + # after uninstalling). + + print \ + "scons: *** Ignoring the registry key for the Intel compiler version %s.\n" \ + "scons: *** It seems that the compiler was uninstalled and that the registry\n" \ + "scons: *** was not cleaned up properly.\n" % subkey + else: + print "scons: *** Ignoring "+str(value) + + i = i + 1 + except EnvironmentError: + # no more subkeys + pass + elif is_linux: + for d in glob.glob('/opt/intel_cc_*'): + # Typical dir here is /opt/intel_cc_80. + m = re.search(r'cc_(.*)$', d) + if m: + versions.append(m.group(1)) + for d in glob.glob('/opt/intel/cc*/*'): + # Typical dir here is /opt/intel/cc/9.0 for IA32, + # /opt/intel/cce/9.0 for EMT64 (AMD64) + m = re.search(r'([0-9.]+)$', d) + if m: + versions.append(m.group(1)) + elif is_mac: + for d in glob.glob('/opt/intel/cc*/*'): + # Typical dir here is /opt/intel/cc/9.0 for IA32, + # /opt/intel/cce/9.0 for EMT64 (AMD64) + m = re.search(r'([0-9.]+)$', d) + if m: + versions.append(m.group(1)) + versions = uniquify(versions) # remove dups + versions.sort(vercmp) + return versions + +def get_intel_compiler_top(version, abi): + """ + Return the main path to the top-level dir of the Intel compiler, + using the given version. + The compiler will be in /bin/icl.exe (icc on linux), + the include dir is /include, etc. + """ + + if is_windows: + if not SCons.Util.can_read_reg: + raise NoRegistryModuleError, "No Windows registry module was found" + top = get_intel_registry_value('ProductDir', version, abi) + # pre-11, icl was in Bin. 11 and later, it's in Bin/ apparently. + if not os.path.exists(os.path.join(top, "Bin", "icl.exe")) \ + and not os.path.exists(os.path.join(top, "Bin", abi, "icl.exe")): + raise MissingDirError, \ + "Can't find Intel compiler in %s"%(top) + elif is_mac or is_linux: + # first dir is new (>=9.0) style, second is old (8.0) style. + dirs=('/opt/intel/cc/%s', '/opt/intel_cc_%s') + if abi == 'x86_64': + dirs=('/opt/intel/cce/%s',) # 'e' stands for 'em64t', aka x86_64 aka amd64 + top=None + for d in dirs: + if os.path.exists(os.path.join(d%version, "bin", "icc")): + top = d%version + break + if not top: + raise MissingDirError, \ + "Can't find version %s Intel compiler in %s (abi='%s')"%(version,top, abi) + return top + + +def generate(env, version=None, abi=None, topdir=None, verbose=0): + """Add Builders and construction variables for Intel C/C++ compiler + to an Environment. + args: + version: (string) compiler version to use, like "80" + abi: (string) 'win32' or whatever Itanium version wants + topdir: (string) compiler top dir, like + "c:\Program Files\Intel\Compiler70" + If topdir is used, version and abi are ignored. + verbose: (int) if >0, prints compiler version used. + """ + if not (is_mac or is_linux or is_windows): + # can't handle this platform + return + + if is_windows: + SCons.Tool.msvc.generate(env) + elif is_linux: + SCons.Tool.gcc.generate(env) + elif is_mac: + SCons.Tool.gcc.generate(env) + + # if version is unspecified, use latest + vlist = get_all_compiler_versions() + if not version: + if vlist: + version = vlist[0] + else: + # User may have specified '90' but we need to get actual dirname '9.0'. + # get_version_from_list does that mapping. + v = get_version_from_list(version, vlist) + if not v: + raise SCons.Errors.UserError, \ + "Invalid Intel compiler version %s: "%version + \ + "installed versions are %s"%(', '.join(vlist)) + version = v + + # if abi is unspecified, use ia32 + # alternatives are ia64 for Itanium, or amd64 or em64t or x86_64 (all synonyms here) + abi = check_abi(abi) + if abi is None: + if is_mac or is_linux: + # Check if we are on 64-bit linux, default to 64 then. + uname_m = os.uname()[4] + if uname_m == 'x86_64': + abi = 'x86_64' + else: + abi = 'ia32' + else: + if is_win64: + abi = 'em64t' + else: + abi = 'ia32' + + if version and not topdir: + try: + topdir = get_intel_compiler_top(version, abi) + except (SCons.Util.RegError, IntelCError): + topdir = None + + if not topdir: + # Normally this is an error, but it might not be if the compiler is + # on $PATH and the user is importing their env. + class ICLTopDirWarning(SCons.Warnings.Warning): + pass + if (is_mac or is_linux) and not env.Detect('icc') or \ + is_windows and not env.Detect('icl'): + + SCons.Warnings.enableWarningClass(ICLTopDirWarning) + SCons.Warnings.warn(ICLTopDirWarning, + "Failed to find Intel compiler for version='%s', abi='%s'"% + (str(version), str(abi))) + else: + # should be cleaned up to say what this other version is + # since in this case we have some other Intel compiler installed + SCons.Warnings.enableWarningClass(ICLTopDirWarning) + SCons.Warnings.warn(ICLTopDirWarning, + "Can't find Intel compiler top dir for version='%s', abi='%s'"% + (str(version), str(abi))) + + if topdir: + if verbose: + print "Intel C compiler: using version %s (%g), abi %s, in '%s'"%\ + (repr(version), linux_ver_normalize(version),abi,topdir) + if is_linux: + # Show the actual compiler version by running the compiler. + os.system('%s/bin/icc --version'%topdir) + if is_mac: + # Show the actual compiler version by running the compiler. + os.system('%s/bin/icc --version'%topdir) + + env['INTEL_C_COMPILER_TOP'] = topdir + if is_linux: + paths={'INCLUDE' : 'include', + 'LIB' : 'lib', + 'PATH' : 'bin', + 'LD_LIBRARY_PATH' : 'lib'} + for p in paths.keys(): + env.PrependENVPath(p, os.path.join(topdir, paths[p])) + if is_mac: + paths={'INCLUDE' : 'include', + 'LIB' : 'lib', + 'PATH' : 'bin', + 'LD_LIBRARY_PATH' : 'lib'} + for p in paths.keys(): + env.PrependENVPath(p, os.path.join(topdir, paths[p])) + if is_windows: + # env key reg valname default subdir of top + paths=(('INCLUDE', 'IncludeDir', 'Include'), + ('LIB' , 'LibDir', 'Lib'), + ('PATH' , 'BinDir', 'Bin')) + # We are supposed to ignore version if topdir is set, so set + # it to the emptry string if it's not already set. + if version is None: + version = '' + # Each path has a registry entry, use that or default to subdir + for p in paths: + try: + path=get_intel_registry_value(p[1], version, abi) + # These paths may have $(ICInstallDir) + # which needs to be substituted with the topdir. + path=path.replace('$(ICInstallDir)', topdir + os.sep) + except IntelCError: + # Couldn't get it from registry: use default subdir of topdir + env.PrependENVPath(p[0], os.path.join(topdir, p[2])) + else: + env.PrependENVPath(p[0], string.split(path, os.pathsep)) + # print "ICL %s: %s, final=%s"%(p[0], path, str(env['ENV'][p[0]])) + + if is_windows: + env['CC'] = 'icl' + env['CXX'] = 'icl' + env['LINK'] = 'xilink' + else: + env['CC'] = 'icc' + env['CXX'] = 'icpc' + # Don't reset LINK here; + # use smart_link which should already be here from link.py. + #env['LINK'] = '$CC' + env['AR'] = 'xiar' + env['LD'] = 'xild' # not used by default + + # This is not the exact (detailed) compiler version, + # just the major version as determined above or specified + # by the user. It is a float like 80 or 90, in normalized form for Linux + # (i.e. even for Linux 9.0 compiler, still returns 90 rather than 9.0) + if version: + env['INTEL_C_COMPILER_VERSION']=linux_ver_normalize(version) + + if is_windows: + # Look for license file dir + # in system environment, registry, and default location. + envlicdir = os.environ.get("INTEL_LICENSE_FILE", '') + K = ('SOFTWARE\Intel\Licenses') + try: + k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K) + reglicdir = SCons.Util.RegQueryValueEx(k, "w_cpp")[0] + except (AttributeError, SCons.Util.RegError): + reglicdir = "" + defaultlicdir = r'C:\Program Files\Common Files\Intel\Licenses' + + licdir = None + for ld in [envlicdir, reglicdir]: + # If the string contains an '@', then assume it's a network + # license (port@system) and good by definition. + if ld and (string.find(ld, '@') != -1 or os.path.exists(ld)): + licdir = ld + break + if not licdir: + licdir = defaultlicdir + if not os.path.exists(licdir): + class ICLLicenseDirWarning(SCons.Warnings.Warning): + pass + SCons.Warnings.enableWarningClass(ICLLicenseDirWarning) + SCons.Warnings.warn(ICLLicenseDirWarning, + "Intel license dir was not found." + " Tried using the INTEL_LICENSE_FILE environment variable (%s), the registry (%s) and the default path (%s)." + " Using the default path as a last resort." + % (envlicdir, reglicdir, defaultlicdir)) + env['ENV']['INTEL_LICENSE_FILE'] = licdir + +def exists(env): + if not (is_mac or is_linux or is_windows): + # can't handle this platform + return 0 + + try: + versions = get_all_compiler_versions() + except (SCons.Util.RegError, IntelCError): + versions = None + detected = versions is not None and len(versions) > 0 + if not detected: + # try env.Detect, maybe that will work + if is_windows: + return env.Detect('icl') + elif is_linux: + return env.Detect('icc') + elif is_mac: + return env.Detect('icc') + return detected + +# end of file + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/intelc.xml b/src/engine/SCons/Tool/intelc.xml new file mode 100644 index 0000000..61fb8d8 --- /dev/null +++ b/src/engine/SCons/Tool/intelc.xml @@ -0,0 +1,33 @@ + + + +Sets construction variables for the Intel C/C++ compiler +(Linux and Windows, version 7 and later). +Calls the &t-gcc; or &t-msvc; +(on Linux and Windows, respectively) +to set underlying variables. + + +CC +CXX +LINK +AR + +INTEL_C_COMPILER_VERSION + + + + + + + +Set by the "intelc" Tool +to the major version number of the Intel C compiler +selected for use. + + diff --git a/src/engine/SCons/Tool/ipkg.py b/src/engine/SCons/Tool/ipkg.py new file mode 100644 index 0000000..b026938 --- /dev/null +++ b/src/engine/SCons/Tool/ipkg.py @@ -0,0 +1,71 @@ +"""SCons.Tool.ipkg + +Tool-specific initialization for ipkg. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +The ipkg tool calls the ipkg-build. Its only argument should be the +packages fake_root. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/ipkg.py 4577 2009/12/27 19:44:43 scons" + +import os +import string + +import SCons.Builder + +def generate(env): + """Add Builders and construction variables for ipkg to an Environment.""" + try: + bld = env['BUILDERS']['Ipkg'] + except KeyError: + bld = SCons.Builder.Builder( action = '$IPKGCOM', + suffix = '$IPKGSUFFIX', + source_scanner = None, + target_scanner = None) + env['BUILDERS']['Ipkg'] = bld + + env['IPKG'] = 'ipkg-build' + env['IPKGCOM'] = '$IPKG $IPKGFLAGS ${SOURCE}' + # TODO(1.5) + #env['IPKGUSER'] = os.popen('id -un').read().strip() + #env['IPKGGROUP'] = os.popen('id -gn').read().strip() + env['IPKGUSER'] = string.strip(os.popen('id -un').read()) + env['IPKGGROUP'] = string.strip(os.popen('id -gn').read()) + env['IPKGFLAGS'] = SCons.Util.CLVar('-o $IPKGUSER -g $IPKGGROUP') + env['IPKGSUFFIX'] = '.ipk' + +def exists(env): + return env.Detect('ipkg-build') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/jar.py b/src/engine/SCons/Tool/jar.py new file mode 100644 index 0000000..1c086c2 --- /dev/null +++ b/src/engine/SCons/Tool/jar.py @@ -0,0 +1,110 @@ +"""SCons.Tool.jar + +Tool-specific initialization for jar. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/jar.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Subst +import SCons.Util + +def jarSources(target, source, env, for_signature): + """Only include sources that are not a manifest file.""" + try: + env['JARCHDIR'] + except KeyError: + jarchdir_set = False + else: + jarchdir_set = True + jarchdir = env.subst('$JARCHDIR', target=target, source=source) + if jarchdir: + jarchdir = env.fs.Dir(jarchdir) + result = [] + for src in source: + contents = src.get_text_contents() + if contents[:16] != "Manifest-Version": + if jarchdir_set: + _chdir = jarchdir + else: + try: + _chdir = src.attributes.java_classdir + except AttributeError: + _chdir = None + if _chdir: + # If we are changing the dir with -C, then sources should + # be relative to that directory. + src = SCons.Subst.Literal(src.get_path(_chdir)) + result.append('-C') + result.append(_chdir) + result.append(src) + return result + +def jarManifest(target, source, env, for_signature): + """Look in sources for a manifest file, if any.""" + for src in source: + contents = src.get_text_contents() + if contents[:16] == "Manifest-Version": + return src + return '' + +def jarFlags(target, source, env, for_signature): + """If we have a manifest, make sure that the 'm' + flag is specified.""" + jarflags = env.subst('$JARFLAGS', target=target, source=source) + for src in source: + contents = src.get_text_contents() + if contents[:16] == "Manifest-Version": + if not 'm' in jarflags: + return jarflags + 'm' + break + return jarflags + +def generate(env): + """Add Builders and construction variables for jar to an Environment.""" + SCons.Tool.CreateJarBuilder(env) + + env['JAR'] = 'jar' + env['JARFLAGS'] = SCons.Util.CLVar('cf') + env['_JARFLAGS'] = jarFlags + env['_JARMANIFEST'] = jarManifest + env['_JARSOURCES'] = jarSources + env['_JARCOM'] = '$JAR $_JARFLAGS $TARGET $_JARMANIFEST $_JARSOURCES' + env['JARCOM'] = "${TEMPFILE('$_JARCOM')}" + env['JARSUFFIX'] = '.jar' + +def exists(env): + return env.Detect('jar') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/jar.xml b/src/engine/SCons/Tool/jar.xml new file mode 100644 index 0000000..d6a986d --- /dev/null +++ b/src/engine/SCons/Tool/jar.xml @@ -0,0 +1,110 @@ + + + +Sets construction variables for the &jar; utility. + + +JAR +JARFLAGS +JARCOM +JARSUFFIX + + +JARCOMSTR + + + + + +Builds a Java archive (.jar) file +from the specified list of sources. +Any directories in the source list +will be searched for .class files). +Any .java files in the source list +will be compiled to .class files +by calling the &b-link-Java; Builder. + +If the &cv-link-JARCHDIR; value is set, the +&jar; +command will change to the specified directory using the + +option. +If &cv-JARCHDIR; is not set explicitly, +&SCons; will use the top of any subdirectory tree +in which Java .class +were built by the &b-link-Java; Builder. + +If the contents any of the source files begin with the string +Manifest-Version, +the file is assumed to be a manifest +and is passed to the +&jar; +command with the + +option set. + + +env.Jar(target = 'foo.jar', source = 'classes') + +env.Jar(target = 'bar.jar', + source = ['bar1.java', 'bar2.java']) + + + + + + +The Java archive tool. + + + + + +The directory to which the Java archive tool should change +(using the + +option). + + + + + +The command line used to call the Java archive tool. + + + + + +The string displayed when the Java archive tool +is called +If this is not set, then &cv-link-JARCOM; (the command line) is displayed. + + +env = Environment(JARCOMSTR = "JARchiving $SOURCES into $TARGET") + + + + + + +General options passed to the Java archive tool. +By default this is set to + +to create the necessary +jar +file. + + + + + +The suffix for Java archives: +.jar +by default. + + diff --git a/src/engine/SCons/Tool/javac.py b/src/engine/SCons/Tool/javac.py new file mode 100644 index 0000000..e51afe2 --- /dev/null +++ b/src/engine/SCons/Tool/javac.py @@ -0,0 +1,234 @@ +"""SCons.Tool.javac + +Tool-specific initialization for javac. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/javac.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string + +import SCons.Action +import SCons.Builder +from SCons.Node.FS import _my_normcase +from SCons.Tool.JavaCommon import parse_java_file +import SCons.Util + +def classname(path): + """Turn a string (path name) into a Java class name.""" + return string.replace(os.path.normpath(path), os.sep, '.') + +def emit_java_classes(target, source, env): + """Create and return lists of source java files + and their corresponding target class files. + """ + java_suffix = env.get('JAVASUFFIX', '.java') + class_suffix = env.get('JAVACLASSSUFFIX', '.class') + + target[0].must_be_same(SCons.Node.FS.Dir) + classdir = target[0] + + s = source[0].rentry().disambiguate() + if isinstance(s, SCons.Node.FS.File): + sourcedir = s.dir.rdir() + elif isinstance(s, SCons.Node.FS.Dir): + sourcedir = s.rdir() + else: + raise SCons.Errors.UserError("Java source must be File or Dir, not '%s'" % s.__class__) + + slist = [] + js = _my_normcase(java_suffix) + find_java = lambda n, js=js, ljs=len(js): _my_normcase(n[-ljs:]) == js + for entry in source: + entry = entry.rentry().disambiguate() + if isinstance(entry, SCons.Node.FS.File): + slist.append(entry) + elif isinstance(entry, SCons.Node.FS.Dir): + result = SCons.Util.OrderedDict() + def visit(arg, dirname, names, fj=find_java, dirnode=entry.rdir()): + java_files = filter(fj, names) + # The on-disk entries come back in arbitrary order. Sort + # them so our target and source lists are determinate. + java_files.sort() + mydir = dirnode.Dir(dirname) + java_paths = map(lambda f, d=mydir: d.File(f), java_files) + for jp in java_paths: + arg[jp] = True + + os.path.walk(entry.rdir().get_abspath(), visit, result) + entry.walk(visit, result) + + slist.extend(result.keys()) + else: + raise SCons.Errors.UserError("Java source must be File or Dir, not '%s'" % entry.__class__) + + version = env.get('JAVAVERSION', '1.4') + full_tlist = [] + for f in slist: + tlist = [] + source_file_based = True + pkg_dir = None + if not f.is_derived(): + pkg_dir, classes = parse_java_file(f.rfile().get_abspath(), version) + if classes: + source_file_based = False + if pkg_dir: + d = target[0].Dir(pkg_dir) + p = pkg_dir + os.sep + else: + d = target[0] + p = '' + for c in classes: + t = d.File(c + class_suffix) + t.attributes.java_classdir = classdir + t.attributes.java_sourcedir = sourcedir + t.attributes.java_classname = classname(p + c) + tlist.append(t) + + if source_file_based: + base = f.name[:-len(java_suffix)] + if pkg_dir: + t = target[0].Dir(pkg_dir).File(base + class_suffix) + else: + t = target[0].File(base + class_suffix) + t.attributes.java_classdir = classdir + t.attributes.java_sourcedir = f.dir + t.attributes.java_classname = classname(base) + tlist.append(t) + + for t in tlist: + t.set_specific_source([f]) + + full_tlist.extend(tlist) + + return full_tlist, slist + +JavaAction = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR') + +JavaBuilder = SCons.Builder.Builder(action = JavaAction, + emitter = emit_java_classes, + target_factory = SCons.Node.FS.Entry, + source_factory = SCons.Node.FS.Entry) + +class pathopt: + """ + Callable object for generating javac-style path options from + a construction variable (e.g. -classpath, -sourcepath). + """ + def __init__(self, opt, var, default=None): + self.opt = opt + self.var = var + self.default = default + + def __call__(self, target, source, env, for_signature): + path = env[self.var] + if path and not SCons.Util.is_List(path): + path = [path] + if self.default: + path = path + [ env[self.default] ] + if path: + return [self.opt, string.join(path, os.pathsep)] + #return self.opt + " " + string.join(path, os.pathsep) + else: + return [] + #return "" + +def Java(env, target, source, *args, **kw): + """ + A pseudo-Builder wrapper around the separate JavaClass{File,Dir} + Builders. + """ + if not SCons.Util.is_List(target): + target = [target] + if not SCons.Util.is_List(source): + source = [source] + + # Pad the target list with repetitions of the last element in the + # list so we have a target for every source element. + target = target + ([target[-1]] * (len(source) - len(target))) + + java_suffix = env.subst('$JAVASUFFIX') + result = [] + + for t, s in zip(target, source): + if isinstance(s, SCons.Node.FS.Base): + if isinstance(s, SCons.Node.FS.File): + b = env.JavaClassFile + else: + b = env.JavaClassDir + else: + if os.path.isfile(s): + b = env.JavaClassFile + elif os.path.isdir(s): + b = env.JavaClassDir + elif s[-len(java_suffix):] == java_suffix: + b = env.JavaClassFile + else: + b = env.JavaClassDir + result.extend(apply(b, (t, s) + args, kw)) + + return result + +def generate(env): + """Add Builders and construction variables for javac to an Environment.""" + java_file = SCons.Tool.CreateJavaFileBuilder(env) + java_class = SCons.Tool.CreateJavaClassFileBuilder(env) + java_class_dir = SCons.Tool.CreateJavaClassDirBuilder(env) + java_class.add_emitter(None, emit_java_classes) + java_class.add_emitter(env.subst('$JAVASUFFIX'), emit_java_classes) + java_class_dir.emitter = emit_java_classes + + env.AddMethod(Java) + + env['JAVAC'] = 'javac' + env['JAVACFLAGS'] = SCons.Util.CLVar('') + env['JAVABOOTCLASSPATH'] = [] + env['JAVACLASSPATH'] = [] + env['JAVASOURCEPATH'] = [] + env['_javapathopt'] = pathopt + env['_JAVABOOTCLASSPATH'] = '${_javapathopt("-bootclasspath", "JAVABOOTCLASSPATH")} ' + env['_JAVACLASSPATH'] = '${_javapathopt("-classpath", "JAVACLASSPATH")} ' + env['_JAVASOURCEPATH'] = '${_javapathopt("-sourcepath", "JAVASOURCEPATH", "_JAVASOURCEPATHDEFAULT")} ' + env['_JAVASOURCEPATHDEFAULT'] = '${TARGET.attributes.java_sourcedir}' + env['_JAVACCOM'] = '$JAVAC $JAVACFLAGS $_JAVABOOTCLASSPATH $_JAVACLASSPATH -d ${TARGET.attributes.java_classdir} $_JAVASOURCEPATH $SOURCES' + env['JAVACCOM'] = "${TEMPFILE('$_JAVACCOM')}" + env['JAVACLASSSUFFIX'] = '.class' + env['JAVASUFFIX'] = '.java' + +def exists(env): + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/javac.xml b/src/engine/SCons/Tool/javac.xml new file mode 100644 index 0000000..a55e514 --- /dev/null +++ b/src/engine/SCons/Tool/javac.xml @@ -0,0 +1,224 @@ + + + +Sets construction variables for the &javac; compiler. + + +JAVAC +JAVACFLAGS +JAVACCOM +JAVACLASSSUFFIX +JAVASUFFIX +JAVABOOTCLASSPATH +JAVACLASSPATH +JAVASOURCEPATH + + +JAVACCOMSTR + + + + + +Builds one or more Java class files. +The sources may be any combination of explicit +.java files, +or directory trees which will be scanned +for .java files. + +SCons will parse each source .java file +to find the classes +(including inner classes) +defined within that file, +and from that figure out the +target .class files that will be created. +The class files will be placed underneath +the specified target directory. + +SCons will also search each Java file +for the Java package name, +which it assumes can be found on a line +beginning with the string +package +in the first column; +the resulting .class files +will be placed in a directory reflecting +the specified package name. +For example, +the file +Foo.java +defining a single public +Foo +class and +containing a package name of +sub.dir +will generate a corresponding +sub/dir/Foo.class +class file. + +Examples: + + +env.Java(target = 'classes', source = 'src') +env.Java(target = 'classes', source = ['src1', 'src2']) +env.Java(target = 'classes', source = ['File1.java', 'File2.java']) + + +Java source files can use the native encoding for the underlying OS. +Since SCons compiles in simple ASCII mode by default, +the compiler will generate warnings about unmappable characters, +which may lead to errors as the file is processed further. +In this case, the user must specify the LANG +environment variable to tell the compiler what encoding is used. +For portibility, it's best if the encoding is hard-coded +so that the compile will work if it is done on a system +with a different encoding. + + +env = Environment() +env['ENV']['LANG'] = 'en_GB.UTF-8' + + + + + + +Specifies the list of directories that +will be added to the +&javac; command line +via the option. +The individual directory names will be +separated by the operating system's path separate character +(: on UNIX/Linux/POSIX, +; on Windows). + + + + + +The Java compiler. + + + + + +The command line used to compile a directory tree containing +Java source files to +corresponding Java class files. +Any options specified in the &cv-link-JAVACFLAGS; construction variable +are included on this command line. + + + + + +The string displayed when compiling +a directory tree of Java source files to +corresponding Java class files. +If this is not set, then &cv-link-JAVACCOM; (the command line) is displayed. + + +env = Environment(JAVACCOMSTR = "Compiling class files $TARGETS from $SOURCES") + + + + + + +General options that are passed to the Java compiler. + + + + + +The directory in which Java class files may be found. +This is stripped from the beginning of any Java .class +file names supplied to the +JavaH +builder. + + + + + +Specifies the list of directories that +will be searched for Java +.class file. +The directories in this list will be added to the +&javac; and &javah; command lines +via the option. +The individual directory names will be +separated by the operating system's path separate character +(: on UNIX/Linux/POSIX, +; on Windows). + +Note that this currently just adds the specified +directory via the option. +&SCons; does not currently search the +&cv-JAVACLASSPATH; directories for dependency +.class files. + + + + + +The suffix for Java class files; +.class +by default. + + + + + +Specifies the list of directories that +will be searched for input +.java file. +The directories in this list will be added to the +&javac; command line +via the option. +The individual directory names will be +separated by the operating system's path separate character +(: on UNIX/Linux/POSIX, +; on Windows). + +Note that this currently just adds the specified +directory via the option. +&SCons; does not currently search the +&cv-JAVASOURCEPATH; directories for dependency +.java files. + + + + + +The suffix for Java files; +.java +by default. + + + + + +Specifies the Java version being used by the &b-Java; builder. +This is not currently used to select one +version of the Java compiler vs. another. +Instead, you should set this to specify the version of Java +supported by your &javac; compiler. +The default is 1.4. + +This is sometimes necessary because +Java 1.5 changed the file names that are created +for nested anonymous inner classes, +which can cause a mismatch with the files +that &SCons; expects will be generated by the &javac; compiler. +Setting &cv-JAVAVERSION; to 1.5 +(or 1.6, as appropriate) +can make &SCons; realize that a Java 1.5 or 1.6 +build is actually up to date. + + diff --git a/src/engine/SCons/Tool/javah.py b/src/engine/SCons/Tool/javah.py new file mode 100644 index 0000000..0a54005 --- /dev/null +++ b/src/engine/SCons/Tool/javah.py @@ -0,0 +1,138 @@ +"""SCons.Tool.javah + +Tool-specific initialization for javah. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/javah.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string + +import SCons.Action +import SCons.Builder +import SCons.Node.FS +import SCons.Tool.javac +import SCons.Util + +def emit_java_headers(target, source, env): + """Create and return lists of Java stub header files that will + be created from a set of class files. + """ + class_suffix = env.get('JAVACLASSSUFFIX', '.class') + classdir = env.get('JAVACLASSDIR') + + if not classdir: + try: + s = source[0] + except IndexError: + classdir = '.' + else: + try: + classdir = s.attributes.java_classdir + except AttributeError: + classdir = '.' + classdir = env.Dir(classdir).rdir() + + if str(classdir) == '.': + c_ = None + else: + c_ = str(classdir) + os.sep + + slist = [] + for src in source: + try: + classname = src.attributes.java_classname + except AttributeError: + classname = str(src) + if c_ and classname[:len(c_)] == c_: + classname = classname[len(c_):] + if class_suffix and classname[-len(class_suffix):] == class_suffix: + classname = classname[:-len(class_suffix)] + classname = SCons.Tool.javac.classname(classname) + s = src.rfile() + s.attributes.java_classname = classname + slist.append(s) + + s = source[0].rfile() + if not hasattr(s.attributes, 'java_classdir'): + s.attributes.java_classdir = classdir + + if target[0].__class__ is SCons.Node.FS.File: + tlist = target + else: + if not isinstance(target[0], SCons.Node.FS.Dir): + target[0].__class__ = SCons.Node.FS.Dir + target[0]._morph() + tlist = [] + for s in source: + fname = string.replace(s.attributes.java_classname, '.', '_') + '.h' + t = target[0].File(fname) + t.attributes.java_lookupdir = target[0] + tlist.append(t) + + return tlist, source + +def JavaHOutFlagGenerator(target, source, env, for_signature): + try: + t = target[0] + except (AttributeError, IndexError, TypeError): + t = target + try: + return '-d ' + str(t.attributes.java_lookupdir) + except AttributeError: + return '-o ' + str(t) + +def getJavaHClassPath(env,target, source, for_signature): + path = "${SOURCE.attributes.java_classdir}" + if env.has_key('JAVACLASSPATH') and env['JAVACLASSPATH']: + path = SCons.Util.AppendPath(path, env['JAVACLASSPATH']) + return "-classpath %s" % (path) + +def generate(env): + """Add Builders and construction variables for javah to an Environment.""" + java_javah = SCons.Tool.CreateJavaHBuilder(env) + java_javah.emitter = emit_java_headers + + env['_JAVAHOUTFLAG'] = JavaHOutFlagGenerator + env['JAVAH'] = 'javah' + env['JAVAHFLAGS'] = SCons.Util.CLVar('') + env['_JAVAHCLASSPATH'] = getJavaHClassPath + env['JAVAHCOM'] = '$JAVAH $JAVAHFLAGS $_JAVAHOUTFLAG $_JAVAHCLASSPATH ${SOURCES.attributes.java_classname}' + env['JAVACLASSSUFFIX'] = '.class' + +def exists(env): + return env.Detect('javah') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/javah.xml b/src/engine/SCons/Tool/javah.xml new file mode 100644 index 0000000..8a31662 --- /dev/null +++ b/src/engine/SCons/Tool/javah.xml @@ -0,0 +1,100 @@ + + + +Sets construction variables for the &javah; tool. + + +JAVAH +JAVAHFLAGS +JAVAHCOM +JAVACLASSSUFFIX + + +JAVAHCOMSTR +JAVACLASSPATH + + + + + +Builds C header and source files for +implementing Java native methods. +The target can be either a directory +in which the header files will be written, +or a header file name which +will contain all of the definitions. +The source can be the names of .class files, +the names of .java files +to be compiled into .class files +by calling the &b-link-Java; builder method, +or the objects returned from the +&b-Java; +builder method. + +If the construction variable +&cv-link-JAVACLASSDIR; +is set, either in the environment +or in the call to the +&b-JavaH; +builder method itself, +then the value of the variable +will be stripped from the +beginning of any .class file names. + +Examples: + + +# builds java_native.h +classes = env.Java(target = 'classdir', source = 'src') +env.JavaH(target = 'java_native.h', source = classes) + +# builds include/package_foo.h and include/package_bar.h +env.JavaH(target = 'include', + source = ['package/foo.class', 'package/bar.class']) + +# builds export/foo.h and export/bar.h +env.JavaH(target = 'export', + source = ['classes/foo.class', 'classes/bar.class'], + JAVACLASSDIR = 'classes') + + + + + + +The Java generator for C header and stub files. + + + + + +The command line used to generate C header and stub files +from Java classes. +Any options specified in the &cv-link-JAVAHFLAGS; construction variable +are included on this command line. + + + + + +The string displayed when C header and stub files +are generated from Java classes. +If this is not set, then &cv-link-JAVAHCOM; (the command line) is displayed. + + +env = Environment(JAVAHCOMSTR = "Generating header/stub file(s) $TARGETS from $SOURCES") + + + + + + +General options passed to the C header and stub file generator +for Java classes. + + diff --git a/src/engine/SCons/Tool/latex.py b/src/engine/SCons/Tool/latex.py new file mode 100644 index 0000000..d5b75ae --- /dev/null +++ b/src/engine/SCons/Tool/latex.py @@ -0,0 +1,79 @@ +"""SCons.Tool.latex + +Tool-specific initialization for LaTeX. +Generates .dvi files from .latex or .ltx files + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/latex.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Defaults +import SCons.Scanner.LaTeX +import SCons.Util +import SCons.Tool +import SCons.Tool.tex + +def LaTeXAuxFunction(target = None, source= None, env=None): + result = SCons.Tool.tex.InternalLaTeXAuxAction( SCons.Tool.tex.LaTeXAction, target, source, env ) + if result != 0: + print env['LATEX']," returned an error, check the log file" + return result + +LaTeXAuxAction = SCons.Action.Action(LaTeXAuxFunction, + strfunction=SCons.Tool.tex.TeXLaTeXStrFunction) + +def generate(env): + """Add Builders and construction variables for LaTeX to an Environment.""" + + env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes) + + import dvi + dvi.generate(env) + + import pdf + pdf.generate(env) + + bld = env['BUILDERS']['DVI'] + bld.add_action('.ltx', LaTeXAuxAction) + bld.add_action('.latex', LaTeXAuxAction) + bld.add_emitter('.ltx', SCons.Tool.tex.tex_eps_emitter) + bld.add_emitter('.latex', SCons.Tool.tex.tex_eps_emitter) + + SCons.Tool.tex.generate_common(env) + +def exists(env): + return env.Detect('latex') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/latex.xml b/src/engine/SCons/Tool/latex.xml new file mode 100644 index 0000000..aac9866 --- /dev/null +++ b/src/engine/SCons/Tool/latex.xml @@ -0,0 +1,71 @@ + + + +Sets construction variables for the &latex; utility. + + +LATEX +LATEXFLAGS +LATEXCOM + + +LATEXCOMSTR + + + + + +The LaTeX structured formatter and typesetter. + + + + + +The command line used to call the LaTeX structured formatter and typesetter. + + + + + +The string displayed when calling +the LaTeX structured formatter and typesetter. +If this is not set, then &cv-link-LATEXCOM; (the command line) is displayed. + + +env = Environment(LATEXCOMSTR = "Building $TARGET from LaTeX input $SOURCES") + + + + + + +General options passed to the LaTeX structured formatter and typesetter. + + + + + +The maximum number of times that LaTeX +will be re-run if the +.log +generated by the &cv-link-LATEXCOM; command +indicates that there are undefined references. +The default is to try to resolve undefined references +by re-running LaTeX up to three times. + + + + + +List of directories that the LaTeX programm will search +for include directories. +The LaTeX implicit dependency scanner will search these +directories for \include and \import files. + + + diff --git a/src/engine/SCons/Tool/lex.py b/src/engine/SCons/Tool/lex.py new file mode 100644 index 0000000..57d7cca --- /dev/null +++ b/src/engine/SCons/Tool/lex.py @@ -0,0 +1,99 @@ +"""SCons.Tool.lex + +Tool-specific initialization for lex. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/lex.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import string + +import SCons.Action +import SCons.Tool +import SCons.Util + +LexAction = SCons.Action.Action("$LEXCOM", "$LEXCOMSTR") + +def lexEmitter(target, source, env): + sourceBase, sourceExt = os.path.splitext(SCons.Util.to_String(source[0])) + + if sourceExt == ".lm": # If using Objective-C + target = [sourceBase + ".m"] # the extension is ".m". + + # This emitter essentially tries to add to the target all extra + # files generated by flex. + + # Different options that are used to trigger the creation of extra files. + fileGenOptions = ["--header-file=", "--tables-file="] + + lexflags = env.subst("$LEXFLAGS", target=target, source=source) + for option in SCons.Util.CLVar(lexflags): + for fileGenOption in fileGenOptions: + l = len(fileGenOption) + if option[:l] == fileGenOption: + # A file generating option is present, so add the + # file name to the target list. + fileName = string.strip(option[l:]) + target.append(fileName) + return (target, source) + +def generate(env): + """Add Builders and construction variables for lex to an Environment.""" + c_file, cxx_file = SCons.Tool.createCFileBuilders(env) + + # C + c_file.add_action(".l", LexAction) + c_file.add_emitter(".l", lexEmitter) + + c_file.add_action(".lex", LexAction) + c_file.add_emitter(".lex", lexEmitter) + + # Objective-C + cxx_file.add_action(".lm", LexAction) + cxx_file.add_emitter(".lm", lexEmitter) + + # C++ + cxx_file.add_action(".ll", LexAction) + cxx_file.add_emitter(".ll", lexEmitter) + + env["LEX"] = env.Detect("flex") or "lex" + env["LEXFLAGS"] = SCons.Util.CLVar("") + env["LEXCOM"] = "$LEX $LEXFLAGS -t $SOURCES > $TARGET" + +def exists(env): + return env.Detect(["flex", "lex"]) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/lex.xml b/src/engine/SCons/Tool/lex.xml new file mode 100644 index 0000000..8b3fcf8 --- /dev/null +++ b/src/engine/SCons/Tool/lex.xml @@ -0,0 +1,50 @@ + + + +Sets construction variables for the &lex; lexical analyser. + + +LEX +LEXFLAGS +LEXCOM + + +LEXCOMSTR + + + + + +The lexical analyzer generator. + + + + + +The command line used to call the lexical analyzer generator +to generate a source file. + + + + + +The string displayed when generating a source file +using the lexical analyzer generator. +If this is not set, then &cv-link-LEXCOM; (the command line) is displayed. + + +env = Environment(LEXCOMSTR = "Lex'ing $TARGET from $SOURCES") + + + + + + +General options passed to the lexical analyzer generator. + + diff --git a/src/engine/SCons/Tool/link.py b/src/engine/SCons/Tool/link.py new file mode 100644 index 0000000..09938d6 --- /dev/null +++ b/src/engine/SCons/Tool/link.py @@ -0,0 +1,121 @@ +"""SCons.Tool.link + +Tool-specific initialization for the generic Posix linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/link.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util +import SCons.Warnings + +from SCons.Tool.FortranCommon import isfortran + +cplusplus = __import__('c++', globals(), locals(), []) + +issued_mixed_link_warning = False + +def smart_link(source, target, env, for_signature): + has_cplusplus = cplusplus.iscplusplus(source) + has_fortran = isfortran(env, source) + if has_cplusplus and has_fortran: + global issued_mixed_link_warning + if not issued_mixed_link_warning: + msg = "Using $CXX to link Fortran and C++ code together.\n\t" + \ + "This may generate a buggy executable if the '%s'\n\t" + \ + "compiler does not know how to deal with Fortran runtimes." + SCons.Warnings.warn(SCons.Warnings.FortranCxxMixWarning, + msg % env.subst('$CXX')) + issued_mixed_link_warning = True + return '$CXX' + elif has_fortran: + return '$FORTRAN' + elif has_cplusplus: + return '$CXX' + return '$CC' + +def shlib_emitter(target, source, env): + for tgt in target: + tgt.attributes.shared = 1 + return (target, source) + +def generate(env): + """Add Builders and construction variables for gnulink to an Environment.""" + SCons.Tool.createSharedLibBuilder(env) + SCons.Tool.createProgBuilder(env) + + env['SHLINK'] = '$LINK' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared') + env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + # don't set up the emitter, cause AppendUnique will generate a list + # starting with None :-( + env.Append(SHLIBEMITTER = [shlib_emitter]) + env['SMARTLINK'] = smart_link + env['LINK'] = "$SMARTLINK" + env['LINKFLAGS'] = SCons.Util.CLVar('') + env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + env['LIBDIRPREFIX']='-L' + env['LIBDIRSUFFIX']='' + env['_LIBFLAGS']='${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}' + env['LIBLINKPREFIX']='-l' + env['LIBLINKSUFFIX']='' + + if env['PLATFORM'] == 'hpux': + env['SHLIBSUFFIX'] = '.sl' + elif env['PLATFORM'] == 'aix': + env['SHLIBSUFFIX'] = '.a' + + # For most platforms, a loadable module is the same as a shared + # library. Platforms which are different can override these, but + # setting them the same means that LoadableModule works everywhere. + SCons.Tool.createLoadableModuleBuilder(env) + env['LDMODULE'] = '$SHLINK' + # don't set up the emitter, cause AppendUnique will generate a list + # starting with None :-( + env.Append(LDMODULEEMITTER='$SHLIBEMITTER') + env['LDMODULEPREFIX'] = '$SHLIBPREFIX' + env['LDMODULESUFFIX'] = '$SHLIBSUFFIX' + env['LDMODULEFLAGS'] = '$SHLINKFLAGS' + env['LDMODULECOM'] = '$LDMODULE -o $TARGET $LDMODULEFLAGS $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + + + +def exists(env): + # This module isn't really a Tool on its own, it's common logic for + # other linkers. + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/link.xml b/src/engine/SCons/Tool/link.xml new file mode 100644 index 0000000..b9a6a11 --- /dev/null +++ b/src/engine/SCons/Tool/link.xml @@ -0,0 +1,175 @@ + + + +Sets construction variables for generic POSIX linkers. + + +SHLINK +SHLINKFLAGS +SHLINKCOM +LINK +LINKFLAGS +LINKCOM +LIBDIRPREFIX +LIBDIRSUFFIX +LIBLINKPREFIX +LIBLINKSUFFIX +SHLIBSUFFIX +LDMODULE +LDMODULEPREFIX +LDMODULESUFFIX +LDMODULEFLAGS +LDMODULECOM + + +SHLINKCOMSTR +LINKCOMSTR +LDMODULECOMSTR + + + + + +The linker for building loadable modules. +By default, this is the same as &cv-link-SHLINK;. + + + + + +The command line for building loadable modules. +On Mac OS X, this uses the &cv-link-LDMODULE;, +&cv-link-LDMODULEFLAGS; and +&cv-link-FRAMEWORKSFLAGS; variables. +On other systems, this is the same as &cv-link-SHLINK;. + + + + + +The string displayed when building loadable modules. +If this is not set, then &cv-link-LDMODULECOM; (the command line) is displayed. + + + + + +General user options passed to the linker for building loadable modules. + + + + + +The prefix used for loadable module file names. +On Mac OS X, this is null; +on other systems, this is +the same as &cv-link-SHLIBPREFIX;. + + + + + +The suffix used for loadable module file names. +On Mac OS X, this is null; +on other systems, this is +the same as $SHLIBSUFFIX. + + + + + +The linker. + + + + + +The command line used to link object files into an executable. + + + + + +The string displayed when object files +are linked into an executable. +If this is not set, then &cv-link-LINKCOM; (the command line) is displayed. + + +env = Environment(LINKCOMSTR = "Linking $TARGET") + + + + + + +General user options passed to the linker. +Note that this variable should +not +contain + +(or similar) options for linking with the libraries listed in &cv-link-LIBS;, +nor + +(or similar) library search path options +that scons generates automatically from &cv-link-LIBPATH;. +See +&cv-link-_LIBFLAGS; +above, +for the variable that expands to library-link options, +and +&cv-link-_LIBDIRFLAGS; +above, +for the variable that expands to library search path options. + + + + + +The linker for programs that use shared libraries. + + + + + +The command line used to link programs using shared libaries. + + + + + +The string displayed when programs using shared libraries are linked. +If this is not set, then &cv-link-SHLINKCOM; (the command line) is displayed. + + +env = Environment(SHLINKCOMSTR = "Linking shared $TARGET") + + + + + + +General user options passed to the linker for programs using shared libraries. +Note that this variable should +not +contain + +(or similar) options for linking with the libraries listed in &cv-link-LIBS;, +nor + +(or similar) include search path options +that scons generates automatically from &cv-link-LIBPATH;. +See +&cv-link-_LIBFLAGS; +above, +for the variable that expands to library-link options, +and +&cv-link-_LIBDIRFLAGS; +above, +for the variable that expands to library search path options. + + diff --git a/src/engine/SCons/Tool/linkloc.py b/src/engine/SCons/Tool/linkloc.py new file mode 100644 index 0000000..0c50dc8 --- /dev/null +++ b/src/engine/SCons/Tool/linkloc.py @@ -0,0 +1,112 @@ +"""SCons.Tool.linkloc + +Tool specification for the LinkLoc linker for the Phar Lap ETS embedded +operating system. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/linkloc.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import re + +import SCons.Action +import SCons.Defaults +import SCons.Errors +import SCons.Tool +import SCons.Util + +from SCons.Tool.MSCommon import msvs_exists, merge_default_version +from SCons.Tool.PharLapCommon import addPharLapPaths + +_re_linker_command = re.compile(r'(\s)@\s*([^\s]+)') + +def repl_linker_command(m): + # Replaces any linker command file directives (e.g. "@foo.lnk") with + # the actual contents of the file. + try: + f=open(m.group(2), "r") + return m.group(1) + f.read() + except IOError: + # the linker should return an error if it can't + # find the linker command file so we will remain quiet. + # However, we will replace the @ with a # so we will not continue + # to find it with recursive substitution + return m.group(1) + '#' + m.group(2) + +class LinklocGenerator: + def __init__(self, cmdline): + self.cmdline = cmdline + + def __call__(self, env, target, source, for_signature): + if for_signature: + # Expand the contents of any linker command files recursively + subs = 1 + strsub = env.subst(self.cmdline, target=target, source=source) + while subs: + strsub, subs = _re_linker_command.subn(repl_linker_command, strsub) + return strsub + else: + return "${TEMPFILE('" + self.cmdline + "')}" + +def generate(env): + """Add Builders and construction variables for ar to an Environment.""" + SCons.Tool.createSharedLibBuilder(env) + SCons.Tool.createProgBuilder(env) + + env['SUBST_CMD_FILE'] = LinklocGenerator + env['SHLINK'] = '$LINK' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS') + env['SHLINKCOM'] = '${SUBST_CMD_FILE("$SHLINK $SHLINKFLAGS $_LIBDIRFLAGS $_LIBFLAGS -dll $TARGET $SOURCES")}' + env['SHLIBEMITTER']= None + env['LINK'] = "linkloc" + env['LINKFLAGS'] = SCons.Util.CLVar('') + env['LINKCOM'] = '${SUBST_CMD_FILE("$LINK $LINKFLAGS $_LIBDIRFLAGS $_LIBFLAGS -exe $TARGET $SOURCES")}' + env['LIBDIRPREFIX']='-libpath ' + env['LIBDIRSUFFIX']='' + env['LIBLINKPREFIX']='-lib ' + env['LIBLINKSUFFIX']='$LIBSUFFIX' + + # Set-up ms tools paths for default version + merge_default_version(env) + + addPharLapPaths(env) + +def exists(env): + if msvs_exists(): + return env.Detect('linkloc') + else: + return 0 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/linkloc.xml b/src/engine/SCons/Tool/linkloc.xml new file mode 100644 index 0000000..bcec6a9 --- /dev/null +++ b/src/engine/SCons/Tool/linkloc.xml @@ -0,0 +1,31 @@ + + + +Sets construction variables for the +LinkLoc +linker for the Phar Lap ETS embedded operating system. + + + +SHLINK +SHLINKFLAGS +SHLINKCOM + +LINK +LINKFLAGS +LINKCOM +LIBDIRPREFIX +LIBDIRSUFFIX +LIBLINKPREFIX +LIBLINKSUFFIX + + +SHLINKCOMSTR +LINKCOMSTR + + diff --git a/src/engine/SCons/Tool/m4.py b/src/engine/SCons/Tool/m4.py new file mode 100644 index 0000000..f3f1d28 --- /dev/null +++ b/src/engine/SCons/Tool/m4.py @@ -0,0 +1,63 @@ +"""SCons.Tool.m4 + +Tool-specific initialization for m4. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/m4.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Util + +def generate(env): + """Add Builders and construction variables for m4 to an Environment.""" + M4Action = SCons.Action.Action('$M4COM', '$M4COMSTR') + bld = SCons.Builder.Builder(action = M4Action, src_suffix = '.m4') + + env['BUILDERS']['M4'] = bld + + # .m4 files might include other files, and it would be pretty hard + # to write a scanner for it, so let's just cd to the dir of the m4 + # file and run from there. + # The src_suffix setup is like so: file.c.m4 -> file.c, + # file.cpp.m4 -> file.cpp etc. + env['M4'] = 'm4' + env['M4FLAGS'] = SCons.Util.CLVar('-E') + env['M4COM'] = 'cd ${SOURCE.rsrcdir} && $M4 $M4FLAGS < ${SOURCE.file} > ${TARGET.abspath}' + +def exists(env): + return env.Detect('m4') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/m4.xml b/src/engine/SCons/Tool/m4.xml new file mode 100644 index 0000000..dbca146 --- /dev/null +++ b/src/engine/SCons/Tool/m4.xml @@ -0,0 +1,61 @@ + + + +Sets construction variables for the &m4; macro processor. + + +M4 +M4FLAGS +M4COM + + +M4COMSTR + + + + + +Builds an output file from an M4 input file. +This uses a default &cv-link-M4FLAGS; value of +, +which considers all warnings to be fatal +and stops on the first warning +when using the GNU version of m4. +Example: + + +env.M4(target = 'foo.c', source = 'foo.c.m4') + + + + + + +The M4 macro preprocessor. + + + + + +The command line used to pass files through the M4 macro preprocessor. + + + + + +The string displayed when +a file is passed through the M4 macro preprocessor. +If this is not set, then &cv-link-M4COM; (the command line) is displayed. + + + + + +General options passed to the M4 macro preprocessor. + + diff --git a/src/engine/SCons/Tool/masm.py b/src/engine/SCons/Tool/masm.py new file mode 100644 index 0000000..01db60a --- /dev/null +++ b/src/engine/SCons/Tool/masm.py @@ -0,0 +1,77 @@ +"""SCons.Tool.masm + +Tool-specific initialization for the Microsoft Assembler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/masm.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +ASSuffixes = ['.s', '.asm', '.ASM'] +ASPPSuffixes = ['.spp', '.SPP', '.sx'] +if SCons.Util.case_sensitive_suffixes('.s', '.S'): + ASPPSuffixes.extend(['.S']) +else: + ASSuffixes.extend(['.S']) + +def generate(env): + """Add Builders and construction variables for masm to an Environment.""" + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in ASSuffixes: + static_obj.add_action(suffix, SCons.Defaults.ASAction) + shared_obj.add_action(suffix, SCons.Defaults.ASAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + for suffix in ASPPSuffixes: + static_obj.add_action(suffix, SCons.Defaults.ASPPAction) + shared_obj.add_action(suffix, SCons.Defaults.ASPPAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter) + + env['AS'] = 'ml' + env['ASFLAGS'] = SCons.Util.CLVar('/nologo') + env['ASPPFLAGS'] = '$ASFLAGS' + env['ASCOM'] = '$AS $ASFLAGS /c /Fo$TARGET $SOURCES' + env['ASPPCOM'] = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c /Fo$TARGET $SOURCES' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + +def exists(env): + return env.Detect('ml') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/masm.xml b/src/engine/SCons/Tool/masm.xml new file mode 100644 index 0000000..3cc0ff2 --- /dev/null +++ b/src/engine/SCons/Tool/masm.xml @@ -0,0 +1,26 @@ + + + +Sets construction variables for the Microsoft assembler. + + +AS +ASFLAGS +ASPPFLAGS +ASCOM +ASPPCOM + + + +ASCOMSTR +ASPPCOMSTR +CPPFLAGS +_CPPDEFFLAGS +_CPPINCFLAGS + + diff --git a/src/engine/SCons/Tool/midl.py b/src/engine/SCons/Tool/midl.py new file mode 100644 index 0000000..9a20677 --- /dev/null +++ b/src/engine/SCons/Tool/midl.py @@ -0,0 +1,90 @@ +"""SCons.Tool.midl + +Tool-specific initialization for midl (Microsoft IDL compiler). + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/midl.py 4577 2009/12/27 19:44:43 scons" + +import string + +import SCons.Action +import SCons.Builder +import SCons.Defaults +import SCons.Scanner.IDL +import SCons.Util + +from MSCommon import msvc_exists + +def midl_emitter(target, source, env): + """Produces a list of outputs from the MIDL compiler""" + base, ext = SCons.Util.splitext(str(target[0])) + tlb = target[0] + incl = base + '.h' + interface = base + '_i.c' + t = [tlb, incl, interface] + + midlcom = env['MIDLCOM'] + + if string.find(midlcom, '/proxy') != -1: + proxy = base + '_p.c' + t.append(proxy) + if string.find(midlcom, '/dlldata') != -1: + dlldata = base + '_data.c' + t.append(dlldata) + + return (t,source) + +idl_scanner = SCons.Scanner.IDL.IDLScan() + +midl_action = SCons.Action.Action('$MIDLCOM', '$MIDLCOMSTR') + +midl_builder = SCons.Builder.Builder(action = midl_action, + src_suffix = '.idl', + suffix='.tlb', + emitter = midl_emitter, + source_scanner = idl_scanner) + +def generate(env): + """Add Builders and construction variables for midl to an Environment.""" + + env['MIDL'] = 'MIDL.EXE' + env['MIDLFLAGS'] = SCons.Util.CLVar('/nologo') + env['MIDLCOM'] = '$MIDL $MIDLFLAGS /tlb ${TARGETS[0]} /h ${TARGETS[1]} /iid ${TARGETS[2]} /proxy ${TARGETS[3]} /dlldata ${TARGETS[4]} $SOURCE 2> NUL' + env['BUILDERS']['TypeLibrary'] = midl_builder + +def exists(env): + return msvc_exists() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/midl.xml b/src/engine/SCons/Tool/midl.xml new file mode 100644 index 0000000..640207d --- /dev/null +++ b/src/engine/SCons/Tool/midl.xml @@ -0,0 +1,68 @@ + + + +Sets construction variables for the Microsoft IDL compiler. + + +MIDL +MIDLFLAGS +MIDLCOM + + +MIDLCOMSTR + + + + + +Builds a Windows type library (.tlb) +file from an input IDL file (.idl). +In addition, it will build the associated inteface stub and +proxy source files, +naming them according to the base name of the .idl file. +For example, + + +env.TypeLibrary(source="foo.idl") + + +Will create foo.tlb, +foo.h, +foo_i.c, +foo_p.c +and +foo_data.c +files. + + + + + +The Microsoft IDL compiler. + + + + + +The command line used to pass files to the Microsoft IDL compiler. + + + + + +The string displayed when +the Microsoft IDL copmiler is called. +If this is not set, then &cv-link-MIDLCOM; (the command line) is displayed. + + + + + +General options passed to the Microsoft IDL compiler. + + diff --git a/src/engine/SCons/Tool/mingw.py b/src/engine/SCons/Tool/mingw.py new file mode 100644 index 0000000..61980e3 --- /dev/null +++ b/src/engine/SCons/Tool/mingw.py @@ -0,0 +1,159 @@ +"""SCons.Tool.gcc + +Tool-specific initialization for MinGW (http://www.mingw.org/) + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/mingw.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string + +import SCons.Action +import SCons.Builder +import SCons.Defaults +import SCons.Tool +import SCons.Util + +# This is what we search for to find mingw: +key_program = 'mingw32-gcc' + +def find(env): + # First search in the SCons path and then the OS path: + return env.WhereIs(key_program) or SCons.Util.WhereIs(key_program) + +def shlib_generator(target, source, env, for_signature): + cmd = SCons.Util.CLVar(['$SHLINK', '$SHLINKFLAGS']) + + dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX') + if dll: cmd.extend(['-o', dll]) + + cmd.extend(['$SOURCES', '$_LIBDIRFLAGS', '$_LIBFLAGS']) + + implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX') + if implib: cmd.append('-Wl,--out-implib,'+implib.get_string(for_signature)) + + def_target = env.FindIxes(target, 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX') + insert_def = env.subst("$WINDOWS_INSERT_DEF") + if not insert_def in ['', '0', 0] and def_target: \ + cmd.append('-Wl,--output-def,'+def_target.get_string(for_signature)) + + return [cmd] + +def shlib_emitter(target, source, env): + dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX') + no_import_lib = env.get('no_import_lib', 0) + + if not dll: + raise SCons.Errors.UserError, "A shared library should have exactly one target with the suffix: %s" % env.subst("$SHLIBSUFFIX") + + if not no_import_lib and \ + not env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX'): + + # Append an import library to the list of targets. + target.append(env.ReplaceIxes(dll, + 'SHLIBPREFIX', 'SHLIBSUFFIX', + 'LIBPREFIX', 'LIBSUFFIX')) + + # Append a def file target if there isn't already a def file target + # or a def file source. There is no option to disable def file + # target emitting, because I can't figure out why someone would ever + # want to turn it off. + def_source = env.FindIxes(source, 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX') + def_target = env.FindIxes(target, 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX') + if not def_source and not def_target: + target.append(env.ReplaceIxes(dll, + 'SHLIBPREFIX', 'SHLIBSUFFIX', + 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX')) + + return (target, source) + + +shlib_action = SCons.Action.Action(shlib_generator, generator=1) + +res_action = SCons.Action.Action('$RCCOM', '$RCCOMSTR') + +res_builder = SCons.Builder.Builder(action=res_action, suffix='.o', + source_scanner=SCons.Tool.SourceFileScanner) +SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan) + +def generate(env): + mingw = find(env) + if mingw: + dir = os.path.dirname(mingw) + env.PrependENVPath('PATH', dir ) + + + # Most of mingw is the same as gcc and friends... + gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas', 'm4'] + for tool in gnu_tools: + SCons.Tool.Tool(tool)(env) + + #... but a few things differ: + env['CC'] = 'gcc' + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + env['CXX'] = 'g++' + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS') + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared') + env['SHLINKCOM'] = shlib_action + env['LDMODULECOM'] = shlib_action + env.Append(SHLIBEMITTER = [shlib_emitter]) + env['AS'] = 'as' + + env['WIN32DEFPREFIX'] = '' + env['WIN32DEFSUFFIX'] = '.def' + env['WINDOWSDEFPREFIX'] = '${WIN32DEFPREFIX}' + env['WINDOWSDEFSUFFIX'] = '${WIN32DEFSUFFIX}' + + env['SHOBJSUFFIX'] = '.o' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + + env['RC'] = 'windres' + env['RCFLAGS'] = SCons.Util.CLVar('') + env['RCINCFLAGS'] = '$( ${_concat(RCINCPREFIX, CPPPATH, RCINCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' + env['RCINCPREFIX'] = '--include-dir ' + env['RCINCSUFFIX'] = '' + env['RCCOM'] = '$RC $_CPPDEFFLAGS $RCINCFLAGS ${RCINCPREFIX} ${SOURCE.dir} $RCFLAGS -i $SOURCE -o $TARGET' + env['BUILDERS']['RES'] = res_builder + + # Some setting from the platform also have to be overridden: + env['OBJSUFFIX'] = '.o' + env['LIBPREFIX'] = 'lib' + env['LIBSUFFIX'] = '.a' + +def exists(env): + return find(env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mingw.xml b/src/engine/SCons/Tool/mingw.xml new file mode 100644 index 0000000..5be291c --- /dev/null +++ b/src/engine/SCons/Tool/mingw.xml @@ -0,0 +1,38 @@ + + + +Sets construction variables for MinGW (Minimal Gnu on Windows). + + +CC +SHCCFLAGS +CXX +SHCXXFLAGS +SHLINKFLAGS +SHLINKCOM +LDMODULECOM +AS +WINDOWSDEFPREFIX +WINDOWSDEFSUFFIX +SHOBJSUFFIX + +RC +RCFLAGS +RCINCFLAGS +RCINCPREFIX +RCINCSUFFIX +RCCOM +OBJSUFFIX +LIBPREFIX +LIBSUFFIX + + +SHLINKCOMSTR +RCCOMSTR + + diff --git a/src/engine/SCons/Tool/mslib.py b/src/engine/SCons/Tool/mslib.py new file mode 100644 index 0000000..6945efd --- /dev/null +++ b/src/engine/SCons/Tool/mslib.py @@ -0,0 +1,64 @@ +"""SCons.Tool.mslib + +Tool-specific initialization for lib (MicroSoft library archiver). + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/mslib.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Tool.msvs +import SCons.Tool.msvc +import SCons.Util + +from MSCommon import msvc_exists, msvc_setup_env_once + +def generate(env): + """Add Builders and construction variables for lib to an Environment.""" + SCons.Tool.createStaticLibBuilder(env) + + # Set-up ms tools paths + msvc_setup_env_once(env) + + env['AR'] = 'lib' + env['ARFLAGS'] = SCons.Util.CLVar('/nologo') + env['ARCOM'] = "${TEMPFILE('$AR $ARFLAGS /OUT:$TARGET $SOURCES')}" + env['LIBPREFIX'] = '' + env['LIBSUFFIX'] = '.lib' + +def exists(env): + return msvc_exists() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mslib.xml b/src/engine/SCons/Tool/mslib.xml new file mode 100644 index 0000000..0bd4ccb --- /dev/null +++ b/src/engine/SCons/Tool/mslib.xml @@ -0,0 +1,23 @@ + + + +Sets construction variables for the Microsoft +mslib +library archiver. + + +AR +ARFLAGS +ARCOM +LIBPREFIX +LIBSUFFIX + + +ARCOMSTR + + diff --git a/src/engine/SCons/Tool/mslink.py b/src/engine/SCons/Tool/mslink.py new file mode 100644 index 0000000..4a7aa6c --- /dev/null +++ b/src/engine/SCons/Tool/mslink.py @@ -0,0 +1,266 @@ +"""SCons.Tool.mslink + +Tool-specific initialization for the Microsoft linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/mslink.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import SCons.Action +import SCons.Defaults +import SCons.Errors +import SCons.Platform.win32 +import SCons.Tool +import SCons.Tool.msvc +import SCons.Tool.msvs +import SCons.Util + +from MSCommon import msvc_setup_env_once, msvc_exists + +def pdbGenerator(env, target, source, for_signature): + try: + return ['/PDB:%s' % target[0].attributes.pdb, '/DEBUG'] + except (AttributeError, IndexError): + return None + +def _dllTargets(target, source, env, for_signature, paramtp): + listCmd = [] + dll = env.FindIxes(target, '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp) + if dll: listCmd.append("/out:%s"%dll.get_string(for_signature)) + + implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX') + if implib: listCmd.append("/implib:%s"%implib.get_string(for_signature)) + + return listCmd + +def _dllSources(target, source, env, for_signature, paramtp): + listCmd = [] + + deffile = env.FindIxes(source, "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX") + for src in source: + # Check explicitly for a non-None deffile so that the __cmp__ + # method of the base SCons.Util.Proxy class used for some Node + # proxies doesn't try to use a non-existent __dict__ attribute. + if deffile and src == deffile: + # Treat this source as a .def file. + listCmd.append("/def:%s" % src.get_string(for_signature)) + else: + # Just treat it as a generic source file. + listCmd.append(src) + return listCmd + +def windowsShlinkTargets(target, source, env, for_signature): + return _dllTargets(target, source, env, for_signature, 'SHLIB') + +def windowsShlinkSources(target, source, env, for_signature): + return _dllSources(target, source, env, for_signature, 'SHLIB') + +def _windowsLdmodTargets(target, source, env, for_signature): + """Get targets for loadable modules.""" + return _dllTargets(target, source, env, for_signature, 'LDMODULE') + +def _windowsLdmodSources(target, source, env, for_signature): + """Get sources for loadable modules.""" + return _dllSources(target, source, env, for_signature, 'LDMODULE') + +def _dllEmitter(target, source, env, paramtp): + """Common implementation of dll emitter.""" + SCons.Tool.msvc.validate_vars(env) + + extratargets = [] + extrasources = [] + + dll = env.FindIxes(target, '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp) + no_import_lib = env.get('no_import_lib', 0) + + if not dll: + raise SCons.Errors.UserError, 'A shared library should have exactly one target with the suffix: %s' % env.subst('$%sSUFFIX' % paramtp) + + insert_def = env.subst("$WINDOWS_INSERT_DEF") + if not insert_def in ['', '0', 0] and \ + not env.FindIxes(source, "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"): + + # append a def file to the list of sources + extrasources.append( + env.ReplaceIxes(dll, + '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp, + "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX")) + + version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0')) + if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0): + # MSVC 8 automatically generates .manifest files that must be installed + extratargets.append( + env.ReplaceIxes(dll, + '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp, + "WINDOWSSHLIBMANIFESTPREFIX", "WINDOWSSHLIBMANIFESTSUFFIX")) + + if env.has_key('PDB') and env['PDB']: + pdb = env.arg2nodes('$PDB', target=target, source=source)[0] + extratargets.append(pdb) + target[0].attributes.pdb = pdb + + if not no_import_lib and \ + not env.FindIxes(target, "LIBPREFIX", "LIBSUFFIX"): + # Append an import library to the list of targets. + extratargets.append( + env.ReplaceIxes(dll, + '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp, + "LIBPREFIX", "LIBSUFFIX")) + # and .exp file is created if there are exports from a DLL + extratargets.append( + env.ReplaceIxes(dll, + '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp, + "WINDOWSEXPPREFIX", "WINDOWSEXPSUFFIX")) + + return (target+extratargets, source+extrasources) + +def windowsLibEmitter(target, source, env): + return _dllEmitter(target, source, env, 'SHLIB') + +def ldmodEmitter(target, source, env): + """Emitter for loadable modules. + + Loadable modules are identical to shared libraries on Windows, but building + them is subject to different parameters (LDMODULE*). + """ + return _dllEmitter(target, source, env, 'LDMODULE') + +def prog_emitter(target, source, env): + SCons.Tool.msvc.validate_vars(env) + + extratargets = [] + + exe = env.FindIxes(target, "PROGPREFIX", "PROGSUFFIX") + if not exe: + raise SCons.Errors.UserError, "An executable should have exactly one target with the suffix: %s" % env.subst("$PROGSUFFIX") + + version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0')) + if version_num >= 8.0 and env.get('WINDOWS_INSERT_MANIFEST', 0): + # MSVC 8 automatically generates .manifest files that have to be installed + extratargets.append( + env.ReplaceIxes(exe, + "PROGPREFIX", "PROGSUFFIX", + "WINDOWSPROGMANIFESTPREFIX", "WINDOWSPROGMANIFESTSUFFIX")) + + if env.has_key('PDB') and env['PDB']: + pdb = env.arg2nodes('$PDB', target=target, source=source)[0] + extratargets.append(pdb) + target[0].attributes.pdb = pdb + + return (target+extratargets,source) + +def RegServerFunc(target, source, env): + if env.has_key('register') and env['register']: + ret = regServerAction([target[0]], [source[0]], env) + if ret: + raise SCons.Errors.UserError, "Unable to register %s" % target[0] + else: + print "Registered %s sucessfully" % target[0] + return ret + return 0 + +regServerAction = SCons.Action.Action("$REGSVRCOM", "$REGSVRCOMSTR") +regServerCheck = SCons.Action.Action(RegServerFunc, None) +shlibLinkAction = SCons.Action.Action('${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_SHLINK_SOURCES")}') +compositeShLinkAction = shlibLinkAction + regServerCheck +ldmodLinkAction = SCons.Action.Action('${TEMPFILE("$LDMODULE $LDMODULEFLAGS $_LDMODULE_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_LDMODULE_SOURCES")}') +compositeLdmodAction = ldmodLinkAction + regServerCheck + +def generate(env): + """Add Builders and construction variables for ar to an Environment.""" + SCons.Tool.createSharedLibBuilder(env) + SCons.Tool.createProgBuilder(env) + + env['SHLINK'] = '$LINK' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS /dll') + env['_SHLINK_TARGETS'] = windowsShlinkTargets + env['_SHLINK_SOURCES'] = windowsShlinkSources + env['SHLINKCOM'] = compositeShLinkAction + env.Append(SHLIBEMITTER = [windowsLibEmitter]) + env['LINK'] = 'link' + env['LINKFLAGS'] = SCons.Util.CLVar('/nologo') + env['_PDB'] = pdbGenerator + env['LINKCOM'] = '${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET.windows $_LIBDIRFLAGS $_LIBFLAGS $_PDB $SOURCES.windows")}' + env.Append(PROGEMITTER = [prog_emitter]) + env['LIBDIRPREFIX']='/LIBPATH:' + env['LIBDIRSUFFIX']='' + env['LIBLINKPREFIX']='' + env['LIBLINKSUFFIX']='$LIBSUFFIX' + + env['WIN32DEFPREFIX'] = '' + env['WIN32DEFSUFFIX'] = '.def' + env['WIN32_INSERT_DEF'] = 0 + env['WINDOWSDEFPREFIX'] = '${WIN32DEFPREFIX}' + env['WINDOWSDEFSUFFIX'] = '${WIN32DEFSUFFIX}' + env['WINDOWS_INSERT_DEF'] = '${WIN32_INSERT_DEF}' + + env['WIN32EXPPREFIX'] = '' + env['WIN32EXPSUFFIX'] = '.exp' + env['WINDOWSEXPPREFIX'] = '${WIN32EXPPREFIX}' + env['WINDOWSEXPSUFFIX'] = '${WIN32EXPSUFFIX}' + + env['WINDOWSSHLIBMANIFESTPREFIX'] = '' + env['WINDOWSSHLIBMANIFESTSUFFIX'] = '${SHLIBSUFFIX}.manifest' + env['WINDOWSPROGMANIFESTPREFIX'] = '' + env['WINDOWSPROGMANIFESTSUFFIX'] = '${PROGSUFFIX}.manifest' + + env['REGSVRACTION'] = regServerCheck + env['REGSVR'] = os.path.join(SCons.Platform.win32.get_system_root(),'System32','regsvr32') + env['REGSVRFLAGS'] = '/s ' + env['REGSVRCOM'] = '$REGSVR $REGSVRFLAGS ${TARGET.windows}' + + # Set-up ms tools paths + msvc_setup_env_once(env) + + + # Loadable modules are on Windows the same as shared libraries, but they + # are subject to different build parameters (LDMODULE* variables). + # Therefore LDMODULE* variables correspond as much as possible to + # SHLINK*/SHLIB* ones. + SCons.Tool.createLoadableModuleBuilder(env) + env['LDMODULE'] = '$SHLINK' + env['LDMODULEPREFIX'] = '$SHLIBPREFIX' + env['LDMODULESUFFIX'] = '$SHLIBSUFFIX' + env['LDMODULEFLAGS'] = '$SHLINKFLAGS' + env['_LDMODULE_TARGETS'] = _windowsLdmodTargets + env['_LDMODULE_SOURCES'] = _windowsLdmodSources + env['LDMODULEEMITTER'] = [ldmodEmitter] + env['LDMODULECOM'] = compositeLdmodAction + +def exists(env): + return msvc_exists() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mslink.xml b/src/engine/SCons/Tool/mslink.xml new file mode 100644 index 0000000..05b0222 --- /dev/null +++ b/src/engine/SCons/Tool/mslink.xml @@ -0,0 +1,237 @@ + + + +Sets construction variables for the Microsoft linker. + + +SHLINK +SHLINKFLAGS +SHLINKCOM +LINK +LINKFLAGS +LINKCOM +LIBDIRPREFIX +LIBDIRSUFFIX +LIBLINKPREFIX +LIBLINKSUFFIX +WIN32DEFPREFIX +WIN32DEFSUFFIX +WINDOWSDEFPREFIX +WINDOWSDEFSUFFIX +WINDOWS_INSERT_DEF +WIN32EXPPREFIX +WIN32EXPSUFFIX +WINDOWSEXPPREFIX +WINDOWSEXPSUFFIX +WINDOWSSHLIBMANIFESTPREFIX +WINDOWSSHLIBMANIFESTSUFFIX +WINDOWSPROGMANIFESTPREFIX +WINDOWSPROGMANIFESTSUFFIX + +REGSVR +REGSVRFLAGS +REGSVRCOM +LDMODULE +LDMODULEPREFIX +LDMODULESUFFIX +LDMODULEFLAGS +LDMODULECOM + + +SHLINKCOMSTR +LINKCOMSTR +REGSVRCOMSTR +MSVS_IGNORE_IDE_PATHS +LDMODULECOMSTR + + + + + +When set to non-zero, +suppresses creation of a corresponding Windows static import lib by the +SharedLibrary +builder when used with +MinGW, Microsoft Visual Studio or Metrowerks. +This also suppresses creation +of an export (.exp) file +when using Microsoft Visual Studio. + + + + + +The Microsoft Visual C++ PDB file that will store debugging information for +object files, shared libraries, and programs. This variable is ignored by +tools other than Microsoft Visual C++. +When this variable is +defined SCons will add options to the compiler and linker command line to +cause them to generate external debugging information, and will also set up the +dependencies for the PDB file. +Example: + + +env['PDB'] = 'hello.pdb' + + +The Visual C++ compiler switch that SCons uses by default +to generate PDB information is . +This works correctly with parallel () builds +because it embeds the debug information in the intermediate object files, +as opposed to sharing a single PDB file between multiple object files. +This is also the only way to get debug information +embedded into a static library. +Using the instead may yield improved +link-time performance, +although parallel builds will no longer work. +You can generate PDB files with the +switch by overriding the default &cv-link-CCPDBFLAGS; variable; +see the entry for that variable for specific examples. + + + + + +The program used on Windows systems +to register a newly-built DLL library +whenever the &b-SharedLibrary; builder +is passed a keyword argument of register=1. + + + + + +The command line used on Windows systems +to register a newly-built DLL library +whenever the &b-SharedLibrary; builder +is passed a keyword argument of register=1. + + + + + +The string displayed when registering a newly-built DLL file. +If this is not set, then &cv-link-REGSVRCOM; (the command line) is displayed. + + + + + +Flags passed to the DLL registration program +on Windows systems when a newly-built DLL library is registered. +By default, +this includes the +that prevents dialog boxes from popping up +and requiring user attention. + + + + + +A deprecated synonym for &cv-link-WINDOWS_INSERT_DEF;. + + + + + +A deprecated synonym for &cv-link-WINDOWSDEFPREFIX;. + + + + + +A deprecated synonym for &cv-link-WINDOWSDEFSUFFIX;. + + + + + +A deprecated synonym for &cv-link-WINDOWSEXPSUFFIX;. + + + + + +A deprecated synonym for &cv-link-WINDOWSEXPSUFFIX;. + + + + + +When this is set to true, +a library build of a Windows shared library +(.dllfile) +will also build a corresponding .def file +at the same time, +if a .def file +is not already listed as a build target. +The default is 0 (do not build a .def file). + + + + + +When this is set to true, +&scons; +will be aware of the +.manifest +files generated by Microsoft Visua C/C++ 8. + + + + + +The prefix used for Windows .deffile names. + + + + + +The suffix used for Windows .def file names. + + + + + +The prefix used for Windows .exp file names. + + + + + +The suffix used for Windows .exp file names. + + + + + +The prefix used for executable program .manifest files +generated by Microsoft Visual C/C++. + + + + + +The suffix used for executable program .manifest files +generated by Microsoft Visual C/C++. + + + + + +The prefix used for shared library .manifest files +generated by Microsoft Visual C/C++. + + + + + +The suffix used for shared library .manifest files +generated by Microsoft Visual C/C++. + + diff --git a/src/engine/SCons/Tool/mssdk.py b/src/engine/SCons/Tool/mssdk.py new file mode 100644 index 0000000..9f7cc6f --- /dev/null +++ b/src/engine/SCons/Tool/mssdk.py @@ -0,0 +1,50 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/mssdk.py 4577 2009/12/27 19:44:43 scons" + +"""engine.SCons.Tool.mssdk + +Tool-specific initialization for Microsoft SDKs, both Platform +SDKs and Windows SDKs. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +from MSCommon import mssdk_exists, \ + mssdk_setup_env + +def generate(env): + """Add construction variables for an MS SDK to an Environment.""" + mssdk_setup_env(env) + +def exists(env): + return mssdk_exists() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mssdk.xml b/src/engine/SCons/Tool/mssdk.xml new file mode 100644 index 0000000..9c2e6d0 --- /dev/null +++ b/src/engine/SCons/Tool/mssdk.xml @@ -0,0 +1,50 @@ + + + +Sets variables for Microsoft Platform SDK and/or Windows SDK. +Note that unlike most other Tool modules, +mssdk does not set construction variables, +but sets the environment variables +in the environment &SCons; uses to execute +the Microsoft toolchain: +%INCLUDE%, +%LIB%, +%LIBPATH% and +%PATH%. + + + + +MSSDK_DIR +MSSDK_VERSION +MSVS_VERSION + + + + + +The directory containing the Microsoft SDK +(either Platform SDK or Windows SDK) +to be used for compilation. + + + + + +The version string of the Microsoft SDK +(either Platform SDK or Windows SDK) +to be used for compilation. +Supported versions include +6.1, +6.0A, +6.0, +2003R2 +and +2003R1. + + diff --git a/src/engine/SCons/Tool/msvc.py b/src/engine/SCons/Tool/msvc.py new file mode 100644 index 0000000..0d3ec66 --- /dev/null +++ b/src/engine/SCons/Tool/msvc.py @@ -0,0 +1,257 @@ +"""engine.SCons.Tool.msvc + +Tool-specific initialization for Microsoft Visual C/C++. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/msvc.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import re +import string +import sys + +import SCons.Action +import SCons.Builder +import SCons.Errors +import SCons.Platform.win32 +import SCons.Tool +import SCons.Tool.msvs +import SCons.Util +import SCons.Warnings +import SCons.Scanner.RC + +from MSCommon import msvc_exists, msvc_setup_env_once + +CSuffixes = ['.c', '.C'] +CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++'] + +def validate_vars(env): + """Validate the PCH and PCHSTOP construction variables.""" + if env.has_key('PCH') and env['PCH']: + if not env.has_key('PCHSTOP'): + raise SCons.Errors.UserError, "The PCHSTOP construction must be defined if PCH is defined." + if not SCons.Util.is_String(env['PCHSTOP']): + raise SCons.Errors.UserError, "The PCHSTOP construction variable must be a string: %r"%env['PCHSTOP'] + +def pch_emitter(target, source, env): + """Adds the object file target.""" + + validate_vars(env) + + pch = None + obj = None + + for t in target: + if SCons.Util.splitext(str(t))[1] == '.pch': + pch = t + if SCons.Util.splitext(str(t))[1] == '.obj': + obj = t + + if not obj: + obj = SCons.Util.splitext(str(pch))[0]+'.obj' + + target = [pch, obj] # pch must be first, and obj second for the PCHCOM to work + + return (target, source) + +def object_emitter(target, source, env, parent_emitter): + """Sets up the PCH dependencies for an object file.""" + + validate_vars(env) + + parent_emitter(target, source, env) + + if env.has_key('PCH') and env['PCH']: + env.Depends(target, env['PCH']) + + return (target, source) + +def static_object_emitter(target, source, env): + return object_emitter(target, source, env, + SCons.Defaults.StaticObjectEmitter) + +def shared_object_emitter(target, source, env): + return object_emitter(target, source, env, + SCons.Defaults.SharedObjectEmitter) + +pch_action = SCons.Action.Action('$PCHCOM', '$PCHCOMSTR') +pch_builder = SCons.Builder.Builder(action=pch_action, suffix='.pch', + emitter=pch_emitter, + source_scanner=SCons.Tool.SourceFileScanner) + + +# Logic to build .rc files into .res files (resource files) +res_scanner = SCons.Scanner.RC.RCScan() +res_action = SCons.Action.Action('$RCCOM', '$RCCOMSTR') +res_builder = SCons.Builder.Builder(action=res_action, + src_suffix='.rc', + suffix='.res', + src_builder=[], + source_scanner=res_scanner) + +def msvc_batch_key(action, env, target, source): + """ + Returns a key to identify unique batches of sources for compilation. + + If batching is enabled (via the $MSVC_BATCH setting), then all + target+source pairs that use the same action, defined by the same + environment, and have the same target and source directories, will + be batched. + + Returning None specifies that the specified target+source should not + be batched with other compilations. + """ + b = env.subst('$MSVC_BATCH') + if b in (None, '', '0'): + # We're not using batching; return no key. + return None + t = target[0] + s = source[0] + if os.path.splitext(t.name)[0] != os.path.splitext(s.name)[0]: + # The base names are different, so this *must* be compiled + # separately; return no key. + return None + return (id(action), id(env), t.dir, s.dir) + +def msvc_output_flag(target, source, env, for_signature): + """ + Returns the correct /Fo flag for batching. + + If batching is disabled or there's only one source file, then we + return an /Fo string that specifies the target explicitly. Otherwise, + we return an /Fo string that just specifies the first target's + directory (where the Visual C/C++ compiler will put the .obj files). + """ + b = env.subst('$MSVC_BATCH') + if b in (None, '', '0') or len(source) == 1: + return '/Fo$TARGET' + else: + # The Visual C/C++ compiler requires a \ at the end of the /Fo + # option to indicate an output directory. We use os.sep here so + # that the test(s) for this can be run on non-Windows systems + # without having a hard-coded backslash mess up command-line + # argument parsing. + return '/Fo${TARGET.dir}' + os.sep + +CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR", + batch_key=msvc_batch_key, + targets='$CHANGED_TARGETS') +ShCAction = SCons.Action.Action("$SHCCCOM", "$SHCCCOMSTR", + batch_key=msvc_batch_key, + targets='$CHANGED_TARGETS') +CXXAction = SCons.Action.Action("$CXXCOM", "$CXXCOMSTR", + batch_key=msvc_batch_key, + targets='$CHANGED_TARGETS') +ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR", + batch_key=msvc_batch_key, + targets='$CHANGED_TARGETS') + +def generate(env): + """Add Builders and construction variables for MSVC++ to an Environment.""" + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + # TODO(batch): shouldn't reach in to cmdgen this way; necessary + # for now to bypass the checks in Builder.DictCmdGenerator.__call__() + # and allow .cc and .cpp to be compiled in the same command line. + static_obj.cmdgen.source_ext_match = False + shared_obj.cmdgen.source_ext_match = False + + for suffix in CSuffixes: + static_obj.add_action(suffix, CAction) + shared_obj.add_action(suffix, ShCAction) + static_obj.add_emitter(suffix, static_object_emitter) + shared_obj.add_emitter(suffix, shared_object_emitter) + + for suffix in CXXSuffixes: + static_obj.add_action(suffix, CXXAction) + shared_obj.add_action(suffix, ShCXXAction) + static_obj.add_emitter(suffix, static_object_emitter) + shared_obj.add_emitter(suffix, shared_object_emitter) + + env['CCPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Z7") or ""}']) + env['CCPCHFLAGS'] = SCons.Util.CLVar(['${(PCH and "/Yu%s /Fp%s"%(PCHSTOP or "",File(PCH))) or ""}']) + env['_MSVC_OUTPUT_FLAG'] = msvc_output_flag + env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $CCPCHFLAGS $CCPDBFLAGS' + env['CC'] = 'cl' + env['CCFLAGS'] = SCons.Util.CLVar('/nologo') + env['CFLAGS'] = SCons.Util.CLVar('') + env['CCCOM'] = '$CC $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CFLAGS $CCFLAGS $_CCCOMCOM' + env['SHCC'] = '$CC' + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS') + env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS') + env['SHCCCOM'] = '$SHCC $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCFLAGS $SHCCFLAGS $_CCCOMCOM' + env['CXX'] = '$CC' + env['CXXFLAGS'] = SCons.Util.CLVar('$( /TP $)') + env['CXXCOM'] = '$CXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CXXFLAGS $CCFLAGS $_CCCOMCOM' + env['SHCXX'] = '$CXX' + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS') + env['SHCXXCOM'] = '$SHCXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM' + env['CPPDEFPREFIX'] = '/D' + env['CPPDEFSUFFIX'] = '' + env['INCPREFIX'] = '/I' + env['INCSUFFIX'] = '' +# env.Append(OBJEMITTER = [static_object_emitter]) +# env.Append(SHOBJEMITTER = [shared_object_emitter]) + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + + env['RC'] = 'rc' + env['RCFLAGS'] = SCons.Util.CLVar('') + env['RCSUFFIXES']=['.rc','.rc2'] + env['RCCOM'] = '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES' + env['BUILDERS']['RES'] = res_builder + env['OBJPREFIX'] = '' + env['OBJSUFFIX'] = '.obj' + env['SHOBJPREFIX'] = '$OBJPREFIX' + env['SHOBJSUFFIX'] = '$OBJSUFFIX' + + # Set-up ms tools paths + msvc_setup_env_once(env) + + env['CFILESUFFIX'] = '.c' + env['CXXFILESUFFIX'] = '.cc' + + env['PCHPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Yd") or ""}']) + env['PCHCOM'] = '$CXX /Fo${TARGETS[1]} $CXXFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS $PCHPDBFLAGS' + env['BUILDERS']['PCH'] = pch_builder + + if not env.has_key('ENV'): + env['ENV'] = {} + if not env['ENV'].has_key('SystemRoot'): # required for dlls in the winsxs folders + env['ENV']['SystemRoot'] = SCons.Platform.win32.get_system_root() + +def exists(env): + return msvc_exists() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/msvc.xml b/src/engine/SCons/Tool/msvc.xml new file mode 100644 index 0000000..c744903 --- /dev/null +++ b/src/engine/SCons/Tool/msvc.xml @@ -0,0 +1,316 @@ + + + +Sets construction variables for the Microsoft Visual C/C++ compiler. + + +CCPDBFLAGS +CCPCHFLAGS + +CC +CCFLAGS +CFLAGS +CCCOM +SHCC +SHCCFLAGS +SHCFLAGS +SHCCCOM +CXX +CXXFLAGS +CXXCOM +SHCXX +SHCXXFLAGS +SHCXXCOM +CPPDEFPREFIX +CPPDEFSUFFIX +INCPREFIX +INCSUFFIX + +RC +RCFLAGS +RCCOM +BUILDERS +OBJPREFIX +OBJSUFFIX +SHOBJPREFIX +SHOBJSUFFIX +CFILESUFFIX +CXXFILESUFFIX +PCHPDBFLAGS +PCHCOM + + +CCCOMSTR +SHCCCOMSTR +CXXCOMSTR +SHCXXCOMSTR +PCH +PCHSTOP +PDB + + + + + +Builds a Microsoft Visual C++ precompiled header. +Calling this builder method +returns a list of two targets: the PCH as the first element, and the object +file as the second element. Normally the object file is ignored. +This builder method is only +provided when Microsoft Visual C++ is being used as the compiler. +The PCH builder method is generally used in +conjuction with the PCH construction variable to force object files to use +the precompiled header: + + +env['PCH'] = env.PCH('StdAfx.cpp')[0] + + + + + + +Builds a Microsoft Visual C++ resource file. +This builder method is only provided +when Microsoft Visual C++ or MinGW is being used as the compiler. The +.res +(or +.o +for MinGW) suffix is added to the target name if no other suffix is given. +The source +file is scanned for implicit dependencies as though it were a C file. +Example: + + +env.RES('resource.rc') + + + + + + +Options added to the compiler command line +to support building with precompiled headers. +The default value expands expands to the appropriate +Microsoft Visual C++ command-line options +when the &cv-link-PCH; construction variable is set. + + + + + +Options added to the compiler command line +to support storing debugging information in a +Microsoft Visual C++ PDB file. +The default value expands expands to appropriate +Microsoft Visual C++ command-line options +when the &cv-link-PDB; construction variable is set. + +The Visual C++ compiler option that SCons uses by default +to generate PDB information is . +This works correctly with parallel () builds +because it embeds the debug information in the intermediate object files, +as opposed to sharing a single PDB file between multiple object files. +This is also the only way to get debug information +embedded into a static library. +Using the instead may yield improved +link-time performance, +although parallel builds will no longer work. + +You can generate PDB files with the +switch by overriding the default &cv-link-CCPDBFLAGS; variable as follows: + + +env['CCPDBFLAGS'] = ['${(PDB and "/Zi /Fd%s" % File(PDB)) or ""}'] + + +An alternative would be to use the +to put the debugging information in a separate .pdb +file for each object file by overriding +the &cv-link-CCPDBFLAGS; variable as follows: + + +env['CCPDBFLAGS'] = '/Zi /Fd${TARGET}.pdb' + + + + + + +When set to any true value, +specifies that &SCons; should batch +compilation of object files +when calling the Microsoft Visual C/C++ compiler. +All compilations of source files from the same source directory +that generate target files in a same output directory +and were configured in &SCons; using the same construction environment +will be built in a single call to the compiler. +Only source files that have changed since their +object files were built will be passed to each compiler invocation +(via the &cv-link-CHANGED_SOURCES; construction variable). +Any compilations where the object (target) file base name +(minus the .obj) +does not match the source file base name +will be compiled separately. + + + + + +The Microsoft Visual C++ precompiled header that will be used when compiling +object files. This variable is ignored by tools other than Microsoft Visual C++. +When this variable is +defined SCons will add options to the compiler command line to +cause it to use the precompiled header, and will also set up the +dependencies for the PCH file. +Example: + + +env['PCH'] = 'StdAfx.pch' + + + + + + +The command line used by the +&b-PCH; +builder to generated a precompiled header. + + + + + +The string displayed when generating a precompiled header. +If this is not set, then &cv-link-PCHCOM; (the command line) is displayed. + + + + + +A construction variable that, when expanded, +adds the /yD flag to the command line +only if the &cv-PDB; construction variable is set. + + + + + +This variable specifies how much of a source file is precompiled. This +variable is ignored by tools other than Microsoft Visual C++, or when +the PCH variable is not being used. When this variable is define it +must be a string that is the name of the header that +is included at the end of the precompiled portion of the source files, or +the empty string if the "#pragma hrdstop" construct is being used: + + +env['PCHSTOP'] = 'StdAfx.h' + + + + + + +The resource compiler used to build +a Microsoft Visual C++ resource file. + + + + + +The command line used to build +a Microsoft Visual C++ resource file. + + + + + +The string displayed when invoking the resource compiler +to build a Microsoft Visual C++ resource file. +If this is not set, then &cv-link-RCCOM; (the command line) is displayed. + + + + + +The flags passed to the resource compiler by the RES builder. + + + + + +An automatically-generated construction variable +containing the command-line options +for specifying directories to be searched +by the resource compiler. +The value of &cv-RCINCFLAGS; is created +by appending &cv-RCINCPREFIX; and &cv-RCINCSUFFIX; +to the beginning and end +of each directory in &cv-CPPPATH;. + + + + + +The prefix (flag) used to specify an include directory +on the resource compiler command line. +This will be appended to the beginning of each directory +in the &cv-CPPPATH; construction variable +when the &cv-RCINCFLAGS; variable is expanded. + + + + + +The suffix used to specify an include directory +on the resource compiler command line. +This will be appended to the end of each directory +in the &cv-CPPPATH; construction variable +when the &cv-RCINCFLAGS; variable is expanded. + + + + + +Sets the preferred version of Microsoft Visual C/C++ to use. + +If &cv-MSVC_VERSION; is not set, +&SCons; will (by default) select the latest version +of Visual C/C++ installed on your system. +If the specified version isn't installed, +tool initialization will fail. + + + + + +Sets the host architecture for Visual Studio compiler. If not set, +default to the detected host architecture: note that this may depend +on the python you are using. + +Valid values are the same as for &cv-TARGET_ARCH;. + +This is currently only used on Windows, but in the future it will be +used on other OSes as well. + + + + +Sets the target architecture for Visual Studio compiler (i.e. the arch +of the binaries generated by the compiler). If not set, default to +&cv-HOST_ARCH;. +This is currently only used on Windows, but in the future it will be +used on other OSes as well. + +Valid values for Windows are 'x86', 'i386' (for 32 bits); +'amd64', 'emt64', 'x86_64' (64 bits); +and 'ia64' (Itanium). +For example, if you want to compile 64-bit binaries, you would set +TARGET_ARCH='x86_64' in your SCons environment. + + diff --git a/src/engine/SCons/Tool/msvs.py b/src/engine/SCons/Tool/msvs.py new file mode 100644 index 0000000..a490b9c --- /dev/null +++ b/src/engine/SCons/Tool/msvs.py @@ -0,0 +1,1439 @@ +"""SCons.Tool.msvs + +Tool-specific initialization for Microsoft Visual Studio project files. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/msvs.py 4577 2009/12/27 19:44:43 scons" + +import base64 +import hashlib +import ntpath +import os +import pickle +import re +import string +import sys + +import SCons.Builder +import SCons.Node.FS +import SCons.Platform.win32 +import SCons.Script.SConscript +import SCons.Util +import SCons.Warnings + +from MSCommon import msvc_exists, msvc_setup_env_once +from SCons.Defaults import processDefines + +############################################################################## +# Below here are the classes and functions for generation of +# DSP/DSW/SLN/VCPROJ files. +############################################################################## + +def _hexdigest(s): + """Return a string as a string of hex characters. + """ + # NOTE: This routine is a method in the Python 2.0 interface + # of the native md5 module, but we want SCons to operate all + # the way back to at least Python 1.5.2, which doesn't have it. + h = string.hexdigits + r = '' + for c in s: + i = ord(c) + r = r + h[(i >> 4) & 0xF] + h[i & 0xF] + return r + +def xmlify(s): + s = string.replace(s, "&", "&") # do this first + s = string.replace(s, "'", "'") + s = string.replace(s, '"', """) + return s + +external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' + +def _generateGUID(slnfile, name): + """This generates a dummy GUID for the sln file to use. It is + based on the MD5 signatures of the sln filename plus the name of + the project. It basically just needs to be unique, and not + change with each invocation.""" + m = hashlib.md5() + # Normalize the slnfile path to a Windows path (\ separators) so + # the generated file has a consistent GUID even if we generate + # it on a non-Windows platform. + m.update(ntpath.normpath(str(slnfile)) + str(name)) + # TODO(1.5) + #solution = m.hexdigest().upper() + solution = string.upper(_hexdigest(m.digest())) + # convert most of the signature to GUID form (discard the rest) + solution = "{" + solution[:8] + "-" + solution[8:12] + "-" + solution[12:16] + "-" + solution[16:20] + "-" + solution[20:32] + "}" + return solution + +version_re = re.compile(r'(\d+\.\d+)(.*)') + +def msvs_parse_version(s): + """ + Split a Visual Studio version, which may in fact be something like + '7.0Exp', into is version number (returned as a float) and trailing + "suite" portion. + """ + num, suite = version_re.match(s).groups() + return float(num), suite + +# This is how we re-invoke SCons from inside MSVS Project files. +# The problem is that we might have been invoked as either scons.bat +# or scons.py. If we were invoked directly as scons.py, then we could +# use sys.argv[0] to find the SCons "executable," but that doesn't work +# if we were invoked as scons.bat, which uses "python -c" to execute +# things and ends up with "-c" as sys.argv[0]. Consequently, we have +# the MSVS Project file invoke SCons the same way that scons.bat does, +# which works regardless of how we were invoked. +def getExecScriptMain(env, xml=None): + scons_home = env.get('SCONS_HOME') + if not scons_home and os.environ.has_key('SCONS_LIB_DIR'): + scons_home = os.environ['SCONS_LIB_DIR'] + if scons_home: + exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home + else: + version = SCons.__version__ + exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%(version)s'), join(sys.prefix, 'scons-%(version)s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % locals() + if xml: + exec_script_main = xmlify(exec_script_main) + return exec_script_main + +# The string for the Python executable we tell the Project file to use +# is either sys.executable or, if an external PYTHON_ROOT environment +# variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to +# pluck the actual executable name from sys.executable). +try: + python_root = os.environ['PYTHON_ROOT'] +except KeyError: + python_executable = sys.executable +else: + python_executable = os.path.join('$$(PYTHON_ROOT)', + os.path.split(sys.executable)[1]) + +class Config: + pass + +def splitFully(path): + dir, base = os.path.split(path) + if dir and dir != '' and dir != path: + return splitFully(dir)+[base] + if base == '': + return [] + return [base] + +def makeHierarchy(sources): + '''Break a list of files into a hierarchy; for each value, if it is a string, + then it is a file. If it is a dictionary, it is a folder. The string is + the original path of the file.''' + + hierarchy = {} + for file in sources: + path = splitFully(file) + if len(path): + dict = hierarchy + for part in path[:-1]: + if not dict.has_key(part): + dict[part] = {} + dict = dict[part] + dict[path[-1]] = file + #else: + # print 'Warning: failed to decompose path for '+str(file) + return hierarchy + +class _DSPGenerator: + """ Base class for DSP generators """ + + srcargs = [ + 'srcs', + 'incs', + 'localincs', + 'resources', + 'misc'] + + def __init__(self, dspfile, source, env): + self.dspfile = str(dspfile) + try: + get_abspath = dspfile.get_abspath + except AttributeError: + self.dspabs = os.path.abspath(dspfile) + else: + self.dspabs = get_abspath() + + if not env.has_key('variant'): + raise SCons.Errors.InternalError, \ + "You must specify a 'variant' argument (i.e. 'Debug' or " +\ + "'Release') to create an MSVSProject." + elif SCons.Util.is_String(env['variant']): + variants = [env['variant']] + elif SCons.Util.is_List(env['variant']): + variants = env['variant'] + + if not env.has_key('buildtarget') or env['buildtarget'] == None: + buildtarget = [''] + elif SCons.Util.is_String(env['buildtarget']): + buildtarget = [env['buildtarget']] + elif SCons.Util.is_List(env['buildtarget']): + if len(env['buildtarget']) != len(variants): + raise SCons.Errors.InternalError, \ + "Sizes of 'buildtarget' and 'variant' lists must be the same." + buildtarget = [] + for bt in env['buildtarget']: + if SCons.Util.is_String(bt): + buildtarget.append(bt) + else: + buildtarget.append(bt.get_abspath()) + else: + buildtarget = [env['buildtarget'].get_abspath()] + if len(buildtarget) == 1: + bt = buildtarget[0] + buildtarget = [] + for _ in variants: + buildtarget.append(bt) + + if not env.has_key('outdir') or env['outdir'] == None: + outdir = [''] + elif SCons.Util.is_String(env['outdir']): + outdir = [env['outdir']] + elif SCons.Util.is_List(env['outdir']): + if len(env['outdir']) != len(variants): + raise SCons.Errors.InternalError, \ + "Sizes of 'outdir' and 'variant' lists must be the same." + outdir = [] + for s in env['outdir']: + if SCons.Util.is_String(s): + outdir.append(s) + else: + outdir.append(s.get_abspath()) + else: + outdir = [env['outdir'].get_abspath()] + if len(outdir) == 1: + s = outdir[0] + outdir = [] + for v in variants: + outdir.append(s) + + if not env.has_key('runfile') or env['runfile'] == None: + runfile = buildtarget[-1:] + elif SCons.Util.is_String(env['runfile']): + runfile = [env['runfile']] + elif SCons.Util.is_List(env['runfile']): + if len(env['runfile']) != len(variants): + raise SCons.Errors.InternalError, \ + "Sizes of 'runfile' and 'variant' lists must be the same." + runfile = [] + for s in env['runfile']: + if SCons.Util.is_String(s): + runfile.append(s) + else: + runfile.append(s.get_abspath()) + else: + runfile = [env['runfile'].get_abspath()] + if len(runfile) == 1: + s = runfile[0] + runfile = [] + for v in variants: + runfile.append(s) + + self.sconscript = env['MSVSSCONSCRIPT'] + + cmdargs = env.get('cmdargs', '') + + self.env = env + + if self.env.has_key('name'): + self.name = self.env['name'] + else: + self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0]) + self.name = self.env.subst(self.name) + + sourcenames = [ + 'Source Files', + 'Header Files', + 'Local Headers', + 'Resource Files', + 'Other Files'] + + self.sources = {} + for n in sourcenames: + self.sources[n] = [] + + self.configs = {} + + self.nokeep = 0 + if env.has_key('nokeep') and env['variant'] != 0: + self.nokeep = 1 + + if self.nokeep == 0 and os.path.exists(self.dspabs): + self.Parse() + + for t in zip(sourcenames,self.srcargs): + if self.env.has_key(t[1]): + if SCons.Util.is_List(self.env[t[1]]): + for i in self.env[t[1]]: + if not i in self.sources[t[0]]: + self.sources[t[0]].append(i) + else: + if not self.env[t[1]] in self.sources[t[0]]: + self.sources[t[0]].append(self.env[t[1]]) + + for n in sourcenames: + # TODO(1.5): + #self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower())) + self.sources[n].sort(lambda a, b: cmp(string.lower(a), string.lower(b))) + + def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile): + config = Config() + config.buildtarget = buildtarget + config.outdir = outdir + config.cmdargs = cmdargs + config.runfile = runfile + + match = re.match('(.*)\|(.*)', variant) + if match: + config.variant = match.group(1) + config.platform = match.group(2) + else: + config.variant = variant + config.platform = 'Win32' + + self.configs[variant] = config + print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'" + + for i in range(len(variants)): + AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs) + + self.platforms = [] + for key in self.configs.keys(): + platform = self.configs[key].platform + if not platform in self.platforms: + self.platforms.append(platform) + + def Build(self): + pass + +V6DSPHeader = """\ +# Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) External Target" 0x0106 + +CFG=%(name)s - Win32 %(confkey)s +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "%(name)s.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +""" + +class _GenerateV6DSP(_DSPGenerator): + """Generates a Project file for MSVS 6.0""" + + def PrintHeader(self): + # pick a default config + confkeys = self.configs.keys() + confkeys.sort() + + name = self.name + confkey = confkeys[0] + + self.file.write(V6DSPHeader % locals()) + + for kind in confkeys: + self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind)) + + self.file.write('!MESSAGE \n\n') + + def PrintProject(self): + name = self.name + self.file.write('# Begin Project\n' + '# PROP AllowPerConfigDependencies 0\n' + '# PROP Scc_ProjName ""\n' + '# PROP Scc_LocalPath ""\n\n') + + first = 1 + confkeys = self.configs.keys() + confkeys.sort() + for kind in confkeys: + outdir = self.configs[kind].outdir + buildtarget = self.configs[kind].buildtarget + if first == 1: + self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind)) + first = 0 + else: + self.file.write('\n!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind)) + + env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET') + if not env_has_buildtarget: + self.env['MSVSBUILDTARGET'] = buildtarget + + # have to write this twice, once with the BASE settings, and once without + for base in ("BASE ",""): + self.file.write('# PROP %sUse_MFC 0\n' + '# PROP %sUse_Debug_Libraries ' % (base, base)) + # TODO(1.5): + #if kind.lower().find('debug') < 0: + if string.find(string.lower(kind), 'debug') < 0: + self.file.write('0\n') + else: + self.file.write('1\n') + self.file.write('# PROP %sOutput_Dir "%s"\n' + '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir)) + cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1) + self.file.write('# PROP %sCmd_Line "%s"\n' + '# PROP %sRebuild_Opt "-c && %s"\n' + '# PROP %sTarget_File "%s"\n' + '# PROP %sBsc_Name ""\n' + '# PROP %sTarget_Dir ""\n'\ + %(base,cmd,base,cmd,base,buildtarget,base,base)) + + if not env_has_buildtarget: + del self.env['MSVSBUILDTARGET'] + + self.file.write('\n!ENDIF\n\n' + '# Begin Target\n\n') + for kind in confkeys: + self.file.write('# Name "%s - Win32 %s"\n' % (name,kind)) + self.file.write('\n') + first = 0 + for kind in confkeys: + if first == 0: + self.file.write('!IF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind)) + first = 1 + else: + self.file.write('!ELSEIF "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind)) + self.file.write('!ENDIF \n\n') + self.PrintSourceFiles() + self.file.write('# End Target\n' + '# End Project\n') + + if self.nokeep == 0: + # now we pickle some data and add it to the file -- MSDEV will ignore it. + pdata = pickle.dumps(self.configs,1) + pdata = base64.encodestring(pdata) + self.file.write(pdata + '\n') + pdata = pickle.dumps(self.sources,1) + pdata = base64.encodestring(pdata) + self.file.write(pdata + '\n') + + def PrintSourceFiles(self): + categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat', + 'Header Files': 'h|hpp|hxx|hm|inl', + 'Local Headers': 'h|hpp|hxx|hm|inl', + 'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe', + 'Other Files': ''} + + cats = categories.keys() + # TODO(1.5): + #cats.sort(lambda a, b: cmp(a.lower(), b.lower())) + cats.sort(lambda a, b: cmp(string.lower(a), string.lower(b))) + for kind in cats: + if not self.sources[kind]: + continue # skip empty groups + + self.file.write('# Begin Group "' + kind + '"\n\n') + # TODO(1.5) + #typelist = categories[kind].replace('|', ';') + typelist = string.replace(categories[kind], '|', ';') + self.file.write('# PROP Default_Filter "' + typelist + '"\n') + + for file in self.sources[kind]: + file = os.path.normpath(file) + self.file.write('# Begin Source File\n\n' + 'SOURCE="' + file + '"\n' + '# End Source File\n') + self.file.write('# End Group\n') + + # add the SConscript file outside of the groups + self.file.write('# Begin Source File\n\n' + 'SOURCE="' + str(self.sconscript) + '"\n' + '# End Source File\n') + + def Parse(self): + try: + dspfile = open(self.dspabs,'r') + except IOError: + return # doesn't exist yet, so can't add anything to configs. + + line = dspfile.readline() + while line: + # TODO(1.5): + #if line.find("# End Project") > -1: + if string.find(line, "# End Project") > -1: + break + line = dspfile.readline() + + line = dspfile.readline() + datas = line + while line and line != '\n': + line = dspfile.readline() + datas = datas + line + + # OK, we've found our little pickled cache of data. + try: + datas = base64.decodestring(datas) + data = pickle.loads(datas) + except KeyboardInterrupt: + raise + except: + return # unable to unpickle any data for some reason + + self.configs.update(data) + + data = None + line = dspfile.readline() + datas = line + while line and line != '\n': + line = dspfile.readline() + datas = datas + line + + # OK, we've found our little pickled cache of data. + # it has a "# " in front of it, so we strip that. + try: + datas = base64.decodestring(datas) + data = pickle.loads(datas) + except KeyboardInterrupt: + raise + except: + return # unable to unpickle any data for some reason + + self.sources.update(data) + + def Build(self): + try: + self.file = open(self.dspabs,'w') + except IOError, detail: + raise SCons.Errors.InternalError, 'Unable to open "' + self.dspabs + '" for writing:' + str(detail) + else: + self.PrintHeader() + self.PrintProject() + self.file.close() + +V7DSPHeader = """\ + + +""" + +V7DSPConfiguration = """\ +\t\t +\t\t\t +\t\t +""" + +V8DSPHeader = """\ + + +""" + +V8DSPConfiguration = """\ +\t\t +\t\t\t +\t\t +""" +class _GenerateV7DSP(_DSPGenerator): + """Generates a Project file for MSVS .NET""" + + def __init__(self, dspfile, source, env): + _DSPGenerator.__init__(self, dspfile, source, env) + self.version = env['MSVS_VERSION'] + self.version_num, self.suite = msvs_parse_version(self.version) + if self.version_num >= 8.0: + self.versionstr = '8.00' + self.dspheader = V8DSPHeader + self.dspconfiguration = V8DSPConfiguration + else: + if self.version_num >= 7.1: + self.versionstr = '7.10' + else: + self.versionstr = '7.00' + self.dspheader = V7DSPHeader + self.dspconfiguration = V7DSPConfiguration + self.file = None + + def PrintHeader(self): + env = self.env + versionstr = self.versionstr + name = self.name + encoding = self.env.subst('$MSVSENCODING') + scc_provider = env.get('MSVS_SCC_PROVIDER', '') + scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '') + scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '') + scc_local_path = env.get('MSVS_SCC_LOCAL_PATH', '') + project_guid = env.get('MSVS_PROJECT_GUID', '') + if self.version_num >= 8.0 and not project_guid: + project_guid = _generateGUID(self.dspfile, '') + if scc_provider != '': + scc_attrs = ('\tProjectGUID="%s"\n' + '\tSccProjectName="%s"\n' + '\tSccAuxPath="%s"\n' + '\tSccLocalPath="%s"\n' + '\tSccProvider="%s"' % (project_guid, scc_project_name, scc_aux_path, scc_local_path, scc_provider)) + else: + scc_attrs = ('\tProjectGUID="%s"\n' + '\tSccProjectName="%s"\n' + '\tSccLocalPath="%s"' % (project_guid, scc_project_name, scc_local_path)) + + self.file.write(self.dspheader % locals()) + + self.file.write('\t\n') + for platform in self.platforms: + self.file.write( + '\t\t\n' % platform) + self.file.write('\t\n') + + if self.version_num >= 8.0: + self.file.write('\t\n' + '\t\n') + + def PrintProject(self): + self.file.write('\t\n') + + confkeys = self.configs.keys() + confkeys.sort() + for kind in confkeys: + variant = self.configs[kind].variant + platform = self.configs[kind].platform + outdir = self.configs[kind].outdir + buildtarget = self.configs[kind].buildtarget + runfile = self.configs[kind].runfile + cmdargs = self.configs[kind].cmdargs + + env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET') + if not env_has_buildtarget: + self.env['MSVSBUILDTARGET'] = buildtarget + + starting = 'echo Starting SCons && ' + if cmdargs: + cmdargs = ' ' + cmdargs + else: + cmdargs = '' + buildcmd = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs) + rebuildcmd = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs) + cleancmd = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs) + + # TODO(1.5) + #preprocdefs = xmlify(';'.join(self.env.get('CPPDEFINES', []))) + #includepath = xmlify(';'.join(self.env.get('CPPPATH', []))) + preprocdefs = xmlify(string.join(processDefines(self.env.get('CPPDEFINES', [])), ';')) + includepath = xmlify(string.join(self.env.get('CPPPATH', []), ';')) + + if not env_has_buildtarget: + del self.env['MSVSBUILDTARGET'] + + self.file.write(self.dspconfiguration % locals()) + + self.file.write('\t\n') + + if self.version_num >= 7.1: + self.file.write('\t\n' + '\t\n') + + self.PrintSourceFiles() + + self.file.write('\n') + + if self.nokeep == 0: + # now we pickle some data and add it to the file -- MSDEV will ignore it. + pdata = pickle.dumps(self.configs,1) + pdata = base64.encodestring(pdata) + self.file.write('\n') + + def printSources(self, hierarchy, commonprefix): + sorteditems = hierarchy.items() + # TODO(1.5): + #sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower())) + sorteditems.sort(lambda a, b: cmp(string.lower(a[0]), string.lower(b[0]))) + + # First folders, then files + for key, value in sorteditems: + if SCons.Util.is_Dict(value): + self.file.write('\t\t\t\n' % (key)) + self.printSources(value, commonprefix) + self.file.write('\t\t\t\n') + + for key, value in sorteditems: + if SCons.Util.is_String(value): + file = value + if commonprefix: + file = os.path.join(commonprefix, value) + file = os.path.normpath(file) + self.file.write('\t\t\t\n' + '\t\t\t\n' % (file)) + + def PrintSourceFiles(self): + categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat', + 'Header Files': 'h;hpp;hxx;hm;inl', + 'Local Headers': 'h;hpp;hxx;hm;inl', + 'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe', + 'Other Files': ''} + + self.file.write('\t\n') + + cats = categories.keys() + # TODO(1.5) + #cats.sort(lambda a, b: cmp(a.lower(), b.lower())) + cats.sort(lambda a, b: cmp(string.lower(a), string.lower(b))) + cats = filter(lambda k, s=self: s.sources[k], cats) + for kind in cats: + if len(cats) > 1: + self.file.write('\t\t\n' % (kind, categories[kind])) + + sources = self.sources[kind] + + # First remove any common prefix + commonprefix = None + if len(sources) > 1: + s = map(os.path.normpath, sources) + # take the dirname because the prefix may include parts + # of the filenames (e.g. if you have 'dir\abcd' and + # 'dir\acde' then the cp will be 'dir\a' ) + cp = os.path.dirname( os.path.commonprefix(s) ) + if cp and s[0][len(cp)] == os.sep: + # +1 because the filename starts after the separator + sources = map(lambda s, l=len(cp)+1: s[l:], sources) + commonprefix = cp + elif len(sources) == 1: + commonprefix = os.path.dirname( sources[0] ) + sources[0] = os.path.basename( sources[0] ) + + hierarchy = makeHierarchy(sources) + self.printSources(hierarchy, commonprefix=commonprefix) + + if len(cats)>1: + self.file.write('\t\t\n') + + # add the SConscript file outside of the groups + self.file.write('\t\t\n' + '\t\t\n' % str(self.sconscript)) + + self.file.write('\t\n' + '\t\n' + '\t\n') + + def Parse(self): + try: + dspfile = open(self.dspabs,'r') + except IOError: + return # doesn't exist yet, so can't add anything to configs. + + line = dspfile.readline() + while line: + # TODO(1.5) + #if line.find(' + + +Sets construction variables for Microsoft Visual Studio. + + +MSVSPROJECTCOM +MSVSSOLUTIONCOM +MSVSSCONSCRIPT +MSVSSCONS +MSVSSCONSFLAGS +MSVSSCONSCOM +MSVSBUILDCOM +MSVSREBUILDCOM +MSVSCLEANCOM +MSVSENCODING + + + + + + + +Builds a Microsoft Visual Studio project file, +and by default builds a solution file as well. + +This builds a Visual Studio project file, based on the version of +Visual Studio that is configured (either the latest installed version, +or the version specified by +&cv-link-MSVS_VERSION; +in the Environment constructor). +For Visual Studio 6, it will generate a +.dsp +file. +For Visual Studio 7 (.NET) and later versions, it will generate a +.vcproj +file. + +By default, +this also generates a solution file +for the specified project, +a +.dsw +file for Visual Studio 6 +or a +.sln +file for Visual Studio 7 (.NET). +This behavior may be disabled by specifying +auto_build_solution=0 +when you call +&b-MSVSProject;, +in which case you presumably want to +build the solution file(s) +by calling the +&b-MSVSSolution; +Builder (see below). + +The &b-MSVSProject; builder +takes several lists of filenames +to be placed into the project file. +These are currently limited to +srcs, +incs, +localincs, +resources, +and +misc. +These are pretty self-explanatory, but it should be noted that these +lists are added to the &cv-link-SOURCES; construction variable as strings, +NOT as SCons File Nodes. This is because they represent file +names to be added to the project file, not the source files used to +build the project file. + +The above filename lists are all optional, +although at least one must be specified +for the resulting project file to be non-empty. + +In addition to the above lists of values, +the following values may be specified: + +target: +The name of the target +.dsp +or +.vcproj +file. +The correct +suffix for the version of Visual Studio must be used, +but the +&cv-link-MSVSPROJECTSUFFIX; +construction variable +will be defined to the correct value (see example below). + +variant: +The name of this particular variant. +For Visual Studio 7 projects, +this can also be a list of variant names. +These are typically things like "Debug" or "Release", but really +can be anything you want. +For Visual Studio 7 projects, +they may also specify a target platform +separated from the variant name by a +| +(vertical pipe) +character: +Debug|Xbox. +The default target platform is Win32. +Multiple calls to +&b-MSVSProject; +with different variants are allowed; +all variants will be added to the project file with their appropriate +build targets and sources. + +buildtarget: +An optional string, node, or list of strings or nodes +(one per build variant), to tell the Visual Studio debugger +what output target to use in what build variant. +The number of +buildtarget +entries must match the number of +variant +entries. + +runfile: +The name of the file that Visual Studio 7 and later +will run and debug. +This appears as the value of the +Output +field in the resutling Visual Studio project file. +If this is not specified, +the default is the same as the specified +buildtarget +value. + +Note that because &SCons; always executes its build commands +from the directory in which the &SConstruct; file is located, +if you generate a project file in a different directory +than the &SConstruct; directory, +users will not be able to double-click +on the file name in compilation error messages +displayed in the Visual Studio console output window. +This can be remedied by adding the +Visual C/C++ +.B /FC +compiler option to the &cv-link-CCFLAGS; variable +so that the compiler will print +the full path name of any +files that cause compilation errors. + +Example usage: + + +barsrcs = ['bar.cpp'], +barincs = ['bar.h'], +barlocalincs = ['StdAfx.h'] +barresources = ['bar.rc','resource.h'] +barmisc = ['bar_readme.txt'] + +dll = env.SharedLibrary(target = 'bar.dll', + source = barsrcs) + +env.MSVSProject(target = 'Bar' + env['MSVSPROJECTSUFFIX'], + srcs = barsrcs, + incs = barincs, + localincs = barlocalincs, + resources = barresources, + misc = barmisc, + buildtarget = dll, + variant = 'Release') + + + + + + +Builds a Microsoft Visual Studio solution file. + +This builds a Visual Studio solution file, +based on the version of Visual Studio that is configured +(either the latest installed version, +or the version specified by +&cv-link-MSVS_VERSION; +in the construction environment). +For Visual Studio 6, it will generate a +.dsw +file. +For Visual Studio 7 (.NET), it will +generate a +.sln +file. + +The following values must be specified: + +target: +The name of the target .dsw or .sln file. The correct +suffix for the version of Visual Studio must be used, but the value +&cv-link-MSVSSOLUTIONSUFFIX; +will be defined to the correct value (see example below). + +variant: +The name of this particular variant, or a list of variant +names (the latter is only supported for MSVS 7 solutions). These are +typically things like "Debug" or "Release", but really can be anything +you want. For MSVS 7 they may also specify target platform, like this +"Debug|Xbox". Default platform is Win32. + +projects: +A list of project file names, or Project nodes returned by calls to the +&b-MSVSProject; +Builder, +to be placed into the solution file. +It should be noted that these file names are NOT added to the $SOURCES +environment variable in form of files, but rather as strings. This +is because they represent file names to be added to the solution file, +not the source files used to build the solution file. + +(NOTE: Currently only one project is supported per solution.) + +Example Usage: + + +env.MSVSSolution(target = 'Bar' + env['MSVSSOLUTIONSUFFIX'], + projects = ['bar' + env['MSVSPROJECTSUFFIX']], + variant = 'Release') + + + + + + +When the Microsoft Visual Studio tools are initialized, they set up +this dictionary with the following keys: + +VERSION: +the version of MSVS being used (can be set via +&cv-link-MSVS_VERSION;) + +VERSIONS: +the available versions of MSVS installed + +VCINSTALLDIR: +installed directory of Visual C++ + +VSINSTALLDIR: +installed directory of Visual Studio + +FRAMEWORKDIR: +installed directory of the .NET framework + +FRAMEWORKVERSIONS: +list of installed versions of the .NET framework, sorted latest to oldest. + +FRAMEWORKVERSION: +latest installed version of the .NET framework + +FRAMEWORKSDKDIR: +installed location of the .NET SDK. + +PLATFORMSDKDIR: +installed location of the Platform SDK. + +PLATFORMSDK_MODULES: +dictionary of installed Platform SDK modules, +where the dictionary keys are keywords for the various modules, and +the values are 2-tuples where the first is the release date, and the +second is the version number. + +If a value isn't set, it wasn't available in the registry. + + + + + +Sets the architecture for which the generated project(s) should build. + +The default value is x86. +amd64 is also supported +by &SCons; for some Visual Studio versions. +Trying to set &cv-MSVS_ARCH; to an architecture that's not +supported for a given Visual Studio version +will generate an error. + + + + + +Tells the MS Visual Studio tools to use minimal INCLUDE, LIB, and PATH settings, +instead of the settings from the IDE. + +For Visual Studio, SCons will (by default) automatically determine +where MSVS is installed, and use the LIB, INCLUDE, and PATH variables +set by the IDE. You can override this behavior by setting these +variables after Environment initialization, or by setting +MSVS_IGNORE_IDE_PATHS = 1 +in the Environment initialization. +Specifying this will not leave these unset, but will set them to a +minimal set of paths needed to run the tools successfully. + +For VS6, the mininimal set is: + + INCLUDE:'<VSDir>\VC98\ATL\include;<VSDir>\VC98\MFC\include;<VSDir>\VC98\include' + LIB:'<VSDir>\VC98\MFC\lib;<VSDir>\VC98\lib' + PATH:'<VSDir>\Common\MSDev98\bin;<VSDir>\VC98\bin' + +For VS7, it is: + + INCLUDE:'<VSDir>\Vc7\atlmfc\include;<VSDir>\Vc7\include' + LIB:'<VSDir>\Vc7\atlmfc\lib;<VSDir>\Vc7\lib' + PATH:'<VSDir>\Common7\Tools\bin;<VSDir>\Common7\Tools;<VSDir>\Vc7\bin' + + +Where '<VSDir>' is the installed location of Visual Studio. + + + + + +The string +placed in a generated Microsoft Visual Studio solution file +as the value of the +SccProjectFilePathRelativizedFromConnection0 +and +SccProjectFilePathRelativizedFromConnection1 +attributes of the +GlobalSection(SourceCodeControl) +section. +There is no default value. + + + + + +The string +placed in a generated Microsoft Visual Studio project file +as the value of the +ProjectGUID +attribute. +The string is also placed in the +SolutionUniqueID +attribute of the +GlobalSection(SourceCodeControl) +section of the Microsoft Visual Studio solution file. +There is no default value. + + + + + +The path name +placed in a generated Microsoft Visual Studio project file +as the value of the +SccAuxPath +attribute +if the +MSVS_SCC_PROVIDER +construction variable is also set. +There is no default value. + + + + + +The path name +placed in a generated Microsoft Visual Studio project file +as the value of the +SccLocalPath +attribute +if the +MSVS_SCC_PROVIDER +construction variable is also set. +The path name is also placed in the +SccLocalPath0 +and +SccLocalPath1 +attributes of the +GlobalSection(SourceCodeControl) +section of the Microsoft Visual Studio solution file. +There is no default value. + + + + + +The project name +placed in a generated Microsoft Visual Studio project file +as the value of the +SccProjectName +attribute. +There is no default value. + + + + + +The string +placed in a generated Microsoft Visual Studio project file +as the value of the +SccProvider +attribute. +The string is also placed in the +SccProvider1 +attribute of the +GlobalSection(SourceCodeControl) +section of the Microsoft Visual Studio solution file. +There is no default value. + + + + + +Tells the MS Visual Studio tool(s) to use +the MFC directories in its default paths +for compiling and linking. +The &cv-MSVS_USE_MFC_DIRS; variable has no effect if the +INCLUDE +or +LIB +environment variables are set explictly. + +Under Visual Studio version 6, +setting +&cv-MSVS_USE_MFC_DIRS; +to a non-zero value +adds the +ATL\include +and +MFC\include +directories to +the default +INCLUDE +external environment variable, +and adds the +MFC\lib +directory to +the default +LIB +external environment variable. + +Under Visual Studio version 7, +setting +&cv-MSVS_USE_MFC_DIRS; +to a non-zero value +adds the +atlmfc\include +directory to the default +INCLUDE +external environment variable, +and adds the +atlmfc\lib +directory to the default +LIB +external environment variable. + +Under Visual Studio version 8, +setting +&cv-MSVS_USE_MFC_DIRS; +to a non-zero value will, +by default, +add the +atlmfc\include +directory to the default +INCLUDE +external environment variable, +and the +atlmfc\lib +directory to the default +LIB +external environment variable. +If, however, the +['MSVS']['PLATFORMSDKDIR'] +variable is set, +then the +mfc +and the +atl +subdirectories of the +PLATFORMSDKDIR +are added to the default value of the +INCLUDE +external environment variable, +and the default value of the +LIB +external environment variable is left untouched. + + + + + +Sets the preferred version of Microsoft Visual Studio to use. + +If &cv-MSVS_VERSION; is not set, +&SCons; will (by default) select the latest version +of Visual Studio installed on your system. +So, if you have version 6 and version 7 (MSVS .NET) installed, +it will prefer version 7. +You can override this by +specifying the +MSVS_VERSION +variable in the Environment initialization, setting it to the +appropriate version ('6.0' or '7.0', for example). +If the specified version isn't installed, +tool initialization will fail. + +This is obsolete: use &cv-MSVC_VERSION; instead. If &cv-MSVS_VERSION; is set and +&cv-MSVC_VERSION; is not, &cv-MSVC_VERSION; will be set automatically to &cv-MSVS_VERSION;. +If both are set to different values, scons will raise an error. + + + + + +The build command line placed in +a generated Microsoft Visual Studio project file. +The default is to have Visual Studio invoke SCons with any specified +build targets. + + + + + +The clean command line placed in +a generated Microsoft Visual Studio project file. +The default is to have Visual Studio invoke SCons with the -c option +to remove any specified targets. + + + + + +The encoding string placed in +a generated Microsoft Visual Studio project file. +The default is encoding +Windows-1252. + + + + + +The action used to generate Microsoft Visual Studio project files. + + + + + +The suffix used for Microsoft Visual Studio project (DSP) files. +The default value is +.vcproj +when using Visual Studio version 7.x (.NET) +or later version, +and +.dsp +when using earlier versions of Visual Studio. + + + + + +The rebuild command line placed in +a generated Microsoft Visual Studio project file. +The default is to have Visual Studio invoke SCons with any specified +rebuild targets. + + + + + +The SCons used in generated Microsoft Visual Studio project files. +The default is the version of SCons being +used to generate the project file. + + + + + +The SCons flags used in generated Microsoft Visual Studio +project files. + + + + + +The default SCons command used in generated Microsoft Visual Studio +project files. + + + + + +The sconscript file +(that is, +&SConstruct; +or +&SConscript; +file) +that will be invoked by Visual Studio +project files +(through the +&cv-link-MSVSSCONSCOM; +variable). +The default is the same sconscript file +that contains the call to +&b-MSVSProject; +to build the project file. + + + + + +The action used to generate Microsoft Visual Studio solution files. + + + + + +The suffix used for Microsoft Visual Studio solution (DSW) files. +The default value is +.sln +when using Visual Studio version 7.x (.NET), +and +.dsw +when using earlier versions of Visual Studio. + + + + + +The (optional) path to the SCons library directory, +initialized from the external environment. +If set, this is used to construct a shorter and more +efficient search path in the +&cv-link-MSVSSCONS; +command line executed +from Microsoft Visual Studio project files. + + diff --git a/src/engine/SCons/Tool/msvsTests.py b/src/engine/SCons/Tool/msvsTests.py new file mode 100644 index 0000000..08066fb --- /dev/null +++ b/src/engine/SCons/Tool/msvsTests.py @@ -0,0 +1,757 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/msvsTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import string +import sys +import TestCmd +import unittest +import copy + +from SCons.Tool.msvs import * +import SCons.Util +import SCons.Warnings + +from SCons.Tool.MSCommon.common import debug + +from SCons.Tool.MSCommon import get_default_version, \ + query_versions + +regdata_6a = string.split(r'''[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\ServicePacks] +"sp3"="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup] +"VsCommonDir"="C:\Program Files\Microsoft Visual Studio\Common" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Developer Network Library - Visual Studio 6.0a] +"ProductDir"="C:\Program Files\Microsoft Visual Studio\MSDN98\98VSa\1033" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++] +"ProductDir"="C:\Program Files\Microsoft Visual Studio\VC98" +''','\n') + +regdata_6b = string.split(r'''[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0] +"InstallDir"="C:\VS6\Common\IDE\IDE98" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\ServicePacks] +"sp5"="" +"latest"=dword:00000005 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup] +"VsCommonDir"="C:\VS6\Common" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Basic] +"ProductDir"="C:\VS6\VB98" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++] +"ProductDir"="C:\VS6\VC98" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio] +"ProductDir"="C:\VS6" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft VSEE Client] +"ProductDir"="C:\VS6\Common\Tools" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Visual Studio 98] +''','\n') + +regdata_7 = string.split(r''' +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0] +"InstallDir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\" +"Source Directories"="C:\Program Files\Microsoft Visual Studio .NET\Vc7\crt\;C:\Program Files\Microsoft Visual Studio .NET\Vc7\atlmfc\src\mfc\;C:\Program Files\Microsoft Visual Studio .NET\Vc7\atlmfc\src\atl\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\CrystalReports] +@="#15007" +"Package"="{F05E92C6-8346-11D3-B4AD-00A0C9B04E7B}" +"ProductDetails"="#15009" +"LogoID"="0" +"PID"="#15008" +"UseInterface"=dword:00000001 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\Visual Basic.NET] +@="" +"DefaultProductAttribute"="VB" +"Package"="{164B10B9-B200-11D0-8C61-00A0C91E29D5}" +"UseInterface"=dword:00000001 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\Visual C#] +@="" +"Package"="{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}" +"UseInterface"=dword:00000001 +"DefaultProductAttribute"="C#" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\VisualC++] +"UseInterface"=dword:00000001 +"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}" +"DefaultProductAttribute"="VC" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup] +"Dbghelp_path"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\" +"dw_dir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\MSDN] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\Msdn\1033\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\Servicing\SKU] +"Visual Studio .NET Professional - English"="{D0610409-7D65-11D5-A54F-0090278A1BB8}" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VB] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\Vb7\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VC] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\Vc7\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VC#] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\VC#\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\Visual Studio .NET Professional - English] +"InstallSuccess"=dword:00000001 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VS] +"EnvironmentDirectory"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\" +"EnvironmentPath"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\devenv.exe" +"VS7EnvironmentLocation"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\devenv.exe" +"MSMDir"="C:\Program Files\Common Files\Merge Modules\" +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\" +"VS7CommonBinDir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\Tools\" +"VS7CommonDir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\" +"VSUpdateDir"="C:\Program Files\Microsoft Visual Studio .NET\Setup\VSUpdate\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VS\BuildNumber] +"1033"="7.0.9466" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VS\Pro] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC\VC_OBJECTS_PLATFORM_INFO] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32] +@="{A54AAE91-30C2-11D3-87BF-A04A4CC10000}" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories] +"Path Dirs"="$(VCInstallDir)bin;$(VSInstallDir)Common7\Tools\bin\prerelease;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;C:\Program Files\HTML Help Workshop\;$(FrameworkSDKDir)bin;$(FrameworkDir)$(FrameworkVersion);C:\perl\bin;C:\cygwin\bin;c:\cygwin\usr\bin;C:\bin;C:\program files\perforce;C:\cygwin\usr\local\bin\i686-pc-cygwin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem" +"Library Dirs"="$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(VCInstallDir)PlatformSDK\lib\prerelease;$(VCInstallDir)PlatformSDK\lib;$(FrameworkSDKDir)lib" +"Include Dirs"="$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(VCInstallDir)PlatformSDK\include\prerelease;$(VCInstallDir)PlatformSDK\include;$(FrameworkSDKDir)include" +"Source Dirs"="$(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src" +"Reference Dirs"="" +''','\n') + +regdata_7_1 = string.split(r''' +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1] +@="" +"Source Directories"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src\;C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\mfc\;C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\atl\" +"ThisVersionSolutionCLSID"="{246C57AE-40DD-4d6b-9E8D-B0F5757BB2A8}" +"ThisVersionDTECLSID"="{8CD2DD97-4EC1-4bc4-9359-89A3EEDD57A6}" +"InstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\" +"CLR Version"="v1.1.4322" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Smart Device Extensions] +"UseInterface"=dword:00000001 +"VS7InstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\" +"VBDeviceInstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VB7\" +"CSharpDeviceInstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Visual Basic.NET] +"UseInterface"=dword:00000001 +"Package"="{164B10B9-B200-11D0-8C61-00A0C91E29D5}" +"DefaultProductAttribute"="VB" +@="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Visual C#] +"DefaultProductAttribute"="C#" +"UseInterface"=dword:00000001 +"Package"="{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}" +@="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Visual JSharp] +@="" +"Package"="{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}" +"UseInterface"=dword:00000001 +"DefaultProductAttribute"="Visual JSharp" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\VisualC++] +"UseInterface"=dword:00000001 +"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}" +"DefaultProductAttribute"="VC" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup] +"Dbghelp_path"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\" +"dw_dir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\CSDPROJ] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\JSHPROJ] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VJ#\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Servicing] +"CurrentSULevel"=dword:00000000 +"CurrentSPLevel"=dword:00000000 +"Server Path"="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Servicing\Package] +"FxSDK"="" +"VB"="" +"VC"="" +"VCS"="" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Servicing\SKU] +"Visual Studio .NET Professional 2003 - English"="{20610409-CA18-41A6-9E21-A93AE82EE7C5}" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VB] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vb7\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VBDPROJ] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vb7\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VC] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VC#] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Visual Studio .NET Professional 2003 - English] +"InstallSuccess"=dword:00000001 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VS] +"EnvironmentDirectory"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\" +"EnvironmentPath"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.exe" +"VS7EnvironmentLocation"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.exe" +"MSMDir"="C:\Program Files\Common Files\Merge Modules\" +"VS7CommonBinDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\" +"VS7CommonDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\" +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\" +"VSUpdateDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Setup\VSUpdate\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VS\BuildNumber] +"1033"="7.1.3088" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VS\Pro] +"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO\Win32] +@="{759354D0-6B42-4705-AFFB-56E34D2BC3D4}" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories] +"Path Dirs"="$(VCInstallDir)bin;$(VSInstallDir)Common7\Tools\bin\prerelease;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;C:\Program Files\HTML Help Workshop\;$(FrameworkSDKDir)bin;$(FrameworkDir)$(FrameworkVersion);C:\Perl\bin\;c:\bin;c:\cygwin\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\Common Files\Avid;C:\Program Files\backburner 2\;C:\Program Files\cvsnt;C:\Program Files\Subversion\bin;C:\Program Files\Common Files\Adobe\AGL;C:\Program Files\HTMLDoc" +"Library Dirs"="$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(VCInstallDir)PlatformSDK\lib\prerelease;$(VCInstallDir)PlatformSDK\lib;$(FrameworkSDKDir)lib" +"Include Dirs"="$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(VCInstallDir)PlatformSDK\include\prerelease;$(VCInstallDir)PlatformSDK\include;$(FrameworkSDKDir)include" +"Source Dirs"="$(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src" +"Reference Dirs"="$(FrameWorkDir)$(FrameWorkVersion)" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO\Win32\ToolDefaultExtensionLists] +"VCCLCompilerTool"="*.cpp;*.cxx;*.cc;*.c" +"VCLinkerTool"="*.obj;*.res;*.lib;*.rsc" +"VCLibrarianTool"="*.obj;*.res;*.lib;*.rsc" +"VCMIDLTool"="*.idl;*.odl" +"VCCustomBuildTool"="*.bat" +"VCResourceCompilerTool"="*.rc" +"VCPreBuildEventTool"="*.bat" +"VCPreLinkEventTool"="*.bat" +"VCPostBuildEventTool"="*.bat" +"VCBscMakeTool"="*.sbr" +"VCNMakeTool"="" +"VCWebServiceProxyGeneratorTool"="*.discomap" +"VCWebDeploymentTool"="" +"VCALinkTool"="*.resources" +"VCManagedResourceCompilerTool"="*.resx" +"VCXMLDataGeneratorTool"="*.xsd" +"VCManagedWrapperGeneratorTool"="" +"VCAuxiliaryManagedWrapperGeneratorTool"="" +"VCPrimaryInteropTool"="" +''','\n') + +regdata_8exp = string.split(r''' +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0] +"CLR Version"="v2.0.50727" +"ApplicationID"="VCExpress" +"SecurityAppID"="{741726F6-1EAE-4680-86A6-6085E8872CF8}" +"InstallDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\" +"EnablePreloadCLR"=dword:00000001 +"RestoreAppPath"=dword:00000001 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\InstalledProducts] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\InstalledProducts\Microsoft Visual C++] +"UseInterface"=dword:00000001 +"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}" +"DefaultProductAttribute"="VC" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\Setup] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\Setup\VC] +"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\VC\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\Setup\VS] +"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC\VC_OBJECTS_PLATFORM_INFO] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32] +@="{72f11281-2429-11d7-8bf6-00b0d03daa06}" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\ToolDefaultExtensionLists] +"VCCLCompilerTool"="*.cpp;*.cxx;*.cc;*.c" +"VCLinkerTool"="*.obj;*.res;*.lib;*.rsc;*.licenses" +"VCLibrarianTool"="*.obj;*.res;*.lib;*.rsc" +"VCMIDLTool"="*.idl;*.odl" +"VCCustomBuildTool"="*.bat" +"VCResourceCompilerTool"="*.rc" +"VCPreBuildEventTool"="*.bat" +"VCPreLinkEventTool"="*.bat" +"VCPostBuildEventTool"="*.bat" +"VCBscMakeTool"="*.sbr" +"VCFxCopTool"="*.dll;*.exe" +"VCNMakeTool"="" +"VCWebServiceProxyGeneratorTool"="*.discomap" +"VCWebDeploymentTool"="" +"VCALinkTool"="*.resources" +"VCManagedResourceCompilerTool"="*.resx" +"VCXMLDataGeneratorTool"="*.xsd" +"VCManifestTool"="*.manifest" +"VCXDCMakeTool"="*.xdc" +''','\n') + +regdata_80 = string.split(r''' +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0] +"CLR Version"="v2.0.50727" +"ApplicationID"="VisualStudio" +"ThisVersionDTECLSID"="{BA018599-1DB3-44f9-83B4-461454C84BF8}" +"ThisVersionSolutionCLSID"="{1B2EEDD6-C203-4d04-BD59-78906E3E8AAB}" +"SecurityAppID"="{DF99D4F5-9F04-4CEF-9D39-095821B49C77}" +"InstallDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\" +"EnablePreloadCLR"=dword:00000001 +"RestoreAppPath"=dword:00000001 +"Source Directories"="C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\;C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\mfc\;C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\atl\;C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\InstalledProducts] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\InstalledProducts\Microsoft Visual C++] +"UseInterface"=dword:00000001 +"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}" +"DefaultProductAttribute"="VC" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup] +"Dbghelp_path"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\EF] +"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\EnterpriseFrameworks\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\Microsoft Visual Studio 2005 Professional Edition - ENU] +"SrcPath"="d:\vs\" +"InstallSuccess"=dword:00000001 +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VC] +"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\VC\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VS] +"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\" +"VS7EnvironmentLocation"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe" +"EnvironmentPath"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe" +"EnvironmentDirectory"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\" +"VS7CommonDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\" +"VS7CommonBinDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VS\BuildNumber] +"1033"="8.0.50727.42" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VS\Pro] +"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC\VC_OBJECTS_PLATFORM_INFO] +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32] +@="{72f11281-2429-11d7-8bf6-00b0d03daa06}" +[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\ToolDefaultExtensionLists] +"VCCLCompilerTool"="*.cpp;*.cxx;*.cc;*.c" +"VCLinkerTool"="*.obj;*.res;*.lib;*.rsc;*.licenses" +"VCLibrarianTool"="*.obj;*.res;*.lib;*.rsc" +"VCMIDLTool"="*.idl;*.odl" +"VCCustomBuildTool"="*.bat" +"VCResourceCompilerTool"="*.rc" +"VCPreBuildEventTool"="*.bat" +"VCPreLinkEventTool"="*.bat" +"VCPostBuildEventTool"="*.bat" +"VCBscMakeTool"="*.sbr" +"VCFxCopTool"="*.dll;*.exe" +"VCNMakeTool"="" +"VCWebServiceProxyGeneratorTool"="*.discomap" +"VCWebDeploymentTool"="" +"VCALinkTool"="*.resources" +"VCManagedResourceCompilerTool"="*.resx" +"VCXMLDataGeneratorTool"="*.xsd" +"VCManifestTool"="*.manifest" +"VCXDCMakeTool"="*.xdc" +''','\n') + +regdata_cv = string.split(r'''[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion] +"ProgramFilesDir"="C:\Program Files" +"CommonFilesDir"="C:\Program Files\Common Files" +"MediaPath"="C:\WINDOWS\Media" +''','\n') + + +regdata_none = [] + +class DummyEnv: + def __init__(self, dict=None): + if dict: + self.dict = dict + else: + self.dict = {} + + def Dictionary(self, key = None): + if not key: + return self.dict + return self.dict[key] + + def __setitem__(self,key,value): + self.dict[key] = value + + def __getitem__(self,key): + return self.dict[key] + + def has_key(self,name): + return self.dict.has_key(name) + +class RegKey: + """key class for storing an 'open' registry key""" + def __init__(self,key): + self.key = key + +# Warning: this is NOT case-insensitive, unlike the Windows registry. +# So e.g. HKLM\Software is NOT the same key as HKLM\SOFTWARE. +class RegNode: + """node in the dummy registry""" + def __init__(self,name): + self.valdict = {} + self.keydict = {} + self.keyarray = [] + self.valarray = [] + self.name = name + + def value(self,val): + if self.valdict.has_key(val): + return (self.valdict[val],1) + else: + raise SCons.Util.RegError + + def addValue(self,name,val): + self.valdict[name] = val + self.valarray.append(name) + + def valindex(self,index): + rv = None + try: + rv = (self.valarray[index],self.valdict[self.valarray[index]],1) + except KeyError: + raise SCons.Util.RegError + return rv + + def key(self,key,sep = '\\'): + if key.find(sep) != -1: + keyname, subkeys = key.split(sep,1) + else: + keyname = key + subkeys = "" + try: + # recurse, and return the lowest level key node + if subkeys: + return self.keydict[keyname].key(subkeys) + else: + return self.keydict[keyname] + except KeyError: + raise SCons.Util.RegError + + def addKey(self,name,sep = '\\'): + if string.find(name, sep) != -1: + keyname, subkeys = string.split(name, sep, 1) + else: + keyname = name + subkeys = "" + + if not self.keydict.has_key(keyname): + self.keydict[keyname] = RegNode(keyname) + self.keyarray.append(keyname) + + # recurse, and return the lowest level key node + if subkeys: + return self.keydict[keyname].addKey(subkeys) + else: + return self.keydict[keyname] + + def keyindex(self,index): + return self.keydict[self.keyarray[index]] + + def __str__(self): + return self._doStr() + + def _doStr(self, indent = ''): + rv = "" + for value in self.valarray: + rv = rv + '%s"%s" = "%s"\n' % (indent, value, self.valdict[value]) + for key in self.keyarray: + rv = rv + "%s%s: {\n"%(indent, key) + rv = rv + self.keydict[key]._doStr(indent + ' ') + rv = rv + indent + '}\n' + return rv + +class DummyRegistry: + """registry class for storing fake registry attributes""" + def __init__(self,data): + """parse input data into the fake registry""" + self.root = RegNode('REGISTRY') + self.root.addKey('HKEY_LOCAL_MACHINE') + self.root.addKey('HKEY_CURRENT_USER') + self.root.addKey('HKEY_USERS') + self.root.addKey('HKEY_CLASSES_ROOT') + + self.parse(data) + + def parse(self, data): + parent = self.root + keymatch = re.compile('^\[(.*)\]$') + valmatch = re.compile('^(?:"(.*)"|[@])="(.*)"$') + for line in data: + m1 = keymatch.match(line) + if m1: + # add a key, set it to current parent + parent = self.root.addKey(m1.group(1)) + else: + m2 = valmatch.match(line) + if m2: + parent.addValue(m2.group(1),m2.group(2)) + + def OpenKeyEx(self,root,key): + if root == SCons.Util.HKEY_CLASSES_ROOT: + mykey = 'HKEY_CLASSES_ROOT\\' + key + if root == SCons.Util.HKEY_USERS: + mykey = 'HKEY_USERS\\' + key + if root == SCons.Util.HKEY_CURRENT_USER: + mykey = 'HKEY_CURRENT_USER\\' + key + if root == SCons.Util.HKEY_LOCAL_MACHINE: + mykey = 'HKEY_LOCAL_MACHINE\\' + key + debug("Open Key:%s"%mykey) + return self.root.key(mykey) + +def DummyOpenKeyEx(root, key): + return registry.OpenKeyEx(root,key) + +def DummyEnumKey(key, index): + rv = None + try: + rv = key.keyarray[index] + except IndexError: + raise SCons.Util.RegError +# print "Enum Key",key.name,"[",index,"] =>",rv + return rv + +def DummyEnumValue(key, index): + rv = key.valindex(index) +# print "Enum Value",key.name,"[",index,"] =>",rv + return rv + +def DummyQueryValue(key, value): + rv = key.value(value) +# print "Query Value",key.name+"\\"+value,"=>",rv + return rv + +def DummyExists(path): + return 1 + +class msvsTestCase(unittest.TestCase): + """This test case is run several times with different defaults. + See its subclasses below.""" + def setUp(self): + debug("THIS TYPE :%s"%self) + global registry + registry = self.registry + from SCons.Tool.MSCommon.vs import reset_installed_visual_studios + reset_installed_visual_studios() + + def test_get_default_version(self): + """Test retrieval of the default visual studio version""" + + debug("Testing for default version %s"%self.default_version) + env = DummyEnv() + v1 = get_default_version(env) + assert env['MSVS_VERSION'] == self.default_version, \ + ("env['MSVS_VERSION'] != self.default_version",self.default_version, env['MSVS_VERSION']) + assert env['MSVS']['VERSION'] == self.default_version, \ + ("env['MSVS']['VERSION'] != self.default_version",self.default_version, env['MSVS']['VERSION']) + assert v1 == self.default_version, (self.default_version, v1) + + env = DummyEnv({'MSVS_VERSION':'7.0'}) + v2 = get_default_version(env) + assert env['MSVS_VERSION'] == '7.0', env['MSVS_VERSION'] + assert env['MSVS']['VERSION'] == '7.0', env['MSVS']['VERSION'] + assert v2 == '7.0', v2 + + env = DummyEnv() + v3 = get_default_version(env) + if v3 == '7.1': + override = '7.0' + else: + override = '7.1' + env['MSVS_VERSION'] = override + v3 = get_default_version(env) + assert env['MSVS_VERSION'] == override, env['MSVS_VERSION'] + assert env['MSVS']['VERSION'] == override, env['MSVS']['VERSION'] + assert v3 == override, v3 + + def _TODO_test_merge_default_version(self): + """Test the merge_default_version() function""" + pass + + def test_query_versions(self): + """Test retrieval of the list of visual studio versions""" + v1 = query_versions() + assert not v1 or str(v1[0]) == self.highest_version, \ + (v1, self.highest_version) + assert len(v1) == self.number_of_versions, v1 + +class msvs6aTestCase(msvsTestCase): + """Test MSVS 6 Registry""" + registry = DummyRegistry(regdata_6a + regdata_cv) + default_version = '6.0' + highest_version = '6.0' + number_of_versions = 1 + install_locs = { + '6.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98\\Bin'}, + '7.0' : {}, + '7.1' : {}, + '8.0' : {}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['6.0'] + +class msvs6bTestCase(msvsTestCase): + """Test Other MSVS 6 Registry""" + registry = DummyRegistry(regdata_6b + regdata_cv) + default_version = '6.0' + highest_version = '6.0' + number_of_versions = 1 + install_locs = { + '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, + '7.0' : {}, + '7.1' : {}, + '8.0' : {}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['6.0'] + +class msvs6and7TestCase(msvsTestCase): + """Test MSVS 6 & 7 Registry""" + registry = DummyRegistry(regdata_6b + regdata_7 + regdata_cv) + default_version = '7.0' + highest_version = '7.0' + number_of_versions = 2 + install_locs = { + '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', + 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'}, + '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7', + 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'}, + '7.1' : {}, + '8.0' : {}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['7.0'] + +class msvs7TestCase(msvsTestCase): + """Test MSVS 7 Registry""" + registry = DummyRegistry(regdata_7 + regdata_cv) + default_version = '7.0' + highest_version = '7.0' + number_of_versions = 1 + install_locs = { + '6.0' : {}, + '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7', + 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'}, + '7.1' : {}, + '8.0' : {}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['7.0'] + +class msvs71TestCase(msvsTestCase): + """Test MSVS 7.1 Registry""" + registry = DummyRegistry(regdata_7_1 + regdata_cv) + default_version = '7.1' + highest_version = '7.1' + number_of_versions = 1 + install_locs = { + '6.0' : {}, + '7.0' : {}, + '7.1' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7', + 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7\\Tools'}, + '8.0' : {}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['7.1'] + +class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working + """Test MSVS 8 Express Registry""" + registry = DummyRegistry(regdata_8exp + regdata_cv) + default_version = '8.0Exp' + highest_version = '8.0Exp' + number_of_versions = 1 + install_locs = { + '6.0' : {}, + '7.0' : {}, + '7.1' : {}, + '8.0' : {}, + '8.0Exp' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8', + 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8\\VC'}, + } + default_install_loc = install_locs['8.0Exp'] + +class msvs80TestCase(msvsTestCase): + """Test MSVS 8 Registry""" + registry = DummyRegistry(regdata_80 + regdata_cv) + default_version = '8.0' + highest_version = '8.0' + number_of_versions = 1 + install_locs = { + '6.0' : {}, + '7.0' : {}, + '7.1' : {}, + '8.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8', + 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8\\VC'}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['8.0'] + +class msvsEmptyTestCase(msvsTestCase): + """Test Empty Registry""" + registry = DummyRegistry(regdata_none) + default_version = '9.0' + highest_version = None + number_of_versions = 0 + install_locs = { + '6.0' : {}, + '7.0' : {}, + '7.1' : {}, + '8.0' : {}, + '8.0Exp' : {}, + } + default_install_loc = install_locs['8.0Exp'] + +if __name__ == "__main__": + + # only makes sense to test this on win32 + if sys.platform != 'win32': + sys.stdout.write("NO RESULT for msvsTests.py: '%s' is not win32\n" % sys.platform) + sys.exit(0) + + SCons.Util.RegOpenKeyEx = DummyOpenKeyEx + SCons.Util.RegEnumKey = DummyEnumKey + SCons.Util.RegEnumValue = DummyEnumValue + SCons.Util.RegQueryValueEx = DummyQueryValue + + os.path.exists = DummyExists # make sure all files exist :-) + os.path.isfile = DummyExists # make sure all files are files :-) + os.path.isdir = DummyExists # make sure all dirs are dirs :-) + + exit_val = 0 + + test_classes = [ + msvs6aTestCase, + msvs6bTestCase, + msvs6and7TestCase, + msvs7TestCase, + msvs71TestCase, + msvs8ExpTestCase, + msvs80TestCase, + msvsEmptyTestCase, + ] + + for test_class in test_classes: + print "TEST: ", test_class.__doc__ + back_osenv = copy.deepcopy(os.environ) + try: + # XXX: overriding the os.environ is bad, but doing it + # correctly is too complicated for now. Those tests should + # be fixed + for k in ['VS71COMNTOOLS', + 'VS80COMNTOOLS', + 'VS90COMNTOOLS']: + if os.environ.has_key(k): + del os.environ[k] + + suite = unittest.makeSuite(test_class, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + exit_val = 1 + finally: + os.env = back_osenv + + sys.exit(exit_val) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mwcc.py b/src/engine/SCons/Tool/mwcc.py new file mode 100644 index 0000000..885f009 --- /dev/null +++ b/src/engine/SCons/Tool/mwcc.py @@ -0,0 +1,208 @@ +"""SCons.Tool.mwcc + +Tool-specific initialization for the Metrowerks CodeWarrior compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/mwcc.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string + +import SCons.Util + +def set_vars(env): + """Set MWCW_VERSION, MWCW_VERSIONS, and some codewarrior environment vars + + MWCW_VERSIONS is set to a list of objects representing installed versions + + MWCW_VERSION is set to the version object that will be used for building. + MWCW_VERSION can be set to a string during Environment + construction to influence which version is chosen, otherwise + the latest one from MWCW_VERSIONS is used. + + Returns true if at least one version is found, false otherwise + """ + desired = env.get('MWCW_VERSION', '') + + # return right away if the variables are already set + if isinstance(desired, MWVersion): + return 1 + elif desired is None: + return 0 + + versions = find_versions() + version = None + + if desired: + for v in versions: + if str(v) == desired: + version = v + elif versions: + version = versions[-1] + + env['MWCW_VERSIONS'] = versions + env['MWCW_VERSION'] = version + + if version is None: + return 0 + + env.PrependENVPath('PATH', version.clpath) + env.PrependENVPath('PATH', version.dllpath) + ENV = env['ENV'] + ENV['CWFolder'] = version.path + ENV['LM_LICENSE_FILE'] = version.license + plus = lambda x: '+%s' % x + ENV['MWCIncludes'] = string.join(map(plus, version.includes), os.pathsep) + ENV['MWLibraries'] = string.join(map(plus, version.libs), os.pathsep) + return 1 + + +def find_versions(): + """Return a list of MWVersion objects representing installed versions""" + versions = [] + + ### This function finds CodeWarrior by reading from the registry on + ### Windows. Some other method needs to be implemented for other + ### platforms, maybe something that calls env.WhereIs('mwcc') + + if SCons.Util.can_read_reg: + try: + HLM = SCons.Util.HKEY_LOCAL_MACHINE + product = 'SOFTWARE\\Metrowerks\\CodeWarrior\\Product Versions' + product_key = SCons.Util.RegOpenKeyEx(HLM, product) + + i = 0 + while 1: + name = product + '\\' + SCons.Util.RegEnumKey(product_key, i) + name_key = SCons.Util.RegOpenKeyEx(HLM, name) + + try: + version = SCons.Util.RegQueryValueEx(name_key, 'VERSION') + path = SCons.Util.RegQueryValueEx(name_key, 'PATH') + mwv = MWVersion(version[0], path[0], 'Win32-X86') + versions.append(mwv) + except SCons.Util.RegError: + pass + + i = i + 1 + + except SCons.Util.RegError: + pass + + return versions + + +class MWVersion: + def __init__(self, version, path, platform): + self.version = version + self.path = path + self.platform = platform + self.clpath = os.path.join(path, 'Other Metrowerks Tools', + 'Command Line Tools') + self.dllpath = os.path.join(path, 'Bin') + + # The Metrowerks tools don't store any configuration data so they + # are totally dumb when it comes to locating standard headers, + # libraries, and other files, expecting all the information + # to be handed to them in environment variables. The members set + # below control what information scons injects into the environment + + ### The paths below give a normal build environment in CodeWarrior for + ### Windows, other versions of CodeWarrior might need different paths. + + msl = os.path.join(path, 'MSL') + support = os.path.join(path, '%s Support' % platform) + + self.license = os.path.join(path, 'license.dat') + self.includes = [msl, support] + self.libs = [msl, support] + + def __str__(self): + return self.version + + +CSuffixes = ['.c', '.C'] +CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++'] + + +def generate(env): + """Add Builders and construction variables for the mwcc to an Environment.""" + import SCons.Defaults + import SCons.Tool + + set_vars(env) + + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in CSuffixes: + static_obj.add_action(suffix, SCons.Defaults.CAction) + shared_obj.add_action(suffix, SCons.Defaults.ShCAction) + + for suffix in CXXSuffixes: + static_obj.add_action(suffix, SCons.Defaults.CXXAction) + shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction) + + env['CCCOMFLAGS'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -nolink -o $TARGET $SOURCES' + + env['CC'] = 'mwcc' + env['CCCOM'] = '$CC $CFLAGS $CCFLAGS $CCCOMFLAGS' + + env['CXX'] = 'mwcc' + env['CXXCOM'] = '$CXX $CXXFLAGS $CCCOMFLAGS' + + env['SHCC'] = '$CC' + env['SHCCFLAGS'] = '$CCFLAGS' + env['SHCFLAGS'] = '$CFLAGS' + env['SHCCCOM'] = '$SHCC $SHCFLAGS $SHCCFLAGS $CCCOMFLAGS' + + env['SHCXX'] = '$CXX' + env['SHCXXFLAGS'] = '$CXXFLAGS' + env['SHCXXCOM'] = '$SHCXX $SHCXXFLAGS $CCCOMFLAGS' + + env['CFILESUFFIX'] = '.c' + env['CXXFILESUFFIX'] = '.cpp' + env['CPPDEFPREFIX'] = '-D' + env['CPPDEFSUFFIX'] = '' + env['INCPREFIX'] = '-I' + env['INCSUFFIX'] = '' + + #env['PCH'] = ? + #env['PCHSTOP'] = ? + + +def exists(env): + return set_vars(env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mwcc.xml b/src/engine/SCons/Tool/mwcc.xml new file mode 100644 index 0000000..e7df1f3 --- /dev/null +++ b/src/engine/SCons/Tool/mwcc.xml @@ -0,0 +1,53 @@ + + + +Sets construction variables for the Metrowerks CodeWarrior compiler. + + +MWCW_VERSIONS +MWCW_VERSION + +CC +CCCOM +CXX +CXXCOM +SHCC +SHCCFLAGS +SHCFLAGS +SHCCCOM +SHCXX +SHCXXFLAGS +SHCXXCOM +CFILESUFFIX +CXXFILESUFFIX +CPPDEFPREFIX +CPPDEFSUFFIX +INCPREFIX +INCSUFFIX + + +CCCOMSTR +CXXCOMSTR +SHCCCOMSTR +SHCXXCOMSTR + + + + + +The version number of the MetroWerks CodeWarrior C compiler +to be used. + + + + + +A list of installed versions of the MetroWerks CodeWarrior C compiler +on this system. + + diff --git a/src/engine/SCons/Tool/mwld.py b/src/engine/SCons/Tool/mwld.py new file mode 100644 index 0000000..e0e1484 --- /dev/null +++ b/src/engine/SCons/Tool/mwld.py @@ -0,0 +1,107 @@ +"""SCons.Tool.mwld + +Tool-specific initialization for the Metrowerks CodeWarrior linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/mwld.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Tool + + +def generate(env): + """Add Builders and construction variables for lib to an Environment.""" + SCons.Tool.createStaticLibBuilder(env) + SCons.Tool.createSharedLibBuilder(env) + SCons.Tool.createProgBuilder(env) + + env['AR'] = 'mwld' + env['ARCOM'] = '$AR $ARFLAGS -library -o $TARGET $SOURCES' + + env['LIBDIRPREFIX'] = '-L' + env['LIBDIRSUFFIX'] = '' + env['LIBLINKPREFIX'] = '-l' + env['LIBLINKSUFFIX'] = '.lib' + + env['LINK'] = 'mwld' + env['LINKCOM'] = '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + + env['SHLINK'] = '$LINK' + env['SHLINKFLAGS'] = '$LINKFLAGS' + env['SHLINKCOM'] = shlib_action + env['SHLIBEMITTER']= shlib_emitter + + +def exists(env): + import SCons.Tool.mwcc + return SCons.Tool.mwcc.set_vars(env) + + +def shlib_generator(target, source, env, for_signature): + cmd = ['$SHLINK', '$SHLINKFLAGS', '-shared'] + + no_import_lib = env.get('no_import_lib', 0) + if no_import_lib: cmd.extend('-noimplib') + + dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX') + if dll: cmd.extend(['-o', dll]) + + implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX') + if implib: cmd.extend(['-implib', implib.get_string(for_signature)]) + + cmd.extend(['$SOURCES', '$_LIBDIRFLAGS', '$_LIBFLAGS']) + + return [cmd] + + +def shlib_emitter(target, source, env): + dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX') + no_import_lib = env.get('no_import_lib', 0) + + if not dll: + raise SCons.Errors.UserError, "A shared library should have exactly one target with the suffix: %s" % env.subst("$SHLIBSUFFIX") + + if not no_import_lib and \ + not env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX'): + + # Append an import library to the list of targets. + target.append(env.ReplaceIxes(dll, + 'SHLIBPREFIX', 'SHLIBSUFFIX', + 'LIBPREFIX', 'LIBSUFFIX')) + + return target, source + + +shlib_action = SCons.Action.Action(shlib_generator, generator=1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/mwld.xml b/src/engine/SCons/Tool/mwld.xml new file mode 100644 index 0000000..368d658 --- /dev/null +++ b/src/engine/SCons/Tool/mwld.xml @@ -0,0 +1,27 @@ + + + +Sets construction variables for the Metrowerks CodeWarrior linker. + + +AR +ARCOM +LIBDIRPREFIX +LIBDIRSUFFIX +LIBLINKPREFIX +LIBLINKSUFFIX +LINK +LINKCOM +SHLINK +SHLINKFLAGS +SHLINKCOM + + + + + diff --git a/src/engine/SCons/Tool/nasm.py b/src/engine/SCons/Tool/nasm.py new file mode 100644 index 0000000..16ef193 --- /dev/null +++ b/src/engine/SCons/Tool/nasm.py @@ -0,0 +1,72 @@ +"""SCons.Tool.nasm + +Tool-specific initialization for nasm, the famous Netwide Assembler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/nasm.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +ASSuffixes = ['.s', '.asm', '.ASM'] +ASPPSuffixes = ['.spp', '.SPP', '.sx'] +if SCons.Util.case_sensitive_suffixes('.s', '.S'): + ASPPSuffixes.extend(['.S']) +else: + ASSuffixes.extend(['.S']) + +def generate(env): + """Add Builders and construction variables for nasm to an Environment.""" + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + + for suffix in ASSuffixes: + static_obj.add_action(suffix, SCons.Defaults.ASAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + + for suffix in ASPPSuffixes: + static_obj.add_action(suffix, SCons.Defaults.ASPPAction) + static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter) + + env['AS'] = 'nasm' + env['ASFLAGS'] = SCons.Util.CLVar('') + env['ASPPFLAGS'] = '$ASFLAGS' + env['ASCOM'] = '$AS $ASFLAGS -o $TARGET $SOURCES' + env['ASPPCOM'] = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES' + +def exists(env): + return env.Detect('nasm') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/nasm.xml b/src/engine/SCons/Tool/nasm.xml new file mode 100644 index 0000000..725c186 --- /dev/null +++ b/src/engine/SCons/Tool/nasm.xml @@ -0,0 +1,23 @@ + + + +Sets construction variables for the +nasm Netwide Assembler. + + +AS +ASFLAGS +ASPPFLAGS +ASCOM +ASPPCOM + + +ASCOMSTR +ASPPCOMSTR + + diff --git a/src/engine/SCons/Tool/packaging.xml b/src/engine/SCons/Tool/packaging.xml new file mode 100644 index 0000000..0e36128 --- /dev/null +++ b/src/engine/SCons/Tool/packaging.xml @@ -0,0 +1,73 @@ + + + +A framework for building binary and source packages. + + + + + +Builds a Binary Package of the given source files. + + +env.Package(source = FindInstalledFiles()) + + + + + + +The Java archive tool. + + + + + +The directory to which the Java archive tool should change +(using the + +option). + + + + + +The command line used to call the Java archive tool. + + + + + +The string displayed when the Java archive tool +is called +If this is not set, then &cv-JARCOM; (the command line) is displayed. + + +env = Environment(JARCOMSTR = "JARchiving $SOURCES into $TARGET") + + + + + + +General options passed to the Java archive tool. +By default this is set to + +to create the necessary +jar +file. + + + + + +The suffix for Java archives: +.jar +by default. + + diff --git a/src/engine/SCons/Tool/packaging/__init__.py b/src/engine/SCons/Tool/packaging/__init__.py new file mode 100644 index 0000000..90947e4 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/__init__.py @@ -0,0 +1,314 @@ +"""SCons.Tool.Packaging + +SCons Packaging Tool. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/__init__.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Environment +from SCons.Variables import * +from SCons.Errors import * +from SCons.Util import is_List, make_path_relative +from SCons.Warnings import warn, Warning + +import os, imp +import SCons.Defaults + +__all__ = [ 'src_targz', 'src_tarbz2', 'src_zip', 'tarbz2', 'targz', 'zip', 'rpm', 'msi', 'ipk' ] + +# +# Utility and Builder function +# +def Tag(env, target, source, *more_tags, **kw_tags): + """ Tag a file with the given arguments, just sets the accordingly named + attribute on the file object. + + TODO: FIXME + """ + if not target: + target=source + first_tag=None + else: + first_tag=source + + if first_tag: + kw_tags[first_tag[0]] = '' + + if len(kw_tags) == 0 and len(more_tags) == 0: + raise UserError, "No tags given." + + # XXX: sanity checks + for x in more_tags: + kw_tags[x] = '' + + if not SCons.Util.is_List(target): + target=[target] + else: + # hmm, sometimes the target list, is a list of a list + # make sure it is flattened prior to processing. + # TODO: perhaps some bug ?!? + target=env.Flatten(target) + + for t in target: + for (k,v) in kw_tags.items(): + # all file tags have to start with PACKAGING_, so we can later + # differentiate between "normal" object attributes and the + # packaging attributes. As the user should not be bothered with + # that, the prefix will be added here if missing. + #if not k.startswith('PACKAGING_'): + if k[:10] != 'PACKAGING_': + k='PACKAGING_'+k + setattr(t, k, v) + +def Package(env, target=None, source=None, **kw): + """ Entry point for the package tool. + """ + # check if we need to find the source files ourself + if not source: + source = env.FindInstalledFiles() + + if len(source)==0: + raise UserError, "No source for Package() given" + + # decide which types of packages shall be built. Can be defined through + # four mechanisms: command line argument, keyword argument, + # environment argument and default selection( zip or tar.gz ) in that + # order. + try: kw['PACKAGETYPE']=env['PACKAGETYPE'] + except KeyError: pass + + if not kw.get('PACKAGETYPE'): + from SCons.Script import GetOption + kw['PACKAGETYPE'] = GetOption('package_type') + + if kw['PACKAGETYPE'] == None: + if env['BUILDERS'].has_key('Tar'): + kw['PACKAGETYPE']='targz' + elif env['BUILDERS'].has_key('Zip'): + kw['PACKAGETYPE']='zip' + else: + raise UserError, "No type for Package() given" + + PACKAGETYPE=kw['PACKAGETYPE'] + if not is_List(PACKAGETYPE): + PACKAGETYPE=string.split(PACKAGETYPE, ',') + + # load the needed packagers. + def load_packager(type): + try: + file,path,desc=imp.find_module(type, __path__) + return imp.load_module(type, file, path, desc) + except ImportError, e: + raise EnvironmentError("packager %s not available: %s"%(type,str(e))) + + packagers=map(load_packager, PACKAGETYPE) + + # set up targets and the PACKAGEROOT + try: + # fill up the target list with a default target name until the PACKAGETYPE + # list is of the same size as the target list. + if not target: target = [] + + size_diff = len(PACKAGETYPE)-len(target) + default_name = "%(NAME)s-%(VERSION)s" + + if size_diff>0: + default_target = default_name%kw + target.extend( [default_target]*size_diff ) + + if not kw.has_key('PACKAGEROOT'): + kw['PACKAGEROOT'] = default_name%kw + + except KeyError, e: + raise SCons.Errors.UserError( "Missing Packagetag '%s'"%e.args[0] ) + + # setup the source files + source=env.arg2nodes(source, env.fs.Entry) + + # call the packager to setup the dependencies. + targets=[] + try: + for packager in packagers: + t=[target.pop(0)] + t=apply(packager.package, [env,t,source], kw) + targets.extend(t) + + assert( len(target) == 0 ) + + except KeyError, e: + raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\ + % (e.args[0],packager.__name__) ) + except TypeError, e: + # this exception means that a needed argument for the packager is + # missing. As our packagers get their "tags" as named function + # arguments we need to find out which one is missing. + from inspect import getargspec + args,varargs,varkw,defaults=getargspec(packager.package) + if defaults!=None: + args=args[:-len(defaults)] # throw away arguments with default values + args.remove('env') + args.remove('target') + args.remove('source') + # now remove any args for which we have a value in kw. + #args=[x for x in args if not kw.has_key(x)] + args=filter(lambda x, kw=kw: not kw.has_key(x), args) + + if len(args)==0: + raise # must be a different error, so reraise + elif len(args)==1: + raise SCons.Errors.UserError( "Missing Packagetag '%s' for %s packager"\ + % (args[0],packager.__name__) ) + else: + raise SCons.Errors.UserError( "Missing Packagetags '%s' for %s packager"\ + % (", ".join(args),packager.__name__) ) + + target=env.arg2nodes(target, env.fs.Entry) + targets.extend(env.Alias( 'package', targets )) + return targets + +# +# SCons tool initialization functions +# + +added = None + +def generate(env): + from SCons.Script import AddOption + global added + if not added: + added = 1 + AddOption('--package-type', + dest='package_type', + default=None, + type="string", + action="store", + help='The type of package to create.') + + try: + env['BUILDERS']['Package'] + env['BUILDERS']['Tag'] + except KeyError: + env['BUILDERS']['Package'] = Package + env['BUILDERS']['Tag'] = Tag + +def exists(env): + return 1 + +# XXX +def options(opts): + opts.AddVariables( + EnumVariable( 'PACKAGETYPE', + 'the type of package to create.', + None, allowed_values=map( str, __all__ ), + ignorecase=2 + ) + ) + +# +# Internal utility functions +# + +def copy_attr(f1, f2): + """ copies the special packaging file attributes from f1 to f2. + """ + #pattrs = [x for x in dir(f1) if not hasattr(f2, x) and\ + # x.startswith('PACKAGING_')] + copyit = lambda x, f2=f2: not hasattr(f2, x) and x[:10] == 'PACKAGING_' + pattrs = filter(copyit, dir(f1)) + for attr in pattrs: + setattr(f2, attr, getattr(f1, attr)) +def putintopackageroot(target, source, env, pkgroot, honor_install_location=1): + """ Uses the CopyAs builder to copy all source files to the directory given + in pkgroot. + + If honor_install_location is set and the copied source file has an + PACKAGING_INSTALL_LOCATION attribute, the PACKAGING_INSTALL_LOCATION is + used as the new name of the source file under pkgroot. + + The source file will not be copied if it is already under the the pkgroot + directory. + + All attributes of the source file will be copied to the new file. + """ + # make sure the packageroot is a Dir object. + if SCons.Util.is_String(pkgroot): pkgroot=env.Dir(pkgroot) + if not SCons.Util.is_List(source): source=[source] + + new_source = [] + for file in source: + if SCons.Util.is_String(file): file = env.File(file) + + if file.is_under(pkgroot): + new_source.append(file) + else: + if hasattr(file, 'PACKAGING_INSTALL_LOCATION') and\ + honor_install_location: + new_name=make_path_relative(file.PACKAGING_INSTALL_LOCATION) + else: + new_name=make_path_relative(file.get_path()) + + new_file=pkgroot.File(new_name) + new_file=env.CopyAs(new_file, file)[0] + copy_attr(file, new_file) + new_source.append(new_file) + + return (target, new_source) + +def stripinstallbuilder(target, source, env): + """ strips the install builder action from the source list and stores + the final installation location as the "PACKAGING_INSTALL_LOCATION" of + the source of the source file. This effectively removes the final installed + files from the source list while remembering the installation location. + + It also warns about files which have no install builder attached. + """ + def has_no_install_location(file): + return not (file.has_builder() and\ + hasattr(file.builder, 'name') and\ + (file.builder.name=="InstallBuilder" or\ + file.builder.name=="InstallAsBuilder")) + + if len(filter(has_no_install_location, source)): + warn(Warning, "there are files to package which have no\ + InstallBuilder attached, this might lead to irreproducible packages") + + n_source=[] + for s in source: + if has_no_install_location(s): + n_source.append(s) + else: + for ss in s.sources: + n_source.append(ss) + copy_attr(s, ss) + setattr(ss, 'PACKAGING_INSTALL_LOCATION', s.get_path()) + + return (target, n_source) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/__init__.xml b/src/engine/SCons/Tool/packaging/__init__.xml new file mode 100644 index 0000000..521c9f6 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/__init__.xml @@ -0,0 +1,659 @@ + + + +Sets construction variables for the &b-Package; Builder. + + + + + + + + + +Builds software distribution packages. +Packages consist of files to install and packaging information. +The former may be specified with the &source; parameter and may be left out, +in which case the &FindInstalledFiles; function will collect +all files that have an &b-Install; or &b-InstallAs; Builder attached. +If the ⌖ is not specified +it will be deduced from additional information given to this Builder. + +The packaging information is specified +with the help of construction variables documented below. +This information is called a tag to stress that +some of them can also be attached to files with the &Tag; function. +The mandatory ones will complain if they were not specified. +They vary depending on chosen target packager. + +The target packager may be selected with the "PACKAGETYPE" command line +option or with the &cv-PACKAGETYPE; construction variable. Currently +the following packagers available: + + * msi - Microsoft Installer + * rpm - Redhat Package Manger + * ipkg - Itsy Package Management System + * tarbz2 - compressed tar + * targz - compressed tar + * zip - zip file + * src_tarbz2 - compressed tar source + * src_targz - compressed tar source + * src_zip - zip file source + +An updated list is always available under the "package_type" option when +running "scons --help" on a project that has packaging activated. + +env = Environment(tools=['default', 'packaging']) +env.Install('/bin/', 'my_program') +env.Package( NAME = 'foo', + VERSION = '1.2.3', + PACKAGEVERSION = 0, + PACKAGETYPE = 'rpm', + LICENSE = 'gpl', + SUMMARY = 'balalalalal', + DESCRIPTION = 'this should be really really long', + X_RPM_GROUP = 'Application/fu', + SOURCE_URL = 'http://foo.org/foo-1.2.3.tar.gz' + ) + + + + + + +Specifies the system architecture for which +the package is being built. +The default is the system architecture +of the machine on which SCons is running. +This is used to fill in the +Architecture: +field in an Ipkg +control file, +and as part of the name of a generated RPM file. + + + + + +A hook for modifying the file that controls the packaging build +(the .spec for RPM, +the control for Ipkg, +the .wxs for MSI). +If set, the function will be called +after the SCons template for the file has been written. +XXX + + + + + +The name of a file containing the change log text +to be included in the package. +This is included as the +%changelog +section of the RPM +.spec file. + + + + + +A long description of the project being packaged. +This is included in the relevant section +of the file that controls the packaging build. + + + + + +A language-specific long description for +the specified lang. +This is used to populate a +%description -l +section of an RPM +.spec file. + + + + + +The abbreviated name of the license under which +this project is released (gpl, lpgl, bsd etc.). +See http://www.opensource.org/licenses/alphabetical +for a list of license names. + + + + + +Specfies the name of the project to package. + + + + + +Specifies the directory where all files in resulting archive will be +placed if applicable. The default value is "$NAME-$VERSION". + + + + + +Selects the package type to build. Currently these are available: + + * msi - Microsoft Installer + * rpm - Redhat Package Manger + * ipkg - Itsy Package Management System + * tarbz2 - compressed tar + * targz - compressed tar + * zip - zip file + * src_tarbz2 - compressed tar source + * src_targz - compressed tar source + * src_zip - zip file source + +This may be overridden with the "package_type" command line option. + + + + + +The version of the package (not the underlying project). +This is currently only used by the rpm packager +and should reflect changes in the packaging, +not the underlying project code itself. + + + + + +The URL +(web address) +of the location from which the project was retrieved. +This is used to fill in the +Source: +field in the controlling information for Ipkg and RPM packages. + + + + + +A short summary of what the project is about. +This is used to fill in the +Summary: +field in the controlling information for Ipkg and RPM packages, +and as the +Description: +field in MSI packages. + + + + + +The person or organization who supply the packaged software. +This is used to fill in the +Vendor: +field in the controlling information for RPM packages, +and the +Manufacturer: +field in the controlling information for MSI packages. + + + + + +The version of the project, specified as a string. + + + + + + +This is used to fill in the +Depends: +field in the controlling information for Ipkg packages. + + + + + +This is used to fill in the +Description: +field in the controlling information for Ipkg packages. +The default value is +$SUMMARY\n$DESCRIPTION + + + + + +This is used to fill in the +Maintainer: +field in the controlling information for Ipkg packages. + + + + + +This is used to fill in the +Priority: +field in the controlling information for Ipkg packages. + + + + + +This is used to fill in the +Section: +field in the controlling information for Ipkg packages. + + + + + + + +This is used to fill in the +Language: +attribute in the controlling information for MSI packages. + + + + + +The text of the software license in RTF format. +Carriage return characters will be +replaced with the RTF equivalent \\par. + + + + + +TODO + + + + + + +This is used to fill in the +AutoReqProv: +field in the RPM +.spec file. + + + + + +internal, but overridable + + + + + +This is used to fill in the +BuildRequires: +field in the RPM +.spec file. + + + + + +internal, but overridable + + + + + +internal, but overridable + + + + + +This is used to fill in the +Conflicts: +field in the RPM +.spec file. + + + + + +This value is used as the default attributes +for the files in the RPM package. +The default value is +(-,root,root). + + + + + +This is used to fill in the +Distribution: +field in the RPM +.spec file. + + + + + +This is used to fill in the +Epoch: +field in the controlling information for RPM packages. + + + + + +This is used to fill in the +ExcludeArch: +field in the RPM +.spec file. + + + + + +This is used to fill in the +ExclusiveArch: +field in the RPM +.spec file. + + + + + +This is used to fill in the +Group: +field in the RPM +.spec file. + + + + + +This is used to fill in the +Group(lang): +field in the RPM +.spec file. +Note that +lang +is not literal +and should be replaced by +the appropriate language code. + + + + + +This is used to fill in the +Icon: +field in the RPM +.spec file. + + + + + +internal, but overridable + + + + + +This is used to fill in the +Packager: +field in the RPM +.spec file. + + + + + +This is used to fill in the +Provides: +field in the RPM +.spec file. + + + + + +This is used to fill in the +%post: +section in the RPM +.spec file. + + + + + +This is used to fill in the +%pre: +section in the RPM +.spec file. + + + + + +This is used to fill in the +Prefix: +field in the RPM +.spec file. + + + + + +internal, but overridable + + + + + +This is used to fill in the +%postun: +section in the RPM +.spec file. + + + + + +This is used to fill in the +%preun: +section in the RPM +.spec file. + + + + + +This is used to fill in the +Requires: +field in the RPM +.spec file. + + + + + +This is used to fill in the +Serial: +field in the RPM +.spec file. + + + + + +This is used to fill in the +Url: +field in the RPM +.spec file. + + + + + + + + + diff --git a/src/engine/SCons/Tool/packaging/ipk.py b/src/engine/SCons/Tool/packaging/ipk.py new file mode 100644 index 0000000..d566f48 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/ipk.py @@ -0,0 +1,185 @@ +"""SCons.Tool.Packaging.ipk +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/ipk.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Builder +import SCons.Node.FS +import os + +from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot + +def package(env, target, source, PACKAGEROOT, NAME, VERSION, DESCRIPTION, + SUMMARY, X_IPK_PRIORITY, X_IPK_SECTION, SOURCE_URL, + X_IPK_MAINTAINER, X_IPK_DEPENDS, **kw): + """ this function prepares the packageroot directory for packaging with the + ipkg builder. + """ + SCons.Tool.Tool('ipkg').generate(env) + + # setup the Ipkg builder + bld = env['BUILDERS']['Ipkg'] + target, source = stripinstallbuilder(target, source, env) + target, source = putintopackageroot(target, source, env, PACKAGEROOT) + + # This should be overridable from the construction environment, + # which it is by using ARCHITECTURE=. + # Guessing based on what os.uname() returns at least allows it + # to work for both i386 and x86_64 Linux systems. + archmap = { + 'i686' : 'i386', + 'i586' : 'i386', + 'i486' : 'i386', + } + + buildarchitecture = os.uname()[4] + buildarchitecture = archmap.get(buildarchitecture, buildarchitecture) + + if kw.has_key('ARCHITECTURE'): + buildarchitecture = kw['ARCHITECTURE'] + + # setup the kw to contain the mandatory arguments to this fucntion. + # do this before calling any builder or setup function + loc=locals() + del loc['kw'] + kw.update(loc) + del kw['source'], kw['target'], kw['env'] + + # generate the specfile + specfile = gen_ipk_dir(PACKAGEROOT, source, env, kw) + + # override the default target. + if str(target[0])=="%s-%s"%(NAME, VERSION): + target=[ "%s_%s_%s.ipk"%(NAME, VERSION, buildarchitecture) ] + + # now apply the Ipkg builder + return apply(bld, [env, target, specfile], kw) + +def gen_ipk_dir(proot, source, env, kw): + # make sure the packageroot is a Dir object. + if SCons.Util.is_String(proot): proot=env.Dir(proot) + + # create the specfile builder + s_bld=SCons.Builder.Builder( + action = build_specfiles, + ) + + # create the specfile targets + spec_target=[] + control=proot.Dir('CONTROL') + spec_target.append(control.File('control')) + spec_target.append(control.File('conffiles')) + spec_target.append(control.File('postrm')) + spec_target.append(control.File('prerm')) + spec_target.append(control.File('postinst')) + spec_target.append(control.File('preinst')) + + # apply the builder to the specfile targets + apply(s_bld, [env, spec_target, source], kw) + + # the packageroot directory does now contain the specfiles. + return proot + +def build_specfiles(source, target, env): + """ filter the targets for the needed files and use the variables in env + to create the specfile. + """ + # + # At first we care for the CONTROL/control file, which is the main file for ipk. + # + # For this we need to open multiple files in random order, so we store into + # a dict so they can be easily accessed. + # + # + opened_files={} + def open_file(needle, haystack): + try: + return opened_files[needle] + except KeyError: + file=filter(lambda x: x.get_path().rfind(needle)!=-1, haystack)[0] + opened_files[needle]=open(file.abspath, 'w') + return opened_files[needle] + + control_file=open_file('control', target) + + if not env.has_key('X_IPK_DESCRIPTION'): + env['X_IPK_DESCRIPTION']="%s\n %s"%(env['SUMMARY'], + env['DESCRIPTION'].replace('\n', '\n ')) + + + content = """ +Package: $NAME +Version: $VERSION +Priority: $X_IPK_PRIORITY +Section: $X_IPK_SECTION +Source: $SOURCE_URL +Architecture: $ARCHITECTURE +Maintainer: $X_IPK_MAINTAINER +Depends: $X_IPK_DEPENDS +Description: $X_IPK_DESCRIPTION +""" + + control_file.write(env.subst(content)) + + # + # now handle the various other files, which purpose it is to set post-, + # pre-scripts and mark files as config files. + # + # We do so by filtering the source files for files which are marked with + # the "config" tag and afterwards we do the same for x_ipk_postrm, + # x_ipk_prerm, x_ipk_postinst and x_ipk_preinst tags. + # + # The first one will write the name of the file into the file + # CONTROL/configfiles, the latter add the content of the x_ipk_* variable + # into the same named file. + # + for f in [x for x in source if 'PACKAGING_CONFIG' in dir(x)]: + config=open_file('conffiles') + config.write(f.PACKAGING_INSTALL_LOCATION) + config.write('\n') + + for str in 'POSTRM PRERM POSTINST PREINST'.split(): + name="PACKAGING_X_IPK_%s"%str + for f in [x for x in source if name in dir(x)]: + file=open_file(name) + file.write(env[str]) + + # + # close all opened files + for f in opened_files.values(): + f.close() + + # call a user specified function + if env.has_key('CHANGE_SPECFILE'): + content += env['CHANGE_SPECFILE'](target) + + return 0 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/msi.py b/src/engine/SCons/Tool/packaging/msi.py new file mode 100644 index 0000000..3806df9 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/msi.py @@ -0,0 +1,526 @@ +"""SCons.Tool.packaging.msi + +The msi packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/msi.py 4577 2009/12/27 19:44:43 scons" + +import os +import SCons +from SCons.Action import Action +from SCons.Builder import Builder + +from xml.dom.minidom import * +from xml.sax.saxutils import escape + +from SCons.Tool.packaging import stripinstallbuilder + +# +# Utility functions +# +def convert_to_id(s, id_set): + """ Some parts of .wxs need an Id attribute (for example: The File and + Directory directives. The charset is limited to A-Z, a-z, digits, + underscores, periods. Each Id must begin with a letter or with a + underscore. Google for "CNDL0015" for information about this. + + Requirements: + * the string created must only contain chars from the target charset. + * the string created must have a minimal editing distance from the + original string. + * the string created must be unique for the whole .wxs file. + + Observation: + * There are 62 chars in the charset. + + Idea: + * filter out forbidden characters. Check for a collision with the help + of the id_set. Add the number of the number of the collision at the + end of the created string. Furthermore care for a correct start of + the string. + """ + charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxyz0123456789_.' + if s[0] in '0123456789.': + s += '_'+s + id = filter( lambda c : c in charset, s ) + + # did we already generate an id for this file? + try: + return id_set[id][s] + except KeyError: + # no we did not so initialize with the id + if not id_set.has_key(id): id_set[id] = { s : id } + # there is a collision, generate an id which is unique by appending + # the collision number + else: id_set[id][s] = id + str(len(id_set[id])) + + return id_set[id][s] + +def is_dos_short_file_name(file): + """ examine if the given file is in the 8.3 form. + """ + fname, ext = os.path.splitext(file) + proper_ext = len(ext) == 0 or (2 <= len(ext) <= 4) # the ext contains the dot + proper_fname = file.isupper() and len(fname) <= 8 + + return proper_ext and proper_fname + +def gen_dos_short_file_name(file, filename_set): + """ see http://support.microsoft.com/default.aspx?scid=kb;en-us;Q142982 + + These are no complete 8.3 dos short names. The ~ char is missing and + replaced with one character from the filename. WiX warns about such + filenames, since a collision might occur. Google for "CNDL1014" for + more information. + """ + # guard this to not confuse the generation + if is_dos_short_file_name(file): + return file + + fname, ext = os.path.splitext(file) # ext contains the dot + + # first try if it suffices to convert to upper + file = file.upper() + if is_dos_short_file_name(file): + return file + + # strip forbidden characters. + forbidden = '."/[]:;=, ' + fname = filter( lambda c : c not in forbidden, fname ) + + # check if we already generated a filename with the same number: + # thisis1.txt, thisis2.txt etc. + duplicate, num = not None, 1 + while duplicate: + shortname = "%s%s" % (fname[:8-len(str(num))].upper(),\ + str(num)) + if len(ext) >= 2: + shortname = "%s%s" % (shortname, ext[:4].upper()) + + duplicate, num = shortname in filename_set, num+1 + + assert( is_dos_short_file_name(shortname) ), 'shortname is %s, longname is %s' % (shortname, file) + filename_set.append(shortname) + return shortname + +def create_feature_dict(files): + """ X_MSI_FEATURE and doc FileTag's can be used to collect files in a + hierarchy. This function collects the files into this hierarchy. + """ + dict = {} + + def add_to_dict( feature, file ): + if not SCons.Util.is_List( feature ): + feature = [ feature ] + + for f in feature: + if not dict.has_key( f ): + dict[ f ] = [ file ] + else: + dict[ f ].append( file ) + + for file in files: + if hasattr( file, 'PACKAGING_X_MSI_FEATURE' ): + add_to_dict(file.PACKAGING_X_MSI_FEATURE, file) + elif hasattr( file, 'PACKAGING_DOC' ): + add_to_dict( 'PACKAGING_DOC', file ) + else: + add_to_dict( 'default', file ) + + return dict + +def generate_guids(root): + """ generates globally unique identifiers for parts of the xml which need + them. + + Component tags have a special requirement. Their UUID is only allowed to + change if the list of their contained resources has changed. This allows + for clean removal and proper updates. + + To handle this requirement, the uuid is generated with an md5 hashing the + whole subtree of a xml node. + """ + from hashlib import md5 + + # specify which tags need a guid and in which attribute this should be stored. + needs_id = { 'Product' : 'Id', + 'Package' : 'Id', + 'Component' : 'Guid', + } + + # find all XMl nodes matching the key, retrieve their attribute, hash their + # subtree, convert hash to string and add as a attribute to the xml node. + for (key,value) in needs_id.items(): + node_list = root.getElementsByTagName(key) + attribute = value + for node in node_list: + hash = md5(node.toxml()).hexdigest() + hash_str = '%s-%s-%s-%s-%s' % ( hash[:8], hash[8:12], hash[12:16], hash[16:20], hash[20:] ) + node.attributes[attribute] = hash_str + + + +def string_wxsfile(target, source, env): + return "building WiX file %s"%( target[0].path ) + +def build_wxsfile(target, source, env): + """ compiles a .wxs file from the keywords given in env['msi_spec'] and + by analyzing the tree of source nodes and their tags. + """ + file = open(target[0].abspath, 'w') + + try: + # Create a document with the Wix root tag + doc = Document() + root = doc.createElement( 'Wix' ) + root.attributes['xmlns']='http://schemas.microsoft.com/wix/2003/01/wi' + doc.appendChild( root ) + + filename_set = [] # this is to circumvent duplicates in the shortnames + id_set = {} # this is to circumvent duplicates in the ids + + # Create the content + build_wxsfile_header_section(root, env) + build_wxsfile_file_section(root, source, env['NAME'], env['VERSION'], env['VENDOR'], filename_set, id_set) + generate_guids(root) + build_wxsfile_features_section(root, source, env['NAME'], env['VERSION'], env['SUMMARY'], id_set) + build_wxsfile_default_gui(root) + build_license_file(target[0].get_dir(), env) + + # write the xml to a file + file.write( doc.toprettyxml() ) + + # call a user specified function + if env.has_key('CHANGE_SPECFILE'): + env['CHANGE_SPECFILE'](target, source) + + except KeyError, e: + raise SCons.Errors.UserError( '"%s" package field for MSI is missing.' % e.args[0] ) + +# +# setup function +# +def create_default_directory_layout(root, NAME, VERSION, VENDOR, filename_set): + """ Create the wix default target directory layout and return the innermost + directory. + + We assume that the XML tree delivered in the root argument already contains + the Product tag. + + Everything is put under the PFiles directory property defined by WiX. + After that a directory with the 'VENDOR' tag is placed and then a + directory with the name of the project and its VERSION. This leads to the + following TARGET Directory Layout: + C:\\\\ + Example: C:\Programme\Company\Product-1.2\ + """ + doc = Document() + d1 = doc.createElement( 'Directory' ) + d1.attributes['Id'] = 'TARGETDIR' + d1.attributes['Name'] = 'SourceDir' + + d2 = doc.createElement( 'Directory' ) + d2.attributes['Id'] = 'ProgramFilesFolder' + d2.attributes['Name'] = 'PFiles' + + d3 = doc.createElement( 'Directory' ) + d3.attributes['Id'] = 'VENDOR_folder' + d3.attributes['Name'] = escape( gen_dos_short_file_name( VENDOR, filename_set ) ) + d3.attributes['LongName'] = escape( VENDOR ) + + d4 = doc.createElement( 'Directory' ) + project_folder = "%s-%s" % ( NAME, VERSION ) + d4.attributes['Id'] = 'MY_DEFAULT_FOLDER' + d4.attributes['Name'] = escape( gen_dos_short_file_name( project_folder, filename_set ) ) + d4.attributes['LongName'] = escape( project_folder ) + + d1.childNodes.append( d2 ) + d2.childNodes.append( d3 ) + d3.childNodes.append( d4 ) + + root.getElementsByTagName('Product')[0].childNodes.append( d1 ) + + return d4 + +# +# mandatory and optional file tags +# +def build_wxsfile_file_section(root, files, NAME, VERSION, VENDOR, filename_set, id_set): + """ builds the Component sections of the wxs file with their included files. + + Files need to be specified in 8.3 format and in the long name format, long + filenames will be converted automatically. + + Features are specficied with the 'X_MSI_FEATURE' or 'DOC' FileTag. + """ + root = create_default_directory_layout( root, NAME, VERSION, VENDOR, filename_set ) + components = create_feature_dict( files ) + factory = Document() + + def get_directory( node, dir ): + """ returns the node under the given node representing the directory. + + Returns the component node if dir is None or empty. + """ + if dir == '' or not dir: + return node + + Directory = node + dir_parts = dir.split(os.path.sep) + + # to make sure that our directory ids are unique, the parent folders are + # consecutively added to upper_dir + upper_dir = '' + + # walk down the xml tree finding parts of the directory + dir_parts = filter( lambda d: d != '', dir_parts ) + for d in dir_parts[:]: + already_created = filter( lambda c: c.nodeName == 'Directory' and c.attributes['LongName'].value == escape(d), Directory.childNodes ) + + if already_created != []: + Directory = already_created[0] + dir_parts.remove(d) + upper_dir += d + else: + break + + for d in dir_parts: + nDirectory = factory.createElement( 'Directory' ) + nDirectory.attributes['LongName'] = escape( d ) + nDirectory.attributes['Name'] = escape( gen_dos_short_file_name( d, filename_set ) ) + upper_dir += d + nDirectory.attributes['Id'] = convert_to_id( upper_dir, id_set ) + + Directory.childNodes.append( nDirectory ) + Directory = nDirectory + + return Directory + + for file in files: + drive, path = os.path.splitdrive( file.PACKAGING_INSTALL_LOCATION ) + filename = os.path.basename( path ) + dirname = os.path.dirname( path ) + + h = { + # tagname : default value + 'PACKAGING_X_MSI_VITAL' : 'yes', + 'PACKAGING_X_MSI_FILEID' : convert_to_id(filename, id_set), + 'PACKAGING_X_MSI_LONGNAME' : filename, + 'PACKAGING_X_MSI_SHORTNAME' : gen_dos_short_file_name(filename, filename_set), + 'PACKAGING_X_MSI_SOURCE' : file.get_path(), + } + + # fill in the default tags given above. + for k,v in [ (k, v) for (k,v) in h.items() if not hasattr(file, k) ]: + setattr( file, k, v ) + + File = factory.createElement( 'File' ) + File.attributes['LongName'] = escape( file.PACKAGING_X_MSI_LONGNAME ) + File.attributes['Name'] = escape( file.PACKAGING_X_MSI_SHORTNAME ) + File.attributes['Source'] = escape( file.PACKAGING_X_MSI_SOURCE ) + File.attributes['Id'] = escape( file.PACKAGING_X_MSI_FILEID ) + File.attributes['Vital'] = escape( file.PACKAGING_X_MSI_VITAL ) + + # create the Tag under which this file should appear + Component = factory.createElement('Component') + Component.attributes['DiskId'] = '1' + Component.attributes['Id'] = convert_to_id( filename, id_set ) + + # hang the component node under the root node and the file node + # under the component node. + Directory = get_directory( root, dirname ) + Directory.childNodes.append( Component ) + Component.childNodes.append( File ) + +# +# additional functions +# +def build_wxsfile_features_section(root, files, NAME, VERSION, SUMMARY, id_set): + """ This function creates the tag based on the supplied xml tree. + + This is achieved by finding all s and adding them to a default target. + + It should be called after the tree has been built completly. We assume + that a MY_DEFAULT_FOLDER Property is defined in the wxs file tree. + + Furthermore a top-level with the name and VERSION of the software will be created. + + An PACKAGING_X_MSI_FEATURE can either be a string, where the feature + DESCRIPTION will be the same as its title or a Tuple, where the first + part will be its title and the second its DESCRIPTION. + """ + factory = Document() + Feature = factory.createElement('Feature') + Feature.attributes['Id'] = 'complete' + Feature.attributes['ConfigurableDirectory'] = 'MY_DEFAULT_FOLDER' + Feature.attributes['Level'] = '1' + Feature.attributes['Title'] = escape( '%s %s' % (NAME, VERSION) ) + Feature.attributes['Description'] = escape( SUMMARY ) + Feature.attributes['Display'] = 'expand' + + for (feature, files) in create_feature_dict(files).items(): + SubFeature = factory.createElement('Feature') + SubFeature.attributes['Level'] = '1' + + if SCons.Util.is_Tuple(feature): + SubFeature.attributes['Id'] = convert_to_id( feature[0], id_set ) + SubFeature.attributes['Title'] = escape(feature[0]) + SubFeature.attributes['Description'] = escape(feature[1]) + else: + SubFeature.attributes['Id'] = convert_to_id( feature, id_set ) + if feature=='default': + SubFeature.attributes['Description'] = 'Main Part' + SubFeature.attributes['Title'] = 'Main Part' + elif feature=='PACKAGING_DOC': + SubFeature.attributes['Description'] = 'Documentation' + SubFeature.attributes['Title'] = 'Documentation' + else: + SubFeature.attributes['Description'] = escape(feature) + SubFeature.attributes['Title'] = escape(feature) + + # build the componentrefs. As one of the design decision is that every + # file is also a component we walk the list of files and create a + # reference. + for f in files: + ComponentRef = factory.createElement('ComponentRef') + ComponentRef.attributes['Id'] = convert_to_id( os.path.basename(f.get_path()), id_set ) + SubFeature.childNodes.append(ComponentRef) + + Feature.childNodes.append(SubFeature) + + root.getElementsByTagName('Product')[0].childNodes.append(Feature) + +def build_wxsfile_default_gui(root): + """ this function adds a default GUI to the wxs file + """ + factory = Document() + Product = root.getElementsByTagName('Product')[0] + + UIRef = factory.createElement('UIRef') + UIRef.attributes['Id'] = 'WixUI_Mondo' + Product.childNodes.append(UIRef) + + UIRef = factory.createElement('UIRef') + UIRef.attributes['Id'] = 'WixUI_ErrorProgressText' + Product.childNodes.append(UIRef) + +def build_license_file(directory, spec): + """ creates a License.rtf file with the content of "X_MSI_LICENSE_TEXT" + in the given directory + """ + name, text = '', '' + + try: + name = spec['LICENSE'] + text = spec['X_MSI_LICENSE_TEXT'] + except KeyError: + pass # ignore this as X_MSI_LICENSE_TEXT is optional + + if name!='' or text!='': + file = open( os.path.join(directory.get_path(), 'License.rtf'), 'w' ) + file.write('{\\rtf') + if text!='': + file.write(text.replace('\n', '\\par ')) + else: + file.write(name+'\\par\\par') + file.write('}') + file.close() + +# +# mandatory and optional package tags +# +def build_wxsfile_header_section(root, spec): + """ Adds the xml file node which define the package meta-data. + """ + # Create the needed DOM nodes and add them at the correct position in the tree. + factory = Document() + Product = factory.createElement( 'Product' ) + Package = factory.createElement( 'Package' ) + + root.childNodes.append( Product ) + Product.childNodes.append( Package ) + + # set "mandatory" default values + if not spec.has_key('X_MSI_LANGUAGE'): + spec['X_MSI_LANGUAGE'] = '1033' # select english + + # mandatory sections, will throw a KeyError if the tag is not available + Product.attributes['Name'] = escape( spec['NAME'] ) + Product.attributes['Version'] = escape( spec['VERSION'] ) + Product.attributes['Manufacturer'] = escape( spec['VENDOR'] ) + Product.attributes['Language'] = escape( spec['X_MSI_LANGUAGE'] ) + Package.attributes['Description'] = escape( spec['SUMMARY'] ) + + # now the optional tags, for which we avoid the KeyErrror exception + if spec.has_key( 'DESCRIPTION' ): + Package.attributes['Comments'] = escape( spec['DESCRIPTION'] ) + + if spec.has_key( 'X_MSI_UPGRADE_CODE' ): + Package.attributes['X_MSI_UPGRADE_CODE'] = escape( spec['X_MSI_UPGRADE_CODE'] ) + + # We hardcode the media tag as our current model cannot handle it. + Media = factory.createElement('Media') + Media.attributes['Id'] = '1' + Media.attributes['Cabinet'] = 'default.cab' + Media.attributes['EmbedCab'] = 'yes' + root.getElementsByTagName('Product')[0].childNodes.append(Media) + +# this builder is the entry-point for .wxs file compiler. +wxs_builder = Builder( + action = Action( build_wxsfile, string_wxsfile ), + ensure_suffix = '.wxs' ) + +def package(env, target, source, PACKAGEROOT, NAME, VERSION, + DESCRIPTION, SUMMARY, VENDOR, X_MSI_LANGUAGE, **kw): + # make sure that the Wix Builder is in the environment + SCons.Tool.Tool('wix').generate(env) + + # get put the keywords for the specfile compiler. These are the arguments + # given to the package function and all optional ones stored in kw, minus + # the the source, target and env one. + loc = locals() + del loc['kw'] + kw.update(loc) + del kw['source'], kw['target'], kw['env'] + + # strip the install builder from the source files + target, source = stripinstallbuilder(target, source, env) + + # put the arguments into the env and call the specfile builder. + env['msi_spec'] = kw + specfile = apply( wxs_builder, [env, target, source], kw ) + + # now call the WiX Tool with the built specfile added as a source. + msifile = env.WiX(target, specfile) + + # return the target and source tuple. + return (msifile, source+[specfile]) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/rpm.py b/src/engine/SCons/Tool/packaging/rpm.py new file mode 100644 index 0000000..461b4b1 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/rpm.py @@ -0,0 +1,367 @@ +"""SCons.Tool.Packaging.rpm + +The rpm packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/rpm.py 4577 2009/12/27 19:44:43 scons" + +import os +import string + +import SCons.Builder + +from SCons.Environment import OverrideEnvironment +from SCons.Tool.packaging import stripinstallbuilder, src_targz +from SCons.Errors import UserError + +def package(env, target, source, PACKAGEROOT, NAME, VERSION, + PACKAGEVERSION, DESCRIPTION, SUMMARY, X_RPM_GROUP, LICENSE, + **kw): + # initialize the rpm tool + SCons.Tool.Tool('rpm').generate(env) + + bld = env['BUILDERS']['Rpm'] + + # Generate a UserError whenever the target name has been set explicitly, + # since rpm does not allow for controlling it. This is detected by + # checking if the target has been set to the default by the Package() + # Environment function. + if str(target[0])!="%s-%s"%(NAME, VERSION): + raise UserError( "Setting target is not supported for rpm." ) + else: + # This should be overridable from the construction environment, + # which it is by using ARCHITECTURE=. + # Guessing based on what os.uname() returns at least allows it + # to work for both i386 and x86_64 Linux systems. + archmap = { + 'i686' : 'i386', + 'i586' : 'i386', + 'i486' : 'i386', + } + + buildarchitecture = os.uname()[4] + buildarchitecture = archmap.get(buildarchitecture, buildarchitecture) + + if kw.has_key('ARCHITECTURE'): + buildarchitecture = kw['ARCHITECTURE'] + + fmt = '%s-%s-%s.%s.rpm' + srcrpm = fmt % (NAME, VERSION, PACKAGEVERSION, 'src') + binrpm = fmt % (NAME, VERSION, PACKAGEVERSION, buildarchitecture) + + target = [ srcrpm, binrpm ] + + # get the correct arguments into the kw hash + loc=locals() + del loc['kw'] + kw.update(loc) + del kw['source'], kw['target'], kw['env'] + + # if no "SOURCE_URL" tag is given add a default one. + if not kw.has_key('SOURCE_URL'): + #kw['SOURCE_URL']=(str(target[0])+".tar.gz").replace('.rpm', '') + kw['SOURCE_URL']=string.replace(str(target[0])+".tar.gz", '.rpm', '') + + # mangle the source and target list for the rpmbuild + env = OverrideEnvironment(env, kw) + target, source = stripinstallbuilder(target, source, env) + target, source = addspecfile(target, source, env) + target, source = collectintargz(target, source, env) + + # now call the rpm builder to actually build the packet. + return apply(bld, [env, target, source], kw) + +def collectintargz(target, source, env): + """ Puts all source files into a tar.gz file. """ + # the rpm tool depends on a source package, until this is chagned + # this hack needs to be here that tries to pack all sources in. + sources = env.FindSourceFiles() + + # filter out the target we are building the source list for. + #sources = [s for s in sources if not (s in target)] + sources = filter(lambda s, t=target: not (s in t), sources) + + # find the .spec file for rpm and add it since it is not necessarily found + # by the FindSourceFiles function. + #sources.extend( [s for s in source if str(s).rfind('.spec')!=-1] ) + spec_file = lambda s: string.rfind(str(s), '.spec') != -1 + sources.extend( filter(spec_file, source) ) + + # as the source contains the url of the source package this rpm package + # is built from, we extract the target name + #tarball = (str(target[0])+".tar.gz").replace('.rpm', '') + tarball = string.replace(str(target[0])+".tar.gz", '.rpm', '') + try: + #tarball = env['SOURCE_URL'].split('/')[-1] + tarball = string.split(env['SOURCE_URL'], '/')[-1] + except KeyError, e: + raise SCons.Errors.UserError( "Missing PackageTag '%s' for RPM packager" % e.args[0] ) + + tarball = src_targz.package(env, source=sources, target=tarball, + PACKAGEROOT=env['PACKAGEROOT'], ) + + return (target, tarball) + +def addspecfile(target, source, env): + specfile = "%s-%s" % (env['NAME'], env['VERSION']) + + bld = SCons.Builder.Builder(action = build_specfile, + suffix = '.spec', + target_factory = SCons.Node.FS.File) + + source.extend(bld(env, specfile, source)) + + return (target,source) + +def build_specfile(target, source, env): + """ Builds a RPM specfile from a dictionary with string metadata and + by analyzing a tree of nodes. + """ + file = open(target[0].abspath, 'w') + str = "" + + try: + file.write( build_specfile_header(env) ) + file.write( build_specfile_sections(env) ) + file.write( build_specfile_filesection(env, source) ) + file.close() + + # call a user specified function + if env.has_key('CHANGE_SPECFILE'): + env['CHANGE_SPECFILE'](target, source) + + except KeyError, e: + raise SCons.Errors.UserError( '"%s" package field for RPM is missing.' % e.args[0] ) + + +# +# mandatory and optional package tag section +# +def build_specfile_sections(spec): + """ Builds the sections of a rpm specfile. + """ + str = "" + + mandatory_sections = { + 'DESCRIPTION' : '\n%%description\n%s\n\n', } + + str = str + SimpleTagCompiler(mandatory_sections).compile( spec ) + + optional_sections = { + 'DESCRIPTION_' : '%%description -l %s\n%s\n\n', + 'CHANGELOG' : '%%changelog\n%s\n\n', + 'X_RPM_PREINSTALL' : '%%pre\n%s\n\n', + 'X_RPM_POSTINSTALL' : '%%post\n%s\n\n', + 'X_RPM_PREUNINSTALL' : '%%preun\n%s\n\n', + 'X_RPM_POSTUNINSTALL' : '%%postun\n%s\n\n', + 'X_RPM_VERIFY' : '%%verify\n%s\n\n', + + # These are for internal use but could possibly be overriden + 'X_RPM_PREP' : '%%prep\n%s\n\n', + 'X_RPM_BUILD' : '%%build\n%s\n\n', + 'X_RPM_INSTALL' : '%%install\n%s\n\n', + 'X_RPM_CLEAN' : '%%clean\n%s\n\n', + } + + # Default prep, build, install and clean rules + # TODO: optimize those build steps, to not compile the project a second time + if not spec.has_key('X_RPM_PREP'): + spec['X_RPM_PREP'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"' + '\n%setup -q' + + if not spec.has_key('X_RPM_BUILD'): + spec['X_RPM_BUILD'] = 'mkdir "$RPM_BUILD_ROOT"' + + if not spec.has_key('X_RPM_INSTALL'): + spec['X_RPM_INSTALL'] = 'scons --install-sandbox="$RPM_BUILD_ROOT" "$RPM_BUILD_ROOT"' + + if not spec.has_key('X_RPM_CLEAN'): + spec['X_RPM_CLEAN'] = '[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"' + + str = str + SimpleTagCompiler(optional_sections, mandatory=0).compile( spec ) + + return str + +def build_specfile_header(spec): + """ Builds all section but the %file of a rpm specfile + """ + str = "" + + # first the mandatory sections + mandatory_header_fields = { + 'NAME' : '%%define name %s\nName: %%{name}\n', + 'VERSION' : '%%define version %s\nVersion: %%{version}\n', + 'PACKAGEVERSION' : '%%define release %s\nRelease: %%{release}\n', + 'X_RPM_GROUP' : 'Group: %s\n', + 'SUMMARY' : 'Summary: %s\n', + 'LICENSE' : 'License: %s\n', } + + str = str + SimpleTagCompiler(mandatory_header_fields).compile( spec ) + + # now the optional tags + optional_header_fields = { + 'VENDOR' : 'Vendor: %s\n', + 'X_RPM_URL' : 'Url: %s\n', + 'SOURCE_URL' : 'Source: %s\n', + 'SUMMARY_' : 'Summary(%s): %s\n', + 'X_RPM_DISTRIBUTION' : 'Distribution: %s\n', + 'X_RPM_ICON' : 'Icon: %s\n', + 'X_RPM_PACKAGER' : 'Packager: %s\n', + 'X_RPM_GROUP_' : 'Group(%s): %s\n', + + 'X_RPM_REQUIRES' : 'Requires: %s\n', + 'X_RPM_PROVIDES' : 'Provides: %s\n', + 'X_RPM_CONFLICTS' : 'Conflicts: %s\n', + 'X_RPM_BUILDREQUIRES' : 'BuildRequires: %s\n', + + 'X_RPM_SERIAL' : 'Serial: %s\n', + 'X_RPM_EPOCH' : 'Epoch: %s\n', + 'X_RPM_AUTOREQPROV' : 'AutoReqProv: %s\n', + 'X_RPM_EXCLUDEARCH' : 'ExcludeArch: %s\n', + 'X_RPM_EXCLUSIVEARCH' : 'ExclusiveArch: %s\n', + 'X_RPM_PREFIX' : 'Prefix: %s\n', + 'X_RPM_CONFLICTS' : 'Conflicts: %s\n', + + # internal use + 'X_RPM_BUILDROOT' : 'BuildRoot: %s\n', } + + # fill in default values: + # Adding a BuildRequires renders the .rpm unbuildable under System, which + # are not managed by rpm, since the database to resolve this dependency is + # missing (take Gentoo as an example) +# if not s.has_key('x_rpm_BuildRequires'): +# s['x_rpm_BuildRequires'] = 'scons' + + if not spec.has_key('X_RPM_BUILDROOT'): + spec['X_RPM_BUILDROOT'] = '%{_tmppath}/%{name}-%{version}-%{release}' + + str = str + SimpleTagCompiler(optional_header_fields, mandatory=0).compile( spec ) + return str + +# +# mandatory and optional file tags +# +def build_specfile_filesection(spec, files): + """ builds the %file section of the specfile + """ + str = '%files\n' + + if not spec.has_key('X_RPM_DEFATTR'): + spec['X_RPM_DEFATTR'] = '(-,root,root)' + + str = str + '%%defattr %s\n' % spec['X_RPM_DEFATTR'] + + supported_tags = { + 'PACKAGING_CONFIG' : '%%config %s', + 'PACKAGING_CONFIG_NOREPLACE' : '%%config(noreplace) %s', + 'PACKAGING_DOC' : '%%doc %s', + 'PACKAGING_UNIX_ATTR' : '%%attr %s', + 'PACKAGING_LANG_' : '%%lang(%s) %s', + 'PACKAGING_X_RPM_VERIFY' : '%%verify %s', + 'PACKAGING_X_RPM_DIR' : '%%dir %s', + 'PACKAGING_X_RPM_DOCDIR' : '%%docdir %s', + 'PACKAGING_X_RPM_GHOST' : '%%ghost %s', } + + for file in files: + # build the tagset + tags = {} + for k in supported_tags.keys(): + try: + tags[k]=getattr(file, k) + except AttributeError: + pass + + # compile the tagset + str = str + SimpleTagCompiler(supported_tags, mandatory=0).compile( tags ) + + str = str + ' ' + str = str + file.PACKAGING_INSTALL_LOCATION + str = str + '\n\n' + + return str + +class SimpleTagCompiler: + """ This class is a simple string substition utility: + the replacement specfication is stored in the tagset dictionary, something + like: + { "abc" : "cdef %s ", + "abc_" : "cdef %s %s" } + + the compile function gets a value dictionary, which may look like: + { "abc" : "ghij", + "abc_gh" : "ij" } + + The resulting string will be: + "cdef ghij cdef gh ij" + """ + def __init__(self, tagset, mandatory=1): + self.tagset = tagset + self.mandatory = mandatory + + def compile(self, values): + """ compiles the tagset and returns a str containing the result + """ + def is_international(tag): + #return tag.endswith('_') + return tag[-1:] == '_' + + def get_country_code(tag): + return tag[-2:] + + def strip_country_code(tag): + return tag[:-2] + + replacements = self.tagset.items() + + str = "" + #domestic = [ (k,v) for k,v in replacements if not is_international(k) ] + domestic = filter(lambda t, i=is_international: not i(t[0]), replacements) + for key, replacement in domestic: + try: + str = str + replacement % values[key] + except KeyError, e: + if self.mandatory: + raise e + + #international = [ (k,v) for k,v in replacements if is_international(k) ] + international = filter(lambda t, i=is_international: i(t[0]), replacements) + for key, replacement in international: + try: + #int_values_for_key = [ (get_country_code(k),v) for k,v in values.items() if strip_country_code(k) == key ] + x = filter(lambda t,key=key,s=strip_country_code: s(t[0]) == key, values.items()) + int_values_for_key = map(lambda t,g=get_country_code: (g(t[0]),t[1]), x) + for v in int_values_for_key: + str = str + replacement % v + except KeyError, e: + if self.mandatory: + raise e + + return str + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/src_tarbz2.py b/src/engine/SCons/Tool/packaging/src_tarbz2.py new file mode 100644 index 0000000..aaeb7a8 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/src_tarbz2.py @@ -0,0 +1,43 @@ +"""SCons.Tool.Packaging.tarbz2 + +The tarbz2 SRC packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/src_tarbz2.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.packaging import putintopackageroot + +def package(env, target, source, PACKAGEROOT, **kw): + bld = env['BUILDERS']['Tar'] + bld.set_suffix('.tar.bz2') + target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0) + return bld(env, target, source, TARFLAGS='-jc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/src_targz.py b/src/engine/SCons/Tool/packaging/src_targz.py new file mode 100644 index 0000000..5be52e6 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/src_targz.py @@ -0,0 +1,43 @@ +"""SCons.Tool.Packaging.targz + +The targz SRC packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/src_targz.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.packaging import putintopackageroot + +def package(env, target, source, PACKAGEROOT, **kw): + bld = env['BUILDERS']['Tar'] + bld.set_suffix('.tar.gz') + target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0) + return bld(env, target, source, TARFLAGS='-zc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/src_zip.py b/src/engine/SCons/Tool/packaging/src_zip.py new file mode 100644 index 0000000..cfa73b1 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/src_zip.py @@ -0,0 +1,43 @@ +"""SCons.Tool.Packaging.zip + +The zip SRC packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/src_zip.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.packaging import putintopackageroot + +def package(env, target, source, PACKAGEROOT, **kw): + bld = env['BUILDERS']['Zip'] + bld.set_suffix('.zip') + target, source = putintopackageroot(target, source, env, PACKAGEROOT, honor_install_location=0) + return bld(env, target, source) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/tarbz2.py b/src/engine/SCons/Tool/packaging/tarbz2.py new file mode 100644 index 0000000..a0c2b81 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/tarbz2.py @@ -0,0 +1,44 @@ +"""SCons.Tool.Packaging.tarbz2 + +The tarbz2 SRC packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/tarbz2.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot + +def package(env, target, source, PACKAGEROOT, **kw): + bld = env['BUILDERS']['Tar'] + bld.set_suffix('.tar.gz') + target, source = putintopackageroot(target, source, env, PACKAGEROOT) + target, source = stripinstallbuilder(target, source, env) + return bld(env, target, source, TARFLAGS='-jc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/targz.py b/src/engine/SCons/Tool/packaging/targz.py new file mode 100644 index 0000000..b985163 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/targz.py @@ -0,0 +1,44 @@ +"""SCons.Tool.Packaging.targz + +The targz SRC packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/targz.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot + +def package(env, target, source, PACKAGEROOT, **kw): + bld = env['BUILDERS']['Tar'] + bld.set_suffix('.tar.gz') + target, source = stripinstallbuilder(target, source, env) + target, source = putintopackageroot(target, source, env, PACKAGEROOT) + return bld(env, target, source, TARFLAGS='-zc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/packaging/zip.py b/src/engine/SCons/Tool/packaging/zip.py new file mode 100644 index 0000000..590c975 --- /dev/null +++ b/src/engine/SCons/Tool/packaging/zip.py @@ -0,0 +1,44 @@ +"""SCons.Tool.Packaging.zip + +The zip SRC packager. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/packaging/zip.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Tool.packaging import stripinstallbuilder, putintopackageroot + +def package(env, target, source, PACKAGEROOT, **kw): + bld = env['BUILDERS']['Zip'] + bld.set_suffix('.zip') + target, source = stripinstallbuilder(target, source, env) + target, source = putintopackageroot(target, source, env, PACKAGEROOT) + return bld(env, target, source) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/pdf.py b/src/engine/SCons/Tool/pdf.py new file mode 100644 index 0000000..1ce561e --- /dev/null +++ b/src/engine/SCons/Tool/pdf.py @@ -0,0 +1,78 @@ +"""SCons.Tool.pdf + +Common PDF Builder definition for various other Tool modules that use it. +Add an explicit action to run epstopdf to convert .eps files to .pdf + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/pdf.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Builder +import SCons.Tool + +PDFBuilder = None + +EpsPdfAction = SCons.Action.Action('$EPSTOPDFCOM', '$EPSTOPDFCOMSTR') + +def generate(env): + try: + env['BUILDERS']['PDF'] + except KeyError: + global PDFBuilder + if PDFBuilder is None: + PDFBuilder = SCons.Builder.Builder(action = {}, + source_scanner = SCons.Tool.PDFLaTeXScanner, + prefix = '$PDFPREFIX', + suffix = '$PDFSUFFIX', + emitter = {}, + source_ext_match = None, + single_source=True) + env['BUILDERS']['PDF'] = PDFBuilder + + env['PDFPREFIX'] = '' + env['PDFSUFFIX'] = '.pdf' + +# put the epstopdf builder in this routine so we can add it after +# the pdftex builder so that one is the default for no source suffix +def generate2(env): + bld = env['BUILDERS']['PDF'] + #bld.add_action('.ps', EpsPdfAction) # this is covered by direct Ghostcript action in gs.py + bld.add_action('.eps', EpsPdfAction) + + env['EPSTOPDF'] = 'epstopdf' + env['EPSTOPDFFLAGS'] = SCons.Util.CLVar('') + env['EPSTOPDFCOM'] = '$EPSTOPDF $EPSTOPDFFLAGS ${SOURCE} --outfile=${TARGET}' + +def exists(env): + # This only puts a skeleton Builder in place, so if someone + # references this Tool directly, it's always "available." + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/pdf.xml b/src/engine/SCons/Tool/pdf.xml new file mode 100644 index 0000000..a695079 --- /dev/null +++ b/src/engine/SCons/Tool/pdf.xml @@ -0,0 +1,49 @@ + + + +Sets construction variables for the Portable Document Format builder. + + +PDFPREFIX +PDFSUFFIX + + + + + +Builds a .pdf file +from a .dvi input file +(or, by extension, a .tex, +.ltx, +or +.latex input file). +The suffix specified by the &cv-link-PDFSUFFIX; construction variable +(.pdf by default) +is added automatically to the target +if it is not already present. Example: + + +# builds from aaa.tex +env.PDF(target = 'aaa.pdf', source = 'aaa.tex') +# builds bbb.pdf from bbb.dvi +env.PDF(target = 'bbb', source = 'bbb.dvi') + + + + + + +The prefix used for PDF file names. + + + + + +The suffix used for PDF file names. + + diff --git a/src/engine/SCons/Tool/pdflatex.py b/src/engine/SCons/Tool/pdflatex.py new file mode 100644 index 0000000..8a008b3 --- /dev/null +++ b/src/engine/SCons/Tool/pdflatex.py @@ -0,0 +1,83 @@ +"""SCons.Tool.pdflatex + +Tool-specific initialization for pdflatex. +Generates .pdf files from .latex or .ltx files + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/pdflatex.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Util +import SCons.Tool.pdf +import SCons.Tool.tex + +PDFLaTeXAction = None + +def PDFLaTeXAuxFunction(target = None, source= None, env=None): + result = SCons.Tool.tex.InternalLaTeXAuxAction( PDFLaTeXAction, target, source, env ) + if result != 0: + print env['PDFLATEX']," returned an error, check the log file" + return result + +PDFLaTeXAuxAction = None + +def generate(env): + """Add Builders and construction variables for pdflatex to an Environment.""" + global PDFLaTeXAction + if PDFLaTeXAction is None: + PDFLaTeXAction = SCons.Action.Action('$PDFLATEXCOM', '$PDFLATEXCOMSTR') + + global PDFLaTeXAuxAction + if PDFLaTeXAuxAction is None: + PDFLaTeXAuxAction = SCons.Action.Action(PDFLaTeXAuxFunction, + strfunction=SCons.Tool.tex.TeXLaTeXStrFunction) + + env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes) + + import pdf + pdf.generate(env) + + bld = env['BUILDERS']['PDF'] + bld.add_action('.ltx', PDFLaTeXAuxAction) + bld.add_action('.latex', PDFLaTeXAuxAction) + bld.add_emitter('.ltx', SCons.Tool.tex.tex_pdf_emitter) + bld.add_emitter('.latex', SCons.Tool.tex.tex_pdf_emitter) + + SCons.Tool.tex.generate_common(env) + +def exists(env): + return env.Detect('pdflatex') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/pdflatex.xml b/src/engine/SCons/Tool/pdflatex.xml new file mode 100644 index 0000000..3a4eaeb --- /dev/null +++ b/src/engine/SCons/Tool/pdflatex.xml @@ -0,0 +1,49 @@ + + + +Sets construction variables for the &pdflatex; utility. + + +PDFLATEX +PDFLATEXFLAGS +PDFLATEXCOM +LATEXRETRIES + + +PDFLATEXCOMSTR + + + + + +The &pdflatex; utility. + + + + + +The command line used to call the &pdflatex; utility. + + + + + +The string displayed when calling the &pdflatex; utility. +If this is not set, then &cv-link-PDFLATEXCOM; (the command line) is displayed. + + +env = Environment(PDFLATEX;COMSTR = "Building $TARGET from LaTeX input $SOURCES") + + + + + + +General options passed to the &pdflatex; utility. + + diff --git a/src/engine/SCons/Tool/pdftex.py b/src/engine/SCons/Tool/pdftex.py new file mode 100644 index 0000000..0733d16 --- /dev/null +++ b/src/engine/SCons/Tool/pdftex.py @@ -0,0 +1,108 @@ +"""SCons.Tool.pdftex + +Tool-specific initialization for pdftex. +Generates .pdf files from .tex files + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/pdftex.py 4577 2009/12/27 19:44:43 scons" + +import os +import SCons.Action +import SCons.Util +import SCons.Tool.tex + +PDFTeXAction = None + +# This action might be needed more than once if we are dealing with +# labels and bibtex. +PDFLaTeXAction = None + +def PDFLaTeXAuxAction(target = None, source= None, env=None): + result = SCons.Tool.tex.InternalLaTeXAuxAction( PDFLaTeXAction, target, source, env ) + return result + +def PDFTeXLaTeXFunction(target = None, source= None, env=None): + """A builder for TeX and LaTeX that scans the source file to + decide the "flavor" of the source and then executes the appropriate + program.""" + basedir = os.path.split(str(source[0]))[0] + abspath = os.path.abspath(basedir) + + if SCons.Tool.tex.is_LaTeX(source,env,abspath): + result = PDFLaTeXAuxAction(target,source,env) + if result != 0: + print env['PDFLATEX']," returned an error, check the log file" + else: + result = PDFTeXAction(target,source,env) + if result != 0: + print env['PDFTEX']," returned an error, check the log file" + return result + +PDFTeXLaTeXAction = None + +def generate(env): + """Add Builders and construction variables for pdftex to an Environment.""" + global PDFTeXAction + if PDFTeXAction is None: + PDFTeXAction = SCons.Action.Action('$PDFTEXCOM', '$PDFTEXCOMSTR') + + global PDFLaTeXAction + if PDFLaTeXAction is None: + PDFLaTeXAction = SCons.Action.Action("$PDFLATEXCOM", "$PDFLATEXCOMSTR") + + global PDFTeXLaTeXAction + if PDFTeXLaTeXAction is None: + PDFTeXLaTeXAction = SCons.Action.Action(PDFTeXLaTeXFunction, + strfunction=SCons.Tool.tex.TeXLaTeXStrFunction) + + env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes) + + import pdf + pdf.generate(env) + + bld = env['BUILDERS']['PDF'] + bld.add_action('.tex', PDFTeXLaTeXAction) + bld.add_emitter('.tex', SCons.Tool.tex.tex_pdf_emitter) + + # Add the epstopdf builder after the pdftex builder + # so pdftex is the default for no source suffix + pdf.generate2(env) + + SCons.Tool.tex.generate_common(env) + +def exists(env): + return env.Detect('pdftex') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/pdftex.xml b/src/engine/SCons/Tool/pdftex.xml new file mode 100644 index 0000000..15614a2 --- /dev/null +++ b/src/engine/SCons/Tool/pdftex.xml @@ -0,0 +1,53 @@ + + + +Sets construction variables for the &pdftex; utility. + + +PDFTEX +PDFTEXFLAGS +PDFTEXCOM +PDFLATEX +PDFLATEXFLAGS +PDFLATEXCOM +LATEXRETRIES + + +PDFTEXCOMSTR +PDFLATEXCOMSTR + + + + + +The &pdftex; utility. + + + + + +The command line used to call the &pdftex; utility. + + + + + +The string displayed when calling the &pdftex; utility. +If this is not set, then &cv-link-PDFTEXCOM; (the command line) is displayed. + + +env = Environment(PDFTEXCOMSTR = "Building $TARGET from TeX input $SOURCES") + + + + + + +General options passed to the &pdftex; utility. + + diff --git a/src/engine/SCons/Tool/qt.py b/src/engine/SCons/Tool/qt.py new file mode 100644 index 0000000..72ae194 --- /dev/null +++ b/src/engine/SCons/Tool/qt.py @@ -0,0 +1,336 @@ + +"""SCons.Tool.qt + +Tool-specific initialization for Qt. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/qt.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import re + +import SCons.Action +import SCons.Builder +import SCons.Defaults +import SCons.Scanner +import SCons.Tool +import SCons.Util + +class ToolQtWarning(SCons.Warnings.Warning): + pass + +class GeneratedMocFileNotIncluded(ToolQtWarning): + pass + +class QtdirNotFound(ToolQtWarning): + pass + +SCons.Warnings.enableWarningClass(ToolQtWarning) + +header_extensions = [".h", ".hxx", ".hpp", ".hh"] +if SCons.Util.case_sensitive_suffixes('.h', '.H'): + header_extensions.append('.H') +cplusplus = __import__('c++', globals(), locals(), []) +cxx_suffixes = cplusplus.CXXSuffixes + +def checkMocIncluded(target, source, env): + moc = target[0] + cpp = source[0] + # looks like cpp.includes is cleared before the build stage :-( + # not really sure about the path transformations (moc.cwd? cpp.cwd?) :-/ + path = SCons.Defaults.CScan.path(env, moc.cwd) + includes = SCons.Defaults.CScan(cpp, env, path) + if not moc in includes: + SCons.Warnings.warn( + GeneratedMocFileNotIncluded, + "Generated moc file '%s' is not included by '%s'" % + (str(moc), str(cpp))) + +def find_file(filename, paths, node_factory): + for dir in paths: + node = node_factory(filename, dir) + if node.rexists(): + return node + return None + +class _Automoc: + """ + Callable class, which works as an emitter for Programs, SharedLibraries and + StaticLibraries. + """ + + def __init__(self, objBuilderName): + self.objBuilderName = objBuilderName + + def __call__(self, target, source, env): + """ + Smart autoscan function. Gets the list of objects for the Program + or Lib. Adds objects and builders for the special qt files. + """ + try: + if int(env.subst('$QT_AUTOSCAN')) == 0: + return target, source + except ValueError: + pass + try: + debug = int(env.subst('$QT_DEBUG')) + except ValueError: + debug = 0 + + # some shortcuts used in the scanner + splitext = SCons.Util.splitext + objBuilder = getattr(env, self.objBuilderName) + + # some regular expressions: + # Q_OBJECT detection + q_object_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]') + # cxx and c comment 'eater' + #comment = re.compile(r'(//.*)|(/\*(([^*])|(\*[^/]))*\*/)') + # CW: something must be wrong with the regexp. See also bug #998222 + # CURRENTLY THERE IS NO TEST CASE FOR THAT + + # The following is kind of hacky to get builders working properly (FIXME) + objBuilderEnv = objBuilder.env + objBuilder.env = env + mocBuilderEnv = env.Moc.env + env.Moc.env = env + + # make a deep copy for the result; MocH objects will be appended + out_sources = source[:] + + for obj in source: + if not obj.has_builder(): + # binary obj file provided + if debug: + print "scons: qt: '%s' seems to be a binary. Discarded." % str(obj) + continue + cpp = obj.sources[0] + if not splitext(str(cpp))[1] in cxx_suffixes: + if debug: + print "scons: qt: '%s' is no cxx file. Discarded." % str(cpp) + # c or fortran source + continue + #cpp_contents = comment.sub('', cpp.get_text_contents()) + cpp_contents = cpp.get_text_contents() + h=None + for h_ext in header_extensions: + # try to find the header file in the corresponding source + # directory + hname = splitext(cpp.name)[0] + h_ext + h = find_file(hname, (cpp.get_dir(),), env.File) + if h: + if debug: + print "scons: qt: Scanning '%s' (header of '%s')" % (str(h), str(cpp)) + #h_contents = comment.sub('', h.get_text_contents()) + h_contents = h.get_text_contents() + break + if not h and debug: + print "scons: qt: no header for '%s'." % (str(cpp)) + if h and q_object_search.search(h_contents): + # h file with the Q_OBJECT macro found -> add moc_cpp + moc_cpp = env.Moc(h) + moc_o = objBuilder(moc_cpp) + out_sources.append(moc_o) + #moc_cpp.target_scanner = SCons.Defaults.CScan + if debug: + print "scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(h), str(moc_cpp)) + if cpp and q_object_search.search(cpp_contents): + # cpp file with Q_OBJECT macro found -> add moc + # (to be included in cpp) + moc = env.Moc(cpp) + env.Ignore(moc, moc) + if debug: + print "scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(cpp), str(moc)) + #moc.source_scanner = SCons.Defaults.CScan + # restore the original env attributes (FIXME) + objBuilder.env = objBuilderEnv + env.Moc.env = mocBuilderEnv + + return (target, out_sources) + +AutomocShared = _Automoc('SharedObject') +AutomocStatic = _Automoc('StaticObject') + +def _detect(env): + """Not really safe, but fast method to detect the QT library""" + QTDIR = None + if not QTDIR: + QTDIR = env.get('QTDIR',None) + if not QTDIR: + QTDIR = os.environ.get('QTDIR',None) + if not QTDIR: + moc = env.WhereIs('moc') + if moc: + QTDIR = os.path.dirname(os.path.dirname(moc)) + SCons.Warnings.warn( + QtdirNotFound, + "Could not detect qt, using moc executable as a hint (QTDIR=%s)" % QTDIR) + else: + QTDIR = None + SCons.Warnings.warn( + QtdirNotFound, + "Could not detect qt, using empty QTDIR") + return QTDIR + +def uicEmitter(target, source, env): + adjustixes = SCons.Util.adjustixes + bs = SCons.Util.splitext(str(source[0].name))[0] + bs = os.path.join(str(target[0].get_dir()),bs) + # first target (header) is automatically added by builder + if len(target) < 2: + # second target is implementation + target.append(adjustixes(bs, + env.subst('$QT_UICIMPLPREFIX'), + env.subst('$QT_UICIMPLSUFFIX'))) + if len(target) < 3: + # third target is moc file + target.append(adjustixes(bs, + env.subst('$QT_MOCHPREFIX'), + env.subst('$QT_MOCHSUFFIX'))) + return target, source + +def uicScannerFunc(node, env, path): + lookout = [] + lookout.extend(env['CPPPATH']) + lookout.append(str(node.rfile().dir)) + includes = re.findall("(.*?)", node.get_text_contents()) + result = [] + for incFile in includes: + dep = env.FindFile(incFile,lookout) + if dep: + result.append(dep) + return result + +uicScanner = SCons.Scanner.Base(uicScannerFunc, + name = "UicScanner", + node_class = SCons.Node.FS.File, + node_factory = SCons.Node.FS.File, + recursive = 0) + +def generate(env): + """Add Builders and construction variables for qt to an Environment.""" + CLVar = SCons.Util.CLVar + Action = SCons.Action.Action + Builder = SCons.Builder.Builder + + env.SetDefault(QTDIR = _detect(env), + QT_BINPATH = os.path.join('$QTDIR', 'bin'), + QT_CPPPATH = os.path.join('$QTDIR', 'include'), + QT_LIBPATH = os.path.join('$QTDIR', 'lib'), + QT_MOC = os.path.join('$QT_BINPATH','moc'), + QT_UIC = os.path.join('$QT_BINPATH','uic'), + QT_LIB = 'qt', # may be set to qt-mt + + QT_AUTOSCAN = 1, # scan for moc'able sources + + # Some QT specific flags. I don't expect someone wants to + # manipulate those ... + QT_UICIMPLFLAGS = CLVar(''), + QT_UICDECLFLAGS = CLVar(''), + QT_MOCFROMHFLAGS = CLVar(''), + QT_MOCFROMCXXFLAGS = CLVar('-i'), + + # suffixes/prefixes for the headers / sources to generate + QT_UICDECLPREFIX = '', + QT_UICDECLSUFFIX = '.h', + QT_UICIMPLPREFIX = 'uic_', + QT_UICIMPLSUFFIX = '$CXXFILESUFFIX', + QT_MOCHPREFIX = 'moc_', + QT_MOCHSUFFIX = '$CXXFILESUFFIX', + QT_MOCCXXPREFIX = '', + QT_MOCCXXSUFFIX = '.moc', + QT_UISUFFIX = '.ui', + + # Commands for the qt support ... + # command to generate header, implementation and moc-file + # from a .ui file + QT_UICCOM = [ + CLVar('$QT_UIC $QT_UICDECLFLAGS -o ${TARGETS[0]} $SOURCE'), + CLVar('$QT_UIC $QT_UICIMPLFLAGS -impl ${TARGETS[0].file} ' + '-o ${TARGETS[1]} $SOURCE'), + CLVar('$QT_MOC $QT_MOCFROMHFLAGS -o ${TARGETS[2]} ${TARGETS[0]}')], + # command to generate meta object information for a class + # declarated in a header + QT_MOCFROMHCOM = ( + '$QT_MOC $QT_MOCFROMHFLAGS -o ${TARGETS[0]} $SOURCE'), + # command to generate meta object information for a class + # declarated in a cpp file + QT_MOCFROMCXXCOM = [ + CLVar('$QT_MOC $QT_MOCFROMCXXFLAGS -o ${TARGETS[0]} $SOURCE'), + Action(checkMocIncluded,None)]) + + # ... and the corresponding builders + uicBld = Builder(action=SCons.Action.Action('$QT_UICCOM', '$QT_UICCOMSTR'), + emitter=uicEmitter, + src_suffix='$QT_UISUFFIX', + suffix='$QT_UICDECLSUFFIX', + prefix='$QT_UICDECLPREFIX', + source_scanner=uicScanner) + mocBld = Builder(action={}, prefix={}, suffix={}) + for h in header_extensions: + act = SCons.Action.Action('$QT_MOCFROMHCOM', '$QT_MOCFROMHCOMSTR') + mocBld.add_action(h, act) + mocBld.prefix[h] = '$QT_MOCHPREFIX' + mocBld.suffix[h] = '$QT_MOCHSUFFIX' + for cxx in cxx_suffixes: + act = SCons.Action.Action('$QT_MOCFROMCXXCOM', '$QT_MOCFROMCXXCOMSTR') + mocBld.add_action(cxx, act) + mocBld.prefix[cxx] = '$QT_MOCCXXPREFIX' + mocBld.suffix[cxx] = '$QT_MOCCXXSUFFIX' + + # register the builders + env['BUILDERS']['Uic'] = uicBld + env['BUILDERS']['Moc'] = mocBld + static_obj, shared_obj = SCons.Tool.createObjBuilders(env) + static_obj.add_src_builder('Uic') + shared_obj.add_src_builder('Uic') + + # We use the emitters of Program / StaticLibrary / SharedLibrary + # to scan for moc'able files + # We can't refer to the builders directly, we have to fetch them + # as Environment attributes because that sets them up to be called + # correctly later by our emitter. + env.AppendUnique(PROGEMITTER =[AutomocStatic], + SHLIBEMITTER=[AutomocShared], + LIBEMITTER =[AutomocStatic], + # Of course, we need to link against the qt libraries + CPPPATH=["$QT_CPPPATH"], + LIBPATH=["$QT_LIBPATH"], + LIBS=['$QT_LIB']) + +def exists(env): + return _detect(env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/qt.xml b/src/engine/SCons/Tool/qt.xml new file mode 100644 index 0000000..3e41c68 --- /dev/null +++ b/src/engine/SCons/Tool/qt.xml @@ -0,0 +1,314 @@ + + + +Sets construction variables for building Qt applications. + + +QTDIR +QT_BINPATH +QT_CPPPATH +QT_LIBPATH +QT_MOC +QT_UIC +QT_LIB +QT_AUTOSCAN +QT_UICIMPLFLAGS +QT_UICDECLFLAGS +QT_MOCFROMHFLAGS +QT_MOCFROMCXXFLAGS +QT_UICDECLPREFIX +QT_UICDECLSUFFIX +QT_UICIMPLPREFIX +QT_UICIMPLSUFFIX +QT_MOCHPREFIX +QT_MOCHSUFFIX +QT_MOCCXXPREFIX +QT_MOCCXXSUFFIX +QT_UISUFFIX +QT_UICCOM +QT_MOCFROMHCOM +QT_MOCFROMCXXCOM + + + + + + + +Builds an output file from a moc input file. Moc input files are either +header files or cxx files. This builder is only available after using the +tool 'qt'. See the &cv-link-QTDIR; variable for more information. +Example: + + +env.Moc('foo.h') # generates moc_foo.cc +env.Moc('foo.cpp') # generates foo.moc + + + + + + +Builds a header file, an implementation file and a moc file from an ui file. +and returns the corresponding nodes in the above order. +This builder is only available after using the tool 'qt'. Note: you can +specify .ui files directly as source +files to the &b-Program;, +&b-Library; and &b-SharedLibrary; builders +without using this builder. Using this builder lets you override the standard +naming conventions (be careful: prefixes are always prepended to names of +built files; if you don't want prefixes, you may set them to ``). +See the &cv-link-QTDIR; variable for more information. +Example: + + +env.Uic('foo.ui') # -> ['foo.h', 'uic_foo.cc', 'moc_foo.cc'] +env.Uic(target = Split('include/foo.h gen/uicfoo.cc gen/mocfoo.cc'), + source = 'foo.ui') # -> ['include/foo.h', 'gen/uicfoo.cc', 'gen/mocfoo.cc'] + + + + + + +The qt tool tries to take this from os.environ. +It also initializes all QT_* +construction variables listed below. +(Note that all paths are constructed +with python's os.path.join() method, +but are listed here with the '/' separator +for easier reading.) +In addition, the construction environment +variables &cv-link-CPPPATH;, +&cv-link-LIBPATH; and +&cv-link-LIBS; may be modified +and the variables +PROGEMITTER, SHLIBEMITTER and LIBEMITTER +are modified. Because the build-performance is affected when using this tool, +you have to explicitly specify it at Environment creation: + + +Environment(tools=['default','qt']) + + +The qt tool supports the following operations: + +Automatic moc file generation from header files. +You do not have to specify moc files explicitly, the tool does it for you. +However, there are a few preconditions to do so: Your header file must have +the same filebase as your implementation file and must stay in the same +directory. It must have one of the suffixes .h, .hpp, .H, .hxx, .hh. You +can turn off automatic moc file generation by setting QT_AUTOSCAN to 0. +See also the corresponding builder method +.B Moc() + +Automatic moc file generation from cxx files. +As stated in the qt documentation, include the moc file at the end of +the cxx file. Note that you have to include the file, which is generated +by the transformation ${QT_MOCCXXPREFIX}<basename>${QT_MOCCXXSUFFIX}, by default +<basename>.moc. A warning is generated after building the moc file, if you +do not include the correct file. If you are using VariantDir, you may +need to specify duplicate=1. You can turn off automatic moc file generation +by setting QT_AUTOSCAN to 0. See also the corresponding +&b-Moc; +builder method. + +Automatic handling of .ui files. +The implementation files generated from .ui files are handled much the same +as yacc or lex files. Each .ui file given as a source of Program, Library or +SharedLibrary will generate three files, the declaration file, the +implementation file and a moc file. Because there are also generated headers, +you may need to specify duplicate=1 in calls to VariantDir. +See also the corresponding +&b-Uic; +builder method. + + + + + +Turn off scanning for mocable files. Use the Moc Builder to explicitely +specify files to run moc on. + + + + + +The path where the qt binaries are installed. +The default value is '&cv-link-QTDIR;/bin'. + + + + + +The path where the qt header files are installed. +The default value is '&cv-link-QTDIR;/include'. +Note: If you set this variable to None, +the tool won't change the &cv-link-CPPPATH; +construction variable. + + + + + +Prints lots of debugging information while scanning for moc files. + + + + + +Default value is 'qt'. You may want to set this to 'qt-mt'. Note: If you set +this variable to None, the tool won't change the &cv-link-LIBS; variable. + + + + + +The path where the qt libraries are installed. +The default value is '&cv-link-QTDIR;/lib'. +Note: If you set this variable to None, +the tool won't change the &cv-link-LIBPATH; +construction variable. + + + + + +Default value is '&cv-link-QT_BINPATH;/moc'. + + + + + +Default value is ''. Prefix for moc output files, when source is a cxx file. + + + + + +Default value is '.moc'. Suffix for moc output files, when source is a cxx +file. + + + + + +Default value is '-i'. These flags are passed to moc, when moccing a +C++ file. + + + + + +Command to generate a moc file from a cpp file. + + + + + +The string displayed when generating a moc file from a cpp file. +If this is not set, then &cv-link-QT_MOCFROMCXXCOM; (the command line) is displayed. + + + + + +Command to generate a moc file from a header. + + + + + +The string displayed when generating a moc file from a cpp file. +If this is not set, then &cv-link-QT_MOCFROMHCOM; (the command line) is displayed. + + + + + +Default value is ''. These flags are passed to moc, when moccing a header +file. + + + + + +Default value is 'moc_'. Prefix for moc output files, when source is a header. + + + + + +Default value is '&cv-link-CXXFILESUFFIX;'. Suffix for moc output files, when source is +a header. + + + + + +Default value is '&cv-link-QT_BINPATH;/uic'. + + + + + +Command to generate header files from .ui files. + + + + + +The string displayed when generating header files from .ui files. +If this is not set, then &cv-link-QT_UICCOM; (the command line) is displayed. + + + + + +Default value is ''. These flags are passed to uic, when creating a a h +file from a .ui file. + + + + + +Default value is ''. Prefix for uic generated header files. + + + + + +Default value is '.h'. Suffix for uic generated header files. + + + + + +Default value is ''. These flags are passed to uic, when creating a cxx +file from a .ui file. + + + + + +Default value is 'uic_'. Prefix for uic generated implementation files. + + + + + +Default value is '&cv-link-CXXFILESUFFIX;'. Suffix for uic generated implementation +files. + + + + + +Default value is '.ui'. Suffix of designer input files. + + diff --git a/src/engine/SCons/Tool/rmic.py b/src/engine/SCons/Tool/rmic.py new file mode 100644 index 0000000..f41b38c --- /dev/null +++ b/src/engine/SCons/Tool/rmic.py @@ -0,0 +1,121 @@ +"""SCons.Tool.rmic + +Tool-specific initialization for rmic. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/rmic.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string + +import SCons.Action +import SCons.Builder +import SCons.Node.FS +import SCons.Util + +def emit_rmic_classes(target, source, env): + """Create and return lists of Java RMI stub and skeleton + class files to be created from a set of class files. + """ + class_suffix = env.get('JAVACLASSSUFFIX', '.class') + classdir = env.get('JAVACLASSDIR') + + if not classdir: + try: + s = source[0] + except IndexError: + classdir = '.' + else: + try: + classdir = s.attributes.java_classdir + except AttributeError: + classdir = '.' + classdir = env.Dir(classdir).rdir() + if str(classdir) == '.': + c_ = None + else: + c_ = str(classdir) + os.sep + + slist = [] + for src in source: + try: + classname = src.attributes.java_classname + except AttributeError: + classname = str(src) + if c_ and classname[:len(c_)] == c_: + classname = classname[len(c_):] + if class_suffix and classname[:-len(class_suffix)] == class_suffix: + classname = classname[-len(class_suffix):] + s = src.rfile() + s.attributes.java_classdir = classdir + s.attributes.java_classname = classname + slist.append(s) + + stub_suffixes = ['_Stub'] + if env.get('JAVAVERSION') == '1.4': + stub_suffixes.append('_Skel') + + tlist = [] + for s in source: + for suff in stub_suffixes: + fname = string.replace(s.attributes.java_classname, '.', os.sep) + \ + suff + class_suffix + t = target[0].File(fname) + t.attributes.java_lookupdir = target[0] + tlist.append(t) + + return tlist, source + +RMICAction = SCons.Action.Action('$RMICCOM', '$RMICCOMSTR') + +RMICBuilder = SCons.Builder.Builder(action = RMICAction, + emitter = emit_rmic_classes, + src_suffix = '$JAVACLASSSUFFIX', + target_factory = SCons.Node.FS.Dir, + source_factory = SCons.Node.FS.File) + +def generate(env): + """Add Builders and construction variables for rmic to an Environment.""" + env['BUILDERS']['RMIC'] = RMICBuilder + + env['RMIC'] = 'rmic' + env['RMICFLAGS'] = SCons.Util.CLVar('') + env['RMICCOM'] = '$RMIC $RMICFLAGS -d ${TARGET.attributes.java_lookupdir} -classpath ${SOURCE.attributes.java_classdir} ${SOURCES.attributes.java_classname}' + env['JAVACLASSSUFFIX'] = '.class' + +def exists(env): + return env.Detect('rmic') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/rmic.xml b/src/engine/SCons/Tool/rmic.xml new file mode 100644 index 0000000..eacc6bf --- /dev/null +++ b/src/engine/SCons/Tool/rmic.xml @@ -0,0 +1,93 @@ + + + +Sets construction variables for the &rmic; utility. + + +RMIC +RMICFLAGS +RMICCOM +JAVACLASSSUFFIX + + +RMICCOMSTR + + + + + +Builds stub and skeleton class files +for remote objects +from Java .class files. +The target is a directory +relative to which the stub +and skeleton class files will be written. +The source can be the names of .class files, +or the objects return from the +&b-Java; +builder method. + +If the construction variable +&cv-link-JAVACLASSDIR; +is set, either in the environment +or in the call to the +&b-RMIC; +builder method itself, +then the value of the variable +will be stripped from the +beginning of any .class +file names. + + +classes = env.Java(target = 'classdir', source = 'src') +env.RMIC(target = 'outdir1', source = classes) + +env.RMIC(target = 'outdir2', + source = ['package/foo.class', 'package/bar.class']) + +env.RMIC(target = 'outdir3', + source = ['classes/foo.class', 'classes/bar.class'], + JAVACLASSDIR = 'classes') + + + + + + +The Java RMI stub compiler. + + + + + +The command line used to compile stub +and skeleton class files +from Java classes that contain RMI implementations. +Any options specified in the &cv-link-RMICFLAGS; construction variable +are included on this command line. + + + + + +The string displayed when compiling +stub and skeleton class files +from Java classes that contain RMI implementations. +If this is not set, then &cv-link-RMICCOM; (the command line) is displayed. + + +env = Environment(RMICCOMSTR = "Generating stub/skeleton class files $TARGETS from $SOURCES") + + + + + + +General options passed to the Java RMI stub compiler. + + diff --git a/src/engine/SCons/Tool/rpcgen.py b/src/engine/SCons/Tool/rpcgen.py new file mode 100644 index 0000000..86feac9 --- /dev/null +++ b/src/engine/SCons/Tool/rpcgen.py @@ -0,0 +1,70 @@ +"""SCons.Tool.rpcgen + +Tool-specific initialization for RPCGEN tools. + +Three normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/rpcgen.py 4577 2009/12/27 19:44:43 scons" + +from SCons.Builder import Builder +import SCons.Util + +cmd = "cd ${SOURCE.dir} && $RPCGEN -%s $RPCGENFLAGS %s -o ${TARGET.abspath} ${SOURCE.file}" + +rpcgen_client = cmd % ('l', '$RPCGENCLIENTFLAGS') +rpcgen_header = cmd % ('h', '$RPCGENHEADERFLAGS') +rpcgen_service = cmd % ('m', '$RPCGENSERVICEFLAGS') +rpcgen_xdr = cmd % ('c', '$RPCGENXDRFLAGS') + +def generate(env): + "Add RPCGEN Builders and construction variables for an Environment." + + client = Builder(action=rpcgen_client, suffix='_clnt.c', src_suffix='.x') + header = Builder(action=rpcgen_header, suffix='.h', src_suffix='.x') + service = Builder(action=rpcgen_service, suffix='_svc.c', src_suffix='.x') + xdr = Builder(action=rpcgen_xdr, suffix='_xdr.c', src_suffix='.x') + env.Append(BUILDERS={'RPCGenClient' : client, + 'RPCGenHeader' : header, + 'RPCGenService' : service, + 'RPCGenXDR' : xdr}) + env['RPCGEN'] = 'rpcgen' + env['RPCGENFLAGS'] = SCons.Util.CLVar('') + env['RPCGENCLIENTFLAGS'] = SCons.Util.CLVar('') + env['RPCGENHEADERFLAGS'] = SCons.Util.CLVar('') + env['RPCGENSERVICEFLAGS'] = SCons.Util.CLVar('') + env['RPCGENXDRFLAGS'] = SCons.Util.CLVar('') + +def exists(env): + return env.Detect('rpcgen') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/rpcgen.xml b/src/engine/SCons/Tool/rpcgen.xml new file mode 100644 index 0000000..60848be --- /dev/null +++ b/src/engine/SCons/Tool/rpcgen.xml @@ -0,0 +1,137 @@ + + + +Sets construction variables for building with RPCGEN. + + +RPCGEN +RPCGENFLAGS +RPCGENCLIENTFLAGS +RPCGENHEADERFLAGS +RPCGENSERVICEFLAGS +RPCGENXDRFLAGS + + + + + + + +Generates an RPC client stub (_clnt.c) file +from a specified RPC (.x) source file. +Because rpcgen only builds output files +in the local directory, +the command will be executed +in the source file's directory by default. + + +# Builds src/rpcif_clnt.c +env.RPCGenClient('src/rpcif.x') + + + + + + +Generates an RPC header (.h) file +from a specified RPC (.x) source file. +Because rpcgen only builds output files +in the local directory, +the command will be executed +in the source file's directory by default. + + +# Builds src/rpcif.h +env.RPCGenHeader('src/rpcif.x') + + + + + + +Generates an RPC server-skeleton (_svc.c) file +from a specified RPC (.x) source file. +Because rpcgen only builds output files +in the local directory, +the command will be executed +in the source file's directory by default. + + +# Builds src/rpcif_svc.c +env.RPCGenClient('src/rpcif.x') + + + + + + +Generates an RPC XDR routine (_xdr.c) file +from a specified RPC (.x) source file. +Because rpcgen only builds output files +in the local directory, +the command will be executed +in the source file's directory by default. + + +# Builds src/rpcif_xdr.c +env.RPCGenClient('src/rpcif.x') + + + + + + +The RPC protocol compiler. + + + + + +Options passed to the RPC protocol compiler +when generating client side stubs. +These are in addition to any flags specified in the +&cv-link-RPCGENFLAGS; +construction variable. + + + + + +General options passed to the RPC protocol compiler. + + + + + +Options passed to the RPC protocol compiler +when generating a header file. +These are in addition to any flags specified in the +&cv-link-RPCGENFLAGS; +construction variable. + + + + + +Options passed to the RPC protocol compiler +when generating server side stubs. +These are in addition to any flags specified in the +&cv-link-RPCGENFLAGS; +construction variable. + + + + + +Options passed to the RPC protocol compiler +when generating XDR routines. +These are in addition to any flags specified in the +&cv-link-RPCGENFLAGS; +construction variable. + + diff --git a/src/engine/SCons/Tool/rpm.py b/src/engine/SCons/Tool/rpm.py new file mode 100644 index 0000000..19edd56 --- /dev/null +++ b/src/engine/SCons/Tool/rpm.py @@ -0,0 +1,132 @@ +"""SCons.Tool.rpm + +Tool-specific initialization for rpm. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +The rpm tool calls the rpmbuild command. The first and only argument should a +tar.gz consisting of the source file and a specfile. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/rpm.py 4577 2009/12/27 19:44:43 scons" + +import os +import re +import shutil +import subprocess + +import SCons.Builder +import SCons.Node.FS +import SCons.Util +import SCons.Action +import SCons.Defaults + +def get_cmd(source, env): + tar_file_with_included_specfile = source + if SCons.Util.is_List(source): + tar_file_with_included_specfile = source[0] + return "%s %s %s"%(env['RPM'], env['RPMFLAGS'], + tar_file_with_included_specfile.abspath ) + +def build_rpm(target, source, env): + # create a temporary rpm build root. + tmpdir = os.path.join( os.path.dirname( target[0].abspath ), 'rpmtemp' ) + if os.path.exists(tmpdir): + shutil.rmtree(tmpdir) + + # now create the mandatory rpm directory structure. + for d in ['RPMS', 'SRPMS', 'SPECS', 'BUILD']: + os.makedirs( os.path.join( tmpdir, d ) ) + + # set the topdir as an rpmflag. + env.Prepend( RPMFLAGS = '--define \'_topdir %s\'' % tmpdir ) + + # now call rpmbuild to create the rpm package. + handle = subprocess.Popen(get_cmd(source, env), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True) + output = handle.stdout.read() + status = handle.wait() + + if status: + raise SCons.Errors.BuildError( node=target[0], + errstr=output, + filename=str(target[0]) ) + else: + # XXX: assume that LC_ALL=c is set while running rpmbuild + output_files = re.compile( 'Wrote: (.*)' ).findall( output ) + + for output, input in zip( output_files, target ): + rpm_output = os.path.basename(output) + expected = os.path.basename(input.get_path()) + + assert expected == rpm_output, "got %s but expected %s" % (rpm_output, expected) + shutil.copy( output, input.abspath ) + + + # cleanup before leaving. + shutil.rmtree(tmpdir) + + return status + +def string_rpm(target, source, env): + try: + return env['RPMCOMSTR'] + except KeyError: + return get_cmd(source, env) + +rpmAction = SCons.Action.Action(build_rpm, string_rpm) + +RpmBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$RPMCOM', '$RPMCOMSTR'), + source_scanner = SCons.Defaults.DirScanner, + suffix = '$RPMSUFFIX') + + + +def generate(env): + """Add Builders and construction variables for rpm to an Environment.""" + try: + bld = env['BUILDERS']['Rpm'] + except KeyError: + bld = RpmBuilder + env['BUILDERS']['Rpm'] = bld + + env.SetDefault(RPM = 'LC_ALL=c rpmbuild') + env.SetDefault(RPMFLAGS = SCons.Util.CLVar('-ta')) + env.SetDefault(RPMCOM = rpmAction) + env.SetDefault(RPMSUFFIX = '.rpm') + +def exists(env): + return env.Detect('rpmbuild') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sgiar.py b/src/engine/SCons/Tool/sgiar.py new file mode 100644 index 0000000..278b4c6 --- /dev/null +++ b/src/engine/SCons/Tool/sgiar.py @@ -0,0 +1,68 @@ +"""SCons.Tool.sgiar + +Tool-specific initialization for SGI ar (library archive). If CC +exists, static libraries should be built with it, so the prelinker has +a chance to resolve C++ template instantiations. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sgiar.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +def generate(env): + """Add Builders and construction variables for ar to an Environment.""" + SCons.Tool.createStaticLibBuilder(env) + + if env.Detect('CC'): + env['AR'] = 'CC' + env['ARFLAGS'] = SCons.Util.CLVar('-ar') + env['ARCOM'] = '$AR $ARFLAGS -o $TARGET $SOURCES' + else: + env['AR'] = 'ar' + env['ARFLAGS'] = SCons.Util.CLVar('r') + env['ARCOM'] = '$AR $ARFLAGS $TARGET $SOURCES' + + env['SHLINK'] = '$LINK' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared') + env['SHLINKCOM'] = '$SHLINK $SHLINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + env['LIBPREFIX'] = 'lib' + env['LIBSUFFIX'] = '.a' + +def exists(env): + return env.Detect('CC') or env.Detect('ar') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sgiar.xml b/src/engine/SCons/Tool/sgiar.xml new file mode 100644 index 0000000..38be862 --- /dev/null +++ b/src/engine/SCons/Tool/sgiar.xml @@ -0,0 +1,24 @@ + + + +Sets construction variables for the SGI library archiver. + + +AR +ARFLAGS +ARCOMSTR +SHLINK +SHLINKFLAGS +LIBPREFIX +LIBSUFFIX + + +ARCOMSTR +SHLINKCOMSTR + + diff --git a/src/engine/SCons/Tool/sgic++.py b/src/engine/SCons/Tool/sgic++.py new file mode 100644 index 0000000..8aece3a --- /dev/null +++ b/src/engine/SCons/Tool/sgic++.py @@ -0,0 +1,58 @@ +"""SCons.Tool.sgic++ + +Tool-specific initialization for MIPSpro C++ on SGI. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sgic++.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +cplusplus = __import__('c++', globals(), locals(), []) + +def generate(env): + """Add Builders and construction variables for SGI MIPS C++ to an Environment.""" + + cplusplus.generate(env) + + env['CXX'] = 'CC' + env['CXXFLAGS'] = SCons.Util.CLVar('-LANG:std') + env['SHCXX'] = '$CXX' + env['SHOBJSUFFIX'] = '.o' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + +def exists(env): + return env.Detect('CC') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sgic++.xml b/src/engine/SCons/Tool/sgic++.xml new file mode 100644 index 0000000..918988b --- /dev/null +++ b/src/engine/SCons/Tool/sgic++.xml @@ -0,0 +1,18 @@ + + + +Sets construction variables for the SGI C++ compiler. + + +CXX +CXXFLAGS +SHCXX +SHOBJSUFFIX + + + diff --git a/src/engine/SCons/Tool/sgicc.py b/src/engine/SCons/Tool/sgicc.py new file mode 100644 index 0000000..ac311db --- /dev/null +++ b/src/engine/SCons/Tool/sgicc.py @@ -0,0 +1,53 @@ +"""SCons.Tool.sgicc + +Tool-specific initialization for MIPSPro cc on SGI. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sgicc.py 4577 2009/12/27 19:44:43 scons" + +import cc + +def generate(env): + """Add Builders and construction variables for gcc to an Environment.""" + cc.generate(env) + + env['CXX'] = 'CC' + env['SHOBJSUFFIX'] = '.o' + env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1 + +def exists(env): + return env.Detect('cc') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sgicc.xml b/src/engine/SCons/Tool/sgicc.xml new file mode 100644 index 0000000..668ecd1 --- /dev/null +++ b/src/engine/SCons/Tool/sgicc.xml @@ -0,0 +1,18 @@ + + + +Sets construction variables for the SGI C compiler. + + +CXX +SHOBJSUFFIX + + + + + diff --git a/src/engine/SCons/Tool/sgilink.py b/src/engine/SCons/Tool/sgilink.py new file mode 100644 index 0000000..3138782 --- /dev/null +++ b/src/engine/SCons/Tool/sgilink.py @@ -0,0 +1,63 @@ +"""SCons.Tool.sgilink + +Tool-specific initialization for the SGI MIPSPro linker on SGI. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sgilink.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +import link + +linkers = ['CC', 'cc'] + +def generate(env): + """Add Builders and construction variables for MIPSPro to an Environment.""" + link.generate(env) + + env['LINK'] = env.Detect(linkers) or 'cc' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared') + + # __RPATH is set to $_RPATH in the platform specification if that + # platform supports it. + env.Append(LINKFLAGS=['$__RPATH']) + env['RPATHPREFIX'] = '-rpath ' + env['RPATHSUFFIX'] = '' + env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}' + +def exists(env): + return env.Detect(linkers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sgilink.xml b/src/engine/SCons/Tool/sgilink.xml new file mode 100644 index 0000000..2618133 --- /dev/null +++ b/src/engine/SCons/Tool/sgilink.xml @@ -0,0 +1,17 @@ + + + +Sets construction variables for the SGI linker. + + +LINK +SHLINKFLAGS +RPATHPREFIX +RPATHSUFFIX + + diff --git a/src/engine/SCons/Tool/sunar.py b/src/engine/SCons/Tool/sunar.py new file mode 100644 index 0000000..afb1add --- /dev/null +++ b/src/engine/SCons/Tool/sunar.py @@ -0,0 +1,67 @@ +"""engine.SCons.Tool.sunar + +Tool-specific initialization for Solaris (Forte) ar (library archive). If CC +exists, static libraries should be built with it, so that template +instantians can be resolved. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sunar.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +def generate(env): + """Add Builders and construction variables for ar to an Environment.""" + SCons.Tool.createStaticLibBuilder(env) + + if env.Detect('CC'): + env['AR'] = 'CC' + env['ARFLAGS'] = SCons.Util.CLVar('-xar') + env['ARCOM'] = '$AR $ARFLAGS -o $TARGET $SOURCES' + else: + env['AR'] = 'ar' + env['ARFLAGS'] = SCons.Util.CLVar('r') + env['ARCOM'] = '$AR $ARFLAGS $TARGET $SOURCES' + + env['SHLINK'] = '$LINK' + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -G') + env['SHLINKCOM'] = '$SHLINK $SHLINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS' + env['LIBPREFIX'] = 'lib' + env['LIBSUFFIX'] = '.a' + +def exists(env): + return env.Detect('CC') or env.Detect('ar') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sunar.xml b/src/engine/SCons/Tool/sunar.xml new file mode 100644 index 0000000..ab40f8f --- /dev/null +++ b/src/engine/SCons/Tool/sunar.xml @@ -0,0 +1,25 @@ + + + +Sets construction variables for the Sun library archiver. + + +AR +ARFLAGS +ARCOM +SHLINK +SHLINKFLAGS +SHLINKCOM +LIBPREFIX +LIBSUFFIX + + +ARCOMSTR +SHLINKCOMSTR + + diff --git a/src/engine/SCons/Tool/sunc++.py b/src/engine/SCons/Tool/sunc++.py new file mode 100644 index 0000000..dcd88ef --- /dev/null +++ b/src/engine/SCons/Tool/sunc++.py @@ -0,0 +1,142 @@ +"""SCons.Tool.sunc++ + +Tool-specific initialization for C++ on SunOS / Solaris. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sunc++.py 4577 2009/12/27 19:44:43 scons" + +import SCons + +import os +import re +import subprocess + +cplusplus = __import__('c++', globals(), locals(), []) + +package_info = {} + +def get_package_info(package_name, pkginfo, pkgchk): + try: + return package_info[package_name] + except KeyError: + version = None + pathname = None + try: + sadm_contents = open('/var/sadm/install/contents', 'r').read() + except EnvironmentError: + pass + else: + sadm_re = re.compile('^(\S*/bin/CC)(=\S*)? %s$' % package_name, re.M) + sadm_match = sadm_re.search(sadm_contents) + if sadm_match: + pathname = os.path.dirname(sadm_match.group(1)) + + try: + p = subprocess.Popen([pkginfo, '-l', package_name], + stdout=subprocess.PIPE, + stderr=open('/dev/null', 'w')) + except EnvironmentError: + pass + else: + pkginfo_contents = p.communicate()[0] + version_re = re.compile('^ *VERSION:\s*(.*)$', re.M) + version_match = version_re.search(pkginfo_contents) + if version_match: + version = version_match.group(1) + + if pathname is None: + try: + p = subprocess.Popen([pkgchk, '-l', package_name], + stdout=subprocess.PIPE, + stderr=open('/dev/null', 'w')) + except EnvironmentError: + pass + else: + pkgchk_contents = p.communicate()[0] + pathname_re = re.compile(r'^Pathname:\s*(.*/bin/CC)$', re.M) + pathname_match = pathname_re.search(pkgchk_contents) + if pathname_match: + pathname = os.path.dirname(pathname_match.group(1)) + + package_info[package_name] = (pathname, version) + return package_info[package_name] + +# use the package installer tool lslpp to figure out where cppc and what +# version of it is installed +def get_cppc(env): + cxx = env.subst('$CXX') + if cxx: + cppcPath = os.path.dirname(cxx) + else: + cppcPath = None + + cppcVersion = None + + pkginfo = env.subst('$PKGINFO') + pkgchk = env.subst('$PKGCHK') + + for package in ['SPROcpl']: + path, version = get_package_info(package, pkginfo, pkgchk) + if path and version: + cppcPath, cppcVersion = path, version + break + + return (cppcPath, 'CC', 'CC', cppcVersion) + +def generate(env): + """Add Builders and construction variables for SunPRO C++.""" + path, cxx, shcxx, version = get_cppc(env) + if path: + cxx = os.path.join(path, cxx) + shcxx = os.path.join(path, shcxx) + + cplusplus.generate(env) + + env['CXX'] = cxx + env['SHCXX'] = shcxx + env['CXXVERSION'] = version + env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -KPIC') + env['SHOBJPREFIX'] = 'so_' + env['SHOBJSUFFIX'] = '.o' + +def exists(env): + path, cxx, shcxx, version = get_cppc(env) + if path and cxx: + cppc = os.path.join(path, cxx) + if os.path.exists(cppc): + return cppc + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sunc++.xml b/src/engine/SCons/Tool/sunc++.xml new file mode 100644 index 0000000..e6c6dbf --- /dev/null +++ b/src/engine/SCons/Tool/sunc++.xml @@ -0,0 +1,19 @@ + + + +Sets construction variables for the Sun C++ compiler. + + +CXX +SHCXX +CXXVERSION +SHCXXFLAGS +SHOBJPREFIX +SHOBJSUFFIX + + diff --git a/src/engine/SCons/Tool/suncc.py b/src/engine/SCons/Tool/suncc.py new file mode 100644 index 0000000..a37af82 --- /dev/null +++ b/src/engine/SCons/Tool/suncc.py @@ -0,0 +1,58 @@ +"""SCons.Tool.suncc + +Tool-specific initialization for Sun Solaris (Forte) CC and cc. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/suncc.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +import cc + +def generate(env): + """ + Add Builders and construction variables for Forte C and C++ compilers + to an Environment. + """ + cc.generate(env) + + env['CXX'] = 'CC' + env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -KPIC') + env['SHOBJPREFIX'] = 'so_' + env['SHOBJSUFFIX'] = '.o' + +def exists(env): + return env.Detect('CC') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/suncc.xml b/src/engine/SCons/Tool/suncc.xml new file mode 100644 index 0000000..0511180 --- /dev/null +++ b/src/engine/SCons/Tool/suncc.xml @@ -0,0 +1,17 @@ + + + +Sets construction variables for the Sun C compiler. + + +CXX +SHCCFLAGS +SHOBJPREFIX +SHOBJSUFFIX + + diff --git a/src/engine/SCons/Tool/sunf77.py b/src/engine/SCons/Tool/sunf77.py new file mode 100644 index 0000000..66b98ad --- /dev/null +++ b/src/engine/SCons/Tool/sunf77.py @@ -0,0 +1,63 @@ +"""SCons.Tool.sunf77 + +Tool-specific initialization for sunf77, the Sun Studio F77 compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sunf77.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +from FortranCommon import add_all_to_env + +compilers = ['sunf77', 'f77'] + +def generate(env): + """Add Builders and construction variables for sunf77 to an Environment.""" + add_all_to_env(env) + + fcomp = env.Detect(compilers) or 'f77' + env['FORTRAN'] = fcomp + env['F77'] = fcomp + + env['SHFORTRAN'] = '$FORTRAN' + env['SHF77'] = '$F77' + + env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -KPIC') + env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS -KPIC') + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sunf77.xml b/src/engine/SCons/Tool/sunf77.xml new file mode 100644 index 0000000..0365bea --- /dev/null +++ b/src/engine/SCons/Tool/sunf77.xml @@ -0,0 +1,19 @@ + + + +Set construction variables for the Sun &f77; Fortran compiler. + + +FORTRAN +F77 +SHFORTRAN +SHF77 +SHFORTRANFLAGS +SHF77FLAGS + + diff --git a/src/engine/SCons/Tool/sunf90.py b/src/engine/SCons/Tool/sunf90.py new file mode 100644 index 0000000..70fb2d1 --- /dev/null +++ b/src/engine/SCons/Tool/sunf90.py @@ -0,0 +1,64 @@ +"""SCons.Tool.sunf90 + +Tool-specific initialization for sunf90, the Sun Studio F90 compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sunf90.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +from FortranCommon import add_all_to_env + +compilers = ['sunf90', 'f90'] + +def generate(env): + """Add Builders and construction variables for sun f90 compiler to an + Environment.""" + add_all_to_env(env) + + fcomp = env.Detect(compilers) or 'f90' + env['FORTRAN'] = fcomp + env['F90'] = fcomp + + env['SHFORTRAN'] = '$FORTRAN' + env['SHF90'] = '$F90' + + env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -KPIC') + env['SHF90FLAGS'] = SCons.Util.CLVar('$F90FLAGS -KPIC') + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sunf90.xml b/src/engine/SCons/Tool/sunf90.xml new file mode 100644 index 0000000..f7a069a --- /dev/null +++ b/src/engine/SCons/Tool/sunf90.xml @@ -0,0 +1,19 @@ + + + +Set construction variables for the Sun &f90; Fortran compiler. + + +FORTRAN +F90 +SHFORTRAN +SHF90 +SHFORTRANFLAGS +SHF90FLAGS + + diff --git a/src/engine/SCons/Tool/sunf95.py b/src/engine/SCons/Tool/sunf95.py new file mode 100644 index 0000000..16800e8 --- /dev/null +++ b/src/engine/SCons/Tool/sunf95.py @@ -0,0 +1,64 @@ +"""SCons.Tool.sunf95 + +Tool-specific initialization for sunf95, the Sun Studio F95 compiler. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sunf95.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Util + +from FortranCommon import add_all_to_env + +compilers = ['sunf95', 'f95'] + +def generate(env): + """Add Builders and construction variables for sunf95 to an + Environment.""" + add_all_to_env(env) + + fcomp = env.Detect(compilers) or 'f95' + env['FORTRAN'] = fcomp + env['F95'] = fcomp + + env['SHFORTRAN'] = '$FORTRAN' + env['SHF95'] = '$F95' + + env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -KPIC') + env['SHF95FLAGS'] = SCons.Util.CLVar('$F95FLAGS -KPIC') + +def exists(env): + return env.Detect(compilers) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sunf95.xml b/src/engine/SCons/Tool/sunf95.xml new file mode 100644 index 0000000..0f1e005 --- /dev/null +++ b/src/engine/SCons/Tool/sunf95.xml @@ -0,0 +1,19 @@ + + + +Set construction variables for the Sun &f95; Fortran compiler. + + +FORTRAN +F95 +SHFORTRAN +SHF95 +SHFORTRANFLAGS +SHF95FLAGS + + diff --git a/src/engine/SCons/Tool/sunlink.py b/src/engine/SCons/Tool/sunlink.py new file mode 100644 index 0000000..88abae9 --- /dev/null +++ b/src/engine/SCons/Tool/sunlink.py @@ -0,0 +1,77 @@ +"""SCons.Tool.sunlink + +Tool-specific initialization for the Sun Solaris (Forte) linker. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/sunlink.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path + +import SCons.Util + +import link + +ccLinker = None + +# search for the acc compiler and linker front end + +try: + dirs = os.listdir('/opt') +except (IOError, OSError): + # Not being able to read the directory because it doesn't exist + # (IOError) or isn't readable (OSError) is okay. + dirs = [] + +for d in dirs: + linker = '/opt/' + d + '/bin/CC' + if os.path.exists(linker): + ccLinker = linker + break + +def generate(env): + """Add Builders and construction variables for Forte to an Environment.""" + link.generate(env) + + env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -G') + + env.Append(LINKFLAGS=['$__RPATH']) + env['RPATHPREFIX'] = '-R' + env['RPATHSUFFIX'] = '' + env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}' + +def exists(env): + return ccLinker + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/sunlink.xml b/src/engine/SCons/Tool/sunlink.xml new file mode 100644 index 0000000..17b407f --- /dev/null +++ b/src/engine/SCons/Tool/sunlink.xml @@ -0,0 +1,16 @@ + + + +Sets construction variables for the Sun linker. + + +SHLINKFLAGS +RPATHPREFIX +RPATHSUFFIX + + diff --git a/src/engine/SCons/Tool/swig.py b/src/engine/SCons/Tool/swig.py new file mode 100644 index 0000000..1dfbb40 --- /dev/null +++ b/src/engine/SCons/Tool/swig.py @@ -0,0 +1,186 @@ +"""SCons.Tool.swig + +Tool-specific initialization for swig. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/swig.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import re +import string +import subprocess + +import SCons.Action +import SCons.Defaults +import SCons.Scanner +import SCons.Tool +import SCons.Util + +SwigAction = SCons.Action.Action('$SWIGCOM', '$SWIGCOMSTR') + +def swigSuffixEmitter(env, source): + if '-c++' in SCons.Util.CLVar(env.subst("$SWIGFLAGS", source=source)): + return '$SWIGCXXFILESUFFIX' + else: + return '$SWIGCFILESUFFIX' + +# Match '%module test', as well as '%module(directors="1") test' +# Also allow for test to be quoted (SWIG permits double quotes, but not single) +_reModule = re.compile(r'%module(\s*\(.*\))?\s+("?)(.+)\2') + +def _find_modules(src): + """Find all modules referenced by %module lines in `src`, a SWIG .i file. + Returns a list of all modules, and a flag set if SWIG directors have + been requested (SWIG will generate an additional header file in this + case.)""" + directors = 0 + mnames = [] + try: + matches = _reModule.findall(open(src).read()) + except IOError: + # If the file's not yet generated, guess the module name from the filename + matches = [] + mnames.append(os.path.splitext(src)[0]) + + for m in matches: + mnames.append(m[2]) + directors = directors or string.find(m[0], 'directors') >= 0 + return mnames, directors + +def _add_director_header_targets(target, env): + # Directors only work with C++ code, not C + suffix = env.subst(env['SWIGCXXFILESUFFIX']) + # For each file ending in SWIGCXXFILESUFFIX, add a new target director + # header by replacing the ending with SWIGDIRECTORSUFFIX. + for x in target[:]: + n = x.name + d = x.dir + if n[-len(suffix):] == suffix: + target.append(d.File(n[:-len(suffix)] + env['SWIGDIRECTORSUFFIX'])) + +def _swigEmitter(target, source, env): + swigflags = env.subst("$SWIGFLAGS", target=target, source=source) + flags = SCons.Util.CLVar(swigflags) + for src in source: + src = str(src.rfile()) + mnames = None + if "-python" in flags and "-noproxy" not in flags: + if mnames is None: + mnames, directors = _find_modules(src) + if directors: + _add_director_header_targets(target, env) + python_files = map(lambda m: m + ".py", mnames) + outdir = env.subst('$SWIGOUTDIR', target=target, source=source) + # .py files should be generated in SWIGOUTDIR if specified, + # otherwise in the same directory as the target + if outdir: + python_files = map(lambda j, o=outdir, e=env: + e.fs.File(os.path.join(o, j)), + python_files) + else: + python_files = map(lambda m, d=target[0].dir: + d.File(m), python_files) + target.extend(python_files) + if "-java" in flags: + if mnames is None: + mnames, directors = _find_modules(src) + if directors: + _add_director_header_targets(target, env) + java_files = map(lambda m: [m + ".java", m + "JNI.java"], mnames) + java_files = SCons.Util.flatten(java_files) + outdir = env.subst('$SWIGOUTDIR', target=target, source=source) + if outdir: + java_files = map(lambda j, o=outdir: os.path.join(o, j), java_files) + java_files = map(env.fs.File, java_files) + for jf in java_files: + t_from_s = lambda t, p, s, x: t.dir + SCons.Util.AddMethod(jf, t_from_s, 'target_from_source') + target.extend(java_files) + return (target, source) + +def _get_swig_version(env): + """Run the SWIG command line tool to get and return the version number""" + pipe = SCons.Action._subproc(env, [env['SWIG'], '-version'], + stdin = 'devnull', + stderr = 'devnull', + stdout = subprocess.PIPE) + if pipe.wait() != 0: return + + out = pipe.stdout.read() + match = re.search(r'SWIG Version\s+(\S+)$', out, re.MULTILINE) + if match: + return match.group(1) + +def generate(env): + """Add Builders and construction variables for swig to an Environment.""" + c_file, cxx_file = SCons.Tool.createCFileBuilders(env) + + c_file.suffix['.i'] = swigSuffixEmitter + cxx_file.suffix['.i'] = swigSuffixEmitter + + c_file.add_action('.i', SwigAction) + c_file.add_emitter('.i', _swigEmitter) + cxx_file.add_action('.i', SwigAction) + cxx_file.add_emitter('.i', _swigEmitter) + + java_file = SCons.Tool.CreateJavaFileBuilder(env) + + java_file.suffix['.i'] = swigSuffixEmitter + + java_file.add_action('.i', SwigAction) + java_file.add_emitter('.i', _swigEmitter) + + env['SWIG'] = 'swig' + env['SWIGVERSION'] = _get_swig_version(env) + env['SWIGFLAGS'] = SCons.Util.CLVar('') + env['SWIGDIRECTORSUFFIX'] = '_wrap.h' + env['SWIGCFILESUFFIX'] = '_wrap$CFILESUFFIX' + env['SWIGCXXFILESUFFIX'] = '_wrap$CXXFILESUFFIX' + env['_SWIGOUTDIR'] = r'${"-outdir \"%s\"" % SWIGOUTDIR}' + env['SWIGPATH'] = [] + env['SWIGINCPREFIX'] = '-I' + env['SWIGINCSUFFIX'] = '' + env['_SWIGINCFLAGS'] = '$( ${_concat(SWIGINCPREFIX, SWIGPATH, SWIGINCSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)' + env['SWIGCOM'] = '$SWIG -o $TARGET ${_SWIGOUTDIR} ${_SWIGINCFLAGS} $SWIGFLAGS $SOURCES' + + expr = '^[ \t]*%[ \t]*(?:include|import|extern)[ \t]*(<|"?)([^>\s"]+)(?:>|"?)' + scanner = SCons.Scanner.ClassicCPP("SWIGScan", ".i", "SWIGPATH", expr) + + env.Append(SCANNERS = scanner) + +def exists(env): + return env.Detect(['swig']) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/swig.xml b/src/engine/SCons/Tool/swig.xml new file mode 100644 index 0000000..2716a33 --- /dev/null +++ b/src/engine/SCons/Tool/swig.xml @@ -0,0 +1,212 @@ + + + +Sets construction variables for the SWIG interface generator. + + +SWIG +SWIGFLAGS +SWIGDIRECTORSUFFIX +SWIGCFILESUFFIX +SWIGCXXFILESUFFIX +_SWIGINCFLAGS +SWIGINCPREFIX +SWIGINCSUFFIX +SWIGCOM +SWIGPATH +SWIGVERSION + + +SWIGCOMSTR + + + + + +The scripting language wrapper and interface generator. + + + + + +The suffix that will be used for intermediate C +source files generated by +the scripting language wrapper and interface generator. +The default value is +_wrap&cv-link-CFILESUFFIX;. +By default, this value is used whenever the + +option is +not +specified as part of the +&cv-link-SWIGFLAGS; +construction variable. + + + + + +The suffix that will be used for intermediate C++ header +files generated by the scripting language wrapper and interface generator. +These are only generated for C++ code when the SWIG 'directors' feature is +turned on. +The default value is +_wrap.h. + + + + + +The command line used to call +the scripting language wrapper and interface generator. + + + + + +The string displayed when calling +the scripting language wrapper and interface generator. +If this is not set, then &cv-link-SWIGCOM; (the command line) is displayed. + + + + + +The suffix that will be used for intermediate C++ +source files generated by +the scripting language wrapper and interface generator. +The default value is +_wrap&cv-link-CFILESUFFIX;. +By default, this value is used whenever the +-c++ +option is specified as part of the +&cv-link-SWIGFLAGS; +construction variable. + + + + + +General options passed to +the scripting language wrapper and interface generator. +This is where you should set +, +, +, +or whatever other options you want to specify to SWIG. +If you set the + +option in this variable, +&scons; +will, by default, +generate a C++ intermediate source file +with the extension that is specified as the +&cv-link-CXXFILESUFFIX; +variable. + + + + + +An automatically-generated construction variable +containing the SWIG command-line options +for specifying directories to be searched for included files. +The value of &cv-_SWIGINCFLAGS; is created +by appending &cv-SWIGINCPREFIX; and &cv-SWIGINCSUFFIX; +to the beginning and end +of each directory in &cv-SWIGPATH;. + + + + + +The prefix used to specify an include directory on the SWIG command line. +This will be appended to the beginning of each directory +in the &cv-SWIGPATH; construction variable +when the &cv-_SWIGINCFLAGS; variable is automatically generated. + + + + + +The suffix used to specify an include directory on the SWIG command line. +This will be appended to the end of each directory +in the &cv-SWIGPATH; construction variable +when the &cv-_SWIGINCFLAGS; variable is automatically generated. + + + + + +Specifies the output directory in which +the scripting language wrapper and interface generator +should place generated language-specific files. +This will be used by SCons to identify +the files that will be generated by the &swig; call, +and translated into the +swig -outdir option on the command line. + + + + + +The list of directories that the scripting language wrapper +and interface generate will search for included files. +The SWIG implicit dependency scanner will search these +directories for include files. +The default is to use the same path +specified as &cv-CPPPATH;. + +Don't explicitly put include directory +arguments in SWIGFLAGS; +the result will be non-portable +and the directories will not be searched by the dependency scanner. +Note: directory names in SWIGPATH will be looked-up relative to the SConscript +directory when they are used in a command. +To force +&scons; +to look-up a directory relative to the root of the source tree use #: + + +env = Environment(SWIGPATH='#/include') + + +The directory look-up can also be forced using the +&Dir;() +function: + + +include = Dir('include') +env = Environment(SWIGPATH=include) + + +The directory list will be added to command lines +through the automatically-generated +&cv-_SWIGINCFLAGS; +construction variable, +which is constructed by +appending the values of the +&cv-SWIGINCPREFIX; and &cv-SWIGINCSUFFIX; +construction variables +to the beginning and end +of each directory in &cv-SWIGPATH;. +Any command lines you define that need +the SWIGPATH directory list should +include &cv-_SWIGINCFLAGS;: + + +env = Environment(SWIGCOM="my_swig -o $TARGET $_SWIGINCFLAGS $SORUCES") + + + + + + +The version number of the SWIG tool. + + diff --git a/src/engine/SCons/Tool/tar.py b/src/engine/SCons/Tool/tar.py new file mode 100644 index 0000000..7d74004 --- /dev/null +++ b/src/engine/SCons/Tool/tar.py @@ -0,0 +1,73 @@ +"""SCons.Tool.tar + +Tool-specific initialization for tar. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/tar.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Action +import SCons.Builder +import SCons.Defaults +import SCons.Node.FS +import SCons.Util + +tars = ['tar', 'gtar'] + +TarAction = SCons.Action.Action('$TARCOM', '$TARCOMSTR') + +TarBuilder = SCons.Builder.Builder(action = TarAction, + source_factory = SCons.Node.FS.Entry, + source_scanner = SCons.Defaults.DirScanner, + suffix = '$TARSUFFIX', + multi = 1) + + +def generate(env): + """Add Builders and construction variables for tar to an Environment.""" + try: + bld = env['BUILDERS']['Tar'] + except KeyError: + bld = TarBuilder + env['BUILDERS']['Tar'] = bld + + env['TAR'] = env.Detect(tars) or 'gtar' + env['TARFLAGS'] = SCons.Util.CLVar('-c') + env['TARCOM'] = '$TAR $TARFLAGS -f $TARGET $SOURCES' + env['TARSUFFIX'] = '.tar' + +def exists(env): + return env.Detect(tars) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/tar.xml b/src/engine/SCons/Tool/tar.xml new file mode 100644 index 0000000..4e6926a --- /dev/null +++ b/src/engine/SCons/Tool/tar.xml @@ -0,0 +1,95 @@ + + + +Sets construction variables for the &tar; archiver. + + +TAR +TARFLAGS +TARCOM +TARSUFFIX + + +TARCOMSTR + + + + + +Builds a tar archive of the specified files +and/or directories. +Unlike most builder methods, +the +&b-Tar; +builder method may be called multiple times +for a given target; +each additional call +adds to the list of entries +that will be built into the archive. +Any source directories will +be scanned for changes to +any on-disk files, +regardless of whether or not +&scons; +knows about them from other Builder or function calls. + + +env.Tar('src.tar', 'src') + +# Create the stuff.tar file. +env.Tar('stuff', ['subdir1', 'subdir2']) +# Also add "another" to the stuff.tar file. +env.Tar('stuff', 'another') + +# Set TARFLAGS to create a gzip-filtered archive. +env = Environment(TARFLAGS = '-c -z') +env.Tar('foo.tar.gz', 'foo') + +# Also set the suffix to .tgz. +env = Environment(TARFLAGS = '-c -z', + TARSUFFIX = '.tgz') +env.Tar('foo') + + + + + + +The tar archiver. + + + + + +The command line used to call the tar archiver. + + + + + +The string displayed when archiving files +using the tar archiver. +If this is not set, then &cv-link-TARCOM; (the command line) is displayed. + + +env = Environment(TARCOMSTR = "Archiving $TARGET") + + + + + + +General options passed to the tar archiver. + + + + + +The suffix used for tar file names. + + diff --git a/src/engine/SCons/Tool/tex.py b/src/engine/SCons/Tool/tex.py new file mode 100644 index 0000000..f12675f --- /dev/null +++ b/src/engine/SCons/Tool/tex.py @@ -0,0 +1,792 @@ +"""SCons.Tool.tex + +Tool-specific initialization for TeX. +Generates .dvi files from .tex files + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/tex.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import re +import string +import shutil + +import SCons.Action +import SCons.Node +import SCons.Node.FS +import SCons.Util +import SCons.Scanner.LaTeX + +Verbose = False + +must_rerun_latex = True + +# these are files that just need to be checked for changes and then rerun latex +check_suffixes = ['.toc', '.lof', '.lot', '.out', '.nav', '.snm'] + +# these are files that require bibtex or makeindex to be run when they change +all_suffixes = check_suffixes + ['.bbl', '.idx', '.nlo', '.glo', '.acn'] + +# +# regular expressions used to search for Latex features +# or outputs that require rerunning latex +# +# search for all .aux files opened by latex (recorded in the .fls file) +openout_aux_re = re.compile(r"INPUT *(.*\.aux)") + +#printindex_re = re.compile(r"^[^%]*\\printindex", re.MULTILINE) +#printnomenclature_re = re.compile(r"^[^%]*\\printnomenclature", re.MULTILINE) +#printglossary_re = re.compile(r"^[^%]*\\printglossary", re.MULTILINE) + +# search to find rerun warnings +warning_rerun_str = '(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)' +warning_rerun_re = re.compile(warning_rerun_str, re.MULTILINE) + +# search to find citation rerun warnings +rerun_citations_str = "^LaTeX Warning:.*\n.*Rerun to get citations correct" +rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE) + +# search to find undefined references or citations warnings +undefined_references_str = '(^LaTeX Warning:.*undefined references)|(^Package \w+ Warning:.*undefined citations)' +undefined_references_re = re.compile(undefined_references_str, re.MULTILINE) + +# used by the emitter +auxfile_re = re.compile(r".", re.MULTILINE) +tableofcontents_re = re.compile(r"^[^%\n]*\\tableofcontents", re.MULTILINE) +makeindex_re = re.compile(r"^[^%\n]*\\makeindex", re.MULTILINE) +bibliography_re = re.compile(r"^[^%\n]*\\bibliography", re.MULTILINE) +listoffigures_re = re.compile(r"^[^%\n]*\\listoffigures", re.MULTILINE) +listoftables_re = re.compile(r"^[^%\n]*\\listoftables", re.MULTILINE) +hyperref_re = re.compile(r"^[^%\n]*\\usepackage.*\{hyperref\}", re.MULTILINE) +makenomenclature_re = re.compile(r"^[^%\n]*\\makenomenclature", re.MULTILINE) +makeglossary_re = re.compile(r"^[^%\n]*\\makeglossary", re.MULTILINE) +makeglossaries_re = re.compile(r"^[^%\n]*\\makeglossaries", re.MULTILINE) +makeacronyms_re = re.compile(r"^[^%\n]*\\makeglossaries", re.MULTILINE) +beamer_re = re.compile(r"^[^%\n]*\\documentclass\{beamer\}", re.MULTILINE) + +# search to find all files included by Latex +include_re = re.compile(r'^[^%\n]*\\(?:include|input){([^}]*)}', re.MULTILINE) + +# search to find all graphics files included by Latex +includegraphics_re = re.compile(r'^[^%\n]*\\(?:includegraphics(?:\[[^\]]+\])?){([^}]*)}', re.MULTILINE) + +# search to find all files opened by Latex (recorded in .log file) +openout_re = re.compile(r"OUTPUT *(.*)") + +# list of graphics file extensions for TeX and LaTeX +TexGraphics = SCons.Scanner.LaTeX.TexGraphics +LatexGraphics = SCons.Scanner.LaTeX.LatexGraphics + +# An Action sufficient to build any generic tex file. +TeXAction = None + +# An action to build a latex file. This action might be needed more +# than once if we are dealing with labels and bibtex. +LaTeXAction = None + +# An action to run BibTeX on a file. +BibTeXAction = None + +# An action to run MakeIndex on a file. +MakeIndexAction = None + +# An action to run MakeIndex (for nomencl) on a file. +MakeNclAction = None + +# An action to run MakeIndex (for glossary) on a file. +MakeGlossaryAction = None + +# An action to run MakeIndex (for acronyms) on a file. +MakeAcronymsAction = None + +# Used as a return value of modify_env_var if the variable is not set. +_null = SCons.Scanner.LaTeX._null + +modify_env_var = SCons.Scanner.LaTeX.modify_env_var + +def FindFile(name,suffixes,paths,env,requireExt=False): + if requireExt: + name,ext = SCons.Util.splitext(name) + # if the user gave an extension use it. + if ext: + name = name + ext + if Verbose: + print " searching for '%s' with extensions: " % name,suffixes + + for path in paths: + testName = os.path.join(path,name) + if Verbose: + print " look for '%s'" % testName + if os.path.exists(testName): + if Verbose: + print " found '%s'" % testName + return env.fs.File(testName) + else: + name_ext = SCons.Util.splitext(testName)[1] + if name_ext: + continue + + # if no suffix try adding those passed in + for suffix in suffixes: + testNameExt = testName + suffix + if Verbose: + print " look for '%s'" % testNameExt + + if os.path.exists(testNameExt): + if Verbose: + print " found '%s'" % testNameExt + return env.fs.File(testNameExt) + if Verbose: + print " did not find '%s'" % name + return None + +def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None): + """A builder for LaTeX files that checks the output in the aux file + and decides how many times to use LaTeXAction, and BibTeXAction.""" + + global must_rerun_latex + + # This routine is called with two actions. In this file for DVI builds + # with LaTeXAction and from the pdflatex.py with PDFLaTeXAction + # set this up now for the case where the user requests a different extension + # for the target filename + if (XXXLaTeXAction == LaTeXAction): + callerSuffix = ".dvi" + else: + callerSuffix = env['PDFSUFFIX'] + + basename = SCons.Util.splitext(str(source[0]))[0] + basedir = os.path.split(str(source[0]))[0] + basefile = os.path.split(str(basename))[1] + abspath = os.path.abspath(basedir) + + targetext = os.path.splitext(str(target[0]))[1] + targetdir = os.path.split(str(target[0]))[0] + + saved_env = {} + for var in SCons.Scanner.LaTeX.LaTeX.env_variables: + saved_env[var] = modify_env_var(env, var, abspath) + + # Create base file names with the target directory since the auxiliary files + # will be made there. That's because the *COM variables have the cd + # command in the prolog. We check + # for the existence of files before opening them--even ones like the + # aux file that TeX always creates--to make it possible to write tests + # with stubs that don't necessarily generate all of the same files. + + targetbase = os.path.join(targetdir, basefile) + + # if there is a \makeindex there will be a .idx and thus + # we have to run makeindex at least once to keep the build + # happy even if there is no index. + # Same for glossaries and nomenclature + src_content = source[0].get_text_contents() + run_makeindex = makeindex_re.search(src_content) and not os.path.exists(targetbase + '.idx') + run_nomenclature = makenomenclature_re.search(src_content) and not os.path.exists(targetbase + '.nlo') + run_glossary = makeglossary_re.search(src_content) and not os.path.exists(targetbase + '.glo') + run_glossaries = makeglossaries_re.search(src_content) and not os.path.exists(targetbase + '.glo') + run_acronyms = makeacronyms_re.search(src_content) and not os.path.exists(targetbase + '.acn') + + saved_hashes = {} + suffix_nodes = {} + + for suffix in all_suffixes: + theNode = env.fs.File(targetbase + suffix) + suffix_nodes[suffix] = theNode + saved_hashes[suffix] = theNode.get_csig() + + if Verbose: + print "hashes: ",saved_hashes + + must_rerun_latex = True + + # + # routine to update MD5 hash and compare + # + # TODO(1.5): nested scopes + def check_MD5(filenode, suffix, saved_hashes=saved_hashes, targetbase=targetbase): + global must_rerun_latex + # two calls to clear old csig + filenode.clear_memoized_values() + filenode.ninfo = filenode.new_ninfo() + new_md5 = filenode.get_csig() + + if saved_hashes[suffix] == new_md5: + if Verbose: + print "file %s not changed" % (targetbase+suffix) + return False # unchanged + saved_hashes[suffix] = new_md5 + must_rerun_latex = True + if Verbose: + print "file %s changed, rerunning Latex, new hash = " % (targetbase+suffix), new_md5 + return True # changed + + # generate the file name that latex will generate + resultfilename = targetbase + callerSuffix + + count = 0 + + while (must_rerun_latex and count < int(env.subst('$LATEXRETRIES'))) : + result = XXXLaTeXAction(target, source, env) + if result != 0: + return result + + count = count + 1 + + must_rerun_latex = False + # Decide if various things need to be run, or run again. + + # Read the log file to find warnings/errors + logfilename = targetbase + '.log' + logContent = '' + if os.path.exists(logfilename): + logContent = open(logfilename, "rb").read() + + + # Read the fls file to find all .aux files + flsfilename = targetbase + '.fls' + flsContent = '' + auxfiles = [] + if os.path.exists(flsfilename): + flsContent = open(flsfilename, "rb").read() + auxfiles = openout_aux_re.findall(flsContent) + if Verbose: + print "auxfiles ",auxfiles + + # Now decide if bibtex will need to be run. + # The information that bibtex reads from the .aux file is + # pass-independent. If we find (below) that the .bbl file is unchanged, + # then the last latex saw a correct bibliography. + # Therefore only do this on the first pass + if count == 1: + for auxfilename in auxfiles: + target_aux = os.path.join(targetdir, auxfilename) + if os.path.exists(target_aux): + content = open(target_aux, "rb").read() + if string.find(content, "bibdata") != -1: + if Verbose: + print "Need to run bibtex" + bibfile = env.fs.File(targetbase) + result = BibTeXAction(bibfile, bibfile, env) + if result != 0: + print env['BIBTEX']," returned an error, check the blg file" + return result + must_rerun_latex = check_MD5(suffix_nodes['.bbl'],'.bbl') + break + + # Now decide if latex will need to be run again due to index. + if check_MD5(suffix_nodes['.idx'],'.idx') or (count == 1 and run_makeindex): + # We must run makeindex + if Verbose: + print "Need to run makeindex" + idxfile = suffix_nodes['.idx'] + result = MakeIndexAction(idxfile, idxfile, env) + if result != 0: + print env['MAKEINDEX']," returned an error, check the ilg file" + return result + + # TO-DO: need to add a way for the user to extend this list for whatever + # auxiliary files they create in other (or their own) packages + # Harder is case is where an action needs to be called -- that should be rare (I hope?) + + for index in check_suffixes: + check_MD5(suffix_nodes[index],index) + + # Now decide if latex will need to be run again due to nomenclature. + if check_MD5(suffix_nodes['.nlo'],'.nlo') or (count == 1 and run_nomenclature): + # We must run makeindex + if Verbose: + print "Need to run makeindex for nomenclature" + nclfile = suffix_nodes['.nlo'] + result = MakeNclAction(nclfile, nclfile, env) + if result != 0: + print env['MAKENCL']," (nomenclature) returned an error, check the nlg file" + #return result + + # Now decide if latex will need to be run again due to glossary. + if check_MD5(suffix_nodes['.glo'],'.glo') or (count == 1 and run_glossaries) or (count == 1 and run_glossary): + # We must run makeindex + if Verbose: + print "Need to run makeindex for glossary" + glofile = suffix_nodes['.glo'] + result = MakeGlossaryAction(glofile, glofile, env) + if result != 0: + print env['MAKEGLOSSARY']," (glossary) returned an error, check the glg file" + #return result + + # Now decide if latex will need to be run again due to acronyms. + if check_MD5(suffix_nodes['.acn'],'.acn') or (count == 1 and run_acronyms): + # We must run makeindex + if Verbose: + print "Need to run makeindex for acronyms" + acrfile = suffix_nodes['.acn'] + result = MakeAcronymsAction(acrfile, acrfile, env) + if result != 0: + print env['MAKEACRONYMS']," (acronymns) returned an error, check the alg file" + return result + + # Now decide if latex needs to be run yet again to resolve warnings. + if warning_rerun_re.search(logContent): + must_rerun_latex = True + if Verbose: + print "rerun Latex due to latex or package rerun warning" + + if rerun_citations_re.search(logContent): + must_rerun_latex = True + if Verbose: + print "rerun Latex due to 'Rerun to get citations correct' warning" + + if undefined_references_re.search(logContent): + must_rerun_latex = True + if Verbose: + print "rerun Latex due to undefined references or citations" + + if (count >= int(env.subst('$LATEXRETRIES')) and must_rerun_latex): + print "reached max number of retries on Latex ,",int(env.subst('$LATEXRETRIES')) +# end of while loop + + # rename Latex's output to what the target name is + if not (str(target[0]) == resultfilename and os.path.exists(resultfilename)): + if os.path.exists(resultfilename): + print "move %s to %s" % (resultfilename, str(target[0]), ) + shutil.move(resultfilename,str(target[0])) + + # Original comment (when TEXPICTS was not restored): + # The TEXPICTS enviroment variable is needed by a dvi -> pdf step + # later on Mac OSX so leave it + # + # It is also used when searching for pictures (implicit dependencies). + # Why not set the variable again in the respective builder instead + # of leaving local modifications in the environment? What if multiple + # latex builds in different directories need different TEXPICTS? + for var in SCons.Scanner.LaTeX.LaTeX.env_variables: + if var == 'TEXPICTS': + continue + if saved_env[var] is _null: + try: + del env['ENV'][var] + except KeyError: + pass # was never set + else: + env['ENV'][var] = saved_env[var] + + return result + +def LaTeXAuxAction(target = None, source= None, env=None): + result = InternalLaTeXAuxAction( LaTeXAction, target, source, env ) + return result + +LaTeX_re = re.compile("\\\\document(style|class)") + +def is_LaTeX(flist,env,abspath): + """Scan a file list to decide if it's TeX- or LaTeX-flavored.""" + + # We need to scan files that are included in case the + # \documentclass command is in them. + + # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS'] + savedpath = modify_env_var(env, 'TEXINPUTS', abspath) + paths = env['ENV']['TEXINPUTS'] + if SCons.Util.is_List(paths): + pass + else: + # Split at os.pathsep to convert into absolute path + # TODO(1.5) + #paths = paths.split(os.pathsep) + paths = string.split(paths, os.pathsep) + + # now that we have the path list restore the env + if savedpath is _null: + try: + del env['ENV']['TEXINPUTS'] + except KeyError: + pass # was never set + else: + env['ENV']['TEXINPUTS'] = savedpath + if Verbose: + print "is_LaTeX search path ",paths + print "files to search :",flist + + # Now that we have the search path and file list, check each one + for f in flist: + if Verbose: + print " checking for Latex source ",str(f) + + content = f.get_text_contents() + if LaTeX_re.search(content): + if Verbose: + print "file %s is a LaTeX file" % str(f) + return 1 + if Verbose: + print "file %s is not a LaTeX file" % str(f) + + # now find included files + inc_files = [ ] + inc_files.extend( include_re.findall(content) ) + if Verbose: + print "files included by '%s': "%str(f),inc_files + # inc_files is list of file names as given. need to find them + # using TEXINPUTS paths. + + # search the included files + for src in inc_files: + srcNode = FindFile(src,['.tex','.ltx','.latex'],paths,env,requireExt=False) + # make this a list since is_LaTeX takes a list. + fileList = [srcNode,] + if Verbose: + print "FindFile found ",srcNode + if srcNode is not None: + file_test = is_LaTeX(fileList, env, abspath) + + # return on first file that finds latex is needed. + if file_test: + return file_test + + if Verbose: + print " done scanning ",str(f) + + return 0 + +def TeXLaTeXFunction(target = None, source= None, env=None): + """A builder for TeX and LaTeX that scans the source file to + decide the "flavor" of the source and then executes the appropriate + program.""" + + # find these paths for use in is_LaTeX to search for included files + basedir = os.path.split(str(source[0]))[0] + abspath = os.path.abspath(basedir) + + if is_LaTeX(source,env,abspath): + result = LaTeXAuxAction(target,source,env) + if result != 0: + print env['LATEX']," returned an error, check the log file" + else: + result = TeXAction(target,source,env) + if result != 0: + print env['TEX']," returned an error, check the log file" + return result + +def TeXLaTeXStrFunction(target = None, source= None, env=None): + """A strfunction for TeX and LaTeX that scans the source file to + decide the "flavor" of the source and then returns the appropriate + command string.""" + if env.GetOption("no_exec"): + + # find these paths for use in is_LaTeX to search for included files + basedir = os.path.split(str(source[0]))[0] + abspath = os.path.abspath(basedir) + + if is_LaTeX(source,env,abspath): + result = env.subst('$LATEXCOM',0,target,source)+" ..." + else: + result = env.subst("$TEXCOM",0,target,source)+" ..." + else: + result = '' + return result + +def tex_eps_emitter(target, source, env): + """An emitter for TeX and LaTeX sources when + executing tex or latex. It will accept .ps and .eps + graphics files + """ + (target, source) = tex_emitter_core(target, source, env, TexGraphics) + + return (target, source) + +def tex_pdf_emitter(target, source, env): + """An emitter for TeX and LaTeX sources when + executing pdftex or pdflatex. It will accept graphics + files of types .pdf, .jpg, .png, .gif, and .tif + """ + (target, source) = tex_emitter_core(target, source, env, LatexGraphics) + + return (target, source) + +def ScanFiles(theFile, target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir): + """ For theFile (a Node) update any file_tests and search for graphics files + then find all included files and call ScanFiles recursively for each of them""" + + content = theFile.get_text_contents() + if Verbose: + print " scanning ",str(theFile) + + for i in range(len(file_tests_search)): + if file_tests[i][0] is None: + file_tests[i][0] = file_tests_search[i].search(content) + + # recursively call this on each of the included files + inc_files = [ ] + inc_files.extend( include_re.findall(content) ) + if Verbose: + print "files included by '%s': "%str(theFile),inc_files + # inc_files is list of file names as given. need to find them + # using TEXINPUTS paths. + + for src in inc_files: + srcNode = FindFile(src,['.tex','.ltx','.latex'],paths,env,requireExt=False) + if srcNode is not None: + file_tests = ScanFiles(srcNode, target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir) + if Verbose: + print " done scanning ",str(theFile) + return file_tests + +def tex_emitter_core(target, source, env, graphics_extensions): + """An emitter for TeX and LaTeX sources. + For LaTeX sources we try and find the common created files that + are needed on subsequent runs of latex to finish tables of contents, + bibliographies, indices, lists of figures, and hyperlink references. + """ + basename = SCons.Util.splitext(str(source[0]))[0] + basefile = os.path.split(str(basename))[1] + targetdir = os.path.split(str(target[0]))[0] + targetbase = os.path.join(targetdir, basefile) + + basedir = os.path.split(str(source[0]))[0] + abspath = os.path.abspath(basedir) + target[0].attributes.path = abspath + + # + # file names we will make use of in searching the sources and log file + # + emit_suffixes = ['.aux', '.log', '.ilg', '.blg', '.nls', '.nlg', '.gls', '.glg', '.alg'] + all_suffixes + auxfilename = targetbase + '.aux' + logfilename = targetbase + '.log' + flsfilename = targetbase + '.fls' + + env.SideEffect(auxfilename,target[0]) + env.SideEffect(logfilename,target[0]) + env.SideEffect(flsfilename,target[0]) + if Verbose: + print "side effect :",auxfilename,logfilename,flsfilename + env.Clean(target[0],auxfilename) + env.Clean(target[0],logfilename) + env.Clean(target[0],flsfilename) + + content = source[0].get_text_contents() + + idx_exists = os.path.exists(targetbase + '.idx') + nlo_exists = os.path.exists(targetbase + '.nlo') + glo_exists = os.path.exists(targetbase + '.glo') + acr_exists = os.path.exists(targetbase + '.acn') + + # set up list with the regular expressions + # we use to find features used + file_tests_search = [auxfile_re, + makeindex_re, + bibliography_re, + tableofcontents_re, + listoffigures_re, + listoftables_re, + hyperref_re, + makenomenclature_re, + makeglossary_re, + makeglossaries_re, + makeacronyms_re, + beamer_re ] + # set up list with the file suffixes that need emitting + # when a feature is found + file_tests_suff = [['.aux'], + ['.idx', '.ind', '.ilg'], + ['.bbl', '.blg'], + ['.toc'], + ['.lof'], + ['.lot'], + ['.out'], + ['.nlo', '.nls', '.nlg'], + ['.glo', '.gls', '.glg'], + ['.glo', '.gls', '.glg'], + ['.acn', '.acr', '.alg'], + ['.nav', '.snm', '.out', '.toc'] ] + # build the list of lists + file_tests = [] + for i in range(len(file_tests_search)): + file_tests.append( [None, file_tests_suff[i]] ) + + # TO-DO: need to add a way for the user to extend this list for whatever + # auxiliary files they create in other (or their own) packages + + # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS'] + savedpath = modify_env_var(env, 'TEXINPUTS', abspath) + paths = env['ENV']['TEXINPUTS'] + if SCons.Util.is_List(paths): + pass + else: + # Split at os.pathsep to convert into absolute path + # TODO(1.5) + #paths = paths.split(os.pathsep) + paths = string.split(paths, os.pathsep) + + # now that we have the path list restore the env + if savedpath is _null: + try: + del env['ENV']['TEXINPUTS'] + except KeyError: + pass # was never set + else: + env['ENV']['TEXINPUTS'] = savedpath + if Verbose: + print "search path ",paths + + file_tests = ScanFiles(source[0], target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir) + + for (theSearch,suffix_list) in file_tests: + if theSearch: + for suffix in suffix_list: + env.SideEffect(targetbase + suffix,target[0]) + if Verbose: + print "side effect :",targetbase + suffix + env.Clean(target[0],targetbase + suffix) + + # read fls file to get all other files that latex creates and will read on the next pass + # remove files from list that we explicitly dealt with above + if os.path.exists(flsfilename): + content = open(flsfilename, "rb").read() + out_files = openout_re.findall(content) + myfiles = [auxfilename, logfilename, flsfilename, targetbase+'.dvi',targetbase+'.pdf'] + for filename in out_files[:]: + if filename in myfiles: + out_files.remove(filename) + env.SideEffect(out_files,target[0]) + if Verbose: + print "side effect :",out_files + env.Clean(target[0],out_files) + + return (target, source) + + +TeXLaTeXAction = None + +def generate(env): + """Add Builders and construction variables for TeX to an Environment.""" + + global TeXLaTeXAction + if TeXLaTeXAction is None: + TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction, + strfunction=TeXLaTeXStrFunction) + + env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes) + + generate_common(env) + + import dvi + dvi.generate(env) + + bld = env['BUILDERS']['DVI'] + bld.add_action('.tex', TeXLaTeXAction) + bld.add_emitter('.tex', tex_eps_emitter) + +def generate_common(env): + """Add internal Builders and construction variables for LaTeX to an Environment.""" + + # A generic tex file Action, sufficient for all tex files. + global TeXAction + if TeXAction is None: + TeXAction = SCons.Action.Action("$TEXCOM", "$TEXCOMSTR") + + # An Action to build a latex file. This might be needed more + # than once if we are dealing with labels and bibtex. + global LaTeXAction + if LaTeXAction is None: + LaTeXAction = SCons.Action.Action("$LATEXCOM", "$LATEXCOMSTR") + + # Define an action to run BibTeX on a file. + global BibTeXAction + if BibTeXAction is None: + BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR") + + # Define an action to run MakeIndex on a file. + global MakeIndexAction + if MakeIndexAction is None: + MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR") + + # Define an action to run MakeIndex on a file for nomenclatures. + global MakeNclAction + if MakeNclAction is None: + MakeNclAction = SCons.Action.Action("$MAKENCLCOM", "$MAKENCLCOMSTR") + + # Define an action to run MakeIndex on a file for glossaries. + global MakeGlossaryAction + if MakeGlossaryAction is None: + MakeGlossaryAction = SCons.Action.Action("$MAKEGLOSSARYCOM", "$MAKEGLOSSARYCOMSTR") + + # Define an action to run MakeIndex on a file for acronyms. + global MakeAcronymsAction + if MakeAcronymsAction is None: + MakeAcronymsAction = SCons.Action.Action("$MAKEACRONYMSCOM", "$MAKEACRONYMSCOMSTR") + + env['TEX'] = 'tex' + env['TEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder') + env['TEXCOM'] = 'cd ${TARGET.dir} && $TEX $TEXFLAGS ${SOURCE.file}' + + env['PDFTEX'] = 'pdftex' + env['PDFTEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder') + env['PDFTEXCOM'] = 'cd ${TARGET.dir} && $PDFTEX $PDFTEXFLAGS ${SOURCE.file}' + + env['LATEX'] = 'latex' + env['LATEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder') + env['LATEXCOM'] = 'cd ${TARGET.dir} && $LATEX $LATEXFLAGS ${SOURCE.file}' + env['LATEXRETRIES'] = 3 + + env['PDFLATEX'] = 'pdflatex' + env['PDFLATEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder') + env['PDFLATEXCOM'] = 'cd ${TARGET.dir} && $PDFLATEX $PDFLATEXFLAGS ${SOURCE.file}' + + env['BIBTEX'] = 'bibtex' + env['BIBTEXFLAGS'] = SCons.Util.CLVar('') + env['BIBTEXCOM'] = 'cd ${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}' + + env['MAKEINDEX'] = 'makeindex' + env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('') + env['MAKEINDEXCOM'] = 'cd ${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}' + + env['MAKEGLOSSARY'] = 'makeindex' + env['MAKEGLOSSARYSTYLE'] = '${SOURCE.filebase}.ist' + env['MAKEGLOSSARYFLAGS'] = SCons.Util.CLVar('-s ${MAKEGLOSSARYSTYLE} -t ${SOURCE.filebase}.glg') + env['MAKEGLOSSARYCOM'] = 'cd ${TARGET.dir} && $MAKEGLOSSARY ${SOURCE.filebase}.glo $MAKEGLOSSARYFLAGS -o ${SOURCE.filebase}.gls' + + env['MAKEACRONYMS'] = 'makeindex' + env['MAKEACRONYMSSTYLE'] = '${SOURCE.filebase}.ist' + env['MAKEACRONYMSFLAGS'] = SCons.Util.CLVar('-s ${MAKEACRONYMSSTYLE} -t ${SOURCE.filebase}.alg') + env['MAKEACRONYMSCOM'] = 'cd ${TARGET.dir} && $MAKEACRONYMS ${SOURCE.filebase}.acn $MAKEACRONYMSFLAGS -o ${SOURCE.filebase}.acr' + + env['MAKENCL'] = 'makeindex' + env['MAKENCLSTYLE'] = 'nomencl.ist' + env['MAKENCLFLAGS'] = '-s ${MAKENCLSTYLE} -t ${SOURCE.filebase}.nlg' + env['MAKENCLCOM'] = 'cd ${TARGET.dir} && $MAKENCL ${SOURCE.filebase}.nlo $MAKENCLFLAGS -o ${SOURCE.filebase}.nls' + +def exists(env): + return env.Detect('tex') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/tex.xml b/src/engine/SCons/Tool/tex.xml new file mode 100644 index 0000000..e8c61c7 --- /dev/null +++ b/src/engine/SCons/Tool/tex.xml @@ -0,0 +1,126 @@ + + + +Sets construction variables for the TeX formatter and typesetter. + + +TEX +TEXFLAGS +TEXCOM +LATEX +LATEXFLAGS +LATEXCOM +BIBTEX +BIBTEXFLAGS +BIBTEXCOM +MAKEINDEX +MAKEINDEXFLAGS +MAKEINDEXCOM + + +TEXCOMSTR +LATEXCOMSTR +BIBTEXCOMSTR +MAKEINDEXCOMSTR + + + + + +The bibliography generator for the TeX formatter and typesetter and the +LaTeX structured formatter and typesetter. + + + + + +The command line used to call the bibliography generator for the +TeX formatter and typesetter and the LaTeX structured formatter and +typesetter. + + + + + +The string displayed when generating a bibliography +for TeX or LaTeX. +If this is not set, then &cv-link-BIBTEXCOM; (the command line) is displayed. + + +env = Environment(BIBTEXCOMSTR = "Generating bibliography $TARGET") + + + + + + +General options passed to the bibliography generator for the TeX formatter +and typesetter and the LaTeX structured formatter and typesetter. + + + + + +The makeindex generator for the TeX formatter and typesetter and the +LaTeX structured formatter and typesetter. + + + + + +The command line used to call the makeindex generator for the +TeX formatter and typesetter and the LaTeX structured formatter and +typesetter. + + + + + +The string displayed when calling the makeindex generator for the +TeX formatter and typesetter +and the LaTeX structured formatter and typesetter. +If this is not set, then &cv-link-MAKEINDEXCOM; (the command line) is displayed. + + + + + +General options passed to the makeindex generator for the TeX formatter +and typesetter and the LaTeX structured formatter and typesetter. + + + + + +The TeX formatter and typesetter. + + + + + +The command line used to call the TeX formatter and typesetter. + + + + + +The string displayed when calling +the TeX formatter and typesetter. +If this is not set, then &cv-link-TEXCOM; (the command line) is displayed. + + +env = Environment(TEXCOMSTR = "Building $TARGET from TeX input $SOURCES") + + + + + + +General options passed to the TeX formatter and typesetter. + + diff --git a/src/engine/SCons/Tool/textfile.py b/src/engine/SCons/Tool/textfile.py new file mode 100644 index 0000000..8dea678 --- /dev/null +++ b/src/engine/SCons/Tool/textfile.py @@ -0,0 +1,175 @@ +# -*- python -*- +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = """ +Textfile/Substfile builder for SCons. + + Create file 'target' which typically is a textfile. The 'source' + may be any combination of strings, Nodes, or lists of same. A + 'linesep' will be put between any part written and defaults to + os.linesep. + + The only difference between the Textfile builder and the Substfile + builder is that strings are converted to Value() nodes for the + former and File() nodes for the latter. To insert files in the + former or strings in the latter, wrap them in a File() or Value(), + respectively. + + The values of SUBST_DICT first have any construction variables + expanded (its keys are not expanded). If a value of SUBST_DICT is + a python callable function, it is called and the result is expanded + as the value. Values are substituted in a "random" order; if any + substitution could be further expanded by another subsitition, it + is unpredictible whether the expansion will occur. +""" + +__revision__ = "src/engine/SCons/Tool/textfile.py 4577 2009/12/27 19:44:43 scons" + +import SCons + +import os +import re + +from SCons.Node import Node +from SCons.Node.Python import Value +from SCons.Util import is_String, is_Sequence, is_Dict + +def _do_subst(node, subs): + """ + Fetch the node contents and replace all instances of the keys with + their values. For example, if subs is + {'%VERSION%': '1.2345', '%BASE%': 'MyProg', '%prefix%': '/bin'}, + then all instances of %VERSION% in the file will be replaced with + 1.2345 and so forth. + """ + contents = node.get_text_contents() + if not subs: return contents + for (k,v) in subs: + contents = re.sub(k, v, contents) + return contents + +def _action(target, source, env): + # prepare the line separator + linesep = env['LINESEPARATOR'] + if linesep is None: + linesep = os.linesep + elif is_String(linesep): + pass + elif isinstance(linesep, Value): + linesep = linesep.get_text_contents() + else: + raise SCons.Errors.UserError( + 'unexpected type/class for LINESEPARATOR: %s' + % repr(linesep), None) + + # create a dictionary to use for the substitutions + if not env.has_key('SUBST_DICT'): + subs = None # no substitutions + else: + d = env['SUBST_DICT'] + if is_Dict(d): + d = d.items() + elif is_Sequence(d): + pass + else: + raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence') + subs = [] + for (k,v) in d: + if callable(v): + v = v() + if is_String(v): + v = env.subst(v) + else: + v = str(v) + subs.append((k,v)) + + # write the file + try: + fd = open(target[0].get_path(), "wb") + except (OSError,IOError), e: + raise SCons.Errors.UserError("Can't write target file %s" % target[0]) + # separate lines by 'linesep' only if linesep is not empty + lsep = None + for s in source: + if lsep: fd.write(lsep) + fd.write(_do_subst(s, subs)) + lsep = linesep + fd.close() + +def _strfunc(target, source, env): + return "Creating '%s'" % target[0] + +def _convert_list_R(newlist, sources): + for elem in sources: + if is_Sequence(elem): + _convert_list_R(newlist, elem) + elif isinstance(elem, Node): + newlist.append(elem) + else: + newlist.append(Value(elem)) +def _convert_list(target, source, env): + if len(target) != 1: + raise SCons.Errors.UserError("Only one target file allowed") + newlist = [] + _convert_list_R(newlist, source) + return target, newlist + +_common_varlist = ['SUBST_DICT', 'LINESEPARATOR'] + +_text_varlist = _common_varlist + ['TEXTFILEPREFIX', 'TEXTFILESUFFIX'] +_text_builder = SCons.Builder.Builder( + action = SCons.Action.Action(_action, _strfunc, varlist = _text_varlist), + source_factory = Value, + emitter = _convert_list, + prefix = '$TEXTFILEPREFIX', + suffix = '$TEXTFILESUFFIX', + ) + +_subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'TEXTFILESUFFIX'] +_subst_builder = SCons.Builder.Builder( + action = SCons.Action.Action(_action, _strfunc, varlist = _subst_varlist), + source_factory = SCons.Node.FS.File, + emitter = _convert_list, + prefix = '$SUBSTFILEPREFIX', + suffix = '$SUBSTFILESUFFIX', + src_suffix = ['.in'], + ) + +def generate(env): + env['LINESEPARATOR'] = os.linesep + env['BUILDERS']['Textfile'] = _text_builder + env['TEXTFILEPREFIX'] = '' + env['TEXTFILESUFFIX'] = '.txt' + env['BUILDERS']['Substfile'] = _subst_builder + env['SUBSTFILEPREFIX'] = '' + env['SUBSTFILESUFFIX'] = '' + +def exists(env): + return 1 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/textfile.xml b/src/engine/SCons/Tool/textfile.xml new file mode 100644 index 0000000..9b66607 --- /dev/null +++ b/src/engine/SCons/Tool/textfile.xml @@ -0,0 +1,205 @@ + + + +Set construction variables for the &b-Textfile; and &b-Substfile; builders. + + +LINESEPARATOR +SUBSTFILEPREFIX +SUBSTFILESUFFIX +TEXTFILEPREFIX +TEXTFILESUFFIX + + +SUBST_DICT + + + + + +The &b-Textfile; builder generates a single text file. +The source strings constitute the lines; +nested lists of sources are flattened. +&cv-LINESEPARATOR; is used to separate the strings. + +If present, the &cv-SUBST_DICT; construction variable +is used to modify the strings before they are written; +see the &b-Substfile; description for details. + +The prefix and suffix specified by the &cv-TEXTFILEPREFIX; +and &cv-TEXTFILESUFFIX; construction variables +(the null string and .txt by default, respectively) +are automatically added to the target if they are not already present. +Examples: + + +# builds/writes foo.txt +env.Textfile(target = 'foo.txt', source = ['Goethe', 42, 'Schiller']) + +# builds/writes bar.txt +env.Textfile(target = 'bar', + source = ['lalala', 'tanteratei'], + LINESEPARATOR='|*') + +# nested lists are flattened automatically +env.Textfile(target = 'blob', + source = ['lalala', ['Goethe', 42 'Schiller'], 'tanteratei']) + +# files may be used as input by wraping them in File() +env.Textfile(target = 'concat', # concatenate files with a marker between + source = [File('concat1'), File('concat2')], + LINESEPARATOR = '====================\n') + +Results are: +foo.txt + ....8<---- + Goethe + 42 + Schiller + ....8<---- (no linefeed at the end) + +bar.txt: + ....8<---- + lalala|*tanteratei + ....8<---- (no linefeed at the end) + +blob.txt + ....8<---- + lalala + Goethe + 42 + Schiller + tanteratei + ....8<---- (no linefeed at the end) + + + + + + +The &b-Substfile; builder generates a single text file +by concatenating the source files. +Nested lists of sources are flattened. +&cv-LINESEPARATOR; is used to separate the source files; +see the description of &b-Textfile; for details. + +If a single source file is present with an .in suffix, +the suffix is stripped and the remainder is used as the default target name. + +The prefix and suffix specified by the &cv-SUBSTFILEPREFIX; +and &cv-SUBSTFILESUFFIX; construction variables +(the null string by default in both cases) +are automatically added to the target if they are not already present. + +If a construction variable named &cv-SUBST_DICT; is present, +it may be either a Python dictionary or a sequence of (key,value) tuples. +If the former, +the dictionary is converted into a list of tuples in an arbitrary order, +so if one key is a prefix of another key +or if one substitution could be further expanded by another subsitition, +it is unpredictible whether the expansion will occur. + +Any occurences in the source of a key +are replaced by the corresponding value, +which may be a Python callable function or a string. +If a value is a function, +it is first called (with no arguments) to produce a string. +The string is subst-expanded +and the result replaces the key. + + +env = Environment(tools = ['default', 'textfile']) + +env['prefix'] = '/usr/bin' +script_dict = {'@prefix@': '/bin', @exec_prefix@: '$prefix'} +env.Substfile('script.in', SUBST_DICT = script_dict) + +conf_dict = {'%VERSION%': '1.2.3', '%BASE%': 'MyProg'} +env.Substfile('config.h.in', conf_dict, SUBST_DICT = conf_dict) + +# UNPREDICTABLE - one key is a prefix of another +bad_foo = {'$foo': '$foo', '$foobar': '$foobar'} +env.Substfile('foo.in', SUBST_DICT = bad_foo) + +# PREDICTABLE - keys are applied longest first +good_foo = [('$foobar', '$foobar'), ('$foo', '$foo')] +env.Substfile('foo.in', SUBST_DICT = good_foo) + +# UNPREDICTABLE - one substitution could be futher expanded +bad_bar = {'@bar@': '@soap@', '@soap@': 'lye'} +env.Substfile('bar.in', SUBST_DICT = bad_bar) + +# PREDICTABLE - substitutions are expanded in order +good_bar = (('@bar@', '@soap@'), ('@soap@', 'lye')) +env.Substfile('bar.in', SUBST_DICT = good_bar) + +# the SUBST_DICT may be in common (and not an override) +substutions = {} +subst = Environment(tools = ['textfile', SUBST_DICT = substitutions) +substitutions['@foo@'] = 'foo' +subst['SUBST_DICT']['@bar@'] = 'bar' +subst.Substfile('pgm1.c', [Value('#include "@foo@.h"'), + Value('#include "@bar@.h"'), + "common.in", + "pgm1.in" + ]) +subst.Substfile('pgm2.c', [Value('#include "@foo@.h"'), + Value('#include "@bar@.h"'), + "common.in", + "pgm2.in" + ]) + + + + + + + +The separator used by the &b-Substfile; and &b-Textfile; builders. +This value is used between sources when constructing the target. +It defaults to the current system line separator. + + + + + +The dictionary used by the &b-Substfile; or &b-Textfile; builders +for substitution values. +It can be anything acceptable to the dict() constructor, +so in addition to a dictionary, +lists of tuples are also acceptable. + + + + + +The prefix used for &b-Substfile; file names, +the null string by default. + + + + + +The suffix used for &b-Substfile; file names, +the null string by default. + + + + + +The prefix used for &b-Textfile; file names, +the null string by default. + + + + + +The suffix used for &b-Textfile; file names; +.txt by default. + + diff --git a/src/engine/SCons/Tool/tlib.py b/src/engine/SCons/Tool/tlib.py new file mode 100644 index 0000000..0d6f802 --- /dev/null +++ b/src/engine/SCons/Tool/tlib.py @@ -0,0 +1,53 @@ +"""SCons.Tool.tlib + +XXX + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/tlib.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Tool +import SCons.Tool.bcc32 +import SCons.Util + +def generate(env): + SCons.Tool.bcc32.findIt('tlib', env) + """Add Builders and construction variables for ar to an Environment.""" + SCons.Tool.createStaticLibBuilder(env) + env['AR'] = 'tlib' + env['ARFLAGS'] = SCons.Util.CLVar('') + env['ARCOM'] = '$AR $TARGET $ARFLAGS /a $SOURCES' + env['LIBPREFIX'] = '' + env['LIBSUFFIX'] = '.lib' + +def exists(env): + return SCons.Tool.bcc32.findIt('tlib', env) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/tlib.xml b/src/engine/SCons/Tool/tlib.xml new file mode 100644 index 0000000..fb386ec --- /dev/null +++ b/src/engine/SCons/Tool/tlib.xml @@ -0,0 +1,22 @@ + + + +Sets construction variables for the Borlan +tib library archiver. + + +AR +ARFLAGS +ARCOM +LIBPREFIX +LIBSUFFIX + + +ARCOMSTR + + diff --git a/src/engine/SCons/Tool/wix.py b/src/engine/SCons/Tool/wix.py new file mode 100644 index 0000000..350ae49 --- /dev/null +++ b/src/engine/SCons/Tool/wix.py @@ -0,0 +1,100 @@ +"""SCons.Tool.wix + +Tool-specific initialization for wix, the Windows Installer XML Tool. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/wix.py 4577 2009/12/27 19:44:43 scons" + +import SCons.Builder +import SCons.Action +import os +import string + +def generate(env): + """Add Builders and construction variables for WiX to an Environment.""" + if not exists(env): + return + + env['WIXCANDLEFLAGS'] = ['-nologo'] + env['WIXCANDLEINCLUDE'] = [] + env['WIXCANDLECOM'] = '$WIXCANDLE $WIXCANDLEFLAGS -I $WIXCANDLEINCLUDE -o ${TARGET} ${SOURCE}' + + env['WIXLIGHTFLAGS'].append( '-nologo' ) + env['WIXLIGHTCOM'] = "$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}" + + object_builder = SCons.Builder.Builder( + action = '$WIXCANDLECOM', + suffix = '.wxiobj', + src_suffix = '.wxs') + + linker_builder = SCons.Builder.Builder( + action = '$WIXLIGHTCOM', + src_suffix = '.wxiobj', + src_builder = object_builder) + + env['BUILDERS']['WiX'] = linker_builder + +def exists(env): + env['WIXCANDLE'] = 'candle.exe' + env['WIXLIGHT'] = 'light.exe' + + # try to find the candle.exe and light.exe tools and + # add the install directory to light libpath. + #for path in os.environ['PATH'].split(os.pathsep): + for path in string.split(os.environ['PATH'], os.pathsep): + if not path: + continue + + # workaround for some weird python win32 bug. + if path[0] == '"' and path[-1:]=='"': + path = path[1:-1] + + # normalize the path + path = os.path.normpath(path) + + # search for the tools in the PATH environment variable + try: + if env['WIXCANDLE'] in os.listdir(path) and\ + env['WIXLIGHT'] in os.listdir(path): + env.PrependENVPath('PATH', path) + env['WIXLIGHTFLAGS'] = [ os.path.join( path, 'wixui.wixlib' ), + '-loc', + os.path.join( path, 'WixUI_en-us.wxl' ) ] + return 1 + except OSError: + pass # ignore this, could be a stale PATH entry. + + return None + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/yacc.py b/src/engine/SCons/Tool/yacc.py new file mode 100644 index 0000000..83636d7 --- /dev/null +++ b/src/engine/SCons/Tool/yacc.py @@ -0,0 +1,131 @@ +"""SCons.Tool.yacc + +Tool-specific initialization for yacc. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/yacc.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string + +import SCons.Defaults +import SCons.Tool +import SCons.Util + +YaccAction = SCons.Action.Action("$YACCCOM", "$YACCCOMSTR") + +def _yaccEmitter(target, source, env, ysuf, hsuf): + yaccflags = env.subst("$YACCFLAGS", target=target, source=source) + flags = SCons.Util.CLVar(yaccflags) + targetBase, targetExt = os.path.splitext(SCons.Util.to_String(target[0])) + + if '.ym' in ysuf: # If using Objective-C + target = [targetBase + ".m"] # the extension is ".m". + + + # If -d is specified on the command line, yacc will emit a .h + # or .hpp file with the same name as the .c or .cpp output file. + if '-d' in flags: + target.append(targetBase + env.subst(hsuf, target=target, source=source)) + + # If -g is specified on the command line, yacc will emit a .vcg + # file with the same base name as the .y, .yacc, .ym or .yy file. + if "-g" in flags: + base, ext = os.path.splitext(SCons.Util.to_String(source[0])) + target.append(base + env.subst("$YACCVCGFILESUFFIX")) + + # With --defines and --graph, the name of the file is totally defined + # in the options. + fileGenOptions = ["--defines=", "--graph="] + for option in flags: + for fileGenOption in fileGenOptions: + l = len(fileGenOption) + if option[:l] == fileGenOption: + # A file generating option is present, so add the file + # name to the list of targets. + fileName = string.strip(option[l:]) + target.append(fileName) + + return (target, source) + +def yEmitter(target, source, env): + return _yaccEmitter(target, source, env, ['.y', '.yacc'], '$YACCHFILESUFFIX') + +def ymEmitter(target, source, env): + return _yaccEmitter(target, source, env, ['.ym'], '$YACCHFILESUFFIX') + +def yyEmitter(target, source, env): + return _yaccEmitter(target, source, env, ['.yy'], '$YACCHXXFILESUFFIX') + +def generate(env): + """Add Builders and construction variables for yacc to an Environment.""" + c_file, cxx_file = SCons.Tool.createCFileBuilders(env) + + # C + c_file.add_action('.y', YaccAction) + c_file.add_emitter('.y', yEmitter) + + c_file.add_action('.yacc', YaccAction) + c_file.add_emitter('.yacc', yEmitter) + + # Objective-C + c_file.add_action('.ym', YaccAction) + c_file.add_emitter('.ym', ymEmitter) + + # C++ + cxx_file.add_action('.yy', YaccAction) + cxx_file.add_emitter('.yy', yyEmitter) + + env['YACC'] = env.Detect('bison') or 'yacc' + env['YACCFLAGS'] = SCons.Util.CLVar('') + env['YACCCOM'] = '$YACC $YACCFLAGS -o $TARGET $SOURCES' + env['YACCHFILESUFFIX'] = '.h' + + # Apparently, OS X now creates file.hpp like everybody else + # I have no idea when it changed; it was fixed in 10.4 + #if env['PLATFORM'] == 'darwin': + # # Bison on Mac OS X just appends ".h" to the generated target .cc + # # or .cpp file name. Hooray for delayed expansion of variables. + # env['YACCHXXFILESUFFIX'] = '${TARGET.suffix}.h' + #else: + # env['YACCHXXFILESUFFIX'] = '.hpp' + env['YACCHXXFILESUFFIX'] = '.hpp' + + env['YACCVCGFILESUFFIX'] = '.vcg' + +def exists(env): + return env.Detect(['bison', 'yacc']) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/yacc.xml b/src/engine/SCons/Tool/yacc.xml new file mode 100644 index 0000000..456c379 --- /dev/null +++ b/src/engine/SCons/Tool/yacc.xml @@ -0,0 +1,115 @@ + + + +Sets construction variables for the &yacc; parse generator. + + +YACC +YACCFLAGS +YACCCOM +YACCHFILESUFFIX +YACCHXXFILESUFFIX +YACCVCGFILESUFFIX + + +YACCCOMSTR + + + + + +The parser generator. + + + + + +The command line used to call the parser generator +to generate a source file. + + + + + +The string displayed when generating a source file +using the parser generator. +If this is not set, then &cv-link-YACCCOM; (the command line) is displayed. + + +env = Environment(YACCCOMSTR = "Yacc'ing $TARGET from $SOURCES") + + + + + + +General options passed to the parser generator. +If &cv-link-YACCFLAGS; contains a option, +SCons assumes that the call will also create a .h file +(if the yacc source file ends in a .y suffix) +or a .hpp file +(if the yacc source file ends in a .yy suffix) + + + + + +The suffix of the C +header file generated by the parser generator +when the + +option is used. +Note that setting this variable does not cause +the parser generator to generate a header +file with the specified suffix, +it exists to allow you to specify +what suffix the parser generator will use of its own accord. +The default value is +.h. + + + + + +The suffix of the C++ +header file generated by the parser generator +when the + +option is used. +Note that setting this variable does not cause +the parser generator to generate a header +file with the specified suffix, +it exists to allow you to specify +what suffix the parser generator will use of its own accord. +The default value is +.hpp, +except on Mac OS X, +where the default is +${TARGET.suffix}.h. +because the default &bison; parser generator just +appends .h +to the name of the generated C++ file. + + + + + +The suffix of the file +containing the VCG grammar automaton definition +when the + +option is used. +Note that setting this variable does not cause +the parser generator to generate a VCG +file with the specified suffix, +it exists to allow you to specify +what suffix the parser generator will use of its own accord. +The default value is +.vcg. + + diff --git a/src/engine/SCons/Tool/zip.py b/src/engine/SCons/Tool/zip.py new file mode 100644 index 0000000..1b31ad9 --- /dev/null +++ b/src/engine/SCons/Tool/zip.py @@ -0,0 +1,100 @@ +"""SCons.Tool.zip + +Tool-specific initialization for zip. + +There normally shouldn't be any need to import this module directly. +It will usually be imported through the generic SCons.Tool.Tool() +selection method. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Tool/zip.py 4577 2009/12/27 19:44:43 scons" + +import os.path + +import SCons.Builder +import SCons.Defaults +import SCons.Node.FS +import SCons.Util + +try: + import zipfile + internal_zip = 1 +except ImportError: + internal_zip = 0 + +if internal_zip: + zipcompression = zipfile.ZIP_DEFLATED + def zip(target, source, env): + def visit(arg, dirname, names): + for name in names: + path = os.path.join(dirname, name) + if os.path.isfile(path): + arg.write(path) + compression = env.get('ZIPCOMPRESSION', 0) + zf = zipfile.ZipFile(str(target[0]), 'w', compression) + for s in source: + if s.isdir(): + os.path.walk(str(s), visit, zf) + else: + zf.write(str(s)) + zf.close() +else: + zipcompression = 0 + zip = "$ZIP $ZIPFLAGS ${TARGET.abspath} $SOURCES" + + +zipAction = SCons.Action.Action(zip, varlist=['ZIPCOMPRESSION']) + +ZipBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'), + source_factory = SCons.Node.FS.Entry, + source_scanner = SCons.Defaults.DirScanner, + suffix = '$ZIPSUFFIX', + multi = 1) + + +def generate(env): + """Add Builders and construction variables for zip to an Environment.""" + try: + bld = env['BUILDERS']['Zip'] + except KeyError: + bld = ZipBuilder + env['BUILDERS']['Zip'] = bld + + env['ZIP'] = 'zip' + env['ZIPFLAGS'] = SCons.Util.CLVar('') + env['ZIPCOM'] = zipAction + env['ZIPCOMPRESSION'] = zipcompression + env['ZIPSUFFIX'] = '.zip' + +def exists(env): + return internal_zip or env.Detect('zip') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Tool/zip.xml b/src/engine/SCons/Tool/zip.xml new file mode 100644 index 0000000..b08b5f6 --- /dev/null +++ b/src/engine/SCons/Tool/zip.xml @@ -0,0 +1,110 @@ + + + +Sets construction variables for the &zip; archiver. + + +ZIP +ZIPFLAGS +ZIPCOM +ZIPCOMPRESSION +ZIPSUFFIX + + +ZIPCOMSTR + + + + + +Builds a zip archive of the specified files +and/or directories. +Unlike most builder methods, +the +&b-Zip; +builder method may be called multiple times +for a given target; +each additional call +adds to the list of entries +that will be built into the archive. +Any source directories will +be scanned for changes to +any on-disk files, +regardless of whether or not +&scons; +knows about them from other Builder or function calls. + + +env.Zip('src.zip', 'src') + +# Create the stuff.zip file. +env.Zip('stuff', ['subdir1', 'subdir2']) +# Also add "another" to the stuff.tar file. +env.Zip('stuff', 'another') + + + + + + +The zip compression and file packaging utility. + + + + + +The command line used to call the zip utility, +or the internal Python function used to create a +zip archive. + + + + + +The string displayed when archiving files +using the zip utility. +If this is not set, then &cv-link-ZIPCOM; +(the command line or internal Python function) is displayed. + + +env = Environment(ZIPCOMSTR = "Zipping $TARGET") + + + + + + +The +compression +flag +from the Python +zipfile +module used by the internal Python function +to control whether the zip archive +is compressed or not. +The default value is +zipfile.ZIP_DEFLATED, +which creates a compressed zip archive. +This value has no effect when using Python 1.5.2 +or if the +zipfile +module is otherwise unavailable. + + + + + +General options passed to the zip utility. + + + + + +The suffix used for zip file names. + + diff --git a/src/engine/SCons/Util.py b/src/engine/SCons/Util.py new file mode 100644 index 0000000..c7ebf3e --- /dev/null +++ b/src/engine/SCons/Util.py @@ -0,0 +1,1645 @@ +"""SCons.Util + +Various utility functions go here. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Util.py 4577 2009/12/27 19:44:43 scons" + +import copy +import os +import os.path +import re +import string +import sys +import types + +from UserDict import UserDict +from UserList import UserList +from UserString import UserString + +# Don't "from types import ..." these because we need to get at the +# types module later to look for UnicodeType. +DictType = types.DictType +InstanceType = types.InstanceType +ListType = types.ListType +StringType = types.StringType +TupleType = types.TupleType + +def dictify(keys, values, result={}): + for k, v in zip(keys, values): + result[k] = v + return result + +_altsep = os.altsep +if _altsep is None and sys.platform == 'win32': + # My ActivePython 2.0.1 doesn't set os.altsep! What gives? + _altsep = '/' +if _altsep: + def rightmost_separator(path, sep, _altsep=_altsep): + rfind = string.rfind + return max(rfind(path, sep), rfind(path, _altsep)) +else: + rightmost_separator = string.rfind + +# First two from the Python Cookbook, just for completeness. +# (Yeah, yeah, YAGNI...) +def containsAny(str, set): + """Check whether sequence str contains ANY of the items in set.""" + for c in set: + if c in str: return 1 + return 0 + +def containsAll(str, set): + """Check whether sequence str contains ALL of the items in set.""" + for c in set: + if c not in str: return 0 + return 1 + +def containsOnly(str, set): + """Check whether sequence str contains ONLY items in set.""" + for c in str: + if c not in set: return 0 + return 1 + +def splitext(path): + "Same as os.path.splitext() but faster." + sep = rightmost_separator(path, os.sep) + dot = string.rfind(path, '.') + # An ext is only real if it has at least one non-digit char + if dot > sep and not containsOnly(path[dot:], "0123456789."): + return path[:dot],path[dot:] + else: + return path,"" + +def updrive(path): + """ + Make the drive letter (if any) upper case. + This is useful because Windows is inconsitent on the case + of the drive letter, which can cause inconsistencies when + calculating command signatures. + """ + drive, rest = os.path.splitdrive(path) + if drive: + path = string.upper(drive) + rest + return path + +class NodeList(UserList): + """This class is almost exactly like a regular list of Nodes + (actually it can hold any object), with one important difference. + If you try to get an attribute from this list, it will return that + attribute from every item in the list. For example: + + >>> someList = NodeList([ ' foo ', ' bar ' ]) + >>> someList.strip() + [ 'foo', 'bar' ] + """ + def __nonzero__(self): + return len(self.data) != 0 + + def __str__(self): + return string.join(map(str, self.data)) + + def __iter__(self): + return iter(self.data) + + def __call__(self, *args, **kwargs): + result = map(lambda x, args=args, kwargs=kwargs: apply(x, + args, + kwargs), + self.data) + return self.__class__(result) + + def __getattr__(self, name): + result = map(lambda x, n=name: getattr(x, n), self.data) + return self.__class__(result) + + +_get_env_var = re.compile(r'^\$([_a-zA-Z]\w*|{[_a-zA-Z]\w*})$') + +def get_environment_var(varstr): + """Given a string, first determine if it looks like a reference + to a single environment variable, like "$FOO" or "${FOO}". + If so, return that variable with no decorations ("FOO"). + If not, return None.""" + mo=_get_env_var.match(to_String(varstr)) + if mo: + var = mo.group(1) + if var[0] == '{': + return var[1:-1] + else: + return var + else: + return None + +class DisplayEngine: + def __init__(self): + self.__call__ = self.print_it + + def print_it(self, text, append_newline=1): + if append_newline: text = text + '\n' + try: + sys.stdout.write(text) + except IOError: + # Stdout might be connected to a pipe that has been closed + # by now. The most likely reason for the pipe being closed + # is that the user has press ctrl-c. It this is the case, + # then SCons is currently shutdown. We therefore ignore + # IOError's here so that SCons can continue and shutdown + # properly so that the .sconsign is correctly written + # before SCons exits. + pass + + def dont_print(self, text, append_newline=1): + pass + + def set_mode(self, mode): + if mode: + self.__call__ = self.print_it + else: + self.__call__ = self.dont_print + +def render_tree(root, child_func, prune=0, margin=[0], visited={}): + """ + Render a tree of nodes into an ASCII tree view. + root - the root node of the tree + child_func - the function called to get the children of a node + prune - don't visit the same node twice + margin - the format of the left margin to use for children of root. + 1 results in a pipe, and 0 results in no pipe. + visited - a dictionary of visited nodes in the current branch if not prune, + or in the whole tree if prune. + """ + + rname = str(root) + + children = child_func(root) + retval = "" + for pipe in margin[:-1]: + if pipe: + retval = retval + "| " + else: + retval = retval + " " + + if visited.has_key(rname): + return retval + "+-[" + rname + "]\n" + + retval = retval + "+-" + rname + "\n" + if not prune: + visited = copy.copy(visited) + visited[rname] = 1 + + for i in range(len(children)): + margin.append(i 0 + last = t[0] + lasti = i = 1 + while i < n: + if t[i] != last: + t[lasti] = last = t[i] + lasti = lasti + 1 + i = i + 1 + return t[:lasti] + del t + + # Brute force is all that's left. + u = [] + for x in s: + if x not in u: + u.append(x) + return u + + + +# From Alex Martelli, +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560 +# ASPN: Python Cookbook: Remove duplicates from a sequence +# First comment, dated 2001/10/13. +# (Also in the printed Python Cookbook.) + +def uniquer(seq, idfun=None): + if idfun is None: + def idfun(x): return x + seen = {} + result = [] + for item in seq: + marker = idfun(item) + # in old Python versions: + # if seen.has_key(marker) + # but in new ones: + if marker in seen: continue + seen[marker] = 1 + result.append(item) + return result + +# A more efficient implementation of Alex's uniquer(), this avoids the +# idfun() argument and function-call overhead by assuming that all +# items in the sequence are hashable. + +def uniquer_hashables(seq): + seen = {} + result = [] + for item in seq: + #if not item in seen: + if not seen.has_key(item): + seen[item] = 1 + result.append(item) + return result + + + +# Much of the logic here was originally based on recipe 4.9 from the +# Python CookBook, but we had to dumb it way down for Python 1.5.2. +class LogicalLines: + + def __init__(self, fileobj): + self.fileobj = fileobj + + def readline(self): + result = [] + while 1: + line = self.fileobj.readline() + if not line: + break + if line[-2:] == '\\\n': + result.append(line[:-2]) + else: + result.append(line) + break + return string.join(result, '') + + def readlines(self): + result = [] + while 1: + line = self.readline() + if not line: + break + result.append(line) + return result + + + +class UniqueList(UserList): + def __init__(self, seq = []): + UserList.__init__(self, seq) + self.unique = True + def __make_unique(self): + if not self.unique: + self.data = uniquer_hashables(self.data) + self.unique = True + def __lt__(self, other): + self.__make_unique() + return UserList.__lt__(self, other) + def __le__(self, other): + self.__make_unique() + return UserList.__le__(self, other) + def __eq__(self, other): + self.__make_unique() + return UserList.__eq__(self, other) + def __ne__(self, other): + self.__make_unique() + return UserList.__ne__(self, other) + def __gt__(self, other): + self.__make_unique() + return UserList.__gt__(self, other) + def __ge__(self, other): + self.__make_unique() + return UserList.__ge__(self, other) + def __cmp__(self, other): + self.__make_unique() + return UserList.__cmp__(self, other) + def __len__(self): + self.__make_unique() + return UserList.__len__(self) + def __getitem__(self, i): + self.__make_unique() + return UserList.__getitem__(self, i) + def __setitem__(self, i, item): + UserList.__setitem__(self, i, item) + self.unique = False + def __getslice__(self, i, j): + self.__make_unique() + return UserList.__getslice__(self, i, j) + def __setslice__(self, i, j, other): + UserList.__setslice__(self, i, j, other) + self.unique = False + def __add__(self, other): + result = UserList.__add__(self, other) + result.unique = False + return result + def __radd__(self, other): + result = UserList.__radd__(self, other) + result.unique = False + return result + def __iadd__(self, other): + result = UserList.__iadd__(self, other) + result.unique = False + return result + def __mul__(self, other): + result = UserList.__mul__(self, other) + result.unique = False + return result + def __rmul__(self, other): + result = UserList.__rmul__(self, other) + result.unique = False + return result + def __imul__(self, other): + result = UserList.__imul__(self, other) + result.unique = False + return result + def append(self, item): + UserList.append(self, item) + self.unique = False + def insert(self, i): + UserList.insert(self, i) + self.unique = False + def count(self, item): + self.__make_unique() + return UserList.count(self, item) + def index(self, item): + self.__make_unique() + return UserList.index(self, item) + def reverse(self): + self.__make_unique() + UserList.reverse(self) + def sort(self, *args, **kwds): + self.__make_unique() + #return UserList.sort(self, *args, **kwds) + return apply(UserList.sort, (self,)+args, kwds) + def extend(self, other): + UserList.extend(self, other) + self.unique = False + + + +class Unbuffered: + """ + A proxy class that wraps a file object, flushing after every write, + and delegating everything else to the wrapped object. + """ + def __init__(self, file): + self.file = file + def write(self, arg): + try: + self.file.write(arg) + self.file.flush() + except IOError: + # Stdout might be connected to a pipe that has been closed + # by now. The most likely reason for the pipe being closed + # is that the user has press ctrl-c. It this is the case, + # then SCons is currently shutdown. We therefore ignore + # IOError's here so that SCons can continue and shutdown + # properly so that the .sconsign is correctly written + # before SCons exits. + pass + def __getattr__(self, attr): + return getattr(self.file, attr) + +def make_path_relative(path): + """ makes an absolute path name to a relative pathname. + """ + if os.path.isabs(path): + drive_s,path = os.path.splitdrive(path) + + import re + if not drive_s: + path=re.compile("/*(.*)").findall(path)[0] + else: + path=path[1:] + + assert( not os.path.isabs( path ) ), path + return path + + + +# The original idea for AddMethod() and RenameFunction() come from the +# following post to the ActiveState Python Cookbook: +# +# ASPN: Python Cookbook : Install bound methods in an instance +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/223613 +# +# That code was a little fragile, though, so the following changes +# have been wrung on it: +# +# * Switched the installmethod() "object" and "function" arguments, +# so the order reflects that the left-hand side is the thing being +# "assigned to" and the right-hand side is the value being assigned. +# +# * Changed explicit type-checking to the "try: klass = object.__class__" +# block in installmethod() below so that it still works with the +# old-style classes that SCons uses. +# +# * Replaced the by-hand creation of methods and functions with use of +# the "new" module, as alluded to in Alex Martelli's response to the +# following Cookbook post: +# +# ASPN: Python Cookbook : Dynamically added methods to a class +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81732 + +def AddMethod(object, function, name = None): + """ + Adds either a bound method to an instance or an unbound method to + a class. If name is ommited the name of the specified function + is used by default. + Example: + a = A() + def f(self, x, y): + self.z = x + y + AddMethod(f, A, "add") + a.add(2, 4) + print a.z + AddMethod(lambda self, i: self.l[i], a, "listIndex") + print a.listIndex(5) + """ + import new + + if name is None: + name = function.func_name + else: + function = RenameFunction(function, name) + + try: + klass = object.__class__ + except AttributeError: + # "object" is really a class, so it gets an unbound method. + object.__dict__[name] = new.instancemethod(function, None, object) + else: + # "object" is really an instance, so it gets a bound method. + object.__dict__[name] = new.instancemethod(function, object, klass) + +def RenameFunction(function, name): + """ + Returns a function identical to the specified function, but with + the specified name. + """ + import new + + # Compatibility for Python 1.5 and 2.1. Can be removed in favor of + # passing function.func_defaults directly to new.function() once + # we base on Python 2.2 or later. + func_defaults = function.func_defaults + if func_defaults is None: + func_defaults = () + + return new.function(function.func_code, + function.func_globals, + name, + func_defaults) + + +md5 = False +def MD5signature(s): + return str(s) + +def MD5filesignature(fname, chunksize=65536): + f = open(fname, "rb") + result = f.read() + f.close() + return result + +try: + import hashlib +except ImportError: + pass +else: + if hasattr(hashlib, 'md5'): + md5 = True + def MD5signature(s): + m = hashlib.md5() + m.update(str(s)) + return m.hexdigest() + + def MD5filesignature(fname, chunksize=65536): + m = hashlib.md5() + f = open(fname, "rb") + while 1: + blck = f.read(chunksize) + if not blck: + break + m.update(str(blck)) + f.close() + return m.hexdigest() + +def MD5collect(signatures): + """ + Collects a list of signatures into an aggregate signature. + + signatures - a list of signatures + returns - the aggregate signature + """ + if len(signatures) == 1: + return signatures[0] + else: + return MD5signature(string.join(signatures, ', ')) + + + +# Wrap the intern() function so it doesn't throw exceptions if ineligible +# arguments are passed. The intern() function was moved into the sys module in +# Python 3. +try: + intern +except NameError: + from sys import intern + +def silent_intern(x): + """ + Perform intern() on the passed argument and return the result. + If the input is ineligible (e.g. a unicode string) the original argument is + returned and no exception is thrown. + """ + try: + return intern(x) + except TypeError: + return x + + + +# From Dinu C. Gherman, +# Python Cookbook, second edition, recipe 6.17, p. 277. +# Also: +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205 +# ASPN: Python Cookbook: Null Object Design Pattern + +# TODO(1.5): +#class Null(object): +class Null: + """ Null objects always and reliably "do nothing." """ + def __new__(cls, *args, **kwargs): + if not '_inst' in vars(cls): + #cls._inst = type.__new__(cls, *args, **kwargs) + cls._inst = apply(type.__new__, (cls,) + args, kwargs) + return cls._inst + def __init__(self, *args, **kwargs): + pass + def __call__(self, *args, **kwargs): + return self + def __repr__(self): + return "Null(0x%08X)" % id(self) + def __nonzero__(self): + return False + def __getattr__(self, name): + return self + def __setattr__(self, name, value): + return self + def __delattr__(self, name): + return self + +class NullSeq(Null): + def __len__(self): + return 0 + def __iter__(self): + return iter(()) + def __getitem__(self, i): + return self + def __delitem__(self, i): + return self + def __setitem__(self, i, v): + return self + + +del __revision__ + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/UtilTests.py b/src/engine/SCons/UtilTests.py new file mode 100644 index 0000000..f14e7c3 --- /dev/null +++ b/src/engine/SCons/UtilTests.py @@ -0,0 +1,802 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/UtilTests.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import string +import StringIO +import sys +import types +import unittest + +from UserDict import UserDict + +import TestCmd + +import SCons.Errors + +from SCons.Util import * + +class OutBuffer: + def __init__(self): + self.buffer = "" + + def write(self, str): + self.buffer = self.buffer + str + +class dictifyTestCase(unittest.TestCase): + def test_dictify(self): + """Test the dictify() function""" + r = SCons.Util.dictify(['a', 'b', 'c'], [1, 2, 3]) + assert r == {'a':1, 'b':2, 'c':3}, r + + r = {} + SCons.Util.dictify(['a'], [1], r) + SCons.Util.dictify(['b'], [2], r) + SCons.Util.dictify(['c'], [3], r) + assert r == {'a':1, 'b':2, 'c':3}, r + +class UtilTestCase(unittest.TestCase): + def test_splitext(self): + assert splitext('foo') == ('foo','') + assert splitext('foo.bar') == ('foo','.bar') + assert splitext(os.path.join('foo.bar', 'blat')) == (os.path.join('foo.bar', 'blat'),'') + + class Node: + def __init__(self, name, children=[]): + self.children = children + self.name = name + self.nocache = None + def __str__(self): + return self.name + def exists(self): + return 1 + def rexists(self): + return 1 + def has_builder(self): + return 1 + def has_explicit_builder(self): + return 1 + def side_effect(self): + return 1 + def precious(self): + return 1 + def always_build(self): + return 1 + def is_up_to_date(self): + return 1 + def noclean(self): + return 1 + + def tree_case_1(self): + """Fixture for the render_tree() and print_tree() tests.""" + windows_h = self.Node("windows.h") + stdlib_h = self.Node("stdlib.h") + stdio_h = self.Node("stdio.h") + bar_c = self.Node("bar.c", [stdlib_h, windows_h]) + bar_o = self.Node("bar.o", [bar_c]) + foo_c = self.Node("foo.c", [stdio_h]) + foo_o = self.Node("foo.o", [foo_c]) + foo = self.Node("foo", [foo_o, bar_o]) + + expect = """\ ++-foo + +-foo.o + | +-foo.c + | +-stdio.h + +-bar.o + +-bar.c + +-stdlib.h + +-windows.h +""" + + lines = string.split(expect, '\n')[:-1] + lines = map(lambda l: '[E BSPACN ]'+l, lines) + withtags = string.join(lines, '\n') + '\n' + + return foo, expect, withtags + + def tree_case_2(self, prune=1): + """Fixture for the render_tree() and print_tree() tests.""" + + stdlib_h = self.Node("stdlib.h") + bar_h = self.Node('bar.h', [stdlib_h]) + blat_h = self.Node('blat.h', [stdlib_h]) + blat_c = self.Node('blat.c', [blat_h, bar_h]) + blat_o = self.Node('blat.o', [blat_c]) + + expect = """\ ++-blat.o + +-blat.c + +-blat.h + | +-stdlib.h + +-bar.h + +-[stdlib.h] +""" + + if not prune: + expect = string.replace(expect, '[', '') + expect = string.replace(expect, ']', '') + + lines = string.split(expect, '\n')[:-1] + lines = map(lambda l: '[E BSPACN ]'+l, lines) + withtags = string.join(lines, '\n') + '\n' + + return blat_o, expect, withtags + + def test_render_tree(self): + """Test the render_tree() function""" + def get_children(node): + return node.children + + node, expect, withtags = self.tree_case_1() + actual = render_tree(node, get_children) + assert expect == actual, (expect, actual) + + node, expect, withtags = self.tree_case_2() + actual = render_tree(node, get_children, 1) + assert expect == actual, (expect, actual) + + def test_print_tree(self): + """Test the print_tree() function""" + def get_children(node): + return node.children + + save_stdout = sys.stdout + + try: + node, expect, withtags = self.tree_case_1() + + sys.stdout = StringIO.StringIO() + print_tree(node, get_children) + actual = sys.stdout.getvalue() + assert expect == actual, (expect, actual) + + sys.stdout = StringIO.StringIO() + print_tree(node, get_children, showtags=1) + actual = sys.stdout.getvalue() + assert withtags == actual, (withtags, actual) + + node, expect, withtags = self.tree_case_2(prune=0) + + sys.stdout = StringIO.StringIO() + print_tree(node, get_children, 1) + actual = sys.stdout.getvalue() + assert expect == actual, (expect, actual) + + sys.stdout = StringIO.StringIO() + # The following call should work here: + # print_tree(node, get_children, 1, showtags=1) + # For some reason I don't understand, though, *this* + # time that we call print_tree, the visited dictionary + # is still populated with the values from the last call! + # I can't see why this would be, short of a bug in Python, + # and rather than continue banging my head against the + # brick wall for a *test*, we're going to going with + # the cheap, easy workaround: + print_tree(node, get_children, 1, showtags=1, visited={}) + actual = sys.stdout.getvalue() + assert withtags == actual, (withtags, actual) + finally: + sys.stdout = save_stdout + + def test_is_Dict(self): + assert is_Dict({}) + assert is_Dict(UserDict()) + try: + class mydict(dict): + pass + except TypeError: + pass + else: + assert is_Dict(mydict({})) + assert not is_Dict([]) + assert not is_Dict(()) + assert not is_Dict("") + if hasattr(types, 'UnicodeType'): + exec "assert not is_Dict(u'')" + + def test_is_List(self): + assert is_List([]) + import UserList + assert is_List(UserList.UserList()) + try: + class mylist(list): + pass + except TypeError: + pass + else: + assert is_List(mylist([])) + assert not is_List(()) + assert not is_List({}) + assert not is_List("") + if hasattr(types, 'UnicodeType'): + exec "assert not is_List(u'')" + + def test_is_String(self): + assert is_String("") + if hasattr(types, 'UnicodeType'): + exec "assert is_String(u'')" + try: + import UserString + except: + pass + else: + assert is_String(UserString.UserString('')) + try: + class mystr(str): + pass + except TypeError: + pass + else: + assert is_String(mystr('')) + assert not is_String({}) + assert not is_String([]) + assert not is_String(()) + + def test_is_Tuple(self): + assert is_Tuple(()) + try: + class mytuple(tuple): + pass + except TypeError: + pass + else: + assert is_Tuple(mytuple(())) + assert not is_Tuple([]) + assert not is_Tuple({}) + assert not is_Tuple("") + if hasattr(types, 'UnicodeType'): + exec "assert not is_Tuple(u'')" + + def test_to_String(self): + """Test the to_String() method.""" + assert to_String(1) == "1", to_String(1) + assert to_String([ 1, 2, 3]) == str([1, 2, 3]), to_String([1,2,3]) + assert to_String("foo") == "foo", to_String("foo") + + try: + import UserString + + s1=UserString.UserString('blah') + assert to_String(s1) == s1, s1 + assert to_String(s1) == 'blah', s1 + + class Derived(UserString.UserString): + pass + s2 = Derived('foo') + assert to_String(s2) == s2, s2 + assert to_String(s2) == 'foo', s2 + + if hasattr(types, 'UnicodeType'): + s3=UserString.UserString(unicode('bar')) + assert to_String(s3) == s3, s3 + assert to_String(s3) == unicode('bar'), s3 + assert type(to_String(s3)) is types.UnicodeType, \ + type(to_String(s3)) + except ImportError: + pass + + if hasattr(types, 'UnicodeType'): + s4 = unicode('baz') + assert to_String(s4) == unicode('baz'), to_String(s4) + assert type(to_String(s4)) is types.UnicodeType, \ + type(to_String(s4)) + + def test_WhereIs(self): + test = TestCmd.TestCmd(workdir = '') + + sub1_xxx_exe = test.workpath('sub1', 'xxx.exe') + sub2_xxx_exe = test.workpath('sub2', 'xxx.exe') + sub3_xxx_exe = test.workpath('sub3', 'xxx.exe') + sub4_xxx_exe = test.workpath('sub4', 'xxx.exe') + + test.subdir('subdir', 'sub1', 'sub2', 'sub3', 'sub4') + + if sys.platform != 'win32': + test.write(sub1_xxx_exe, "\n") + + os.mkdir(sub2_xxx_exe) + + test.write(sub3_xxx_exe, "\n") + os.chmod(sub3_xxx_exe, 0777) + + test.write(sub4_xxx_exe, "\n") + os.chmod(sub4_xxx_exe, 0777) + + env_path = os.environ['PATH'] + + try: + pathdirs_1234 = [ test.workpath('sub1'), + test.workpath('sub2'), + test.workpath('sub3'), + test.workpath('sub4'), + ] + string.split(env_path, os.pathsep) + + pathdirs_1243 = [ test.workpath('sub1'), + test.workpath('sub2'), + test.workpath('sub4'), + test.workpath('sub3'), + ] + string.split(env_path, os.pathsep) + + os.environ['PATH'] = string.join(pathdirs_1234, os.pathsep) + wi = WhereIs('xxx.exe') + assert wi == test.workpath(sub3_xxx_exe), wi + wi = WhereIs('xxx.exe', pathdirs_1243) + assert wi == test.workpath(sub4_xxx_exe), wi + wi = WhereIs('xxx.exe', string.join(pathdirs_1243, os.pathsep)) + assert wi == test.workpath(sub4_xxx_exe), wi + + wi = WhereIs('xxx.exe',reject = sub3_xxx_exe) + assert wi == test.workpath(sub4_xxx_exe), wi + wi = WhereIs('xxx.exe', pathdirs_1243, reject = sub3_xxx_exe) + assert wi == test.workpath(sub4_xxx_exe), wi + + os.environ['PATH'] = string.join(pathdirs_1243, os.pathsep) + wi = WhereIs('xxx.exe') + assert wi == test.workpath(sub4_xxx_exe), wi + wi = WhereIs('xxx.exe', pathdirs_1234) + assert wi == test.workpath(sub3_xxx_exe), wi + wi = WhereIs('xxx.exe', string.join(pathdirs_1234, os.pathsep)) + assert wi == test.workpath(sub3_xxx_exe), wi + + if sys.platform == 'win32': + wi = WhereIs('xxx', pathext = '') + assert wi is None, wi + + wi = WhereIs('xxx', pathext = '.exe') + assert wi == test.workpath(sub4_xxx_exe), wi + + wi = WhereIs('xxx', path = pathdirs_1234, pathext = '.BAT;.EXE') + assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi + + # Test that we return a normalized path even when + # the path contains forward slashes. + forward_slash = test.workpath('') + '/sub3' + wi = WhereIs('xxx', path = forward_slash, pathext = '.EXE') + assert string.lower(wi) == string.lower(test.workpath(sub3_xxx_exe)), wi + + del os.environ['PATH'] + wi = WhereIs('xxx.exe') + assert wi is None, wi + + finally: + os.environ['PATH'] = env_path + + def test_get_env_var(self): + """Testing get_environment_var().""" + assert get_environment_var("$FOO") == "FOO", get_environment_var("$FOO") + assert get_environment_var("${BAR}") == "BAR", get_environment_var("${BAR}") + assert get_environment_var("$FOO_BAR1234") == "FOO_BAR1234", get_environment_var("$FOO_BAR1234") + assert get_environment_var("${BAR_FOO1234}") == "BAR_FOO1234", get_environment_var("${BAR_FOO1234}") + assert get_environment_var("${BAR}FOO") == None, get_environment_var("${BAR}FOO") + assert get_environment_var("$BAR ") == None, get_environment_var("$BAR ") + assert get_environment_var("FOO$BAR") == None, get_environment_var("FOO$BAR") + assert get_environment_var("$FOO[0]") == None, get_environment_var("$FOO[0]") + assert get_environment_var("${some('complex expression')}") == None, get_environment_var("${some('complex expression')}") + + def test_Proxy(self): + """Test generic Proxy class.""" + class Subject: + def foo(self): + return 1 + def bar(self): + return 2 + + s=Subject() + s.baz = 3 + + class ProxyTest(Proxy): + def bar(self): + return 4 + + p=ProxyTest(s) + + assert p.foo() == 1, p.foo() + assert p.bar() == 4, p.bar() + assert p.baz == 3, p.baz + + p.baz = 5 + s.baz = 6 + + assert p.baz == 5, p.baz + assert p.get() == s, p.get() + + def test_display(self): + old_stdout = sys.stdout + sys.stdout = OutBuffer() + display("line1") + display.set_mode(0) + display("line2") + display.set_mode(1) + display("line3") + display("line4\n", append_newline=0) + display.set_mode(0) + display("dont print1") + display("dont print2\n", append_newline=0) + display.set_mode(1) + assert sys.stdout.buffer == "line1\nline3\nline4\n" + sys.stdout = old_stdout + + def test_get_native_path(self): + """Test the get_native_path() function.""" + import tempfile + filename = tempfile.mktemp() + str = '1234567890 ' + filename + try: + open(filename, 'w').write(str) + assert open(get_native_path(filename)).read() == str + finally: + try: + os.unlink(filename) + except OSError: + pass + + def test_PrependPath(self): + """Test prepending to a path""" + p1 = r'C:\dir\num\one;C:\dir\num\two' + p2 = r'C:\mydir\num\one;C:\mydir\num\two' + # have to include the pathsep here so that the test will work on UNIX too. + p1 = PrependPath(p1,r'C:\dir\num\two',sep = ';') + p1 = PrependPath(p1,r'C:\dir\num\three',sep = ';') + p2 = PrependPath(p2,r'C:\mydir\num\three',sep = ';') + p2 = PrependPath(p2,r'C:\mydir\num\one',sep = ';') + assert(p1 == r'C:\dir\num\three;C:\dir\num\two;C:\dir\num\one') + assert(p2 == r'C:\mydir\num\one;C:\mydir\num\three;C:\mydir\num\two') + + def test_AppendPath(self): + """Test appending to a path.""" + p1 = r'C:\dir\num\one;C:\dir\num\two' + p2 = r'C:\mydir\num\one;C:\mydir\num\two' + # have to include the pathsep here so that the test will work on UNIX too. + p1 = AppendPath(p1,r'C:\dir\num\two',sep = ';') + p1 = AppendPath(p1,r'C:\dir\num\three',sep = ';') + p2 = AppendPath(p2,r'C:\mydir\num\three',sep = ';') + p2 = AppendPath(p2,r'C:\mydir\num\one',sep = ';') + assert(p1 == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three') + assert(p2 == r'C:\mydir\num\two;C:\mydir\num\three;C:\mydir\num\one') + + def test_PrependPathPreserveOld(self): + """Test prepending to a path while preserving old paths""" + p1 = r'C:\dir\num\one;C:\dir\num\two' + # have to include the pathsep here so that the test will work on UNIX too. + p1 = PrependPath(p1,r'C:\dir\num\two',sep = ';', delete_existing=0) + p1 = PrependPath(p1,r'C:\dir\num\three',sep = ';') + assert(p1 == r'C:\dir\num\three;C:\dir\num\one;C:\dir\num\two') + + def test_AppendPathPreserveOld(self): + """Test appending to a path while preserving old paths""" + p1 = r'C:\dir\num\one;C:\dir\num\two' + # have to include the pathsep here so that the test will work on UNIX too. + p1 = AppendPath(p1,r'C:\dir\num\one',sep = ';', delete_existing=0) + p1 = AppendPath(p1,r'C:\dir\num\three',sep = ';') + assert(p1 == r'C:\dir\num\one;C:\dir\num\two;C:\dir\num\three') + + def test_CLVar(self): + """Test the command-line construction variable class""" + f = SCons.Util.CLVar('a b') + + r = f + 'c d' + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ' c d' + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ['c d'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c d'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + [' c d'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', ' c d'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ['c', 'd'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + [' c', 'd'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', ' c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + f = SCons.Util.CLVar(['a b']) + + r = f + 'c d' + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ' c d' + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ['c d'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a b', 'c d'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + [' c d'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a b', ' c d'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ['c', 'd'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + [' c', 'd'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a b', ' c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + f = SCons.Util.CLVar(['a', 'b']) + + r = f + 'c d' + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ' c d' + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ['c d'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c d'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + [' c d'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', ' c d'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + ['c', 'd'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', 'c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + r = f + [' c', 'd'] + assert isinstance(r, SCons.Util.CLVar), type(r) + assert r.data == ['a', 'b', ' c', 'd'], r.data + assert str(r) == 'a b c d', str(r) + + def test_Selector(self): + """Test the Selector class""" + + class MyNode: + def __init__(self, name): + self.name = name + self.suffix = os.path.splitext(name)[1] + + def __str__(self): + return self.name + + s = Selector({'a' : 'AAA', 'b' : 'BBB'}) + assert s['a'] == 'AAA', s['a'] + assert s['b'] == 'BBB', s['b'] + exc_caught = None + try: + x = s['c'] + except KeyError: + exc_caught = 1 + assert exc_caught, "should have caught a KeyError" + s['c'] = 'CCC' + assert s['c'] == 'CCC', s['c'] + + class DummyEnv(UserDict): + def subst(self, key): + if key[0] == '$': + return self[key[1:]] + return key + + env = DummyEnv() + + s = Selector({'.d' : 'DDD', '.e' : 'EEE'}) + ret = s(env, []) + assert ret is None, ret + ret = s(env, [MyNode('foo.d')]) + assert ret == 'DDD', ret + ret = s(env, [MyNode('bar.e')]) + assert ret == 'EEE', ret + ret = s(env, [MyNode('bar.x')]) + assert ret is None, ret + s[None] = 'XXX' + ret = s(env, [MyNode('bar.x')]) + assert ret == 'XXX', ret + + env = DummyEnv({'FSUFF' : '.f', 'GSUFF' : '.g'}) + + s = Selector({'$FSUFF' : 'FFF', '$GSUFF' : 'GGG'}) + ret = s(env, [MyNode('foo.f')]) + assert ret == 'FFF', ret + ret = s(env, [MyNode('bar.g')]) + assert ret == 'GGG', ret + + def test_adjustixes(self): + """Test the adjustixes() function""" + r = adjustixes('file', 'pre-', '-suf') + assert r == 'pre-file-suf', r + r = adjustixes('pre-file', 'pre-', '-suf') + assert r == 'pre-file-suf', r + r = adjustixes('file-suf', 'pre-', '-suf') + assert r == 'pre-file-suf', r + r = adjustixes('pre-file-suf', 'pre-', '-suf') + assert r == 'pre-file-suf', r + r = adjustixes('pre-file.xxx', 'pre-', '-suf') + assert r == 'pre-file.xxx', r + r = adjustixes('dir/file', 'pre-', '-suf') + assert r == os.path.join('dir', 'pre-file-suf'), r + + def test_containsAny(self): + """Test the containsAny() function""" + assert containsAny('*.py', '*?[]') + assert not containsAny('file.txt', '*?[]') + + def test_containsAll(self): + """Test the containsAll() function""" + assert containsAll('43221', '123') + assert not containsAll('134', '123') + + def test_containsOnly(self): + """Test the containsOnly() function""" + assert containsOnly('.83', '0123456789.') + assert not containsOnly('43221', '123') + + def test_LogicalLines(self): + """Test the LogicalLines class""" + fobj = StringIO.StringIO(r""" +foo \ +bar \ +baz +foo +bling \ +bling \ bling +bling +""") + + lines = LogicalLines(fobj).readlines() + assert lines == [ + '\n', + 'foo bar baz\n', + 'foo\n', + 'bling bling \\ bling\n', + 'bling\n', + ], lines + + def test_intern(self): + s1 = silent_intern("spam") + # Python 1.5 and 3.x do not have a unicode() built-in + if sys.version[0] == '2': + s2 = silent_intern(unicode("unicode spam")) + s3 = silent_intern(42) + s4 = silent_intern("spam") + assert id(s1) == id(s4) + + +class MD5TestCase(unittest.TestCase): + + def test_collect(self): + """Test collecting a list of signatures into a new signature value + """ + s = map(MD5signature, ('111', '222', '333')) + + assert '698d51a19d8a121ce581499d7b701668' == MD5collect(s[0:1]) + assert '8980c988edc2c78cc43ccb718c06efd5' == MD5collect(s[0:2]) + assert '53fd88c84ff8a285eb6e0a687e55b8c7' == MD5collect(s) + + def test_MD5signature(self): + """Test generating a signature""" + s = MD5signature('111') + assert '698d51a19d8a121ce581499d7b701668' == s, s + + s = MD5signature('222') + assert 'bcbe3365e6ac95ea2c0343a2395834dd' == s, s + +class NodeListTestCase(unittest.TestCase): + def test_simple_attributes(self): + """Test simple attributes of a NodeList class""" + class TestClass: + def __init__(self, name, child=None): + self.child = child + self.bar = name + + t1 = TestClass('t1', TestClass('t1child')) + t2 = TestClass('t2', TestClass('t2child')) + t3 = TestClass('t3') + + nl = NodeList([t1, t2, t3]) + assert nl.bar == [ 't1', 't2', 't3' ], nl.bar + assert nl[0:2].child.bar == [ 't1child', 't2child' ], \ + nl[0:2].child.bar + + def test_callable_attributes(self): + """Test callable attributes of a NodeList class""" + class TestClass: + def __init__(self, name, child=None): + self.child = child + self.bar = name + def foo(self): + return self.bar + "foo" + def getself(self): + return self + + t1 = TestClass('t1', TestClass('t1child')) + t2 = TestClass('t2', TestClass('t2child')) + t3 = TestClass('t3') + + nl = NodeList([t1, t2, t3]) + assert nl.foo() == [ 't1foo', 't2foo', 't3foo' ], nl.foo() + assert nl.bar == [ 't1', 't2', 't3' ], nl.bar + assert nl.getself().bar == [ 't1', 't2', 't3' ], nl.getself().bar + assert nl[0:2].child.foo() == [ 't1childfoo', 't2childfoo' ], \ + nl[0:2].child.foo() + assert nl[0:2].child.bar == [ 't1child', 't2child' ], \ + nl[0:2].child.bar + + def test_null(self): + """Test a null NodeList""" + nl = NodeList([]) + r = str(nl) + assert r == '', r + for node in nl: + raise Exception, "should not enter this loop" + + +class flattenTestCase(unittest.TestCase): + + def test_scalar(self): + """Test flattening a scalar""" + result = flatten('xyz') + assert result == ['xyz'], result + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ dictifyTestCase, + flattenTestCase, + MD5TestCase, + NodeListTestCase, + UtilTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/BoolVariable.py b/src/engine/SCons/Variables/BoolVariable.py new file mode 100644 index 0000000..1209a4c --- /dev/null +++ b/src/engine/SCons/Variables/BoolVariable.py @@ -0,0 +1,91 @@ +"""engine.SCons.Variables.BoolVariable + +This file defines the option type for SCons implementing true/false values. + +Usage example: + + opts = Variables() + opts.Add(BoolVariable('embedded', 'build for an embedded system', 0)) + ... + if env['embedded'] == 1: + ... +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/BoolVariable.py 4577 2009/12/27 19:44:43 scons" + +__all__ = ['BoolVariable',] + +import string + +import SCons.Errors + +__true_strings = ('y', 'yes', 'true', 't', '1', 'on' , 'all' ) +__false_strings = ('n', 'no', 'false', 'f', '0', 'off', 'none') + + +def _text2bool(val): + """ + Converts strings to True/False depending on the 'truth' expressed by + the string. If the string can't be converted, the original value + will be returned. + + See '__true_strings' and '__false_strings' for values considered + 'true' or 'false respectivly. + + This is usable as 'converter' for SCons' Variables. + """ + lval = string.lower(val) + if lval in __true_strings: return True + if lval in __false_strings: return False + raise ValueError("Invalid value for boolean option: %s" % val) + + +def _validator(key, val, env): + """ + Validates the given value to be either '0' or '1'. + + This is usable as 'validator' for SCons' Variables. + """ + if not env[key] in (True, False): + raise SCons.Errors.UserError( + 'Invalid value for boolean option %s: %s' % (key, env[key])) + + +def BoolVariable(key, help, default): + """ + The input parameters describe a boolen option, thus they are + returned with the correct converter and validator appended. The + 'help' text will by appended by '(yes|no) to show the valid + valued. The result is usable for input to opts.Add(). + """ + return (key, '%s (yes|no)' % help, default, + _validator, _text2bool) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/BoolVariableTests.py b/src/engine/SCons/Variables/BoolVariableTests.py new file mode 100644 index 0000000..c33d9d0 --- /dev/null +++ b/src/engine/SCons/Variables/BoolVariableTests.py @@ -0,0 +1,127 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/BoolVariableTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Variables + +class BoolVariableTestCase(unittest.TestCase): + def test_BoolVariable(self): + """Test BoolVariable creation""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.BoolVariable('test', 'test option help', 0)) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help (yes|no)', o.help + assert o.default == 0, o.default + assert o.validator is not None, o.validator + assert o.converter is not None, o.converter + + def test_converter(self): + """Test the BoolVariable converter""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.BoolVariable('test', 'test option help', 0)) + + o = opts.options[0] + + true_values = [ + 'y', 'Y', + 'yes', 'YES', + 't', 'T', + 'true', 'TRUE', + 'on', 'ON', + 'all', 'ALL', + '1', + ] + false_values = [ + 'n', 'N', + 'no', 'NO', + 'f', 'F', + 'false', 'FALSE', + 'off', 'OFF', + 'none', 'NONE', + '0', + ] + + for t in true_values: + x = o.converter(t) + assert x, "converter returned false for '%s'" % t + + for f in false_values: + x = o.converter(f) + assert not x, "converter returned true for '%s'" % f + + caught = None + try: + o.converter('x') + except ValueError: + caught = 1 + assert caught, "did not catch expected ValueError" + + def test_validator(self): + """Test the BoolVariable validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.BoolVariable('test', 'test option help', 0)) + + o = opts.options[0] + + env = { + 'T' : True, + 'F' : False, + 'N' : 'xyzzy', + } + + o.validator('T', 0, env) + + o.validator('F', 0, env) + + caught = None + try: + o.validator('N', 0, env) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError for N" + + caught = None + try: + o.validator('NOSUCHKEY', 0, env) + except KeyError: + caught = 1 + assert caught, "did not catch expected KeyError for NOSUCHKEY" + + +if __name__ == "__main__": + suite = unittest.makeSuite(BoolVariableTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/EnumVariable.py b/src/engine/SCons/Variables/EnumVariable.py new file mode 100644 index 0000000..90ed48e --- /dev/null +++ b/src/engine/SCons/Variables/EnumVariable.py @@ -0,0 +1,107 @@ +"""engine.SCons.Variables.EnumVariable + +This file defines the option type for SCons allowing only specified +input-values. + +Usage example: + + opts = Variables() + opts.Add(EnumVariable('debug', 'debug output and symbols', 'no', + allowed_values=('yes', 'no', 'full'), + map={}, ignorecase=2)) + ... + if env['debug'] == 'full': + ... +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/EnumVariable.py 4577 2009/12/27 19:44:43 scons" + +__all__ = ['EnumVariable',] + +import string + +import SCons.Errors + +def _validator(key, val, env, vals): + if not val in vals: + raise SCons.Errors.UserError( + 'Invalid value for option %s: %s' % (key, val)) + + +def EnumVariable(key, help, default, allowed_values, map={}, ignorecase=0): + """ + The input parameters describe a option with only certain values + allowed. They are returned with an appropriate converter and + validator appended. The result is usable for input to + Variables.Add(). + + 'key' and 'default' are the values to be passed on to Variables.Add(). + + 'help' will be appended by the allowed values automatically + + 'allowed_values' is a list of strings, which are allowed as values + for this option. + + The 'map'-dictionary may be used for converting the input value + into canonical values (eg. for aliases). + + 'ignorecase' defines the behaviour of the validator: + + If ignorecase == 0, the validator/converter are case-sensitive. + If ignorecase == 1, the validator/converter are case-insensitive. + If ignorecase == 2, the validator/converter is case-insensitive and + the converted value will always be lower-case. + + The 'validator' tests whether the value is in the list of allowed + values. The 'converter' converts input values according to the + given 'map'-dictionary (unmapped input values are returned + unchanged). + """ + help = '%s (%s)' % (help, string.join(allowed_values, '|')) + # define validator + if ignorecase >= 1: + validator = lambda key, val, env, vals=allowed_values: \ + _validator(key, string.lower(val), env, vals) + else: + validator = lambda key, val, env, vals=allowed_values: \ + _validator(key, val, env, vals) + # define converter + if ignorecase == 2: + converter = lambda val, map=map: \ + string.lower(map.get(string.lower(val), val)) + elif ignorecase == 1: + converter = lambda val, map=map: \ + map.get(string.lower(val), val) + else: + converter = lambda val, map=map: \ + map.get(val, val) + return (key, help, default, validator, converter) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/EnumVariableTests.py b/src/engine/SCons/Variables/EnumVariableTests.py new file mode 100644 index 0000000..668487d --- /dev/null +++ b/src/engine/SCons/Variables/EnumVariableTests.py @@ -0,0 +1,204 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/EnumVariableTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Variables + +class EnumVariableTestCase(unittest.TestCase): + def test_EnumVariable(self): + """Test EnumVariable creation""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.EnumVariable('test', 'test option help', 0, + ['one', 'two', 'three'], + {})) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help (one|two|three)', o.help + assert o.default == 0, o.default + assert o.validator is not None, o.validator + assert o.converter is not None, o.converter + + def test_converter(self): + """Test the EnumVariable converter""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.EnumVariable('test', 'test option help', 0, + ['one', 'two', 'three'])) + + o = opts.options[0] + + for a in ['one', 'two', 'three', 'no_match']: + x = o.converter(a) + assert x == a, x + + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.EnumVariable('test', 'test option help', 0, + ['one', 'two', 'three'], + {'1' : 'one', + '2' : 'two', + '3' : 'three'})) + + o = opts.options[0] + + x = o.converter('one') + assert x == 'one', x + x = o.converter('1') + assert x == 'one', x + + x = o.converter('two') + assert x == 'two', x + x = o.converter('2') + assert x == 'two', x + + x = o.converter('three') + assert x == 'three', x + x = o.converter('3') + assert x == 'three', x + + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.EnumVariable('test0', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=0)) + opts.Add(SCons.Variables.EnumVariable('test1', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=1)) + opts.Add(SCons.Variables.EnumVariable('test2', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=2)) + + o0 = opts.options[0] + o1 = opts.options[1] + o2 = opts.options[2] + + table = { + 'one' : ['one', 'one', 'one'], + 'One' : ['One', 'One', 'one'], + 'ONE' : ['ONE', 'ONE', 'one'], + 'two' : ['two', 'two', 'two'], + 'twO' : ['twO', 'twO', 'two'], + 'TWO' : ['TWO', 'TWO', 'two'], + 'three' : ['three', 'three', 'three'], + 'thRee' : ['thRee', 'thRee', 'three'], + 'THREE' : ['THREE', 'THREE', 'three'], + 'a' : ['one', 'one', 'one'], + 'A' : ['A', 'one', 'one'], + 'b' : ['two', 'two', 'two'], + 'B' : ['B', 'two', 'two'], + 'c' : ['three', 'three', 'three'], + 'C' : ['C', 'three', 'three'], + } + + for k, l in table.items(): + x = o0.converter(k) + assert x == l[0], "o0 got %s, expected %s" % (x, l[0]) + x = o1.converter(k) + assert x == l[1], "o1 got %s, expected %s" % (x, l[1]) + x = o2.converter(k) + assert x == l[2], "o2 got %s, expected %s" % (x, l[2]) + + def test_validator(self): + """Test the EnumVariable validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.EnumVariable('test0', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=0)) + opts.Add(SCons.Variables.EnumVariable('test1', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=1)) + opts.Add(SCons.Variables.EnumVariable('test2', 'test option help', 0, + ['one', 'two', 'three'], + {'a' : 'one', + 'b' : 'two', + 'c' : 'three'}, + ignorecase=2)) + + o0 = opts.options[0] + o1 = opts.options[1] + o2 = opts.options[2] + + def valid(o, v): + o.validator('X', v, {}) + + def invalid(o, v): + caught = None + try: + o.validator('X', v, {}) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError for o = %s, v = %s" % (o.key, v) + + table = { + 'one' : [ valid, valid, valid], + 'One' : [invalid, valid, valid], + 'ONE' : [invalid, valid, valid], + 'two' : [ valid, valid, valid], + 'twO' : [invalid, valid, valid], + 'TWO' : [invalid, valid, valid], + 'three' : [ valid, valid, valid], + 'thRee' : [invalid, valid, valid], + 'THREE' : [invalid, valid, valid], + 'a' : [invalid, invalid, invalid], + 'A' : [invalid, invalid, invalid], + 'b' : [invalid, invalid, invalid], + 'B' : [invalid, invalid, invalid], + 'c' : [invalid, invalid, invalid], + 'C' : [invalid, invalid, invalid], + 'no_v' : [invalid, invalid, invalid], + } + + for v, l in table.items(): + l[0](o0, v) + l[1](o1, v) + l[2](o2, v) + + +if __name__ == "__main__": + suite = unittest.makeSuite(EnumVariableTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/ListVariable.py b/src/engine/SCons/Variables/ListVariable.py new file mode 100644 index 0000000..0debd14 --- /dev/null +++ b/src/engine/SCons/Variables/ListVariable.py @@ -0,0 +1,139 @@ +"""engine.SCons.Variables.ListVariable + +This file defines the option type for SCons implementing 'lists'. + +A 'list' option may either be 'all', 'none' or a list of names +separated by comma. After the option has been processed, the option +value holds either the named list elements, all list elemens or no +list elements at all. + +Usage example: + + list_of_libs = Split('x11 gl qt ical') + + opts = Variables() + opts.Add(ListVariable('shared', + 'libraries to build as shared libraries', + 'all', + elems = list_of_libs)) + ... + for lib in list_of_libs: + if lib in env['shared']: + env.SharedObject(...) + else: + env.Object(...) +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/ListVariable.py 4577 2009/12/27 19:44:43 scons" + +# Know Bug: This should behave like a Set-Type, but does not really, +# since elements can occur twice. + +__all__ = ['ListVariable',] + +import string +import UserList + +import SCons.Util + + +class _ListVariable(UserList.UserList): + def __init__(self, initlist=[], allowedElems=[]): + UserList.UserList.__init__(self, filter(None, initlist)) + self.allowedElems = allowedElems[:] + self.allowedElems.sort() + + def __cmp__(self, other): + raise NotImplementedError + def __eq__(self, other): + raise NotImplementedError + def __ge__(self, other): + raise NotImplementedError + def __gt__(self, other): + raise NotImplementedError + def __le__(self, other): + raise NotImplementedError + def __lt__(self, other): + raise NotImplementedError + def __str__(self): + if len(self) == 0: + return 'none' + self.data.sort() + if self.data == self.allowedElems: + return 'all' + else: + return string.join(self, ',') + def prepare_to_store(self): + return self.__str__() + +def _converter(val, allowedElems, mapdict): + """ + """ + if val == 'none': + val = [] + elif val == 'all': + val = allowedElems + else: + val = filter(None, string.split(val, ',')) + val = map(lambda v, m=mapdict: m.get(v, v), val) + notAllowed = filter(lambda v, aE=allowedElems: not v in aE, val) + if notAllowed: + raise ValueError("Invalid value(s) for option: %s" % + string.join(notAllowed, ',')) + return _ListVariable(val, allowedElems) + + +## def _validator(key, val, env): +## """ +## """ +## # todo: write validater for pgk list +## return 1 + + +def ListVariable(key, help, default, names, map={}): + """ + The input parameters describe a 'package list' option, thus they + are returned with the correct converter and validater appended. The + result is usable for input to opts.Add() . + + A 'package list' option may either be 'all', 'none' or a list of + package names (separated by space). + """ + names_str = 'allowed names: %s' % string.join(names, ' ') + if SCons.Util.is_List(default): + default = string.join(default, ',') + help = string.join( + (help, '(all|none|comma-separated list of names)', names_str), + '\n ') + return (key, help, default, + None, #_validator, + lambda val, elems=names, m=map: _converter(val, elems, m)) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/ListVariableTests.py b/src/engine/SCons/Variables/ListVariableTests.py new file mode 100644 index 0000000..353fbe6 --- /dev/null +++ b/src/engine/SCons/Variables/ListVariableTests.py @@ -0,0 +1,134 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/ListVariableTests.py 4577 2009/12/27 19:44:43 scons" + +import copy +import sys +import unittest + +import SCons.Errors +import SCons.Variables + +class ListVariableTestCase(unittest.TestCase): + def test_ListVariable(self): + """Test ListVariable creation""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.ListVariable('test', 'test option help', 'all', + ['one', 'two', 'three'])) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help\n (all|none|comma-separated list of names)\n allowed names: one two three', repr(o.help) + assert o.default == 'all', o.default + assert o.validator is None, o.validator + assert not o.converter is None, o.converter + + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.ListVariable('test2', 'test2 help', + ['one', 'three'], + ['one', 'two', 'three'])) + + o = opts.options[0] + assert o.default == 'one,three' + + def test_converter(self): + """Test the ListVariable converter""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.ListVariable('test', 'test option help', 'all', + ['one', 'two', 'three'], + {'ONE':'one', 'TWO':'two'})) + + o = opts.options[0] + + x = o.converter('all') + assert str(x) == 'all', x + + x = o.converter('none') + assert str(x) == 'none', x + + x = o.converter('one') + assert str(x) == 'one', x + x = o.converter('ONE') + assert str(x) == 'one', x + + x = o.converter('two') + assert str(x) == 'two', x + x = o.converter('TWO') + assert str(x) == 'two', x + + x = o.converter('three') + assert str(x) == 'three', x + + x = o.converter('one,two') + assert str(x) == 'one,two', x + x = o.converter('two,one') + assert str(x) == 'one,two', x + + x = o.converter('one,three') + assert str(x) == 'one,three', x + x = o.converter('three,one') + assert str(x) == 'one,three', x + + x = o.converter('two,three') + assert str(x) == 'three,two', x + x = o.converter('three,two') + assert str(x) == 'three,two', x + + x = o.converter('one,two,three') + assert str(x) == 'all', x + + x = o.converter('three,two,one') + assert str(x) == 'all', x + + x = o.converter('three,ONE,TWO') + assert str(x) == 'all', x + + caught = None + try: + x = o.converter('no_match') + except ValueError: + caught = 1 + assert caught, "did not catch expected ValueError" + + def test_copy(self): + """Test copying a ListVariable like an Environment would""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.ListVariable('test', 'test option help', 'all', + ['one', 'two', 'three'])) + + o = opts.options[0] + + l = o.converter('all') + n = l.__class__(copy.copy(l)) + +if __name__ == "__main__": + suite = unittest.makeSuite(ListVariableTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/PackageVariable.py b/src/engine/SCons/Variables/PackageVariable.py new file mode 100644 index 0000000..e10ff0c --- /dev/null +++ b/src/engine/SCons/Variables/PackageVariable.py @@ -0,0 +1,109 @@ +"""engine.SCons.Variables.PackageVariable + +This file defines the option type for SCons implementing 'package +activation'. + +To be used whenever a 'package' may be enabled/disabled and the +package path may be specified. + +Usage example: + + Examples: + x11=no (disables X11 support) + x11=yes (will search for the package installation dir) + x11=/usr/local/X11 (will check this path for existance) + + To replace autoconf's --with-xxx=yyy + + opts = Variables() + opts.Add(PackageVariable('x11', + 'use X11 installed here (yes = search some places', + 'yes')) + ... + if env['x11'] == True: + dir = ... search X11 in some standard places ... + env['x11'] = dir + if env['x11']: + ... build with x11 ... +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/PackageVariable.py 4577 2009/12/27 19:44:43 scons" + +__all__ = ['PackageVariable',] + +import string + +import SCons.Errors + +__enable_strings = ('1', 'yes', 'true', 'on', 'enable', 'search') +__disable_strings = ('0', 'no', 'false', 'off', 'disable') + +def _converter(val): + """ + """ + lval = string.lower(val) + if lval in __enable_strings: return True + if lval in __disable_strings: return False + #raise ValueError("Invalid value for boolean option: %s" % val) + return val + + +def _validator(key, val, env, searchfunc): + # NB: searchfunc is currenty undocumented and unsupported + """ + """ + # todo: write validator, check for path + import os + if env[key] is True: + if searchfunc: + env[key] = searchfunc(key, val) + elif env[key] and not os.path.exists(val): + raise SCons.Errors.UserError( + 'Path does not exist for option %s: %s' % (key, val)) + + +def PackageVariable(key, help, default, searchfunc=None): + # NB: searchfunc is currenty undocumented and unsupported + """ + The input parameters describe a 'package list' option, thus they + are returned with the correct converter and validator appended. The + result is usable for input to opts.Add() . + + A 'package list' option may either be 'all', 'none' or a list of + package names (seperated by space). + """ + help = string.join( + (help, '( yes | no | /path/to/%s )' % key), + '\n ') + return (key, help, default, + lambda k, v, e, f=searchfunc: _validator(k,v,e,f), + _converter) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/PackageVariableTests.py b/src/engine/SCons/Variables/PackageVariableTests.py new file mode 100644 index 0000000..70ea191 --- /dev/null +++ b/src/engine/SCons/Variables/PackageVariableTests.py @@ -0,0 +1,124 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/PackageVariableTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest + +import SCons.Errors +import SCons.Variables + +import TestCmd + +class PackageVariableTestCase(unittest.TestCase): + def test_PackageVariable(self): + """Test PackageVariable creation""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PackageVariable('test', 'test option help', '/default/path')) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help\n ( yes | no | /path/to/test )', repr(o.help) + assert o.default == '/default/path', o.default + assert o.validator is not None, o.validator + assert o.converter is not None, o.converter + + def test_converter(self): + """Test the PackageVariable converter""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PackageVariable('test', 'test option help', '/default/path')) + + o = opts.options[0] + + true_values = [ + 'yes', 'YES', + 'true', 'TRUE', + 'on', 'ON', + 'enable', 'ENABLE', + 'search', 'SEARCH', + ] + false_values = [ + 'no', 'NO', + 'false', 'FALSE', + 'off', 'OFF', + 'disable', 'DISABLE', + ] + + for t in true_values: + x = o.converter(t) + assert x, "converter returned false for '%s'" % t + + for f in false_values: + x = o.converter(f) + assert not x, "converter returned true for '%s'" % f + + x = o.converter('/explicit/path') + assert x == '/explicit/path', x + + # Make sure the converter returns True if we give it str(True) and + # False when we give it str(False). This assures consistent operation + # through a cycle of Variables.Save() -> Variables(). + x = o.converter(str(True)) + assert x == True, "converter returned a string when given str(True)" + + x = o.converter(str(False)) + assert x == False, "converter returned a string when given str(False)" + + def test_validator(self): + """Test the PackageVariable validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PackageVariable('test', 'test option help', '/default/path')) + + test = TestCmd.TestCmd(workdir='') + test.write('exists', 'exists\n') + + o = opts.options[0] + + env = {'F':False, 'T':True, 'X':'x'} + + exists = test.workpath('exists') + does_not_exist = test.workpath('does_not_exist') + + o.validator('F', '/path', env) + o.validator('T', '/path', env) + o.validator('X', exists, env) + + caught = None + try: + o.validator('X', does_not_exist, env) + except SCons.Errors.UserError: + caught = 1 + assert caught, "did not catch expected UserError" + + +if __name__ == "__main__": + suite = unittest.makeSuite(PackageVariableTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/PathVariable.py b/src/engine/SCons/Variables/PathVariable.py new file mode 100644 index 0000000..2ebbd8d --- /dev/null +++ b/src/engine/SCons/Variables/PathVariable.py @@ -0,0 +1,147 @@ +"""SCons.Variables.PathVariable + +This file defines an option type for SCons implementing path settings. + +To be used whenever a a user-specified path override should be allowed. + +Arguments to PathVariable are: + option-name = name of this option on the command line (e.g. "prefix") + option-help = help string for option + option-dflt = default value for this option + validator = [optional] validator for option value. Predefined + validators are: + + PathAccept -- accepts any path setting; no validation + PathIsDir -- path must be an existing directory + PathIsDirCreate -- path must be a dir; will create + PathIsFile -- path must be a file + PathExists -- path must exist (any type) [default] + + The validator is a function that is called and which + should return True or False to indicate if the path + is valid. The arguments to the validator function + are: (key, val, env). The key is the name of the + option, the val is the path specified for the option, + and the env is the env to which the Otions have been + added. + +Usage example: + + Examples: + prefix=/usr/local + + opts = Variables() + + opts = Variables() + opts.Add(PathVariable('qtdir', + 'where the root of Qt is installed', + qtdir, PathIsDir)) + opts.Add(PathVariable('qt_includes', + 'where the Qt includes are installed', + '$qtdir/includes', PathIsDirCreate)) + opts.Add(PathVariable('qt_libraries', + 'where the Qt library is installed', + '$qtdir/lib')) + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/PathVariable.py 4577 2009/12/27 19:44:43 scons" + +__all__ = ['PathVariable',] + +import os +import os.path + +import SCons.Errors + +class _PathVariableClass: + + def PathAccept(self, key, val, env): + """Accepts any path, no checking done.""" + pass + + def PathIsDir(self, key, val, env): + """Validator to check if Path is a directory.""" + if not os.path.isdir(val): + if os.path.isfile(val): + m = 'Directory path for option %s is a file: %s' + else: + m = 'Directory path for option %s does not exist: %s' + raise SCons.Errors.UserError(m % (key, val)) + + def PathIsDirCreate(self, key, val, env): + """Validator to check if Path is a directory, + creating it if it does not exist.""" + if os.path.isfile(val): + m = 'Path for option %s is a file, not a directory: %s' + raise SCons.Errors.UserError(m % (key, val)) + if not os.path.isdir(val): + os.makedirs(val) + + def PathIsFile(self, key, val, env): + """validator to check if Path is a file""" + if not os.path.isfile(val): + if os.path.isdir(val): + m = 'File path for option %s is a directory: %s' + else: + m = 'File path for option %s does not exist: %s' + raise SCons.Errors.UserError(m % (key, val)) + + def PathExists(self, key, val, env): + """validator to check if Path exists""" + if not os.path.exists(val): + m = 'Path for option %s does not exist: %s' + raise SCons.Errors.UserError(m % (key, val)) + + def __call__(self, key, help, default, validator=None): + # NB: searchfunc is currenty undocumented and unsupported + """ + The input parameters describe a 'path list' option, thus they + are returned with the correct converter and validator appended. The + result is usable for input to opts.Add() . + + The 'default' option specifies the default path to use if the + user does not specify an override with this option. + + validator is a validator, see this file for examples + """ + if validator is None: + validator = self.PathExists + + if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key): + return (key, '%s ( /path/to/%s )' % (help, key[0]), default, + validator, None) + else: + return (key, '%s ( /path/to/%s )' % (help, key), default, + validator, None) + +PathVariable = _PathVariableClass() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/PathVariableTests.py b/src/engine/SCons/Variables/PathVariableTests.py new file mode 100644 index 0000000..e9e35e7 --- /dev/null +++ b/src/engine/SCons/Variables/PathVariableTests.py @@ -0,0 +1,237 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/PathVariableTests.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import sys +import unittest + +import SCons.Errors +import SCons.Variables + +import TestCmd + +class PathVariableTestCase(unittest.TestCase): + def test_PathVariable(self): + """Test PathVariable creation""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path')) + + o = opts.options[0] + assert o.key == 'test', o.key + assert o.help == 'test option help ( /path/to/test )', repr(o.help) + assert o.default == '/default/path', o.default + assert o.validator is not None, o.validator + assert o.converter is None, o.converter + + def test_PathExists(self): + """Test the PathExists validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path', + SCons.Variables.PathVariable.PathExists)) + + test = TestCmd.TestCmd(workdir='') + test.write('exists', 'exists\n') + + o = opts.options[0] + + o.validator('X', test.workpath('exists'), {}) + + dne = test.workpath('does_not_exist') + try: + o.validator('X', dne, {}) + except SCons.Errors.UserError, e: + assert str(e) == 'Path for option X does not exist: %s' % dne, e + except: + raise "did not catch expected UserError" + + def test_PathIsDir(self): + """Test the PathIsDir validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path', + SCons.Variables.PathVariable.PathIsDir)) + + test = TestCmd.TestCmd(workdir='') + test.subdir('dir') + test.write('file', "file\n") + + o = opts.options[0] + + o.validator('X', test.workpath('dir'), {}) + + f = test.workpath('file') + try: + o.validator('X', f, {}) + except SCons.Errors.UserError, e: + assert str(e) == 'Directory path for option X is a file: %s' % f, e + except: + raise "did not catch expected UserError" + + dne = test.workpath('does_not_exist') + try: + o.validator('X', dne, {}) + except SCons.Errors.UserError, e: + assert str(e) == 'Directory path for option X does not exist: %s' % dne, e + except: + raise "did not catch expected UserError" + + def test_PathIsDirCreate(self): + """Test the PathIsDirCreate validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path', + SCons.Variables.PathVariable.PathIsDirCreate)) + + test = TestCmd.TestCmd(workdir='') + test.write('file', "file\n") + + o = opts.options[0] + + d = test.workpath('dir') + o.validator('X', d, {}) + assert os.path.isdir(d) + + f = test.workpath('file') + try: + o.validator('X', f, {}) + except SCons.Errors.UserError, e: + assert str(e) == 'Path for option X is a file, not a directory: %s' % f, e + except: + raise "did not catch expected UserError" + + def test_PathIsFile(self): + """Test the PathIsFile validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path', + SCons.Variables.PathVariable.PathIsFile)) + + test = TestCmd.TestCmd(workdir='') + test.subdir('dir') + test.write('file', "file\n") + + o = opts.options[0] + + o.validator('X', test.workpath('file'), {}) + + d = test.workpath('d') + try: + o.validator('X', d, {}) + except SCons.Errors.UserError, e: + assert str(e) == 'File path for option X does not exist: %s' % d, e + except: + raise "did not catch expected UserError" + + dne = test.workpath('does_not_exist') + try: + o.validator('X', dne, {}) + except SCons.Errors.UserError, e: + assert str(e) == 'File path for option X does not exist: %s' % dne, e + except: + raise "did not catch expected UserError" + + def test_PathAccept(self): + """Test the PathAccept validator""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path', + SCons.Variables.PathVariable.PathAccept)) + + test = TestCmd.TestCmd(workdir='') + test.subdir('dir') + test.write('file', "file\n") + + o = opts.options[0] + + o.validator('X', test.workpath('file'), {}) + + d = test.workpath('d') + o.validator('X', d, {}) + + dne = test.workpath('does_not_exist') + o.validator('X', dne, {}) + + def test_validator(self): + """Test the PathVariable validator argument""" + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test', + 'test option help', + '/default/path')) + + test = TestCmd.TestCmd(workdir='') + test.write('exists', 'exists\n') + + o = opts.options[0] + + o.validator('X', test.workpath('exists'), {}) + + dne = test.workpath('does_not_exist') + try: + o.validator('X', dne, {}) + except SCons.Errors.UserError, e: + expect = 'Path for option X does not exist: %s' % dne + assert str(e) == expect, e + else: + raise "did not catch expected UserError" + + def my_validator(key, val, env): + raise Exception, "my_validator() got called for %s, %s!" % (key, val) + + opts = SCons.Variables.Variables() + opts.Add(SCons.Variables.PathVariable('test2', + 'more help', + '/default/path/again', + my_validator)) + + o = opts.options[0] + + try: + o.validator('Y', 'value', {}) + except Exception, e: + assert str(e) == 'my_validator() got called for Y, value!', e + else: + raise "did not catch expected exception from my_validator()" + + + + +if __name__ == "__main__": + suite = unittest.makeSuite(PathVariableTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/VariablesTests.py b/src/engine/SCons/Variables/VariablesTests.py new file mode 100644 index 0000000..b89404b --- /dev/null +++ b/src/engine/SCons/Variables/VariablesTests.py @@ -0,0 +1,663 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/VariablesTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest +import TestSCons + +import SCons.Variables +import SCons.Subst +import SCons.Warnings + + +class Environment: + def __init__(self): + self.dict = {} + def subst(self, x): + return SCons.Subst.scons_subst(x, self, gvars=self.dict) + def __setitem__(self, key, value): + self.dict[key] = value + def __getitem__(self, key): + return self.dict[key] + def has_key(self, key): + return self.dict.has_key(key) + + +def check(key, value, env): + assert int(value) == 6 * 9, "key %s = %s" % (key, repr(value)) + +# Check saved option file by executing and comparing against +# the expected dictionary +def checkSave(file, expected): + gdict = {} + ldict = {} + exec open(file, 'rU').read() in gdict, ldict + assert expected == ldict, "%s\n...not equal to...\n%s" % (expected, ldict) + +class VariablesTestCase(unittest.TestCase): + + def test_keys(self): + """Test the Variables.keys() method""" + opts = SCons.Variables.Variables() + + opts.Add('VAR1') + opts.Add('VAR2', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + keys = opts.keys() + assert keys == ['VAR1', 'VAR2'], keys + + def test_Add(self): + """Test adding to a Variables object""" + opts = SCons.Variables.Variables() + + opts.Add('VAR') + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + o = opts.options[0] + assert o.key == 'VAR' + assert o.help == '' + assert o.default is None + assert o.validator is None + assert o.converter is None + + o = opts.options[1] + assert o.key == 'ANSWER' + assert o.help == 'THE answer to THE question' + assert o.default == "42" + o.validator(o.key, o.converter(o.default), {}) + + def test_it(var, opts=opts): + exc_caught = None + try: + opts.Add(var) + except SCons.Errors.UserError: + exc_caught = 1 + assert exc_caught, "did not catch UserError for '%s'" % var + test_it('foo/bar') + test_it('foo-bar') + test_it('foo.bar') + + def test_AddVariables(self): + """Test adding a list of options to a Variables object""" + opts = SCons.Variables.Variables() + + opts.AddVariables(('VAR2',), + ('ANSWER2', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12)) + + o = opts.options[0] + assert o.key == 'VAR2', o.key + assert o.help == '', o.help + assert o.default is None, o.default + assert o.validator is None, o.validator + assert o.converter is None, o.converter + + o = opts.options[1] + assert o.key == 'ANSWER2', o.key + assert o.help == 'THE answer to THE question', o.help + assert o.default == "42", o.default + o.validator(o.key, o.converter(o.default), {}) + + def test_Update(self): + """Test updating an Environment""" + + # Test that a default value is validated correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env) + assert env['ANSWER'] == 54 + + env = Environment() + opts.Update(env, {}) + assert env['ANSWER'] == 54 + + # Test that a bad value from the file is used and + # validation fails correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=54') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + exc_caught = None + try: + opts.Update(env) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + env = Environment() + exc_caught = None + try: + opts.Update(env, {}) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + # Test that a good value from the file is used and validated. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=42') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "10", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env) + assert env['ANSWER'] == 54 + + env = Environment() + opts.Update(env, {}) + assert env['ANSWER'] == 54 + + # Test that a bad value from an args dictionary passed to + # Update() is used and validation fails correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=10') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "12", + check, + lambda x: int(x) + 12) + + env = Environment() + exc_caught = None + try: + opts.Update(env, {'ANSWER':'54'}) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + # Test that a good value from an args dictionary + # passed to Update() is used and validated. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=10') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + 'THE answer to THE question', + "12", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {'ANSWER':'42'}) + assert env['ANSWER'] == 54 + + # Test against a former bug. If we supply a converter, + # but no default, the value should *not* appear in the + # Environment if no value is specified in the options file + # or args. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + help='THE answer to THE question', + converter=str) + + env = Environment() + opts.Update(env, {}) + assert not env.has_key('ANSWER') + + # Test that a default value of None is all right. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + opts = SCons.Variables.Variables(file) + + opts.Add('ANSWER', + "This is the answer", + None, + check) + + env = Environment() + opts.Update(env, {}) + assert not env.has_key('ANSWER') + + def test_args(self): + """Test updating an Environment with arguments overridden""" + + # Test that a bad (command-line) argument is used + # and the validation fails correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=42') + opts = SCons.Variables.Variables(file, {'ANSWER':54}) + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + exc_caught = None + try: + opts.Update(env) + except AssertionError: + exc_caught = 1 + assert exc_caught, "did not catch expected assertion" + + # Test that a good (command-line) argument is used and validated. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=54') + opts = SCons.Variables.Variables(file, {'ANSWER':42}) + + opts.Add('ANSWER', + 'THE answer to THE question', + "54", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env) + assert env['ANSWER'] == 54 + + # Test that a (command-line) argument is overridden by a dictionary + # supplied to Update() and the dictionary value is validated correctly. + test = TestSCons.TestSCons() + file = test.workpath('custom.py') + test.write('custom.py', 'ANSWER=54') + opts = SCons.Variables.Variables(file, {'ANSWER':54}) + + opts.Add('ANSWER', + 'THE answer to THE question', + "54", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {'ANSWER':42}) + assert env['ANSWER'] == 54 + + def test_Save(self): + """Testing saving Variables""" + + test = TestSCons.TestSCons() + cache_file = test.workpath('cached.options') + opts = SCons.Variables.Variables() + + def bool_converter(val): + if val in [1, 'y']: val = 1 + if val in [0, 'n']: val = 0 + return val + + # test saving out empty file + opts.Add('OPT_VAL', + 'An option to test', + 21, + None, + None) + opts.Add('OPT_VAL_2', + default='foo') + opts.Add('OPT_VAL_3', + default=1) + opts.Add('OPT_BOOL_0', + default='n', + converter=bool_converter) + opts.Add('OPT_BOOL_1', + default='y', + converter=bool_converter) + opts.Add('OPT_BOOL_2', + default=0, + converter=bool_converter) + + env = Environment() + opts.Update(env, {'OPT_VAL_3' : 2}) + assert env['OPT_VAL'] == 21, env['OPT_VAL'] + assert env['OPT_VAL_2'] == 'foo', env['OPT_VAL_2'] + assert env['OPT_VAL_3'] == 2, env['OPT_VAL_3'] + assert env['OPT_BOOL_0'] == 0, env['OPT_BOOL_0'] + assert env['OPT_BOOL_1'] == 1, env['OPT_BOOL_1'] + assert env['OPT_BOOL_2'] == '0', env['OPT_BOOL_2'] + + env['OPT_VAL_2'] = 'bar' + env['OPT_BOOL_0'] = 0 + env['OPT_BOOL_1'] = 1 + env['OPT_BOOL_2'] = 2 + + opts.Save(cache_file, env) + checkSave(cache_file, { 'OPT_VAL_2' : 'bar', + 'OPT_VAL_3' : 2, + 'OPT_BOOL_2' : 2}) + + # Test against some old bugs + class Foo: + def __init__(self, x): + self.x = x + def __str__(self): + return self.x + + test = TestSCons.TestSCons() + cache_file = test.workpath('cached.options') + opts = SCons.Variables.Variables() + + opts.Add('THIS_USED_TO_BREAK', + 'An option to test', + "Default") + + opts.Add('THIS_ALSO_BROKE', + 'An option to test', + "Default2") + + opts.Add('THIS_SHOULD_WORK', + 'An option to test', + Foo('bar')) + + env = Environment() + opts.Update(env, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", + 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", + 'THIS_SHOULD_WORK' : Foo('baz') }) + opts.Save(cache_file, env) + checkSave(cache_file, { 'THIS_USED_TO_BREAK' : "Single'Quotes'In'String", + 'THIS_ALSO_BROKE' : "\\Escape\nSequences\t", + 'THIS_SHOULD_WORK' : 'baz' }) + + def test_GenerateHelpText(self): + """Test generating the default format help text""" + opts = SCons.Variables.Variables() + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + opts.Add('B', + 'b - alpha test', + "42", + check, + lambda x: int(x) + 12) + + opts.Add('A', + 'a - alpha test', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {}) + + expect = """ +ANSWER: THE answer to THE question + default: 42 + actual: 54 + +B: b - alpha test + default: 42 + actual: 54 + +A: a - alpha test + default: 42 + actual: 54 +""" + + text = opts.GenerateHelpText(env) + assert text == expect, text + + expectAlpha = """ +A: a - alpha test + default: 42 + actual: 54 + +ANSWER: THE answer to THE question + default: 42 + actual: 54 + +B: b - alpha test + default: 42 + actual: 54 +""" + text = opts.GenerateHelpText(env, sort=cmp) + assert text == expectAlpha, text + + def test_FormatVariableHelpText(self): + """Test generating custom format help text""" + opts = SCons.Variables.Variables() + + def my_format(env, opt, help, default, actual, aliases): + return '%s %s %s %s %s\n' % (opt, default, actual, help, aliases) + + opts.FormatVariableHelpText = my_format + + opts.Add('ANSWER', + 'THE answer to THE question', + "42", + check, + lambda x: int(x) + 12) + + opts.Add('B', + 'b - alpha test', + "42", + check, + lambda x: int(x) + 12) + + opts.Add('A', + 'a - alpha test', + "42", + check, + lambda x: int(x) + 12) + + env = Environment() + opts.Update(env, {}) + + expect = """\ +ANSWER 42 54 THE answer to THE question ['ANSWER'] +B 42 54 b - alpha test ['B'] +A 42 54 a - alpha test ['A'] +""" + + text = opts.GenerateHelpText(env) + assert text == expect, text + + expectAlpha = """\ +A 42 54 a - alpha test ['A'] +ANSWER 42 54 THE answer to THE question ['ANSWER'] +B 42 54 b - alpha test ['B'] +""" + text = opts.GenerateHelpText(env, sort=cmp) + assert text == expectAlpha, text + + def test_Aliases(self): + """Test option aliases""" + # test alias as a tuple + opts = SCons.Variables.Variables() + opts.AddVariables( + (('ANSWER', 'ANSWERALIAS'), + 'THE answer to THE question', + "42"), + ) + + env = Environment() + opts.Update(env, {'ANSWER' : 'answer'}) + + assert env.has_key('ANSWER') + + env = Environment() + opts.Update(env, {'ANSWERALIAS' : 'answer'}) + + assert env.has_key('ANSWER') and not env.has_key('ANSWERALIAS') + + # test alias as a list + opts = SCons.Variables.Variables() + opts.AddVariables( + (['ANSWER', 'ANSWERALIAS'], + 'THE answer to THE question', + "42"), + ) + + env = Environment() + opts.Update(env, {'ANSWER' : 'answer'}) + + assert env.has_key('ANSWER') + + env = Environment() + opts.Update(env, {'ANSWERALIAS' : 'answer'}) + + assert env.has_key('ANSWER') and not env.has_key('ANSWERALIAS') + + + +class UnknownVariablesTestCase(unittest.TestCase): + + def test_unknown(self): + """Test the UnknownVariables() method""" + opts = SCons.Variables.Variables() + + opts.Add('ANSWER', + 'THE answer to THE question', + "42") + + args = { + 'ANSWER' : 'answer', + 'UNKNOWN' : 'unknown', + } + + env = Environment() + opts.Update(env, args) + + r = opts.UnknownVariables() + assert r == {'UNKNOWN' : 'unknown'}, r + assert env['ANSWER'] == 'answer', env['ANSWER'] + + def test_AddOptionUpdatesUnknown(self): + """Test updating of the 'unknown' dict""" + opts = SCons.Variables.Variables() + + opts.Add('A', + 'A test variable', + "1") + + args = { + 'A' : 'a', + 'ADDEDLATER' : 'notaddedyet', + } + + env = Environment() + opts.Update(env,args) + + r = opts.UnknownVariables() + assert r == {'ADDEDLATER' : 'notaddedyet'}, r + assert env['A'] == 'a', env['A'] + + opts.Add('ADDEDLATER', + 'An option not present initially', + "1") + + args = { + 'A' : 'a', + 'ADDEDLATER' : 'added', + } + + opts.Update(env, args) + + r = opts.UnknownVariables() + assert len(r) == 0, r + assert env['ADDEDLATER'] == 'added', env['ADDEDLATER'] + + def test_AddOptionWithAliasUpdatesUnknown(self): + """Test updating of the 'unknown' dict (with aliases)""" + opts = SCons.Variables.Variables() + + opts.Add('A', + 'A test variable', + "1") + + args = { + 'A' : 'a', + 'ADDEDLATERALIAS' : 'notaddedyet', + } + + env = Environment() + opts.Update(env,args) + + r = opts.UnknownVariables() + assert r == {'ADDEDLATERALIAS' : 'notaddedyet'}, r + assert env['A'] == 'a', env['A'] + + opts.AddVariables( + (('ADDEDLATER', 'ADDEDLATERALIAS'), + 'An option not present initially', + "1"), + ) + + args['ADDEDLATERALIAS'] = 'added' + + opts.Update(env, args) + + r = opts.UnknownVariables() + assert len(r) == 0, r + assert env['ADDEDLATER'] == 'added', env['ADDEDLATER'] + + +if __name__ == "__main__": + suite = unittest.TestSuite() + tclasses = [ VariablesTestCase, + UnknownVariablesTestCase ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Variables/__init__.py b/src/engine/SCons/Variables/__init__.py new file mode 100644 index 0000000..7ab711a --- /dev/null +++ b/src/engine/SCons/Variables/__init__.py @@ -0,0 +1,317 @@ +"""engine.SCons.Variables + +This file defines the Variables class that is used to add user-friendly +customizable variables to an SCons build. +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/Variables/__init__.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import string +import sys + +import SCons.Environment +import SCons.Errors +import SCons.Util +import SCons.Warnings + +from BoolVariable import BoolVariable # okay +from EnumVariable import EnumVariable # okay +from ListVariable import ListVariable # naja +from PackageVariable import PackageVariable # naja +from PathVariable import PathVariable # okay + + +class Variables: + instance=None + + """ + Holds all the options, updates the environment with the variables, + and renders the help text. + """ + def __init__(self, files=[], args={}, is_global=1): + """ + files - [optional] List of option configuration files to load + (backward compatibility) If a single string is passed it is + automatically placed in a file list + """ + self.options = [] + self.args = args + if not SCons.Util.is_List(files): + if files: + files = [ files ] + else: + files = [] + self.files = files + self.unknown = {} + + # create the singleton instance + if is_global: + self=Variables.instance + + if not Variables.instance: + Variables.instance=self + + def _do_add(self, key, help="", default=None, validator=None, converter=None): + class Variable: + pass + + option = Variable() + + # if we get a list or a tuple, we take the first element as the + # option key and store the remaining in aliases. + if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key): + option.key = key[0] + option.aliases = key[1:] + else: + option.key = key + option.aliases = [ key ] + option.help = help + option.default = default + option.validator = validator + option.converter = converter + + self.options.append(option) + + # options might be added after the 'unknown' dict has been set up, + # so we remove the key and all its aliases from that dict + for alias in list(option.aliases) + [ option.key ]: + # TODO(1.5) + #if alias in self.unknown: + if alias in self.unknown.keys(): + del self.unknown[alias] + + def keys(self): + """ + Returns the keywords for the options + """ + return map(lambda o: o.key, self.options) + + def Add(self, key, help="", default=None, validator=None, converter=None, **kw): + """ + Add an option. + + key - the name of the variable, or a list or tuple of arguments + help - optional help text for the options + default - optional default value + validator - optional function that is called to validate the option's value + Called with (key, value, environment) + converter - optional function that is called to convert the option's value before + putting it in the environment. + """ + + if SCons.Util.is_List(key) or type(key) == type(()): + apply(self._do_add, key) + return + + if not SCons.Util.is_String(key) or \ + not SCons.Environment.is_valid_construction_var(key): + raise SCons.Errors.UserError, "Illegal Variables.Add() key `%s'" % str(key) + + self._do_add(key, help, default, validator, converter) + + def AddVariables(self, *optlist): + """ + Add a list of options. + + Each list element is a tuple/list of arguments to be passed on + to the underlying method for adding options. + + Example: + opt.AddVariables( + ('debug', '', 0), + ('CC', 'The C compiler'), + ('VALIDATE', 'An option for testing validation', 'notset', + validator, None), + ) + """ + for o in optlist: + apply(self._do_add, o) + + + def Update(self, env, args=None): + """ + Update an environment with the option variables. + + env - the environment to update. + """ + + values = {} + + # first set the defaults: + for option in self.options: + if not option.default is None: + values[option.key] = option.default + + # next set the value specified in the options file + for filename in self.files: + if os.path.exists(filename): + dir = os.path.split(os.path.abspath(filename))[0] + if dir: + sys.path.insert(0, dir) + try: + values['__name__'] = filename + exec open(filename, 'rU').read() in {}, values + finally: + if dir: + del sys.path[0] + del values['__name__'] + + # set the values specified on the command line + if args is None: + args = self.args + + for arg, value in args.items(): + added = False + for option in self.options: + if arg in list(option.aliases) + [ option.key ]: + values[option.key] = value + added = True + if not added: + self.unknown[arg] = value + + # put the variables in the environment: + # (don't copy over variables that are not declared as options) + for option in self.options: + try: + env[option.key] = values[option.key] + except KeyError: + pass + + # Call the convert functions: + for option in self.options: + if option.converter and values.has_key(option.key): + value = env.subst('${%s}'%option.key) + try: + try: + env[option.key] = option.converter(value) + except TypeError: + env[option.key] = option.converter(value, env) + except ValueError, x: + raise SCons.Errors.UserError, 'Error converting option: %s\n%s'%(option.key, x) + + + # Finally validate the values: + for option in self.options: + if option.validator and values.has_key(option.key): + option.validator(option.key, env.subst('${%s}'%option.key), env) + + def UnknownVariables(self): + """ + Returns any options in the specified arguments lists that + were not known, declared options in this object. + """ + return self.unknown + + def Save(self, filename, env): + """ + Saves all the options in the given file. This file can + then be used to load the options next run. This can be used + to create an option cache file. + + filename - Name of the file to save into + env - the environment get the option values from + """ + + # Create the file and write out the header + try: + fh = open(filename, 'w') + + try: + # Make an assignment in the file for each option + # within the environment that was assigned a value + # other than the default. + for option in self.options: + try: + value = env[option.key] + try: + prepare = value.prepare_to_store + except AttributeError: + try: + eval(repr(value)) + except KeyboardInterrupt: + raise + except: + # Convert stuff that has a repr() that + # cannot be evaluated into a string + value = SCons.Util.to_String(value) + else: + value = prepare() + + defaultVal = env.subst(SCons.Util.to_String(option.default)) + if option.converter: + defaultVal = option.converter(defaultVal) + + if str(env.subst('${%s}' % option.key)) != str(defaultVal): + fh.write('%s = %s\n' % (option.key, repr(value))) + except KeyError: + pass + finally: + fh.close() + + except IOError, x: + raise SCons.Errors.UserError, 'Error writing options to file: %s\n%s' % (filename, x) + + def GenerateHelpText(self, env, sort=None): + """ + Generate the help text for the options. + + env - an environment that is used to get the current values + of the options. + """ + + if sort: + options = self.options[:] + options.sort(lambda x,y,func=sort: func(x.key,y.key)) + else: + options = self.options + + def format(opt, self=self, env=env): + if env.has_key(opt.key): + actual = env.subst('${%s}' % opt.key) + else: + actual = None + return self.FormatVariableHelpText(env, opt.key, opt.help, opt.default, actual, opt.aliases) + lines = filter(None, map(format, options)) + + return string.join(lines, '') + + format = '\n%s: %s\n default: %s\n actual: %s\n' + format_ = '\n%s: %s\n default: %s\n actual: %s\n aliases: %s\n' + + def FormatVariableHelpText(self, env, key, help, default, actual, aliases=[]): + # Don't display the key name itself as an alias. + aliases = filter(lambda a, k=key: a != k, aliases) + if len(aliases)==0: + return self.format % (key, help, default, actual) + else: + return self.format_ % (key, help, default, actual, aliases) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/Warnings.py b/src/engine/SCons/Warnings.py new file mode 100644 index 0000000..8f290f7 --- /dev/null +++ b/src/engine/SCons/Warnings.py @@ -0,0 +1,228 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +"""SCons.Warnings + +This file implements the warnings framework for SCons. + +""" + +__revision__ = "src/engine/SCons/Warnings.py 4577 2009/12/27 19:44:43 scons" + +import string +import sys + +import SCons.Errors + +class Warning(SCons.Errors.UserError): + pass + +class MandatoryWarning(Warning): + pass + + + +class FutureDeprecatedWarning(Warning): + pass + +class DeprecatedWarning(Warning): + pass + +class MandatoryDeprecatedWarning(MandatoryWarning): + pass + + + +# NOTE: If you add a new warning class, add it to the man page, too! + +class CacheWriteErrorWarning(Warning): + pass + +class CorruptSConsignWarning(Warning): + pass + +class DependencyWarning(Warning): + pass + +class DeprecatedCopyWarning(DeprecatedWarning): + pass + +class DeprecatedOptionsWarning(DeprecatedWarning): + pass + +class DeprecatedSourceSignaturesWarning(DeprecatedWarning): + pass + +class DeprecatedTargetSignaturesWarning(DeprecatedWarning): + pass + +class DuplicateEnvironmentWarning(Warning): + pass + +class FutureReservedVariableWarning(Warning): + pass + +class LinkWarning(Warning): + pass + +class MisleadingKeywordsWarning(Warning): + pass + +class MissingSConscriptWarning(Warning): + pass + +class NoMD5ModuleWarning(Warning): + pass + +class NoMetaclassSupportWarning(Warning): + pass + +class NoObjectCountWarning(Warning): + pass + +class NoParallelSupportWarning(Warning): + pass + +class PythonVersionWarning(DeprecatedWarning): + pass + +class ReservedVariableWarning(Warning): + pass + +class StackSizeWarning(Warning): + pass + +class TaskmasterNeedsExecuteWarning(FutureDeprecatedWarning): + pass + +class VisualCMissingWarning(Warning): + pass + +# Used when MSVC_VERSION and MSVS_VERSION do not point to the +# same version (MSVS_VERSION is deprecated) +class VisualVersionMismatch(Warning): + pass + +class VisualStudioMissingWarning(Warning): + pass + +class FortranCxxMixWarning(LinkWarning): + pass + +_warningAsException = 0 + +# The below is a list of 2-tuples. The first element is a class object. +# The second element is true if that class is enabled, false if it is disabled. +_enabled = [] + +_warningOut = None + +def suppressWarningClass(clazz): + """Suppresses all warnings that are of type clazz or + derived from clazz.""" + _enabled.insert(0, (clazz, 0)) + +def enableWarningClass(clazz): + """Suppresses all warnings that are of type clazz or + derived from clazz.""" + _enabled.insert(0, (clazz, 1)) + +def warningAsException(flag=1): + """Turn warnings into exceptions. Returns the old value of the flag.""" + global _warningAsException + old = _warningAsException + _warningAsException = flag + return old + +def warn(clazz, *args): + global _enabled, _warningAsException, _warningOut + + warning = clazz(args) + for clazz, flag in _enabled: + if isinstance(warning, clazz): + if flag: + if _warningAsException: + raise warning + + if _warningOut: + _warningOut(warning) + break + +def process_warn_strings(arguments): + """Process string specifications of enabling/disabling warnings, + as passed to the --warn option or the SetOption('warn') function. + + + An argument to this option should be of the form + or no-. The warning class is munged in order + to get an actual class name from the classes above, which we + need to pass to the {enable,disable}WarningClass() functions. + The supplied is split on hyphens, each element + is capitalized, then smushed back together. Then the string + "Warning" is appended to get the class name. + + For example, 'deprecated' will enable the DeprecatedWarning + class. 'no-dependency' will disable the .DependencyWarning + class. + + As a special case, --warn=all and --warn=no-all will enable or + disable (respectively) the base Warning class of all warnings. + + """ + + def _capitalize(s): + if s[:5] == "scons": + return "SCons" + s[5:] + else: + return string.capitalize(s) + + for arg in arguments: + + elems = string.split(string.lower(arg), '-') + enable = 1 + if elems[0] == 'no': + enable = 0 + del elems[0] + + if len(elems) == 1 and elems[0] == 'all': + class_name = "Warning" + else: + class_name = string.join(map(_capitalize, elems), '') + "Warning" + try: + clazz = globals()[class_name] + except KeyError: + sys.stderr.write("No warning type: '%s'\n" % arg) + else: + if enable: + enableWarningClass(clazz) + elif issubclass(clazz, MandatoryDeprecatedWarning): + fmt = "Can not disable mandataory warning: '%s'\n" + sys.stderr.write(fmt % arg) + else: + suppressWarningClass(clazz) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/WarningsTests.py b/src/engine/SCons/WarningsTests.py new file mode 100644 index 0000000..a4a7b58 --- /dev/null +++ b/src/engine/SCons/WarningsTests.py @@ -0,0 +1,135 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/WarningsTests.py 4577 2009/12/27 19:44:43 scons" + +import sys +import unittest +import SCons.Warnings + +class TestOutput: + def __call__(self, x): + args = x[0] + if len(args) == 1: + args = args[0] + self.out = str(args) + +class WarningsTestCase(unittest.TestCase): + def test_Warning(self): + """Test warn function.""" + + # Reset global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = 0 + + to = TestOutput() + SCons.Warnings._warningOut=to + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out == "Foo", to.out + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "Foo", 1) + assert to.out == "('Foo', 1)", to.out + + def test_WarningAsExc(self): + """Test warnings as exceptions.""" + + # Reset global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = 0 + + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + old = SCons.Warnings.warningAsException() + assert old == 0, old + exc_caught = 0 + try: + SCons.Warnings.warn(SCons.Warnings.Warning, "Foo") + except: + exc_caught = 1 + assert exc_caught == 1 + + old = SCons.Warnings.warningAsException(old) + assert old == 1, old + exc_caught = 0 + try: + SCons.Warnings.warn(SCons.Warnings.Warning, "Foo") + except: + exc_caught = 1 + assert exc_caught == 0 + + def test_Disable(self): + """Test disabling/enabling warnings.""" + + # Reset global state + SCons.Warnings._enabled = [] + SCons.Warnings._warningAsException = 0 + + to = TestOutput() + SCons.Warnings._warningOut=to + to.out = None + + # No warnings by default + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out is None, to.out + + SCons.Warnings.warn(SCons.Warnings.MandatoryWarning, + "Foo") + assert to.out is None, to.out + + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out == "Foo", to.out + + to.out = None + SCons.Warnings.suppressWarningClass(SCons.Warnings.DeprecatedWarning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out is None, to.out + + # Dependency warnings should still be enabled though + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DependencyWarning, + "Foo") + assert to.out == "Foo", to.out + + # Try reenabling all warnings... + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + + SCons.Warnings.enableWarningClass(SCons.Warnings.Warning) + SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning, + "Foo") + assert to.out == "Foo", to.out + +if __name__ == "__main__": + suite = unittest.makeSuite(WarningsTestCase, 'test_') + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/__init__.py b/src/engine/SCons/__init__.py new file mode 100644 index 0000000..dd6115a --- /dev/null +++ b/src/engine/SCons/__init__.py @@ -0,0 +1,49 @@ +"""SCons + +The main package for the SCons software construction utility. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/__init__.py 4577 2009/12/27 19:44:43 scons" + +__version__ = "1.2.0.d20091224" + +__build__ = "r4577[MODIFIED]" + +__buildsys__ = "scons-dev" + +__date__ = "2009/12/27 19:44:43" + +__developer__ = "scons" + +# make sure compatibility is always in place +import SCons.compat + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/__init__.py b/src/engine/SCons/compat/__init__.py new file mode 100644 index 0000000..6eb9340 --- /dev/null +++ b/src/engine/SCons/compat/__init__.py @@ -0,0 +1,302 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = """ +SCons compatibility package for old Python versions + +This subpackage holds modules that provide backwards-compatible +implementations of various things that we'd like to use in SCons but which +only show up in later versions of Python than the early, old version(s) +we still support. + +Other code will not generally reference things in this package through +the SCons.compat namespace. The modules included here add things to +the __builtin__ namespace or the global module list so that the rest +of our code can use the objects and names imported here regardless of +Python version. + +Simply enough, things that go in the __builtin__ name space come from +our builtins module. + +The rest of the things here will be in individual compatibility modules +that are either: 1) suitably modified copies of the future modules that +we want to use; or 2) backwards compatible re-implementations of the +specific portions of a future module's API that we want to use. + +GENERAL WARNINGS: Implementations of functions in the SCons.compat +modules are *NOT* guaranteed to be fully compliant with these functions in +later versions of Python. We are only concerned with adding functionality +that we actually use in SCons, so be wary if you lift this code for +other uses. (That said, making these more nearly the same as later, +official versions is still a desirable goal, we just don't need to be +obsessive about it.) + +We name the compatibility modules with an initial '_scons_' (for example, +_scons_subprocess.py is our compatibility module for subprocess) so +that we can still try to import the real module name and fall back to +our compatibility module if we get an ImportError. The import_as() +function defined below loads the module as the "real" name (without the +'_scons'), after which all of the "import {module}" statements in the +rest of our code will find our pre-loaded compatibility module. +""" + +__revision__ = "src/engine/SCons/compat/__init__.py 4577 2009/12/27 19:44:43 scons" + +def import_as(module, name): + """ + Imports the specified module (from our local directory) as the + specified name. + """ + import imp + import os.path + dir = os.path.split(__file__)[0] + file, filename, suffix_mode_type = imp.find_module(module, [dir]) + imp.load_module(name, file, filename, suffix_mode_type) + +import builtins + +try: + import hashlib +except ImportError: + # Pre-2.5 Python has no hashlib module. + try: + import_as('_scons_hashlib', 'hashlib') + except ImportError: + # If we failed importing our compatibility module, it probably + # means this version of Python has no md5 module. Don't do + # anything and let the higher layer discover this fact, so it + # can fall back to using timestamp. + pass + +try: + set +except NameError: + # Pre-2.4 Python has no native set type + try: + # Python 2.2 and 2.3 can use the copy of the 2.[45] sets module + # that we grabbed. + import_as('_scons_sets', 'sets') + except (ImportError, SyntaxError): + # Python 1.5 (ImportError, no __future_ module) and 2.1 + # (SyntaxError, no generators in __future__) will blow up + # trying to import the 2.[45] sets module, so back off to a + # custom sets module that can be discarded easily when we + # stop supporting those versions. + import_as('_scons_sets15', 'sets') + import __builtin__ + import sets + __builtin__.set = sets.Set + +import fnmatch +try: + fnmatch.filter +except AttributeError: + # Pre-2.2 Python has no fnmatch.filter() function. + def filter(names, pat): + """Return the subset of the list NAMES that match PAT""" + import os,posixpath + result=[] + pat = os.path.normcase(pat) + if not fnmatch._cache.has_key(pat): + import re + res = fnmatch.translate(pat) + fnmatch._cache[pat] = re.compile(res) + match = fnmatch._cache[pat].match + if os.path is posixpath: + # normcase on posix is NOP. Optimize it away from the loop. + for name in names: + if match(name): + result.append(name) + else: + for name in names: + if match(os.path.normcase(name)): + result.append(name) + return result + fnmatch.filter = filter + del filter + +try: + import itertools +except ImportError: + # Pre-2.3 Python has no itertools module. + import_as('_scons_itertools', 'itertools') + +# If we need the compatibility version of textwrap, it must be imported +# before optparse, which uses it. +try: + import textwrap +except ImportError: + # Pre-2.3 Python has no textwrap module. + import_as('_scons_textwrap', 'textwrap') + +try: + import optparse +except ImportError: + # Pre-2.3 Python has no optparse module. + import_as('_scons_optparse', 'optparse') + +import os +try: + os.devnull +except AttributeError: + # Pre-2.4 Python has no os.devnull attribute + import sys + _names = sys.builtin_module_names + if 'posix' in _names: + os.devnull = '/dev/null' + elif 'nt' in _names: + os.devnull = 'nul' + os.path.devnull = os.devnull +try: + os.path.lexists +except AttributeError: + # Pre-2.4 Python has no os.path.lexists function + def lexists(path): + return os.path.exists(path) or os.path.islink(path) + os.path.lexists = lexists + + +try: + import platform +except ImportError: + # Pre-2.3 Python has no platform module. + import_as('_scons_platform', 'platform') + + +import shlex +try: + shlex.split +except AttributeError: + # Pre-2.3 Python has no shlex.split() function. + # + # The full white-space splitting semantics of shlex.split() are + # complicated to reproduce by hand, so just use a compatibility + # version of the shlex module cribbed from Python 2.5 with some + # minor modifications for older Python versions. + del shlex + import_as('_scons_shlex', 'shlex') + + +import shutil +try: + shutil.move +except AttributeError: + # Pre-2.3 Python has no shutil.move() function. + # + # Cribbed from Python 2.5. + import os + + def move(src, dst): + """Recursively move a file or directory to another location. + + If the destination is on our current filesystem, then simply use + rename. Otherwise, copy src to the dst and then remove src. + A lot more could be done here... A look at a mv.c shows a lot of + the issues this implementation glosses over. + + """ + try: + os.rename(src, dst) + except OSError: + if os.path.isdir(src): + if shutil.destinsrc(src, dst): + raise Error, "Cannot move a directory '%s' into itself '%s'." % (src, dst) + shutil.copytree(src, dst, symlinks=True) + shutil.rmtree(src) + else: + shutil.copy2(src,dst) + os.unlink(src) + shutil.move = move + del move + + def destinsrc(src, dst): + src = os.path.abspath(src) + return os.path.abspath(dst)[:len(src)] == src + shutil.destinsrc = destinsrc + del destinsrc + + +try: + import subprocess +except ImportError: + # Pre-2.4 Python has no subprocess module. + import_as('_scons_subprocess', 'subprocess') + +import sys +try: + sys.version_info +except AttributeError: + # Pre-1.6 Python has no sys.version_info + import string + version_string = string.split(sys.version)[0] + version_ints = map(int, string.split(version_string, '.')) + sys.version_info = tuple(version_ints + ['final', 0]) + +try: + import UserString +except ImportError: + # Pre-1.6 Python has no UserString module. + import_as('_scons_UserString', 'UserString') + +import tempfile +try: + tempfile.mkstemp +except AttributeError: + # Pre-2.3 Python has no tempfile.mkstemp function, so try to simulate it. + # adapted from the mkstemp implementation in python 3. + import os + import errno + def mkstemp(*args, **kw): + text = False + # TODO (1.5) + #if 'text' in kw : + if 'text' in kw.keys() : + text = kw['text'] + del kw['text'] + elif len( args ) == 4 : + text = args[3] + args = args[:3] + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + if not text and hasattr( os, 'O_BINARY' ) : + flags = flags | os.O_BINARY + while True: + try : + name = apply(tempfile.mktemp, args, kw) + fd = os.open( name, flags, 0600 ) + return (fd, os.path.abspath(name)) + except OSError, e: + if e.errno == errno.EEXIST: + continue + raise + + tempfile.mkstemp = mkstemp + del mkstemp + + + + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_UserString.py b/src/engine/SCons/compat/_scons_UserString.py new file mode 100644 index 0000000..932c216 --- /dev/null +++ b/src/engine/SCons/compat/_scons_UserString.py @@ -0,0 +1,98 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/compat/_scons_UserString.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +A user-defined wrapper around string objects + +This class is "borrowed" from the Python 2.2 UserString and modified +slightly for use with SCons. It is *NOT* guaranteed to be fully compliant +with the standard UserString class from all later versions of Python. +In particular, it does not necessarily contain all of the methods found +in later versions. +""" + +import types + +StringType = types.StringType + +if hasattr(types, 'UnicodeType'): + UnicodeType = types.UnicodeType + def is_String(obj): + return type(obj) in (StringType, UnicodeType) +else: + def is_String(obj): + return type(obj) is StringType + +class UserString: + def __init__(self, seq): + if is_String(seq): + self.data = seq + elif isinstance(seq, UserString): + self.data = seq.data[:] + else: + self.data = str(seq) + def __str__(self): return str(self.data) + def __repr__(self): return repr(self.data) + def __int__(self): return int(self.data) + def __long__(self): return long(self.data) + def __float__(self): return float(self.data) + def __complex__(self): return complex(self.data) + def __hash__(self): return hash(self.data) + + def __cmp__(self, string): + if isinstance(string, UserString): + return cmp(self.data, string.data) + else: + return cmp(self.data, string) + def __contains__(self, char): + return char in self.data + + def __len__(self): return len(self.data) + def __getitem__(self, index): return self.__class__(self.data[index]) + def __getslice__(self, start, end): + start = max(start, 0); end = max(end, 0) + return self.__class__(self.data[start:end]) + + def __add__(self, other): + if isinstance(other, UserString): + return self.__class__(self.data + other.data) + elif is_String(other): + return self.__class__(self.data + other) + else: + return self.__class__(self.data + str(other)) + def __radd__(self, other): + if is_String(other): + return self.__class__(other + self.data) + else: + return self.__class__(str(other) + self.data) + def __mul__(self, n): + return self.__class__(self.data*n) + __rmul__ = __mul__ + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_hashlib.py b/src/engine/SCons/compat/_scons_hashlib.py new file mode 100644 index 0000000..bb4c69c --- /dev/null +++ b/src/engine/SCons/compat/_scons_hashlib.py @@ -0,0 +1,91 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = """ +hashlib backwards-compatibility module for older (pre-2.5) Python versions + +This does not not NOT (repeat, *NOT*) provide complete hashlib +functionality. It only wraps the portions of MD5 functionality used +by SCons, in an interface that looks like hashlib (or enough for our +purposes, anyway). In fact, this module will raise an ImportError if +the underlying md5 module isn't available. +""" + +__revision__ = "src/engine/SCons/compat/_scons_hashlib.py 4577 2009/12/27 19:44:43 scons" + +import md5 +import string + +class md5obj: + + md5_module = md5 + + def __init__(self, name, string=''): + if not name in ('MD5', 'md5'): + raise ValueError, "unsupported hash type" + self.name = 'md5' + self.m = self.md5_module.md5() + + def __repr__(self): + return '<%s HASH object @ %#x>' % (self.name, id(self)) + + def copy(self): + import copy + result = copy.copy(self) + result.m = self.m.copy() + return result + + def digest(self): + return self.m.digest() + + def update(self, arg): + return self.m.update(arg) + + if hasattr(md5.md5(), 'hexdigest'): + + def hexdigest(self): + return self.m.hexdigest() + + else: + + # Objects created by the underlying md5 module have no native + # hexdigest() method (*cough* 1.5.2 *cough*), so provide an + # equivalent lifted from elsewhere. + def hexdigest(self): + h = string.hexdigits + r = '' + for c in self.digest(): + i = ord(c) + r = r + h[(i >> 4) & 0xF] + h[i & 0xF] + return r + +new = md5obj + +def md5(string=''): + return md5obj('md5', string) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_itertools.py b/src/engine/SCons/compat/_scons_itertools.py new file mode 100644 index 0000000..f2e93c6 --- /dev/null +++ b/src/engine/SCons/compat/_scons_itertools.py @@ -0,0 +1,124 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/compat/_scons_itertools.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +Implementations of itertools functions for Python versions that don't +have iterators. + +These implement the functions by creating the entire list, not returning +it element-by-element as the real itertools functions do. This means +that early Python versions won't get the performance benefit of using +the itertools, but we can still use them so the later Python versions +do get the advantages of using iterators. + +Because we return the entire list, we intentionally do not implement the +itertools functions that "return" infinitely-long lists: the count(), +cycle() and repeat() functions. Other functions below have remained +unimplemented simply because they aren't being used (yet) and it wasn't +obvious how to do it. Or, conversely, we only implemented those functions +that *were* easy to implement (mostly because the Python documentation +contained examples of equivalent code). + +Note that these do not have independent unit tests, so it's possible +that there are bugs. +""" + +def chain(*iterables): + result = [] + for x in iterables: + result.extend(list(x)) + return result + +def count(n=0): + # returns infinite length, should not be supported + raise NotImplementedError + +def cycle(iterable): + # returns infinite length, should not be supported + raise NotImplementedError + +def dropwhile(predicate, iterable): + result = [] + for x in iterable: + if not predicate(x): + result.append(x) + break + result.extend(iterable) + return result + +def groupby(iterable, *args): + raise NotImplementedError + +def ifilter(predicate, iterable): + result = [] + if predicate is None: + predicate = bool + for x in iterable: + if predicate(x): + result.append(x) + return result + +def ifilterfalse(predicate, iterable): + result = [] + if predicate is None: + predicate = bool + for x in iterable: + if not predicate(x): + result.append(x) + return result + +def imap(function, *iterables): + return apply(map, (function,) + tuple(iterables)) + +def islice(*args, **kw): + raise NotImplementedError + +def izip(*iterables): + return apply(zip, iterables) + +def repeat(*args, **kw): + # returns infinite length, should not be supported + raise NotImplementedError + +def starmap(*args, **kw): + raise NotImplementedError + +def takewhile(predicate, iterable): + result = [] + for x in iterable: + if predicate(x): + result.append(x) + else: + break + return result + +def tee(*args, **kw): + raise NotImplementedError + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_optparse.py b/src/engine/SCons/compat/_scons_optparse.py new file mode 100644 index 0000000..219adba --- /dev/null +++ b/src/engine/SCons/compat/_scons_optparse.py @@ -0,0 +1,1725 @@ +"""optparse - a powerful, extensible, and easy-to-use option parser. + +By Greg Ward + +Originally distributed as Optik; see http://optik.sourceforge.net/ . + +If you have problems with this module, please do not file bugs, +patches, or feature requests with Python; instead, use Optik's +SourceForge project page: + http://sourceforge.net/projects/optik + +For support, use the optik-users@lists.sourceforge.net mailing list +(http://lists.sourceforge.net/lists/listinfo/optik-users). +""" + +# Python developers: please do not make changes to this file, since +# it is automatically generated from the Optik source code. + +__version__ = "1.5.3" + +__all__ = ['Option', + 'SUPPRESS_HELP', + 'SUPPRESS_USAGE', + 'Values', + 'OptionContainer', + 'OptionGroup', + 'OptionParser', + 'HelpFormatter', + 'IndentedHelpFormatter', + 'TitledHelpFormatter', + 'OptParseError', + 'OptionError', + 'OptionConflictError', + 'OptionValueError', + 'BadOptionError'] + +__copyright__ = """ +Copyright (c) 2001-2006 Gregory P. Ward. All rights reserved. +Copyright (c) 2002-2006 Python Software Foundation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import string +import sys, os +import types +import textwrap + +def _repr(self): + return "<%s at 0x%x: %s>" % (self.__class__.__name__, id(self), self) + + +try: + sys.getdefaultencoding +except AttributeError: + def fake_getdefaultencoding(): + return None + sys.getdefaultencoding = fake_getdefaultencoding + +try: + ''.encode +except AttributeError: + def encode_wrapper(s, encoding, replacement): + return s +else: + def encode_wrapper(s, encoding, replacement): + return s.encode(encoding, replacement) + + +# This file was generated from: +# Id: option_parser.py 527 2006-07-23 15:21:30Z greg +# Id: option.py 522 2006-06-11 16:22:03Z gward +# Id: help.py 527 2006-07-23 15:21:30Z greg +# Id: errors.py 509 2006-04-20 00:58:24Z gward + +try: + from gettext import gettext +except ImportError: + def gettext(message): + return message +_ = gettext + + +class OptParseError (Exception): + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return self.msg + + +class OptionError (OptParseError): + """ + Raised if an Option instance is created with invalid or + inconsistent arguments. + """ + + def __init__(self, msg, option): + self.msg = msg + self.option_id = str(option) + + def __str__(self): + if self.option_id: + return "option %s: %s" % (self.option_id, self.msg) + else: + return self.msg + +class OptionConflictError (OptionError): + """ + Raised if conflicting options are added to an OptionParser. + """ + +class OptionValueError (OptParseError): + """ + Raised if an invalid option value is encountered on the command + line. + """ + +class BadOptionError (OptParseError): + """ + Raised if an invalid option is seen on the command line. + """ + def __init__(self, opt_str): + self.opt_str = opt_str + + def __str__(self): + return _("no such option: %s") % self.opt_str + +class AmbiguousOptionError (BadOptionError): + """ + Raised if an ambiguous option is seen on the command line. + """ + def __init__(self, opt_str, possibilities): + BadOptionError.__init__(self, opt_str) + self.possibilities = possibilities + + def __str__(self): + return (_("ambiguous option: %s (%s?)") + % (self.opt_str, string.join(self.possibilities, ", "))) + + +class HelpFormatter: + + """ + Abstract base class for formatting option help. OptionParser + instances should use one of the HelpFormatter subclasses for + formatting help; by default IndentedHelpFormatter is used. + + Instance attributes: + parser : OptionParser + the controlling OptionParser instance + indent_increment : int + the number of columns to indent per nesting level + max_help_position : int + the maximum starting column for option help text + help_position : int + the calculated starting column for option help text; + initially the same as the maximum + width : int + total number of columns for output (pass None to constructor for + this value to be taken from the $COLUMNS environment variable) + level : int + current indentation level + current_indent : int + current indentation level (in columns) + help_width : int + number of columns available for option help text (calculated) + default_tag : str + text to replace with each option's default value, "%default" + by default. Set to false value to disable default value expansion. + option_strings : { Option : str } + maps Option instances to the snippet of help text explaining + the syntax of that option, e.g. "-h, --help" or + "-fFILE, --file=FILE" + _short_opt_fmt : str + format string controlling how short options with values are + printed in help text. Must be either "%s%s" ("-fFILE") or + "%s %s" ("-f FILE"), because those are the two syntaxes that + Optik supports. + _long_opt_fmt : str + similar but for long options; must be either "%s %s" ("--file FILE") + or "%s=%s" ("--file=FILE"). + """ + + NO_DEFAULT_VALUE = "none" + + def __init__(self, + indent_increment, + max_help_position, + width, + short_first): + self.parser = None + self.indent_increment = indent_increment + self.help_position = self.max_help_position = max_help_position + if width is None: + try: + width = int(os.environ['COLUMNS']) + except (KeyError, ValueError): + width = 80 + width = width - 2 + self.width = width + self.current_indent = 0 + self.level = 0 + self.help_width = None # computed later + self.short_first = short_first + self.default_tag = "%default" + self.option_strings = {} + self._short_opt_fmt = "%s %s" + self._long_opt_fmt = "%s=%s" + + def set_parser(self, parser): + self.parser = parser + + def set_short_opt_delimiter(self, delim): + if delim not in ("", " "): + raise ValueError( + "invalid metavar delimiter for short options: %r" % delim) + self._short_opt_fmt = "%s" + delim + "%s" + + def set_long_opt_delimiter(self, delim): + if delim not in ("=", " "): + raise ValueError( + "invalid metavar delimiter for long options: %r" % delim) + self._long_opt_fmt = "%s" + delim + "%s" + + def indent(self): + self.current_indent = self.current_indent + self.indent_increment + self.level = self.level + 1 + + def dedent(self): + self.current_indent = self.current_indent - self.indent_increment + assert self.current_indent >= 0, "Indent decreased below 0." + self.level = self.level - 1 + + def format_usage(self, usage): + raise NotImplementedError, "subclasses must implement" + + def format_heading(self, heading): + raise NotImplementedError, "subclasses must implement" + + def _format_text(self, text): + """ + Format a paragraph of free-form text for inclusion in the + help output at the current indentation level. + """ + text_width = self.width - self.current_indent + indent = " "*self.current_indent + return textwrap.fill(text, + text_width, + initial_indent=indent, + subsequent_indent=indent) + + def format_description(self, description): + if description: + return self._format_text(description) + "\n" + else: + return "" + + def format_epilog(self, epilog): + if epilog: + return "\n" + self._format_text(epilog) + "\n" + else: + return "" + + + def expand_default(self, option): + if self.parser is None or not self.default_tag: + return option.help + + default_value = self.parser.defaults.get(option.dest) + if default_value is NO_DEFAULT or default_value is None: + default_value = self.NO_DEFAULT_VALUE + + return string.replace(option.help, self.default_tag, str(default_value)) + + def format_option(self, option): + # The help for each option consists of two parts: + # * the opt strings and metavars + # eg. ("-x", or "-fFILENAME, --file=FILENAME") + # * the user-supplied help string + # eg. ("turn on expert mode", "read data from FILENAME") + # + # If possible, we write both of these on the same line: + # -x turn on expert mode + # + # But if the opt string list is too long, we put the help + # string on a second line, indented to the same column it would + # start in if it fit on the first line. + # -fFILENAME, --file=FILENAME + # read data from FILENAME + result = [] + opts = self.option_strings[option] + opt_width = self.help_position - self.current_indent - 2 + if len(opts) > opt_width: + opts = "%*s%s\n" % (self.current_indent, "", opts) + indent_first = self.help_position + else: # start help on same line as opts + opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) + indent_first = 0 + result.append(opts) + if option.help: + help_text = self.expand_default(option) + help_lines = textwrap.wrap(help_text, self.help_width) + result.append("%*s%s\n" % (indent_first, "", help_lines[0])) + for line in help_lines[1:]: + result.append("%*s%s\n" % (self.help_position, "", line)) + elif opts[-1] != "\n": + result.append("\n") + return string.join(result, "") + + def store_option_strings(self, parser): + self.indent() + max_len = 0 + for opt in parser.option_list: + strings = self.format_option_strings(opt) + self.option_strings[opt] = strings + max_len = max(max_len, len(strings) + self.current_indent) + self.indent() + for group in parser.option_groups: + for opt in group.option_list: + strings = self.format_option_strings(opt) + self.option_strings[opt] = strings + max_len = max(max_len, len(strings) + self.current_indent) + self.dedent() + self.dedent() + self.help_position = min(max_len + 2, self.max_help_position) + self.help_width = self.width - self.help_position + + def format_option_strings(self, option): + """Return a comma-separated list of option strings & metavariables.""" + if option.takes_value(): + metavar = option.metavar or string.upper(option.dest) + short_opts = [] + for sopt in option._short_opts: + short_opts.append(self._short_opt_fmt % (sopt, metavar)) + long_opts = [] + for lopt in option._long_opts: + long_opts.append(self._long_opt_fmt % (lopt, metavar)) + else: + short_opts = option._short_opts + long_opts = option._long_opts + + if self.short_first: + opts = short_opts + long_opts + else: + opts = long_opts + short_opts + + return string.join(opts, ", ") + +class IndentedHelpFormatter (HelpFormatter): + """Format help with indented section bodies. + """ + + def __init__(self, + indent_increment=2, + max_help_position=24, + width=None, + short_first=1): + HelpFormatter.__init__( + self, indent_increment, max_help_position, width, short_first) + + def format_usage(self, usage): + return _("Usage: %s\n") % usage + + def format_heading(self, heading): + return "%*s%s:\n" % (self.current_indent, "", heading) + + +class TitledHelpFormatter (HelpFormatter): + """Format help with underlined section headers. + """ + + def __init__(self, + indent_increment=0, + max_help_position=24, + width=None, + short_first=0): + HelpFormatter.__init__ ( + self, indent_increment, max_help_position, width, short_first) + + def format_usage(self, usage): + return "%s %s\n" % (self.format_heading(_("Usage")), usage) + + def format_heading(self, heading): + return "%s\n%s\n" % (heading, "=-"[self.level] * len(heading)) + + +def _parse_num(val, type): + if string.lower(val[:2]) == "0x": # hexadecimal + radix = 16 + elif string.lower(val[:2]) == "0b": # binary + radix = 2 + val = val[2:] or "0" # have to remove "0b" prefix + elif val[:1] == "0": # octal + radix = 8 + else: # decimal + radix = 10 + + return type(val, radix) + +def _parse_int(val): + return _parse_num(val, int) + +def _parse_long(val): + return _parse_num(val, long) + +try: + int('0', 10) +except TypeError: + # Python 1.5.2 doesn't allow a radix value to be passed to int(). + _parse_int = int + +try: + long('0', 10) +except TypeError: + # Python 1.5.2 doesn't allow a radix value to be passed to long(). + _parse_long = long + +_builtin_cvt = { "int" : (_parse_int, _("integer")), + "long" : (_parse_long, _("long integer")), + "float" : (float, _("floating-point")), + "complex" : (complex, _("complex")) } + +def check_builtin(option, opt, value): + (cvt, what) = _builtin_cvt[option.type] + try: + return cvt(value) + except ValueError: + raise OptionValueError( + _("option %s: invalid %s value: %r") % (opt, what, value)) + +def check_choice(option, opt, value): + if value in option.choices: + return value + else: + choices = string.join(map(repr, option.choices), ", ") + raise OptionValueError( + _("option %s: invalid choice: %r (choose from %s)") + % (opt, value, choices)) + +# Not supplying a default is different from a default of None, +# so we need an explicit "not supplied" value. +NO_DEFAULT = ("NO", "DEFAULT") + + +class Option: + """ + Instance attributes: + _short_opts : [string] + _long_opts : [string] + + action : string + type : string + dest : string + default : any + nargs : int + const : any + choices : [string] + callback : function + callback_args : (any*) + callback_kwargs : { string : any } + help : string + metavar : string + """ + + # The list of instance attributes that may be set through + # keyword args to the constructor. + ATTRS = ['action', + 'type', + 'dest', + 'default', + 'nargs', + 'const', + 'choices', + 'callback', + 'callback_args', + 'callback_kwargs', + 'help', + 'metavar'] + + # The set of actions allowed by option parsers. Explicitly listed + # here so the constructor can validate its arguments. + ACTIONS = ("store", + "store_const", + "store_true", + "store_false", + "append", + "append_const", + "count", + "callback", + "help", + "version") + + # The set of actions that involve storing a value somewhere; + # also listed just for constructor argument validation. (If + # the action is one of these, there must be a destination.) + STORE_ACTIONS = ("store", + "store_const", + "store_true", + "store_false", + "append", + "append_const", + "count") + + # The set of actions for which it makes sense to supply a value + # type, ie. which may consume an argument from the command line. + TYPED_ACTIONS = ("store", + "append", + "callback") + + # The set of actions which *require* a value type, ie. that + # always consume an argument from the command line. + ALWAYS_TYPED_ACTIONS = ("store", + "append") + + # The set of actions which take a 'const' attribute. + CONST_ACTIONS = ("store_const", + "append_const") + + # The set of known types for option parsers. Again, listed here for + # constructor argument validation. + TYPES = ("string", "int", "long", "float", "complex", "choice") + + # Dictionary of argument checking functions, which convert and + # validate option arguments according to the option type. + # + # Signature of checking functions is: + # check(option : Option, opt : string, value : string) -> any + # where + # option is the Option instance calling the checker + # opt is the actual option seen on the command-line + # (eg. "-a", "--file") + # value is the option argument seen on the command-line + # + # The return value should be in the appropriate Python type + # for option.type -- eg. an integer if option.type == "int". + # + # If no checker is defined for a type, arguments will be + # unchecked and remain strings. + TYPE_CHECKER = { "int" : check_builtin, + "long" : check_builtin, + "float" : check_builtin, + "complex": check_builtin, + "choice" : check_choice, + } + + + # CHECK_METHODS is a list of unbound method objects; they are called + # by the constructor, in order, after all attributes are + # initialized. The list is created and filled in later, after all + # the methods are actually defined. (I just put it here because I + # like to define and document all class attributes in the same + # place.) Subclasses that add another _check_*() method should + # define their own CHECK_METHODS list that adds their check method + # to those from this class. + CHECK_METHODS = None + + + # -- Constructor/initialization methods ---------------------------- + + def __init__(self, *opts, **attrs): + # Set _short_opts, _long_opts attrs from 'opts' tuple. + # Have to be set now, in case no option strings are supplied. + self._short_opts = [] + self._long_opts = [] + opts = self._check_opt_strings(opts) + self._set_opt_strings(opts) + + # Set all other attrs (action, type, etc.) from 'attrs' dict + self._set_attrs(attrs) + + # Check all the attributes we just set. There are lots of + # complicated interdependencies, but luckily they can be farmed + # out to the _check_*() methods listed in CHECK_METHODS -- which + # could be handy for subclasses! The one thing these all share + # is that they raise OptionError if they discover a problem. + for checker in self.CHECK_METHODS: + checker(self) + + def _check_opt_strings(self, opts): + # Filter out None because early versions of Optik had exactly + # one short option and one long option, either of which + # could be None. + opts = filter(None, opts) + if not opts: + raise TypeError("at least one option string must be supplied") + return opts + + def _set_opt_strings(self, opts): + for opt in opts: + if len(opt) < 2: + raise OptionError( + "invalid option string %r: " + "must be at least two characters long" % opt, self) + elif len(opt) == 2: + if not (opt[0] == "-" and opt[1] != "-"): + raise OptionError( + "invalid short option string %r: " + "must be of the form -x, (x any non-dash char)" % opt, + self) + self._short_opts.append(opt) + else: + if not (opt[0:2] == "--" and opt[2] != "-"): + raise OptionError( + "invalid long option string %r: " + "must start with --, followed by non-dash" % opt, + self) + self._long_opts.append(opt) + + def _set_attrs(self, attrs): + for attr in self.ATTRS: + if attrs.has_key(attr): + setattr(self, attr, attrs[attr]) + del attrs[attr] + else: + if attr == 'default': + setattr(self, attr, NO_DEFAULT) + else: + setattr(self, attr, None) + if attrs: + attrs = attrs.keys() + attrs.sort() + raise OptionError( + "invalid keyword arguments: %s" % string.join(attrs, ", "), + self) + + + # -- Constructor validation methods -------------------------------- + + def _check_action(self): + if self.action is None: + self.action = "store" + elif self.action not in self.ACTIONS: + raise OptionError("invalid action: %r" % self.action, self) + + def _check_type(self): + if self.type is None: + if self.action in self.ALWAYS_TYPED_ACTIONS: + if self.choices is not None: + # The "choices" attribute implies "choice" type. + self.type = "choice" + else: + # No type given? "string" is the most sensible default. + self.type = "string" + else: + # Allow type objects or builtin type conversion functions + # (int, str, etc.) as an alternative to their names. (The + # complicated check of __builtin__ is only necessary for + # Python 2.1 and earlier, and is short-circuited by the + # first check on modern Pythons.) + import __builtin__ + if ( type(self.type) is types.TypeType or + (hasattr(self.type, "__name__") and + getattr(__builtin__, self.type.__name__, None) is self.type) ): + self.type = self.type.__name__ + + if self.type == "str": + self.type = "string" + + if self.type not in self.TYPES: + raise OptionError("invalid option type: %r" % self.type, self) + if self.action not in self.TYPED_ACTIONS: + raise OptionError( + "must not supply a type for action %r" % self.action, self) + + def _check_choice(self): + if self.type == "choice": + if self.choices is None: + raise OptionError( + "must supply a list of choices for type 'choice'", self) + elif type(self.choices) not in (types.TupleType, types.ListType): + raise OptionError( + "choices must be a list of strings ('%s' supplied)" + % string.split(str(type(self.choices)), "'")[1], self) + elif self.choices is not None: + raise OptionError( + "must not supply choices for type %r" % self.type, self) + + def _check_dest(self): + # No destination given, and we need one for this action. The + # self.type check is for callbacks that take a value. + takes_value = (self.action in self.STORE_ACTIONS or + self.type is not None) + if self.dest is None and takes_value: + + # Glean a destination from the first long option string, + # or from the first short option string if no long options. + if self._long_opts: + # eg. "--foo-bar" -> "foo_bar" + self.dest = string.replace(self._long_opts[0][2:], '-', '_') + else: + self.dest = self._short_opts[0][1] + + def _check_const(self): + if self.action not in self.CONST_ACTIONS and self.const is not None: + raise OptionError( + "'const' must not be supplied for action %r" % self.action, + self) + + def _check_nargs(self): + if self.action in self.TYPED_ACTIONS: + if self.nargs is None: + self.nargs = 1 + elif self.nargs is not None: + raise OptionError( + "'nargs' must not be supplied for action %r" % self.action, + self) + + def _check_callback(self): + if self.action == "callback": + if not callable(self.callback): + raise OptionError( + "callback not callable: %r" % self.callback, self) + if (self.callback_args is not None and + type(self.callback_args) is not types.TupleType): + raise OptionError( + "callback_args, if supplied, must be a tuple: not %r" + % self.callback_args, self) + if (self.callback_kwargs is not None and + type(self.callback_kwargs) is not types.DictType): + raise OptionError( + "callback_kwargs, if supplied, must be a dict: not %r" + % self.callback_kwargs, self) + else: + if self.callback is not None: + raise OptionError( + "callback supplied (%r) for non-callback option" + % self.callback, self) + if self.callback_args is not None: + raise OptionError( + "callback_args supplied for non-callback option", self) + if self.callback_kwargs is not None: + raise OptionError( + "callback_kwargs supplied for non-callback option", self) + + + CHECK_METHODS = [_check_action, + _check_type, + _check_choice, + _check_dest, + _check_const, + _check_nargs, + _check_callback] + + + # -- Miscellaneous methods ----------------------------------------- + + def __str__(self): + return string.join(self._short_opts + self._long_opts, "/") + + __repr__ = _repr + + def takes_value(self): + return self.type is not None + + def get_opt_string(self): + if self._long_opts: + return self._long_opts[0] + else: + return self._short_opts[0] + + + # -- Processing methods -------------------------------------------- + + def check_value(self, opt, value): + checker = self.TYPE_CHECKER.get(self.type) + if checker is None: + return value + else: + return checker(self, opt, value) + + def convert_value(self, opt, value): + if value is not None: + if self.nargs == 1: + return self.check_value(opt, value) + else: + return tuple(map(lambda v, o=opt, s=self: s.check_value(o, v), value)) + + def process(self, opt, value, values, parser): + + # First, convert the value(s) to the right type. Howl if any + # value(s) are bogus. + value = self.convert_value(opt, value) + + # And then take whatever action is expected of us. + # This is a separate method to make life easier for + # subclasses to add new actions. + return self.take_action( + self.action, self.dest, opt, value, values, parser) + + def take_action(self, action, dest, opt, value, values, parser): + if action == "store": + setattr(values, dest, value) + elif action == "store_const": + setattr(values, dest, self.const) + elif action == "store_true": + setattr(values, dest, True) + elif action == "store_false": + setattr(values, dest, False) + elif action == "append": + values.ensure_value(dest, []).append(value) + elif action == "append_const": + values.ensure_value(dest, []).append(self.const) + elif action == "count": + setattr(values, dest, values.ensure_value(dest, 0) + 1) + elif action == "callback": + args = self.callback_args or () + kwargs = self.callback_kwargs or {} + apply(self.callback, (self, opt, value, parser,) + args, kwargs) + elif action == "help": + parser.print_help() + parser.exit() + elif action == "version": + parser.print_version() + parser.exit() + else: + raise RuntimeError, "unknown action %r" % self.action + + return 1 + +# class Option + + +SUPPRESS_HELP = "SUPPRESS"+"HELP" +SUPPRESS_USAGE = "SUPPRESS"+"USAGE" + +# For compatibility with Python 2.2 +try: + True, False +except NameError: + (True, False) = (1, 0) + +try: + types.UnicodeType +except AttributeError: + def isbasestring(x): + return isinstance(x, types.StringType) +else: + def isbasestring(x): + return isinstance(x, types.StringType) or isinstance(x, types.UnicodeType) + +class Values: + + def __init__(self, defaults=None): + if defaults: + for (attr, val) in defaults.items(): + setattr(self, attr, val) + + def __str__(self): + return str(self.__dict__) + + __repr__ = _repr + + def __cmp__(self, other): + if isinstance(other, Values): + return cmp(self.__dict__, other.__dict__) + elif isinstance(other, types.DictType): + return cmp(self.__dict__, other) + else: + return -1 + + def _update_careful(self, dict): + """ + Update the option values from an arbitrary dictionary, but only + use keys from dict that already have a corresponding attribute + in self. Any keys in dict without a corresponding attribute + are silently ignored. + """ + for attr in dir(self): + if dict.has_key(attr): + dval = dict[attr] + if dval is not None: + setattr(self, attr, dval) + + def _update_loose(self, dict): + """ + Update the option values from an arbitrary dictionary, + using all keys from the dictionary regardless of whether + they have a corresponding attribute in self or not. + """ + self.__dict__.update(dict) + + def _update(self, dict, mode): + if mode == "careful": + self._update_careful(dict) + elif mode == "loose": + self._update_loose(dict) + else: + raise ValueError, "invalid update mode: %r" % mode + + def read_module(self, modname, mode="careful"): + __import__(modname) + mod = sys.modules[modname] + self._update(vars(mod), mode) + + def read_file(self, filename, mode="careful"): + vars = {} + exec open(filename, 'rU').read() in vars + self._update(vars, mode) + + def ensure_value(self, attr, value): + if not hasattr(self, attr) or getattr(self, attr) is None: + setattr(self, attr, value) + return getattr(self, attr) + + +class OptionContainer: + + """ + Abstract base class. + + Class attributes: + standard_option_list : [Option] + list of standard options that will be accepted by all instances + of this parser class (intended to be overridden by subclasses). + + Instance attributes: + option_list : [Option] + the list of Option objects contained by this OptionContainer + _short_opt : { string : Option } + dictionary mapping short option strings, eg. "-f" or "-X", + to the Option instances that implement them. If an Option + has multiple short option strings, it will appears in this + dictionary multiple times. [1] + _long_opt : { string : Option } + dictionary mapping long option strings, eg. "--file" or + "--exclude", to the Option instances that implement them. + Again, a given Option can occur multiple times in this + dictionary. [1] + defaults : { string : any } + dictionary mapping option destination names to default + values for each destination [1] + + [1] These mappings are common to (shared by) all components of the + controlling OptionParser, where they are initially created. + + """ + + def __init__(self, option_class, conflict_handler, description): + # Initialize the option list and related data structures. + # This method must be provided by subclasses, and it must + # initialize at least the following instance attributes: + # option_list, _short_opt, _long_opt, defaults. + self._create_option_list() + + self.option_class = option_class + self.set_conflict_handler(conflict_handler) + self.set_description(description) + + def _create_option_mappings(self): + # For use by OptionParser constructor -- create the master + # option mappings used by this OptionParser and all + # OptionGroups that it owns. + self._short_opt = {} # single letter -> Option instance + self._long_opt = {} # long option -> Option instance + self.defaults = {} # maps option dest -> default value + + + def _share_option_mappings(self, parser): + # For use by OptionGroup constructor -- use shared option + # mappings from the OptionParser that owns this OptionGroup. + self._short_opt = parser._short_opt + self._long_opt = parser._long_opt + self.defaults = parser.defaults + + def set_conflict_handler(self, handler): + if handler not in ("error", "resolve"): + raise ValueError, "invalid conflict_resolution value %r" % handler + self.conflict_handler = handler + + def set_description(self, description): + self.description = description + + def get_description(self): + return self.description + + + def destroy(self): + """see OptionParser.destroy().""" + del self._short_opt + del self._long_opt + del self.defaults + + + # -- Option-adding methods ----------------------------------------- + + def _check_conflict(self, option): + conflict_opts = [] + for opt in option._short_opts: + if self._short_opt.has_key(opt): + conflict_opts.append((opt, self._short_opt[opt])) + for opt in option._long_opts: + if self._long_opt.has_key(opt): + conflict_opts.append((opt, self._long_opt[opt])) + + if conflict_opts: + handler = self.conflict_handler + if handler == "error": + raise OptionConflictError( + "conflicting option string(s): %s" + % string.join(map(lambda co: co[0], conflict_opts), ", "), + option) + elif handler == "resolve": + for (opt, c_option) in conflict_opts: + if opt[:2] == "--": + c_option._long_opts.remove(opt) + del self._long_opt[opt] + else: + c_option._short_opts.remove(opt) + del self._short_opt[opt] + if not (c_option._short_opts or c_option._long_opts): + c_option.container.option_list.remove(c_option) + + def add_option(self, *args, **kwargs): + """add_option(Option) + add_option(opt_str, ..., kwarg=val, ...) + """ + if type(args[0]) is types.StringType: + option = apply(self.option_class, args, kwargs) + elif len(args) == 1 and not kwargs: + option = args[0] + if not isinstance(option, Option): + raise TypeError, "not an Option instance: %r" % option + else: + raise TypeError, "invalid arguments" + + self._check_conflict(option) + + self.option_list.append(option) + option.container = self + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + if option.dest is not None: # option has a dest, we need a default + if option.default is not NO_DEFAULT: + self.defaults[option.dest] = option.default + elif not self.defaults.has_key(option.dest): + self.defaults[option.dest] = None + + return option + + def add_options(self, option_list): + for option in option_list: + self.add_option(option) + + # -- Option query/removal methods ---------------------------------- + + def get_option(self, opt_str): + return (self._short_opt.get(opt_str) or + self._long_opt.get(opt_str)) + + def has_option(self, opt_str): + return (self._short_opt.has_key(opt_str) or + self._long_opt.has_key(opt_str)) + + def remove_option(self, opt_str): + option = self._short_opt.get(opt_str) + if option is None: + option = self._long_opt.get(opt_str) + if option is None: + raise ValueError("no such option %r" % opt_str) + + for opt in option._short_opts: + del self._short_opt[opt] + for opt in option._long_opts: + del self._long_opt[opt] + option.container.option_list.remove(option) + + + # -- Help-formatting methods --------------------------------------- + + def format_option_help(self, formatter): + if not self.option_list: + return "" + result = [] + for option in self.option_list: + if not option.help is SUPPRESS_HELP: + result.append(formatter.format_option(option)) + return string.join(result, "") + + def format_description(self, formatter): + return formatter.format_description(self.get_description()) + + def format_help(self, formatter): + result = [] + if self.description: + result.append(self.format_description(formatter)) + if self.option_list: + result.append(self.format_option_help(formatter)) + return string.join(result, "\n") + + +class OptionGroup (OptionContainer): + + def __init__(self, parser, title, description=None): + self.parser = parser + OptionContainer.__init__( + self, parser.option_class, parser.conflict_handler, description) + self.title = title + + def _create_option_list(self): + self.option_list = [] + self._share_option_mappings(self.parser) + + def set_title(self, title): + self.title = title + + def destroy(self): + """see OptionParser.destroy().""" + OptionContainer.destroy(self) + del self.option_list + + # -- Help-formatting methods --------------------------------------- + + def format_help(self, formatter): + result = formatter.format_heading(self.title) + formatter.indent() + result = result + OptionContainer.format_help(self, formatter) + formatter.dedent() + return result + + +class OptionParser (OptionContainer): + + """ + Class attributes: + standard_option_list : [Option] + list of standard options that will be accepted by all instances + of this parser class (intended to be overridden by subclasses). + + Instance attributes: + usage : string + a usage string for your program. Before it is displayed + to the user, "%prog" will be expanded to the name of + your program (self.prog or os.path.basename(sys.argv[0])). + prog : string + the name of the current program (to override + os.path.basename(sys.argv[0])). + epilog : string + paragraph of help text to print after option help + + option_groups : [OptionGroup] + list of option groups in this parser (option groups are + irrelevant for parsing the command-line, but very useful + for generating help) + + allow_interspersed_args : bool = true + if true, positional arguments may be interspersed with options. + Assuming -a and -b each take a single argument, the command-line + -ablah foo bar -bboo baz + will be interpreted the same as + -ablah -bboo -- foo bar baz + If this flag were false, that command line would be interpreted as + -ablah -- foo bar -bboo baz + -- ie. we stop processing options as soon as we see the first + non-option argument. (This is the tradition followed by + Python's getopt module, Perl's Getopt::Std, and other argument- + parsing libraries, but it is generally annoying to users.) + + process_default_values : bool = true + if true, option default values are processed similarly to option + values from the command line: that is, they are passed to the + type-checking function for the option's type (as long as the + default value is a string). (This really only matters if you + have defined custom types; see SF bug #955889.) Set it to false + to restore the behaviour of Optik 1.4.1 and earlier. + + rargs : [string] + the argument list currently being parsed. Only set when + parse_args() is active, and continually trimmed down as + we consume arguments. Mainly there for the benefit of + callback options. + largs : [string] + the list of leftover arguments that we have skipped while + parsing options. If allow_interspersed_args is false, this + list is always empty. + values : Values + the set of option values currently being accumulated. Only + set when parse_args() is active. Also mainly for callbacks. + + Because of the 'rargs', 'largs', and 'values' attributes, + OptionParser is not thread-safe. If, for some perverse reason, you + need to parse command-line arguments simultaneously in different + threads, use different OptionParser instances. + + """ + + standard_option_list = [] + + def __init__(self, + usage=None, + option_list=None, + option_class=Option, + version=None, + conflict_handler="error", + description=None, + formatter=None, + add_help_option=True, + prog=None, + epilog=None): + OptionContainer.__init__( + self, option_class, conflict_handler, description) + self.set_usage(usage) + self.prog = prog + self.version = version + self.allow_interspersed_args = True + self.process_default_values = True + if formatter is None: + formatter = IndentedHelpFormatter() + self.formatter = formatter + self.formatter.set_parser(self) + self.epilog = epilog + + # Populate the option list; initial sources are the + # standard_option_list class attribute, the 'option_list' + # argument, and (if applicable) the _add_version_option() and + # _add_help_option() methods. + self._populate_option_list(option_list, + add_help=add_help_option) + + self._init_parsing_state() + + + def destroy(self): + """ + Declare that you are done with this OptionParser. This cleans up + reference cycles so the OptionParser (and all objects referenced by + it) can be garbage-collected promptly. After calling destroy(), the + OptionParser is unusable. + """ + OptionContainer.destroy(self) + for group in self.option_groups: + group.destroy() + del self.option_list + del self.option_groups + del self.formatter + + + # -- Private methods ----------------------------------------------- + # (used by our or OptionContainer's constructor) + + def _create_option_list(self): + self.option_list = [] + self.option_groups = [] + self._create_option_mappings() + + def _add_help_option(self): + self.add_option("-h", "--help", + action="help", + help=_("show this help message and exit")) + + def _add_version_option(self): + self.add_option("--version", + action="version", + help=_("show program's version number and exit")) + + def _populate_option_list(self, option_list, add_help=True): + if self.standard_option_list: + self.add_options(self.standard_option_list) + if option_list: + self.add_options(option_list) + if self.version: + self._add_version_option() + if add_help: + self._add_help_option() + + def _init_parsing_state(self): + # These are set in parse_args() for the convenience of callbacks. + self.rargs = None + self.largs = None + self.values = None + + + # -- Simple modifier methods --------------------------------------- + + def set_usage(self, usage): + if usage is None: + self.usage = _("%prog [options]") + elif usage is SUPPRESS_USAGE: + self.usage = None + # For backwards compatibility with Optik 1.3 and earlier. + elif string.lower(usage)[:7] == "usage: ": + self.usage = usage[7:] + else: + self.usage = usage + + def enable_interspersed_args(self): + self.allow_interspersed_args = True + + def disable_interspersed_args(self): + self.allow_interspersed_args = False + + def set_process_default_values(self, process): + self.process_default_values = process + + def set_default(self, dest, value): + self.defaults[dest] = value + + def set_defaults(self, **kwargs): + self.defaults.update(kwargs) + + def _get_all_options(self): + options = self.option_list[:] + for group in self.option_groups: + options.extend(group.option_list) + return options + + def get_default_values(self): + if not self.process_default_values: + # Old, pre-Optik 1.5 behaviour. + return Values(self.defaults) + + defaults = self.defaults.copy() + for option in self._get_all_options(): + default = defaults.get(option.dest) + if isbasestring(default): + opt_str = option.get_opt_string() + defaults[option.dest] = option.check_value(opt_str, default) + + return Values(defaults) + + + # -- OptionGroup methods ------------------------------------------- + + def add_option_group(self, *args, **kwargs): + # XXX lots of overlap with OptionContainer.add_option() + if type(args[0]) is types.StringType: + group = apply(OptionGroup, (self,) + args, kwargs) + elif len(args) == 1 and not kwargs: + group = args[0] + if not isinstance(group, OptionGroup): + raise TypeError, "not an OptionGroup instance: %r" % group + if group.parser is not self: + raise ValueError, "invalid OptionGroup (wrong parser)" + else: + raise TypeError, "invalid arguments" + + self.option_groups.append(group) + return group + + def get_option_group(self, opt_str): + option = (self._short_opt.get(opt_str) or + self._long_opt.get(opt_str)) + if option and option.container is not self: + return option.container + return None + + + # -- Option-parsing methods ---------------------------------------- + + def _get_args(self, args): + if args is None: + return sys.argv[1:] + else: + return args[:] # don't modify caller's list + + def parse_args(self, args=None, values=None): + """ + parse_args(args : [string] = sys.argv[1:], + values : Values = None) + -> (values : Values, args : [string]) + + Parse the command-line options found in 'args' (default: + sys.argv[1:]). Any errors result in a call to 'error()', which + by default prints the usage message to stderr and calls + sys.exit() with an error message. On success returns a pair + (values, args) where 'values' is an Values instance (with all + your option values) and 'args' is the list of arguments left + over after parsing options. + """ + rargs = self._get_args(args) + if values is None: + values = self.get_default_values() + + # Store the halves of the argument list as attributes for the + # convenience of callbacks: + # rargs + # the rest of the command-line (the "r" stands for + # "remaining" or "right-hand") + # largs + # the leftover arguments -- ie. what's left after removing + # options and their arguments (the "l" stands for "leftover" + # or "left-hand") + self.rargs = rargs + self.largs = largs = [] + self.values = values + + try: + stop = self._process_args(largs, rargs, values) + except (BadOptionError, OptionValueError), err: + self.error(str(err)) + + args = largs + rargs + return self.check_values(values, args) + + def check_values(self, values, args): + """ + check_values(values : Values, args : [string]) + -> (values : Values, args : [string]) + + Check that the supplied option values and leftover arguments are + valid. Returns the option values and leftover arguments + (possibly adjusted, possibly completely new -- whatever you + like). Default implementation just returns the passed-in + values; subclasses may override as desired. + """ + return (values, args) + + def _process_args(self, largs, rargs, values): + """_process_args(largs : [string], + rargs : [string], + values : Values) + + Process command-line arguments and populate 'values', consuming + options and arguments from 'rargs'. If 'allow_interspersed_args' is + false, stop at the first non-option argument. If true, accumulate any + interspersed non-option arguments in 'largs'. + """ + while rargs: + arg = rargs[0] + # We handle bare "--" explicitly, and bare "-" is handled by the + # standard arg handler since the short arg case ensures that the + # len of the opt string is greater than 1. + if arg == "--": + del rargs[0] + return + elif arg[0:2] == "--": + # process a single long option (possibly with value(s)) + self._process_long_opt(rargs, values) + elif arg[:1] == "-" and len(arg) > 1: + # process a cluster of short options (possibly with + # value(s) for the last one only) + self._process_short_opts(rargs, values) + elif self.allow_interspersed_args: + largs.append(arg) + del rargs[0] + else: + return # stop now, leave this arg in rargs + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt(self, opt): + """_match_long_opt(opt : string) -> string + + Determine which long option string 'opt' matches, ie. which one + it is an unambiguous abbrevation for. Raises BadOptionError if + 'opt' doesn't unambiguously match any long option string. + """ + return _match_abbrev(opt, self._long_opt) + + def _process_long_opt(self, rargs, values): + arg = rargs.pop(0) + + # Value explicitly attached to arg? Pretend it's the next + # argument. + if "=" in arg: + (opt, next_arg) = string.split(arg, "=", 1) + rargs.insert(0, next_arg) + had_explicit_value = True + else: + opt = arg + had_explicit_value = False + + opt = self._match_long_opt(opt) + option = self._long_opt[opt] + if option.takes_value(): + nargs = option.nargs + if len(rargs) < nargs: + if nargs == 1: + self.error(_("%s option requires an argument") % opt) + else: + self.error(_("%s option requires %d arguments") + % (opt, nargs)) + elif nargs == 1: + value = rargs.pop(0) + else: + value = tuple(rargs[0:nargs]) + del rargs[0:nargs] + + elif had_explicit_value: + self.error(_("%s option does not take a value") % opt) + + else: + value = None + + option.process(opt, value, values, self) + + def _process_short_opts(self, rargs, values): + arg = rargs.pop(0) + stop = False + i = 1 + for ch in arg[1:]: + opt = "-" + ch + option = self._short_opt.get(opt) + i = i + 1 # we have consumed a character + + if not option: + raise BadOptionError(opt) + if option.takes_value(): + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + rargs.insert(0, arg[i:]) + stop = True + + nargs = option.nargs + if len(rargs) < nargs: + if nargs == 1: + self.error(_("%s option requires an argument") % opt) + else: + self.error(_("%s option requires %d arguments") + % (opt, nargs)) + elif nargs == 1: + value = rargs.pop(0) + else: + value = tuple(rargs[0:nargs]) + del rargs[0:nargs] + + else: # option doesn't take a value + value = None + + option.process(opt, value, values, self) + + if stop: + break + + + # -- Feedback methods ---------------------------------------------- + + def get_prog_name(self): + if self.prog is None: + return os.path.basename(sys.argv[0]) + else: + return self.prog + + def expand_prog_name(self, s): + return string.replace(s, "%prog", self.get_prog_name()) + + def get_description(self): + return self.expand_prog_name(self.description) + + def exit(self, status=0, msg=None): + if msg: + sys.stderr.write(msg) + sys.exit(status) + + def error(self, msg): + """error(msg : string) + + Print a usage message incorporating 'msg' to stderr and exit. + If you override this in a subclass, it should not return -- it + should either exit or raise an exception. + """ + self.print_usage(sys.stderr) + self.exit(2, "%s: error: %s\n" % (self.get_prog_name(), msg)) + + def get_usage(self): + if self.usage: + return self.formatter.format_usage( + self.expand_prog_name(self.usage)) + else: + return "" + + def print_usage(self, file=None): + """print_usage(file : file = stdout) + + Print the usage message for the current program (self.usage) to + 'file' (default stdout). Any occurence of the string "%prog" in + self.usage is replaced with the name of the current program + (basename of sys.argv[0]). Does nothing if self.usage is empty + or not defined. + """ + if self.usage: + file.write(self.get_usage() + '\n') + + def get_version(self): + if self.version: + return self.expand_prog_name(self.version) + else: + return "" + + def print_version(self, file=None): + """print_version(file : file = stdout) + + Print the version message for this program (self.version) to + 'file' (default stdout). As with print_usage(), any occurence + of "%prog" in self.version is replaced by the current program's + name. Does nothing if self.version is empty or undefined. + """ + if self.version: + file.write(self.get_version() + '\n') + + def format_option_help(self, formatter=None): + if formatter is None: + formatter = self.formatter + formatter.store_option_strings(self) + result = [] + result.append(formatter.format_heading(_("Options"))) + formatter.indent() + if self.option_list: + result.append(OptionContainer.format_option_help(self, formatter)) + result.append("\n") + for group in self.option_groups: + result.append(group.format_help(formatter)) + result.append("\n") + formatter.dedent() + # Drop the last "\n", or the header if no options or option groups: + return string.join(result[:-1], "") + + def format_epilog(self, formatter): + return formatter.format_epilog(self.epilog) + + def format_help(self, formatter=None): + if formatter is None: + formatter = self.formatter + result = [] + if self.usage: + result.append(self.get_usage() + "\n") + if self.description: + result.append(self.format_description(formatter) + "\n") + result.append(self.format_option_help(formatter)) + result.append(self.format_epilog(formatter)) + return string.join(result, "") + + # used by test suite + def _get_encoding(self, file): + encoding = getattr(file, "encoding", None) + if not encoding: + encoding = sys.getdefaultencoding() + return encoding + + def print_help(self, file=None): + """print_help(file : file = stdout) + + Print an extended help message, listing all options and any + help text provided with them, to 'file' (default stdout). + """ + if file is None: + file = sys.stdout + encoding = self._get_encoding(file) + file.write(encode_wrapper(self.format_help(), encoding, "replace")) + +# class OptionParser + + +def _match_abbrev(s, wordmap): + """_match_abbrev(s : string, wordmap : {string : Option}) -> string + + Return the string key in 'wordmap' for which 's' is an unambiguous + abbreviation. If 's' is found to be ambiguous or doesn't match any of + 'words', raise BadOptionError. + """ + # Is there an exact match? + if wordmap.has_key(s): + return s + else: + # Isolate all words with s as a prefix. + possibilities = filter(lambda w, s=s: w[:len(s)] == s, wordmap.keys()) + # No exact match, so there had better be just one possibility. + if len(possibilities) == 1: + return possibilities[0] + elif not possibilities: + raise BadOptionError(s) + else: + # More than one possible completion: ambiguous prefix. + possibilities.sort() + raise AmbiguousOptionError(s, possibilities) + + +# Some day, there might be many Option classes. As of Optik 1.3, the +# preferred way to instantiate Options is indirectly, via make_option(), +# which will become a factory function when there are many Option +# classes. +make_option = Option + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_platform.py b/src/engine/SCons/compat/_scons_platform.py new file mode 100644 index 0000000..8b675ea --- /dev/null +++ b/src/engine/SCons/compat/_scons_platform.py @@ -0,0 +1,237 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__doc__ = """ +platform backwards-compatibility module for older (pre-2.3) Python versions + +This does not not NOT (repeat, *NOT*) provide complete platform +functionality. It only wraps the portions of platform functionality used +by SCons. +""" + +__revision__ = "src/engine/SCons/compat/_scons_platform.py 4577 2009/12/27 19:44:43 scons" + +### Portable uname() interface + +_uname_cache = None + +def uname(): + + """ Fairly portable uname interface. Returns a tuple + of strings (system,node,release,version,machine,processor) + identifying the underlying platform. + + Note that unlike the os.uname function this also returns + possible processor information as an additional tuple entry. + + Entries which cannot be determined are set to ''. + + """ + global _uname_cache + no_os_uname = 0 + + if _uname_cache is not None: + return _uname_cache + + processor = '' + + # Get some infos from the builtin os.uname API... + try: + system,node,release,version,machine = os.uname() + except AttributeError: + no_os_uname = 1 + + if no_os_uname or not filter(None, (system, node, release, version, machine)): + # Hmm, no there is either no uname or uname has returned + #'unknowns'... we'll have to poke around the system then. + if no_os_uname: + system = sys.platform + release = '' + version = '' + node = _node() + machine = '' + + use_syscmd_ver = 01 + + # Try win32_ver() on win32 platforms + if system == 'win32': + release,version,csd,ptype = win32_ver() + if release and version: + use_syscmd_ver = 0 + # Try to use the PROCESSOR_* environment variables + # available on Win XP and later; see + # http://support.microsoft.com/kb/888731 and + # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM + if not machine: + machine = os.environ.get('PROCESSOR_ARCHITECTURE', '') + if not processor: + processor = os.environ.get('PROCESSOR_IDENTIFIER', machine) + + # Try the 'ver' system command available on some + # platforms + if use_syscmd_ver: + system,release,version = _syscmd_ver(system) + # Normalize system to what win32_ver() normally returns + # (_syscmd_ver() tends to return the vendor name as well) + if system == 'Microsoft Windows': + system = 'Windows' + elif system == 'Microsoft' and release == 'Windows': + # Under Windows Vista and Windows Server 2008, + # Microsoft changed the output of the ver command. The + # release is no longer printed. This causes the + # system and release to be misidentified. + system = 'Windows' + if '6.0' == version[:3]: + release = 'Vista' + else: + release = '' + + # In case we still don't know anything useful, we'll try to + # help ourselves + if system in ('win32','win16'): + if not version: + if system == 'win32': + version = '32bit' + else: + version = '16bit' + system = 'Windows' + + elif system[:4] == 'java': + release,vendor,vminfo,osinfo = java_ver() + system = 'Java' + version = string.join(vminfo,', ') + if not version: + version = vendor + + elif os.name == 'mac': + release,(version,stage,nonrel),machine = mac_ver() + system = 'MacOS' + + # System specific extensions + if system == 'OpenVMS': + # OpenVMS seems to have release and version mixed up + if not release or release == '0': + release = version + version = '' + # Get processor information + try: + import vms_lib + except ImportError: + pass + else: + csid, cpu_number = vms_lib.getsyi('SYI$_CPU',0) + if (cpu_number >= 128): + processor = 'Alpha' + else: + processor = 'VAX' + if not processor: + # Get processor information from the uname system command + processor = _syscmd_uname('-p','') + + #If any unknowns still exist, replace them with ''s, which are more portable + if system == 'unknown': + system = '' + if node == 'unknown': + node = '' + if release == 'unknown': + release = '' + if version == 'unknown': + version = '' + if machine == 'unknown': + machine = '' + if processor == 'unknown': + processor = '' + + # normalize name + if system == 'Microsoft' and release == 'Windows': + system = 'Windows' + release = 'Vista' + + _uname_cache = system,node,release,version,machine,processor + return _uname_cache + +### Direct interfaces to some of the uname() return values + +def system(): + + """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'. + + An empty string is returned if the value cannot be determined. + + """ + return uname()[0] + +def node(): + + """ Returns the computer's network name (which may not be fully + qualified) + + An empty string is returned if the value cannot be determined. + + """ + return uname()[1] + +def release(): + + """ Returns the system's release, e.g. '2.2.0' or 'NT' + + An empty string is returned if the value cannot be determined. + + """ + return uname()[2] + +def version(): + + """ Returns the system's release version, e.g. '#3 on degas' + + An empty string is returned if the value cannot be determined. + + """ + return uname()[3] + +def machine(): + + """ Returns the machine type, e.g. 'i386' + + An empty string is returned if the value cannot be determined. + + """ + return uname()[4] + +def processor(): + + """ Returns the (true) processor name, e.g. 'amdk6' + + An empty string is returned if the value cannot be + determined. Note that many platforms do not provide this + information or simply return the same value as for machine(), + e.g. NetBSD does this. + + """ + return uname()[5] + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_sets.py b/src/engine/SCons/compat/_scons_sets.py new file mode 100644 index 0000000..12dbead --- /dev/null +++ b/src/engine/SCons/compat/_scons_sets.py @@ -0,0 +1,583 @@ +"""Classes to represent arbitrary sets (including sets of sets). + +This module implements sets using dictionaries whose values are +ignored. The usual operations (union, intersection, deletion, etc.) +are provided as both methods and operators. + +Important: sets are not sequences! While they support 'x in s', +'len(s)', and 'for x in s', none of those operations are unique for +sequences; for example, mappings support all three as well. The +characteristic operation for sequences is subscripting with small +integers: s[i], for i in range(len(s)). Sets don't support +subscripting at all. Also, sequences allow multiple occurrences and +their elements have a definite order; sets on the other hand don't +record multiple occurrences and don't remember the order of element +insertion (which is why they don't support s[i]). + +The following classes are provided: + +BaseSet -- All the operations common to both mutable and immutable + sets. This is an abstract class, not meant to be directly + instantiated. + +Set -- Mutable sets, subclass of BaseSet; not hashable. + +ImmutableSet -- Immutable sets, subclass of BaseSet; hashable. + An iterable argument is mandatory to create an ImmutableSet. + +_TemporarilyImmutableSet -- A wrapper around a Set, hashable, + giving the same hash value as the immutable set equivalent + would have. Do not use this class directly. + +Only hashable objects can be added to a Set. In particular, you cannot +really add a Set as an element to another Set; if you try, what is +actually added is an ImmutableSet built from it (it compares equal to +the one you tried adding). + +When you ask if `x in y' where x is a Set and y is a Set or +ImmutableSet, x is wrapped into a _TemporarilyImmutableSet z, and +what's tested is actually `z in y'. + +""" + +# Code history: +# +# - Greg V. Wilson wrote the first version, using a different approach +# to the mutable/immutable problem, and inheriting from dict. +# +# - Alex Martelli modified Greg's version to implement the current +# Set/ImmutableSet approach, and make the data an attribute. +# +# - Guido van Rossum rewrote much of the code, made some API changes, +# and cleaned up the docstrings. +# +# - Raymond Hettinger added a number of speedups and other +# improvements. + +from __future__ import generators +try: + from itertools import ifilter, ifilterfalse +except ImportError: + # Code to make the module run under Py2.2 + def ifilter(predicate, iterable): + if predicate is None: + def predicate(x): + return x + for x in iterable: + if predicate(x): + yield x + def ifilterfalse(predicate, iterable): + if predicate is None: + def predicate(x): + return x + for x in iterable: + if not predicate(x): + yield x + try: + True, False + except NameError: + True, False = (0==0, 0!=0) + +__all__ = ['BaseSet', 'Set', 'ImmutableSet'] + +class BaseSet(object): + """Common base class for mutable and immutable sets.""" + + __slots__ = ['_data'] + + # Constructor + + def __init__(self): + """This is an abstract class.""" + # Don't call this from a concrete subclass! + if self.__class__ is BaseSet: + raise TypeError, ("BaseSet is an abstract class. " + "Use Set or ImmutableSet.") + + # Standard protocols: __len__, __repr__, __str__, __iter__ + + def __len__(self): + """Return the number of elements of a set.""" + return len(self._data) + + def __repr__(self): + """Return string representation of a set. + + This looks like 'Set([])'. + """ + return self._repr() + + # __str__ is the same as __repr__ + __str__ = __repr__ + + def _repr(self, sorted=False): + elements = self._data.keys() + if sorted: + elements.sort() + return '%s(%r)' % (self.__class__.__name__, elements) + + def __iter__(self): + """Return an iterator over the elements or a set. + + This is the keys iterator for the underlying dict. + """ + return self._data.iterkeys() + + # Three-way comparison is not supported. However, because __eq__ is + # tried before __cmp__, if Set x == Set y, x.__eq__(y) returns True and + # then cmp(x, y) returns 0 (Python doesn't actually call __cmp__ in this + # case). + + def __cmp__(self, other): + raise TypeError, "can't compare sets using cmp()" + + # Equality comparisons using the underlying dicts. Mixed-type comparisons + # are allowed here, where Set == z for non-Set z always returns False, + # and Set != z always True. This allows expressions like "x in y" to + # give the expected result when y is a sequence of mixed types, not + # raising a pointless TypeError just because y contains a Set, or x is + # a Set and y contain's a non-set ("in" invokes only __eq__). + # Subtle: it would be nicer if __eq__ and __ne__ could return + # NotImplemented instead of True or False. Then the other comparand + # would get a chance to determine the result, and if the other comparand + # also returned NotImplemented then it would fall back to object address + # comparison (which would always return False for __eq__ and always + # True for __ne__). However, that doesn't work, because this type + # *also* implements __cmp__: if, e.g., __eq__ returns NotImplemented, + # Python tries __cmp__ next, and the __cmp__ here then raises TypeError. + + def __eq__(self, other): + if isinstance(other, BaseSet): + return self._data == other._data + else: + return False + + def __ne__(self, other): + if isinstance(other, BaseSet): + return self._data != other._data + else: + return True + + # Copying operations + + def copy(self): + """Return a shallow copy of a set.""" + result = self.__class__() + result._data.update(self._data) + return result + + __copy__ = copy # For the copy module + + def __deepcopy__(self, memo): + """Return a deep copy of a set; used by copy module.""" + # This pre-creates the result and inserts it in the memo + # early, in case the deep copy recurses into another reference + # to this same set. A set can't be an element of itself, but + # it can certainly contain an object that has a reference to + # itself. + from copy import deepcopy + result = self.__class__() + memo[id(self)] = result + data = result._data + value = True + for elt in self: + data[deepcopy(elt, memo)] = value + return result + + # Standard set operations: union, intersection, both differences. + # Each has an operator version (e.g. __or__, invoked with |) and a + # method version (e.g. union). + # Subtle: Each pair requires distinct code so that the outcome is + # correct when the type of other isn't suitable. For example, if + # we did "union = __or__" instead, then Set().union(3) would return + # NotImplemented instead of raising TypeError (albeit that *why* it + # raises TypeError as-is is also a bit subtle). + + def __or__(self, other): + """Return the union of two sets as a new set. + + (I.e. all elements that are in either set.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.union(other) + + def union(self, other): + """Return the union of two sets as a new set. + + (I.e. all elements that are in either set.) + """ + result = self.__class__(self) + result._update(other) + return result + + def __and__(self, other): + """Return the intersection of two sets as a new set. + + (I.e. all elements that are in both sets.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.intersection(other) + + def intersection(self, other): + """Return the intersection of two sets as a new set. + + (I.e. all elements that are in both sets.) + """ + if not isinstance(other, BaseSet): + other = Set(other) + if len(self) <= len(other): + little, big = self, other + else: + little, big = other, self + common = ifilter(big._data.has_key, little) + return self.__class__(common) + + def __xor__(self, other): + """Return the symmetric difference of two sets as a new set. + + (I.e. all elements that are in exactly one of the sets.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.symmetric_difference(other) + + def symmetric_difference(self, other): + """Return the symmetric difference of two sets as a new set. + + (I.e. all elements that are in exactly one of the sets.) + """ + result = self.__class__() + data = result._data + value = True + selfdata = self._data + try: + otherdata = other._data + except AttributeError: + otherdata = Set(other)._data + for elt in ifilterfalse(otherdata.has_key, selfdata): + data[elt] = value + for elt in ifilterfalse(selfdata.has_key, otherdata): + data[elt] = value + return result + + def __sub__(self, other): + """Return the difference of two sets as a new Set. + + (I.e. all elements that are in this set and not in the other.) + """ + if not isinstance(other, BaseSet): + return NotImplemented + return self.difference(other) + + def difference(self, other): + """Return the difference of two sets as a new Set. + + (I.e. all elements that are in this set and not in the other.) + """ + result = self.__class__() + data = result._data + try: + otherdata = other._data + except AttributeError: + otherdata = Set(other)._data + value = True + for elt in ifilterfalse(otherdata.has_key, self): + data[elt] = value + return result + + # Membership test + + def __contains__(self, element): + """Report whether an element is a member of a set. + + (Called in response to the expression `element in self'.) + """ + try: + return element in self._data + except TypeError: + transform = getattr(element, "__as_temporarily_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + return transform() in self._data + + # Subset and superset test + + def issubset(self, other): + """Report whether another set contains this set.""" + self._binary_sanity_check(other) + if len(self) > len(other): # Fast check for obvious cases + return False + for elt in ifilterfalse(other._data.has_key, self): + return False + return True + + def issuperset(self, other): + """Report whether this set contains another set.""" + self._binary_sanity_check(other) + if len(self) < len(other): # Fast check for obvious cases + return False + for elt in ifilterfalse(self._data.has_key, other): + return False + return True + + # Inequality comparisons using the is-subset relation. + __le__ = issubset + __ge__ = issuperset + + def __lt__(self, other): + self._binary_sanity_check(other) + return len(self) < len(other) and self.issubset(other) + + def __gt__(self, other): + self._binary_sanity_check(other) + return len(self) > len(other) and self.issuperset(other) + + # Assorted helpers + + def _binary_sanity_check(self, other): + # Check that the other argument to a binary operation is also + # a set, raising a TypeError otherwise. + if not isinstance(other, BaseSet): + raise TypeError, "Binary operation only permitted between sets" + + def _compute_hash(self): + # Calculate hash code for a set by xor'ing the hash codes of + # the elements. This ensures that the hash code does not depend + # on the order in which elements are added to the set. This is + # not called __hash__ because a BaseSet should not be hashable; + # only an ImmutableSet is hashable. + result = 0 + for elt in self: + result ^= hash(elt) + return result + + def _update(self, iterable): + # The main loop for update() and the subclass __init__() methods. + data = self._data + + # Use the fast update() method when a dictionary is available. + if isinstance(iterable, BaseSet): + data.update(iterable._data) + return + + value = True + + if type(iterable) in (list, tuple, xrange): + # Optimized: we know that __iter__() and next() can't + # raise TypeError, so we can move 'try:' out of the loop. + it = iter(iterable) + while True: + try: + for element in it: + data[element] = value + return + except TypeError: + transform = getattr(element, "__as_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + data[transform()] = value + else: + # Safe: only catch TypeError where intended + for element in iterable: + try: + data[element] = value + except TypeError: + transform = getattr(element, "__as_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + data[transform()] = value + + +class ImmutableSet(BaseSet): + """Immutable set class.""" + + __slots__ = ['_hashcode'] + + # BaseSet + hashing + + def __init__(self, iterable=None): + """Construct an immutable set from an optional iterable.""" + self._hashcode = None + self._data = {} + if iterable is not None: + self._update(iterable) + + def __hash__(self): + if self._hashcode is None: + self._hashcode = self._compute_hash() + return self._hashcode + + def __getstate__(self): + return self._data, self._hashcode + + def __setstate__(self, state): + self._data, self._hashcode = state + +class Set(BaseSet): + """ Mutable set class.""" + + __slots__ = [] + + # BaseSet + operations requiring mutability; no hashing + + def __init__(self, iterable=None): + """Construct a set from an optional iterable.""" + self._data = {} + if iterable is not None: + self._update(iterable) + + def __getstate__(self): + # getstate's results are ignored if it is not + return self._data, + + def __setstate__(self, data): + self._data, = data + + def __hash__(self): + """A Set cannot be hashed.""" + # We inherit object.__hash__, so we must deny this explicitly + raise TypeError, "Can't hash a Set, only an ImmutableSet." + + # In-place union, intersection, differences. + # Subtle: The xyz_update() functions deliberately return None, + # as do all mutating operations on built-in container types. + # The __xyz__ spellings have to return self, though. + + def __ior__(self, other): + """Update a set with the union of itself and another.""" + self._binary_sanity_check(other) + self._data.update(other._data) + return self + + def union_update(self, other): + """Update a set with the union of itself and another.""" + self._update(other) + + def __iand__(self, other): + """Update a set with the intersection of itself and another.""" + self._binary_sanity_check(other) + self._data = (self & other)._data + return self + + def intersection_update(self, other): + """Update a set with the intersection of itself and another.""" + if isinstance(other, BaseSet): + self &= other + else: + self._data = (self.intersection(other))._data + + def __ixor__(self, other): + """Update a set with the symmetric difference of itself and another.""" + self._binary_sanity_check(other) + self.symmetric_difference_update(other) + return self + + def symmetric_difference_update(self, other): + """Update a set with the symmetric difference of itself and another.""" + data = self._data + value = True + if not isinstance(other, BaseSet): + other = Set(other) + if self is other: + self.clear() + for elt in other: + if elt in data: + del data[elt] + else: + data[elt] = value + + def __isub__(self, other): + """Remove all elements of another set from this set.""" + self._binary_sanity_check(other) + self.difference_update(other) + return self + + def difference_update(self, other): + """Remove all elements of another set from this set.""" + data = self._data + if not isinstance(other, BaseSet): + other = Set(other) + if self is other: + self.clear() + for elt in ifilter(data.has_key, other): + del data[elt] + + # Python dict-like mass mutations: update, clear + + def update(self, iterable): + """Add all values from an iterable (such as a list or file).""" + self._update(iterable) + + def clear(self): + """Remove all elements from this set.""" + self._data.clear() + + # Single-element mutations: add, remove, discard + + def add(self, element): + """Add an element to a set. + + This has no effect if the element is already present. + """ + try: + self._data[element] = True + except TypeError: + transform = getattr(element, "__as_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + self._data[transform()] = True + + def remove(self, element): + """Remove an element from a set; it must be a member. + + If the element is not a member, raise a KeyError. + """ + try: + del self._data[element] + except TypeError: + transform = getattr(element, "__as_temporarily_immutable__", None) + if transform is None: + raise # re-raise the TypeError exception we caught + del self._data[transform()] + + def discard(self, element): + """Remove an element from a set if it is a member. + + If the element is not a member, do nothing. + """ + try: + self.remove(element) + except KeyError: + pass + + def pop(self): + """Remove and return an arbitrary set element.""" + return self._data.popitem()[0] + + def __as_immutable__(self): + # Return a copy of self as an immutable set + return ImmutableSet(self) + + def __as_temporarily_immutable__(self): + # Return self wrapped in a temporarily immutable set + return _TemporarilyImmutableSet(self) + + +class _TemporarilyImmutableSet(BaseSet): + # Wrap a mutable set as if it was temporarily immutable. + # This only supplies hashing and equality comparisons. + + def __init__(self, set): + self._set = set + self._data = set._data # Needed by ImmutableSet.__eq__() + + def __hash__(self): + return self._set._compute_hash() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_sets15.py b/src/engine/SCons/compat/_scons_sets15.py new file mode 100644 index 0000000..bafa009 --- /dev/null +++ b/src/engine/SCons/compat/_scons_sets15.py @@ -0,0 +1,176 @@ +# +# A Set class that works all the way back to Python 1.5. From: +# +# Python Cookbook: Yet another Set class for Python +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/106469 +# Goncalo Rodriques +# +# This is a pure Pythonic implementation of a set class. The syntax +# and methods implemented are, for the most part, borrowed from +# PEP 218 by Greg Wilson. +# +# Note that this class violates the formal definition of a set() by adding +# a __getitem__() method so we can iterate over a set's elements under +# Python 1.5 and 2.1, which don't support __iter__() and iterator types. +# + +import string + +class Set: + """The set class. It can contain mutable objects.""" + + def __init__(self, seq = None): + """The constructor. It can take any object giving an iterator as an optional + argument to populate the new set.""" + self.elems = [] + if seq: + for elem in seq: + if elem not in self.elems: + hash(elem) + self.elems.append(elem) + + def __str__(self): + return "set([%s])" % string.join(map(str, self.elems), ", ") + + + def copy(self): + """Shallow copy of a set object.""" + return Set(self.elems) + + def __contains__(self, elem): + return elem in self.elems + + def __len__(self): + return len(self.elems) + + def __getitem__(self, index): + # Added so that Python 1.5 can iterate over the elements. + # The cookbook recipe's author didn't like this because there + # really isn't any order in a set object, but this is necessary + # to make the class work well enough for our purposes. + return self.elems[index] + + def items(self): + """Returns a list of the elements in the set.""" + return self.elems + + def add(self, elem): + """Add one element to the set.""" + if elem not in self.elems: + hash(elem) + self.elems.append(elem) + + def remove(self, elem): + """Remove an element from the set. Return an error if elem is not in the set.""" + try: + self.elems.remove(elem) + except ValueError: + raise LookupError, "Object %s is not a member of the set." % str(elem) + + def discard(self, elem): + """Remove an element from the set. Do nothing if elem is not in the set.""" + try: + self.elems.remove(elem) + except ValueError: + pass + + def sort(self, func=cmp): + self.elems.sort(func) + + #Define an iterator for a set. + def __iter__(self): + return iter(self.elems) + + #The basic binary operations with sets. + def __or__(self, other): + """Union of two sets.""" + ret = self.copy() + for elem in other.elems: + if elem not in ret: + ret.elems.append(elem) + return ret + + def __sub__(self, other): + """Difference of two sets.""" + ret = self.copy() + for elem in other.elems: + ret.discard(elem) + return ret + + def __and__(self, other): + """Intersection of two sets.""" + ret = Set() + for elem in self.elems: + if elem in other.elems: + ret.elems.append(elem) + return ret + + def __add__(self, other): + """Symmetric difference of two sets.""" + ret = Set() + temp = other.copy() + for elem in self.elems: + if elem in temp.elems: + temp.elems.remove(elem) + else: + ret.elems.append(elem) + #Add remaining elements. + for elem in temp.elems: + ret.elems.append(elem) + return ret + + def __mul__(self, other): + """Cartesian product of two sets.""" + ret = Set() + for elemself in self.elems: + x = map(lambda other, s=elemself: (s, other), other.elems) + ret.elems.extend(x) + return ret + + #Some of the binary comparisons. + def __lt__(self, other): + """Returns 1 if the lhs set is contained but not equal to the rhs set.""" + if len(self.elems) < len(other.elems): + temp = other.copy() + for elem in self.elems: + if elem in temp.elems: + temp.remove(elem) + else: + return 0 + return len(temp.elems) == 0 + else: + return 0 + + def __le__(self, other): + """Returns 1 if the lhs set is contained in the rhs set.""" + if len(self.elems) <= len(other.elems): + ret = 1 + for elem in self.elems: + if elem not in other.elems: + ret = 0 + break + return ret + else: + return 0 + + def __eq__(self, other): + """Returns 1 if the sets are equal.""" + if len(self.elems) != len(other.elems): + return 0 + else: + return len(self - other) == 0 + + def __cmp__(self, other): + """Returns 1 if the sets are equal.""" + if self.__lt__(other): + return -1 + elif other.__lt__(self): + return 1 + else: + return 0 + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_shlex.py b/src/engine/SCons/compat/_scons_shlex.py new file mode 100644 index 0000000..9e30a01 --- /dev/null +++ b/src/engine/SCons/compat/_scons_shlex.py @@ -0,0 +1,325 @@ +# -*- coding: iso-8859-1 -*- +"""A lexical analyzer class for simple shell-like syntaxes.""" + +# Module and documentation by Eric S. Raymond, 21 Dec 1998 +# Input stacking and error message cleanup added by ESR, March 2000 +# push_source() and pop_source() made explicit by ESR, January 2001. +# Posix compliance, split(), string arguments, and +# iterator interface by Gustavo Niemeyer, April 2003. + +import os.path +import sys +#from collections import deque + +class deque: + def __init__(self): + self.data = [] + def __len__(self): + return len(self.data) + def appendleft(self, item): + self.data.insert(0, item) + def popleft(self): + return self.data.pop(0) + +try: + basestring +except NameError: + import types + def is_basestring(s): + return type(s) is types.StringType +else: + def is_basestring(s): + return isinstance(s, basestring) + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +__all__ = ["shlex", "split"] + +class shlex: + "A lexical analyzer class for simple shell-like syntaxes." + def __init__(self, instream=None, infile=None, posix=False): + if is_basestring(instream): + instream = StringIO(instream) + if instream is not None: + self.instream = instream + self.infile = infile + else: + self.instream = sys.stdin + self.infile = None + self.posix = posix + if posix: + self.eof = None + else: + self.eof = '' + self.commenters = '#' + self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_') + if self.posix: + self.wordchars = self.wordchars + ('ßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ' + 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞ') + self.whitespace = ' \t\r\n' + self.whitespace_split = False + self.quotes = '\'"' + self.escape = '\\' + self.escapedquotes = '"' + self.state = ' ' + self.pushback = deque() + self.lineno = 1 + self.debug = 0 + self.token = '' + self.filestack = deque() + self.source = None + if self.debug: + print 'shlex: reading from %s, line %d' \ + % (self.instream, self.lineno) + + def push_token(self, tok): + "Push a token onto the stack popped by the get_token method" + if self.debug >= 1: + print "shlex: pushing token " + repr(tok) + self.pushback.appendleft(tok) + + def push_source(self, newstream, newfile=None): + "Push an input source onto the lexer's input source stack." + if is_basestring(newstream): + newstream = StringIO(newstream) + self.filestack.appendleft((self.infile, self.instream, self.lineno)) + self.infile = newfile + self.instream = newstream + self.lineno = 1 + if self.debug: + if newfile is not None: + print 'shlex: pushing to file %s' % (self.infile,) + else: + print 'shlex: pushing to stream %s' % (self.instream,) + + def pop_source(self): + "Pop the input source stack." + self.instream.close() + (self.infile, self.instream, self.lineno) = self.filestack.popleft() + if self.debug: + print 'shlex: popping to %s, line %d' \ + % (self.instream, self.lineno) + self.state = ' ' + + def get_token(self): + "Get a token from the input stream (or from stack if it's nonempty)" + if self.pushback: + tok = self.pushback.popleft() + if self.debug >= 1: + print "shlex: popping token " + repr(tok) + return tok + # No pushback. Get a token. + raw = self.read_token() + # Handle inclusions + if self.source is not None: + while raw == self.source: + spec = self.sourcehook(self.read_token()) + if spec: + (newfile, newstream) = spec + self.push_source(newstream, newfile) + raw = self.get_token() + # Maybe we got EOF instead? + while raw == self.eof: + if not self.filestack: + return self.eof + else: + self.pop_source() + raw = self.get_token() + # Neither inclusion nor EOF + if self.debug >= 1: + if raw != self.eof: + print "shlex: token=" + repr(raw) + else: + print "shlex: token=EOF" + return raw + + def read_token(self): + quoted = False + escapedstate = ' ' + while True: + nextchar = self.instream.read(1) + if nextchar == '\n': + self.lineno = self.lineno + 1 + if self.debug >= 3: + print "shlex: in state", repr(self.state), \ + "I see character:", repr(nextchar) + if self.state is None: + self.token = '' # past end of file + break + elif self.state == ' ': + if not nextchar: + self.state = None # end of file + break + elif nextchar in self.whitespace: + if self.debug >= 2: + print "shlex: I see whitespace in whitespace state" + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif nextchar in self.commenters: + self.instream.readline() + self.lineno = self.lineno + 1 + elif self.posix and nextchar in self.escape: + escapedstate = 'a' + self.state = nextchar + elif nextchar in self.wordchars: + self.token = nextchar + self.state = 'a' + elif nextchar in self.quotes: + if not self.posix: + self.token = nextchar + self.state = nextchar + elif self.whitespace_split: + self.token = nextchar + self.state = 'a' + else: + self.token = nextchar + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif self.state in self.quotes: + quoted = True + if not nextchar: # end of file + if self.debug >= 2: + print "shlex: I see EOF in quotes state" + # XXX what error should be raised here? + raise ValueError, "No closing quotation" + if nextchar == self.state: + if not self.posix: + self.token = self.token + nextchar + self.state = ' ' + break + else: + self.state = 'a' + elif self.posix and nextchar in self.escape and \ + self.state in self.escapedquotes: + escapedstate = self.state + self.state = nextchar + else: + self.token = self.token + nextchar + elif self.state in self.escape: + if not nextchar: # end of file + if self.debug >= 2: + print "shlex: I see EOF in escape state" + # XXX what error should be raised here? + raise ValueError, "No escaped character" + # In posix shells, only the quote itself or the escape + # character may be escaped within quotes. + if escapedstate in self.quotes and \ + nextchar != self.state and nextchar != escapedstate: + self.token = self.token + self.state + self.token = self.token + nextchar + self.state = escapedstate + elif self.state == 'a': + if not nextchar: + self.state = None # end of file + break + elif nextchar in self.whitespace: + if self.debug >= 2: + print "shlex: I see whitespace in word state" + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif nextchar in self.commenters: + self.instream.readline() + self.lineno = self.lineno + 1 + if self.posix: + self.state = ' ' + if self.token or (self.posix and quoted): + break # emit current token + else: + continue + elif self.posix and nextchar in self.quotes: + self.state = nextchar + elif self.posix and nextchar in self.escape: + escapedstate = 'a' + self.state = nextchar + elif nextchar in self.wordchars or nextchar in self.quotes \ + or self.whitespace_split: + self.token = self.token + nextchar + else: + self.pushback.appendleft(nextchar) + if self.debug >= 2: + print "shlex: I see punctuation in word state" + self.state = ' ' + if self.token: + break # emit current token + else: + continue + result = self.token + self.token = '' + if self.posix and not quoted and result == '': + result = None + if self.debug > 1: + if result: + print "shlex: raw token=" + repr(result) + else: + print "shlex: raw token=EOF" + return result + + def sourcehook(self, newfile): + "Hook called on a filename to be sourced." + if newfile[0] == '"': + newfile = newfile[1:-1] + # This implements cpp-like semantics for relative-path inclusion. + if is_basestring(self.infile) and not os.path.isabs(newfile): + newfile = os.path.join(os.path.dirname(self.infile), newfile) + return (newfile, open(newfile, "r")) + + def error_leader(self, infile=None, lineno=None): + "Emit a C-compiler-like, Emacs-friendly error-message leader." + if infile is None: + infile = self.infile + if lineno is None: + lineno = self.lineno + return "\"%s\", line %d: " % (infile, lineno) + + def __iter__(self): + return self + + def next(self): + token = self.get_token() + if token == self.eof: + raise StopIteration + return token + +def split(s, comments=False): + lex = shlex(s, posix=True) + lex.whitespace_split = True + if not comments: + lex.commenters = '' + #return list(lex) + result = [] + while True: + token = lex.get_token() + if token == lex.eof: + break + result.append(token) + return result + +if __name__ == '__main__': + if len(sys.argv) == 1: + lexer = shlex() + else: + file = sys.argv[1] + lexer = shlex(open(file), file) + while 1: + tt = lexer.get_token() + if tt: + print "Token: " + repr(tt) + else: + break + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_subprocess.py b/src/engine/SCons/compat/_scons_subprocess.py new file mode 100644 index 0000000..4968825 --- /dev/null +++ b/src/engine/SCons/compat/_scons_subprocess.py @@ -0,0 +1,1296 @@ +# subprocess - Subprocesses with accessible I/O streams +# +# For more information about this module, see PEP 324. +# +# This module should remain compatible with Python 2.2, see PEP 291. +# +# Copyright (c) 2003-2005 by Peter Astrand +# +# Licensed to PSF under a Contributor Agreement. +# See http://www.python.org/2.4/license for licensing details. + +r"""subprocess - Subprocesses with accessible I/O streams + +This module allows you to spawn processes, connect to their +input/output/error pipes, and obtain their return codes. This module +intends to replace several other, older modules and functions, like: + +os.system +os.spawn* +os.popen* +popen2.* +commands.* + +Information about how the subprocess module can be used to replace these +modules and functions can be found below. + + + +Using the subprocess module +=========================== +This module defines one class called Popen: + +class Popen(args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + + +Arguments are: + +args should be a string, or a sequence of program arguments. The +program to execute is normally the first item in the args sequence or +string, but can be explicitly set by using the executable argument. + +On UNIX, with shell=False (default): In this case, the Popen class +uses os.execvp() to execute the child program. args should normally +be a sequence. A string will be treated as a sequence with the string +as the only item (the program to execute). + +On UNIX, with shell=True: If args is a string, it specifies the +command string to execute through the shell. If args is a sequence, +the first item specifies the command string, and any additional items +will be treated as additional shell arguments. + +On Windows: the Popen class uses CreateProcess() to execute the child +program, which operates on strings. If args is a sequence, it will be +converted to a string using the list2cmdline method. Please note that +not all MS Windows applications interpret the command line the same +way: The list2cmdline is designed for applications using the same +rules as the MS C runtime. + +bufsize, if given, has the same meaning as the corresponding argument +to the built-in open() function: 0 means unbuffered, 1 means line +buffered, any other positive value means use a buffer of +(approximately) that size. A negative bufsize means to use the system +default, which usually means fully buffered. The default value for +bufsize is 0 (unbuffered). + +stdin, stdout and stderr specify the executed programs' standard +input, standard output and standard error file handles, respectively. +Valid values are PIPE, an existing file descriptor (a positive +integer), an existing file object, and None. PIPE indicates that a +new pipe to the child should be created. With None, no redirection +will occur; the child's file handles will be inherited from the +parent. Additionally, stderr can be STDOUT, which indicates that the +stderr data from the applications should be captured into the same +file handle as for stdout. + +If preexec_fn is set to a callable object, this object will be called +in the child process just before the child is executed. + +If close_fds is true, all file descriptors except 0, 1 and 2 will be +closed before the child process is executed. + +if shell is true, the specified command will be executed through the +shell. + +If cwd is not None, the current directory will be changed to cwd +before the child is executed. + +If env is not None, it defines the environment variables for the new +process. + +If universal_newlines is true, the file objects stdout and stderr are +opened as a text files, but lines may be terminated by any of '\n', +the Unix end-of-line convention, '\r', the Macintosh convention or +'\r\n', the Windows convention. All of these external representations +are seen as '\n' by the Python program. Note: This feature is only +available if Python is built with universal newline support (the +default). Also, the newlines attribute of the file objects stdout, +stdin and stderr are not updated by the communicate() method. + +The startupinfo and creationflags, if given, will be passed to the +underlying CreateProcess() function. They can specify things such as +appearance of the main window and priority for the new process. +(Windows only) + + +This module also defines two shortcut functions: + +call(*popenargs, **kwargs): + Run command with arguments. Wait for command to complete, then + return the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + retcode = call(["ls", "-l"]) + +check_call(*popenargs, **kwargs): + Run command with arguments. Wait for command to complete. If the + exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) + +Exceptions +---------- +Exceptions raised in the child process, before the new program has +started to execute, will be re-raised in the parent. Additionally, +the exception object will have one extra attribute called +'child_traceback', which is a string containing traceback information +from the childs point of view. + +The most common exception raised is OSError. This occurs, for +example, when trying to execute a non-existent file. Applications +should prepare for OSErrors. + +A ValueError will be raised if Popen is called with invalid arguments. + +check_call() will raise CalledProcessError, if the called process +returns a non-zero return code. + + +Security +-------- +Unlike some other popen functions, this implementation will never call +/bin/sh implicitly. This means that all characters, including shell +metacharacters, can safely be passed to child processes. + + +Popen objects +============= +Instances of the Popen class have the following methods: + +poll() + Check if child process has terminated. Returns returncode + attribute. + +wait() + Wait for child process to terminate. Returns returncode attribute. + +communicate(input=None) + Interact with process: Send data to stdin. Read data from stdout + and stderr, until end-of-file is reached. Wait for process to + terminate. The optional stdin argument should be a string to be + sent to the child process, or None, if no data should be sent to + the child. + + communicate() returns a tuple (stdout, stderr). + + Note: The data read is buffered in memory, so do not use this + method if the data size is large or unlimited. + +The following attributes are also available: + +stdin + If the stdin argument is PIPE, this attribute is a file object + that provides input to the child process. Otherwise, it is None. + +stdout + If the stdout argument is PIPE, this attribute is a file object + that provides output from the child process. Otherwise, it is + None. + +stderr + If the stderr argument is PIPE, this attribute is file object that + provides error output from the child process. Otherwise, it is + None. + +pid + The process ID of the child process. + +returncode + The child return code. A None value indicates that the process + hasn't terminated yet. A negative value -N indicates that the + child was terminated by signal N (UNIX only). + + +Replacing older functions with the subprocess module +==================================================== +In this section, "a ==> b" means that b can be used as a replacement +for a. + +Note: All functions in this section fail (more or less) silently if +the executed program cannot be found; this module raises an OSError +exception. + +In the following examples, we assume that the subprocess module is +imported with "from subprocess import *". + + +Replacing /bin/sh shell backquote +--------------------------------- +output=`mycmd myarg` +==> +output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0] + + +Replacing shell pipe line +------------------------- +output=`dmesg | grep hda` +==> +p1 = Popen(["dmesg"], stdout=PIPE) +p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) +output = p2.communicate()[0] + + +Replacing os.system() +--------------------- +sts = os.system("mycmd" + " myarg") +==> +p = Popen("mycmd" + " myarg", shell=True) +pid, sts = os.waitpid(p.pid, 0) + +Note: + +* Calling the program through the shell is usually not required. + +* It's easier to look at the returncode attribute than the + exitstatus. + +A more real-world example would look like this: + +try: + retcode = call("mycmd" + " myarg", shell=True) + if retcode < 0: + print >>sys.stderr, "Child was terminated by signal", -retcode + else: + print >>sys.stderr, "Child returned", retcode +except OSError, e: + print >>sys.stderr, "Execution failed:", e + + +Replacing os.spawn* +------------------- +P_NOWAIT example: + +pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg") +==> +pid = Popen(["/bin/mycmd", "myarg"]).pid + + +P_WAIT example: + +retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg") +==> +retcode = call(["/bin/mycmd", "myarg"]) + + +Vector example: + +os.spawnvp(os.P_NOWAIT, path, args) +==> +Popen([path] + args[1:]) + + +Environment example: + +os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env) +==> +Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"}) + + +Replacing os.popen* +------------------- +pipe = os.popen(cmd, mode='r', bufsize) +==> +pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout + +pipe = os.popen(cmd, mode='w', bufsize) +==> +pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin + + +(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize) +==> +p = Popen(cmd, shell=True, bufsize=bufsize, + stdin=PIPE, stdout=PIPE, close_fds=True) +(child_stdin, child_stdout) = (p.stdin, p.stdout) + + +(child_stdin, + child_stdout, + child_stderr) = os.popen3(cmd, mode, bufsize) +==> +p = Popen(cmd, shell=True, bufsize=bufsize, + stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True) +(child_stdin, + child_stdout, + child_stderr) = (p.stdin, p.stdout, p.stderr) + + +(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize) +==> +p = Popen(cmd, shell=True, bufsize=bufsize, + stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True) +(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout) + + +Replacing popen2.* +------------------ +Note: If the cmd argument to popen2 functions is a string, the command +is executed through /bin/sh. If it is a list, the command is directly +executed. + +(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode) +==> +p = Popen(["somestring"], shell=True, bufsize=bufsize + stdin=PIPE, stdout=PIPE, close_fds=True) +(child_stdout, child_stdin) = (p.stdout, p.stdin) + + +(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode) +==> +p = Popen(["mycmd", "myarg"], bufsize=bufsize, + stdin=PIPE, stdout=PIPE, close_fds=True) +(child_stdout, child_stdin) = (p.stdout, p.stdin) + +The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen, +except that: + +* subprocess.Popen raises an exception if the execution fails +* the capturestderr argument is replaced with the stderr argument. +* stdin=PIPE and stdout=PIPE must be specified. +* popen2 closes all filedescriptors by default, but you have to specify + close_fds=True with subprocess.Popen. + + +""" + +import sys +mswindows = (sys.platform == "win32") + +import os +import string +import types +import traceback + +# Exception classes used by this module. +class CalledProcessError(Exception): + """This exception is raised when a process run by check_call() returns + a non-zero exit status. The exit status will be stored in the + returncode attribute.""" + def __init__(self, returncode, cmd): + self.returncode = returncode + self.cmd = cmd + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + + +if mswindows: + try: + import threading + except ImportError: + # SCons: the threading module is only used by the communicate() + # method, which we don't actually use, so don't worry if we + # can't import it. + pass + import msvcrt + if 0: # <-- change this to use pywin32 instead of the _subprocess driver + import pywintypes + from win32api import GetStdHandle, STD_INPUT_HANDLE, \ + STD_OUTPUT_HANDLE, STD_ERROR_HANDLE + from win32api import GetCurrentProcess, DuplicateHandle, \ + GetModuleFileName, GetVersion + from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE + from win32pipe import CreatePipe + from win32process import CreateProcess, STARTUPINFO, \ + GetExitCodeProcess, STARTF_USESTDHANDLES, \ + STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE + from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0 + else: + # SCons: don't die on Python versions that don't have _subprocess. + try: + from _subprocess import * + except ImportError: + pass + class STARTUPINFO: + dwFlags = 0 + hStdInput = None + hStdOutput = None + hStdError = None + wShowWindow = 0 + class pywintypes: + error = IOError +else: + import select + import errno + import fcntl + import pickle + + try: + fcntl.F_GETFD + except AttributeError: + fcntl.F_GETFD = 1 + + try: + fcntl.F_SETFD + except AttributeError: + fcntl.F_SETFD = 2 + +__all__ = ["Popen", "PIPE", "STDOUT", "call", "check_call", "CalledProcessError"] + +try: + MAXFD = os.sysconf("SC_OPEN_MAX") +except KeyboardInterrupt: + raise # SCons: don't swallow keyboard interrupts +except: + MAXFD = 256 + +# True/False does not exist on 2.2.0 +try: + False +except NameError: + False = 0 + True = 1 + +try: + isinstance(1, int) +except TypeError: + def is_int(obj): + return type(obj) == type(1) + def is_int_or_long(obj): + return type(obj) in (type(1), type(1L)) +else: + def is_int(obj): + return isinstance(obj, int) + def is_int_or_long(obj): + return isinstance(obj, (int, long)) + +try: + types.StringTypes +except AttributeError: + try: + types.StringTypes = (types.StringType, types.UnicodeType) + except AttributeError: + types.StringTypes = (types.StringType,) + def is_string(obj): + return type(obj) in types.StringTypes +else: + def is_string(obj): + return isinstance(obj, types.StringTypes) + +_active = [] + +def _cleanup(): + for inst in _active[:]: + if inst.poll(_deadstate=sys.maxint) >= 0: + try: + _active.remove(inst) + except ValueError: + # This can happen if two threads create a new Popen instance. + # It's harmless that it was already removed, so ignore. + pass + +PIPE = -1 +STDOUT = -2 + + +def call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete, then + return the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + retcode = call(["ls", "-l"]) + """ + return apply(Popen, popenargs, kwargs).wait() + + +def check_call(*popenargs, **kwargs): + """Run command with arguments. Wait for command to complete. If + the exit code was zero then return, otherwise raise + CalledProcessError. The CalledProcessError object will have the + return code in the returncode attribute. + + The arguments are the same as for the Popen constructor. Example: + + check_call(["ls", "-l"]) + """ + retcode = apply(call, popenargs, kwargs) + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + if retcode: + raise CalledProcessError(retcode, cmd) + return retcode + + +def list2cmdline(seq): + """ + Translate a sequence of arguments into a command line + string, using the same rules as the MS C runtime: + + 1) Arguments are delimited by white space, which is either a + space or a tab. + + 2) A string surrounded by double quotation marks is + interpreted as a single argument, regardless of white space + contained within. A quoted string can be embedded in an + argument. + + 3) A double quotation mark preceded by a backslash is + interpreted as a literal double quotation mark. + + 4) Backslashes are interpreted literally, unless they + immediately precede a double quotation mark. + + 5) If backslashes immediately precede a double quotation mark, + every pair of backslashes is interpreted as a literal + backslash. If the number of backslashes is odd, the last + backslash escapes the next double quotation mark as + described in rule 3. + """ + + # See + # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp + result = [] + needquote = False + for arg in seq: + bs_buf = [] + + # Add a space to separate this argument from the others + if result: + result.append(' ') + + needquote = (" " in arg) or ("\t" in arg) + if needquote: + result.append('"') + + for c in arg: + if c == '\\': + # Don't know if we need to double yet. + bs_buf.append(c) + elif c == '"': + # Double backspaces. + result.append('\\' * len(bs_buf)*2) + bs_buf = [] + result.append('\\"') + else: + # Normal char + if bs_buf: + result.extend(bs_buf) + bs_buf = [] + result.append(c) + + # Add remaining backspaces, if any. + if bs_buf: + result.extend(bs_buf) + + if needquote: + result.extend(bs_buf) + result.append('"') + + return string.join(result, '') + + +try: + object +except NameError: + class object: + pass + +class Popen(object): + def __init__(self, args, bufsize=0, executable=None, + stdin=None, stdout=None, stderr=None, + preexec_fn=None, close_fds=False, shell=False, + cwd=None, env=None, universal_newlines=False, + startupinfo=None, creationflags=0): + """Create new Popen instance.""" + _cleanup() + + self._child_created = False + if not is_int_or_long(bufsize): + raise TypeError("bufsize must be an integer") + + if mswindows: + if preexec_fn is not None: + raise ValueError("preexec_fn is not supported on Windows " + "platforms") + if close_fds: + raise ValueError("close_fds is not supported on Windows " + "platforms") + else: + # POSIX + if startupinfo is not None: + raise ValueError("startupinfo is only supported on Windows " + "platforms") + if creationflags != 0: + raise ValueError("creationflags is only supported on Windows " + "platforms") + + self.stdin = None + self.stdout = None + self.stderr = None + self.pid = None + self.returncode = None + self.universal_newlines = universal_newlines + + # Input and output objects. The general principle is like + # this: + # + # Parent Child + # ------ ----- + # p2cwrite ---stdin---> p2cread + # c2pread <--stdout--- c2pwrite + # errread <--stderr--- errwrite + # + # On POSIX, the child objects are file descriptors. On + # Windows, these are Windows file handles. The parent objects + # are file descriptors on both platforms. The parent objects + # are None when not using PIPEs. The child objects are None + # when not redirecting. + + (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) = self._get_handles(stdin, stdout, stderr) + + self._execute_child(args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + if p2cwrite: + self.stdin = os.fdopen(p2cwrite, 'wb', bufsize) + if c2pread: + if universal_newlines: + self.stdout = os.fdopen(c2pread, 'rU', bufsize) + else: + self.stdout = os.fdopen(c2pread, 'rb', bufsize) + if errread: + if universal_newlines: + self.stderr = os.fdopen(errread, 'rU', bufsize) + else: + self.stderr = os.fdopen(errread, 'rb', bufsize) + + + def _translate_newlines(self, data): + data = data.replace("\r\n", "\n") + data = data.replace("\r", "\n") + return data + + + def __del__(self): + if not self._child_created: + # We didn't get to successfully create a child process. + return + # In case the child hasn't been waited on, check if it's done. + self.poll(_deadstate=sys.maxint) + if self.returncode is None and _active is not None: + # Child is still running, keep us alive until we can wait on it. + _active.append(self) + + + def communicate(self, input=None): + """Interact with process: Send data to stdin. Read data from + stdout and stderr, until end-of-file is reached. Wait for + process to terminate. The optional input argument should be a + string to be sent to the child process, or None, if no data + should be sent to the child. + + communicate() returns a tuple (stdout, stderr).""" + + # Optimization: If we are only using one pipe, or no pipe at + # all, using select() or threads is unnecessary. + if [self.stdin, self.stdout, self.stderr].count(None) >= 2: + stdout = None + stderr = None + if self.stdin: + if input: + self.stdin.write(input) + self.stdin.close() + elif self.stdout: + stdout = self.stdout.read() + elif self.stderr: + stderr = self.stderr.read() + self.wait() + return (stdout, stderr) + + return self._communicate(input) + + + if mswindows: + # + # Windows methods + # + def _get_handles(self, stdin, stdout, stderr): + """Construct and return tupel with IO objects: + p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite + """ + if stdin is None and stdout is None and stderr is None: + return (None, None, None, None, None, None) + + p2cread, p2cwrite = None, None + c2pread, c2pwrite = None, None + errread, errwrite = None, None + + if stdin is None: + p2cread = GetStdHandle(STD_INPUT_HANDLE) + elif stdin == PIPE: + p2cread, p2cwrite = CreatePipe(None, 0) + # Detach and turn into fd + p2cwrite = p2cwrite.Detach() + p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0) + elif is_int(stdin): + p2cread = msvcrt.get_osfhandle(stdin) + else: + # Assuming file-like object + p2cread = msvcrt.get_osfhandle(stdin.fileno()) + p2cread = self._make_inheritable(p2cread) + + if stdout is None: + c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE) + elif stdout == PIPE: + c2pread, c2pwrite = CreatePipe(None, 0) + # Detach and turn into fd + c2pread = c2pread.Detach() + c2pread = msvcrt.open_osfhandle(c2pread, 0) + elif is_int(stdout): + c2pwrite = msvcrt.get_osfhandle(stdout) + else: + # Assuming file-like object + c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) + c2pwrite = self._make_inheritable(c2pwrite) + + if stderr is None: + errwrite = GetStdHandle(STD_ERROR_HANDLE) + elif stderr == PIPE: + errread, errwrite = CreatePipe(None, 0) + # Detach and turn into fd + errread = errread.Detach() + errread = msvcrt.open_osfhandle(errread, 0) + elif stderr == STDOUT: + errwrite = c2pwrite + elif is_int(stderr): + errwrite = msvcrt.get_osfhandle(stderr) + else: + # Assuming file-like object + errwrite = msvcrt.get_osfhandle(stderr.fileno()) + errwrite = self._make_inheritable(errwrite) + + return (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + + def _make_inheritable(self, handle): + """Return a duplicate of handle, which is inheritable""" + return DuplicateHandle(GetCurrentProcess(), handle, + GetCurrentProcess(), 0, 1, + DUPLICATE_SAME_ACCESS) + + + def _find_w9xpopen(self): + """Find and return absolut path to w9xpopen.exe""" + w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)), + "w9xpopen.exe") + if not os.path.exists(w9xpopen): + # Eeek - file-not-found - possibly an embedding + # situation - see if we can locate it in sys.exec_prefix + w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix), + "w9xpopen.exe") + if not os.path.exists(w9xpopen): + raise RuntimeError("Cannot locate w9xpopen.exe, which is " + "needed for Popen to work with your " + "shell or platform.") + return w9xpopen + + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + """Execute program (MS Windows version)""" + + if not isinstance(args, types.StringTypes): + args = list2cmdline(args) + + # Process startup details + if startupinfo is None: + startupinfo = STARTUPINFO() + if None not in (p2cread, c2pwrite, errwrite): + startupinfo.dwFlags = startupinfo.dwFlags | STARTF_USESTDHANDLES + startupinfo.hStdInput = p2cread + startupinfo.hStdOutput = c2pwrite + startupinfo.hStdError = errwrite + + if shell: + startupinfo.dwFlags = startupinfo.dwFlags | STARTF_USESHOWWINDOW + startupinfo.wShowWindow = SW_HIDE + comspec = os.environ.get("COMSPEC", "cmd.exe") + args = comspec + " /c " + args + if (GetVersion() >= 0x80000000L or + os.path.basename(comspec).lower() == "command.com"): + # Win9x, or using command.com on NT. We need to + # use the w9xpopen intermediate program. For more + # information, see KB Q150956 + # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp) + w9xpopen = self._find_w9xpopen() + args = '"%s" %s' % (w9xpopen, args) + # Not passing CREATE_NEW_CONSOLE has been known to + # cause random failures on win9x. Specifically a + # dialog: "Your program accessed mem currently in + # use at xxx" and a hopeful warning about the + # stability of your system. Cost is Ctrl+C wont + # kill children. + creationflags = creationflags | CREATE_NEW_CONSOLE + + # Start the process + try: + hp, ht, pid, tid = CreateProcess(executable, args, + # no special security + None, None, + # must inherit handles to pass std + # handles + 1, + creationflags, + env, + cwd, + startupinfo) + except pywintypes.error, e: + # Translate pywintypes.error to WindowsError, which is + # a subclass of OSError. FIXME: We should really + # translate errno using _sys_errlist (or simliar), but + # how can this be done from Python? + raise apply(WindowsError, e.args) + + # Retain the process handle, but close the thread handle + self._child_created = True + self._handle = hp + self.pid = pid + ht.Close() + + # Child is launched. Close the parent's copy of those pipe + # handles that only the child should have open. You need + # to make sure that no handles to the write end of the + # output pipe are maintained in this process or else the + # pipe will not close when the child process exits and the + # ReadFile will hang. + if p2cread is not None: + p2cread.Close() + if c2pwrite is not None: + c2pwrite.Close() + if errwrite is not None: + errwrite.Close() + + + def poll(self, _deadstate=None): + """Check if child process has terminated. Returns returncode + attribute.""" + if self.returncode is None: + if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0: + self.returncode = GetExitCodeProcess(self._handle) + return self.returncode + + + def wait(self): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + obj = WaitForSingleObject(self._handle, INFINITE) + self.returncode = GetExitCodeProcess(self._handle) + return self.returncode + + + def _readerthread(self, fh, buffer): + buffer.append(fh.read()) + + + def _communicate(self, input): + stdout = None # Return + stderr = None # Return + + if self.stdout: + stdout = [] + stdout_thread = threading.Thread(target=self._readerthread, + args=(self.stdout, stdout)) + stdout_thread.setDaemon(True) + stdout_thread.start() + if self.stderr: + stderr = [] + stderr_thread = threading.Thread(target=self._readerthread, + args=(self.stderr, stderr)) + stderr_thread.setDaemon(True) + stderr_thread.start() + + if self.stdin: + if input is not None: + self.stdin.write(input) + self.stdin.close() + + if self.stdout: + stdout_thread.join() + if self.stderr: + stderr_thread.join() + + # All data exchanged. Translate lists into strings. + if stdout is not None: + stdout = stdout[0] + if stderr is not None: + stderr = stderr[0] + + # Translate newlines, if requested. We cannot let the file + # object do the translation: It is based on stdio, which is + # impossible to combine with select (unless forcing no + # buffering). + if self.universal_newlines and hasattr(file, 'newlines'): + if stdout: + stdout = self._translate_newlines(stdout) + if stderr: + stderr = self._translate_newlines(stderr) + + self.wait() + return (stdout, stderr) + + else: + # + # POSIX methods + # + def _get_handles(self, stdin, stdout, stderr): + """Construct and return tupel with IO objects: + p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite + """ + p2cread, p2cwrite = None, None + c2pread, c2pwrite = None, None + errread, errwrite = None, None + + if stdin is None: + pass + elif stdin == PIPE: + p2cread, p2cwrite = os.pipe() + elif is_int(stdin): + p2cread = stdin + else: + # Assuming file-like object + p2cread = stdin.fileno() + + if stdout is None: + pass + elif stdout == PIPE: + c2pread, c2pwrite = os.pipe() + elif is_int(stdout): + c2pwrite = stdout + else: + # Assuming file-like object + c2pwrite = stdout.fileno() + + if stderr is None: + pass + elif stderr == PIPE: + errread, errwrite = os.pipe() + elif stderr == STDOUT: + errwrite = c2pwrite + elif is_int(stderr): + errwrite = stderr + else: + # Assuming file-like object + errwrite = stderr.fileno() + + return (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) + + + def _set_cloexec_flag(self, fd): + try: + cloexec_flag = fcntl.FD_CLOEXEC + except AttributeError: + cloexec_flag = 1 + + old = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) + + + def _close_fds(self, but): + for i in xrange(3, MAXFD): + if i == but: + continue + try: + os.close(i) + except KeyboardInterrupt: + raise # SCons: don't swallow keyboard interrupts + except: + pass + + + def _execute_child(self, args, executable, preexec_fn, close_fds, + cwd, env, universal_newlines, + startupinfo, creationflags, shell, + p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite): + """Execute program (POSIX version)""" + + if is_string(args): + args = [args] + + if shell: + args = ["/bin/sh", "-c"] + args + + if executable is None: + executable = args[0] + + # For transferring possible exec failure from child to parent + # The first char specifies the exception type: 0 means + # OSError, 1 means some other error. + errpipe_read, errpipe_write = os.pipe() + self._set_cloexec_flag(errpipe_write) + + self.pid = os.fork() + self._child_created = True + if self.pid == 0: + # Child + try: + # Close parent's pipe ends + if p2cwrite: + os.close(p2cwrite) + if c2pread: + os.close(c2pread) + if errread: + os.close(errread) + os.close(errpipe_read) + + # Dup fds for child + if p2cread: + os.dup2(p2cread, 0) + if c2pwrite: + os.dup2(c2pwrite, 1) + if errwrite: + os.dup2(errwrite, 2) + + # Close pipe fds. Make sure we don't close the same + # fd more than once, or standard fds. + try: + set + except NameError: + # Fall-back for earlier Python versions, so epydoc + # can use this module directly to execute things. + if p2cread: + os.close(p2cread) + if c2pwrite and c2pwrite not in (p2cread,): + os.close(c2pwrite) + if errwrite and errwrite not in (p2cread, c2pwrite): + os.close(errwrite) + else: + for fd in set((p2cread, c2pwrite, errwrite))-set((0,1,2)): + if fd: os.close(fd) + + # Close all other fds, if asked for + if close_fds: + self._close_fds(but=errpipe_write) + + if cwd is not None: + os.chdir(cwd) + + if preexec_fn: + apply(preexec_fn) + + if env is None: + os.execvp(executable, args) + else: + os.execvpe(executable, args, env) + + except KeyboardInterrupt: + raise # SCons: don't swallow keyboard interrupts + + except: + exc_type, exc_value, tb = sys.exc_info() + # Save the traceback and attach it to the exception object + exc_lines = traceback.format_exception(exc_type, + exc_value, + tb) + exc_value.child_traceback = string.join(exc_lines, '') + os.write(errpipe_write, pickle.dumps(exc_value)) + + # This exitcode won't be reported to applications, so it + # really doesn't matter what we return. + os._exit(255) + + # Parent + os.close(errpipe_write) + if p2cread and p2cwrite: + os.close(p2cread) + if c2pwrite and c2pread: + os.close(c2pwrite) + if errwrite and errread: + os.close(errwrite) + + # Wait for exec to fail or succeed; possibly raising exception + data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB + os.close(errpipe_read) + if data != "": + os.waitpid(self.pid, 0) + child_exception = pickle.loads(data) + raise child_exception + + + def _handle_exitstatus(self, sts): + if os.WIFSIGNALED(sts): + self.returncode = -os.WTERMSIG(sts) + elif os.WIFEXITED(sts): + self.returncode = os.WEXITSTATUS(sts) + else: + # Should never happen + raise RuntimeError("Unknown child exit status!") + + + def poll(self, _deadstate=None): + """Check if child process has terminated. Returns returncode + attribute.""" + if self.returncode is None: + try: + pid, sts = os.waitpid(self.pid, os.WNOHANG) + if pid == self.pid: + self._handle_exitstatus(sts) + except os.error: + if _deadstate is not None: + self.returncode = _deadstate + return self.returncode + + + def wait(self): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + pid, sts = os.waitpid(self.pid, 0) + self._handle_exitstatus(sts) + return self.returncode + + + def _communicate(self, input): + read_set = [] + write_set = [] + stdout = None # Return + stderr = None # Return + + if self.stdin: + # Flush stdio buffer. This might block, if the user has + # been writing to .stdin in an uncontrolled fashion. + self.stdin.flush() + if input: + write_set.append(self.stdin) + else: + self.stdin.close() + if self.stdout: + read_set.append(self.stdout) + stdout = [] + if self.stderr: + read_set.append(self.stderr) + stderr = [] + + input_offset = 0 + while read_set or write_set: + rlist, wlist, xlist = select.select(read_set, write_set, []) + + if self.stdin in wlist: + # When select has indicated that the file is writable, + # we can write up to PIPE_BUF bytes without risk + # blocking. POSIX defines PIPE_BUF >= 512 + bytes_written = os.write(self.stdin.fileno(), buffer(input, input_offset, 512)) + input_offset = input_offset + bytes_written + if input_offset >= len(input): + self.stdin.close() + write_set.remove(self.stdin) + + if self.stdout in rlist: + data = os.read(self.stdout.fileno(), 1024) + if data == "": + self.stdout.close() + read_set.remove(self.stdout) + stdout.append(data) + + if self.stderr in rlist: + data = os.read(self.stderr.fileno(), 1024) + if data == "": + self.stderr.close() + read_set.remove(self.stderr) + stderr.append(data) + + # All data exchanged. Translate lists into strings. + if stdout is not None: + stdout = string.join(stdout, '') + if stderr is not None: + stderr = string.join(stderr, '') + + # Translate newlines, if requested. We cannot let the file + # object do the translation: It is based on stdio, which is + # impossible to combine with select (unless forcing no + # buffering). + if self.universal_newlines and hasattr(file, 'newlines'): + if stdout: + stdout = self._translate_newlines(stdout) + if stderr: + stderr = self._translate_newlines(stderr) + + self.wait() + return (stdout, stderr) + + +def _demo_posix(): + # + # Example 1: Simple redirection: Get process list + # + plist = Popen(["ps"], stdout=PIPE).communicate()[0] + print "Process list:" + print plist + + # + # Example 2: Change uid before executing child + # + if os.getuid() == 0: + p = Popen(["id"], preexec_fn=lambda: os.setuid(100)) + p.wait() + + # + # Example 3: Connecting several subprocesses + # + print "Looking for 'hda'..." + p1 = Popen(["dmesg"], stdout=PIPE) + p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE) + print repr(p2.communicate()[0]) + + # + # Example 4: Catch execution error + # + print + print "Trying a weird file..." + try: + print Popen(["/this/path/does/not/exist"]).communicate() + except OSError, e: + if e.errno == errno.ENOENT: + print "The file didn't exist. I thought so..." + print "Child traceback:" + print e.child_traceback + else: + print "Error", e.errno + else: + sys.stderr.write( "Gosh. No error.\n" ) + + +def _demo_windows(): + # + # Example 1: Connecting several subprocesses + # + print "Looking for 'PROMPT' in set output..." + p1 = Popen("set", stdout=PIPE, shell=True) + p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE) + print repr(p2.communicate()[0]) + + # + # Example 2: Simple execution of program + # + print "Executing calc..." + p = Popen("calc") + p.wait() + + +if __name__ == "__main__": + if mswindows: + _demo_windows() + else: + _demo_posix() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/_scons_textwrap.py b/src/engine/SCons/compat/_scons_textwrap.py new file mode 100644 index 0000000..81781af --- /dev/null +++ b/src/engine/SCons/compat/_scons_textwrap.py @@ -0,0 +1,382 @@ +"""Text wrapping and filling. +""" + +# Copyright (C) 1999-2001 Gregory P. Ward. +# Copyright (C) 2002, 2003 Python Software Foundation. +# Written by Greg Ward + +__revision__ = "$Id: textwrap.py,v 1.32.8.2 2004/05/13 01:48:15 gward Exp $" + +import string, re + +try: + unicode +except NameError: + class unicode: + pass + +# Do the right thing with boolean values for all known Python versions +# (so this module can be copied to projects that don't depend on Python +# 2.3, e.g. Optik and Docutils). +try: + True, False +except NameError: + (True, False) = (1, 0) + +__all__ = ['TextWrapper', 'wrap', 'fill'] + +# Hardcode the recognized whitespace characters to the US-ASCII +# whitespace characters. The main reason for doing this is that in +# ISO-8859-1, 0xa0 is non-breaking whitespace, so in certain locales +# that character winds up in string.whitespace. Respecting +# string.whitespace in those cases would 1) make textwrap treat 0xa0 the +# same as any other whitespace char, which is clearly wrong (it's a +# *non-breaking* space), 2) possibly cause problems with Unicode, +# since 0xa0 is not in range(128). +_whitespace = '\t\n\x0b\x0c\r ' + +class TextWrapper: + """ + Object for wrapping/filling text. The public interface consists of + the wrap() and fill() methods; the other methods are just there for + subclasses to override in order to tweak the default behaviour. + If you want to completely replace the main wrapping algorithm, + you'll probably have to override _wrap_chunks(). + + Several instance attributes control various aspects of wrapping: + width (default: 70) + the maximum width of wrapped lines (unless break_long_words + is false) + initial_indent (default: "") + string that will be prepended to the first line of wrapped + output. Counts towards the line's width. + subsequent_indent (default: "") + string that will be prepended to all lines save the first + of wrapped output; also counts towards each line's width. + expand_tabs (default: true) + Expand tabs in input text to spaces before further processing. + Each tab will become 1 .. 8 spaces, depending on its position in + its line. If false, each tab is treated as a single character. + replace_whitespace (default: true) + Replace all whitespace characters in the input text by spaces + after tab expansion. Note that if expand_tabs is false and + replace_whitespace is true, every tab will be converted to a + single space! + fix_sentence_endings (default: false) + Ensure that sentence-ending punctuation is always followed + by two spaces. Off by default because the algorithm is + (unavoidably) imperfect. + break_long_words (default: true) + Break words longer than 'width'. If false, those words will not + be broken, and some lines might be longer than 'width'. + """ + + whitespace_trans = string.maketrans(_whitespace, ' ' * len(_whitespace)) + + unicode_whitespace_trans = {} + try: + uspace = eval("ord(u' ')") + except SyntaxError: + # Python1.5 doesn't understand u'' syntax, in which case we + # won't actually use the unicode translation below, so it + # doesn't matter what value we put in the table. + uspace = ord(' ') + for x in map(ord, _whitespace): + unicode_whitespace_trans[x] = uspace + + # This funky little regex is just the trick for splitting + # text up into word-wrappable chunks. E.g. + # "Hello there -- you goof-ball, use the -b option!" + # splits into + # Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option! + # (after stripping out empty strings). + try: + wordsep_re = re.compile(r'(\s+|' # any whitespace + r'[^\s\w]*\w{2,}-(?=\w{2,})|' # hyphenated words + r'(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))') # em-dash + except re.error: + # Pre-2.0 Python versions don't have the (?<= negative look-behind + # assertion. It mostly doesn't matter for the simple input + # SCons is going to give it, so just leave it out. + wordsep_re = re.compile(r'(\s+|' # any whitespace + r'-*\w{2,}-(?=\w{2,}))') # hyphenated words + + # XXX will there be a locale-or-charset-aware version of + # string.lowercase in 2.3? + sentence_end_re = re.compile(r'[%s]' # lowercase letter + r'[\.\!\?]' # sentence-ending punct. + r'[\"\']?' # optional end-of-quote + % string.lowercase) + + + def __init__(self, + width=70, + initial_indent="", + subsequent_indent="", + expand_tabs=True, + replace_whitespace=True, + fix_sentence_endings=False, + break_long_words=True): + self.width = width + self.initial_indent = initial_indent + self.subsequent_indent = subsequent_indent + self.expand_tabs = expand_tabs + self.replace_whitespace = replace_whitespace + self.fix_sentence_endings = fix_sentence_endings + self.break_long_words = break_long_words + + + # -- Private methods ----------------------------------------------- + # (possibly useful for subclasses to override) + + def _munge_whitespace(self, text): + """_munge_whitespace(text : string) -> string + + Munge whitespace in text: expand tabs and convert all other + whitespace characters to spaces. Eg. " foo\tbar\n\nbaz" + becomes " foo bar baz". + """ + if self.expand_tabs: + text = string.expandtabs(text) + if self.replace_whitespace: + if type(text) == type(''): + text = string.translate(text, self.whitespace_trans) + elif isinstance(text, unicode): + text = string.translate(text, self.unicode_whitespace_trans) + return text + + + def _split(self, text): + """_split(text : string) -> [string] + + Split the text to wrap into indivisible chunks. Chunks are + not quite the same as words; see wrap_chunks() for full + details. As an example, the text + Look, goof-ball -- use the -b option! + breaks into the following chunks: + 'Look,', ' ', 'goof-', 'ball', ' ', '--', ' ', + 'use', ' ', 'the', ' ', '-b', ' ', 'option!' + """ + chunks = self.wordsep_re.split(text) + chunks = filter(None, chunks) + return chunks + + def _fix_sentence_endings(self, chunks): + """_fix_sentence_endings(chunks : [string]) + + Correct for sentence endings buried in 'chunks'. Eg. when the + original text contains "... foo.\nBar ...", munge_whitespace() + and split() will convert that to [..., "foo.", " ", "Bar", ...] + which has one too few spaces; this method simply changes the one + space to two. + """ + i = 0 + pat = self.sentence_end_re + while i < len(chunks)-1: + if chunks[i+1] == " " and pat.search(chunks[i]): + chunks[i+1] = " " + i = i + 2 + else: + i = i + 1 + + def _handle_long_word(self, chunks, cur_line, cur_len, width): + """_handle_long_word(chunks : [string], + cur_line : [string], + cur_len : int, width : int) + + Handle a chunk of text (most likely a word, not whitespace) that + is too long to fit in any line. + """ + space_left = max(width - cur_len, 1) + + # If we're allowed to break long words, then do so: put as much + # of the next chunk onto the current line as will fit. + if self.break_long_words: + cur_line.append(chunks[0][0:space_left]) + chunks[0] = chunks[0][space_left:] + + # Otherwise, we have to preserve the long word intact. Only add + # it to the current line if there's nothing already there -- + # that minimizes how much we violate the width constraint. + elif not cur_line: + cur_line.append(chunks.pop(0)) + + # If we're not allowed to break long words, and there's already + # text on the current line, do nothing. Next time through the + # main loop of _wrap_chunks(), we'll wind up here again, but + # cur_len will be zero, so the next line will be entirely + # devoted to the long word that we can't handle right now. + + def _wrap_chunks(self, chunks): + """_wrap_chunks(chunks : [string]) -> [string] + + Wrap a sequence of text chunks and return a list of lines of + length 'self.width' or less. (If 'break_long_words' is false, + some lines may be longer than this.) Chunks correspond roughly + to words and the whitespace between them: each chunk is + indivisible (modulo 'break_long_words'), but a line break can + come between any two chunks. Chunks should not have internal + whitespace; ie. a chunk is either all whitespace or a "word". + Whitespace chunks will be removed from the beginning and end of + lines, but apart from that whitespace is preserved. + """ + lines = [] + if self.width <= 0: + raise ValueError("invalid width %r (must be > 0)" % self.width) + + while chunks: + + # Start the list of chunks that will make up the current line. + # cur_len is just the length of all the chunks in cur_line. + cur_line = [] + cur_len = 0 + + # Figure out which static string will prefix this line. + if lines: + indent = self.subsequent_indent + else: + indent = self.initial_indent + + # Maximum width for this line. + width = self.width - len(indent) + + # First chunk on line is whitespace -- drop it, unless this + # is the very beginning of the text (ie. no lines started yet). + if string.strip(chunks[0]) == '' and lines: + del chunks[0] + + while chunks: + l = len(chunks[0]) + + # Can at least squeeze this chunk onto the current line. + if cur_len + l <= width: + cur_line.append(chunks.pop(0)) + cur_len = cur_len + l + + # Nope, this line is full. + else: + break + + # The current line is full, and the next chunk is too big to + # fit on *any* line (not just this one). + if chunks and len(chunks[0]) > width: + self._handle_long_word(chunks, cur_line, cur_len, width) + + # If the last chunk on this line is all whitespace, drop it. + if cur_line and string.strip(cur_line[-1]) == '': + del cur_line[-1] + + # Convert current line back to a string and store it in list + # of all lines (return value). + if cur_line: + lines.append(indent + string.join(cur_line, '')) + + return lines + + + # -- Public interface ---------------------------------------------- + + def wrap(self, text): + """wrap(text : string) -> [string] + + Reformat the single paragraph in 'text' so it fits in lines of + no more than 'self.width' columns, and return a list of wrapped + lines. Tabs in 'text' are expanded with string.expandtabs(), + and all other whitespace characters (including newline) are + converted to space. + """ + text = self._munge_whitespace(text) + indent = self.initial_indent + chunks = self._split(text) + if self.fix_sentence_endings: + self._fix_sentence_endings(chunks) + return self._wrap_chunks(chunks) + + def fill(self, text): + """fill(text : string) -> string + + Reformat the single paragraph in 'text' to fit in lines of no + more than 'self.width' columns, and return a new string + containing the entire wrapped paragraph. + """ + return string.join(self.wrap(text), "\n") + + +# -- Convenience interface --------------------------------------------- + +def wrap(text, width=70, **kwargs): + """Wrap a single paragraph of text, returning a list of wrapped lines. + + Reformat the single paragraph in 'text' so it fits in lines of no + more than 'width' columns, and return a list of wrapped lines. By + default, tabs in 'text' are expanded with string.expandtabs(), and + all other whitespace characters (including newline) are converted to + space. See TextWrapper class for available keyword args to customize + wrapping behaviour. + """ + kw = kwargs.copy() + kw['width'] = width + w = apply(TextWrapper, (), kw) + return w.wrap(text) + +def fill(text, width=70, **kwargs): + """Fill a single paragraph of text, returning a new string. + + Reformat the single paragraph in 'text' to fit in lines of no more + than 'width' columns, and return a new string containing the entire + wrapped paragraph. As with wrap(), tabs are expanded and other + whitespace characters converted to space. See TextWrapper class for + available keyword args to customize wrapping behaviour. + """ + kw = kwargs.copy() + kw['width'] = width + w = apply(TextWrapper, (), kw) + return w.fill(text) + + +# -- Loosely related functionality ------------------------------------- + +def dedent(text): + """dedent(text : string) -> string + + Remove any whitespace than can be uniformly removed from the left + of every line in `text`. + + This can be used e.g. to make triple-quoted strings line up with + the left edge of screen/whatever, while still presenting it in the + source code in indented form. + + For example: + + def test(): + # end first line with \ to avoid the empty line! + s = '''\ + hello + world + ''' + print repr(s) # prints ' hello\n world\n ' + print repr(dedent(s)) # prints 'hello\n world\n' + """ + lines = text.expandtabs().split('\n') + margin = None + for line in lines: + content = line.lstrip() + if not content: + continue + indent = len(line) - len(content) + if margin is None: + margin = indent + else: + margin = min(margin, indent) + + if margin is not None and margin > 0: + for i in range(len(lines)): + lines[i] = lines[i][margin:] + + return string.join(lines, '\n') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/compat/builtins.py b/src/engine/SCons/compat/builtins.py new file mode 100644 index 0000000..a79b2ad --- /dev/null +++ b/src/engine/SCons/compat/builtins.py @@ -0,0 +1,187 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +# Portions of the following are derived from the compat.py file in +# Twisted, under the following copyright: +# +# Copyright (c) 2001-2004 Twisted Matrix Laboratories + +__doc__ = """ +Compatibility idioms for __builtin__ names + +This module adds names to the __builtin__ module for things that we want +to use in SCons but which don't show up until later Python versions than +the earliest ones we support. + +This module checks for the following __builtin__ names: + + all() + any() + bool() + dict() + True + False + zip() + +Implementations of functions are *NOT* guaranteed to be fully compliant +with these functions in later versions of Python. We are only concerned +with adding functionality that we actually use in SCons, so be wary +if you lift this code for other uses. (That said, making these more +nearly the same as later, official versions is still a desirable goal, +we just don't need to be obsessive about it.) + +If you're looking at this with pydoc and various names don't show up in +the FUNCTIONS or DATA output, that means those names are already built in +to this version of Python and we don't need to add them from this module. +""" + +__revision__ = "src/engine/SCons/compat/builtins.py 4577 2009/12/27 19:44:43 scons" + +import __builtin__ + +try: + all +except NameError: + # Pre-2.5 Python has no all() function. + def all(iterable): + """ + Returns True if all elements of the iterable are true. + """ + for element in iterable: + if not element: + return False + return True + __builtin__.all = all + all = all + +try: + any +except NameError: + # Pre-2.5 Python has no any() function. + def any(iterable): + """ + Returns True if any element of the iterable is true. + """ + for element in iterable: + if element: + return True + return False + __builtin__.any = any + any = any + +try: + bool +except NameError: + # Pre-2.2 Python has no bool() function. + def bool(value): + """Demote a value to 0 or 1, depending on its truth value. + + This is not to be confused with types.BooleanType, which is + way too hard to duplicate in early Python versions to be + worth the trouble. + """ + return not not value + __builtin__.bool = bool + bool = bool + +try: + dict +except NameError: + # Pre-2.2 Python has no dict() keyword. + def dict(seq=[], **kwargs): + """ + New dictionary initialization. + """ + d = {} + for k, v in seq: + d[k] = v + d.update(kwargs) + return d + __builtin__.dict = dict + +try: + False +except NameError: + # Pre-2.2 Python has no False keyword. + __builtin__.False = not 1 + # Assign to False in this module namespace so it shows up in pydoc output. + False = False + +try: + True +except NameError: + # Pre-2.2 Python has no True keyword. + __builtin__.True = not 0 + # Assign to True in this module namespace so it shows up in pydoc output. + True = True + +try: + file +except NameError: + # Pre-2.2 Python has no file() function. + __builtin__.file = open + +# +try: + zip +except NameError: + # Pre-2.2 Python has no zip() function. + def zip(*lists): + """ + Emulates the behavior we need from the built-in zip() function + added in Python 2.2. + + Returns a list of tuples, where each tuple contains the i-th + element rom each of the argument sequences. The returned + list is truncated in length to the length of the shortest + argument sequence. + """ + result = [] + for i in xrange(min(map(len, lists))): + result.append(tuple(map(lambda l, i=i: l[i], lists))) + return result + __builtin__.zip = zip + + + +#if sys.version_info[:3] in ((2, 2, 0), (2, 2, 1)): +# def lstrip(s, c=string.whitespace): +# while s and s[0] in c: +# s = s[1:] +# return s +# def rstrip(s, c=string.whitespace): +# while s and s[-1] in c: +# s = s[:-1] +# return s +# def strip(s, c=string.whitespace, l=lstrip, r=rstrip): +# return l(r(s, c), c) +# +# object.__setattr__(str, 'lstrip', lstrip) +# object.__setattr__(str, 'rstrip', rstrip) +# object.__setattr__(str, 'strip', strip) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/cpp.py b/src/engine/SCons/cpp.py new file mode 100644 index 0000000..2a16d37 --- /dev/null +++ b/src/engine/SCons/cpp.py @@ -0,0 +1,598 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/cpp.py 4577 2009/12/27 19:44:43 scons" + +__doc__ = """ +SCons C Pre-Processor module +""" + +# TODO(1.5): remove this import +# This module doesn't use anything from SCons by name, but we import SCons +# here to pull in zip() from the SCons.compat layer for early Pythons. +import SCons + +import os +import re +import string + +# +# First "subsystem" of regular expressions that we set up: +# +# Stuff to turn the C preprocessor directives in a file's contents into +# a list of tuples that we can process easily. +# + +# A table of regular expressions that fetch the arguments from the rest of +# a C preprocessor line. Different directives have different arguments +# that we want to fetch, using the regular expressions to which the lists +# of preprocessor directives map. +cpp_lines_dict = { + # Fetch the rest of a #if/#elif/#ifdef/#ifndef as one argument, + # separated from the keyword by white space. + ('if', 'elif', 'ifdef', 'ifndef',) + : '\s+(.+)', + + # Fetch the rest of a #import/#include/#include_next line as one + # argument, with white space optional. + ('import', 'include', 'include_next',) + : '\s*(.+)', + + # We don't care what comes after a #else or #endif line. + ('else', 'endif',) : '', + + # Fetch three arguments from a #define line: + # 1) The #defined keyword. + # 2) The optional parentheses and arguments (if it's a function-like + # macro, '' if it's not). + # 3) The expansion value. + ('define',) : '\s+([_A-Za-z][_A-Za-z0-9_]*)(\([^)]*\))?\s*(.*)', + + # Fetch the #undefed keyword from a #undef line. + ('undef',) : '\s+([_A-Za-z][A-Za-z0-9_]*)', +} + +# Create a table that maps each individual C preprocessor directive to +# the corresponding compiled regular expression that fetches the arguments +# we care about. +Table = {} +for op_list, expr in cpp_lines_dict.items(): + e = re.compile(expr) + for op in op_list: + Table[op] = e +del e +del op +del op_list + +# Create a list of the expressions we'll use to match all of the +# preprocessor directives. These are the same as the directives +# themselves *except* that we must use a negative lookahead assertion +# when matching "if" so it doesn't match the "if" in "ifdef." +override = { + 'if' : 'if(?!def)', +} +l = map(lambda x, o=override: o.get(x, x), Table.keys()) + + +# Turn the list of expressions into one big honkin' regular expression +# that will match all the preprocessor lines at once. This will return +# a list of tuples, one for each preprocessor line. The preprocessor +# directive will be the first element in each tuple, and the rest of +# the line will be the second element. +e = '^\s*#\s*(' + string.join(l, '|') + ')(.*)$' + +# And last but not least, compile the expression. +CPP_Expression = re.compile(e, re.M) + + + + +# +# Second "subsystem" of regular expressions that we set up: +# +# Stuff to translate a C preprocessor expression (as found on a #if or +# #elif line) into an equivalent Python expression that we can eval(). +# + +# A dictionary that maps the C representation of Boolean operators +# to their Python equivalents. +CPP_to_Python_Ops_Dict = { + '!' : ' not ', + '!=' : ' != ', + '&&' : ' and ', + '||' : ' or ', + '?' : ' and ', + ':' : ' or ', + '\r' : '', +} + +CPP_to_Python_Ops_Sub = lambda m, d=CPP_to_Python_Ops_Dict: d[m.group(0)] + +# We have to sort the keys by length so that longer expressions +# come *before* shorter expressions--in particular, "!=" must +# come before "!" in the alternation. Without this, the Python +# re module, as late as version 2.2.2, empirically matches the +# "!" in "!=" first, instead of finding the longest match. +# What's up with that? +l = CPP_to_Python_Ops_Dict.keys() +l.sort(lambda a, b: cmp(len(b), len(a))) + +# Turn the list of keys into one regular expression that will allow us +# to substitute all of the operators at once. +expr = string.join(map(re.escape, l), '|') + +# ...and compile the expression. +CPP_to_Python_Ops_Expression = re.compile(expr) + +# A separate list of expressions to be evaluated and substituted +# sequentially, not all at once. +CPP_to_Python_Eval_List = [ + ['defined\s+(\w+)', '__dict__.has_key("\\1")'], + ['defined\s*\((\w+)\)', '__dict__.has_key("\\1")'], + ['/\*.*\*/', ''], + ['/\*.*', ''], + ['//.*', ''], + ['(0x[0-9A-Fa-f]*)[UL]+', '\\1L'], +] + +# Replace the string representations of the regular expressions in the +# list with compiled versions. +for l in CPP_to_Python_Eval_List: + l[0] = re.compile(l[0]) + +# Wrap up all of the above into a handy function. +def CPP_to_Python(s): + """ + Converts a C pre-processor expression into an equivalent + Python expression that can be evaluated. + """ + s = CPP_to_Python_Ops_Expression.sub(CPP_to_Python_Ops_Sub, s) + for expr, repl in CPP_to_Python_Eval_List: + s = expr.sub(repl, s) + return s + + + +del expr +del l +del override + + + +class FunctionEvaluator: + """ + Handles delayed evaluation of a #define function call. + """ + def __init__(self, name, args, expansion): + """ + Squirrels away the arguments and expansion value of a #define + macro function for later evaluation when we must actually expand + a value that uses it. + """ + self.name = name + self.args = function_arg_separator.split(args) + try: + expansion = string.split(expansion, '##') + except (AttributeError, TypeError): + # Python 1.5 throws TypeError if "expansion" isn't a string, + # later versions throw AttributeError. + pass + self.expansion = expansion + def __call__(self, *values): + """ + Evaluates the expansion of a #define macro function called + with the specified values. + """ + if len(self.args) != len(values): + raise ValueError, "Incorrect number of arguments to `%s'" % self.name + # Create a dictionary that maps the macro arguments to the + # corresponding values in this "call." We'll use this when we + # eval() the expansion so that arguments will get expanded to + # the right values. + locals = {} + for k, v in zip(self.args, values): + locals[k] = v + + parts = [] + for s in self.expansion: + if not s in self.args: + s = repr(s) + parts.append(s) + statement = string.join(parts, ' + ') + + return eval(statement, globals(), locals) + + + +# Find line continuations. +line_continuations = re.compile('\\\\\r?\n') + +# Search for a "function call" macro on an expansion. Returns the +# two-tuple of the "function" name itself, and a string containing the +# arguments within the call parentheses. +function_name = re.compile('(\S+)\(([^)]*)\)') + +# Split a string containing comma-separated function call arguments into +# the separate arguments. +function_arg_separator = re.compile(',\s*') + + + +class PreProcessor: + """ + The main workhorse class for handling C pre-processing. + """ + def __init__(self, current=os.curdir, cpppath=(), dict={}, all=0): + global Table + + cpppath = tuple(cpppath) + + self.searchpath = { + '"' : (current,) + cpppath, + '<' : cpppath + (current,), + } + + # Initialize our C preprocessor namespace for tracking the + # values of #defined keywords. We use this namespace to look + # for keywords on #ifdef/#ifndef lines, and to eval() the + # expressions on #if/#elif lines (after massaging them from C to + # Python). + self.cpp_namespace = dict.copy() + self.cpp_namespace['__dict__'] = self.cpp_namespace + + if all: + self.do_include = self.all_include + + # For efficiency, a dispatch table maps each C preprocessor + # directive (#if, #define, etc.) to the method that should be + # called when we see it. We accomodate state changes (#if, + # #ifdef, #ifndef) by pushing the current dispatch table on a + # stack and changing what method gets called for each relevant + # directive we might see next at this level (#else, #elif). + # #endif will simply pop the stack. + d = { + 'scons_current_file' : self.scons_current_file + } + for op in Table.keys(): + d[op] = getattr(self, 'do_' + op) + self.default_table = d + + # Controlling methods. + + def tupleize(self, contents): + """ + Turns the contents of a file into a list of easily-processed + tuples describing the CPP lines in the file. + + The first element of each tuple is the line's preprocessor + directive (#if, #include, #define, etc., minus the initial '#'). + The remaining elements are specific to the type of directive, as + pulled apart by the regular expression. + """ + global CPP_Expression, Table + contents = line_continuations.sub('', contents) + cpp_tuples = CPP_Expression.findall(contents) + return map(lambda m, t=Table: + (m[0],) + t[m[0]].match(m[1]).groups(), + cpp_tuples) + + def __call__(self, file): + """ + Pre-processes a file. + + This is the main public entry point. + """ + self.current_file = file + return self.process_contents(self.read_file(file), file) + + def process_contents(self, contents, fname=None): + """ + Pre-processes a file contents. + + This is the main internal entry point. + """ + self.stack = [] + self.dispatch_table = self.default_table.copy() + self.current_file = fname + self.tuples = self.tupleize(contents) + + self.initialize_result(fname) + while self.tuples: + t = self.tuples.pop(0) + # Uncomment to see the list of tuples being processed (e.g., + # to validate the CPP lines are being translated correctly). + #print t + self.dispatch_table[t[0]](t) + return self.finalize_result(fname) + + # Dispatch table stack manipulation methods. + + def save(self): + """ + Pushes the current dispatch table on the stack and re-initializes + the current dispatch table to the default. + """ + self.stack.append(self.dispatch_table) + self.dispatch_table = self.default_table.copy() + + def restore(self): + """ + Pops the previous dispatch table off the stack and makes it the + current one. + """ + try: self.dispatch_table = self.stack.pop() + except IndexError: pass + + # Utility methods. + + def do_nothing(self, t): + """ + Null method for when we explicitly want the action for a + specific preprocessor directive to do nothing. + """ + pass + + def scons_current_file(self, t): + self.current_file = t[1] + + def eval_expression(self, t): + """ + Evaluates a C preprocessor expression. + + This is done by converting it to a Python equivalent and + eval()ing it in the C preprocessor namespace we use to + track #define values. + """ + t = CPP_to_Python(string.join(t[1:])) + try: return eval(t, self.cpp_namespace) + except (NameError, TypeError): return 0 + + def initialize_result(self, fname): + self.result = [fname] + + def finalize_result(self, fname): + return self.result[1:] + + def find_include_file(self, t): + """ + Finds the #include file for a given preprocessor tuple. + """ + fname = t[2] + for d in self.searchpath[t[1]]: + if d == os.curdir: + f = fname + else: + f = os.path.join(d, fname) + if os.path.isfile(f): + return f + return None + + def read_file(self, file): + return open(file).read() + + # Start and stop processing include lines. + + def start_handling_includes(self, t=None): + """ + Causes the PreProcessor object to start processing #import, + #include and #include_next lines. + + This method will be called when a #if, #ifdef, #ifndef or #elif + evaluates True, or when we reach the #else in a #if, #ifdef, + #ifndef or #elif block where a condition already evaluated + False. + + """ + d = self.dispatch_table + d['import'] = self.do_import + d['include'] = self.do_include + d['include_next'] = self.do_include + + def stop_handling_includes(self, t=None): + """ + Causes the PreProcessor object to stop processing #import, + #include and #include_next lines. + + This method will be called when a #if, #ifdef, #ifndef or #elif + evaluates False, or when we reach the #else in a #if, #ifdef, + #ifndef or #elif block where a condition already evaluated True. + """ + d = self.dispatch_table + d['import'] = self.do_nothing + d['include'] = self.do_nothing + d['include_next'] = self.do_nothing + + # Default methods for handling all of the preprocessor directives. + # (Note that what actually gets called for a given directive at any + # point in time is really controlled by the dispatch_table.) + + def _do_if_else_condition(self, condition): + """ + Common logic for evaluating the conditions on #if, #ifdef and + #ifndef lines. + """ + self.save() + d = self.dispatch_table + if condition: + self.start_handling_includes() + d['elif'] = self.stop_handling_includes + d['else'] = self.stop_handling_includes + else: + self.stop_handling_includes() + d['elif'] = self.do_elif + d['else'] = self.start_handling_includes + + def do_ifdef(self, t): + """ + Default handling of a #ifdef line. + """ + self._do_if_else_condition(self.cpp_namespace.has_key(t[1])) + + def do_ifndef(self, t): + """ + Default handling of a #ifndef line. + """ + self._do_if_else_condition(not self.cpp_namespace.has_key(t[1])) + + def do_if(self, t): + """ + Default handling of a #if line. + """ + self._do_if_else_condition(self.eval_expression(t)) + + def do_elif(self, t): + """ + Default handling of a #elif line. + """ + d = self.dispatch_table + if self.eval_expression(t): + self.start_handling_includes() + d['elif'] = self.stop_handling_includes + d['else'] = self.stop_handling_includes + + def do_else(self, t): + """ + Default handling of a #else line. + """ + pass + + def do_endif(self, t): + """ + Default handling of a #endif line. + """ + self.restore() + + def do_define(self, t): + """ + Default handling of a #define line. + """ + _, name, args, expansion = t + try: + expansion = int(expansion) + except (TypeError, ValueError): + pass + if args: + evaluator = FunctionEvaluator(name, args[1:-1], expansion) + self.cpp_namespace[name] = evaluator + else: + self.cpp_namespace[name] = expansion + + def do_undef(self, t): + """ + Default handling of a #undef line. + """ + try: del self.cpp_namespace[t[1]] + except KeyError: pass + + def do_import(self, t): + """ + Default handling of a #import line. + """ + # XXX finish this -- maybe borrow/share logic from do_include()...? + pass + + def do_include(self, t): + """ + Default handling of a #include line. + """ + t = self.resolve_include(t) + include_file = self.find_include_file(t) + if include_file: + #print "include_file =", include_file + self.result.append(include_file) + contents = self.read_file(include_file) + new_tuples = [('scons_current_file', include_file)] + \ + self.tupleize(contents) + \ + [('scons_current_file', self.current_file)] + self.tuples[:] = new_tuples + self.tuples + + # Date: Tue, 22 Nov 2005 20:26:09 -0500 + # From: Stefan Seefeld + # + # By the way, #include_next is not the same as #include. The difference + # being that #include_next starts its search in the path following the + # path that let to the including file. In other words, if your system + # include paths are ['/foo', '/bar'], and you are looking at a header + # '/foo/baz.h', it might issue an '#include_next ' which would + # correctly resolve to '/bar/baz.h' (if that exists), but *not* see + # '/foo/baz.h' again. See http://www.delorie.com/gnu/docs/gcc/cpp_11.html + # for more reasoning. + # + # I have no idea in what context 'import' might be used. + + # XXX is #include_next really the same as #include ? + do_include_next = do_include + + # Utility methods for handling resolution of include files. + + def resolve_include(self, t): + """Resolve a tuple-ized #include line. + + This handles recursive expansion of values without "" or <> + surrounding the name until an initial " or < is found, to handle + #include FILE + where FILE is a #define somewhere else. + """ + s = t[1] + while not s[0] in '<"': + #print "s =", s + try: + s = self.cpp_namespace[s] + except KeyError: + m = function_name.search(s) + s = self.cpp_namespace[m.group(1)] + if callable(s): + args = function_arg_separator.split(m.group(2)) + s = apply(s, args) + if not s: + return None + return (t[0], s[0], s[1:-1]) + + def all_include(self, t): + """ + """ + self.result.append(self.resolve_include(t)) + +class DumbPreProcessor(PreProcessor): + """A preprocessor that ignores all #if/#elif/#else/#endif directives + and just reports back *all* of the #include files (like the classic + SCons scanner did). + + This is functionally equivalent to using a regular expression to + find all of the #include lines, only slower. It exists mainly as + an example of how the main PreProcessor class can be sub-classed + to tailor its behavior. + """ + def __init__(self, *args, **kw): + apply(PreProcessor.__init__, (self,)+args, kw) + d = self.default_table + for func in ['if', 'elif', 'else', 'endif', 'ifdef', 'ifndef']: + d[func] = d[func] = self.do_nothing + +del __revision__ + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/cppTests.py b/src/engine/SCons/cppTests.py new file mode 100644 index 0000000..5bd08f8 --- /dev/null +++ b/src/engine/SCons/cppTests.py @@ -0,0 +1,715 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/cppTests.py 4577 2009/12/27 19:44:43 scons" + +import string +import sys +import unittest + +import cpp + + + +basic_input = """ +#include "file1-yes" +#include +""" + + +substitution_input = """ +#define FILE3 "file3-yes" +#define FILE4 + +#include FILE3 +#include FILE4 + +#define XXX_FILE5 YYY_FILE5 +#define YYY_FILE5 ZZZ_FILE5 +#define ZZZ_FILE5 FILE5 + +#define FILE5 "file5-yes" +#define FILE6 + +#define XXX_FILE6 YYY_FILE6 +#define YYY_FILE6 ZZZ_FILE6 +#define ZZZ_FILE6 FILE6 + +#include XXX_FILE5 +#include XXX_FILE6 +""" + + +ifdef_input = """ +#define DEFINED 0 + +#ifdef DEFINED +#include "file7-yes" +#else +#include "file7-no" +#endif + +#ifdef NOT_DEFINED +#include +#else +#include +#endif +""" + + +if_boolean_input = """ +#define ZERO 0 +#define ONE 1 + +#if ZERO +#include "file9-no" +#else +#include "file9-yes" +#endif + +#if ONE +#include +#else +#include +#endif + +#if ZERO +#include "file11-no-1" +#elif ZERO +#include "file11-no-2" +#else +#include "file11-yes" +#endif + +#if ZERO +#include +#elif ONE +#include +#else +#include +#endif + +#if ONE +#include "file13-yes" +#elif ZERO +#include "file13-no-1" +#else +#include "file13-no-2" +#endif + +#if ONE +#include +#elif ONE +#include +#else +#include +#endif +""" + + +if_defined_input = """ +#define DEFINED 0 + +#if defined(DEFINED) +#include "file15-yes" +#endif + +#if ! defined(DEFINED) +#include +#else +#include +#endif + +#if defined DEFINED +#include "file17-yes" +#endif + +#if ! defined DEFINED +#include +#else +#include +#endif +""" + + +expression_input = """ +#define ZERO 0 +#define ONE 1 + +#if ZERO && ZERO +#include "file19-no" +#else +#include "file19-yes" +#endif + +#if ZERO && ONE +#include +#else +#include +#endif + +#if ONE && ZERO +#include "file21-no" +#else +#include "file21-yes" +#endif + +#if ONE && ONE +#include +#else +#include +#endif + +#if ZERO || ZERO +#include "file23-no" +#else +#include "file23-yes" +#endif + +#if ZERO || ONE +#include +#else +#include +#endif + +#if ONE || ZERO +#include "file25-yes" +#else +#include "file25-no" +#endif + +#if ONE || ONE +#include +#else +#include +#endif + +#if ONE == ONE +#include "file27-yes" +#else +#include "file27-no" +#endif + +#if ONE != ONE +#include +#else +#include +#endif + +#if ! (ONE == ONE) +#include "file29-no" +#else +#include "file29-yes" +#endif + +#if ! (ONE != ONE) +#include +#else +#include +#endif +""" + + +undef_input = """ +#define UNDEFINE 0 + +#ifdef UNDEFINE +#include "file31-yes" +#else +#include "file31-no" +#endif + +#undef UNDEFINE + +#ifdef UNDEFINE +#include +#else +#include +#endif +""" + + +macro_function_input = """ +#define ZERO 0 +#define ONE 1 + +#define FUNC33(x) "file33-yes" +#define FUNC34(x) + +#include FUNC33(ZERO) +#include FUNC34(ZERO) + +#define FILE35 "file35-yes" +#define FILE36 + +#define FUNC35(x, y) FILE35 +#define FUNC36(x, y) FILE36 + +#include FUNC35(ZERO, ONE) +#include FUNC36(ZERO, ONE) + +#define FILE37 "file37-yes" +#define FILE38 + +#define FUNC37a(x, y) FILE37 +#define FUNC38a(x, y) FILE38 + +#define FUNC37b(x, y) FUNC37a(x, y) +#define FUNC38b(x, y) FUNC38a(x, y) + +#define FUNC37c(x, y) FUNC37b(x, y) +#define FUNC38c(x, y) FUNC38b(x, y) + +#include FUNC37c(ZERO, ONE) +#include FUNC38c(ZERO, ONE) + +#define FILE39 "file39-yes" +#define FILE40 + +#define FUNC39a(x0, y0) FILE39 +#define FUNC40a(x0, y0) FILE40 + +#define FUNC39b(x1, y2) FUNC39a(x1, y1) +#define FUNC40b(x1, y2) FUNC40a(x1, y1) + +#define FUNC39c(x2, y2) FUNC39b(x2, y2) +#define FUNC40c(x2, y2) FUNC40b(x2, y2) + +#include FUNC39c(ZERO, ONE) +#include FUNC40c(ZERO, ONE) + +/* Make sure we don't die if the expansion isn't a string. */ +#define FUNC_INTEGER(x) 1 + +/* Make sure one-character names are recognized. */ +#define _(x) translate(x) +#undef _ +""" + + +token_pasting_input = """ +#define PASTE_QUOTE(q, name) q##name##-yes##q +#define PASTE_ANGLE(name) <##name##-yes> + +#define FUNC41 PASTE_QUOTE(", file41) +#define FUNC42 PASTE_ANGLE(file42) + +#include FUNC41 +#include FUNC42 +""" + + +no_space_input = """ +#include +#include"file44-yes" +""" + + + +# pp_class = PreProcessor +# #pp_class = DumbPreProcessor + +# pp = pp_class(current = ".", +# cpppath = ['/usr/include'], +# print_all = 1) +# #pp(open(sys.argv[1]).read()) +# pp(input) + + +class cppTestCase(unittest.TestCase): + def setUp(self): + self.cpp = self.cpp_class(current = ".", + cpppath = ['/usr/include']) + + def test_basic(self): + """Test basic #include scanning""" + expect = self.basic_expect + result = self.cpp.process_contents(basic_input) + assert expect == result, (expect, result) + + def test_substitution(self): + """Test substitution of #include files using CPP variables""" + expect = self.substitution_expect + result = self.cpp.process_contents(substitution_input) + assert expect == result, (expect, result) + + def test_ifdef(self): + """Test basic #ifdef processing""" + expect = self.ifdef_expect + result = self.cpp.process_contents(ifdef_input) + assert expect == result, (expect, result) + + def test_if_boolean(self): + """Test #if with Boolean values""" + expect = self.if_boolean_expect + result = self.cpp.process_contents(if_boolean_input) + assert expect == result, (expect, result) + + def test_if_defined(self): + """Test #if defined() idioms""" + expect = self.if_defined_expect + result = self.cpp.process_contents(if_defined_input) + assert expect == result, (expect, result) + + def test_expression(self): + """Test #if with arithmetic expressions""" + expect = self.expression_expect + result = self.cpp.process_contents(expression_input) + assert expect == result, (expect, result) + + def test_undef(self): + """Test #undef handling""" + expect = self.undef_expect + result = self.cpp.process_contents(undef_input) + assert expect == result, (expect, result) + + def test_macro_function(self): + """Test using macro functions to express file names""" + expect = self.macro_function_expect + result = self.cpp.process_contents(macro_function_input) + assert expect == result, (expect, result) + + def test_token_pasting(self): + """Test token-pasting to construct file names""" + expect = self.token_pasting_expect + result = self.cpp.process_contents(token_pasting_input) + assert expect == result, (expect, result) + + def test_no_space(self): + """Test no space between #include and the quote""" + expect = self.no_space_expect + result = self.cpp.process_contents(no_space_input) + assert expect == result, (expect, result) + +class cppAllTestCase(cppTestCase): + def setUp(self): + self.cpp = self.cpp_class(current = ".", + cpppath = ['/usr/include'], + all=1) + +class PreProcessorTestCase(cppAllTestCase): + cpp_class = cpp.PreProcessor + + basic_expect = [ + ('include', '"', 'file1-yes'), + ('include', '<', 'file2-yes'), + ] + + substitution_expect = [ + ('include', '"', 'file3-yes'), + ('include', '<', 'file4-yes'), + ('include', '"', 'file5-yes'), + ('include', '<', 'file6-yes'), + ] + + ifdef_expect = [ + ('include', '"', 'file7-yes'), + ('include', '<', 'file8-yes'), + ] + + if_boolean_expect = [ + ('include', '"', 'file9-yes'), + ('include', '<', 'file10-yes'), + ('include', '"', 'file11-yes'), + ('include', '<', 'file12-yes'), + ('include', '"', 'file13-yes'), + ('include', '<', 'file14-yes'), + ] + + if_defined_expect = [ + ('include', '"', 'file15-yes'), + ('include', '<', 'file16-yes'), + ('include', '"', 'file17-yes'), + ('include', '<', 'file18-yes'), + ] + + expression_expect = [ + ('include', '"', 'file19-yes'), + ('include', '<', 'file20-yes'), + ('include', '"', 'file21-yes'), + ('include', '<', 'file22-yes'), + ('include', '"', 'file23-yes'), + ('include', '<', 'file24-yes'), + ('include', '"', 'file25-yes'), + ('include', '<', 'file26-yes'), + ('include', '"', 'file27-yes'), + ('include', '<', 'file28-yes'), + ('include', '"', 'file29-yes'), + ('include', '<', 'file30-yes'), + ] + + undef_expect = [ + ('include', '"', 'file31-yes'), + ('include', '<', 'file32-yes'), + ] + + macro_function_expect = [ + ('include', '"', 'file33-yes'), + ('include', '<', 'file34-yes'), + ('include', '"', 'file35-yes'), + ('include', '<', 'file36-yes'), + ('include', '"', 'file37-yes'), + ('include', '<', 'file38-yes'), + ('include', '"', 'file39-yes'), + ('include', '<', 'file40-yes'), + ] + + token_pasting_expect = [ + ('include', '"', 'file41-yes'), + ('include', '<', 'file42-yes'), + ] + + no_space_expect = [ + ('include', '<', 'file43-yes'), + ('include', '"', 'file44-yes'), + ] + +class DumbPreProcessorTestCase(cppAllTestCase): + cpp_class = cpp.DumbPreProcessor + + basic_expect = [ + ('include', '"', 'file1-yes'), + ('include', '<', 'file2-yes'), + ] + + substitution_expect = [ + ('include', '"', 'file3-yes'), + ('include', '<', 'file4-yes'), + ('include', '"', 'file5-yes'), + ('include', '<', 'file6-yes'), + ] + + ifdef_expect = [ + ('include', '"', 'file7-yes'), + ('include', '"', 'file7-no'), + ('include', '<', 'file8-no'), + ('include', '<', 'file8-yes'), + ] + + if_boolean_expect = [ + ('include', '"', 'file9-no'), + ('include', '"', 'file9-yes'), + ('include', '<', 'file10-yes'), + ('include', '<', 'file10-no'), + ('include', '"', 'file11-no-1'), + ('include', '"', 'file11-no-2'), + ('include', '"', 'file11-yes'), + ('include', '<', 'file12-no-1'), + ('include', '<', 'file12-yes'), + ('include', '<', 'file12-no-2'), + ('include', '"', 'file13-yes'), + ('include', '"', 'file13-no-1'), + ('include', '"', 'file13-no-2'), + ('include', '<', 'file14-yes'), + ('include', '<', 'file14-no-1'), + ('include', '<', 'file14-no-2'), + ] + + if_defined_expect = [ + ('include', '"', 'file15-yes'), + ('include', '<', 'file16-no'), + ('include', '<', 'file16-yes'), + ('include', '"', 'file17-yes'), + ('include', '<', 'file18-no'), + ('include', '<', 'file18-yes'), + ] + + expression_expect = [ + ('include', '"', 'file19-no'), + ('include', '"', 'file19-yes'), + ('include', '<', 'file20-no'), + ('include', '<', 'file20-yes'), + ('include', '"', 'file21-no'), + ('include', '"', 'file21-yes'), + ('include', '<', 'file22-yes'), + ('include', '<', 'file22-no'), + ('include', '"', 'file23-no'), + ('include', '"', 'file23-yes'), + ('include', '<', 'file24-yes'), + ('include', '<', 'file24-no'), + ('include', '"', 'file25-yes'), + ('include', '"', 'file25-no'), + ('include', '<', 'file26-yes'), + ('include', '<', 'file26-no'), + ('include', '"', 'file27-yes'), + ('include', '"', 'file27-no'), + ('include', '<', 'file28-no'), + ('include', '<', 'file28-yes'), + ('include', '"', 'file29-no'), + ('include', '"', 'file29-yes'), + ('include', '<', 'file30-yes'), + ('include', '<', 'file30-no'), + ] + + undef_expect = [ + ('include', '"', 'file31-yes'), + ('include', '"', 'file31-no'), + ('include', '<', 'file32-no'), + ('include', '<', 'file32-yes'), + ] + + macro_function_expect = [ + ('include', '"', 'file33-yes'), + ('include', '<', 'file34-yes'), + ('include', '"', 'file35-yes'), + ('include', '<', 'file36-yes'), + ('include', '"', 'file37-yes'), + ('include', '<', 'file38-yes'), + ('include', '"', 'file39-yes'), + ('include', '<', 'file40-yes'), + ] + + token_pasting_expect = [ + ('include', '"', 'file41-yes'), + ('include', '<', 'file42-yes'), + ] + + no_space_expect = [ + ('include', '<', 'file43-yes'), + ('include', '"', 'file44-yes'), + ] + + + +import os +import re +import shutil +import tempfile + +tempfile.template = 'cppTests.' +if os.name in ('posix', 'nt'): + tempfile.template = 'cppTests.' + str(os.getpid()) + '.' +else: + tempfile.template = 'cppTests.' + +_Cleanup = [] + +def _clean(): + for dir in _Cleanup: + if os.path.exists(dir): + shutil.rmtree(dir) + +sys.exitfunc = _clean + +class fileTestCase(unittest.TestCase): + cpp_class = cpp.DumbPreProcessor + + def setUp(self): + try: + path = tempfile.mktemp(prefix=tempfile.template) + except TypeError: + # The tempfile.mktemp() function in earlier versions of Python + # has no prefix argument, but uses the tempfile.template + # value that we set above. + path = tempfile.mktemp() + _Cleanup.append(path) + os.mkdir(path) + self.tempdir = path + self.orig_cwd = os.getcwd() + os.chdir(path) + + def tearDown(self): + os.chdir(self.orig_cwd) + shutil.rmtree(self.tempdir) + _Cleanup.remove(self.tempdir) + + def strip_initial_spaces(self, s): + #lines = s.split('\n') + lines = string.split(s, '\n') + spaces = re.match(' *', lines[0]).group(0) + def strip_spaces(l, spaces=spaces): + #if l.startswith(spaces): + if l[:len(spaces)] == spaces: + l = l[len(spaces):] + return l + #return '\n'.join([ strip_spaces(l) for l in lines ]) + return string.join(map(strip_spaces, lines), '\n') + + def write(self, file, contents): + open(file, 'w').write(self.strip_initial_spaces(contents)) + + def test_basic(self): + """Test basic file inclusion""" + self.write('f1.h', """\ + #include "f2.h" + """) + self.write('f2.h', """\ + #include + """) + self.write('f3.h', """\ + """) + p = cpp.DumbPreProcessor(current = os.curdir, + cpppath = [os.curdir]) + result = p('f1.h') + assert result == ['f2.h', 'f3.h'], result + + def test_current_file(self): + """Test use of the .current_file attribute""" + self.write('f1.h', """\ + #include + """) + self.write('f2.h', """\ + #include "f3.h" + """) + self.write('f3.h', """\ + """) + class MyPreProcessor(cpp.DumbPreProcessor): + def __init__(self, *args, **kw): + apply(cpp.DumbPreProcessor.__init__, (self,) + args, kw) + self.files = [] + def __call__(self, file): + self.files.append(file) + return cpp.DumbPreProcessor.__call__(self, file) + def scons_current_file(self, t): + r = cpp.DumbPreProcessor.scons_current_file(self, t) + self.files.append(self.current_file) + return r + p = MyPreProcessor(current = os.curdir, cpppath = [os.curdir]) + result = p('f1.h') + assert result == ['f2.h', 'f3.h'], result + assert p.files == ['f1.h', 'f2.h', 'f3.h', 'f2.h', 'f1.h'], p.files + + + +if __name__ == '__main__': + suite = unittest.TestSuite() + tclasses = [ PreProcessorTestCase, + DumbPreProcessorTestCase, + fileTestCase, + ] + for tclass in tclasses: + names = unittest.getTestCaseNames(tclass, 'test_') + try: + names = list(set(names)) + except NameError: + pass + names.sort() + suite.addTests(map(tclass, names)) + if not unittest.TextTestRunner().run(suite).wasSuccessful(): + sys.exit(1) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/dblite.py b/src/engine/SCons/dblite.py new file mode 100644 index 0000000..bcb2aa0 --- /dev/null +++ b/src/engine/SCons/dblite.py @@ -0,0 +1,248 @@ +# dblite.py module contributed by Ralf W. Grosse-Kunstleve. +# Extended for Unicode by Steven Knight. + +import cPickle +import time +import shutil +import os +import types +import __builtin__ + +keep_all_files = 00000 +ignore_corrupt_dbfiles = 0 + +def corruption_warning(filename): + print "Warning: Discarding corrupt database:", filename + +if hasattr(types, 'UnicodeType'): + def is_string(s): + t = type(s) + return t is types.StringType or t is types.UnicodeType +else: + def is_string(s): + return type(s) is types.StringType + +try: + unicode('a') +except NameError: + def unicode(s): return s + +dblite_suffix = '.dblite' +tmp_suffix = '.tmp' + +class dblite: + + # Squirrel away references to the functions in various modules + # that we'll use when our __del__() method calls our sync() method + # during shutdown. We might get destroyed when Python is in the midst + # of tearing down the different modules we import in an essentially + # arbitrary order, and some of the various modules's global attributes + # may already be wiped out from under us. + # + # See the discussion at: + # http://mail.python.org/pipermail/python-bugs-list/2003-March/016877.html + + _open = __builtin__.open + _cPickle_dump = cPickle.dump + _os_chmod = os.chmod + try: + _os_chown = os.chown + except AttributeError: + _os_chown = None + _os_rename = os.rename + _os_unlink = os.unlink + _shutil_copyfile = shutil.copyfile + _time_time = time.time + + def __init__(self, file_base_name, flag, mode): + assert flag in (None, "r", "w", "c", "n") + if (flag is None): flag = "r" + base, ext = os.path.splitext(file_base_name) + if ext == dblite_suffix: + # There's already a suffix on the file name, don't add one. + self._file_name = file_base_name + self._tmp_name = base + tmp_suffix + else: + self._file_name = file_base_name + dblite_suffix + self._tmp_name = file_base_name + tmp_suffix + self._flag = flag + self._mode = mode + self._dict = {} + self._needs_sync = 00000 + if self._os_chown is not None and (os.geteuid()==0 or os.getuid()==0): + # running as root; chown back to current owner/group when done + try: + statinfo = os.stat(self._file_name) + self._chown_to = statinfo.st_uid + self._chgrp_to = statinfo.st_gid + except OSError, e: + # db file doesn't exist yet. + # Check os.environ for SUDO_UID, use if set + self._chown_to = int(os.environ.get('SUDO_UID', -1)) + self._chgrp_to = int(os.environ.get('SUDO_GID', -1)) + else: + self._chown_to = -1 # don't chown + self._chgrp_to = -1 # don't chgrp + if (self._flag == "n"): + self._open(self._file_name, "wb", self._mode) + else: + try: + f = self._open(self._file_name, "rb") + except IOError, e: + if (self._flag != "c"): + raise e + self._open(self._file_name, "wb", self._mode) + else: + p = f.read() + if (len(p) > 0): + try: + self._dict = cPickle.loads(p) + except (cPickle.UnpicklingError, EOFError): + if (ignore_corrupt_dbfiles == 0): raise + if (ignore_corrupt_dbfiles == 1): + corruption_warning(self._file_name) + + def __del__(self): + if (self._needs_sync): + self.sync() + + def sync(self): + self._check_writable() + f = self._open(self._tmp_name, "wb", self._mode) + self._cPickle_dump(self._dict, f, 1) + f.close() + # Windows doesn't allow renaming if the file exists, so unlink + # it first, chmod'ing it to make sure we can do so. On UNIX, we + # may not be able to chmod the file if it's owned by someone else + # (e.g. from a previous run as root). We should still be able to + # unlink() the file if the directory's writable, though, so ignore + # any OSError exception thrown by the chmod() call. + try: self._os_chmod(self._file_name, 0777) + except OSError: pass + self._os_unlink(self._file_name) + self._os_rename(self._tmp_name, self._file_name) + if self._os_chown is not None and self._chown_to > 0: # don't chown to root or -1 + try: + self._os_chown(self._file_name, self._chown_to, self._chgrp_to) + except OSError: + pass + self._needs_sync = 00000 + if (keep_all_files): + self._shutil_copyfile( + self._file_name, + self._file_name + "_" + str(int(self._time_time()))) + + def _check_writable(self): + if (self._flag == "r"): + raise IOError("Read-only database: %s" % self._file_name) + + def __getitem__(self, key): + return self._dict[key] + + def __setitem__(self, key, value): + self._check_writable() + if (not is_string(key)): + raise TypeError, "key `%s' must be a string but is %s" % (key, type(key)) + if (not is_string(value)): + raise TypeError, "value `%s' must be a string but is %s" % (value, type(value)) + self._dict[key] = value + self._needs_sync = 0001 + + def keys(self): + return self._dict.keys() + + def has_key(self, key): + return key in self._dict + + def __contains__(self, key): + return key in self._dict + + def iterkeys(self): + return self._dict.iterkeys() + + __iter__ = iterkeys + + def __len__(self): + return len(self._dict) + +def open(file, flag=None, mode=0666): + return dblite(file, flag, mode) + +def _exercise(): + db = open("tmp", "n") + assert len(db) == 0 + db["foo"] = "bar" + assert db["foo"] == "bar" + db[unicode("ufoo")] = unicode("ubar") + assert db[unicode("ufoo")] == unicode("ubar") + db.sync() + db = open("tmp", "c") + assert len(db) == 2, len(db) + assert db["foo"] == "bar" + db["bar"] = "foo" + assert db["bar"] == "foo" + db[unicode("ubar")] = unicode("ufoo") + assert db[unicode("ubar")] == unicode("ufoo") + db.sync() + db = open("tmp", "r") + assert len(db) == 4, len(db) + assert db["foo"] == "bar" + assert db["bar"] == "foo" + assert db[unicode("ufoo")] == unicode("ubar") + assert db[unicode("ubar")] == unicode("ufoo") + try: + db.sync() + except IOError, e: + assert str(e) == "Read-only database: tmp.dblite" + else: + raise RuntimeError, "IOError expected." + db = open("tmp", "w") + assert len(db) == 4 + db["ping"] = "pong" + db.sync() + try: + db[(1,2)] = "tuple" + except TypeError, e: + assert str(e) == "key `(1, 2)' must be a string but is ", str(e) + else: + raise RuntimeError, "TypeError exception expected" + try: + db["list"] = [1,2] + except TypeError, e: + assert str(e) == "value `[1, 2]' must be a string but is ", str(e) + else: + raise RuntimeError, "TypeError exception expected" + db = open("tmp", "r") + assert len(db) == 5 + db = open("tmp", "n") + assert len(db) == 0 + _open("tmp.dblite", "w") + db = open("tmp", "r") + _open("tmp.dblite", "w").write("x") + try: + db = open("tmp", "r") + except cPickle.UnpicklingError: + pass + else: + raise RuntimeError, "cPickle exception expected." + global ignore_corrupt_dbfiles + ignore_corrupt_dbfiles = 2 + db = open("tmp", "r") + assert len(db) == 0 + os.unlink("tmp.dblite") + try: + db = open("tmp", "w") + except IOError, e: + assert str(e) == "[Errno 2] No such file or directory: 'tmp.dblite'", str(e) + else: + raise RuntimeError, "IOError expected." + print "OK" + +if (__name__ == "__main__"): + _exercise() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/SCons/exitfuncs.py b/src/engine/SCons/exitfuncs.py new file mode 100644 index 0000000..4179c37 --- /dev/null +++ b/src/engine/SCons/exitfuncs.py @@ -0,0 +1,77 @@ +"""SCons.exitfuncs + +Register functions which are executed when SCons exits for any reason. + +""" + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/SCons/exitfuncs.py 4577 2009/12/27 19:44:43 scons" + + + +_exithandlers = [] +def _run_exitfuncs(): + """run any registered exit functions + + _exithandlers is traversed in reverse order so functions are executed + last in, first out. + """ + + while _exithandlers: + func, targs, kargs = _exithandlers.pop() + apply(func, targs, kargs) + +def register(func, *targs, **kargs): + """register a function to be executed upon normal program termination + + func - function to be called at exit + targs - optional arguments to pass to func + kargs - optional keyword arguments to pass to func + """ + _exithandlers.append((func, targs, kargs)) + +import sys + +try: + x = sys.exitfunc + + # if x isn't our own exit func executive, assume it's another + # registered exit function - append it to our list... + if x != _run_exitfuncs: + register(x) + +except AttributeError: + pass + +# make our exit function get run by python when it exits: +sys.exitfunc = _run_exitfuncs + +del sys + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/engine/setup.cfg b/src/engine/setup.cfg new file mode 100644 index 0000000..94ede1f --- /dev/null +++ b/src/engine/setup.cfg @@ -0,0 +1,2 @@ +[bdist_rpm] +group = Development/Tools diff --git a/src/engine/setup.py b/src/engine/setup.py new file mode 100644 index 0000000..dab75bd --- /dev/null +++ b/src/engine/setup.py @@ -0,0 +1,74 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/engine/setup.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import sys + +(head, tail) = os.path.split(sys.argv[0]) + +if head: + os.chdir(head) + sys.argv[0] = tail + +# Code to figure out the package name from the current directory. +# May come in handy to allow this setup.py to switch-hit between +# python-scons and python2-scons. +#head, package = os.path.split(os.getcwd()) +#suffix = "-1.2.0.d20091224" +#if package[-len(suffix):] == suffix: +# package = package[:-len(suffix)] + +package = 'python-scons' + +from distutils.core import setup + +ver = { + 'python-scons': '1.5', + 'python2-scons': '2.1', +} + +setup(name = package, + version = "1.2.0.d20091224", + description = "SCons Python %s extension modules" % ver[package], + long_description = """SCons is an Open Source software construction tool--that is, a build tool; an +improved substitute for the classic Make utility; a better way to build +software.""", + author = "Steven Knight", + author_email = "knight@baldmt.com", + url = "http://www.scons.org/", + licence = "MIT, freely distributable", + keywords = "scons, cons, make, build tool, make tool", + packages = ["SCons", + "SCons.Node", + "SCons.Scanner", + "SCons.Sig", + "SCons.Script"]) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/os_spawnv_fix.diff b/src/os_spawnv_fix.diff new file mode 100644 index 0000000..926f896 --- /dev/null +++ b/src/os_spawnv_fix.diff @@ -0,0 +1,83 @@ +? dist/src/Mac/IDE scripts/Hold option to open a script +? dist/src/Mac/IDE scripts/Insert file name +? dist/src/Mac/IDE scripts/Insert folder name +? dist/src/Mac/IDE scripts/Search Python Documentation +? dist/src/Mac/IDE scripts/Hack/Remove .pyc files +? dist/src/Mac/IDE scripts/Hack/Toolbox Assistant +Index: dist/src/Modules/posixmodule.c +=================================================================== +RCS file: /cvsroot/python/python/dist/src/Modules/posixmodule.c,v +retrieving revision 2.213 +diff -c -c -r2.213 posixmodule.c +*** dist/src/Modules/posixmodule.c 2001/12/03 20:41:00 2.213 +--- dist/src/Modules/posixmodule.c 2001/12/05 00:52:58 +*************** +*** 1668,1674 **** + #ifdef HAVE_SPAWNV + static char posix_spawnv__doc__[] = + "spawnv(mode, path, args)\n\ +! Execute an executable path with arguments, replacing current process.\n\ + \n\ + mode: mode of process creation\n\ + path: path of executable file\n\ +--- 1668,1674 ---- + #ifdef HAVE_SPAWNV + static char posix_spawnv__doc__[] = + "spawnv(mode, path, args)\n\ +! Execute the program 'path' in a new process.\n\ + \n\ + mode: mode of process creation\n\ + path: path of executable file\n\ +*************** +*** 1717,1724 **** + + if (mode == _OLD_P_OVERLAY) + mode = _P_OVERLAY; + spawnval = _spawnv(mode, path, argvlist); +! + PyMem_DEL(argvlist); + + if (spawnval == -1) +--- 1717,1727 ---- + + if (mode == _OLD_P_OVERLAY) + mode = _P_OVERLAY; ++ ++ Py_BEGIN_ALLOW_THREADS + spawnval = _spawnv(mode, path, argvlist); +! Py_END_ALLOW_THREADS +! + PyMem_DEL(argvlist); + + if (spawnval == -1) +*************** +*** 1734,1740 **** + + static char posix_spawnve__doc__[] = + "spawnve(mode, path, args, env)\n\ +! Execute a path with arguments and environment, replacing current process.\n\ + \n\ + mode: mode of process creation\n\ + path: path of executable file\n\ +--- 1737,1743 ---- + + static char posix_spawnve__doc__[] = + "spawnve(mode, path, args, env)\n\ +! Execute the program 'path' in a new process.\n\ + \n\ + mode: mode of process creation\n\ + path: path of executable file\n\ +*************** +*** 1830,1836 **** +--- 1833,1843 ---- + + if (mode == _OLD_P_OVERLAY) + mode = _P_OVERLAY; ++ ++ Py_BEGIN_ALLOW_THREADS + spawnval = _spawnve(mode, path, argvlist, envlist); ++ Py_END_ALLOW_THREADS ++ + if (spawnval == -1) + (void) posix_error(); + else diff --git a/src/script/MANIFEST.in b/src/script/MANIFEST.in new file mode 100644 index 0000000..f324ed4 --- /dev/null +++ b/src/script/MANIFEST.in @@ -0,0 +1,3 @@ +scons +sconsign +scons-time diff --git a/src/script/README.txt b/src/script/README.txt new file mode 100644 index 0000000..0060819 --- /dev/null +++ b/src/script/README.txt @@ -0,0 +1,169 @@ +### +### THIS FILE IS NO LONGER USED. THIS IS THE README FILE FOR THE +### SEPARATE SCRIPT PACKAGE FROM THE ORIGINAL (DRAFT) PACKAGING +### SCHEME. WE'RE SAVING THIS IN CASE WE NEED OR WANT TO RESURRECT +### A SEPARATE SCRIPT PACKAGE IN THE FUTURE. +### +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# src/script/README.txt 4577 2009/12/27 19:44:43 scons + + + SCons - a software construction tool + + Version 1.2.0.d20091224 + + +This is an alpha release of SCons, a tool for building software (and +other files). SCons is implemented in Python, and its "configuration +files" are actually Python scripts, allowing you to use the full power +of a real scripting language to solve build problems. You do not, +however, need to know Python to use SCons effectively. + + +LATEST VERSION +============== + +Before going further, you can check that this package you have is +the latest version by checking the SCons download page at: + + http://www.scons.org/download.html + + +ABOUT SCONS PACKAGES +==================== + +The complete SCons system is comprised of three separate packages: + + scons + The scons script itself, plus the SCons build engine + installed into an SCons-specific library directory. + + python-scons + The SCons build engine, installed into the standard + Python library directory. + + scons-script [THIS PACKAGE] + Only the scons script itself. + +Depending on what you want to do with SCons, you may need to install +additional (or other) packages: + + If you just want to use scons (the script) to build software: + + Do NOT install this package. Install the scons package instead, + which contains a copy of both the script and the build engine. + You will not need to install any other packages. + + If you do NOT want to use the scons script, but you want to use the + SCons build engine in other Python software: + + Do NOT install this package. Install the python-scons package + instead. + + If you want to use the scons script AND you want to use the SCons + build engine in other Python software: + + Do NOT install this package. Install both the scons and + python-scons packages instead. + + Note that this installs two separate copies of the build engine, + one (in an SCons-specific library directory) used by the scons + script itself and one (in the standard Python library) used by + other software. This allows you the flexibility to upgrade + one build engine without affecting the other. + + If you want the scons script and other Python software to use the + same version of the build engine: + + Install this package AND the python-scons package. + + +INSTALLATION +============ + +To install this package, simply run the provided Python-standard setup +script as follows: + + # python setup.py + +You should have system installation privileges (that is, "root" or +"Administrator") when running the setup.py script. + + +DOCUMENTATION +============= + +Documentation for SCons is available at: + + http://www.scons.org/doc.html + + +LICENSING +========= + +SCons is distributed under the MIT license, a full copy of which is +available in the LICENSE.txt file. The MIT license is an approved Open +Source license, which means: + + This software is OSI Certified Open Source Software. OSI + Certified is a certification mark of the Open Source Initiative. + +More information about OSI certifications and Open Source software is +available at: + + http://www.opensource.org/ + + +REPORTING BUGS +============== + +You can report bugs either by following the "Tracker - Bugs" link +on the SCons project page: + + http://sourceforge.net/projects/scons/ + +or by sending mail to the SCons developers mailing list: + + scons-devel@lists.sourceforge.net + + +MAILING LISTS +============= + +A mailing list for users of SCons is available. You may send +questions or comments to the list at: + + scons-users@lists.sourceforge.net + +You may subscribe to the mailing list at: + + http://lists.sourceforge.net/lists/listinfo/scons-users + +There is also a low-volume mailing list available for announcements +about SCons. Subscribe at: + + http://lists.sourceforge.net/lists/listinfo/scons-announce + + +FOR MORE INFORMATION +==================== + +Check the SCons web site at: + + http://www.scons.org/ + + +AUTHOR INFO +=========== + +Steven Knight +knight at baldmt dot com +http://www.baldmt.com/~knight/ + +With more than a little help from: + Chad Austin + Charles Crain + Steve Leblanc + Anthony Roach + Steven Shaw + diff --git a/src/script/scons-post-install.py b/src/script/scons-post-install.py new file mode 100644 index 0000000..7c715d7 --- /dev/null +++ b/src/script/scons-post-install.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# +# scons-post-install - SCons post install script for Windows +# +# A script for configuring "App Paths" registry key so that SCons could +# be run from any directory the same way Python is. +# + +# +# SCons - a Software Constructor +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/script/scons-post-install.py 4577 2009/12/27 19:44:43 scons" + +import os.path +import sys + +scons_bat_path = os.path.join(sys.prefix, 'Scripts', 'scons.bat') + +app_paths_key = r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\SCons.bat' + +def install(): + if sys.platform == 'win32': + try: + import _winreg + except ImportError: + pass + else: + print 'Writing "App Paths" registry entry for %s' % scons_bat_path + _winreg.SetValue( + _winreg.HKEY_LOCAL_MACHINE, + app_paths_key, + _winreg.REG_SZ, + scons_bat_path) + print 'Done.' + + +def remove(): + if sys.platform == 'win32': + try: + import _winreg + except ImportError: + pass + else: + # print 'Remove "App Paths" registry entry' + _winreg.DeleteKey(_winreg.HKEY_LOCAL_MACHINE, app_paths_key) + + +if len(sys.argv) > 1: + if sys.argv[1] == '-install': + install() + elif sys.argv[1] == '-remove': + remove() + +sys.exit(0) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/script/scons-time.py b/src/script/scons-time.py new file mode 100644 index 0000000..dafbe4b --- /dev/null +++ b/src/script/scons-time.py @@ -0,0 +1,1529 @@ +#!/usr/bin/env python +# +# scons-time - run SCons timings and collect statistics +# +# A script for running a configuration through SCons with a standard +# set of invocations to collect timing and memory statistics and to +# capture the results in a consistent set of output files for display +# and analysis. +# + +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +from __future__ import nested_scopes + +__revision__ = "src/script/scons-time.py 4577 2009/12/27 19:44:43 scons" + +import getopt +import glob +import os +import os.path +import re +import shutil +import string +import sys +import tempfile +import time + +try: + False +except NameError: + # Pre-2.2 Python has no False keyword. + import __builtin__ + __builtin__.False = not 1 + +try: + True +except NameError: + # Pre-2.2 Python has no True keyword. + import __builtin__ + __builtin__.True = not 0 + +def make_temp_file(**kw): + try: + result = tempfile.mktemp(**kw) + try: + result = os.path.realpath(result) + except AttributeError: + # Python 2.1 has no os.path.realpath() method. + pass + except TypeError: + try: + save_template = tempfile.template + prefix = kw['prefix'] + del kw['prefix'] + tempfile.template = prefix + result = tempfile.mktemp(**kw) + finally: + tempfile.template = save_template + return result + +def HACK_for_exec(cmd, *args): + ''' + For some reason, Python won't allow an exec() within a function + that also declares an internal function (including lambda functions). + This function is a hack that calls exec() in a function with no + internal functions. + ''' + if not args: exec(cmd) + elif len(args) == 1: exec cmd in args[0] + else: exec cmd in args[0], args[1] + +class Plotter: + def increment_size(self, largest): + """ + Return the size of each horizontal increment line for a specified + maximum value. This returns a value that will provide somewhere + between 5 and 9 horizontal lines on the graph, on some set of + boundaries that are multiples of 10/100/1000/etc. + """ + i = largest / 5 + if not i: + return largest + multiplier = 1 + while i >= 10: + i = i / 10 + multiplier = multiplier * 10 + return i * multiplier + + def max_graph_value(self, largest): + # Round up to next integer. + largest = int(largest) + 1 + increment = self.increment_size(largest) + return ((largest + increment - 1) / increment) * increment + +class Line: + def __init__(self, points, type, title, label, comment, fmt="%s %s"): + self.points = points + self.type = type + self.title = title + self.label = label + self.comment = comment + self.fmt = fmt + + def print_label(self, inx, x, y): + if self.label: + print 'set label %s "%s" at %s,%s right' % (inx, self.label, x, y) + + def plot_string(self): + if self.title: + title_string = 'title "%s"' % self.title + else: + title_string = 'notitle' + return "'-' %s with lines lt %s" % (title_string, self.type) + + def print_points(self, fmt=None): + if fmt is None: + fmt = self.fmt + if self.comment: + print '# %s' % self.comment + for x, y in self.points: + # If y is None, it usually represents some kind of break + # in the line's index number. We might want to represent + # this some way rather than just drawing the line straight + # between the two points on either side. + if not y is None: + print fmt % (x, y) + print 'e' + + def get_x_values(self): + return [ p[0] for p in self.points ] + + def get_y_values(self): + return [ p[1] for p in self.points ] + +class Gnuplotter(Plotter): + + def __init__(self, title, key_location): + self.lines = [] + self.title = title + self.key_location = key_location + + def line(self, points, type, title=None, label=None, comment=None, fmt='%s %s'): + if points: + line = Line(points, type, title, label, comment, fmt) + self.lines.append(line) + + def plot_string(self, line): + return line.plot_string() + + def vertical_bar(self, x, type, label, comment): + if self.get_min_x() <= x and x <= self.get_max_x(): + points = [(x, 0), (x, self.max_graph_value(self.get_max_y()))] + self.line(points, type, label, comment) + + def get_all_x_values(self): + result = [] + for line in self.lines: + result.extend(line.get_x_values()) + return filter(lambda r: not r is None, result) + + def get_all_y_values(self): + result = [] + for line in self.lines: + result.extend(line.get_y_values()) + return filter(lambda r: not r is None, result) + + def get_min_x(self): + try: + return self.min_x + except AttributeError: + try: + self.min_x = min(self.get_all_x_values()) + except ValueError: + self.min_x = 0 + return self.min_x + + def get_max_x(self): + try: + return self.max_x + except AttributeError: + try: + self.max_x = max(self.get_all_x_values()) + except ValueError: + self.max_x = 0 + return self.max_x + + def get_min_y(self): + try: + return self.min_y + except AttributeError: + try: + self.min_y = min(self.get_all_y_values()) + except ValueError: + self.min_y = 0 + return self.min_y + + def get_max_y(self): + try: + return self.max_y + except AttributeError: + try: + self.max_y = max(self.get_all_y_values()) + except ValueError: + self.max_y = 0 + return self.max_y + + def draw(self): + + if not self.lines: + return + + if self.title: + print 'set title "%s"' % self.title + print 'set key %s' % self.key_location + + min_y = self.get_min_y() + max_y = self.max_graph_value(self.get_max_y()) + range = max_y - min_y + incr = range / 10.0 + start = min_y + (max_y / 2.0) + (2.0 * incr) + position = [ start - (i * incr) for i in xrange(5) ] + + inx = 1 + for line in self.lines: + line.print_label(inx, line.points[0][0]-1, + position[(inx-1) % len(position)]) + inx += 1 + + plot_strings = [ self.plot_string(l) for l in self.lines ] + print 'plot ' + ', \\\n '.join(plot_strings) + + for line in self.lines: + line.print_points() + + + +def untar(fname): + import tarfile + tar = tarfile.open(name=fname, mode='r') + for tarinfo in tar: + tar.extract(tarinfo) + tar.close() + +def unzip(fname): + import zipfile + zf = zipfile.ZipFile(fname, 'r') + for name in zf.namelist(): + dir = os.path.dirname(name) + try: + os.makedirs(dir) + except: + pass + open(name, 'w').write(zf.read(name)) + +def read_tree(dir): + def read_files(arg, dirname, fnames): + for fn in fnames: + fn = os.path.join(dirname, fn) + if os.path.isfile(fn): + open(fn, 'rb').read() + os.path.walk('.', read_files, None) + +def redirect_to_file(command, log): + return '%s > %s 2>&1' % (command, log) + +def tee_to_file(command, log): + return '%s 2>&1 | tee %s' % (command, log) + + + +class SConsTimer: + """ + Usage: scons-time SUBCOMMAND [ARGUMENTS] + Type "scons-time help SUBCOMMAND" for help on a specific subcommand. + + Available subcommands: + func Extract test-run data for a function + help Provides help + mem Extract --debug=memory data from test runs + obj Extract --debug=count data from test runs + time Extract --debug=time data from test runs + run Runs a test configuration + """ + + name = 'scons-time' + name_spaces = ' '*len(name) + + def makedict(**kw): + return kw + + default_settings = makedict( + aegis = 'aegis', + aegis_project = None, + chdir = None, + config_file = None, + initial_commands = [], + key_location = 'bottom left', + orig_cwd = os.getcwd(), + outdir = None, + prefix = '', + python = '"%s"' % sys.executable, + redirect = redirect_to_file, + scons = None, + scons_flags = '--debug=count --debug=memory --debug=time --debug=memoizer', + scons_lib_dir = None, + scons_wrapper = None, + startup_targets = '--help', + subdir = None, + subversion_url = None, + svn = 'svn', + svn_co_flag = '-q', + tar = 'tar', + targets = '', + targets0 = None, + targets1 = None, + targets2 = None, + title = None, + unzip = 'unzip', + verbose = False, + vertical_bars = [], + + unpack_map = { + '.tar.gz' : (untar, '%(tar)s xzf %%s'), + '.tgz' : (untar, '%(tar)s xzf %%s'), + '.tar' : (untar, '%(tar)s xf %%s'), + '.zip' : (unzip, '%(unzip)s %%s'), + }, + ) + + run_titles = [ + 'Startup', + 'Full build', + 'Up-to-date build', + ] + + run_commands = [ + '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof0)s %(targets0)s', + '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof1)s %(targets1)s', + '%(python)s %(scons_wrapper)s %(scons_flags)s --profile=%(prof2)s %(targets2)s', + ] + + stages = [ + 'pre-read', + 'post-read', + 'pre-build', + 'post-build', + ] + + stage_strings = { + 'pre-read' : 'Memory before reading SConscript files:', + 'post-read' : 'Memory after reading SConscript files:', + 'pre-build' : 'Memory before building targets:', + 'post-build' : 'Memory after building targets:', + } + + memory_string_all = 'Memory ' + + default_stage = stages[-1] + + time_strings = { + 'total' : 'Total build time', + 'SConscripts' : 'Total SConscript file execution time', + 'SCons' : 'Total SCons execution time', + 'commands' : 'Total command execution time', + } + + time_string_all = 'Total .* time' + + # + + def __init__(self): + self.__dict__.update(self.default_settings) + + # Functions for displaying and executing commands. + + def subst(self, x, dictionary): + try: + return x % dictionary + except TypeError: + # x isn't a string (it's probably a Python function), + # so just return it. + return x + + def subst_variables(self, command, dictionary): + """ + Substitutes (via the format operator) the values in the specified + dictionary into the specified command. + + The command can be an (action, string) tuple. In all cases, we + perform substitution on strings and don't worry if something isn't + a string. (It's probably a Python function to be executed.) + """ + try: + command + '' + except TypeError: + action = command[0] + string = command[1] + args = command[2:] + else: + action = command + string = action + args = (()) + action = self.subst(action, dictionary) + string = self.subst(string, dictionary) + return (action, string, args) + + def _do_not_display(self, msg, *args): + pass + + def display(self, msg, *args): + """ + Displays the specified message. + + Each message is prepended with a standard prefix of our name + plus the time. + """ + if callable(msg): + msg = msg(*args) + else: + msg = msg % args + if msg is None: + return + fmt = '%s[%s]: %s\n' + sys.stdout.write(fmt % (self.name, time.strftime('%H:%M:%S'), msg)) + + def _do_not_execute(self, action, *args): + pass + + def execute(self, action, *args): + """ + Executes the specified action. + + The action is called if it's a callable Python function, and + otherwise passed to os.system(). + """ + if callable(action): + action(*args) + else: + os.system(action % args) + + def run_command_list(self, commands, dict): + """ + Executes a list of commands, substituting values from the + specified dictionary. + """ + commands = [ self.subst_variables(c, dict) for c in commands ] + for action, string, args in commands: + self.display(string, *args) + sys.stdout.flush() + status = self.execute(action, *args) + if status: + sys.exit(status) + + def log_display(self, command, log): + command = self.subst(command, self.__dict__) + if log: + command = self.redirect(command, log) + return command + + def log_execute(self, command, log): + command = self.subst(command, self.__dict__) + output = os.popen(command).read() + if self.verbose: + sys.stdout.write(output) + open(log, 'wb').write(output) + + # + + def archive_splitext(self, path): + """ + Splits an archive name into a filename base and extension. + + This is like os.path.splitext() (which it calls) except that it + also looks for '.tar.gz' and treats it as an atomic extensions. + """ + if path.endswith('.tar.gz'): + return path[:-7], path[-7:] + else: + return os.path.splitext(path) + + def args_to_files(self, args, tail=None): + """ + Takes a list of arguments, expands any glob patterns, and + returns the last "tail" files from the list. + """ + files = [] + for a in args: + g = glob.glob(a) + g.sort() + files.extend(g) + + if tail: + files = files[-tail:] + + return files + + def ascii_table(self, files, columns, + line_function, file_function=lambda x: x, + *args, **kw): + + header_fmt = ' '.join(['%12s'] * len(columns)) + line_fmt = header_fmt + ' %s' + + print header_fmt % columns + + for file in files: + t = line_function(file, *args, **kw) + if t is None: + t = [] + diff = len(columns) - len(t) + if diff > 0: + t += [''] * diff + t.append(file_function(file)) + print line_fmt % tuple(t) + + def collect_results(self, files, function, *args, **kw): + results = {} + + for file in files: + base = os.path.splitext(file)[0] + run, index = string.split(base, '-')[-2:] + + run = int(run) + index = int(index) + + value = function(file, *args, **kw) + + try: + r = results[index] + except KeyError: + r = [] + results[index] = r + r.append((run, value)) + + return results + + def doc_to_help(self, obj): + """ + Translates an object's __doc__ string into help text. + + This strips a consistent number of spaces from each line in the + help text, essentially "outdenting" the text to the left-most + column. + """ + doc = obj.__doc__ + if doc is None: + return '' + return self.outdent(doc) + + def find_next_run_number(self, dir, prefix): + """ + Returns the next run number in a directory for the specified prefix. + + Examines the contents the specified directory for files with the + specified prefix, extracts the run numbers from each file name, + and returns the next run number after the largest it finds. + """ + x = re.compile(re.escape(prefix) + '-([0-9]+).*') + matches = map(lambda e, x=x: x.match(e), os.listdir(dir)) + matches = filter(None, matches) + if not matches: + return 0 + run_numbers = map(lambda m: int(m.group(1)), matches) + return int(max(run_numbers)) + 1 + + def gnuplot_results(self, results, fmt='%s %.3f'): + """ + Prints out a set of results in Gnuplot format. + """ + gp = Gnuplotter(self.title, self.key_location) + + indices = results.keys() + indices.sort() + + for i in indices: + try: + t = self.run_titles[i] + except IndexError: + t = '??? %s ???' % i + results[i].sort() + gp.line(results[i], i+1, t, None, t, fmt=fmt) + + for bar_tuple in self.vertical_bars: + try: + x, type, label, comment = bar_tuple + except ValueError: + x, type, label = bar_tuple + comment = label + gp.vertical_bar(x, type, label, comment) + + gp.draw() + + def logfile_name(self, invocation): + """ + Returns the absolute path of a log file for the specificed + invocation number. + """ + name = self.prefix_run + '-%d.log' % invocation + return os.path.join(self.outdir, name) + + def outdent(self, s): + """ + Strip as many spaces from each line as are found at the beginning + of the first line in the list. + """ + lines = s.split('\n') + if lines[0] == '': + lines = lines[1:] + spaces = re.match(' *', lines[0]).group(0) + def strip_initial_spaces(l, s=spaces): + if l.startswith(spaces): + l = l[len(spaces):] + return l + return '\n'.join([ strip_initial_spaces(l) for l in lines ]) + '\n' + + def profile_name(self, invocation): + """ + Returns the absolute path of a profile file for the specified + invocation number. + """ + name = self.prefix_run + '-%d.prof' % invocation + return os.path.join(self.outdir, name) + + def set_env(self, key, value): + os.environ[key] = value + + # + + def get_debug_times(self, file, time_string=None): + """ + Fetch times from the --debug=time strings in the specified file. + """ + if time_string is None: + search_string = self.time_string_all + else: + search_string = time_string + contents = open(file).read() + if not contents: + sys.stderr.write('file %s has no contents!\n' % repr(file)) + return None + result = re.findall(r'%s: ([\d\.]*)' % search_string, contents)[-4:] + result = [ float(r) for r in result ] + if not time_string is None: + try: + result = result[0] + except IndexError: + sys.stderr.write('file %s has no results!\n' % repr(file)) + return None + return result + + def get_function_profile(self, file, function): + """ + Returns the file, line number, function name, and cumulative time. + """ + try: + import pstats + except ImportError, e: + sys.stderr.write('%s: func: %s\n' % (self.name, e)) + sys.stderr.write('%s This version of Python is missing the profiler.\n' % self.name_spaces) + sys.stderr.write('%s Cannot use the "func" subcommand.\n' % self.name_spaces) + sys.exit(1) + statistics = pstats.Stats(file).stats + matches = [ e for e in statistics.items() if e[0][2] == function ] + r = matches[0] + return r[0][0], r[0][1], r[0][2], r[1][3] + + def get_function_time(self, file, function): + """ + Returns just the cumulative time for the specified function. + """ + return self.get_function_profile(file, function)[3] + + def get_memory(self, file, memory_string=None): + """ + Returns a list of integers of the amount of memory used. The + default behavior is to return all the stages. + """ + if memory_string is None: + search_string = self.memory_string_all + else: + search_string = memory_string + lines = open(file).readlines() + lines = [ l for l in lines if l.startswith(search_string) ][-4:] + result = [ int(l.split()[-1]) for l in lines[-4:] ] + if len(result) == 1: + result = result[0] + return result + + def get_object_counts(self, file, object_name, index=None): + """ + Returns the counts of the specified object_name. + """ + object_string = ' ' + object_name + '\n' + lines = open(file).readlines() + line = [ l for l in lines if l.endswith(object_string) ][0] + result = [ int(field) for field in line.split()[:4] ] + if index is not None: + result = result[index] + return result + + # + + command_alias = {} + + def execute_subcommand(self, argv): + """ + Executes the do_*() function for the specified subcommand (argv[0]). + """ + if not argv: + return + cmdName = self.command_alias.get(argv[0], argv[0]) + try: + func = getattr(self, 'do_' + cmdName) + except AttributeError: + return self.default(argv) + try: + return func(argv) + except TypeError, e: + sys.stderr.write("%s %s: %s\n" % (self.name, cmdName, e)) + import traceback + traceback.print_exc(file=sys.stderr) + sys.stderr.write("Try '%s help %s'\n" % (self.name, cmdName)) + + def default(self, argv): + """ + The default behavior for an unknown subcommand. Prints an + error message and exits. + """ + sys.stderr.write('%s: Unknown subcommand "%s".\n' % (self.name, argv[0])) + sys.stderr.write('Type "%s help" for usage.\n' % self.name) + sys.exit(1) + + # + + def do_help(self, argv): + """ + """ + if argv[1:]: + for arg in argv[1:]: + try: + func = getattr(self, 'do_' + arg) + except AttributeError: + sys.stderr.write('%s: No help for "%s"\n' % (self.name, arg)) + else: + try: + help = getattr(self, 'help_' + arg) + except AttributeError: + sys.stdout.write(self.doc_to_help(func)) + sys.stdout.flush() + else: + help() + else: + doc = self.doc_to_help(self.__class__) + if doc: + sys.stdout.write(doc) + sys.stdout.flush() + return None + + # + + def help_func(self): + help = """\ + Usage: scons-time func [OPTIONS] FILE [...] + + -C DIR, --chdir=DIR Change to DIR before looking for files + -f FILE, --file=FILE Read configuration from specified FILE + --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT + --func=NAME, --function=NAME Report time for function NAME + -h, --help Print this help and exit + -p STRING, --prefix=STRING Use STRING as log file/profile prefix + -t NUMBER, --tail=NUMBER Only report the last NUMBER files + --title=TITLE Specify the output plot TITLE + """ + sys.stdout.write(self.outdent(help)) + sys.stdout.flush() + + def do_func(self, argv): + """ + """ + format = 'ascii' + function_name = '_main' + tail = None + + short_opts = '?C:f:hp:t:' + + long_opts = [ + 'chdir=', + 'file=', + 'fmt=', + 'format=', + 'func=', + 'function=', + 'help', + 'prefix=', + 'tail=', + 'title=', + ] + + opts, args = getopt.getopt(argv[1:], short_opts, long_opts) + + for o, a in opts: + if o in ('-C', '--chdir'): + self.chdir = a + elif o in ('-f', '--file'): + self.config_file = a + elif o in ('--fmt', '--format'): + format = a + elif o in ('--func', '--function'): + function_name = a + elif o in ('-?', '-h', '--help'): + self.do_help(['help', 'func']) + sys.exit(0) + elif o in ('--max',): + max_time = int(a) + elif o in ('-p', '--prefix'): + self.prefix = a + elif o in ('-t', '--tail'): + tail = int(a) + elif o in ('--title',): + self.title = a + + if self.config_file: + exec open(self.config_file, 'rU').read() in self.__dict__ + + if self.chdir: + os.chdir(self.chdir) + + if not args: + + pattern = '%s*.prof' % self.prefix + args = self.args_to_files([pattern], tail) + + if not args: + if self.chdir: + directory = self.chdir + else: + directory = os.getcwd() + + sys.stderr.write('%s: func: No arguments specified.\n' % self.name) + sys.stderr.write('%s No %s*.prof files found in "%s".\n' % (self.name_spaces, self.prefix, directory)) + sys.stderr.write('%s Type "%s help func" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + + else: + + args = self.args_to_files(args, tail) + + cwd_ = os.getcwd() + os.sep + + if format == 'ascii': + + for file in args: + try: + f, line, func, time = \ + self.get_function_profile(file, function_name) + except ValueError, e: + sys.stderr.write("%s: func: %s: %s\n" % + (self.name, file, e)) + else: + if f.startswith(cwd_): + f = f[len(cwd_):] + print "%.3f %s:%d(%s)" % (time, f, line, func) + + elif format == 'gnuplot': + + results = self.collect_results(args, self.get_function_time, + function_name) + + self.gnuplot_results(results) + + else: + + sys.stderr.write('%s: func: Unknown format "%s".\n' % (self.name, format)) + sys.exit(1) + + # + + def help_mem(self): + help = """\ + Usage: scons-time mem [OPTIONS] FILE [...] + + -C DIR, --chdir=DIR Change to DIR before looking for files + -f FILE, --file=FILE Read configuration from specified FILE + --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT + -h, --help Print this help and exit + -p STRING, --prefix=STRING Use STRING as log file/profile prefix + --stage=STAGE Plot memory at the specified stage: + pre-read, post-read, pre-build, + post-build (default: post-build) + -t NUMBER, --tail=NUMBER Only report the last NUMBER files + --title=TITLE Specify the output plot TITLE + """ + sys.stdout.write(self.outdent(help)) + sys.stdout.flush() + + def do_mem(self, argv): + + format = 'ascii' + logfile_path = lambda x: x + stage = self.default_stage + tail = None + + short_opts = '?C:f:hp:t:' + + long_opts = [ + 'chdir=', + 'file=', + 'fmt=', + 'format=', + 'help', + 'prefix=', + 'stage=', + 'tail=', + 'title=', + ] + + opts, args = getopt.getopt(argv[1:], short_opts, long_opts) + + for o, a in opts: + if o in ('-C', '--chdir'): + self.chdir = a + elif o in ('-f', '--file'): + self.config_file = a + elif o in ('--fmt', '--format'): + format = a + elif o in ('-?', '-h', '--help'): + self.do_help(['help', 'mem']) + sys.exit(0) + elif o in ('-p', '--prefix'): + self.prefix = a + elif o in ('--stage',): + if not a in self.stages: + sys.stderr.write('%s: mem: Unrecognized stage "%s".\n' % (self.name, a)) + sys.exit(1) + stage = a + elif o in ('-t', '--tail'): + tail = int(a) + elif o in ('--title',): + self.title = a + + if self.config_file: + HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__) + + if self.chdir: + os.chdir(self.chdir) + logfile_path = lambda x, c=self.chdir: os.path.join(c, x) + + if not args: + + pattern = '%s*.log' % self.prefix + args = self.args_to_files([pattern], tail) + + if not args: + if self.chdir: + directory = self.chdir + else: + directory = os.getcwd() + + sys.stderr.write('%s: mem: No arguments specified.\n' % self.name) + sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory)) + sys.stderr.write('%s Type "%s help mem" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + + else: + + args = self.args_to_files(args, tail) + + cwd_ = os.getcwd() + os.sep + + if format == 'ascii': + + self.ascii_table(args, tuple(self.stages), self.get_memory, logfile_path) + + elif format == 'gnuplot': + + results = self.collect_results(args, self.get_memory, + self.stage_strings[stage]) + + self.gnuplot_results(results) + + else: + + sys.stderr.write('%s: mem: Unknown format "%s".\n' % (self.name, format)) + sys.exit(1) + + return 0 + + # + + def help_obj(self): + help = """\ + Usage: scons-time obj [OPTIONS] OBJECT FILE [...] + + -C DIR, --chdir=DIR Change to DIR before looking for files + -f FILE, --file=FILE Read configuration from specified FILE + --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT + -h, --help Print this help and exit + -p STRING, --prefix=STRING Use STRING as log file/profile prefix + --stage=STAGE Plot memory at the specified stage: + pre-read, post-read, pre-build, + post-build (default: post-build) + -t NUMBER, --tail=NUMBER Only report the last NUMBER files + --title=TITLE Specify the output plot TITLE + """ + sys.stdout.write(self.outdent(help)) + sys.stdout.flush() + + def do_obj(self, argv): + + format = 'ascii' + logfile_path = lambda x: x + stage = self.default_stage + tail = None + + short_opts = '?C:f:hp:t:' + + long_opts = [ + 'chdir=', + 'file=', + 'fmt=', + 'format=', + 'help', + 'prefix=', + 'stage=', + 'tail=', + 'title=', + ] + + opts, args = getopt.getopt(argv[1:], short_opts, long_opts) + + for o, a in opts: + if o in ('-C', '--chdir'): + self.chdir = a + elif o in ('-f', '--file'): + self.config_file = a + elif o in ('--fmt', '--format'): + format = a + elif o in ('-?', '-h', '--help'): + self.do_help(['help', 'obj']) + sys.exit(0) + elif o in ('-p', '--prefix'): + self.prefix = a + elif o in ('--stage',): + if not a in self.stages: + sys.stderr.write('%s: obj: Unrecognized stage "%s".\n' % (self.name, a)) + sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + stage = a + elif o in ('-t', '--tail'): + tail = int(a) + elif o in ('--title',): + self.title = a + + if not args: + sys.stderr.write('%s: obj: Must specify an object name.\n' % self.name) + sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + + object_name = args.pop(0) + + if self.config_file: + HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__) + + if self.chdir: + os.chdir(self.chdir) + logfile_path = lambda x, c=self.chdir: os.path.join(c, x) + + if not args: + + pattern = '%s*.log' % self.prefix + args = self.args_to_files([pattern], tail) + + if not args: + if self.chdir: + directory = self.chdir + else: + directory = os.getcwd() + + sys.stderr.write('%s: obj: No arguments specified.\n' % self.name) + sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory)) + sys.stderr.write('%s Type "%s help obj" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + + else: + + args = self.args_to_files(args, tail) + + cwd_ = os.getcwd() + os.sep + + if format == 'ascii': + + self.ascii_table(args, tuple(self.stages), self.get_object_counts, logfile_path, object_name) + + elif format == 'gnuplot': + + stage_index = 0 + for s in self.stages: + if stage == s: + break + stage_index = stage_index + 1 + + results = self.collect_results(args, self.get_object_counts, + object_name, stage_index) + + self.gnuplot_results(results) + + else: + + sys.stderr.write('%s: obj: Unknown format "%s".\n' % (self.name, format)) + sys.exit(1) + + return 0 + + # + + def help_run(self): + help = """\ + Usage: scons-time run [OPTIONS] [FILE ...] + + --aegis=PROJECT Use SCons from the Aegis PROJECT + --chdir=DIR Name of unpacked directory for chdir + -f FILE, --file=FILE Read configuration from specified FILE + -h, --help Print this help and exit + -n, --no-exec No execute, just print command lines + --number=NUMBER Put output in files for run NUMBER + --outdir=OUTDIR Put output files in OUTDIR + -p STRING, --prefix=STRING Use STRING as log file/profile prefix + --python=PYTHON Time using the specified PYTHON + -q, --quiet Don't print command lines + --scons=SCONS Time using the specified SCONS + --svn=URL, --subversion=URL Use SCons from Subversion URL + -v, --verbose Display output of commands + """ + sys.stdout.write(self.outdent(help)) + sys.stdout.flush() + + def do_run(self, argv): + """ + """ + run_number_list = [None] + + short_opts = '?f:hnp:qs:v' + + long_opts = [ + 'aegis=', + 'file=', + 'help', + 'no-exec', + 'number=', + 'outdir=', + 'prefix=', + 'python=', + 'quiet', + 'scons=', + 'svn=', + 'subdir=', + 'subversion=', + 'verbose', + ] + + opts, args = getopt.getopt(argv[1:], short_opts, long_opts) + + for o, a in opts: + if o in ('--aegis',): + self.aegis_project = a + elif o in ('-f', '--file'): + self.config_file = a + elif o in ('-?', '-h', '--help'): + self.do_help(['help', 'run']) + sys.exit(0) + elif o in ('-n', '--no-exec'): + self.execute = self._do_not_execute + elif o in ('--number',): + run_number_list = self.split_run_numbers(a) + elif o in ('--outdir',): + self.outdir = a + elif o in ('-p', '--prefix'): + self.prefix = a + elif o in ('--python',): + self.python = a + elif o in ('-q', '--quiet'): + self.display = self._do_not_display + elif o in ('-s', '--subdir'): + self.subdir = a + elif o in ('--scons',): + self.scons = a + elif o in ('--svn', '--subversion'): + self.subversion_url = a + elif o in ('-v', '--verbose'): + self.redirect = tee_to_file + self.verbose = True + self.svn_co_flag = '' + + if not args and not self.config_file: + sys.stderr.write('%s: run: No arguments or -f config file specified.\n' % self.name) + sys.stderr.write('%s Type "%s help run" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + + if self.config_file: + exec open(self.config_file, 'rU').read() in self.__dict__ + + if args: + self.archive_list = args + + archive_file_name = os.path.split(self.archive_list[0])[1] + + if not self.subdir: + self.subdir = self.archive_splitext(archive_file_name)[0] + + if not self.prefix: + self.prefix = self.archive_splitext(archive_file_name)[0] + + prepare = None + if self.subversion_url: + prepare = self.prep_subversion_run + elif self.aegis_project: + prepare = self.prep_aegis_run + + for run_number in run_number_list: + self.individual_run(run_number, self.archive_list, prepare) + + def split_run_numbers(self, s): + result = [] + for n in s.split(','): + try: + x, y = n.split('-') + except ValueError: + result.append(int(n)) + else: + result.extend(range(int(x), int(y)+1)) + return result + + def scons_path(self, dir): + return os.path.join(dir, 'src', 'script', 'scons.py') + + def scons_lib_dir_path(self, dir): + return os.path.join(dir, 'src', 'engine') + + def prep_aegis_run(self, commands, removals): + self.aegis_tmpdir = make_temp_file(prefix = self.name + '-aegis-') + removals.append((shutil.rmtree, 'rm -rf %%s', self.aegis_tmpdir)) + + self.aegis_parent_project = os.path.splitext(self.aegis_project)[0] + self.scons = self.scons_path(self.aegis_tmpdir) + self.scons_lib_dir = self.scons_lib_dir_path(self.aegis_tmpdir) + + commands.extend([ + 'mkdir %(aegis_tmpdir)s', + (lambda: os.chdir(self.aegis_tmpdir), 'cd %(aegis_tmpdir)s'), + '%(aegis)s -cp -ind -p %(aegis_parent_project)s .', + '%(aegis)s -cp -ind -p %(aegis_project)s -delta %(run_number)s .', + ]) + + def prep_subversion_run(self, commands, removals): + self.svn_tmpdir = make_temp_file(prefix = self.name + '-svn-') + removals.append((shutil.rmtree, 'rm -rf %%s', self.svn_tmpdir)) + + self.scons = self.scons_path(self.svn_tmpdir) + self.scons_lib_dir = self.scons_lib_dir_path(self.svn_tmpdir) + + commands.extend([ + 'mkdir %(svn_tmpdir)s', + '%(svn)s co %(svn_co_flag)s -r %(run_number)s %(subversion_url)s %(svn_tmpdir)s', + ]) + + def individual_run(self, run_number, archive_list, prepare=None): + """ + Performs an individual run of the default SCons invocations. + """ + + commands = [] + removals = [] + + if prepare: + prepare(commands, removals) + + save_scons = self.scons + save_scons_wrapper = self.scons_wrapper + save_scons_lib_dir = self.scons_lib_dir + + if self.outdir is None: + self.outdir = self.orig_cwd + elif not os.path.isabs(self.outdir): + self.outdir = os.path.join(self.orig_cwd, self.outdir) + + if self.scons is None: + self.scons = self.scons_path(self.orig_cwd) + + if self.scons_lib_dir is None: + self.scons_lib_dir = self.scons_lib_dir_path(self.orig_cwd) + + if self.scons_wrapper is None: + self.scons_wrapper = self.scons + + if not run_number: + run_number = self.find_next_run_number(self.outdir, self.prefix) + + self.run_number = str(run_number) + + self.prefix_run = self.prefix + '-%03d' % run_number + + if self.targets0 is None: + self.targets0 = self.startup_targets + if self.targets1 is None: + self.targets1 = self.targets + if self.targets2 is None: + self.targets2 = self.targets + + self.tmpdir = make_temp_file(prefix = self.name + '-') + + commands.extend([ + 'mkdir %(tmpdir)s', + + (os.chdir, 'cd %%s', self.tmpdir), + ]) + + for archive in archive_list: + if not os.path.isabs(archive): + archive = os.path.join(self.orig_cwd, archive) + if os.path.isdir(archive): + dest = os.path.split(archive)[1] + commands.append((shutil.copytree, 'cp -r %%s %%s', archive, dest)) + else: + suffix = self.archive_splitext(archive)[1] + unpack_command = self.unpack_map.get(suffix) + if not unpack_command: + dest = os.path.split(archive)[1] + commands.append((shutil.copyfile, 'cp %%s %%s', archive, dest)) + else: + commands.append(unpack_command + (archive,)) + + commands.extend([ + (os.chdir, 'cd %%s', self.subdir), + ]) + + commands.extend(self.initial_commands) + + commands.extend([ + (lambda: read_tree('.'), + 'find * -type f | xargs cat > /dev/null'), + + (self.set_env, 'export %%s=%%s', + 'SCONS_LIB_DIR', self.scons_lib_dir), + + '%(python)s %(scons_wrapper)s --version', + ]) + + index = 0 + for run_command in self.run_commands: + setattr(self, 'prof%d' % index, self.profile_name(index)) + c = ( + self.log_execute, + self.log_display, + run_command, + self.logfile_name(index), + ) + commands.append(c) + index = index + 1 + + commands.extend([ + (os.chdir, 'cd %%s', self.orig_cwd), + ]) + + if not os.environ.get('PRESERVE'): + commands.extend(removals) + + commands.append((shutil.rmtree, 'rm -rf %%s', self.tmpdir)) + + self.run_command_list(commands, self.__dict__) + + self.scons = save_scons + self.scons_lib_dir = save_scons_lib_dir + self.scons_wrapper = save_scons_wrapper + + # + + def help_time(self): + help = """\ + Usage: scons-time time [OPTIONS] FILE [...] + + -C DIR, --chdir=DIR Change to DIR before looking for files + -f FILE, --file=FILE Read configuration from specified FILE + --fmt=FORMAT, --format=FORMAT Print data in specified FORMAT + -h, --help Print this help and exit + -p STRING, --prefix=STRING Use STRING as log file/profile prefix + -t NUMBER, --tail=NUMBER Only report the last NUMBER files + --which=TIMER Plot timings for TIMER: total, + SConscripts, SCons, commands. + """ + sys.stdout.write(self.outdent(help)) + sys.stdout.flush() + + def do_time(self, argv): + + format = 'ascii' + logfile_path = lambda x: x + tail = None + which = 'total' + + short_opts = '?C:f:hp:t:' + + long_opts = [ + 'chdir=', + 'file=', + 'fmt=', + 'format=', + 'help', + 'prefix=', + 'tail=', + 'title=', + 'which=', + ] + + opts, args = getopt.getopt(argv[1:], short_opts, long_opts) + + for o, a in opts: + if o in ('-C', '--chdir'): + self.chdir = a + elif o in ('-f', '--file'): + self.config_file = a + elif o in ('--fmt', '--format'): + format = a + elif o in ('-?', '-h', '--help'): + self.do_help(['help', 'time']) + sys.exit(0) + elif o in ('-p', '--prefix'): + self.prefix = a + elif o in ('-t', '--tail'): + tail = int(a) + elif o in ('--title',): + self.title = a + elif o in ('--which',): + if not a in self.time_strings.keys(): + sys.stderr.write('%s: time: Unrecognized timer "%s".\n' % (self.name, a)) + sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + which = a + + if self.config_file: + HACK_for_exec(open(self.config_file, 'rU').read(), self.__dict__) + + if self.chdir: + os.chdir(self.chdir) + logfile_path = lambda x, c=self.chdir: os.path.join(c, x) + + if not args: + + pattern = '%s*.log' % self.prefix + args = self.args_to_files([pattern], tail) + + if not args: + if self.chdir: + directory = self.chdir + else: + directory = os.getcwd() + + sys.stderr.write('%s: time: No arguments specified.\n' % self.name) + sys.stderr.write('%s No %s*.log files found in "%s".\n' % (self.name_spaces, self.prefix, directory)) + sys.stderr.write('%s Type "%s help time" for help.\n' % (self.name_spaces, self.name)) + sys.exit(1) + + else: + + args = self.args_to_files(args, tail) + + cwd_ = os.getcwd() + os.sep + + if format == 'ascii': + + columns = ("Total", "SConscripts", "SCons", "commands") + self.ascii_table(args, columns, self.get_debug_times, logfile_path) + + elif format == 'gnuplot': + + results = self.collect_results(args, self.get_debug_times, + self.time_strings[which]) + + self.gnuplot_results(results, fmt='%s %.6f') + + else: + + sys.stderr.write('%s: time: Unknown format "%s".\n' % (self.name, format)) + sys.exit(1) + +if __name__ == '__main__': + opts, args = getopt.getopt(sys.argv[1:], 'h?V', ['help', 'version']) + + ST = SConsTimer() + + for o, a in opts: + if o in ('-?', '-h', '--help'): + ST.do_help(['help']) + sys.exit(0) + elif o in ('-V', '--version'): + sys.stdout.write('scons-time version\n') + sys.exit(0) + + if not args: + sys.stderr.write('Type "%s help" for usage.\n' % ST.name) + sys.exit(1) + + ST.execute_subcommand(args) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/script/scons.bat b/src/script/scons.bat new file mode 100644 index 0000000..566387a --- /dev/null +++ b/src/script/scons.bat @@ -0,0 +1,31 @@ +@REM Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +@REM src/script/scons.bat 4577 2009/12/27 19:44:43 scons +@echo off +set SCONS_ERRORLEVEL= +if "%OS%" == "Windows_NT" goto WinNT + +@REM for 9x/Me you better not have more than 9 args +python -c "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-1.2.0.d20091224'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons-1.2.0.d20091224'), join(sys.prefix, 'scons')] + sys.path; import SCons.Script; SCons.Script.main()" %1 %2 %3 %4 %5 %6 %7 %8 %9 +@REM no way to set exit status of this script for 9x/Me +goto endscons + +@REM Credit where credit is due: we return the exit code despite our +@REM use of setlocal+endlocal using a technique from Bear's Journal: +@REM http://code-bear.com/bearlog/2007/06/01/getting-the-exit-code-from-a-batch-file-that-is-run-from-a-python-program/ + +:WinNT +setlocal +@REM ensure the script will be executed with the Python it was installed for +set path=%~dp0;%~dp0..;%path% +python -c "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-1.2.0.d20091224'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons-1.2.0.d20091224'), join(sys.prefix, 'scons')] + sys.path; import SCons.Script; SCons.Script.main()" %* +endlocal & set SCONS_ERRORLEVEL=%ERRORLEVEL% + +if NOT "%COMSPEC%" == "%SystemRoot%\system32\cmd.exe" goto returncode +if errorlevel 9009 echo you do not have python in your PATH +goto endscons + +:returncode +exit /B %SCONS_ERRORLEVEL% + +:endscons +call :returncode %SCONS_ERRORLEVEL% diff --git a/src/script/scons.py b/src/script/scons.py new file mode 100644 index 0000000..6e21a09 --- /dev/null +++ b/src/script/scons.py @@ -0,0 +1,184 @@ +#! /usr/bin/env python +# +# SCons - a Software Constructor +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/script/scons.py 4577 2009/12/27 19:44:43 scons" + +__version__ = "1.2.0.d20091224" + +__build__ = "r4577[MODIFIED]" + +__buildsys__ = "scons-dev" + +__date__ = "2009/12/27 19:44:43" + +__developer__ = "scons" + +import os +import os.path +import sys + +############################################################################## +# BEGIN STANDARD SCons SCRIPT HEADER +# +# This is the cut-and-paste logic so that a self-contained script can +# interoperate correctly with different SCons versions and installation +# locations for the engine. If you modify anything in this section, you +# should also change other scripts that use this same header. +############################################################################## + +# Strip the script directory from sys.path() so on case-insensitive +# (WIN32) systems Python doesn't think that the "scons" script is the +# "SCons" package. Replace it with our own library directories +# (version-specific first, in case they installed by hand there, +# followed by generic) so we pick up the right version of the build +# engine modules if they're in either directory. + + +# Check to see if the python version is > 3.0 which is currently unsupported +# If so exit with error message +try: + if sys.version_info >= (3,0,0): + msg = "scons: *** SCons version %s does not run under Python version %s.\n" + sys.stderr.write(msg % (__version__, sys.version.split()[0])) + sys.exit(1) +except AttributeError: + # Pre-1.6 Python has no sys.version_info + # No need to check version as we then know the version is < 3.0.0 and supported + pass + +script_dir = sys.path[0] + +if script_dir in sys.path: + sys.path.remove(script_dir) + +libs = [] + +if os.environ.has_key("SCONS_LIB_DIR"): + libs.append(os.environ["SCONS_LIB_DIR"]) + +local_version = 'scons-local-' + __version__ +local = 'scons-local' +if script_dir: + local_version = os.path.join(script_dir, local_version) + local = os.path.join(script_dir, local) +libs.append(os.path.abspath(local_version)) +libs.append(os.path.abspath(local)) + +scons_version = 'scons-%s' % __version__ + +prefs = [] + +if sys.platform == 'win32': + # sys.prefix is (likely) C:\Python*; + # check only C:\Python*. + prefs.append(sys.prefix) + prefs.append(os.path.join(sys.prefix, 'Lib', 'site-packages')) +else: + # On other (POSIX) platforms, things are more complicated due to + # the variety of path names and library locations. Try to be smart + # about it. + if script_dir == 'bin': + # script_dir is `pwd`/bin; + # check `pwd`/lib/scons*. + prefs.append(os.getcwd()) + else: + if script_dir == '.' or script_dir == '': + script_dir = os.getcwd() + head, tail = os.path.split(script_dir) + if tail == "bin": + # script_dir is /foo/bin; + # check /foo/lib/scons*. + prefs.append(head) + + head, tail = os.path.split(sys.prefix) + if tail == "usr": + # sys.prefix is /foo/usr; + # check /foo/usr/lib/scons* first, + # then /foo/usr/local/lib/scons*. + prefs.append(sys.prefix) + prefs.append(os.path.join(sys.prefix, "local")) + elif tail == "local": + h, t = os.path.split(head) + if t == "usr": + # sys.prefix is /foo/usr/local; + # check /foo/usr/local/lib/scons* first, + # then /foo/usr/lib/scons*. + prefs.append(sys.prefix) + prefs.append(head) + else: + # sys.prefix is /foo/local; + # check only /foo/local/lib/scons*. + prefs.append(sys.prefix) + else: + # sys.prefix is /foo (ends in neither /usr or /local); + # check only /foo/lib/scons*. + prefs.append(sys.prefix) + + temp = map(lambda x: os.path.join(x, 'lib'), prefs) + temp.extend(map(lambda x: os.path.join(x, + 'lib', + 'python' + sys.version[:3], + 'site-packages'), + prefs)) + prefs = temp + + # Add the parent directory of the current python's library to the + # preferences. On SuSE-91/AMD64, for example, this is /usr/lib64, + # not /usr/lib. + try: + libpath = os.__file__ + except AttributeError: + pass + else: + # Split /usr/libfoo/python*/os.py to /usr/libfoo/python*. + libpath, tail = os.path.split(libpath) + # Split /usr/libfoo/python* to /usr/libfoo + libpath, tail = os.path.split(libpath) + # Check /usr/libfoo/scons*. + prefs.append(libpath) + +# Look first for 'scons-__version__' in all of our preference libs, +# then for 'scons'. +libs.extend(map(lambda x: os.path.join(x, scons_version), prefs)) +libs.extend(map(lambda x: os.path.join(x, 'scons'), prefs)) + +sys.path = libs + sys.path + +############################################################################## +# END STANDARD SCons SCRIPT HEADER +############################################################################## + +if __name__ == "__main__": + import SCons.Script + # this does all the work, and calls sys.exit + # with the proper exit status when done. + SCons.Script.main() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/script/sconsign.py b/src/script/sconsign.py new file mode 100644 index 0000000..406c7f5 --- /dev/null +++ b/src/script/sconsign.py @@ -0,0 +1,508 @@ +#! /usr/bin/env python +# +# SCons - a Software Constructor +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/script/sconsign.py 4577 2009/12/27 19:44:43 scons" + +__version__ = "1.2.0.d20091224" + +__build__ = "r4577[MODIFIED]" + +__buildsys__ = "scons-dev" + +__date__ = "2009/12/27 19:44:43" + +__developer__ = "scons" + +import os +import os.path +import sys +import time + +############################################################################## +# BEGIN STANDARD SCons SCRIPT HEADER +# +# This is the cut-and-paste logic so that a self-contained script can +# interoperate correctly with different SCons versions and installation +# locations for the engine. If you modify anything in this section, you +# should also change other scripts that use this same header. +############################################################################## + +# Strip the script directory from sys.path() so on case-insensitive +# (WIN32) systems Python doesn't think that the "scons" script is the +# "SCons" package. Replace it with our own library directories +# (version-specific first, in case they installed by hand there, +# followed by generic) so we pick up the right version of the build +# engine modules if they're in either directory. + +script_dir = sys.path[0] + +if script_dir in sys.path: + sys.path.remove(script_dir) + +libs = [] + +if os.environ.has_key("SCONS_LIB_DIR"): + libs.append(os.environ["SCONS_LIB_DIR"]) + +local_version = 'scons-local-' + __version__ +local = 'scons-local' +if script_dir: + local_version = os.path.join(script_dir, local_version) + local = os.path.join(script_dir, local) +libs.append(os.path.abspath(local_version)) +libs.append(os.path.abspath(local)) + +scons_version = 'scons-%s' % __version__ + +prefs = [] + +if sys.platform == 'win32': + # sys.prefix is (likely) C:\Python*; + # check only C:\Python*. + prefs.append(sys.prefix) + prefs.append(os.path.join(sys.prefix, 'Lib', 'site-packages')) +else: + # On other (POSIX) platforms, things are more complicated due to + # the variety of path names and library locations. Try to be smart + # about it. + if script_dir == 'bin': + # script_dir is `pwd`/bin; + # check `pwd`/lib/scons*. + prefs.append(os.getcwd()) + else: + if script_dir == '.' or script_dir == '': + script_dir = os.getcwd() + head, tail = os.path.split(script_dir) + if tail == "bin": + # script_dir is /foo/bin; + # check /foo/lib/scons*. + prefs.append(head) + + head, tail = os.path.split(sys.prefix) + if tail == "usr": + # sys.prefix is /foo/usr; + # check /foo/usr/lib/scons* first, + # then /foo/usr/local/lib/scons*. + prefs.append(sys.prefix) + prefs.append(os.path.join(sys.prefix, "local")) + elif tail == "local": + h, t = os.path.split(head) + if t == "usr": + # sys.prefix is /foo/usr/local; + # check /foo/usr/local/lib/scons* first, + # then /foo/usr/lib/scons*. + prefs.append(sys.prefix) + prefs.append(head) + else: + # sys.prefix is /foo/local; + # check only /foo/local/lib/scons*. + prefs.append(sys.prefix) + else: + # sys.prefix is /foo (ends in neither /usr or /local); + # check only /foo/lib/scons*. + prefs.append(sys.prefix) + + temp = map(lambda x: os.path.join(x, 'lib'), prefs) + temp.extend(map(lambda x: os.path.join(x, + 'lib', + 'python' + sys.version[:3], + 'site-packages'), + prefs)) + prefs = temp + + # Add the parent directory of the current python's library to the + # preferences. On SuSE-91/AMD64, for example, this is /usr/lib64, + # not /usr/lib. + try: + libpath = os.__file__ + except AttributeError: + pass + else: + # Split /usr/libfoo/python*/os.py to /usr/libfoo/python*. + libpath, tail = os.path.split(libpath) + # Split /usr/libfoo/python* to /usr/libfoo + libpath, tail = os.path.split(libpath) + # Check /usr/libfoo/scons*. + prefs.append(libpath) + +# Look first for 'scons-__version__' in all of our preference libs, +# then for 'scons'. +libs.extend(map(lambda x: os.path.join(x, scons_version), prefs)) +libs.extend(map(lambda x: os.path.join(x, 'scons'), prefs)) + +sys.path = libs + sys.path + +############################################################################## +# END STANDARD SCons SCRIPT HEADER +############################################################################## + +import cPickle +import imp +import string +import whichdb + +import SCons.SConsign + +def my_whichdb(filename): + if filename[-7:] == ".dblite": + return "SCons.dblite" + try: + f = open(filename + ".dblite", "rb") + f.close() + return "SCons.dblite" + except IOError: + pass + return _orig_whichdb(filename) + +_orig_whichdb = whichdb.whichdb +whichdb.whichdb = my_whichdb + +def my_import(mname): + if '.' in mname: + i = string.rfind(mname, '.') + parent = my_import(mname[:i]) + fp, pathname, description = imp.find_module(mname[i+1:], + parent.__path__) + else: + fp, pathname, description = imp.find_module(mname) + return imp.load_module(mname, fp, pathname, description) + +class Flagger: + default_value = 1 + def __setitem__(self, item, value): + self.__dict__[item] = value + self.default_value = 0 + def __getitem__(self, item): + return self.__dict__.get(item, self.default_value) + +Do_Call = None +Print_Directories = [] +Print_Entries = [] +Print_Flags = Flagger() +Verbose = 0 +Readable = 0 + +def default_mapper(entry, name): + try: + val = eval("entry."+name) + except: + val = None + return str(val) + +def map_action(entry, name): + try: + bact = entry.bact + bactsig = entry.bactsig + except AttributeError: + return None + return '%s [%s]' % (bactsig, bact) + +def map_timestamp(entry, name): + try: + timestamp = entry.timestamp + except AttributeError: + timestamp = None + if Readable and timestamp: + return "'" + time.ctime(timestamp) + "'" + else: + return str(timestamp) + +def map_bkids(entry, name): + try: + bkids = entry.bsources + entry.bdepends + entry.bimplicit + bkidsigs = entry.bsourcesigs + entry.bdependsigs + entry.bimplicitsigs + except AttributeError: + return None + result = [] + for i in xrange(len(bkids)): + result.append(nodeinfo_string(bkids[i], bkidsigs[i], " ")) + if result == []: + return None + return string.join(result, "\n ") + +map_field = { + 'action' : map_action, + 'timestamp' : map_timestamp, + 'bkids' : map_bkids, +} + +map_name = { + 'implicit' : 'bkids', +} + +def field(name, entry, verbose=Verbose): + if not Print_Flags[name]: + return None + fieldname = map_name.get(name, name) + mapper = map_field.get(fieldname, default_mapper) + val = mapper(entry, name) + if verbose: + val = name + ": " + val + return val + +def nodeinfo_raw(name, ninfo, prefix=""): + # This just formats the dictionary, which we would normally use str() + # to do, except that we want the keys sorted for deterministic output. + d = ninfo.__dict__ + try: + keys = ninfo.field_list + ['_version_id'] + except AttributeError: + keys = d.keys() + keys.sort() + l = [] + for k in keys: + l.append('%s: %s' % (repr(k), repr(d.get(k)))) + if '\n' in name: + name = repr(name) + return name + ': {' + string.join(l, ', ') + '}' + +def nodeinfo_cooked(name, ninfo, prefix=""): + try: + field_list = ninfo.field_list + except AttributeError: + field_list = [] + f = lambda x, ni=ninfo, v=Verbose: field(x, ni, v) + if '\n' in name: + name = repr(name) + outlist = [name+':'] + filter(None, map(f, field_list)) + if Verbose: + sep = '\n ' + prefix + else: + sep = ' ' + return string.join(outlist, sep) + +nodeinfo_string = nodeinfo_cooked + +def printfield(name, entry, prefix=""): + outlist = field("implicit", entry, 0) + if outlist: + if Verbose: + print " implicit:" + print " " + outlist + outact = field("action", entry, 0) + if outact: + if Verbose: + print " action: " + outact + else: + print " " + outact + +def printentries(entries, location): + if Print_Entries: + for name in Print_Entries: + try: + entry = entries[name] + except KeyError: + sys.stderr.write("sconsign: no entry `%s' in `%s'\n" % (name, location)) + else: + try: + ninfo = entry.ninfo + except AttributeError: + print name + ":" + else: + print nodeinfo_string(name, entry.ninfo) + printfield(name, entry.binfo) + else: + names = entries.keys() + names.sort() + for name in names: + entry = entries[name] + try: + ninfo = entry.ninfo + except AttributeError: + print name + ":" + else: + print nodeinfo_string(name, entry.ninfo) + printfield(name, entry.binfo) + +class Do_SConsignDB: + def __init__(self, dbm_name, dbm): + self.dbm_name = dbm_name + self.dbm = dbm + + def __call__(self, fname): + # The *dbm modules stick their own file suffixes on the names + # that are passed in. This is causes us to jump through some + # hoops here to be able to allow the user + try: + # Try opening the specified file name. Example: + # SPECIFIED OPENED BY self.dbm.open() + # --------- ------------------------- + # .sconsign => .sconsign.dblite + # .sconsign.dblite => .sconsign.dblite.dblite + db = self.dbm.open(fname, "r") + except (IOError, OSError), e: + print_e = e + try: + # That didn't work, so try opening the base name, + # so that if the actually passed in 'sconsign.dblite' + # (for example), the dbm module will put the suffix back + # on for us and open it anyway. + db = self.dbm.open(os.path.splitext(fname)[0], "r") + except (IOError, OSError): + # That didn't work either. See if the file name + # they specified just exists (independent of the dbm + # suffix-mangling). + try: + open(fname, "r") + except (IOError, OSError), e: + # Nope, that file doesn't even exist, so report that + # fact back. + print_e = e + sys.stderr.write("sconsign: %s\n" % (print_e)) + return + except KeyboardInterrupt: + raise + except cPickle.UnpicklingError: + sys.stderr.write("sconsign: ignoring invalid `%s' file `%s'\n" % (self.dbm_name, fname)) + return + except Exception, e: + sys.stderr.write("sconsign: ignoring invalid `%s' file `%s': %s\n" % (self.dbm_name, fname, e)) + return + + if Print_Directories: + for dir in Print_Directories: + try: + val = db[dir] + except KeyError: + sys.stderr.write("sconsign: no dir `%s' in `%s'\n" % (dir, args[0])) + else: + self.printentries(dir, val) + else: + keys = db.keys() + keys.sort() + for dir in keys: + self.printentries(dir, db[dir]) + + def printentries(self, dir, val): + print '=== ' + dir + ':' + printentries(cPickle.loads(val), dir) + +def Do_SConsignDir(name): + try: + fp = open(name, 'rb') + except (IOError, OSError), e: + sys.stderr.write("sconsign: %s\n" % (e)) + return + try: + sconsign = SCons.SConsign.Dir(fp) + except KeyboardInterrupt: + raise + except cPickle.UnpicklingError: + sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s'\n" % (name)) + return + except Exception, e: + sys.stderr.write("sconsign: ignoring invalid .sconsign file `%s': %s\n" % (name, e)) + return + printentries(sconsign.entries, args[0]) + +############################################################################## + +import getopt + +helpstr = """\ +Usage: sconsign [OPTIONS] FILE [...] +Options: + -a, --act, --action Print build action information. + -c, --csig Print content signature information. + -d DIR, --dir=DIR Print only info about DIR. + -e ENTRY, --entry=ENTRY Print only info about ENTRY. + -f FORMAT, --format=FORMAT FILE is in the specified FORMAT. + -h, --help Print this message and exit. + -i, --implicit Print implicit dependency information. + -r, --readable Print timestamps in human-readable form. + --raw Print raw Python object representations. + -s, --size Print file sizes. + -t, --timestamp Print timestamp information. + -v, --verbose Verbose, describe each field. +""" + +opts, args = getopt.getopt(sys.argv[1:], "acd:e:f:hirstv", + ['act', 'action', + 'csig', 'dir=', 'entry=', + 'format=', 'help', 'implicit', + 'raw', 'readable', + 'size', 'timestamp', 'verbose']) + + +for o, a in opts: + if o in ('-a', '--act', '--action'): + Print_Flags['action'] = 1 + elif o in ('-c', '--csig'): + Print_Flags['csig'] = 1 + elif o in ('-d', '--dir'): + Print_Directories.append(a) + elif o in ('-e', '--entry'): + Print_Entries.append(a) + elif o in ('-f', '--format'): + Module_Map = {'dblite' : 'SCons.dblite', + 'sconsign' : None} + dbm_name = Module_Map.get(a, a) + if dbm_name: + try: + dbm = my_import(dbm_name) + except: + sys.stderr.write("sconsign: illegal file format `%s'\n" % a) + print helpstr + sys.exit(2) + Do_Call = Do_SConsignDB(a, dbm) + else: + Do_Call = Do_SConsignDir + elif o in ('-h', '--help'): + print helpstr + sys.exit(0) + elif o in ('-i', '--implicit'): + Print_Flags['implicit'] = 1 + elif o in ('--raw',): + nodeinfo_string = nodeinfo_raw + elif o in ('-r', '--readable'): + Readable = 1 + elif o in ('-s', '--size'): + Print_Flags['size'] = 1 + elif o in ('-t', '--timestamp'): + Print_Flags['timestamp'] = 1 + elif o in ('-v', '--verbose'): + Verbose = 1 + +if Do_Call: + for a in args: + Do_Call(a) +else: + for a in args: + dbm_name = whichdb.whichdb(a) + if dbm_name: + Map_Module = {'SCons.dblite' : 'dblite'} + dbm = my_import(dbm_name) + Do_SConsignDB(Map_Module.get(dbm_name, dbm_name), dbm)(a) + else: + Do_SConsignDir(a) + +sys.exit(0) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/script/setup.cfg b/src/script/setup.cfg new file mode 100644 index 0000000..94ede1f --- /dev/null +++ b/src/script/setup.cfg @@ -0,0 +1,2 @@ +[bdist_rpm] +group = Development/Tools diff --git a/src/script/setup.py b/src/script/setup.py new file mode 100644 index 0000000..4f958f6 --- /dev/null +++ b/src/script/setup.py @@ -0,0 +1,55 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/script/setup.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import sys + +(head, tail) = os.path.split(sys.argv[0]) + +if head: + os.chdir(head) + sys.argv[0] = tail + +from distutils.core import setup + +setup(name = "scons-script", + version = "1.2.0.d20091224", + description = "an Open Source software construction tool script", + long_description = """SCons is an Open Source software construction tool--that is, a build tool; an +improved substitute for the classic Make utility; a better way to build +software.""", + author = "Steven Knight", + author_email = "knight@baldmt.com", + url = "http://www.scons.org/", + licence = "MIT, freely distributable", + keywords = "scons, cons, make, build tool, make tool", + scripts = ["scons"]) + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/setup.cfg b/src/setup.cfg new file mode 100644 index 0000000..a234e22 --- /dev/null +++ b/src/setup.cfg @@ -0,0 +1,6 @@ +[bdist_rpm] +group = Development/Tools + +[bdist_wininst] +title = SCons - a software construction tool +#install-script = scons-post-install.py diff --git a/src/setup.py b/src/setup.py new file mode 100644 index 0000000..f28b3cc --- /dev/null +++ b/src/setup.py @@ -0,0 +1,427 @@ +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/setup.py 4577 2009/12/27 19:44:43 scons" + +import os +import os.path +import stat +import string +import sys + +Version = "1.2.0.d20091224" + +man_pages = [ + 'scons.1', + 'sconsign.1', + 'scons-time.1', +] + +(head, tail) = os.path.split(sys.argv[0]) + +if head: + os.chdir(head) + sys.argv[0] = tail + +is_win32 = 0 +if not sys.platform == 'win32': + try: + if sys.argv[1] == 'bdist_wininst': + is_win32 = 1 + except IndexError: + pass +else: + is_win32 = 1 + +try: + import distutils + import distutils.core + import distutils.command.install + import distutils.command.install_data + import distutils.command.install_lib + import distutils.command.install_scripts + import distutils.command.build_scripts +except ImportError: + sys.stderr.write("""Could not import distutils. + +Building or installing SCons from this package requires that the Python +distutils be installed. See the README or README.txt file from this +package for instructions on where to find distutils for installation on +your system, or on how to install SCons from a different package. +""") + sys.exit(1) + +_install = distutils.command.install.install +_install_data = distutils.command.install_data.install_data +_install_lib = distutils.command.install_lib.install_lib +_install_scripts = distutils.command.install_scripts.install_scripts +_build_scripts = distutils.command.build_scripts.build_scripts + +class _options: + pass + +Options = _options() + +Installed = [] + +def set_explicitly(name, args): + """ + Return if the installation directory was set explicitly by the + user on the command line. This is complicated by the fact that + "install --install-lib=/foo" gets turned into "install_lib + --install-dir=/foo" internally. + """ + if args[0] == "install_" + name: + s = "--install-dir=" + else: + # The command is something else (usually "install") + s = "--install-%s=" % name + set = 0 + length = len(s) + for a in args[1:]: + if a[:length] == s: + set = 1 + break + return set + +class install(_install): + user_options = _install.user_options + [ + ('no-scons-script', None, + "don't install 'scons', only install 'scons-%s'" % Version), + ('no-version-script', None, + "don't install 'scons-%s', only install 'scons'" % Version), + ('install-bat', None, + "install 'scons.bat' script"), + ('no-install-bat', None, + "do not install 'scons.bat' script"), + ('install-man', None, + "install SCons man pages"), + ('no-install-man', None, + "do not install SCons man pages"), + ('standard-lib', None, + "install SCons library in standard Python location"), + ('standalone-lib', None, + "install SCons library in separate standalone directory"), + ('version-lib', None, + "install SCons library in version-numbered directory"), + ] + boolean_options = _install.boolean_options + [ + 'no-scons-script', + 'no-version-script', + 'install-bat', + 'no-install-bat', + 'install-man', + 'no-install-man', + 'standard-lib', + 'standalone-lib', + 'version-lib' + ] + + if hasattr(os, 'symlink'): + user_options.append( + ('hardlink-scons', None, + "hard link 'scons' to the version-numbered script, don't make a separate 'scons' copy"), + ) + boolean_options.append('hardlink-script') + + if hasattr(os, 'symlink'): + user_options.append( + ('symlink-scons', None, + "make 'scons' a symbolic link to the version-numbered script, don't make a separate 'scons' copy"), + ) + boolean_options.append('symlink-script') + + def initialize_options(self): + _install.initialize_options(self) + self.no_scons_script = 0 + self.no_version_script = 0 + self.install_bat = 0 + self.no_install_bat = not is_win32 + self.install_man = 0 + self.no_install_man = is_win32 + self.standard_lib = 0 + self.standalone_lib = 0 + self.version_lib = 0 + self.hardlink_scons = 0 + self.symlink_scons = 0 + # Don't warn about having to put the library directory in the + # search path. + self.warn_dir = 0 + + def finalize_options(self): + _install.finalize_options(self) + if self.install_bat: + Options.install_bat = 1 + else: + Options.install_bat = not self.no_install_bat + if self.install_man: + Options.install_man = 1 + else: + Options.install_man = not self.no_install_man + Options.standard_lib = self.standard_lib + Options.standalone_lib = self.standalone_lib + Options.version_lib = self.version_lib + Options.install_scons_script = not self.no_scons_script + Options.install_version_script = not self.no_version_script + Options.hardlink_scons = self.hardlink_scons + Options.symlink_scons = self.symlink_scons + +def get_scons_prefix(libdir, is_win32): + """ + Return the right prefix for SCons library installation. Find + this by starting with the library installation directory + (.../site-packages, most likely) and crawling back up until we reach + a directory name beginning with "python" (or "Python"). + """ + drive, head = os.path.splitdrive(libdir) + while head: + if head == os.sep: + break + head, tail = os.path.split(head) + if string.lower(tail)[:6] == "python": + # Found the Python library directory... + if is_win32: + # ...on Win32 systems, "scons" goes in the directory: + # C:\PythonXX => C:\PythonXX\scons + return os.path.join(drive + head, tail) + else: + # ...on other systems, "scons" goes above the directory: + # /usr/lib/pythonX.X => /usr/lib/scons + return os.path.join(drive + head) + return libdir + +def force_to_usr_local(self): + """ + A hack to decide if we need to "force" the installation directories + to be under /usr/local. This is because Mac Os X Tiger and + Leopard, by default, put the libraries and scripts in their own + directories under /Library or /System/Library. + """ + return (sys.platform[:6] == 'darwin' and + (self.install_dir[:9] == '/Library/' or + self.install_dir[:16] == '/System/Library/')) + +class install_lib(_install_lib): + def finalize_options(self): + _install_lib.finalize_options(self) + if force_to_usr_local(self): + self.install_dir = '/usr/local/lib' + args = self.distribution.script_args + if not set_explicitly("lib", args): + # They didn't explicitly specify the installation + # directory for libraries... + is_win32 = sys.platform == "win32" or args[0] == 'bdist_wininst' + prefix = get_scons_prefix(self.install_dir, is_win32) + if Options.standalone_lib: + # ...but they asked for a standalone directory. + self.install_dir = os.path.join(prefix, "scons") + elif Options.version_lib or not Options.standard_lib: + # ...they asked for a version-specific directory, + # or they get it by default. + self.install_dir = os.path.join(prefix, "scons-%s" % Version) + + msg = "Installed SCons library modules into %s" % self.install_dir + Installed.append(msg) + +class install_scripts(_install_scripts): + def finalize_options(self): + _install_scripts.finalize_options(self) + if force_to_usr_local(self): + self.install_dir = '/usr/local/bin' + self.build_dir = os.path.join('build', 'scripts') + msg = "Installed SCons scripts into %s" % self.install_dir + Installed.append(msg) + + def do_nothing(self, *args, **kw): + pass + + def hardlink_scons(self, src, dst, ver): + try: os.unlink(dst) + except OSError: pass + os.link(ver, dst) + + def symlink_scons(self, src, dst, ver): + try: os.unlink(dst) + except OSError: pass + os.symlink(os.path.split(ver)[1], dst) + + def copy_scons(self, src, dst, *args): + try: os.unlink(dst) + except OSError: pass + self.copy_file(src, dst) + self.outfiles.append(dst) + + def report(self, msg, args): + # Wrapper around self.announce, used by older distutils versions. + self.announce(msg % args) + + def run(self): + # This "skip_build/build_scripts" block is cut-and-paste from + # distutils. + if not self.skip_build: + self.run_command('build_scripts') + + # Custom SCons installation stuff. + if Options.hardlink_scons: + create_basename_script = self.hardlink_scons + elif Options.symlink_scons: + create_basename_script = self.symlink_scons + elif Options.install_scons_script: + create_basename_script = self.copy_scons + else: + create_basename_script = self.do_nothing + + if Options.install_version_script: + create_version_script = self.copy_scons + else: + create_version_script = self.do_nothing + + inputs = self.get_inputs() + bat_scripts = filter(lambda x: x[-4:] == '.bat', inputs) + non_bat_scripts = filter(lambda x: x[-4:] != '.bat', inputs) + + self.outfiles = [] + self.mkpath(self.install_dir) + + for src in non_bat_scripts: + base = os.path.basename(src) + scons = os.path.join(self.install_dir, base) + scons_ver = scons + '-' + Version + create_version_script(src, scons_ver) + create_basename_script(src, scons, scons_ver) + + if Options.install_bat: + if is_win32: + bat_install_dir = get_scons_prefix(self.install_dir, is_win32) + else: + bat_install_dir = self.install_dir + for src in bat_scripts: + scons_bat = os.path.join(bat_install_dir, 'scons.bat') + scons_version_bat = os.path.join(bat_install_dir, + 'scons-' + Version + '.bat') + self.copy_scons(src, scons_bat) + self.copy_scons(src, scons_version_bat) + + # This section is cut-and-paste from distutils, modulo being + # able + if os.name == 'posix': + try: report = distutils.log.info + except AttributeError: report = self.report + # Set the executable bits (owner, group, and world) on + # all the scripts we just installed. + for file in self.get_outputs(): + if self.dry_run: + report("changing mode of %s", file) + else: + mode = ((os.stat(file)[stat.ST_MODE]) | 0555) & 07777 + report("changing mode of %s", file) + os.chmod(file, mode) + +class build_scripts(_build_scripts): + def finalize_options(self): + _build_scripts.finalize_options(self) + self.build_dir = os.path.join('build', 'scripts') + +class install_data(_install_data): + def initialize_options(self): + _install_data.initialize_options(self) + def finalize_options(self): + _install_data.finalize_options(self) + if force_to_usr_local(self): + self.install_dir = '/usr/local' + if Options.install_man: + if is_win32: + dir = 'Doc' + else: + dir = os.path.join('man', 'man1') + self.data_files = [(dir, man_pages)] + man_dir = os.path.join(self.install_dir, dir) + msg = "Installed SCons man pages into %s" % man_dir + Installed.append(msg) + else: + self.data_files = [] + +description = "Open Source next-generation build tool." + +long_description = """Open Source next-generation build tool. +Improved, cross-platform substitute for the classic Make +utility. In short, SCons is an easier, more reliable +and faster way to build software.""" + +scripts = [ + 'script/scons', + 'script/sconsign', + 'script/scons-time', + + # We include scons.bat in the list of scripts, even on UNIX systems, + # because we provide an option to allow it be installed explicitly, + # for example if you're installing from UNIX on a share that's + # accessible to Windows and you want the scons.bat. + 'script/scons.bat', +] + +#if is_win32: +# scripts = scripts + [ +# 'script/scons-post-install.py' +# ] + +arguments = { + 'name' : "scons", + 'version' : Version, + 'description' : description, + 'long_description' : long_description, + 'author' : 'Steven Knight', + 'author_email' : 'knight@baldmt.com', + 'url' : "http://www.scons.org/", + 'packages' : ["SCons", + "SCons.compat", + "SCons.Node", + "SCons.Options", + "SCons.Platform", + "SCons.Scanner", + "SCons.Script", + "SCons.Tool", + "SCons.Tool.MSCommon", + "SCons.Tool.packaging", + "SCons.Variables", + ], + 'package_dir' : {'' : 'engine'}, + 'data_files' : [('man/man1', man_pages)], + 'scripts' : scripts, + 'cmdclass' : {'install' : install, + 'install_lib' : install_lib, + 'install_data' : install_data, + 'install_scripts' : install_scripts, + 'build_scripts' : build_scripts} +} + +apply(distutils.core.setup, (), arguments) + +if Installed: + print string.join(Installed, '\n') + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/test_aegistests.py b/src/test_aegistests.py new file mode 100644 index 0000000..7ba150d --- /dev/null +++ b/src/test_aegistests.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/test_aegistests.py 4577 2009/12/27 19:44:43 scons" + +""" +Verify that we have proper Copyright notices on all the right files +in our distributions. + +Note that this is a packaging test, not a functional test, so the +name of this script doesn't end in *Tests.py. +""" + +import os +import popen2 +import re +import string +import sys + +import TestSCons + +test = TestSCons.TestSCons() + +try: + popen2.Popen3 +except AttributeError: + def get_stdout(command): + (tochild, fromchild, childerr) = os.popen3(command) + tochild.close() + return fromchild.read() +else: + def get_stdout(command): + p = popen2.Popen3(command, 1) + p.tochild.close() + return p.fromchild.read() + +output = get_stdout('aegis -list -unformatted pf') +\ + get_stdout('aegis -list -unformatted cf') +lines = string.split(output, '\n')[:-1] +sources = filter(lambda x: x[:7] == 'source ', lines) + +re1 = re.compile(r' src/.*Tests\.py') +re2 = re.compile(r' src/test_.*\.py') +re3 = re.compile(r' test/.*\.py') + +def filename_is_a_test(x): + return re1.search(x) or re2.search(x) or re3.search(x) + +test_files = filter(filename_is_a_test, sources) + +if test_files: + sys.stderr.write("Found the following files with test names not marked as Aegis tests:\n") + sys.stderr.write('\t' + string.join(test_files, '\n\t') + '\n') + test.fail_test(1) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/test_files.py b/src/test_files.py new file mode 100644 index 0000000..cea02fd --- /dev/null +++ b/src/test_files.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/test_files.py 4577 2009/12/27 19:44:43 scons" + +""" +Verify that we have certain important files in our distribution +packages. + +Note that this is a packaging test, not a functional test, so the +name of this script doesn't end in *Tests.py. +""" + +import os +import os.path +import re +import string + +import TestSCons + +test = TestSCons.TestSCons() + +try: + cwd = os.environ['SCONS_CWD'] +except KeyError: + cwd = os.getcwd() + +def build_path(*args): + return apply(os.path.join, (cwd, 'build',) + args) + +build_scons_tar_gz = build_path('unpack-tar-gz', 'scons-'+test.scons_version) +build_scons_zip = build_path('unpack-zip', 'scons-'+test.scons_version) +build_local_tar_gz = build_path('test-local-tar-gz') +build_local_zip = build_path('test-local-zip') + +scons_files = [ + 'CHANGES.txt', + 'LICENSE.txt', + 'README.txt', + 'RELEASE.txt', +] + +local_files = [ + 'scons-LICENSE', + 'scons-README', +] + +# Map each directory to search (dictionary keys) to a list of its +# subsidiary files and directories to exclude from copyright checks. +check = { + build_scons_tar_gz : scons_files, + build_scons_zip : scons_files, + build_local_tar_gz : local_files, + build_local_zip : local_files, +} + +missing = [] +no_result = [] + +for directory, check_list in check.items(): + if os.path.exists(directory): + for c in check_list: + f = os.path.join(directory, c) + if not os.path.isfile(f): + missing.append(f) + else: + no_result.append(directory) + +if missing: + print "Missing the following files:\n" + print "\t" + string.join(missing, "\n\t") + test.fail_test(1) + +if no_result: + print "Cannot check files, the following have apparently not been built:" + print "\t" + string.join(no_result, "\n\t") + test.no_result(1) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/test_interrupts.py b/src/test_interrupts.py new file mode 100644 index 0000000..c7171f7 --- /dev/null +++ b/src/test_interrupts.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/test_interrupts.py 4577 2009/12/27 19:44:43 scons" + +""" +Verify that the SCons source code contains only correct handling of +keyboard interrupts (e.g. Ctrl-C). +""" + +import os +import os.path +import re +import string +import time + +import TestSCons + +test = TestSCons.TestSCons() + +# We do not want statements of the form: +# try: +# # do something, e.g. +# return a['x'] +# except: +# # do the exception handling +# a['x'] = getx() +# return a['x'] +# +# The code above may catch a KeyboardInterrupt exception, which was not +# intended by the programmer. We check for these situations in all python +# source files. + +try: + cwd = os.environ['SCONS_CWD'] +except KeyError: + scons_lib_dir = os.environ['SCONS_LIB_DIR'] + MANIFEST = os.path.join(scons_lib_dir, 'MANIFEST.in') +else: + #cwd = os.getcwd() + scons_lib_dir = os.path.join(cwd, 'build', 'scons') + MANIFEST = os.path.join(scons_lib_dir, 'MANIFEST') + +# We expect precisely this many uncaught KeyboardInterrupt exceptions +# from the files in the following dictionary. + +expected_uncaught = { + 'engine/SCons/Job.py' : 5, + 'engine/SCons/Script/Main.py' : 1, + 'engine/SCons/Taskmaster.py' : 3, +} + +try: + fp = open(MANIFEST) +except IOError: + test.skip_test('%s does not exist; skipping test.\n' % MANIFEST) +else: + files = string.split(fp.read()) + files = filter(lambda f: f[-3:] == '.py', files) + +# some regexps to parse the python files +tryexc_pat = re.compile( +r'^(?P(?P *)(try|except)( [^\n]*)?:.*)',re.MULTILINE) +keyboardint_pat = re.compile(r' *except +([^,],)*KeyboardInterrupt([ ,][^\n]*)?:[^\n]*') +exceptall_pat = re.compile(r' *except(?: *| +Exception *, *[^: ]+):[^\n]*') + +uncaughtKeyboardInterrupt = 0 +for f in files: + contents = open(os.path.join(scons_lib_dir, f)).read() + try_except_lines = {} + lastend = 0 + while 1: + match = tryexc_pat.search( contents, lastend ) + if match is None: + break + #print match.groups() + lastend = match.end() + try: + indent_list = try_except_lines[match.group('indent')] + except: + indent_list = [] + line_num = 1 + string.count(contents[:match.start()], '\n') + indent_list.append( (line_num, match.group('try_or_except') ) ) + try_except_lines[match.group('indent')] = indent_list + uncaught_this_file = [] + for indent in try_except_lines.keys(): + exc_keyboardint_seen = 0 + exc_all_seen = 0 + for (l,statement) in try_except_lines[indent] + [(-1,indent + 'try')]: + #print "%4d %s" % (l,statement), + m1 = keyboardint_pat.match(statement) + m2 = exceptall_pat.match(statement) + if string.find(statement, indent + 'try') == 0: + if exc_all_seen and not exc_keyboardint_seen: + uncaught_this_file.append(line) + exc_keyboardint_seen = 0 + exc_all_seen = 0 + line = l + #print " -> reset" + elif m1 is not None: + exc_keyboardint_seen = 1 + #print " -> keyboard -> ", m1.groups() + elif m2 is not None: + exc_all_seen = 1 + #print " -> all -> ", m2.groups() + else: + pass + #print "Warning: unknown statement %s" % statement + expected_num = expected_uncaught.get(f, 0) + if expected_num != len(uncaught_this_file): + uncaughtKeyboardInterrupt = 1 + msg = "%s: expected %d uncaught interrupts, got %d:" + print msg % (f, expected_num, len(uncaught_this_file)) + for line in uncaught_this_file: + print " File %s:%d: Uncaught KeyboardInterrupt!" % (f,line) + +test.fail_test(uncaughtKeyboardInterrupt) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/test_pychecker.py b/src/test_pychecker.py new file mode 100644 index 0000000..92b12e4 --- /dev/null +++ b/src/test_pychecker.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/test_pychecker.py 4577 2009/12/27 19:44:43 scons" + +""" +Use pychecker to catch various Python coding errors. +""" + +import os +import os.path +import string +import sys + +import TestSCons + +test = TestSCons.TestSCons() + +test.skip_test('Not ready for clean pychecker output; skipping test.\n') + +try: + import pychecker +except ImportError: + pychecker = test.where_is('pychecker') + if not pychecker: + test.skip_test("Could not find 'pychecker'; skipping test(s).\n") + program = pychecker + default_arguments = [] +else: + pychecker = os.path.join(os.path.split(pychecker.__file__)[0], 'checker.py') + program = sys.executable + default_arguments = [pychecker] + +try: + cwd = os.environ['SCONS_CWD'] +except KeyError: + src_engine = os.environ['SCONS_LIB_DIR'] +else: + src_engine = os.path.join(cwd, 'build', 'scons-src', 'src', 'engine') + if not os.path.exists(src_engine): + src_engine = os.path.join(cwd, 'src', 'engine') + +src_engine_ = os.path.join(src_engine, '') + +MANIFEST = os.path.join(src_engine, 'MANIFEST.in') +files = string.split(open(MANIFEST).read()) + +files = filter(lambda f: f[-3:] == '.py', files) + +ignore = [ + 'SCons/compat/__init__.py', + 'SCons/compat/_scons_UserString.py', + 'SCons/compat/_scons_hashlib.py', + 'SCons/compat/_scons_itertools.py', + 'SCons/compat/_scons_optparse.py', + 'SCons/compat/_scons_sets.py', + 'SCons/compat/_scons_sets15.py', + 'SCons/compat/_scons_shlex.py', + 'SCons/compat/_scons_subprocess.py', + 'SCons/compat/_scons_textwrap.py', + 'SCons/compat/builtins.py', +] + +u = {} +for file in files: + u[file] = 1 +for file in ignore: + try: + del u[file] + except KeyError: + pass + +files = u.keys() + +files.sort() + +mismatches = [] + +default_arguments.extend([ + '--quiet', + '--limit=1000', + # Suppress warnings about unused arguments to functions and methods. + # We have too many wrapper functions that intentionally only use some + # of their arguments. + '--no-argsused', +]) + +if sys.platform == 'win32': + default_arguments.extend([ + '--blacklist', '"pywintypes,pywintypes.error"', + ]) + +per_file_arguments = { + #'SCons/__init__.py' : [ + # '--varlist', + # '"__revision__,__version__,__build__,__buildsys__,__date__,__developer__"', + #], +} + +pywintypes_warning = "warning: couldn't find real module for class pywintypes.error (module name: pywintypes)\n" + +os.environ['PYTHONPATH'] = src_engine + +for file in files: + + args = (default_arguments + + per_file_arguments.get(file, []) + + [os.path.join(src_engine, file)]) + + test.run(program=program, arguments=args, status=None, stderr=None) + + stdout = test.stdout() + stdout = string.replace(stdout, src_engine_, '') + + stderr = test.stderr() + stderr = string.replace(stderr, src_engine_, '') + stderr = string.replace(stderr, pywintypes_warning, '') + + if test.status or stdout or stderr: + mismatches.append('\n') + mismatches.append(string.join([program] + args) + '\n') + + mismatches.append('STDOUT =====================================\n') + mismatches.append(stdout) + + if stderr: + mismatches.append('STDERR =====================================\n') + mismatches.append(stderr) + +if mismatches: + print string.join(mismatches[1:], '') + test.fail_test() + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/test_setup.py b/src/test_setup.py new file mode 100644 index 0000000..40ea464 --- /dev/null +++ b/src/test_setup.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/test_setup.py 4577 2009/12/27 19:44:43 scons" + +""" +Test how the setup.py script installs SCons. + +Note that this is an installation test, not a functional test, so the +name of this script doesn't end in *Tests.py. +""" + +import os +import os.path +import shutil +import string +import sys + +try: WindowsError +except NameError: WindowsError = OSError + +import TestSCons + +version = TestSCons.TestSCons.scons_version + +scons_version = 'scons-%s' % version + +python = TestSCons.python + +class MyTestSCons(TestSCons.TestSCons): + + _lib_modules = [ + # A representative smattering of build engine modules. + '__init__.py', + 'Action.py', + 'Builder.py', + 'Environment.py', + 'Util.py', + ] + + _base_scripts = [ + 'scons', + 'sconsign', + ] + + _version_scripts = [ + 'scons-%s' % version, + 'sconsign-%s' % version, + ] + + _bat_scripts = [ + 'scons.bat', + ] + + _bat_version_scripts = [ + 'scons-%s.bat' % version, + ] + + _man_pages = [ + 'scons.1', + 'sconsign.1', + ] + + def __init__(self): + TestSCons.TestSCons.__init__(self) + self.root = self.workpath('root') + self.prefix = self.root + os.path.splitdrive(sys.prefix)[1] + + if sys.platform == 'win32': + self.bin_dir = os.path.join(self.prefix, 'Scripts') + self.bat_dir = self.prefix + self.standalone_lib = os.path.join(self.prefix, 'scons') + self.standard_lib = os.path.join(self.prefix, + 'Lib', + 'site-packages', + '') + self.version_lib = os.path.join(self.prefix, scons_version) + self.man_dir = os.path.join(self.prefix, 'Doc') + else: + self.bin_dir = os.path.join(self.prefix, 'bin') + self.bat_dir = self.bin_dir + self.lib_dir = os.path.join(self.prefix, 'lib') + self.standalone_lib = os.path.join(self.lib_dir, 'scons') + self.standard_lib = os.path.join(self.lib_dir, + 'python%s' % sys.version[:3], + 'site-packages', + '') + self.version_lib = os.path.join(self.lib_dir, scons_version) + self.man_dir = os.path.join(self.prefix, 'man', 'man1') + + self.prepend_bin_dir = lambda p, d=self.bin_dir: os.path.join(d, p) + self.prepend_bat_dir = lambda p, d=self.bat_dir: os.path.join(d, p) + self.prepend_man_dir = lambda p, d=self.man_dir: os.path.join(d, p) + + def run(self, *args, **kw): + kw['chdir'] = scons_version + kw['program'] = python + kw['stderr'] = None + return apply(TestSCons.TestSCons.run, (self,)+args, kw) + + def remove(self, dir): + try: shutil.rmtree(dir) + except (OSError, WindowsError): pass + + def stdout_lines(self): + return string.split(self.stdout(), '\n') + + + def lib_line(self, lib): + return 'Installed SCons library modules into %s' % lib + + def lib_paths(self, lib_dir): + prepend_lib_dir = lambda p, d=lib_dir: os.path.join(d, 'SCons', p) + return map(prepend_lib_dir, self._lib_modules) + + def scripts_line(self): + return 'Installed SCons scripts into %s' % self.bin_dir + + def base_script_paths(self): + scripts = self._base_scripts + return map(self.prepend_bin_dir, scripts) + + def version_script_paths(self): + scripts = self._version_scripts + return map(self.prepend_bin_dir, scripts) + + def bat_script_paths(self): + scripts = self._bat_scripts + self._bat_version_scripts + return map(self.prepend_bat_dir, scripts) + + def man_page_line(self): + return 'Installed SCons man pages into %s' % self.man_dir + + def man_page_paths(self): + return map(self.prepend_man_dir, self._man_pages) + + + def must_have_installed(self, paths): + for p in paths: + self.must_exist(p) + + def must_not_have_installed(self, paths): + for p in paths: + self.must_not_exist(p) + +try: + cwd = os.environ['SCONS_CWD'] +except KeyError: + cwd = os.getcwd() + +test = MyTestSCons() + +test.subdir(test.root) + +tar_gz = os.path.join(cwd, 'build', 'dist', '%s.tar.gz' % scons_version) +zip = os.path.join(cwd, 'build', 'dist', '%s.zip' % scons_version) + +if os.path.isfile(zip): + try: import zipfile + except ImportError: pass + else: + zf = zipfile.ZipFile(zip, 'r') + + for name in zf.namelist(): + dir = os.path.dirname(name) + try: os.makedirs(dir) + except: pass + # if the file exists, then delete it before writing + # to it so that we don't end up trying to write to a symlink: + if os.path.isfile(name) or os.path.islink(name): + os.unlink(name) + if not os.path.isdir(name): + open(name, 'w').write(zf.read(name)) + +if not os.path.isdir(scons_version) and os.path.isfile(tar_gz): + # Unpack the .tar.gz file. This should create the scons_version/ + # subdirectory from which we execute the setup.py script therein. + os.system("gunzip -c %s | tar xf -" % tar_gz) + +if not os.path.isdir(scons_version): + print "Cannot test package installation, found none of the following packages:" + print "\t" + tar_gz + print "\t" + zip + test.no_result(1) + +# Verify that a virgin installation installs the version library, +# the scripts and (on UNIX/Linux systems) the man pages. +test.run(arguments = 'setup.py install --root=%s' % test.root) +test.fail_test(not test.lib_line(test.version_lib) in test.stdout_lines()) +test.must_have_installed(test.lib_paths(test.version_lib)) + +# Verify that --standard-lib installs into the Python standard library. +test.run(arguments = 'setup.py install --root=%s --standard-lib' % test.root) +test.fail_test(not test.lib_line(test.standard_lib) in test.stdout_lines()) +test.must_have_installed(test.lib_paths(test.standard_lib)) + +# Verify that --standalone-lib installs the standalone library. +test.run(arguments = 'setup.py install --root=%s --standalone-lib' % test.root) +test.fail_test(not test.lib_line(test.standalone_lib) in test.stdout_lines()) +test.must_have_installed(test.lib_paths(test.standalone_lib)) + +# Verify that --version-lib installs into a version-specific library directory. +test.run(arguments = 'setup.py install --root=%s --version-lib' % test.root) +test.fail_test(not test.lib_line(test.version_lib) in test.stdout_lines()) + +# Now that all of the libraries are in place, +# verify that a default installation still installs the version library. +test.run(arguments = 'setup.py install --root=%s' % test.root) +test.fail_test(not test.lib_line(test.version_lib) in test.stdout_lines()) + +test.remove(test.version_lib) + +# Now with only the standard and standalone libraries in place, +# verify that a default installation still installs the version library. +test.run(arguments = 'setup.py install --root=%s' % test.root) +test.fail_test(not test.lib_line(test.version_lib) in test.stdout_lines()) + +test.remove(test.version_lib) +test.remove(test.standalone_lib) + +# Now with only the standard libraries in place, +# verify that a default installation still installs the version library. +test.run(arguments = 'setup.py install --root=%s' % test.root) +test.fail_test(not test.lib_line(test.version_lib) in test.stdout_lines()) + + + +# +test.run(arguments = 'setup.py install --root=%s' % test.root) +test.fail_test(not test.scripts_line() in test.stdout_lines()) +if sys.platform == 'win32': + test.must_have_installed(test.base_script_paths()) + test.must_have_installed(test.version_script_paths()) + test.must_have_installed(test.bat_script_paths()) +else: + test.must_have_installed(test.base_script_paths()) + test.must_have_installed(test.version_script_paths()) + test.must_not_have_installed(test.bat_script_paths()) + +test.remove(test.prefix) + +test.run(arguments = 'setup.py install --root=%s --no-install-bat' % test.root) +test.fail_test(not test.scripts_line() in test.stdout_lines()) +test.must_have_installed(test.base_script_paths()) +test.must_have_installed(test.version_script_paths()) +test.must_not_have_installed(test.bat_script_paths()) + +test.remove(test.prefix) + +test.run(arguments = 'setup.py install --root=%s --install-bat' % test.root) +test.fail_test(not test.scripts_line() in test.stdout_lines()) +test.must_have_installed(test.base_script_paths()) +test.must_have_installed(test.version_script_paths()) +test.must_have_installed(test.bat_script_paths()) + +test.remove(test.prefix) + +test.run(arguments = 'setup.py install --root=%s --no-scons-script' % test.root) +test.fail_test(not test.scripts_line() in test.stdout_lines()) +test.must_not_have_installed(test.base_script_paths()) +test.must_have_installed(test.version_script_paths()) +# Doesn't matter whether we installed the .bat scripts or not. + +test.remove(test.prefix) + +test.run(arguments = 'setup.py install --root=%s --no-version-script' % test.root) +test.fail_test(not test.scripts_line() in test.stdout_lines()) +test.must_have_installed(test.base_script_paths()) +test.must_not_have_installed(test.version_script_paths()) +# Doesn't matter whether we installed the .bat scripts or not. + + + +test.remove(test.man_dir) + +test.run(arguments = 'setup.py install --root=%s' % test.root) +if sys.platform == 'win32': + test.fail_test(test.man_page_line() in test.stdout_lines()) + test.must_not_have_installed(test.man_page_paths()) +else: + test.fail_test(not test.man_page_line() in test.stdout_lines()) + test.must_have_installed(test.man_page_paths()) + +test.remove(test.man_dir) + +test.run(arguments = 'setup.py install --root=%s --no-install-man' % test.root) +test.fail_test(test.man_page_line() in test.stdout_lines()) +test.must_not_have_installed(test.man_page_paths()) + +test.remove(test.man_dir) + +test.run(arguments = 'setup.py install --root=%s --install-man' % test.root) +test.fail_test(not test.man_page_line() in test.stdout_lines()) +test.must_have_installed(test.man_page_paths()) + + + +# Verify that we don't warn about the directory in which we've +# installed the modules when using a non-standard prefix. +other_prefix = test.workpath('other-prefix') +test.subdir(other_prefix) +test.run(arguments = 'setup.py install --prefix=%s' % other_prefix) +test.fail_test(string.find(test.stderr(), + "you'll have to change the search path yourself") + != -1) + +# All done. +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: diff --git a/src/test_strings.py b/src/test_strings.py new file mode 100644 index 0000000..b11c555 --- /dev/null +++ b/src/test_strings.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python +# +# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY +# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# + +__revision__ = "src/test_strings.py 4577 2009/12/27 19:44:43 scons" + +""" +Verify that we have proper strings like Copyright notices on all the +right files in our distributions. + +Note that this is a source file and packaging test, not a functional test, +so the name of this script doesn't end in *Tests.py. +""" + +import fnmatch +import os +import os.path +import re +import string + +import TestCmd +import TestSCons + +# Use TestCmd, not TestSCons, so we don't chdir to a temporary directory. +test = TestCmd.TestCmd() + +scons_version = TestSCons.SConsVersion + +def build_path(*args): + return apply(os.path.join, ('build',)+args) + +build_scons = build_path('scons') +build_local = build_path('scons-local', 'scons-local-'+scons_version) +build_src = build_path('scons-src') + +class Checker: + def __init__(self, directory, + search_list = [], + remove_list = [], + remove_patterns = []): + self.directory = directory + self.search_list = search_list + self.remove_dict = {} + for r in remove_list: + self.remove_dict[os.path.join(directory, r)] = 1 + self.remove_patterns = remove_patterns + + def directory_exists(self): + return os.path.exists(self.directory) + + def remove_this(self, name, path): + if self.remove_dict.get(path): + return 1 + else: + for pattern in self.remove_patterns: + if fnmatch.fnmatch(name, pattern): + return 1 + return 0 + + def search_this(self, path): + if self.search_list: + for pattern in self.search_list: + if fnmatch.fnmatch(path, pattern): + return 1 + return None + else: + return os.path.isfile(path) + + def visit(self, result, dirname, names): + make_path_tuple = lambda n, d=dirname: (n, os.path.join(d, n)) + for name, path in map(make_path_tuple, names): + if self.remove_this(name, path): + names.remove(name) + elif self.search_this(path): + body = open(path, 'r').read() + for expr in self.expressions: + if not expr.search(body): + msg = '%s: missing %s' % (path, repr(expr.pattern)) + result.append(msg) + + def find_missing(self): + result = [] + os.path.walk(self.directory, self.visit, result) + return result + +class CheckUnexpandedStrings(Checker): + expressions = [ + re.compile('Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 The SCons Foundation'), + re.compile('src/test_strings.py 4577 2009/12/27 19:44:43 scons'), + ] + def must_be_built(self): + return None + +class CheckPassTest(Checker): + expressions = [ + re.compile(r'\.pass_test()'), + ] + def must_be_built(self): + return None + +class CheckExpandedCopyright(Checker): + expressions = [ + re.compile('Copyright.*The SCons Foundation'), + ] + def must_be_built(self): + return 1 + +check_list = [ + + CheckUnexpandedStrings( + 'src', + search_list = [ '*.py' ], + remove_list = [ + 'engine/SCons/compat/_scons_optparse.py', + 'engine/SCons/compat/_scons_sets.py', + 'engine/SCons/compat/_scons_sets15.py', + 'engine/SCons/compat/_scons_shlex.py', + 'engine/SCons/compat/_scons_subprocess.py', + 'engine/SCons/compat/_scons_textwrap.py', + 'engine/SCons/Conftest.py', + 'engine/SCons/dblite.py', + ], + ), + + CheckUnexpandedStrings( + 'test', + search_list = [ '*.py' ], + ), + + CheckPassTest( + 'test', + search_list = [ '*.py' ], + remove_list = [ + 'Fortran/common.py', + ], + ), + + CheckExpandedCopyright( + build_scons, + remove_list = [ + 'build', + 'build-stamp', + 'configure-stamp', + 'debian', + 'dist', + 'gentoo', + 'engine/SCons/compat/_scons_optparse.py', + 'engine/SCons/compat/_scons_sets.py', + 'engine/SCons/compat/_scons_sets15.py', + 'engine/SCons/compat/_scons_shlex.py', + 'engine/SCons/compat/_scons_subprocess.py', + 'engine/SCons/compat/_scons_textwrap.py', + 'engine/SCons/Conftest.py', + 'engine/SCons/dblite.py', + 'MANIFEST', + 'os_spawnv_fix.diff', + 'setup.cfg', + ], + # We run epydoc on the *.py files, which generates *.pyc files. + remove_patterns = [ + '*.pyc', + ] + ), + + CheckExpandedCopyright( + build_local, + remove_list = [ + 'SCons/compat/_scons_optparse.py', + 'SCons/compat/_scons_sets.py', + 'SCons/compat/_scons_sets15.py', + 'SCons/compat/_scons_shlex.py', + 'SCons/compat/_scons_subprocess.py', + 'SCons/compat/_scons_textwrap.py', + 'SCons/Conftest.py', + 'SCons/dblite.py', + 'scons-%s.egg-info' % scons_version, + ], + ), + + CheckExpandedCopyright( + build_src, + remove_list = [ + 'bench/timeit.py', + 'bin', + 'config', + 'debian', + 'gentoo', + 'doc/design', + 'doc/MANIFEST', + 'doc/python10', + 'doc/reference', + 'doc/developer/MANIFEST', + 'doc/man/MANIFEST', + 'doc/user/cons.pl', + 'doc/user/MANIFEST', + 'doc/user/SCons-win32-install-1.jpg', + 'doc/user/SCons-win32-install-2.jpg', + 'doc/user/SCons-win32-install-3.jpg', + 'doc/user/SCons-win32-install-4.jpg', + 'examples', + 'gentoo', + 'QMTest/classes.qmc', + 'QMTest/configuration', + 'QMTest/TestCmd.py', + 'QMTest/TestCommon.py', + 'QMTest/unittest.py', + 'src/os_spawnv_fix.diff', + 'src/MANIFEST.in', + 'src/setup.cfg', + 'src/engine/MANIFEST.in', + 'src/engine/MANIFEST-xml.in', + 'src/engine/setup.cfg', + 'src/engine/SCons/compat/_scons_optparse.py', + 'src/engine/SCons/compat/_scons_sets.py', + 'src/engine/SCons/compat/_scons_sets15.py', + 'src/engine/SCons/compat/_scons_shlex.py', + 'src/engine/SCons/compat/_scons_subprocess.py', + 'src/engine/SCons/compat/_scons_textwrap.py', + 'src/engine/SCons/Conftest.py', + 'src/engine/SCons/dblite.py', + 'src/script/MANIFEST.in', + 'src/script/setup.cfg', + 'test/Fortran/.exclude_tests', + 'timings/changelog.html', + 'timings/graph.html', + 'timings/index.html', + ], + remove_patterns = [ + '*.js', + ] + ), + +] + +missing_strings = [] +not_built = [] + +for collector in check_list: + if collector.directory_exists(): + missing_strings.extend(collector.find_missing()) + elif collector.must_be_built(): + not_built.append(collector.directory) + +if missing_strings: + print "Found the following files with missing strings:" + print "\t" + string.join(missing_strings, "\n\t") + test.fail_test(1) + +if not_built: + print "Cannot check all strings, the following have apparently not been built:" + print "\t" + string.join(not_built, "\n\t") + test.no_result(1) + +test.pass_test() + +# Local Variables: +# tab-width:4 +# indent-tabs-mode:nil +# End: +# vim: set expandtab tabstop=4 shiftwidth=4: -- cgit v1.2.3