third_party:waf: update to upstream 2.0.4 release
authorAlexander Bokovoy <ab@samba.org>
Wed, 31 Jan 2018 09:48:43 +0000 (11:48 +0200)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 5 Sep 2018 04:37:22 +0000 (06:37 +0200)
Update third_party/waf/ to 2.0.4 to bring us closer to Python 3

This change requires a number of changes in buildtools/ too.

Signed-off-by: Alexander Bokovoy <ab@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
219 files changed:
buildtools/bin/waf
buildtools/bin/waf-1.9 [new file with mode: 0755]
buildtools/wafsamba/configure_file.py
buildtools/wafsamba/generic_cc.py
buildtools/wafsamba/hpuxcc.py
buildtools/wafsamba/irixcc.py
buildtools/wafsamba/nothreads.py
buildtools/wafsamba/pkgconfig.py
buildtools/wafsamba/samba3.py
buildtools/wafsamba/samba_abi.py
buildtools/wafsamba/samba_autoconf.py
buildtools/wafsamba/samba_autoproto.py
buildtools/wafsamba/samba_bundled.py
buildtools/wafsamba/samba_conftests.py
buildtools/wafsamba/samba_cross.py
buildtools/wafsamba/samba_deps.py
buildtools/wafsamba/samba_dist.py
buildtools/wafsamba/samba_headers.py
buildtools/wafsamba/samba_install.py
buildtools/wafsamba/samba_patterns.py
buildtools/wafsamba/samba_perl.py
buildtools/wafsamba/samba_pidl.py
buildtools/wafsamba/samba_python.py
buildtools/wafsamba/samba_third_party.py
buildtools/wafsamba/samba_utils.py
buildtools/wafsamba/samba_version.py
buildtools/wafsamba/samba_waf18.py
buildtools/wafsamba/samba_wildcard.py
buildtools/wafsamba/stale_files.py
buildtools/wafsamba/symbols.py
buildtools/wafsamba/tru64cc.py
buildtools/wafsamba/wafsamba.py
buildtools/wafsamba/wscript
third_party/waf/waflib/Build.py
third_party/waf/waflib/ConfigSet.py
third_party/waf/waflib/Configure.py
third_party/waf/waflib/Context.py
third_party/waf/waflib/Errors.py
third_party/waf/waflib/Logs.py
third_party/waf/waflib/Node.py
third_party/waf/waflib/Options.py
third_party/waf/waflib/Runner.py
third_party/waf/waflib/Scripting.py
third_party/waf/waflib/Task.py
third_party/waf/waflib/TaskGen.py
third_party/waf/waflib/Tools/__init__.py
third_party/waf/waflib/Tools/ar.py
third_party/waf/waflib/Tools/asm.py
third_party/waf/waflib/Tools/bison.py
third_party/waf/waflib/Tools/c.py
third_party/waf/waflib/Tools/c_aliases.py
third_party/waf/waflib/Tools/c_config.py
third_party/waf/waflib/Tools/c_osx.py
third_party/waf/waflib/Tools/c_preproc.py
third_party/waf/waflib/Tools/c_tests.py
third_party/waf/waflib/Tools/ccroot.py
third_party/waf/waflib/Tools/clangxx.py
third_party/waf/waflib/Tools/compiler_c.py
third_party/waf/waflib/Tools/compiler_cxx.py
third_party/waf/waflib/Tools/compiler_d.py
third_party/waf/waflib/Tools/compiler_fc.py
third_party/waf/waflib/Tools/cs.py
third_party/waf/waflib/Tools/cxx.py
third_party/waf/waflib/Tools/d.py
third_party/waf/waflib/Tools/d_config.py
third_party/waf/waflib/Tools/d_scan.py
third_party/waf/waflib/Tools/dbus.py
third_party/waf/waflib/Tools/dmd.py
third_party/waf/waflib/Tools/errcheck.py
third_party/waf/waflib/Tools/fc.py
third_party/waf/waflib/Tools/fc_config.py
third_party/waf/waflib/Tools/fc_scan.py
third_party/waf/waflib/Tools/flex.py
third_party/waf/waflib/Tools/g95.py
third_party/waf/waflib/Tools/gas.py
third_party/waf/waflib/Tools/gcc.py
third_party/waf/waflib/Tools/gdc.py
third_party/waf/waflib/Tools/gfortran.py
third_party/waf/waflib/Tools/glib2.py
third_party/waf/waflib/Tools/gnu_dirs.py
third_party/waf/waflib/Tools/gxx.py
third_party/waf/waflib/Tools/icc.py
third_party/waf/waflib/Tools/icpc.py
third_party/waf/waflib/Tools/ifort.py
third_party/waf/waflib/Tools/intltool.py
third_party/waf/waflib/Tools/irixcc.py
third_party/waf/waflib/Tools/javaw.py
third_party/waf/waflib/Tools/ldc2.py
third_party/waf/waflib/Tools/lua.py
third_party/waf/waflib/Tools/md5_tstamp.py
third_party/waf/waflib/Tools/msvc.py
third_party/waf/waflib/Tools/nasm.py
third_party/waf/waflib/Tools/nobuild.py
third_party/waf/waflib/Tools/perl.py
third_party/waf/waflib/Tools/python.py
third_party/waf/waflib/Tools/qt5.py
third_party/waf/waflib/Tools/ruby.py
third_party/waf/waflib/Tools/suncc.py
third_party/waf/waflib/Tools/suncxx.py
third_party/waf/waflib/Tools/tex.py
third_party/waf/waflib/Tools/vala.py
third_party/waf/waflib/Tools/waf_unit_test.py
third_party/waf/waflib/Tools/winres.py
third_party/waf/waflib/Tools/xlc.py
third_party/waf/waflib/Tools/xlcxx.py
third_party/waf/waflib/Utils.py
third_party/waf/waflib/__init__.py
third_party/waf/waflib/ansiterm.py
third_party/waf/waflib/extras/add_objects.py [deleted file]
third_party/waf/waflib/extras/batched_cc.py
third_party/waf/waflib/extras/biber.py [new file with mode: 0644]
third_party/waf/waflib/extras/bjam.py [new file with mode: 0644]
third_party/waf/waflib/extras/blender.py [new file with mode: 0644]
third_party/waf/waflib/extras/boo.py [new file with mode: 0644]
third_party/waf/waflib/extras/boost.py [new file with mode: 0644]
third_party/waf/waflib/extras/build_file_tracker.py
third_party/waf/waflib/extras/build_logs.py
third_party/waf/waflib/extras/buildcopy.py [new file with mode: 0644]
third_party/waf/waflib/extras/c_bgxlc.py
third_party/waf/waflib/extras/c_dumbpreproc.py
third_party/waf/waflib/extras/c_emscripten.py
third_party/waf/waflib/extras/c_nec.py
third_party/waf/waflib/extras/cabal.py [new file with mode: 0644]
third_party/waf/waflib/extras/cfg_altoptions.py
third_party/waf/waflib/extras/cfg_cross_gnu.py [deleted file]
third_party/waf/waflib/extras/clang_compilation_database.py
third_party/waf/waflib/extras/codelite.py
third_party/waf/waflib/extras/color_gcc.py
third_party/waf/waflib/extras/color_rvct.py
third_party/waf/waflib/extras/compat15.py
third_party/waf/waflib/extras/cppcheck.py
third_party/waf/waflib/extras/cpplint.py
third_party/waf/waflib/extras/cross_gnu.py [new file with mode: 0644]
third_party/waf/waflib/extras/cython.py
third_party/waf/waflib/extras/dcc.py
third_party/waf/waflib/extras/distnet.py
third_party/waf/waflib/extras/doxygen.py
third_party/waf/waflib/extras/dpapi.py
third_party/waf/waflib/extras/eclipse.py [new file with mode: 0644]
third_party/waf/waflib/extras/erlang.py [new file with mode: 0644]
third_party/waf/waflib/extras/fast_partial.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_bgxlf.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_cray.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_nag.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_nec.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_open64.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_pgfortran.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_solstudio.py [new file with mode: 0644]
third_party/waf/waflib/extras/fc_xlf.py [new file with mode: 0644]
third_party/waf/waflib/extras/file_to_object.py
third_party/waf/waflib/extras/fluid.py [new file with mode: 0644]
third_party/waf/waflib/extras/freeimage.py
third_party/waf/waflib/extras/fsb.py
third_party/waf/waflib/extras/fsc.py [new file with mode: 0644]
third_party/waf/waflib/extras/gccdeps.py
third_party/waf/waflib/extras/go.py [deleted file]
third_party/waf/waflib/extras/gob2.py
third_party/waf/waflib/extras/halide.py
third_party/waf/waflib/extras/javatest.py [new file with mode: 0755]
third_party/waf/waflib/extras/kde4.py [moved from third_party/waf/waflib/Tools/kde4.py with 89% similarity]
third_party/waf/waflib/extras/local_rpath.py
third_party/waf/waflib/extras/make.py
third_party/waf/waflib/extras/md5_tstamp.py [deleted file]
third_party/waf/waflib/extras/mem_reducer.py [deleted file]
third_party/waf/waflib/extras/midl.py [new file with mode: 0644]
third_party/waf/waflib/extras/misc.py [deleted file]
third_party/waf/waflib/extras/msvcdeps.py
third_party/waf/waflib/extras/msvs.py
third_party/waf/waflib/extras/netcache_client.py
third_party/waf/waflib/extras/nobuild.py [deleted file]
third_party/waf/waflib/extras/objcopy.py
third_party/waf/waflib/extras/ocaml.py [new file with mode: 0644]
third_party/waf/waflib/extras/package.py
third_party/waf/waflib/extras/parallel_debug.py
third_party/waf/waflib/extras/pch.py
third_party/waf/waflib/extras/pep8.py
third_party/waf/waflib/extras/pgicc.py [new file with mode: 0644]
third_party/waf/waflib/extras/pgicxx.py [new file with mode: 0644]
third_party/waf/waflib/extras/prefork.py [deleted file]
third_party/waf/waflib/extras/preforkjava.py [deleted file]
third_party/waf/waflib/extras/preforkunix.py [deleted file]
third_party/waf/waflib/extras/print_commands.py [deleted file]
third_party/waf/waflib/extras/proc.py
third_party/waf/waflib/extras/protoc.py
third_party/waf/waflib/extras/pyqt5.py [new file with mode: 0644]
third_party/waf/waflib/extras/pytest.py [new file with mode: 0644]
third_party/waf/waflib/extras/qnxnto.py [new file with mode: 0644]
third_party/waf/waflib/extras/qt4.py [moved from third_party/waf/waflib/Tools/qt4.py with 97% similarity]
third_party/waf/waflib/extras/relocation.py
third_party/waf/waflib/extras/remote.py
third_party/waf/waflib/extras/resx.py [new file with mode: 0644]
third_party/waf/waflib/extras/review.py
third_party/waf/waflib/extras/rst.py
third_party/waf/waflib/extras/run_do_script.py [new file with mode: 0644]
third_party/waf/waflib/extras/run_m_script.py [new file with mode: 0644]
third_party/waf/waflib/extras/run_py_script.py [new file with mode: 0644]
third_party/waf/waflib/extras/run_r_script.py [new file with mode: 0644]
third_party/waf/waflib/extras/sas.py [new file with mode: 0644]
third_party/waf/waflib/extras/satellite_assembly.py [new file with mode: 0644]
third_party/waf/waflib/extras/scala.py [new file with mode: 0644]
third_party/waf/waflib/extras/slow_qt4.py [new file with mode: 0644]
third_party/waf/waflib/extras/smart_continue.py [deleted file]
third_party/waf/waflib/extras/softlink_libs.py [new file with mode: 0644]
third_party/waf/waflib/extras/stale.py
third_party/waf/waflib/extras/stracedeps.py
third_party/waf/waflib/extras/swig.py
third_party/waf/waflib/extras/syms.py
third_party/waf/waflib/extras/sync_exec.py [deleted file]
third_party/waf/waflib/extras/ticgt.py [new file with mode: 0644]
third_party/waf/waflib/extras/unc.py [deleted file]
third_party/waf/waflib/extras/unity.py
third_party/waf/waflib/extras/use_config.py
third_party/waf/waflib/extras/valadoc.py [new file with mode: 0644]
third_party/waf/waflib/extras/why.py
third_party/waf/waflib/extras/win32_opts.py
third_party/waf/waflib/extras/wix.py [new file with mode: 0644]
third_party/waf/waflib/extras/xcode6.py [new file with mode: 0644]
third_party/waf/waflib/fixpy2.py
third_party/waf/waflib/processor.py

index a83a243..cbb5f2f 100755 (executable)
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
-# encoding: ISO8859-1
-# Thomas Nagy, 2005-2015
-
+# encoding: latin-1
+# Thomas Nagy, 2005-2018
+#
 """
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
@@ -32,16 +32,18 @@ POSSIBILITY OF SUCH DAMAGE.
 
 import os, sys, inspect
 
-VERSION="1.9.10"
+VERSION="2.0.4"
 REVISION="x"
 GIT="x"
-INSTALL=''
-C1='#>'
-C2='#6'
-C3='#4'
+INSTALL="x"
+C1='x'
+C2='x'
+C3='x'
 cwd = os.getcwd()
 join = os.path.join
 
+if sys.hexversion<0x206000f:
+       raise ImportError('Python >= 2.6 is required to create the waf file')
 
 WAF='waf'
 def b(x):
@@ -129,15 +131,16 @@ def test(dir):
                pass
 
 def find_lib():
-       return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../third_party/waf'))
+       path = '../../third_party/waf'
+       paths = [path, path+'/waflib']
+       return [os.path.abspath(os.path.join(os.path.dirname(__file__), x)) for x in paths]
 
 wafdir = find_lib()
-sys.path.insert(0, wafdir)
+for p in wafdir:
+       sys.path.insert(0, p)
 
 if __name__ == '__main__':
-
-       # TODO: remove these when possible
-       from waflib.extras import compat15
+       #import extras.compat15#PRELUDE
        import sys
 
        from waflib.Tools import ccroot, c, ar, compiler_c, gcc
@@ -147,7 +150,7 @@ if __name__ == '__main__':
        sys.modules['compiler_cc'] = compiler_c
        sys.modules['gcc'] = gcc
 
-        from waflib import Options
+       from waflib import Options
        Options.lockfile = os.environ.get('WAFLOCK', '.lock-wscript')
        if os.path.isfile(Options.lockfile) and os.stat(Options.lockfile).st_size == 0:
                os.environ['NOCLIMB'] = "1"
@@ -160,5 +163,5 @@ if __name__ == '__main__':
        Task.classes['cc_link'] = o
 
        from waflib import Scripting
-       Scripting.waf_entry_point(cwd, VERSION, wafdir)
+       Scripting.waf_entry_point(cwd, VERSION, wafdir[0])
 
diff --git a/buildtools/bin/waf-1.9 b/buildtools/bin/waf-1.9
new file mode 100755 (executable)
index 0000000..a83a243
--- /dev/null
@@ -0,0 +1,164 @@
+#!/usr/bin/env python
+# encoding: ISO8859-1
+# Thomas Nagy, 2005-2015
+
+"""
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. 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.
+
+3. The name of the author may not be used to endorse or promote products
+   derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 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 os, sys, inspect
+
+VERSION="1.9.10"
+REVISION="x"
+GIT="x"
+INSTALL=''
+C1='#>'
+C2='#6'
+C3='#4'
+cwd = os.getcwd()
+join = os.path.join
+
+
+WAF='waf'
+def b(x):
+       return x
+if sys.hexversion>0x300000f:
+       WAF='waf3'
+       def b(x):
+               return x.encode()
+
+def err(m):
+       print(('\033[91mError: %s\033[0m' % m))
+       sys.exit(1)
+
+def unpack_wafdir(dir, src):
+       f = open(src,'rb')
+       c = 'corrupt archive (%d)'
+       while 1:
+               line = f.readline()
+               if not line: err('run waf-light from a folder containing waflib')
+               if line == b('#==>\n'):
+                       txt = f.readline()
+                       if not txt: err(c % 1)
+                       if f.readline() != b('#<==\n'): err(c % 2)
+                       break
+       if not txt: err(c % 3)
+       txt = txt[1:-1].replace(b(C1), b('\n')).replace(b(C2), b('\r')).replace(b(C3), b('\x00'))
+
+       import shutil, tarfile
+       try: shutil.rmtree(dir)
+       except OSError: pass
+       try:
+               for x in ('Tools', 'extras'):
+                       os.makedirs(join(dir, 'waflib', x))
+       except OSError:
+               err("Cannot unpack waf lib into %s\nMove waf in a writable directory" % dir)
+
+       os.chdir(dir)
+       tmp = 't.bz2'
+       t = open(tmp,'wb')
+       try: t.write(txt)
+       finally: t.close()
+
+       try:
+               t = tarfile.open(tmp)
+       except:
+               try:
+                       os.system('bunzip2 t.bz2')
+                       t = tarfile.open('t')
+                       tmp = 't'
+               except:
+                       os.chdir(cwd)
+                       try: shutil.rmtree(dir)
+                       except OSError: pass
+                       err("Waf cannot be unpacked, check that bzip2 support is present")
+
+       try:
+               for x in t: t.extract(x)
+       finally:
+               t.close()
+
+       for x in ('Tools', 'extras'):
+               os.chmod(join('waflib',x), 493)
+
+       if sys.hexversion<0x300000f:
+               sys.path = [join(dir, 'waflib')] + sys.path
+               import fixpy2
+               fixpy2.fixdir(dir)
+
+       os.remove(tmp)
+       os.chdir(cwd)
+
+       try: dir = unicode(dir, 'mbcs')
+       except: pass
+       try:
+               from ctypes import windll
+               windll.kernel32.SetFileAttributesW(dir, 2)
+       except:
+               pass
+
+def test(dir):
+       try:
+               os.stat(join(dir, 'waflib'))
+               return os.path.abspath(dir)
+       except OSError:
+               pass
+
+def find_lib():
+       return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../third_party/waf'))
+
+wafdir = find_lib()
+sys.path.insert(0, wafdir)
+
+if __name__ == '__main__':
+
+       # TODO: remove these when possible
+       from waflib.extras import compat15
+       import sys
+
+       from waflib.Tools import ccroot, c, ar, compiler_c, gcc
+       sys.modules['cc'] = c
+       sys.modules['ccroot'] = ccroot
+       sys.modules['ar'] = ar
+       sys.modules['compiler_cc'] = compiler_c
+       sys.modules['gcc'] = gcc
+
+        from waflib import Options
+       Options.lockfile = os.environ.get('WAFLOCK', '.lock-wscript')
+       if os.path.isfile(Options.lockfile) and os.stat(Options.lockfile).st_size == 0:
+               os.environ['NOCLIMB'] = "1"
+       # there is a single top-level, but libraries must build independently
+       os.environ['NO_LOCK_IN_TOP'] = "1"
+
+       from waflib import Task
+       class o(object):
+               display = None
+       Task.classes['cc_link'] = o
+
+       from waflib import Scripting
+       Scripting.waf_entry_point(cwd, VERSION, wafdir)
+
index e28282b..6ad4354 100644 (file)
@@ -1,7 +1,9 @@
 # handle substitution of variables in .in files
 
-import re, os
-import Build, sys, Logs
+import sys
+import re
+import os
+from waflib import Build, Logs
 from samba_utils import SUBST_VARS_RECURSIVE
 
 def subst_at_vars(task):
index 504e902..93a43ea 100644 (file)
@@ -3,11 +3,11 @@
 # based on suncc.py from waf
 
 import os, optparse
-import Utils, Options, Configure
-import ccroot, ar
-from Configure import conftest
+from waflib import Utils, Options, Configure
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conftest
 
-from compiler_cc import c_compiler
+from waflib.Tools.compiler_c import c_compiler
 
 c_compiler['default'] = ['gcc', 'generic_cc']
 c_compiler['hpux'] = ['gcc', 'generic_cc']
index c263556..5938811 100644 (file)
@@ -2,10 +2,10 @@
 # based on suncc.py from waf
 
 import os, optparse, sys
-import Utils, Options, Configure
-import ccroot, ar
-from Configure import conftest
-import gcc
+from waflib import Utils, Options, Configure
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conftest
+from waflib.Tools import gcc
 
 
 @conftest
@@ -38,7 +38,7 @@ def gcc_modifier_hpux(conf):
 
 gcc.gcc_modifier_hpux = gcc_modifier_hpux
 
-from TaskGen import feature, after
+from waflib.TaskGen import feature, after
 @feature('cprogram', 'cshlib')
 @after('apply_link', 'apply_lib_vars', 'apply_obj_vars')
 def hpux_addfullpath(self):
index f3cb451..c33c96b 100644 (file)
@@ -3,11 +3,11 @@
 # based on suncc.py from waf
 
 import os, optparse
-import Utils, Options, Configure
-import ccroot, ar
-from Configure import conftest
+from waflib import Utils, Options, Configure
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conftest
 
-from compiler_cc import c_compiler
+from waflib.Tools.compiler_c import c_compiler
 
 c_compiler['irix'] = ['gcc', 'irixcc']
 
index 9bd33e8..f873d7d 100644 (file)
@@ -13,8 +13,8 @@
 import sys, random, threading
 try: from Queue import Queue
 except ImportError: from queue import Queue
-import Utils, Options
-from Constants import EXCEPTION, CRASHED, MAXJOBS, ASK_LATER, SKIPPED, SKIP_ME, SUCCESS
+from waflib import Utils, Options, Errors
+from waflib.TaskGen import EXCEPTION, CRASHED, MAXJOBS, ASK_LATER, SKIPPED, SKIP_ME, SUCCESS
 
 GAP = 15
 
@@ -58,7 +58,7 @@ def process(tsk):
     else:
         try:
             tsk.post_run()
-        except Utils.WafError:
+        except Errors.WafError:
             pass
         except Exception:
             tsk.err_msg = Utils.ex_stack()
index 999bad4..6094114 100644 (file)
@@ -1,7 +1,7 @@
 # handle substitution of variables in pc files
 
 import os, re, sys
-import Build, Logs
+from waflib import Build, Logs
 from samba_utils import SUBST_VARS_RECURSIVE, TO_LIST
 
 def subst_at_vars(task):
index 44daff9..786d6a6 100644 (file)
@@ -1,11 +1,12 @@
 # a waf tool to add autoconf-like macros to the configure section
 # and for SAMBA_ macros for building libraries, binaries etc
 
-import Options, Build, os
+import os
+from waflib import Options, Build
 from samba_utils import os_path_relpath, TO_LIST, samba_add_onoff_option
 from samba_autoconf import library_flags
 
-Options.Handler.SAMBA3_ADD_OPTION = samba_add_onoff_option
+Options.OptionsContext.SAMBA3_ADD_OPTION = samba_add_onoff_option
 
 def SAMBA3_IS_STATIC_MODULE(bld, module):
     '''Check whether module is in static list'''
@@ -32,7 +33,7 @@ def s3_fix_kwargs(bld, kwargs):
     '''fix the build arguments for s3 build rules to include the
     necessary includes, subdir and cflags options '''
     s3dir = os.path.join(bld.env.srcdir, 'source3')
-    s3reldir = os_path_relpath(s3dir, bld.curdir)
+    s3reldir = os_path_relpath(s3dir, bld.path.abspath())
 
     # the extra_includes list is relative to the source3 directory
     extra_includes = [ '.', 'include', 'lib' ]
index 4603e76..be46a9e 100644 (file)
@@ -1,7 +1,13 @@
 # functions for handling ABI checking of libraries
 
-import Options, Utils, os, Logs, samba_utils, sys, Task, fnmatch, re, Build
-from TaskGen import feature, before, after
+import os
+import sys
+import re
+import fnmatch
+
+from waflib import Options, Utils, Logs, Task, Build, Errors
+from waflib.TaskGen import feature, before, after
+import samba_utils
 
 # these type maps cope with platform specific names for common types
 # please add new type mappings into the list below
@@ -87,7 +93,7 @@ def abi_check_task(self):
     old_sigs = samba_utils.load_file(sig_file)
     if old_sigs is None or Options.options.ABI_UPDATE:
         if not save_sigs(sig_file, parsed_sigs):
-            raise Utils.WafError('Failed to save ABI file "%s"' % sig_file)
+            raise Errors.WafError('Failed to save ABI file "%s"' % sig_file)
         Logs.warn('Generated ABI signatures %s' % sig_file)
         return
 
@@ -112,10 +118,10 @@ def abi_check_task(self):
             got_error = True
 
     if got_error:
-        raise Utils.WafError('ABI for %s has changed - please fix library version then build with --abi-update\nSee http://wiki.samba.org/index.php/Waf#ABI_Checking for more information\nIf you have not changed any ABI, and your platform always gives this error, please configure with --abi-check-disable to skip this check' % libname)
+        raise Errors.WafError('ABI for %s has changed - please fix library version then build with --abi-update\nSee http://wiki.samba.org/index.php/Waf#ABI_Checking for more information\nIf you have not changed any ABI, and your platform always gives this error, please configure with --abi-check-disable to skip this check' % libname)
 
 
-t = Task.task_type_from_func('abi_check', abi_check_task, color='BLUE', ext_in='.bin')
+t = Task.task_factory('abi_check', abi_check_task, color='BLUE', ext_in='.bin')
 t.quiet = True
 # allow "waf --abi-check" to force re-checking the ABI
 if '--abi-check' in sys.argv:
index 521febc..97d5012 100644 (file)
@@ -1,9 +1,10 @@
 # a waf tool to add autoconf-like macros to the configure section
 
 import os, sys
-import Build, Options, preproc, Logs
-from Configure import conf
-from TaskGen import feature
+from waflib import Build, Options, Logs, Context
+from waflib.Configure import conf
+from waflib.TaskGen import feature
+from waflib.Tools import c_preproc as preproc
 from samba_utils import TO_LIST, GET_TARGET_TYPE, SET_TARGET_TYPE, unique_list, mkdir_p
 
 missing_headers = set()
@@ -44,11 +45,11 @@ def COMPOUND_START(conf, msg):
     if v != [] and v != 0:
         conf.env.in_compound = v + 1
         return
-    conf.check_message_1(msg)
-    conf.saved_check_message_1 = conf.check_message_1
-    conf.check_message_1 = null_check_message_1
-    conf.saved_check_message_2 = conf.check_message_2
-    conf.check_message_2 = null_check_message_2
+    conf.start_msg(msg)
+    conf.saved_check_message_1 = conf.start_msg
+    conf.start_msg = null_check_message_1
+    conf.saved_check_message_2 = conf.end_msg
+    conf.end_msg = null_check_message_2
     conf.env.in_compound = 1
 
 
@@ -58,9 +59,9 @@ def COMPOUND_END(conf, result):
     conf.env.in_compound -= 1
     if conf.env.in_compound != 0:
         return
-    conf.check_message_1 = conf.saved_check_message_1
-    conf.check_message_2 = conf.saved_check_message_2
-    p = conf.check_message_2
+    conf.start_msg = conf.saved_check_message_1
+    conf.end_msg = conf.saved_check_message_2
+    p = conf.end_msg
     if result is True:
         p('ok')
     elif not result:
@@ -404,7 +405,7 @@ def CHECK_CODE(conf, code, define,
         cflags.append(extra_cflags)
 
     if local_include:
-        cflags.append('-I%s' % conf.curdir)
+        cflags.append('-I%s' % conf.path.abspath())
 
     if not link:
         type='nolink'
@@ -663,8 +664,8 @@ def CHECK_FUNCS_IN(conf, list, library, mandatory=False, checklibc=False,
 @conf
 def IN_LAUNCH_DIR(conf):
     '''return True if this rule is being run from the launch directory'''
-    return os.path.realpath(conf.curdir) == os.path.realpath(Options.launch_dir)
-Options.Handler.IN_LAUNCH_DIR = IN_LAUNCH_DIR
+    return os.path.realpath(conf.path.abspath()) == os.path.realpath(Context.launch_dir)
+Options.OptionsContext.IN_LAUNCH_DIR = IN_LAUNCH_DIR
 
 
 @conf
@@ -899,7 +900,7 @@ def SETUP_CONFIGURE_CACHE(conf, enable):
         # when -C is chosen, we will use a private cache and will
         # not look into system includes. This roughtly matches what
         # autoconf does with -C
-        cache_path = os.path.join(conf.blddir, '.confcache')
+        cache_path = os.path.join(conf.bldnode.abspath(), '.confcache')
         mkdir_p(cache_path)
         Options.cache_global = os.environ['WAFCACHE'] = cache_path
     else:
index 52b2d7e..ace434f 100644 (file)
@@ -1,13 +1,13 @@
 # waf build tool for building automatic prototypes from C source
 
 import os
-import Build
+from waflib import Build
 from samba_utils import SET_TARGET_TYPE, os_path_relpath
 
 def SAMBA_AUTOPROTO(bld, header, source):
     '''rule for samba prototype generation'''
     bld.SET_BUILD_GROUP('prototypes')
-    relpath = os_path_relpath(bld.curdir, bld.srcnode.abspath())
+    relpath = os_path_relpath(bld.path.abspath(), bld.srcnode.abspath())
     name = os.path.join(relpath, header)
     SET_TARGET_TYPE(bld, name, 'PROTOTYPE')
     t = bld(
index 253d604..fea4cb2 100644 (file)
@@ -1,8 +1,8 @@
 # functions to support bundled libraries
 
 import sys
-import Build, Options, Logs
-from Configure import conf
+from waflib import Build, Options, Logs
+from waflib.Configure import conf
 from samba_utils import TO_LIST
 
 def PRIVATE_NAME(bld, name, private_extension, private_library):
@@ -51,19 +51,19 @@ Build.BuildContext.BUILTIN_LIBRARY = BUILTIN_LIBRARY
 
 def BUILTIN_DEFAULT(opt, builtins):
     '''set a comma separated default list of builtin libraries for this package'''
-    if 'BUILTIN_LIBRARIES_DEFAULT' in Options.options:
+    if 'BUILTIN_LIBRARIES_DEFAULT' in Options.options.__dict__:
         return
-    Options.options['BUILTIN_LIBRARIES_DEFAULT'] = builtins
-Options.Handler.BUILTIN_DEFAULT = BUILTIN_DEFAULT
+    Options.options.__dict__['BUILTIN_LIBRARIES_DEFAULT'] = builtins
+Options.OptionsContext.BUILTIN_DEFAULT = BUILTIN_DEFAULT
 
 
 def PRIVATE_EXTENSION_DEFAULT(opt, extension, noextension=''):
     '''set a default private library extension'''
-    if 'PRIVATE_EXTENSION_DEFAULT' in Options.options:
+    if 'PRIVATE_EXTENSION_DEFAULT' in Options.options.__dict__:
         return
-    Options.options['PRIVATE_EXTENSION_DEFAULT'] = extension
-    Options.options['PRIVATE_EXTENSION_EXCEPTION'] = noextension
-Options.Handler.PRIVATE_EXTENSION_DEFAULT = PRIVATE_EXTENSION_DEFAULT
+    Options.options.__dict__['PRIVATE_EXTENSION_DEFAULT'] = extension
+    Options.options.__dict__['PRIVATE_EXTENSION_EXCEPTION'] = noextension
+Options.OptionsContext.PRIVATE_EXTENSION_DEFAULT = PRIVATE_EXTENSION_DEFAULT
 
 
 def minimum_library_version(conf, libname, default):
index 74f9782..5af57e2 100644 (file)
@@ -2,35 +2,35 @@
 # to test for commonly needed configuration options
 
 import os, shutil, re
-import Build, Configure, Utils, Options, Logs
-from Configure import conf
+from waflib import Build, Configure, Utils, Options, Logs, Errors
+from waflib.Configure import conf
 from samba_utils import TO_LIST, ADD_LD_LIBRARY_PATH
 
 
 def add_option(self, *k, **kw):
     '''syntax help: provide the "match" attribute to opt.add_option() so that folders can be added to specific config tests'''
-    Options.parser = self
+    Options.OptionsContext.parser = self
     match = kw.get('match', [])
     if match:
         del kw['match']
     opt = self.parser.add_option(*k, **kw)
     opt.match = match
     return opt
-Options.Handler.add_option = add_option
+Options.OptionsContext.add_option = add_option
 
 @conf
 def check(self, *k, **kw):
     '''Override the waf defaults to inject --with-directory options'''
 
     if not 'env' in kw:
-        kw['env'] = self.env.copy()
+        kw['env'] = self.env.derive()
 
     # match the configuration test with specific options, for example:
     # --with-libiconv -> Options.options.iconv_open -> "Checking for library iconv"
     additional_dirs = []
     if 'msg' in kw:
         msg = kw['msg']
-        for x in Options.Handler.parser.parser.option_list:
+        for x in Options.OptionsContext.parser.parser.option_list:
              if getattr(x, 'match', None) and msg in x.match:
                  d = getattr(Options.options, x.dest, '')
                  if d:
@@ -47,12 +47,12 @@ def check(self, *k, **kw):
     add_options_dir(additional_dirs, kw['env'])
 
     self.validate_c(kw)
-    self.check_message_1(kw['msg'])
+    self.start_msg(kw['msg'])
     ret = None
     try:
         ret = self.run_c_code(*k, **kw)
     except Configure.ConfigurationError as e:
-        self.check_message_2(kw['errmsg'], 'YELLOW')
+        self.end_msg(kw['errmsg'], 'YELLOW')
         if 'mandatory' in kw and kw['mandatory']:
             if Logs.verbose > 1:
                 raise
@@ -60,7 +60,7 @@ def check(self, *k, **kw):
                 self.fatal('the configuration failed (see %r)' % self.log.name)
     else:
         kw['success'] = ret
-        self.check_message_2(self.ret_msg(kw['okmsg'], kw))
+        self.end_msg(self.ret_msg(kw['okmsg'], kw))
 
         # success! keep the CPPPATH/LIBPATH
         add_options_dir(additional_dirs, self.env)
@@ -163,7 +163,7 @@ def find_config_dir(conf):
     '''find a directory to run tests in'''
     k = 0
     while k < 10000:
-        dir = os.path.join(conf.blddir, '.conf_check_%d' % k)
+        dir = os.path.join(conf.bldnode.abspath(), '.conf_check_%d' % k)
         try:
             shutil.rmtree(dir)
         except OSError:
@@ -338,7 +338,8 @@ def CHECK_LIBRARY_SUPPORT(conf, rpath=False, version_script=False, msg=None):
 
     # we need to run the program, try to get its result
     args = conf.SAMBA_CROSS_ARGS(msg=msg)
-    proc = Utils.pproc.Popen([lastprog] + args, stdout=Utils.pproc.PIPE, stderr=Utils.pproc.PIPE)
+    proc = Utils.subprocess.Popen([lastprog] + args,
+            stdout=Utils.subprocess.PIPE, stderr=Utils.subprocess.PIPE)
     (out, err) = proc.communicate()
     w = conf.log.write
     w(str(out))
@@ -365,7 +366,7 @@ def CHECK_PERL_MANPAGE(conf, msg=None, section=None):
         else:
             msg = "perl manpage generation"
 
-    conf.check_message_1(msg)
+    conf.start_msg(msg)
 
     dir = find_config_dir(conf)
 
@@ -382,28 +383,28 @@ WriteMakefile(
 """)
     back = os.path.abspath('.')
     os.chdir(bdir)
-    proc = Utils.pproc.Popen(['perl', 'Makefile.PL'],
-                             stdout=Utils.pproc.PIPE,
-                             stderr=Utils.pproc.PIPE)
+    proc = Utils.subprocess.Popen(['perl', 'Makefile.PL'],
+                             stdout=Utils.subprocess.PIPE,
+                             stderr=Utils.subprocess.PIPE)
     (out, err) = proc.communicate()
     os.chdir(back)
 
     ret = (proc.returncode == 0)
     if not ret:
-        conf.check_message_2('not found', color='YELLOW')
+        conf.end_msg('not found', color='YELLOW')
         return
 
     if section:
         man = Utils.readf(os.path.join(bdir,'Makefile'))
         m = re.search('MAN%sEXT\s+=\s+(\w+)' % section, man)
         if not m:
-            conf.check_message_2('not found', color='YELLOW')
+            conf.end_msg('not found', color='YELLOW')
             return
         ext = m.group(1)
-        conf.check_message_2(ext)
+        conf.end_msg(ext)
         return ext
 
-    conf.check_message_2('ok')
+    conf.end_msg('ok')
     return True
 
 
@@ -512,7 +513,7 @@ def CHECK_STANDARD_LIBPATH(conf):
         # option not supported by compiler - use a standard list of directories
         dirlist = [ '/usr/lib', '/usr/lib64' ]
     except:
-        raise Utils.WafError('Unexpected error running "%s"' % (cmd))
+        raise Errors.WafError('Unexpected error running "%s"' % (cmd))
     else:
         dirlist = []
         for line in out:
index b8f2000..5191acb 100644 (file)
@@ -1,8 +1,8 @@
 # functions for handling cross-compilation
 
 import os, sys, re, shlex
-import Utils, Logs, Options
-from Configure import conf
+from waflib import Utils, Logs, Options, Errors
+from waflib.Configure import conf
 
 real_Popen = None
 
@@ -81,12 +81,12 @@ def cross_answer(ca_file, msg):
                     f.close()
                     return (int(m.group(1)), m.group(2))
                 else:
-                    raise Utils.WafError("Bad answer format '%s' in %s" % (line, ca_file))
+                    raise Errors.WafError("Bad answer format '%s' in %s" % (line, ca_file))
     f.close()
     return ANSWER_UNKNOWN
 
 
-class cross_Popen(Utils.pproc.Popen):
+class cross_Popen(Utils.subprocess.Popen):
     '''cross-compilation wrapper for Popen'''
     def __init__(*k, **kw):
         (obj, args) = k
@@ -154,11 +154,11 @@ def SAMBA_CROSS_ARGS(conf, msg=None):
 
     if conf.env.CROSS_ANSWERS:
         if msg is None:
-            raise Utils.WafError("Cannot have NULL msg in cross-answers")
+            raise Errors.WafError("Cannot have NULL msg in cross-answers")
         ret.extend(['--cross-answers', os.path.join(Options.launch_dir, conf.env.CROSS_ANSWERS), msg])
 
     if ret == []:
-        raise Utils.WafError("Cannot cross-compile without either --cross-execute or --cross-answers")
+        raise Errors.WafError("Cannot cross-compile without either --cross-execute or --cross-answers")
 
     return ret
 
@@ -167,5 +167,5 @@ def SAMBA_CROSS_CHECK_COMPLETE(conf):
     '''check if we have some unanswered questions'''
     global cross_answers_incomplete
     if conf.env.CROSS_COMPILE and cross_answers_incomplete:
-        raise Utils.WafError("Cross answers file %s is incomplete" % conf.env.CROSS_ANSWERS)
+        raise Errors.WafError("Cross answers file %s is incomplete" % conf.env.CROSS_ANSWERS)
     return True
index 72892be..542e682 100644 (file)
@@ -2,9 +2,10 @@
 
 import os, sys, re, time
 
-import Build, Environment, Options, Logs, Utils
-from Logs import debug
-from Configure import conf
+from waflib import Build, Options, Logs, Utils, Errors
+from waflib.Logs import debug
+from waflib.Configure import conf
+from waflib import ConfigSet
 
 from samba_bundled import BUILTIN_LIBRARY
 from samba_utils import LOCAL_CACHE, TO_LIST, get_tgt_list, unique_list, os_path_relpath
@@ -302,7 +303,7 @@ def check_duplicate_sources(bld, tgt_list):
             Logs.warn("WARNING: source %s is in more than one target: %s" % (s, subsystems[s].keys()))
         for tname in subsystems[s]:
             if len(subsystems[s][tname]) > 1:
-                raise Utils.WafError("ERROR: source %s is in more than one subsystem of target '%s': %s" % (s, tname, subsystems[s][tname]))
+                raise Errors.WafError("ERROR: source %s is in more than one subsystem of target '%s': %s" % (s, tname, subsystems[s][tname]))
 
     return True
 
@@ -963,7 +964,7 @@ savedeps_files   = ['buildtools/wafsamba/samba_deps.py']
 def save_samba_deps(bld, tgt_list):
     '''save the dependency calculations between builds, to make
        further builds faster'''
-    denv = Environment.Environment()
+    denv = ConfigSet.ConfigSet()
 
     denv.version = savedeps_version
     denv.savedeps_inputs = savedeps_inputs
@@ -1017,8 +1018,8 @@ def save_samba_deps(bld, tgt_list):
 
 def load_samba_deps(bld, tgt_list):
     '''load a previous set of build dependencies if possible'''
-    depsfile = os.path.join(bld.bdir, "sambadeps")
-    denv = Environment.Environment()
+    depsfile = os.path.join(bld.bldnode.abspath(), "sambadeps")
+    denv = ConfigSet.ConfigSet()
     try:
         debug('deps: checking saved dependencies')
         denv.load_fast(depsfile)
index 06c973a..29c5fd1 100644 (file)
@@ -2,8 +2,8 @@
 # uses git ls-files to get file lists
 
 import os, sys, tarfile
-import Utils, Scripting, Logs, Options
-from Configure import conf
+from waflib import Utils, Scripting, Logs, Options
+from waflib.Configure import conf
 from samba_utils import os_path_relpath
 from waflib import Context
 
@@ -164,12 +164,12 @@ def dist(appname='', version=''):
 
     if not isinstance(appname, str) or not appname:
         # this copes with a mismatch in the calling arguments for dist()
-        appname = Utils.g_module.APPNAME
-        version = Utils.g_module.VERSION
+        appname = Context.g_module.APPNAME
+        version = Context.g_module.VERSION
     if not version:
-        version = Utils.g_module.VERSION
+        version = Context.g_module.VERSION
 
-    srcdir = os.path.normpath(os.path.join(os.path.dirname(Utils.g_module.root_path), Utils.g_module.srcdir))
+    srcdir = os.path.normpath(os.path.join(os.path.dirname(Context.g_module.root_path), Context.g_module.srcdir))
 
     if not dist_dirs:
         Logs.error('You must use samba_dist.DIST_DIRS() to set which directories to package')
index 0a80082..a268c01 100644 (file)
@@ -1,7 +1,7 @@
 # specialist handling of header files for Samba
 
 import os, re, sys, fnmatch
-import Build, Logs, Utils
+from waflib import Build, Logs, Utils, Errors
 from samba_utils import TO_LIST, os_path_relpath
 
 
@@ -99,7 +99,7 @@ def create_public_header(task):
         os.unlink(tgt)
         sys.stderr.write("%s:%u:Error: unable to resolve public header %s (maybe try one of %s)\n" % (
             os.path.relpath(src, os.getcwd()), linenumber, hpath, suggested))
-        raise Utils.WafError("Unable to resolve header path '%s' in public header '%s' in directory %s" % (
+        raise Errors.WafError("Unable to resolve header path '%s' in public header '%s' in directory %s" % (
             hpath, relsrc, task.env.RELPATH))
     infile.close()
     outfile.close()
@@ -148,11 +148,12 @@ def PUBLIC_HEADERS(bld, public_headers, header_path=None, public_headers_install
         else:
             h_name =  h
             inst_name = os.path.basename(h)
-        relpath1 = os_path_relpath(bld.srcnode.abspath(), bld.curdir)
-        relpath2 = os_path_relpath(bld.curdir, bld.srcnode.abspath())
+        curdir = bld.path.abspath()
+        relpath1 = os_path_relpath(bld.srcnode.abspath(), curdir)
+        relpath2 = os_path_relpath(curdir, bld.srcnode.abspath())
         targetdir = os.path.normpath(os.path.join(relpath1, bld.env.build_public_headers, inst_path))
-        if not os.path.exists(os.path.join(bld.curdir, targetdir)):
-            raise Utils.WafError("missing source directory %s for public header %s" % (targetdir, inst_name))
+        if not os.path.exists(os.path.join(curdir, targetdir)):
+            raise Errors.WafError("missing source directory %s for public header %s" % (targetdir, inst_name))
         target = os.path.join(targetdir, inst_name)
 
         # the source path of the header, relative to the top of the source tree
index e967a32..47bc0cb 100644 (file)
@@ -4,8 +4,8 @@
 # library use
 
 import os
-import Utils
-from TaskGen import feature, before, after
+from waflib import Utils, Errors
+from waflib.TaskGen import feature, before, after
 from samba_utils import LIB_PATH, MODE_755, install_rpath, build_rpath
 
 @feature('install_bin')
@@ -228,7 +228,7 @@ def symlink_bin(self):
         return
 
     if not self.link_task.outputs or not self.link_task.outputs[0]:
-        raise Utils.WafError('no outputs found for %s in symlink_bin' % self.name)
+        raise Errors.WafError('no outputs found for %s in symlink_bin' % self.name)
     binpath = self.link_task.outputs[0].abspath(self.env)
     bldpath = os.path.join(self.bld.env.BUILD_DIRECTORY, self.link_task.outputs[0].name)
 
index 2b93937..00bedcd 100644 (file)
@@ -1,6 +1,6 @@
 # a waf tool to add extension based build patterns for Samba
 
-import Build
+from waflib import Build
 from wafsamba import samba_version_file
 
 def write_version_header(task):
index 48d9a96..eca72d6 100644 (file)
@@ -1,5 +1,5 @@
-import Utils
-from Configure import conf
+from waflib import Utils
+from waflib.Configure import conf
 
 done = {}
 
@@ -9,7 +9,7 @@ def SAMBA_CHECK_PERL(conf, mandatory=True, version=(5,0,0)):
         return
     done["done"] = True
     conf.find_program('perl', var='PERL', mandatory=mandatory)
-    conf.check_tool('perl')
+    conf.load('perl')
     path_perl = conf.find_program('perl')
     conf.env.PERL_SPECIFIED = (conf.env.PERL != path_perl)
     conf.check_perl_version(version)
index 79f081d..2ff2c0d 100644 (file)
@@ -1,8 +1,8 @@
 # waf build tool for building IDL files with pidl
 
 import os
-import Build, Utils
-from TaskGen import feature, before
+from waflib import Build, Utils
+from waflib.TaskGen import feature, before
 from samba_utils import SET_TARGET_TYPE, TO_LIST, LOCAL_CACHE
 
 def SAMBA_PIDL(bld, pname, source,
@@ -97,7 +97,7 @@ def SAMBA_PIDL(bld, pname, source,
         pidl_headers = LOCAL_CACHE(bld, 'PIDL_HEADERS')
         pidl_headers[name] = [bld.path.find_or_declare(out_files[table_header_idx])]
 
-    t.more_includes = '#' + bld.path.relpath_gen(bld.srcnode)
+    t.more_includes = '#' + bld.path.path_from(bld.srcnode)
 Build.BuildContext.SAMBA_PIDL = SAMBA_PIDL
 
 
index a7ffb7a..aecaf7d 100644 (file)
@@ -1,8 +1,8 @@
 # waf build tool for building IDL files with pidl
 
 import os
-import Build, Logs, Utils, Configure
-from Configure import conf
+from waflib import Build, Logs, Utils, Configure, Errors
+from waflib.Configure import conf
 
 @conf
 def SAMBA_CHECK_PYTHON(conf, mandatory=True, version=(2,4,2)):
@@ -14,12 +14,12 @@ def SAMBA_CHECK_PYTHON(conf, mandatory=True, version=(2,4,2)):
     interpreters = []
 
     if conf.env['EXTRA_PYTHON']:
-        conf.all_envs['extrapython'] = conf.env.copy()
+        conf.all_envs['extrapython'] = conf.env.derive()
         conf.setenv('extrapython')
         conf.env['PYTHON'] = conf.env['EXTRA_PYTHON']
         conf.env['IS_EXTRA_PYTHON'] = 'yes'
         conf.find_program('python', var='PYTHON', mandatory=True)
-        conf.check_tool('python')
+        conf.load('python')
         try:
             conf.check_python_version((3, 3, 0))
         except Exception:
@@ -29,7 +29,7 @@ def SAMBA_CHECK_PYTHON(conf, mandatory=True, version=(2,4,2)):
         conf.setenv('default')
 
     conf.find_program('python', var='PYTHON', mandatory=mandatory)
-    conf.check_tool('python')
+    conf.load('python')
     path_python = conf.find_program('python')
     conf.env.PYTHON_SPECIFIED = (conf.env.PYTHON != path_python)
     conf.check_python_version(version)
@@ -42,7 +42,7 @@ def SAMBA_CHECK_PYTHON(conf, mandatory=True, version=(2,4,2)):
 def SAMBA_CHECK_PYTHON_HEADERS(conf, mandatory=True):
     if conf.env.disable_python:
         if mandatory:
-            raise Utils.WafError("Cannot check for python headers when "
+            raise Errors.WafError("Cannot check for python headers when "
                                  "--disable-python specified")
 
         conf.msg("python headers", "Check disabled due to --disable-python")
@@ -66,7 +66,7 @@ def SAMBA_CHECK_PYTHON_HEADERS(conf, mandatory=True):
         if conf.env['EXTRA_PYTHON']:
             extraversion = conf.all_envs['extrapython']['PYTHON_VERSION']
             if extraversion == conf.env['PYTHON_VERSION']:
-                raise Utils.WafError("extrapython %s is same as main python %s" % (
+                raise Errors.WafError("extrapython %s is same as main python %s" % (
                     extraversion, conf.env['PYTHON_VERSION']))
     else:
         conf.msg("python headers", "using cache")
@@ -79,9 +79,9 @@ def SAMBA_CHECK_PYTHON_HEADERS(conf, mandatory=True):
 
 def _check_python_headers(conf, mandatory):
     try:
-        Configure.ConfigurationError
+        conf.errors.ConfigurationError
         conf.check_python_headers()
-    except Configure.ConfigurationError:
+    except conf.errors.ConfigurationError:
         if mandatory:
              raise
 
index 1144f81..0ababa5 100644 (file)
@@ -1,12 +1,12 @@
 # functions to support third party libraries
 
 import os
-import Utils, Build
-from Configure import conf
+from waflib import Utils, Build, Context
+from waflib.Configure import conf
 
 @conf
 def CHECK_FOR_THIRD_PARTY(conf):
-    return os.path.exists(os.path.join(Utils.g_module.srcdir, 'third_party'))
+    return os.path.exists(os.path.join(Context.g_module.srcdir, 'third_party'))
 
 Build.BuildContext.CHECK_FOR_THIRD_PARTY = CHECK_FOR_THIRD_PARTY
 
index 925e874..a2afbbf 100644 (file)
@@ -3,10 +3,11 @@
 
 import os, sys, re, fnmatch, shlex, inspect
 from optparse import SUPPRESS_HELP
-from waflib import Build, Options, Utils, Task, Logs, Configure, Errors
-from TaskGen import feature, before, after
-from Configure import ConfigurationContext
-from Logs import debug
+from waflib import Build, Options, Utils, Task, Logs, Configure, Errors, Context
+from waflib.TaskGen import feature, before, after
+from waflib.Configure import ConfigurationContext
+from waflib.Logs import debug
+from waflib import ConfigSet
 
 # TODO: make this a --option
 LIB_PATH="shared"
@@ -45,10 +46,10 @@ def SET_TARGET_TYPE(ctx, target, value):
     '''set the target type of a target'''
     cache = LOCAL_CACHE(ctx, 'TARGET_TYPE')
     if target in cache and cache[target] != 'EMPTY':
-        Logs.error("ERROR: Target '%s' in directory %s re-defined as %s - was %s" % (target, ctx.curdir, value, cache[target]))
+        Logs.error("ERROR: Target '%s' in directory %s re-defined as %s - was %s" % (target, ctx.path.abspath(), value, cache[target]))
         sys.exit(1)
     LOCAL_CACHE_SET(ctx, 'TARGET_TYPE', target, value)
-    debug("task_gen: Target '%s' created of type '%s' in %s" % (target, value, ctx.curdir))
+    debug("task_gen: Target '%s' created of type '%s' in %s" % (target, value, ctx.path.abspath()))
     return True
 
 
@@ -125,7 +126,7 @@ def LOCAL_CACHE_SET(ctx, cachename, key, value):
 def ASSERT(ctx, expression, msg):
     '''a build assert call'''
     if not expression:
-        raise Utils.WafError("ERROR: %s\n" % msg)
+        raise Errors.WafError("ERROR: %s\n" % msg)
 Build.BuildContext.ASSERT = ASSERT
 
 
@@ -146,9 +147,9 @@ def dict_concat(d1, d2):
 
 def ADD_COMMAND(opt, name, function):
     '''add a new top level command to waf'''
-    Utils.g_module.__dict__[name] = function
+    Context.g_module.__dict__[name] = function
     opt.name = function
-Options.Handler.ADD_COMMAND = ADD_COMMAND
+Options.OptionsContext.ADD_COMMAND = ADD_COMMAND
 
 
 @feature('c', 'cc', 'cshlib', 'cprogram')
@@ -223,7 +224,7 @@ def subst_vars_error(string, env):
         if re.match('\$\{\w+\}', v):
             vname = v[2:-1]
             if not vname in env:
-                raise KeyError("Failed to find variable %s in %s" % (vname, string))
+                raise KeyError("Failed to find variable %s in %s in env %s <%s>" % (vname, string, env.__class__, str(env)))
             v = env[vname]
             if isinstance(v, list):
                 v = ' '.join(v)
@@ -338,8 +339,7 @@ def EXPAND_VARIABLES(ctx, varstr, vars=None):
     if not isinstance(varstr, str):
         return varstr
 
-    import Environment
-    env = Environment.Environment()
+    env = ConfigSet.ConfigSet()
     ret = varstr
     # substitute on user supplied dict if avaiilable
     if vars is not None:
@@ -378,7 +378,7 @@ def RUN_COMMAND(cmd,
 def RUN_PYTHON_TESTS(testfiles, pythonpath=None, extra_env=None):
     env = LOAD_ENVIRONMENT()
     if pythonpath is None:
-        pythonpath = os.path.join(Utils.g_module.blddir, 'python')
+        pythonpath = os.path.join(Context.g_module.blddir, 'python')
     result = 0
     for interp in env.python_interpreters:
         if not isinstance(interp, str):
@@ -410,8 +410,7 @@ except:
         # Try to use MD5 function. In FIPS mode this will cause an exception
         foo = md5.md5('abcd')
     except:
-        import Constants
-        Constants.SIG_NIL = hash('abcd')
+        Context.SIG_NIL = hash('abcd')
         class replace_md5(object):
             def __init__(self):
                 self.val = None
@@ -437,10 +436,10 @@ except:
 def LOAD_ENVIRONMENT():
     '''load the configuration environment, allowing access to env vars
        from new commands'''
-    import Environment
-    env = Environment.Environment()
+    env = ConfigSet.ConfigSet()
     try:
-        env.load('bin/c4che/default_cache.py')
+        p = os.path.join(Context.g_module.out, 'c4che/default_cache.py')
+        env.load(p)
     except (OSError, IOError):
         pass
     return env
@@ -448,8 +447,9 @@ def LOAD_ENVIRONMENT():
 
 def IS_NEWER(bld, file1, file2):
     '''return True if file1 is newer than file2'''
-    t1 = os.stat(os.path.join(bld.curdir, file1)).st_mtime
-    t2 = os.stat(os.path.join(bld.curdir, file2)).st_mtime
+    curdir = bld.path.abspath()
+    t1 = os.stat(os.path.join(curdir, file1)).st_mtime
+    t2 = os.stat(os.path.join(curdir, file2)).st_mtime
     return t1 > t2
 Build.BuildContext.IS_NEWER = IS_NEWER
 
@@ -459,31 +459,27 @@ def RECURSE(ctx, directory):
     '''recurse into a directory, relative to the curdir or top level'''
     try:
         visited_dirs = ctx.visited_dirs
-    except:
+    except AttributeError:
         visited_dirs = ctx.visited_dirs = set()
-    d = os.path.join(ctx.curdir, directory)
+    d = os.path.join(ctx.path.abspath(), directory)
     if os.path.exists(d):
         abspath = os.path.abspath(d)
     else:
-        abspath = os.path.abspath(os.path.join(Utils.g_module.srcdir, directory))
+        abspath = os.path.abspath(os.path.join(Context.g_module.srcdir, directory))
     ctxclass = ctx.__class__.__name__
     key = ctxclass + ':' + abspath
     if key in visited_dirs:
         # already done it
         return
     visited_dirs.add(key)
-    relpath = os_path_relpath(abspath, ctx.curdir)
+    relpath = os_path_relpath(abspath, ctx.path.abspath())
+    if ctxclass in ['tmp', 'OptionsContext', 'ConfigurationContext', 'BuildContext']:
+        return ctx.recurse(relpath)
     if 'waflib.extras.compat15' in sys.modules:
         return ctx.recurse(relpath)
-    if ctxclass == 'Handler':
-        return ctx.sub_options(relpath)
-    if ctxclass == 'ConfigurationContext':
-        return ctx.sub_config(relpath)
-    if ctxclass == 'BuildContext':
-        return ctx.add_subdirs(relpath)
-    Logs.error('Unknown RECURSE context class', ctxclass)
+    Logs.error('Unknown RECURSE context class: {}'.format(ctxclass))
     raise
-Options.Handler.RECURSE = RECURSE
+Options.OptionsContext.RECURSE = RECURSE
 Build.BuildContext.RECURSE = RECURSE
 
 
@@ -542,7 +538,7 @@ def option_group(opt, name):
     gr = opt.add_option_group(name)
     option_groups[name] = gr
     return gr
-Options.Handler.option_group = option_group
+Options.OptionsContext.option_group = option_group
 
 
 def save_file(filename, contents, create_dir=False):
@@ -571,9 +567,9 @@ def load_file(filename):
 
 def reconfigure(ctx):
     '''rerun configure if necessary'''
-    import Configure, samba_wildcard, Scripting
     if not os.path.exists(".lock-wscript"):
-        raise Utils.WafError('configure has not been run')
+        raise Errors.WafError('configure has not been run')
+    import samba_wildcard
     bld = samba_wildcard.fake_build_environment()
     Configure.autoconfig = True
     Scripting.check_configured(bld)
@@ -646,7 +642,7 @@ def get_tgt_list(bld):
         tgt_list.append(t)
     return tgt_list
 
-from Constants import WSCRIPT_FILE
+from waflib.Context import WSCRIPT_FILE
 def PROCESS_SEPARATE_RULE(self, rule):
     ''' cause waf to process additional script based on `rule'.
         You should have file named wscript_<stage>_rule in the current directory
@@ -657,15 +653,21 @@ def PROCESS_SEPARATE_RULE(self, rule):
         stage = 'configure'
     elif isinstance(self, Build.BuildContext):
         stage = 'build'
-    file_path = os.path.join(self.curdir, WSCRIPT_FILE+'_'+stage+'_'+rule)
-    txt = load_file(file_path)
-    if txt:
-        dc = {'ctx': self}
-        if getattr(self.__class__, 'pre_recurse', None):
-            dc = self.pre_recurse(txt, file_path, self.curdir)
-        exec(compile(txt, file_path, 'exec'), dc)
-        if getattr(self.__class__, 'post_recurse', None):
-            dc = self.post_recurse(txt, file_path, self.curdir)
+    file_path = os.path.join(self.path.abspath(), WSCRIPT_FILE+'_'+stage+'_'+rule)
+    node = self.root.find_node(file_path)
+    if node:
+        try:
+            cache = self.recurse_cache
+        except AttributeError:
+            cache = self.recurse_cache = {}
+        if node not in cache:
+            cache[node] = True
+            self.pre_recurse(node)
+            try:
+                function_code = node.read('rU', None)
+                exec(compile(function_code, node.abspath(), 'exec'), self.exec_dict)
+            finally:
+                self.post_recurse(node)
 
 Build.BuildContext.PROCESS_SEPARATE_RULE = PROCESS_SEPARATE_RULE
 ConfigurationContext.PROCESS_SEPARATE_RULE = PROCESS_SEPARATE_RULE
@@ -722,4 +724,4 @@ def samba_add_onoff_option(opt, option, help=(), dest=None, default=True,
                    default=default)
     opt.add_option(without_val, help=SUPPRESS_HELP, action="store_false",
                    dest=dest)
-Options.Handler.samba_add_onoff_option = samba_add_onoff_option
+Options.OptionsContext.samba_add_onoff_option = samba_add_onoff_option
index be26439..239f244 100644 (file)
@@ -1,5 +1,5 @@
 import os
-import Utils
+from waflib import Utils, Context
 import samba_utils
 from samba_git import find_git
 
@@ -260,5 +260,5 @@ def load_version(env=None, is_install=True):
         env = samba_utils.LOAD_ENVIRONMENT()
 
     version = samba_version_file("./VERSION", ".", env, is_install=is_install)
-    Utils.g_module.VERSION = version.STRING
+    Context.g_module.VERSION = version.STRING
     return version
index 6c3523e..0bb9b1b 100644 (file)
@@ -1,10 +1,10 @@
 # compatibility layer for building with more recent waf versions
 
 import os, shlex, sys
-import Build, Configure, Node, Utils, Options, Logs
+from waflib import Build, Configure, Node, Utils, Options, Logs
 from waflib import ConfigSet
-from TaskGen import feature, after
-from Configure import conf, ConfigurationContext
+from waflib.TaskGen import feature, after
+from waflib.Configure import conf, ConfigurationContext
 
 from waflib.Tools import bison, flex
 sys.modules['bison'] = bison
@@ -119,32 +119,6 @@ def find_program_samba(self, *k, **kw):
 Configure.ConfigurationContext.find_program_old = Configure.ConfigurationContext.find_program
 Configure.ConfigurationContext.find_program = find_program_samba
 
-def PROCESS_SEPARATE_RULE(self, rule):
-    ''' cause waf to process additional script based on `rule'.
-        You should have file named wscript_<stage>_rule in the current directory
-        where stage is either 'configure' or 'build'
-    '''
-    stage = ''
-    if isinstance(self, Configure.ConfigurationContext):
-        stage = 'configure'
-    elif isinstance(self, Build.BuildContext):
-        stage = 'build'
-    script = self.path.find_node('wscript_'+stage+'_'+rule)
-    if script:
-        txt = script.read()
-        bld = self
-        conf = self
-        ctx = self
-        dc = {'ctx': self, 'conf': self, 'bld': self}
-        if getattr(self.__class__, 'pre_recurse', None):
-            dc = self.pre_recurse(script)
-        exec(compile(txt, script.abspath(), 'exec'), dc)
-        if getattr(self.__class__, 'post_recurse', None):
-            dc = self.post_recurse(script)
-
-Build.BuildContext.PROCESS_SEPARATE_RULE = PROCESS_SEPARATE_RULE
-ConfigurationContext.PROCESS_SEPARATE_RULE = PROCESS_SEPARATE_RULE
-
 Build.BuildContext.ENFORCE_GROUP_ORDERING = Utils.nada
 Build.BuildContext.AUTOCLEANUP_STALE_FILES = Utils.nada
 
@@ -159,7 +133,7 @@ def check(self, *k, **kw):
     additional_dirs = []
     if 'msg' in kw:
         msg = kw['msg']
-        for x in Options.parser.parser.option_list:
+        for x in Options.OptionsContext.parser.parser.option_list:
              if getattr(x, 'match', None) and msg in x.match:
                  d = getattr(Options.options, x.dest, '')
                  if d:
@@ -265,3 +239,34 @@ def CHECK_CFG(self, *k, **kw):
         kw['mandatory'] = False
     kw['global_define'] = True
     return self.check_cfg(*k, **kw)
+
+def cmd_output(cmd, **kw):
+
+       silent = False
+       if 'silent' in kw:
+               silent = kw['silent']
+               del(kw['silent'])
+
+       if 'e' in kw:
+               tmp = kw['e']
+               del(kw['e'])
+               kw['env'] = tmp
+
+       kw['shell'] = isinstance(cmd, str)
+       kw['stdout'] = Utils.subprocess.PIPE
+       if silent:
+               kw['stderr'] = Utils.subprocess.PIPE
+
+       try:
+               p = Utils.subprocess.Popen(cmd, **kw)
+               output = p.communicate()[0]
+       except OSError as e:
+               raise ValueError(str(e))
+
+       if p.returncode:
+               if not silent:
+                       msg = "command execution failed: %s -> %r" % (cmd, str(output))
+                       raise ValueError(msg)
+               output = ''
+       return output
+Utils.cmd_output = cmd_output
index ed3e0c2..5e15eff 100644 (file)
@@ -1,15 +1,15 @@
 # based on playground/evil in the waf svn tree
 
 import os, datetime, fnmatch
-import Scripting, Utils, Options, Logs, Environment
-from Constants import SRCDIR, BLDDIR
+from waflib import Scripting, Utils, Options, Logs, Errors
+from waflib import ConfigSet
 from samba_utils import LOCAL_CACHE, os_path_relpath
 
 def run_task(t, k):
     '''run a single build task'''
     ret = t.run()
     if ret:
-        raise Utils.WafError("Failed to build %s: %u" % (k, ret))
+        raise Errors.WafError("Failed to build %s: %u" % (k, ret))
 
 
 def run_named_build_task(cmd):
@@ -45,7 +45,7 @@ def run_named_build_task(cmd):
 
 
     if not found:
-        raise Utils.WafError("Unable to find build target matching %s" % cmd)
+        raise Errors.WafError("Unable to find build target matching %s" % cmd)
 
 
 def rewrite_compile_targets():
@@ -125,7 +125,7 @@ def wildcard_main(missing_cmd_fn):
 def fake_build_environment(info=True, flush=False):
     """create all the tasks for the project, but do not run the build
     return the build context in use"""
-    bld = getattr(Utils.g_module, 'build_context', Utils.Context)()
+    bld = getattr(Context.g_module, 'build_context', Utils.Context)()
     bld = Scripting.check_configured(bld)
 
     Options.commands['install'] = False
@@ -134,16 +134,15 @@ def fake_build_environment(info=True, flush=False):
     bld.is_install = 0 # False
 
     try:
-        proj = Environment.Environment(Options.lockfile)
+        proj = ConfigSet.ConfigSet(Options.lockfile)
     except IOError:
-        raise Utils.WafError("Project not configured (run 'waf configure' first)")
+        raise Errors.WafError("Project not configured (run 'waf configure' first)")
 
-    bld.load_dirs(proj[SRCDIR], proj[BLDDIR])
     bld.load_envs()
 
     if info:
         Logs.info("Waf: Entering directory `%s'" % bld.bldnode.abspath())
-    bld.add_subdirs([os.path.split(Utils.g_module.root_path)[0]])
+    bld.add_subdirs([os.path.split(Context.g_module.root_path)[0]])
 
     bld.pre_build()
     if flush:
index 2dd08e1..df127ee 100644 (file)
@@ -14,7 +14,7 @@ nodes/tasks, in which case the method will have to be modified
 to exclude some folders for example.
 """
 
-import Logs, Build, os, samba_utils, Options, Utils
+import Logs, Build, os, samba_utils, Options, Utils, Errors
 from Runner import Parallel
 
 old_refill_task_list = Parallel.refill_task_list
@@ -46,7 +46,7 @@ def replace_refill_task_list(self):
 
     # paranoia
     if bin_base[-4:] != '/bin':
-        raise Utils.WafError("Invalid bin base: %s" % bin_base)
+        raise Errors.WafError("Invalid bin base: %s" % bin_base)
 
     # obtain the expected list of files
     expected = []
index 7ff4bac..502407c 100644 (file)
@@ -2,8 +2,8 @@
 # using nm, producing a set of exposed defined/undefined symbols
 
 import os, re, subprocess
-import Utils, Build, Options, Logs
-from Logs import debug
+from waflib import Utils, Build, Options, Logs, Errors
+from waflib.Logs import debug
 from samba_utils import TO_LIST, LOCAL_CACHE, get_tgt_list, os_path_relpath
 
 # these are the data structures used in symbols.py:
@@ -410,7 +410,7 @@ def check_library_deps(bld, t):
             if dep2 == name and t.in_library != t2.in_library:
                 Logs.warn("WARNING: mutual dependency %s <=> %s" % (name, real_name(t2.sname)))
                 Logs.warn("Libraries should match. %s != %s" % (t.in_library, t2.in_library))
-                # raise Utils.WafError("illegal mutual dependency")
+                # raise Errors.WafError("illegal mutual dependency")
 
 
 def check_syslib_collisions(bld, tgt_list):
@@ -430,7 +430,7 @@ def check_syslib_collisions(bld, tgt_list):
                 Logs.error("ERROR: Target '%s' has symbols '%s' which is also in syslib '%s'" % (t.sname, common, lib))
                 has_error = True
     if has_error:
-        raise Utils.WafError("symbols in common with system libraries")
+        raise Errors.WafError("symbols in common with system libraries")
 
 
 def check_dependencies(bld, t):
@@ -546,7 +546,7 @@ def symbols_whyneeded(task):
 
     why = Options.options.WHYNEEDED.split(":")
     if len(why) != 2:
-        raise Utils.WafError("usage: WHYNEEDED=TARGET:DEPENDENCY")
+        raise Errors.WafError("usage: WHYNEEDED=TARGET:DEPENDENCY")
     target = why[0]
     subsystem = why[1]
 
@@ -579,7 +579,7 @@ def report_duplicate(bld, binname, sym, libs, fail_on_error):
         else:
             libnames.append(lib)
     if fail_on_error:
-        raise Utils.WafError("%s: Symbol %s linked in multiple libraries %s" % (binname, sym, libnames))
+        raise Errors.WafError("%s: Symbol %s linked in multiple libraries %s" % (binname, sym, libnames))
     else:
         print("%s: Symbol %s linked in multiple libraries %s" % (binname, sym, libnames))
 
index e1bbb1d..28b560f 100644 (file)
@@ -3,11 +3,11 @@
 # based on suncc.py from waf
 
 import os, optparse
-import Utils, Options, Configure
-import ccroot, ar
-from Configure import conftest
+from waflib import Utils, Options, Configure
+from waflib.Tools import ccroot, ar
+from waflib.Configure import conftest
 
-from compiler_cc import c_compiler
+from waflib.Tools.compiler_c import c_compiler
 
 c_compiler['osf1V'] = ['gcc', 'tru64cc']
 
index fe829b8..6075cc7 100644 (file)
@@ -1,9 +1,10 @@
 # a waf tool to add autoconf-like macros to the configure section
 # and for SAMBA_ macros for building libraries, binaries etc
 
-import Build, os, sys, Options, Task, Utils, cc, TaskGen, fnmatch, re, shutil, Logs, Constants
-from Configure import conf
-from Logs import debug
+import os, sys, re, shutil, fnmatch
+from waflib import Build, Options, Task, Utils, TaskGen, Logs, Context, Errors
+from waflib.Configure import conf
+from waflib.Logs import debug
 from samba_utils import SUBST_VARS_RECURSIVE
 TaskGen.task_gen.apply_verif = Utils.nada
 
@@ -43,7 +44,7 @@ LIB_PATH="shared"
 
 os.environ['PYTHONUNBUFFERED'] = '1'
 
-if Constants.HEXVERSION not in (0x105019, 0x1090a00):
+if Context.HEXVERSION not in (0x2000400,):
     Logs.error('''
 Please use the version of waf that comes with Samba, not
 a system installed version. See http://wiki.samba.org/index.php/Waf
@@ -53,26 +54,25 @@ Alternatively, please run ./configure and make as usual. That will
 call the right version of waf.''')
     sys.exit(1)
 
-
 @conf
 def SAMBA_BUILD_ENV(conf):
     '''create the samba build environment'''
-    conf.env.BUILD_DIRECTORY = conf.blddir
-    mkdir_p(os.path.join(conf.blddir, LIB_PATH))
-    mkdir_p(os.path.join(conf.blddir, LIB_PATH, "private"))
-    mkdir_p(os.path.join(conf.blddir, "modules"))
-    mkdir_p(os.path.join(conf.blddir, 'python/samba/dcerpc'))
+    conf.env.BUILD_DIRECTORY = getattr(Context.g_module, Context.OUT)
+    mkdir_p(os.path.join(conf.env.BUILD_DIRECTORY, LIB_PATH))
+    mkdir_p(os.path.join(conf.env.BUILD_DIRECTORY, LIB_PATH, "private"))
+    mkdir_p(os.path.join(conf.env.BUILD_DIRECTORY, "modules"))
+    mkdir_p(os.path.join(conf.env.BUILD_DIRECTORY, 'python/samba/dcerpc'))
     # this allows all of the bin/shared and bin/python targets
     # to be expressed in terms of build directory paths
-    mkdir_p(os.path.join(conf.blddir, 'default'))
+    mkdir_p(os.path.join(conf.env.BUILD_DIRECTORY, 'default'))
     for (source, target) in [('shared', 'shared'), ('modules', 'modules'), ('python', 'python_modules')]:
-        link_target = os.path.join(conf.blddir, 'default/' + target)
+        link_target = os.path.join(conf.env.BUILD_DIRECTORY, 'default/' + target)
         if not os.path.lexists(link_target):
             os.symlink('../' + source, link_target)
 
     # get perl to put the blib files in the build directory
-    blib_bld = os.path.join(conf.blddir, 'default/pidl/blib')
-    blib_src = os.path.join(conf.srcdir, 'pidl/blib')
+    blib_bld = os.path.join(conf.env.BUILD_DIRECTORY, 'default/pidl/blib')
+    blib_src = os.path.join(conf.srcnode.abspath(), 'pidl/blib')
     mkdir_p(blib_bld + '/man1')
     mkdir_p(blib_bld + '/man3')
     if os.path.islink(blib_src):
@@ -146,7 +146,7 @@ def SAMBA_LIBRARY(bld, libname, source,
         public_headers = None
 
     if private_library and public_headers:
-        raise Utils.WafError("private library '%s' must not have public header files" %
+        raise Errors.WafError("private library '%s' must not have public header files" %
                              libname)
 
     if LIB_MUST_BE_PRIVATE(bld, libname):
@@ -223,13 +223,13 @@ def SAMBA_LIBRARY(bld, libname, source,
     # we don't want any public libraries without version numbers
     if (not private_library and target_type != 'PYTHON' and not realname):
         if vnum is None and soname is None:
-            raise Utils.WafError("public library '%s' must have a vnum" %
+            raise Errors.WafError("public library '%s' must have a vnum" %
                     libname)
         if pc_files is None:
-            raise Utils.WafError("public library '%s' must have pkg-config file" %
+            raise Errors.WafError("public library '%s' must have pkg-config file" %
                        libname)
         if public_headers is None and not bld.env['IS_EXTRA_PYTHON']:
-            raise Utils.WafError("public library '%s' must have header files" %
+            raise Errors.WafError("public library '%s' must have header files" %
                        libname)
 
     if bundled_name is not None:
@@ -271,7 +271,7 @@ def SAMBA_LIBRARY(bld, libname, source,
     vscript = None
     if bld.env.HAVE_LD_VERSION_SCRIPT:
         if private_library:
-            version = "%s_%s" % (Utils.g_module.APPNAME, Utils.g_module.VERSION)
+            version = "%s_%s" % (Context.g_module.APPNAME, Context.g_module.VERSION)
         elif vnum:
             version = "%s_%s" % (libname, vnum)
         else:
@@ -284,9 +284,9 @@ def SAMBA_LIBRARY(bld, libname, source,
             fullpath = bld.path.find_or_declare(fullname)
             vscriptpath = bld.path.find_or_declare(vscript)
             if not fullpath:
-                raise Utils.WafError("unable to find fullpath for %s" % fullname)
+                raise Errors.WafError("unable to find fullpath for %s" % fullname)
             if not vscriptpath:
-                raise Utils.WafError("unable to find vscript path for %s" % vscript)
+                raise Errors.WafError("unable to find vscript path for %s" % vscript)
             bld.add_manual_dependency(fullpath, vscriptpath)
             if bld.is_install:
                 # also make the .inst file depend on the vscript
@@ -758,7 +758,7 @@ def SAMBA_SCRIPT(bld, name, pattern, installdir, installname=None):
         target = os.path.join(installdir, iname)
         tgtdir = os.path.dirname(os.path.join(bld.srcnode.abspath(bld.env), '..', target))
         mkdir_p(tgtdir)
-        link_src = os.path.normpath(os.path.join(bld.curdir, s))
+        link_src = os.path.normpath(os.path.join(bld.path.abspath(), s))
         link_dst = os.path.join(tgtdir, os.path.basename(iname))
         if os.path.islink(link_dst) and os.readlink(link_dst) == link_src:
             continue
@@ -900,7 +900,7 @@ def INSTALL_DIR(bld, path, chmod=0o755, env=None):
     if not path:
         return []
 
-    destpath = bld.get_install_path(path, env)
+    destpath = bld.EXPAND_VARIABLES(path)
 
     if bld.is_install > 0:
         if not os.path.isdir(destpath):
@@ -909,7 +909,7 @@ def INSTALL_DIR(bld, path, chmod=0o755, env=None):
                 os.chmod(destpath, chmod)
             except OSError as e:
                 if not os.path.isdir(destpath):
-                    raise Utils.WafError("Cannot create the folder '%s' (error: %s)" % (path, e))
+                    raise Errors.WafError("Cannot create the folder '%s' (error: %s)" % (path, e))
 Build.BuildContext.INSTALL_DIR = INSTALL_DIR
 
 def INSTALL_DIRS(bld, destdir, dirs, chmod=0o755, env=None):
index 7952e8a..0d9b302 100644 (file)
@@ -3,7 +3,8 @@
 # this is a base set of waf rules that everything else pulls in first
 
 import os, sys
-import wafsamba, Configure, Logs, Options, Utils
+from waflib import Configure, Logs, Options, Utils, Context, Errors
+import wafsamba
 from samba_utils import os_path_relpath
 from optparse import SUPPRESS_HELP
 
@@ -16,10 +17,15 @@ from optparse import SUPPRESS_HELP
 if '--enable-auto-reconfigure' in sys.argv:
     Configure.autoconfig = 'clobber'
 
-def set_options(opt):
-    opt.tool_options('compiler_cc')
+def default_value(option, default=''):
+    if option in Options.options.__dict__:
+        return Options.options.__dict__[option]
+    return default
 
-    opt.tool_options('gnu_dirs')
+def options(opt):
+    opt.load('compiler_cc')
+
+    opt.load('gnu_dirs')
 
     gr = opt.option_group('library handling options')
 
@@ -31,17 +37,17 @@ def set_options(opt):
                    help=("comma separated list of normally public libraries to build instead as private libraries. May include !LIBNAME to disable making a library private. Can be 'NONE' or 'ALL' [auto]"),
                    action="store", dest='PRIVATE_LIBS', default='')
 
-    extension_default = Options.options['PRIVATE_EXTENSION_DEFAULT']
+    extension_default = default_value('PRIVATE_EXTENSION_DEFAULT')
     gr.add_option('--private-library-extension',
                    help=("name extension for private libraries [%s]" % extension_default),
                    action="store", dest='PRIVATE_EXTENSION', default=extension_default)
 
-    extension_exception = Options.options['PRIVATE_EXTENSION_EXCEPTION']
+    extension_exception = default_value('PRIVATE_EXTENSION_EXCEPTION')
     gr.add_option('--private-extension-exception',
                    help=("comma separated list of libraries to not apply extension to [%s]" % extension_exception),
                    action="store", dest='PRIVATE_EXTENSION_EXCEPTION', default=extension_exception)
 
-    builtin_default = Options.options['BUILTIN_LIBRARIES_DEFAULT']
+    builtin_default = default_value('BUILTIN_LIBRARIES_DEFAULT')
     gr.add_option('--builtin-libraries',
                    help=("command separated list of libraries to build directly into binaries [%s]" % builtin_default),
                    action="store", dest='BUILTIN_LIBRARIES', default=builtin_default)
@@ -71,7 +77,7 @@ def set_options(opt):
                    action="store", dest='MODULESDIR', default='${PREFIX}/modules')
 
     opt.add_option('--with-privatelibdir',
-                   help=("private library directory [PREFIX/lib/%s]" % Utils.g_module.APPNAME),
+                   help=("private library directory [PREFIX/lib/%s]" % Context.g_module.APPNAME),
                    action="store", dest='PRIVATELIBDIR', default=None)
 
     opt.add_option('--with-libiconv',
@@ -210,7 +216,7 @@ def set_options(opt):
 @Utils.run_once
 def configure(conf):
     conf.env.hlist = []
-    conf.env.srcdir = conf.srcdir
+    conf.env.srcdir = conf.srcnode.abspath()
 
     conf.define('SRCDIR', conf.env['srcdir'])
 
@@ -220,13 +226,12 @@ def configure(conf):
     conf.SETUP_CONFIGURE_CACHE(Options.options.enable_configure_cache)
 
     # load our local waf extensions
-    conf.check_tool('gnu_dirs')
-    conf.check_tool('wafsamba')
-    conf.check_tool('print_commands')
+    conf.load('gnu_dirs')
+    conf.load('wafsamba')
 
     conf.CHECK_CC_ENV()
 
-    conf.check_tool('compiler_cc')
+    conf.load('compiler_cc')
 
     conf.CHECK_STANDARD_LIBPATH()
 
@@ -236,7 +241,7 @@ def configure(conf):
     # older gcc versions (< 4.4) does not work with gccdeps, so we have to see if the .d file is generated
     if Options.options.enable_gccdeps:
         # stale file removal - the configuration may pick up the old .pyc file
-        p = os.path.join(conf.srcdir, 'buildtools/wafsamba/gccdeps.pyc')
+        p = os.path.join(conf.env.srcdir, 'buildtools/wafsamba/gccdeps.pyc')
         if os.path.exists(p):
             os.remove(p)
         conf.load('gccdeps')
@@ -480,7 +485,7 @@ struct foo bar = { .y = 'X', .x = 1 };
 
     # see if we need special largefile flags
     if not conf.CHECK_LARGEFILE():
-        raise Utils.WafError('Samba requires large file support support, but not available on this platform: sizeof(off_t) < 8')
+        raise Errors.WafError('Samba requires large file support support, but not available on this platform: sizeof(off_t) < 8')
 
     if conf.env.HAVE_STDDEF_H and conf.env.HAVE_STDLIB_H:
         conf.DEFINE('STDC_HEADERS', 1)
@@ -586,10 +591,12 @@ struct foo bar = { .y = 'X', .x = 1 };
 
 def build(bld):
     # give a more useful message if the source directory has moved
-    relpath = os_path_relpath(bld.curdir, bld.srcnode.abspath())
+    curdir = bld.path.abspath()
+    srcdir = bld.srcnode.abspath()
+    relpath = os_path_relpath(curdir, srcdir)
     if relpath.find('../') != -1:
-        Logs.error('bld.curdir %s is not a child of %s' % (bld.curdir, bld.srcnode.abspath()))
-        raise Utils.WafError('''The top source directory has moved. Please run distclean and reconfigure''')
+        Logs.error('bld.path %s is not a child of %s' % (curdir, srcdir))
+        raise Errors.WafError('''The top source directory has moved. Please run distclean and reconfigure''')
 
     bld.CHECK_MAKEFLAGS()
     bld.SETUP_BUILD_GROUPS()
index 5858348..70ebd69 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Classes related to the build phase (build, clean, install, step, etc)
@@ -147,17 +147,11 @@ class BuildContext(Context.Context):
                        if not hasattr(self, v):
                                setattr(self, v, {})
 
-       def set_cur(self, cur):
-               self.current_group = cur
-       def get_cur(self):
-               return self.current_group
-       cur = property(get_cur, set_cur)
-
        def get_variant_dir(self):
                """Getter for the variant_dir attribute"""
                if not self.variant:
                        return self.out_dir
-               return os.path.join(self.out_dir, self.variant)
+               return os.path.join(self.out_dir, os.path.normpath(self.variant))
        variant_dir = property(get_variant_dir, None)
 
        def __call__(self, *k, **kw):
@@ -185,30 +179,6 @@ class BuildContext(Context.Context):
                self.add_to_group(ret, group=kw.get('group'))
                return ret
 
-       def rule(self, *k, **kw):
-               """
-               Wrapper for creating a task generator using the decorator notation. The following code::
-
-                       @bld.rule(target="foo")
-                       def _(tsk):
-                               print("bar")
-
-               is equivalent to::
-
-                       def bar(tsk):
-                               print("bar")
-
-                       bld(
-                               target = "foo",
-                               rule = bar,
-                       )
-               """
-               def f(rule):
-                       ret = self(*k, **kw)
-                       ret.rule = rule
-                       return ret
-               return f
-
        def __copy__(self):
                """
                Build contexts cannot be copied
@@ -323,7 +293,7 @@ class BuildContext(Context.Context):
                                Node.Nod3 = self.node_class
                                try:
                                        data = cPickle.loads(data)
-                               except Exception ,e:
+                               except Exception as e:
                                        Logs.debug('build: Could not pickle the build cache %s: %r', dbfn, e)
                                else:
                                        for x in SAVED_ATTRS:
@@ -378,15 +348,19 @@ class BuildContext(Context.Context):
                try:
                        self.producer.start()
                except KeyboardInterrupt:
-                       self.store()
+                       if self.is_dirty():
+                               self.store()
                        raise
                else:
-                       if self.producer.dirty:
+                       if self.is_dirty():
                                self.store()
 
                if self.producer.error:
                        raise Errors.BuildError(self.producer.error)
 
+       def is_dirty(self):
+               return self.producer.dirty
+
        def setup(self, tool, tooldir=None, funs=None):
                """
                Import waf tools defined during the configuration::
@@ -404,11 +378,13 @@ class BuildContext(Context.Context):
                :param funs: unused variable
                """
                if isinstance(tool, list):
-                       for i in tool: self.setup(i, tooldir)
+                       for i in tool:
+                               self.setup(i, tooldir)
                        return
 
                module = Context.load_tool(tool, tooldir)
-               if hasattr(module, "setup"): module.setup(self)
+               if hasattr(module, "setup"):
+                       module.setup(self)
 
        def get_env(self):
                """Getter for the env property"""
@@ -548,7 +524,8 @@ class BuildContext(Context.Context):
                right = '][%s%s%s]' % (col1, self.timer, col2)
 
                cols = Logs.get_term_cols() - len(left) - len(right) + 2*len(col1) + 2*len(col2)
-               if cols < 7: cols = 7
+               if cols < 7:
+                       cols = 7
 
                ratio = ((cols * idx)//total) - 1
 
@@ -621,7 +598,7 @@ class BuildContext(Context.Context):
 
        def add_to_group(self, tgen, group=None):
                """Adds a task or a task generator to the build; there is no attempt to remove it if it was already added."""
-               assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.TaskBase))
+               assert(isinstance(tgen, TaskGen.task_gen) or isinstance(tgen, Task.Task))
                tgen.bld = self
                self.get_group(group).append(tgen)
 
@@ -722,10 +699,16 @@ class BuildContext(Context.Context):
 
        def get_targets(self):
                """
-               Returns the task generator corresponding to the 'targets' list; used internally
-               by :py:meth:`waflib.Build.BuildContext.get_build_iterator` to perform partial builds::
+               This method returns a pair containing the index of the last build group to post,
+               and the list of task generator objects corresponding to the target names.
+
+               This is used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
+               to perform partial builds::
 
                        $ waf --targets=myprogram,myshlib
+
+               :return: the minimum build group index, and list of task generators
+               :rtype: tuple
                """
                to_post = []
                min_grp = 0
@@ -753,23 +736,21 @@ class BuildContext(Context.Context):
                Post task generators from the group indexed by self.current_group; used internally
                by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
                """
+               def tgpost(tg):
+                       try:
+                               f = tg.post
+                       except AttributeError:
+                               pass
+                       else:
+                               f()
+
                if self.targets == '*':
                        for tg in self.groups[self.current_group]:
-                               try:
-                                       f = tg.post
-                               except AttributeError:
-                                       pass
-                               else:
-                                       f()
+                               tgpost(tg)
                elif self.targets:
                        if self.current_group < self._min_grp:
                                for tg in self.groups[self.current_group]:
-                                       try:
-                                               f = tg.post
-                                       except AttributeError:
-                                               pass
-                                       else:
-                                               f()
+                                       tgpost(tg)
                        else:
                                for tg in self._exact_tg:
                                        tg.post()
@@ -783,19 +764,19 @@ class BuildContext(Context.Context):
                                ln = self.srcnode
                        for tg in self.groups[self.current_group]:
                                try:
-                                       f = tg.post
+                                       p = tg.path
                                except AttributeError:
                                        pass
                                else:
-                                       if tg.path.is_child_of(ln):
-                                               f()
+                                       if p.is_child_of(ln):
+                                               tgpost(tg)
 
        def get_tasks_group(self, idx):
                """
                Returns all task instances for the build group at position idx,
                used internally by :py:meth:`waflib.Build.BuildContext.get_build_iterator`
 
-               :rtype: list of :py:class:`waflib.Task.TaskBase`
+               :rtype: list of :py:class:`waflib.Task.Task`
                """
                tasks = []
                for tg in self.groups[idx]:
@@ -810,27 +791,23 @@ class BuildContext(Context.Context):
                Creates a Python generator object that returns lists of tasks that may be processed in parallel.
 
                :return: tasks which can be executed immediately
-               :rtype: generator returning lists of :py:class:`waflib.Task.TaskBase`
+               :rtype: generator returning lists of :py:class:`waflib.Task.Task`
                """
-               self.current_group = 0
-
                if self.targets and self.targets != '*':
                        (self._min_grp, self._exact_tg) = self.get_targets()
 
-               global lazy_post
                if self.post_mode != POST_LAZY:
-                       while self.current_group < len(self.groups):
+                       for self.current_group, _ in enumerate(self.groups):
                                self.post_group()
-                               self.current_group += 1
-                       self.current_group = 0
 
-               while self.current_group < len(self.groups):
+               for self.current_group, _ in enumerate(self.groups):
                        # first post the task generators for the group
                        if self.post_mode != POST_AT_ONCE:
                                self.post_group()
 
                        # then extract the tasks
                        tasks = self.get_tasks_group(self.current_group)
+
                        # if the constraints are set properly (ext_in/ext_out, before/after)
                        # the call to set_file_constraints may be removed (can be a 15% penalty on no-op rebuilds)
                        # (but leave set_file_constraints for the installation step)
@@ -841,12 +818,11 @@ class BuildContext(Context.Context):
                        Task.set_precedence_constraints(tasks)
 
                        self.cur_tasks = tasks
-                       self.current_group += 1
-                       if not tasks: # return something else the build will stop
-                               continue
-                       yield tasks
+                       if tasks:
+                               yield tasks
 
                while 1:
+                       # the build stops once there are no tasks to process
                        yield []
 
        def install_files(self, dest, files, **kw):
@@ -1188,7 +1164,7 @@ class inst(Task.Task):
 
                try:
                        self.copy_fun(src, tgt)
-               except EnvironmentError ,e:
+               except EnvironmentError as e:
                        if not os.path.exists(src):
                                Logs.error('File %r does not exist', src)
                        elif not os.path.isfile(src):
@@ -1249,7 +1225,7 @@ class inst(Task.Task):
                #self.uninstall.append(tgt)
                try:
                        os.remove(tgt)
-               except OSError ,e:
+               except OSError as e:
                        if e.errno != errno.ENOENT:
                                if not getattr(self, 'uninstall_error', None):
                                        self.uninstall_error = True
@@ -1286,22 +1262,6 @@ class UninstallContext(InstallContext):
                super(UninstallContext, self).__init__(**kw)
                self.is_install = UNINSTALL
 
-       def execute(self):
-               """
-               See :py:func:`waflib.Build.BuildContext.execute`.
-               """
-               # TODO just mark the tasks are already run with hasrun=Task.SKIPPED?
-               try:
-                       # do not execute any tasks
-                       def runnable_status(self):
-                               return Task.SKIP_ME
-                       setattr(Task.Task, 'runnable_status_back', Task.Task.runnable_status)
-                       setattr(Task.Task, 'runnable_status', runnable_status)
-
-                       super(UninstallContext, self).execute()
-               finally:
-                       setattr(Task.Task, 'runnable_status', Task.Task.runnable_status_back)
-
 class CleanContext(BuildContext):
        '''cleans the project'''
        cmd = 'clean'
@@ -1320,10 +1280,23 @@ class CleanContext(BuildContext):
                        self.store()
 
        def clean(self):
-               """Remove files from the build directory if possible, and reset the caches"""
+               """
+               Remove most files from the build directory, and reset all caches.
+
+               Custom lists of files to clean can be declared as `bld.clean_files`.
+               For example, exclude `build/program/myprogram` from getting removed::
+
+                       def build(bld):
+                               bld.clean_files = bld.bldnode.ant_glob('**',
+                                       excl='.lock* config.log c4che/* config.h program/myprogram',
+                                       quiet=True, generator=True)
+               """
                Logs.debug('build: clean called')
 
-               if self.bldnode != self.srcnode:
+               if hasattr(self, 'clean_files'):
+                       for n in self.clean_files:
+                               n.delete()
+               elif self.bldnode != self.srcnode:
                        # would lead to a disaster if top == out
                        lst = []
                        for env in self.all_envs.values():
@@ -1434,17 +1407,17 @@ class StepContext(BuildContext):
                        for pat in self.files.split(','):
                                matcher = self.get_matcher(pat)
                                for tg in g:
-                                       if isinstance(tg, Task.TaskBase):
+                                       if isinstance(tg, Task.Task):
                                                lst = [tg]
                                        else:
                                                lst = tg.tasks
                                        for tsk in lst:
                                                do_exec = False
-                                               for node in getattr(tsk, 'inputs', []):
+                                               for node in tsk.inputs:
                                                        if matcher(node, output=False):
                                                                do_exec = True
                                                                break
-                                               for node in getattr(tsk, 'outputs', []):
+                                               for node in tsk.outputs:
                                                        if matcher(node, output=True):
                                                                do_exec = True
                                                                break
@@ -1480,9 +1453,9 @@ class StepContext(BuildContext):
                        pattern = re.compile(pat)
 
                def match(node, output):
-                       if output == True and not out:
+                       if output and not out:
                                return False
-                       if output == False and not inn:
+                       if not output and not inn:
                                return False
 
                        if anode:
@@ -1502,3 +1475,4 @@ class EnvContext(BuildContext):
                if not self.all_envs:
                        self.load_envs()
                self.recurse([self.run_dir])
+
index 899c804..d07f4c3 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 
@@ -48,9 +48,12 @@ class ConfigSet(object):
                        if 'foo' in env:
                                print(env['foo'])
                """
-               if key in self.table: return True
-               try: return self.parent.__contains__(key)
-               except AttributeError: return False # parent may not exist
+               if key in self.table:
+                       return True
+               try:
+                       return self.parent.__contains__(key)
+               except AttributeError:
+                       return False # parent may not exist
 
        def keys(self):
                """Dict interface"""
@@ -89,13 +92,13 @@ class ConfigSet(object):
 
        def __setitem__(self, key, value):
                """
-               Dictionary interface: set value for key
+               Dictionary interface: set value from key
                """
                self.table[key] = value
 
        def __delitem__(self, key):
                """
-               Dictionary interface: mark the key as missing
+               Dictionary interface: mark the value as missing
                """
                self[key] = []
 
@@ -108,7 +111,7 @@ class ConfigSet(object):
                                conf.env['value']
                """
                if name in self.__slots__:
-                       return object.__getattr__(self, name)
+                       return object.__getattribute__(self, name)
                else:
                        return self[name]
 
@@ -184,7 +187,8 @@ class ConfigSet(object):
                :type key: string
                """
                s = self[key]
-               if isinstance(s, str): return s
+               if isinstance(s, str):
+                       return s
                return ' '.join(s)
 
        def _get_list_value_for_modification(self, key):
@@ -268,8 +272,10 @@ class ConfigSet(object):
                env = self
                while 1:
                        table_list.insert(0, env.table)
-                       try: env = env.parent
-                       except AttributeError: break
+                       try:
+                               env = env.parent
+                       except AttributeError:
+                               break
                merged_table = {}
                for table in table_list:
                        merged_table.update(table)
@@ -356,3 +362,4 @@ class ConfigSet(object):
                Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
                """
                self.table = self.undo_stack.pop(-1)
+
index aa42f2f..839413e 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Configuration system
@@ -16,7 +16,7 @@ A :py:class:`waflib.Configure.ConfigurationContext` instance is created when ``w
 * hold configuration routines such as ``find_program``, etc
 """
 
-import os, shlex, sys, time, re, shutil
+import os, re, shlex, shutil, sys, time, traceback
 from waflib import ConfigSet, Utils, Options, Logs, Context, Build, Errors
 
 WAF_CONFIG_LOG = 'config.log'
@@ -201,17 +201,17 @@ class ConfigurationContext(Context.Context):
                """
                if not env.PREFIX:
                        if Options.options.prefix or Utils.is_win32:
-                               env.PREFIX = Utils.sane_path(Options.options.prefix)
+                               env.PREFIX = Options.options.prefix
                        else:
-                               env.PREFIX = ''
+                               env.PREFIX = '/'
                if not env.BINDIR:
                        if Options.options.bindir:
-                               env.BINDIR = Utils.sane_path(Options.options.bindir)
+                               env.BINDIR = Options.options.bindir
                        else:
                                env.BINDIR = Utils.subst_vars('${PREFIX}/bin', env)
                if not env.LIBDIR:
                        if Options.options.libdir:
-                               env.LIBDIR = Utils.sane_path(Options.options.libdir)
+                               env.LIBDIR = Options.options.libdir
                        else:
                                env.LIBDIR = Utils.subst_vars('${PREFIX}/lib%s' % Utils.lib64(), env)
 
@@ -227,12 +227,12 @@ class ConfigurationContext(Context.Context):
                        tmpenv = self.all_envs[key]
                        tmpenv.store(os.path.join(self.cachedir.abspath(), key + Build.CACHE_SUFFIX))
 
-       def load(self, input, tooldir=None, funs=None, with_sys_path=True, cache=False):
+       def load(self, tool_list, tooldir=None, funs=None, with_sys_path=True, cache=False):
                """
                Load Waf tools, which will be imported whenever a build is started.
 
-               :param input: waf tools to import
-               :type input: list of string
+               :param tool_list: waf tools to import
+               :type tool_list: list of string
                :param tooldir: paths for the imports
                :type tooldir: list of string
                :param funs: functions to execute from the waf tools
@@ -241,8 +241,9 @@ class ConfigurationContext(Context.Context):
                :type cache: bool
                """
 
-               tools = Utils.to_list(input)
-               if tooldir: tooldir = Utils.to_list(tooldir)
+               tools = Utils.to_list(tool_list)
+               if tooldir:
+                       tooldir = Utils.to_list(tooldir)
                for tool in tools:
                        # avoid loading the same tool more than once with the same functions
                        # used by composite projects
@@ -257,11 +258,11 @@ class ConfigurationContext(Context.Context):
                        module = None
                        try:
                                module = Context.load_tool(tool, tooldir, ctx=self, with_sys_path=with_sys_path)
-                       except ImportError ,e:
-                               self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, sys.path, e))
-                       except Exception ,e:
+                       except ImportError as e:
+                               self.fatal('Could not load the Waf tool %r from %r\n%s' % (tool, getattr(e, 'waf_sys_path', sys.path), e))
+                       except Exception as e:
                                self.to_log('imp %r (%r & %r)' % (tool, tooldir, funs))
-                               self.to_log(Utils.ex_stack())
+                               self.to_log(traceback.format_exc())
                                raise
 
                        if funs is not None:
@@ -269,8 +270,10 @@ class ConfigurationContext(Context.Context):
                        else:
                                func = getattr(module, 'configure', None)
                                if func:
-                                       if type(func) is type(Utils.readf): func(self)
-                                       else: self.eval_rules(func)
+                                       if type(func) is type(Utils.readf):
+                                               func(self)
+                                       else:
+                                               self.eval_rules(func)
 
                        self.tools.append({'tool':tool, 'tooldir':tooldir, 'funs':funs})
 
@@ -373,11 +376,11 @@ def cmd_to_list(self, cmd):
        return cmd
 
 @conf
-def check_waf_version(self, mini='1.8.99', maxi='2.0.0', **kw):
+def check_waf_version(self, mini='1.9.99', maxi='2.1.0', **kw):
        """
        Raise a Configuration error if the Waf version does not strictly match the given bounds::
 
-               conf.check_waf_version(mini='1.8.99', maxi='2.0.0')
+               conf.check_waf_version(mini='1.9.99', maxi='2.1.0')
 
        :type  mini: number, tuple or string
        :param mini: Minimum required version
@@ -399,7 +402,7 @@ def find_file(self, filename, path_list=[]):
 
        :param filename: name of the file to search for
        :param path_list: list of directories to search
-       :return: the first occurrence filename or '' if filename could not be found
+       :return: the first matching filename; else a configuration exception is raised
        """
        for n in Utils.to_list(filename):
                for d in Utils.to_list(path_list):
@@ -429,6 +432,7 @@ def find_program(self, filename, **kw):
        :type msg: string
        :param interpreter: interpreter for the program
        :type interpreter: ConfigSet variable key
+       :raises: :py:class:`waflib.Errors.ConfigurationError`
        """
 
        exts = kw.get('exts', Utils.is_win32 and '.exe,.com,.bat,.cmd' or ',.sh,.pl,.py')
@@ -587,7 +591,7 @@ def run_build(self, *k, **kw):
                try:
                        bld.compile()
                except Errors.WafError:
-                       ret = 'Test does not build: %s' % Utils.ex_stack()
+                       ret = 'Test does not build: %s' % traceback.format_exc()
                        self.fatal(ret)
                else:
                        ret = getattr(bld, 'retval', 0)
@@ -639,3 +643,4 @@ def test(self, *k, **kw):
        else:
                self.end_msg(self.ret_msg(kw['okmsg'], kw), **kw)
        return ret
+
index 4a3b892..7eab573 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2010-2016 (ita)
+# Thomas Nagy, 2010-2018 (ita)
 
 """
 Classes and functions enabling the command system
@@ -15,16 +15,16 @@ from waflib import Utils, Errors, Logs
 import waflib.Node
 
 # the following 3 constants are updated on each new release (do not touch)
-HEXVERSION=0x1090a00
+HEXVERSION=0x2000400
 """Constant updated on new releases"""
 
-WAFVERSION="1.9.10"
+WAFVERSION="2.0.4"
 """Constant updated on new releases"""
 
-WAFREVISION="ae3f254315e0dcea4059703987148882ba414894"
+WAFREVISION="5996879673deb7166b61a299be317a738de6891e"
 """Git revision when the waf version is updated"""
 
-ABI = 99
+ABI = 20
 """Version of the build data cache file format (used in :py:const:`waflib.Context.DBFILE`)"""
 
 DBFILE = '.wafpickle-%s-%d-%d' % (sys.platform, sys.hexversion, ABI)
@@ -85,7 +85,6 @@ def create_context(cmd_name, *k, **kw):
        :return: Context object
        :rtype: :py:class:`waflib.Context.Context`
        """
-       global classes
        for x in classes:
                if x.cmd == cmd_name:
                        return x(*k, **kw)
@@ -99,8 +98,8 @@ class store_context(type):
        Context classes must provide an attribute 'cmd' representing the command name, and a function
        attribute 'fun' representing the function name that the command uses.
        """
-       def __init__(cls, name, bases, dict):
-               super(store_context, cls).__init__(name, bases, dict)
+       def __init__(cls, name, bases, dct):
+               super(store_context, cls).__init__(name, bases, dct)
                name = cls.__name__
 
                if name in ('ctx', 'Context'):
@@ -114,7 +113,6 @@ class store_context(type):
                if not getattr(cls, 'fun', None):
                        cls.fun = cls.cmd
 
-               global classes
                classes.insert(0, cls)
 
 ctx = store_context('ctx', (object,), {})
@@ -154,7 +152,6 @@ class Context(ctx):
                try:
                        rd = kw['run_dir']
                except KeyError:
-                       global run_dir
                        rd = run_dir
 
                # binds the context to the nodes in use to avoid a context singleton
@@ -205,7 +202,6 @@ class Context(ctx):
                Here, it calls the function name in the top-level wscript file. Most subclasses
                redefine this method to provide additional functionality.
                """
-               global g_module
                self.recurse([os.path.dirname(g_module.root_path)])
 
        def pre_recurse(self, node):
@@ -300,6 +296,15 @@ class Context(ctx):
                                                raise Errors.WafError('Cannot read the folder %r' % d)
                                        raise Errors.WafError('No wscript file in directory %s' % d)
 
+       def log_command(self, cmd, kw):
+               if Logs.verbose:
+                       fmt = os.environ.get('WAF_CMD_FORMAT')
+                       if fmt == 'string':
+                               if not isinstance(cmd, str):
+                                       cmd = Utils.shell_escape(cmd)
+                       Logs.debug('runner: %r', cmd)
+                       Logs.debug('runner_env: kw=%s', kw)
+
        def exec_command(self, cmd, **kw):
                """
                Runs an external process and returns the exit status::
@@ -318,11 +323,12 @@ class Context(ctx):
                :type kw: dict
                :returns: process exit status
                :rtype: integer
+               :raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
+               :raises: :py:class:`waflib.Errors.WafError` in case of execution failure
                """
                subprocess = Utils.subprocess
                kw['shell'] = isinstance(cmd, str)
-               Logs.debug('runner: %r', cmd)
-               Logs.debug('runner_env: kw=%s', kw)
+               self.log_command(cmd, kw)
 
                if self.logger:
                        self.logger.info(cmd)
@@ -354,19 +360,19 @@ class Context(ctx):
 
                try:
                        ret, out, err = Utils.run_process(cmd, kw, cargs)
-               except Exception ,e:
+               except Exception as e:
                        raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
 
                if out:
                        if not isinstance(out, str):
-                               out = out.decode(sys.stdout.encoding or 'iso8859-1', errors='replace')
+                               out = out.decode(sys.stdout.encoding or 'latin-1', errors='replace')
                        if self.logger:
                                self.logger.debug('out: %s', out)
                        else:
                                Logs.info(out, extra={'stream':sys.stdout, 'c1': ''})
                if err:
                        if not isinstance(err, str):
-                               err = err.decode(sys.stdout.encoding or 'iso8859-1', errors='replace')
+                               err = err.decode(sys.stdout.encoding or 'latin-1', errors='replace')
                        if self.logger:
                                self.logger.error('err: %s' % err)
                        else:
@@ -378,29 +384,29 @@ class Context(ctx):
                """
                Executes a process and returns stdout/stderr if the execution is successful.
                An exception is thrown when the exit status is non-0. In that case, both stderr and stdout
-               will be bound to the WafError object::
+               will be bound to the WafError object (configuration tests)::
 
                        def configure(conf):
                                out = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.STDOUT, quiet=waflib.Context.BOTH)
                                (out, err) = conf.cmd_and_log(['echo', 'hello'], output=waflib.Context.BOTH)
-                               (out, err) = conf.cmd_and_log(cmd, input='\\n', output=waflib.Context.STDOUT)
+                               (out, err) = conf.cmd_and_log(cmd, input='\\n'.encode(), output=waflib.Context.STDOUT)
                                try:
                                        conf.cmd_and_log(['which', 'someapp'], output=waflib.Context.BOTH)
-                               except Exception ,e:
+                               except Errors.WafError as e:
                                        print(e.stdout, e.stderr)
 
                :param cmd: args for subprocess.Popen
                :type cmd: list or string
                :param kw: keyword arguments for subprocess.Popen. The parameters input/timeout will be passed to wait/communicate.
                :type kw: dict
-               :returns: process exit status
-               :rtype: integer
+               :returns: a tuple containing the contents of stdout and stderr
+               :rtype: string
                :raises: :py:class:`waflib.Errors.WafError` if an invalid executable is specified for a non-shell process
                :raises: :py:class:`waflib.Errors.WafError` in case of execution failure; stdout/stderr/returncode are bound to the exception object
                """
                subprocess = Utils.subprocess
                kw['shell'] = isinstance(cmd, str)
-               Logs.debug('runner: %r', cmd)
+               self.log_command(cmd, kw)
 
                if 'quiet' in kw:
                        quiet = kw['quiet']
@@ -440,13 +446,13 @@ class Context(ctx):
 
                try:
                        ret, out, err = Utils.run_process(cmd, kw, cargs)
-               except Exception ,e:
+               except Exception as e:
                        raise Errors.WafError('Execution failure: %s' % str(e), ex=e)
 
                if not isinstance(out, str):
-                       out = out.decode(sys.stdout.encoding or 'iso8859-1', errors='replace')
+                       out = out.decode(sys.stdout.encoding or 'latin-1', errors='replace')
                if not isinstance(err, str):
-                       err = err.decode(sys.stdout.encoding or 'iso8859-1', errors='replace')
+                       err = err.decode(sys.stdout.encoding or 'latin-1', errors='replace')
 
                if out and quiet != STDOUT and quiet != BOTH:
                        self.to_log('out: %s' % out)
@@ -483,9 +489,15 @@ class Context(ctx):
                if self.logger:
                        self.logger.info('from %s: %s' % (self.path.abspath(), msg))
                try:
-                       msg = '%s\n(complete log in %s)' % (msg, self.logger.handlers[0].baseFilename)
+                       logfile = self.logger.handlers[0].baseFilename
                except AttributeError:
                        pass
+               else:
+                       if os.environ.get('WAF_PRINT_FAILURE_LOG'):
+                               # see #1930
+                               msg = 'Log from (%s):\n%s\n' % (logfile, Utils.readf(logfile))
+                       else:
+                               msg = '%s\n(complete log in %s)' % (msg, logfile)
                raise self.errors.ConfigurationError(msg, ex=ex)
 
        def to_log(self, msg):
@@ -581,9 +593,9 @@ class Context(ctx):
                result = kw.get('result') or k[0]
 
                defcolor = 'GREEN'
-               if result == True:
+               if result is True:
                        msg = 'ok'
-               elif result == False:
+               elif not result:
                        msg = 'not found'
                        defcolor = 'YELLOW'
                else:
@@ -612,7 +624,6 @@ class Context(ctx):
                :param ban: list of exact file names to exclude
                :type ban: list of string
                """
-               global waf_dir
                if os.path.isdir(waf_dir):
                        lst = self.root.find_node(waf_dir).find_node('waflib/extras').ant_glob(var)
                        for x in lst:
@@ -696,6 +707,9 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
                        sys.path = tooldir + sys.path
                        try:
                                __import__(tool)
+                       except ImportError as e:
+                               e.waf_sys_path = list(sys.path)
+                               raise
                        finally:
                                for d in tooldir:
                                        sys.path.remove(d)
@@ -703,7 +717,8 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
                        Context.tools[tool] = ret
                        return ret
                else:
-                       if not with_sys_path: sys.path.insert(0, waf_dir)
+                       if not with_sys_path:
+                               sys.path.insert(0, waf_dir)
                        try:
                                for x in ('waflib.Tools.%s', 'waflib.extras.%s', 'waflib.%s', '%s'):
                                        try:
@@ -713,11 +728,16 @@ def load_tool(tool, tooldir=None, ctx=None, with_sys_path=True):
                                                x = None
                                else: # raise an exception
                                        __import__(tool)
+                       except ImportError as e:
+                               e.waf_sys_path = list(sys.path)
+                               raise
                        finally:
-                               if not with_sys_path: sys.path.remove(waf_dir)
+                               if not with_sys_path:
+                                       sys.path.remove(waf_dir)
                        ret = sys.modules[x % tool]
                        Context.tools[tool] = ret
                        return ret
        finally:
                if not with_sys_path:
                        sys.path += back_path
+
index c9f4262..c38a557 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2010-2016 (ita)
+# Thomas Nagy, 2010-2018 (ita)
 
 """
 Exceptions used in the Waf code
@@ -21,6 +21,7 @@ class WafError(Exception):
                :param ex: exception causing this error (optional)
                :type ex: exception
                """
+               Exception.__init__(self)
                self.msg = msg
                assert not isinstance(msg, Exception)
 
@@ -53,7 +54,8 @@ class BuildError(WafError):
                lst = ['Build failed']
                for tsk in self.tasks:
                        txt = tsk.format_error()
-                       if txt: lst.append(txt)
+                       if txt:
+                               lst.append(txt)
                return '\n'.join(lst)
 
 class ConfigurationError(WafError):
@@ -67,3 +69,4 @@ class TaskRescan(WafError):
 class TaskNotReady(WafError):
        """Task-specific exception type signalling that task signatures cannot be computed"""
        pass
+
index 1216bad..4d4db5e 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 logging, colors, terminal width and pretty-print
@@ -143,7 +143,6 @@ class log_filter(logging.Filter):
 
                :param rec: log entry
                """
-               global verbose
                rec.zone = rec.module
                if rec.levelno >= logging.INFO:
                        return True
@@ -257,18 +256,15 @@ def debug(*k, **kw):
        """
        Wraps logging.debug and discards messages if the verbosity level :py:attr:`waflib.Logs.verbose` ≤ 0
        """
-       global verbose
        if verbose:
                k = list(k)
                k[0] = k[0].replace('\n', ' ')
-               global log
                log.debug(*k, **kw)
 
 def error(*k, **kw):
        """
        Wrap logging.errors, adds the stack trace when the verbosity level :py:attr:`waflib.Logs.verbose` ≥ 2
        """
-       global log, verbose
        log.error(*k, **kw)
        if verbose > 2:
                st = traceback.extract_stack()
@@ -279,20 +275,19 @@ def error(*k, **kw):
                                buf.append('  File %r, line %d, in %s' % (filename, lineno, name))
                                if line:
                                        buf.append('    %s' % line.strip())
-                       if buf: log.error('\n'.join(buf))
+                       if buf:
+                               log.error('\n'.join(buf))
 
 def warn(*k, **kw):
        """
        Wraps logging.warn
        """
-       global log
        log.warn(*k, **kw)
 
 def info(*k, **kw):
        """
        Wraps logging.info
        """
-       global log
        log.info(*k, **kw)
 
 def init_log():
@@ -331,7 +326,11 @@ def make_logger(path, name):
        :type name: string
        """
        logger = logging.getLogger(name)
-       hdlr = logging.FileHandler(path, 'w')
+       if sys.hexversion > 0x3000000:
+               encoding = sys.stdout.encoding
+       else:
+               encoding = None
+       hdlr = logging.FileHandler(path, 'w', encoding=encoding)
        formatter = logging.Formatter('%(message)s')
        hdlr.setFormatter(formatter)
        logger.addHandler(hdlr)
@@ -380,5 +379,5 @@ def pprint(col, msg, label='', sep='\n'):
        :param sep: a string to append at the end (line separator)
        :type sep: string
        """
-       global info
        info('%s%s%s %s', colors(col), msg, colors.NORMAL, label, extra={'terminator':sep})
+
index 3b0f578..a61902d 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Node: filesystem structure
@@ -34,6 +34,7 @@ exclude_regs = '''
 **/.#*
 **/%*%
 **/._*
+**/*.swp
 **/CVS
 **/CVS/**
 **/.cvsignore
@@ -64,6 +65,52 @@ Ant patterns for files and folders to exclude while doing the
 recursive traversal in :py:meth:`waflib.Node.Node.ant_glob`
 """
 
+def ant_matcher(s, ignorecase):
+       reflags = re.I if ignorecase else 0
+       ret = []
+       for x in Utils.to_list(s):
+               x = x.replace('\\', '/').replace('//', '/')
+               if x.endswith('/'):
+                       x += '**'
+               accu = []
+               for k in x.split('/'):
+                       if k == '**':
+                               accu.append(k)
+                       else:
+                               k = k.replace('.', '[.]').replace('*','.*').replace('?', '.').replace('+', '\\+')
+                               k = '^%s$' % k
+                               try:
+                                       exp = re.compile(k, flags=reflags)
+                               except Exception as e:
+                                       raise Errors.WafError('Invalid pattern: %s' % k, e)
+                               else:
+                                       accu.append(exp)
+               ret.append(accu)
+       return ret
+
+def ant_sub_filter(name, nn):
+       ret = []
+       for lst in nn:
+               if not lst:
+                       pass
+               elif lst[0] == '**':
+                       ret.append(lst)
+                       if len(lst) > 1:
+                               if lst[1].match(name):
+                                       ret.append(lst[2:])
+                       else:
+                               ret.append([])
+               elif lst[0].match(name):
+                       ret.append(lst[1:])
+       return ret
+
+def ant_sub_matcher(name, pats):
+       nacc = ant_sub_filter(name, pats[0])
+       nrej = ant_sub_filter(name, pats[1])
+       if [] in nrej:
+               nacc = []
+       return [nacc, nrej]
+
 class Node(object):
        """
        This class is organized in two parts:
@@ -125,7 +172,7 @@ class Node(object):
                """
                raise Errors.WafError('nodes are not supposed to be copied')
 
-       def read(self, flags='r', encoding='ISO8859-1'):
+       def read(self, flags='r', encoding='latin-1'):
                """
                Reads and returns the contents of the file represented by this node, see :py:func:`waflib.Utils.readf`::
 
@@ -141,7 +188,7 @@ class Node(object):
                """
                return Utils.readf(self.abspath(), flags, encoding)
 
-       def write(self, data, flags='w', encoding='ISO8859-1'):
+       def write(self, data, flags='w', encoding='latin-1'):
                """
                Writes data to the file represented by this node, see :py:func:`waflib.Utils.writef`::
 
@@ -344,6 +391,11 @@ class Node(object):
                if isinstance(lst, str):
                        lst = [x for x in Utils.split_path(lst) if x and x != '.']
 
+               if lst and lst[0].startswith('\\\\') and not self.parent:
+                       node = self.ctx.root.make_node(lst[0])
+                       node.cache_isdir = True
+                       return node.find_node(lst[1:])
+
                cur = self
                for x in lst:
                        if x == '..':
@@ -525,7 +577,7 @@ class Node(object):
                        p = p.parent
                return p is node
 
-       def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True):
+       def ant_iter(self, accept=None, maxdepth=25, pats=[], dir=False, src=True, remove=True, quiet=False):
                """
                Recursive method used by :py:meth:`waflib.Node.ant_glob`.
 
@@ -541,6 +593,8 @@ class Node(object):
                :type src: bool
                :param remove: remove files/folders that do not exist (True by default)
                :type remove: bool
+               :param quiet: disable build directory traversal warnings (verbose mode)
+               :type quiet: bool
                :returns: A generator object to iterate from
                :rtype: iterator
                """
@@ -568,14 +622,13 @@ class Node(object):
                                        if isdir:
                                                if dir:
                                                        yield node
-                                       else:
-                                               if src:
-                                                       yield node
+                                       elif src:
+                                               yield node
 
                                if isdir:
                                        node.cache_isdir = True
                                        if maxdepth:
-                                               for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove):
+                                               for k in node.ant_iter(accept=accept, maxdepth=maxdepth - 1, pats=npats, dir=dir, src=src, remove=remove, quiet=quiet):
                                                        yield k
                raise StopIteration
 
@@ -607,79 +660,41 @@ class Node(object):
                :type dir: bool
                :param src: return files (True by default)
                :type src: bool
-               :param remove: remove files/folders that do not exist (True by default)
-               :type remove: bool
                :param maxdepth: maximum depth of recursion
                :type maxdepth: int
                :param ignorecase: ignore case while matching (False by default)
                :type ignorecase: bool
                :returns: The corresponding Nodes
-               :rtype: list of :py:class:`waflib.Node.Node` instances
+               :type generator: bool
+               :param remove: remove files/folders that do not exist (True by default)
+               :type remove: bool
+               :param quiet: disable build directory traversal warnings (verbose mode)
+               :type quiet: bool
+               :returns: Whether to evaluate the Nodes lazily, alters the type of the returned value
+               :rtype: by default, list of :py:class:`waflib.Node.Node` instances
                """
-
                src = kw.get('src', True)
-               dir = kw.get('dir', False)
-
+               dir = kw.get('dir')
                excl = kw.get('excl', exclude_regs)
                incl = k and k[0] or kw.get('incl', '**')
-               reflags = kw.get('ignorecase', 0) and re.I
-
-               def to_pat(s):
-                       lst = Utils.to_list(s)
-                       ret = []
-                       for x in lst:
-                               x = x.replace('\\', '/').replace('//', '/')
-                               if x.endswith('/'):
-                                       x += '**'
-                               lst2 = x.split('/')
-                               accu = []
-                               for k in lst2:
-                                       if k == '**':
-                                               accu.append(k)
-                                       else:
-                                               k = k.replace('.', '[.]').replace('*','.*').replace('?', '.').replace('+', '\\+')
-                                               k = '^%s$' % k
-                                               try:
-                                                       #print "pattern", k
-                                                       accu.append(re.compile(k, flags=reflags))
-                                               except Exception ,e:
-                                                       raise Errors.WafError('Invalid pattern: %s' % k, e)
-                               ret.append(accu)
-                       return ret
-
-               def filtre(name, nn):
-                       ret = []
-                       for lst in nn:
-                               if not lst:
-                                       pass
-                               elif lst[0] == '**':
-                                       ret.append(lst)
-                                       if len(lst) > 1:
-                                               if lst[1].match(name):
-                                                       ret.append(lst[2:])
-                                       else:
-                                               ret.append([])
-                               elif lst[0].match(name):
-                                       ret.append(lst[1:])
-                       return ret
-
-               def accept(name, pats):
-                       nacc = filtre(name, pats[0])
-                       nrej = filtre(name, pats[1])
-                       if [] in nrej:
-                               nacc = []
-                       return [nacc, nrej]
-
-               ret = [x for x in self.ant_iter(accept=accept, pats=[to_pat(incl), to_pat(excl)], maxdepth=kw.get('maxdepth', 25), dir=dir, src=src, remove=kw.get('remove', True))]
-               if kw.get('flat', False):
-                       return ' '.join([x.path_from(self) for x in ret])
+               remove = kw.get('remove', True)
+               maxdepth = kw.get('maxdepth', 25)
+               ignorecase = kw.get('ignorecase', False)
+               quiet = kw.get('quiet', False)
+               pats = (ant_matcher(incl, ignorecase), ant_matcher(excl, ignorecase))
 
-               return ret
+               if kw.get('generator'):
+                       return Utils.lazy_generator(self.ant_iter, (ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet))
 
-       # --------------------------------------------------------------------------------
-       # the following methods require the source/build folders (bld.srcnode/bld.bldnode)
-       # using a subclass is a possibility, but is that really necessary?
-       # --------------------------------------------------------------------------------
+               it = self.ant_iter(ant_sub_matcher, maxdepth, pats, dir, src, remove, quiet)
+               if kw.get('flat'):
+                       # returns relative paths as a space-delimited string
+                       # prefer Node objects whenever possible
+                       return ' '.join(x.path_from(self) for x in it)
+               return list(it)
+
+       # ----------------------------------------------------------------------------
+       # the methods below require the source/build folders (bld.srcnode/bld.bldnode)
 
        def is_src(self):
                """
@@ -784,29 +799,19 @@ class Node(object):
 
        def find_or_declare(self, lst):
                """
-               Use this method in the build phase to declare output files.
+               Use this method in the build phase to declare output files which
+               are meant to be written in the build directory.
 
-               If 'self' is in build directory, it first tries to return an existing node object.
-               If no Node is found, it tries to find one in the source directory.
-               If no Node is found, a new Node object is created in the build directory, and the
-               intermediate folders are added.
+               This method creates the Node object and its parent folder
+               as needed.
 
                :param lst: relative path
                :type lst: string or list of string
                """
-               if isinstance(lst, str):
-                       lst = [x for x in Utils.split_path(lst) if x and x != '.']
-
-               node = self.get_bld().search_node(lst)
-               if node:
-                       if not os.path.isfile(node.abspath()):
-                               node.parent.mkdir()
-                       return node
-               self = self.get_src()
-               node = self.find_node(lst)
-               if node:
-                       return node
-               node = self.get_bld().make_node(lst)
+               if isinstance(lst, str) and os.path.isabs(lst):
+                       node = self.ctx.root.make_node(lst)
+               else:
+                       node = self.get_bld().make_node(lst)
                node.parent.mkdir()
                return node
 
@@ -923,22 +928,11 @@ class Node(object):
                                raise
                return ret
 
-       # --------------------------------------------
-       # TODO waf 2.0, remove the sig and cache_sig attributes
-       def get_sig(self):
-               return self.h_file()
-       def set_sig(self, val):
-               # clear the cache, so that past implementation should still work
-               try:
-                       del self.get_bld_sig.__cache__[(self,)]
-               except (AttributeError, KeyError):
-                       pass
-       sig = property(get_sig, set_sig)
-       cache_sig = property(get_sig, set_sig)
-
 pickle_lock = Utils.threading.Lock()
 """Lock mandatory for thread-safe node serialization"""
 
 class Nod3(Node):
        """Mandatory subclass for thread-safe node serialization"""
        pass # do not remove
+
+
index 4f4f7c6..39873c6 100644 (file)
@@ -5,7 +5,7 @@
 #!/usr/bin/env python
 # encoding: utf-8
 # Scott Newton, 2005 (scottn)
-# Thomas Nagy, 2006-2016 (ita)
+# Thomas Nagy, 2006-2018 (ita)
 
 """
 Support for waf command-line options
@@ -17,7 +17,7 @@ that reads the ``options`` wscript function.
 import os, tempfile, optparse, sys, re
 from waflib import Logs, Utils, Context, Errors
 
-options = {}
+options = optparse.Values()
 """
 A global dictionary representing user-provided command-line options::
 
@@ -46,11 +46,25 @@ class opt_parser(optparse.OptionParser):
        """
        Command-line options parser.
        """
-       def __init__(self, ctx):
-               optparse.OptionParser.__init__(self, conflict_handler="resolve",
+       def __init__(self, ctx, allow_unknown=False):
+               optparse.OptionParser.__init__(self, conflict_handler='resolve', add_help_option=False,
                        version='waf %s (%s)' % (Context.WAFVERSION, Context.WAFREVISION))
                self.formatter.width = Logs.get_term_cols()
                self.ctx = ctx
+               self.allow_unknown = allow_unknown
+
+       def _process_args(self, largs, rargs, values):
+               """
+               Custom _process_args to allow unknown options according to the allow_unknown status
+               """
+               while rargs:
+                       try:
+                               optparse.OptionParser._process_args(self,largs,rargs,values)
+                       except (optparse.BadOptionError, optparse.AmbiguousOptionError) as e:
+                               if self.allow_unknown:
+                                       largs.append(e.opt_str)
+                               else:
+                                       self.error(str(e))
 
        def print_usage(self, file=None):
                return self.print_help(file)
@@ -121,7 +135,9 @@ class OptionsContext(Context.Context):
                p('-k', '--keep',     dest='keep',    default=0,     action='count', help='continue despite errors (-kk to try harder)')
                p('-v', '--verbose',  dest='verbose', default=0,     action='count', help='verbosity level -v -vv or -vvv [default: 0]')
                p('--zones',          dest='zones',   default='',    action='store', help='debugging zones (task_gen, deps, tasks, etc)')
-               p('--profile',        dest='profile', default='',    action='store_true', help=optparse.SUPPRESS_HELP)
+               p('--profile',        dest='profile', default=0,     action='store_true', help=optparse.SUPPRESS_HELP)
+               p('--pdb',            dest='pdb',     default=0,     action='store_true', help=optparse.SUPPRESS_HELP)
+               p('-h', '--help',     dest='whelp',   default=0,     action='store_true', help="show this help message and exit")
 
                gr = self.add_option_group('Configuration options')
                self.option_groups['configure options'] = gr
@@ -247,31 +263,79 @@ class OptionsContext(Context.Context):
                                        return group
                        return None
 
-       def parse_args(self, _args=None):
-               """
-               Parses arguments from a list which is not necessarily the command-line.
+       def sanitize_path(self, path, cwd=None):
+               if not cwd:
+                       cwd = Context.launch_dir
+               p = os.path.expanduser(path)
+               p = os.path.join(cwd, p)
+               p = os.path.normpath(p)
+               p = os.path.abspath(p)
+               return p
 
-               :param _args: arguments
-               :type _args: list of strings
+       def parse_cmd_args(self, _args=None, cwd=None, allow_unknown=False):
+               """
+               Just parse the arguments
                """
-               global options, commands, envvars
+               self.parser.allow_unknown = allow_unknown
                (options, leftover_args) = self.parser.parse_args(args=_args)
-
+               envvars = []
+               commands = []
                for arg in leftover_args:
                        if '=' in arg:
                                envvars.append(arg)
-                       else:
+                       elif arg != 'options':
                                commands.append(arg)
 
-               if options.destdir:
-                       options.destdir = Utils.sane_path(options.destdir)
+               for name in 'top out destdir prefix bindir libdir'.split():
+                       # those paths are usually expanded from Context.launch_dir
+                       if getattr(options, name, None):
+                               path = self.sanitize_path(getattr(options, name), cwd)
+                               setattr(options, name, path)
+               return options, commands, envvars
+
+       def init_module_vars(self, arg_options, arg_commands, arg_envvars):
+               options.__dict__.clear()
+               del commands[:]
+               del envvars[:]
 
+               options.__dict__.update(arg_options.__dict__)
+               commands.extend(arg_commands)
+               envvars.extend(arg_envvars)
+
+               for var in envvars:
+                       (name, value) = var.split('=', 1)
+                       os.environ[name.strip()] = value
+
+       def init_logs(self, options, commands, envvars):
+               Logs.verbose = options.verbose
                if options.verbose >= 1:
                        self.load('errcheck')
 
                colors = {'yes' : 2, 'auto' : 1, 'no' : 0}[options.colors]
                Logs.enable_colors(colors)
 
+               if options.zones:
+                       Logs.zones = options.zones.split(',')
+                       if not Logs.verbose:
+                               Logs.verbose = 1
+               elif Logs.verbose > 0:
+                       Logs.zones = ['runner']
+               if Logs.verbose > 2:
+                       Logs.zones = ['*']
+
+       def parse_args(self, _args=None):
+               """
+               Parses arguments from a list which is not necessarily the command-line.
+               Initializes the module variables options, commands and envvars
+               If help is requested, prints it and exit the application
+
+               :param _args: arguments
+               :type _args: list of strings
+               """
+               options, commands, envvars = self.parse_cmd_args()
+               self.init_logs(options, commands, envvars)
+               self.init_module_vars(options, commands, envvars)
+
        def execute(self):
                """
                See :py:func:`waflib.Context.Context.execute`
@@ -279,3 +343,4 @@ class OptionsContext(Context.Context):
                super(OptionsContext, self).execute()
                self.parse_args()
                Utils.alloc_process_pool(options.jobs)
+
index 1e37401..30e42d3 100644 (file)
@@ -4,24 +4,50 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Runner.py: Task scheduling and execution
 """
 
-import random
+import heapq, traceback
 try:
        from queue import Queue
 except ImportError:
        from Queue import Queue
 from waflib import Utils, Task, Errors, Logs
 
-GAP = 20
+GAP = 5
 """
 Wait for at least ``GAP * njobs`` before trying to enqueue more tasks to run
 """
 
+class PriorityTasks(object):
+       def __init__(self):
+               self.lst = []
+       def __len__(self):
+               return len(self.lst)
+       def __iter__(self):
+               return iter(self.lst)
+       def clear(self):
+               self.lst = []
+       def append(self, task):
+               heapq.heappush(self.lst, task)
+       def appendleft(self, task):
+               heapq.heappush(self.lst, task)
+       def pop(self):
+               return heapq.heappop(self.lst)
+       def extend(self, lst):
+               if self.lst:
+                       for x in lst:
+                               self.append(x)
+               else:
+                       if isinstance(lst, list):
+                               self.lst = lst
+                               heapq.heapify(lst)
+                       else:
+                               self.lst = lst.lst
+
 class Consumer(Utils.threading.Thread):
        """
        Daemon thread object that executes a task. It shares a semaphore with
@@ -42,7 +68,7 @@ class Consumer(Utils.threading.Thread):
                """
                try:
                        if not self.spawner.master.stop:
-                               self.task.process()
+                               self.spawner.master.process_task(self.task)
                finally:
                        self.spawner.sem.release()
                        self.spawner.master.out.put(self.task)
@@ -53,7 +79,7 @@ class Spawner(Utils.threading.Thread):
        """
        Daemon thread that consumes tasks from :py:class:`waflib.Runner.Parallel` producer and
        spawns a consuming thread :py:class:`waflib.Runner.Consumer` for each
-       :py:class:`waflib.Task.TaskBase` instance.
+       :py:class:`waflib.Task.Task` instance.
        """
        def __init__(self, master):
                Utils.threading.Thread.__init__(self)
@@ -106,22 +132,25 @@ class Parallel(object):
                Instance of :py:class:`waflib.Build.BuildContext`
                """
 
-               self.outstanding = Utils.deque()
-               """List of :py:class:`waflib.Task.TaskBase` that may be ready to be executed"""
+               self.outstanding = PriorityTasks()
+               """Heap of :py:class:`waflib.Task.Task` that may be ready to be executed"""
 
-               self.frozen = Utils.deque()
-               """List of :py:class:`waflib.Task.TaskBase` that are not ready yet"""
+               self.postponed = PriorityTasks()
+               """Heap of :py:class:`waflib.Task.Task` which are not ready to run for non-DAG reasons"""
+
+               self.incomplete = set()
+               """List of :py:class:`waflib.Task.Task` waiting for dependent tasks to complete (DAG)"""
 
                self.ready = Queue(0)
-               """List of :py:class:`waflib.Task.TaskBase` ready to be executed by consumers"""
+               """List of :py:class:`waflib.Task.Task` ready to be executed by consumers"""
 
                self.out = Queue(0)
-               """List of :py:class:`waflib.Task.TaskBase` returned by the task consumers"""
+               """List of :py:class:`waflib.Task.Task` returned by the task consumers"""
 
                self.count = 0
                """Amount of tasks that may be processed by :py:class:`waflib.Runner.TaskConsumer`"""
 
-               self.processed = 1
+               self.processed = 0
                """Amount of tasks processed"""
 
                self.stop = False
@@ -138,6 +167,11 @@ class Parallel(object):
                Flag that indicates that the build cache must be saved when a task was executed
                (calls :py:meth:`waflib.Build.BuildContext.store`)"""
 
+               self.revdeps = Utils.defaultdict(set)
+               """
+               The reverse dependency graph of dependencies obtained from Task.run_after
+               """
+
                self.spawner = Spawner(self)
                """
                Coordinating daemon thread that spawns thread consumers
@@ -147,28 +181,26 @@ class Parallel(object):
                """
                Obtains the next Task instance to run
 
-               :rtype: :py:class:`waflib.Task.TaskBase`
+               :rtype: :py:class:`waflib.Task.Task`
                """
                if not self.outstanding:
                        return None
-               return self.outstanding.popleft()
+               return self.outstanding.pop()
 
        def postpone(self, tsk):
                """
-               Adds the task to the list :py:attr:`waflib.Runner.Parallel.frozen`.
+               Adds the task to the list :py:attr:`waflib.Runner.Parallel.postponed`.
                The order is scrambled so as to consume as many tasks in parallel as possible.
 
                :param tsk: task instance
-               :type tsk: :py:class:`waflib.Task.TaskBase`
+               :type tsk: :py:class:`waflib.Task.Task`
                """
-               if random.randint(0, 1):
-                       self.frozen.appendleft(tsk)
-               else:
-                       self.frozen.append(tsk)
+               self.postponed.append(tsk)
 
        def refill_task_list(self):
                """
-               Adds the next group of tasks to execute in :py:attr:`waflib.Runner.Parallel.outstanding`.
+               Pulls a next group of tasks to execute in :py:attr:`waflib.Runner.Parallel.outstanding`.
+               Ensures that all tasks in the current build group are complete before processing the next one.
                """
                while self.count > self.numjobs * GAP:
                        self.get_out()
@@ -176,54 +208,105 @@ class Parallel(object):
                while not self.outstanding:
                        if self.count:
                                self.get_out()
-                       elif self.frozen:
+                               if self.outstanding:
+                                       break
+                       elif self.postponed:
                                try:
                                        cond = self.deadlock == self.processed
                                except AttributeError:
                                        pass
                                else:
                                        if cond:
-                                               msg = 'check the build order for the tasks'
-                                               for tsk in self.frozen:
-                                                       if not tsk.run_after:
-                                                               msg = 'check the methods runnable_status'
-                                                               break
                                                lst = []
-                                               for tsk in self.frozen:
-                                                       lst.append('%s\t-> %r' % (repr(tsk), [id(x) for x in tsk.run_after]))
-                                               raise Errors.WafError('Deadlock detected: %s%s' % (msg, ''.join(lst)))
+                                               for tsk in self.postponed:
+                                                       deps = [id(x) for x in tsk.run_after if not x.hasrun]
+                                                       lst.append('%s\t-> %r' % (repr(tsk), deps))
+                                                       if not deps:
+                                                               lst.append('\n  task %r dependencies are done, check its *runnable_status*?' % id(tsk))
+                                               raise Errors.WafError('Deadlock detected: check the task build order%s' % ''.join(lst))
                                self.deadlock = self.processed
 
-                       if self.frozen:
-                               self.outstanding.extend(self.frozen)
-                               self.frozen.clear()
+                       if self.postponed:
+                               self.outstanding.extend(self.postponed)
+                               self.postponed.clear()
                        elif not self.count:
-                               self.outstanding.extend(self.biter.next())
-                               self.total = self.bld.total()
-                               break
+                               if self.incomplete:
+                                       for x in self.incomplete:
+                                               for k in x.run_after:
+                                                       if not k.hasrun:
+                                                               break
+                                               else:
+                                                       # dependency added after the build started without updating revdeps
+                                                       self.incomplete.remove(x)
+                                                       self.outstanding.append(x)
+                                                       break
+                                       else:
+                                               raise Errors.WafError('Broken revdeps detected on %r' % self.incomplete)
+                               else:
+                                       tasks = next(self.biter)
+                                       ready, waiting = self.prio_and_split(tasks)
+                                       self.outstanding.extend(ready)
+                                       self.incomplete.update(waiting)
+                                       self.total = self.bld.total()
+                                       break
 
        def add_more_tasks(self, tsk):
                """
-               If a task provides :py:attr:`waflib.Task.TaskBase.more_tasks`, then the tasks contained
+               If a task provides :py:attr:`waflib.Task.Task.more_tasks`, then the tasks contained
                in that list are added to the current build and will be processed before the next build group.
 
+               The priorities for dependent tasks are not re-calculated globally
+
                :param tsk: task instance
-               :type tsk: :py:attr:`waflib.Task.TaskBase`
+               :type tsk: :py:attr:`waflib.Task.Task`
                """
                if getattr(tsk, 'more_tasks', None):
-                       self.outstanding.extend(tsk.more_tasks)
+                       # TODO recompute priorities globally?
+                       ready, waiting = self.prio_and_split(tsk.more_tasks)
+                       self.outstanding.extend(ready)
+                       self.incomplete.update(waiting)
                        self.total += len(tsk.more_tasks)
 
+       def mark_finished(self, tsk):
+               def try_unfreeze(x):
+                       # DAG ancestors are likely to be in the incomplete set
+                       if x in self.incomplete:
+                               # TODO remove dependencies to free some memory?
+                               # x.run_after.remove(tsk)
+                               for k in x.run_after:
+                                       if not k.hasrun:
+                                               break
+                               else:
+                                       self.incomplete.remove(x)
+                                       self.outstanding.append(x)
+
+               if tsk in self.revdeps:
+                       for x in self.revdeps[tsk]:
+                               if isinstance(x, Task.TaskGroup):
+                                       x.prev.remove(tsk)
+                                       if not x.prev:
+                                               for k in x.next:
+                                                       # TODO necessary optimization?
+                                                       k.run_after.remove(x)
+                                                       try_unfreeze(k)
+                                               # TODO necessary optimization?
+                                               x.next = []
+                               else:
+                                       try_unfreeze(x)
+                       del self.revdeps[tsk]
+
        def get_out(self):
                """
                Waits for a Task that task consumers add to :py:attr:`waflib.Runner.Parallel.out` after execution.
                Adds more Tasks if necessary through :py:attr:`waflib.Runner.Parallel.add_more_tasks`.
 
-               :rtype: :py:attr:`waflib.Task.TaskBase`
+               :rtype: :py:attr:`waflib.Task.Task`
                """
                tsk = self.out.get()
                if not self.stop:
                        self.add_more_tasks(tsk)
+               self.mark_finished(tsk)
+
                self.count -= 1
                self.dirty = True
                return tsk
@@ -233,32 +316,42 @@ class Parallel(object):
                Enqueue a Task to :py:attr:`waflib.Runner.Parallel.ready` so that consumers can run them.
 
                :param tsk: task instance
-               :type tsk: :py:attr:`waflib.Task.TaskBase`
+               :type tsk: :py:attr:`waflib.Task.Task`
                """
                self.ready.put(tsk)
 
+       def process_task(self, tsk):
+               """
+               Processes a task and attempts to stop the build in case of errors
+               """
+               tsk.process()
+               if tsk.hasrun != Task.SUCCESS:
+                       self.error_handler(tsk)
+
        def skip(self, tsk):
                """
                Mark a task as skipped/up-to-date
                """
                tsk.hasrun = Task.SKIPPED
+               self.mark_finished(tsk)
+
+       def cancel(self, tsk):
+               """
+               Mark a task as failed because of unsatisfiable dependencies
+               """
+               tsk.hasrun = Task.CANCELED
+               self.mark_finished(tsk)
 
        def error_handler(self, tsk):
                """
-               Called when a task cannot be executed. The flag :py:attr:`waflib.Runner.Parallel.stop` is set, unless
-               the build is executed with::
+               Called when a task cannot be executed. The flag :py:attr:`waflib.Runner.Parallel.stop` is set,
+               unless the build is executed with::
 
                        $ waf build -k
 
                :param tsk: task instance
-               :type tsk: :py:attr:`waflib.Task.TaskBase`
+               :type tsk: :py:attr:`waflib.Task.Task`
                """
-               if hasattr(tsk, 'scan') and hasattr(tsk, 'uid'):
-                       # TODO waf 2.0 - this breaks encapsulation
-                       try:
-                               del self.bld.imp_sigs[tsk.uid()]
-                       except KeyError:
-                               pass
                if not self.bld.keep:
                        self.stop = True
                self.error.append(tsk)
@@ -274,11 +367,11 @@ class Parallel(object):
                        return tsk.runnable_status()
                except Exception:
                        self.processed += 1
-                       tsk.err_msg = Utils.ex_stack()
+                       tsk.err_msg = traceback.format_exc()
                        if not self.stop and self.bld.keep:
                                self.skip(tsk)
                                if self.bld.keep == 1:
-                                       # if -k stop at the first exception, if -kk try to go as far as possible
+                                       # if -k stop on the first exception, if -kk try to go as far as possible
                                        if Logs.verbose > 1 or not self.error:
                                                self.error.append(tsk)
                                        self.stop = True
@@ -286,9 +379,10 @@ class Parallel(object):
                                        if Logs.verbose > 1:
                                                self.error.append(tsk)
                                return Task.EXCEPTION
-                       tsk.hasrun = Task.EXCEPTION
 
+                       tsk.hasrun = Task.EXCEPTION
                        self.error_handler(tsk)
+
                        return Task.EXCEPTION
 
        def start(self):
@@ -320,10 +414,9 @@ class Parallel(object):
                                self.processed += 1
                                continue
 
-                       if self.stop: # stop immediately after a failure was detected
+                       if self.stop: # stop immediately after a failure is detected
                                break
 
-
                        st = self.task_status(tsk)
                        if st == Task.RUN_ME:
                                self.count += 1
@@ -332,17 +425,24 @@ class Parallel(object):
                                if self.numjobs == 1:
                                        tsk.log_display(tsk.generator.bld)
                                        try:
-                                               tsk.process()
+                                               self.process_task(tsk)
                                        finally:
                                                self.out.put(tsk)
                                else:
                                        self.add_task(tsk)
-                       if st == Task.ASK_LATER:
+                       elif st == Task.ASK_LATER:
                                self.postpone(tsk)
                        elif st == Task.SKIP_ME:
                                self.processed += 1
                                self.skip(tsk)
                                self.add_more_tasks(tsk)
+                       elif st == Task.CANCEL_ME:
+                               # A dependency problem has occurred, and the
+                               # build is most likely run with `waf -k`
+                               if Logs.verbose > 1:
+                                       self.error.append(tsk)
+                               self.processed += 1
+                               self.cancel(tsk)
 
                # self.count represents the tasks that have been made available to the consumer threads
                # collect all the tasks after an error else the message may be incomplete
@@ -350,4 +450,110 @@ class Parallel(object):
                        self.get_out()
 
                self.ready.put(None)
-               assert (self.count == 0 or self.stop)
+               if not self.stop:
+                       assert not self.count
+                       assert not self.postponed
+                       assert not self.incomplete
+
+       def prio_and_split(self, tasks):
+               """
+               Label input tasks with priority values, and return a pair containing
+               the tasks that are ready to run and the tasks that are necessarily
+               waiting for other tasks to complete.
+
+               The priority system is really meant as an optional layer for optimization:
+               dependency cycles are found quickly, and builds should be more efficient.
+               A high priority number means that a task is processed first.
+
+               This method can be overridden to disable the priority system::
+
+                       def prio_and_split(self, tasks):
+                               return tasks, []
+
+               :return: A pair of task lists
+               :rtype: tuple
+               """
+               # to disable:
+               #return tasks, []
+               for x in tasks:
+                       x.visited = 0
+
+               reverse = self.revdeps
+
+               for x in tasks:
+                       for k in x.run_after:
+                               if isinstance(k, Task.TaskGroup):
+                                       if k.done:
+                                               pass
+                                       else:
+                                               k.done = True
+                                               for j in k.prev:
+                                                       reverse[j].add(k)
+                               else:
+                                       reverse[k].add(x)
+
+               # the priority number is not the tree depth
+               def visit(n):
+                       if isinstance(n, Task.TaskGroup):
+                               return sum(visit(k) for k in n.next)
+
+                       if n.visited == 0:
+                               n.visited = 1
+
+                               if n in reverse:
+                                       rev = reverse[n]
+                                       n.prio_order = n.tree_weight + len(rev) + sum(visit(k) for k in rev)
+                               else:
+                                       n.prio_order = n.tree_weight
+
+                               n.visited = 2
+                       elif n.visited == 1:
+                               raise Errors.WafError('Dependency cycle found!')
+                       return n.prio_order
+
+               for x in tasks:
+                       if x.visited != 0:
+                               # must visit all to detect cycles
+                               continue
+                       try:
+                               visit(x)
+                       except Errors.WafError:
+                               self.debug_cycles(tasks, reverse)
+
+               ready = []
+               waiting = []
+               for x in tasks:
+                       for k in x.run_after:
+                               if not k.hasrun:
+                                       waiting.append(x)
+                                       break
+                       else:
+                               ready.append(x)
+               return (ready, waiting)
+
+       def debug_cycles(self, tasks, reverse):
+               tmp = {}
+               for x in tasks:
+                       tmp[x] = 0
+
+               def visit(n, acc):
+                       if isinstance(n, Task.TaskGroup):
+                               for k in n.next:
+                                       visit(k, acc)
+                               return
+                       if tmp[n] == 0:
+                               tmp[n] = 1
+                               for k in reverse.get(n, []):
+                                       visit(k, [n] + acc)
+                               tmp[n] = 2
+                       elif tmp[n] == 1:
+                               lst = []
+                               for tsk in acc:
+                                       lst.append(repr(tsk))
+                                       if tsk is n:
+                                               # exclude prior nodes, we want the minimum cycle
+                                               break
+                               raise Errors.WafError('Task dependency cycle in "run_after" constraints: %s' % ''.join(lst))
+               for x in tasks:
+                       visit(x, [])
+
index 9b27840..642d6d4 100644 (file)
@@ -4,10 +4,12 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 "Module called for configuring, compiling and installing targets"
 
+from __future__ import with_statement
+
 import os, shlex, shutil, traceback, errno, sys, stat
 from waflib import Utils, Configure, Logs, Options, ConfigSet, Context, Errors, Build, Node
 
@@ -28,58 +30,49 @@ def waf_entry_point(current_directory, version, wafdir):
        :param wafdir: absolute path representing the directory of the waf library
        :type wafdir: string
        """
-
        Logs.init_log()
 
        if Context.WAFVERSION != version:
                Logs.error('Waf script %r and library %r do not match (directory %r)', version, Context.WAFVERSION, wafdir)
                sys.exit(1)
 
-       if '--version' in sys.argv:
-               Context.run_dir = current_directory
-               ctx = Context.create_context('options')
-               ctx.curdir = current_directory
-               ctx.parse_args()
-               sys.exit(0)
+       # Store current directory before any chdir
+       Context.waf_dir = wafdir
+       Context.run_dir = Context.launch_dir = current_directory
+       start_dir = current_directory
+       no_climb = os.environ.get('NOCLIMB')
 
        if len(sys.argv) > 1:
-               # os.path.join handles absolute paths in sys.argv[1] accordingly (it discards the previous ones)
+               # os.path.join handles absolute paths
                # if sys.argv[1] is not an absolute path, then it is relative to the current working directory
                potential_wscript = os.path.join(current_directory, sys.argv[1])
-               # maybe check if the file is executable
-               # perhaps extract 'wscript' as a constant
-               if os.path.basename(potential_wscript) == 'wscript' and os.path.isfile(potential_wscript):
+               if os.path.basename(potential_wscript) == Context.WSCRIPT_FILE and os.path.isfile(potential_wscript):
                        # need to explicitly normalize the path, as it may contain extra '/.'
-                       # TODO abspath?
-                       current_directory = os.path.normpath(os.path.dirname(potential_wscript))
+                       path = os.path.normpath(os.path.dirname(potential_wscript))
+                       start_dir = os.path.abspath(path)
+                       no_climb = True
                        sys.argv.pop(1)
 
-       Context.waf_dir = wafdir
-       Context.launch_dir = current_directory
+       ctx = Context.create_context('options')
+       (options, commands, env) = ctx.parse_cmd_args(allow_unknown=True)
+       if options.top:
+               start_dir = Context.run_dir = Context.top_dir = options.top
+               no_climb = True
+       if options.out:
+               Context.out_dir = options.out
 
        # if 'configure' is in the commands, do not search any further
-       no_climb = os.environ.get('NOCLIMB')
        if not no_climb:
                for k in no_climb_commands:
-                       for y in sys.argv:
+                       for y in commands:
                                if y.startswith(k):
                                        no_climb = True
                                        break
 
-       # if --top is provided assume the build started in the top directory
-       for i, x in enumerate(sys.argv):
-               # WARNING: this modifies sys.argv
-               if x.startswith('--top='):
-                       Context.run_dir = Context.top_dir = Utils.sane_path(x[6:])
-                       sys.argv[i] = '--top=' + Context.run_dir
-               if x.startswith('--out='):
-                       Context.out_dir = Utils.sane_path(x[6:])
-                       sys.argv[i] = '--out=' + Context.out_dir
-
        # try to find a lock file (if the project was configured)
        # at the same time, store the first wscript file seen
-       cur = current_directory
-       while cur and not Context.top_dir:
+       cur = start_dir
+       while cur:
                try:
                        lst = os.listdir(cur)
                except OSError:
@@ -134,14 +127,11 @@ def waf_entry_point(current_directory, version, wafdir):
                        break
 
        if not Context.run_dir:
-               if '-h' in sys.argv or '--help' in sys.argv:
-                       Logs.warn('No wscript file found: the help message may be incomplete')
-                       Context.run_dir = current_directory
-                       ctx = Context.create_context('options')
-                       ctx.curdir = current_directory
-                       ctx.parse_args()
+               if options.whelp:
+                       Logs.warn('These are the generic options (no wscript/project found)')
+                       ctx.parser.print_help()
                        sys.exit(0)
-               Logs.error('Waf: Run from a directory containing a file named %r', Context.WSCRIPT_FILE)
+               Logs.error('Waf: Run from a folder containing a %r file (or try -h for the generic options)', Context.WSCRIPT_FILE)
                sys.exit(1)
 
        try:
@@ -152,31 +142,40 @@ def waf_entry_point(current_directory, version, wafdir):
 
        try:
                set_main_module(os.path.normpath(os.path.join(Context.run_dir, Context.WSCRIPT_FILE)))
-       except Errors.WafError ,e:
+       except Errors.WafError as e:
                Logs.pprint('RED', e.verbose_msg)
                Logs.error(str(e))
                sys.exit(1)
-       except Exception ,e:
+       except Exception as e:
                Logs.error('Waf: The wscript in %r is unreadable', Context.run_dir)
                traceback.print_exc(file=sys.stdout)
                sys.exit(2)
 
-       if '--profile' in sys.argv:
+       if options.profile:
                import cProfile, pstats
                cProfile.runctx('from waflib import Scripting; Scripting.run_commands()', {}, {}, 'profi.txt')
                p = pstats.Stats('profi.txt')
                p.sort_stats('time').print_stats(75) # or 'cumulative'
        else:
                try:
-                       run_commands()
-               except Errors.WafError ,e:
+                       try:
+                               run_commands()
+                       except:
+                               if options.pdb:
+                                       import pdb
+                                       type, value, tb = sys.exc_info()
+                                       traceback.print_exc()
+                                       pdb.post_mortem(tb)
+                               else:
+                                       raise
+               except Errors.WafError as e:
                        if Logs.verbose > 1:
                                Logs.pprint('RED', e.verbose_msg)
                        Logs.error(e.msg)
                        sys.exit(1)
                except SystemExit:
                        raise
-               except Exception ,e:
+               except Exception as e:
                        traceback.print_exc(file=sys.stdout)
                        sys.exit(2)
                except KeyboardInterrupt:
@@ -217,29 +216,13 @@ def parse_options():
        Parses the command-line options and initialize the logging system.
        Called by :py:func:`waflib.Scripting.waf_entry_point` during the initialization.
        """
-       Context.create_context('options').execute()
-
-       for var in Options.envvars:
-               (name, value) = var.split('=', 1)
-               os.environ[name.strip()] = value
-
+       ctx = Context.create_context('options')
+       ctx.execute()
        if not Options.commands:
-               Options.commands = [default_cmd]
-       Options.commands = [x for x in Options.commands if x != 'options'] # issue 1076
-
-       # process some internal Waf options
-       Logs.verbose = Options.options.verbose
-       #Logs.init_log()
-
-       if Options.options.zones:
-               Logs.zones = Options.options.zones.split(',')
-               if not Logs.verbose:
-                       Logs.verbose = 1
-       elif Logs.verbose > 0:
-               Logs.zones = ['runner']
-
-       if Logs.verbose > 2:
-               Logs.zones = ['*']
+               Options.commands.append(default_cmd)
+       if Options.options.whelp:
+               ctx.parser.print_help()
+               sys.exit(0)
 
 def run_command(cmd_name):
        """
@@ -305,38 +288,53 @@ def distclean_dir(dirname):
                pass
 
 def distclean(ctx):
-       '''removes the build directory'''
-       lst = os.listdir('.')
-       for f in lst:
-               if f == Options.lockfile:
-                       try:
-                               proj = ConfigSet.ConfigSet(f)
-                       except IOError:
-                               Logs.warn('Could not read %r', f)
-                               continue
+       '''removes build folders and data'''
 
-                       if proj['out_dir'] != proj['top_dir']:
-                               try:
-                                       shutil.rmtree(proj['out_dir'])
-                               except EnvironmentError ,e:
-                                       if e.errno != errno.ENOENT:
-                                               Logs.warn('Could not remove %r', proj['out_dir'])
-                       else:
-                               distclean_dir(proj['out_dir'])
+       def remove_and_log(k, fun):
+               try:
+                       fun(k)
+               except EnvironmentError as e:
+                       if e.errno != errno.ENOENT:
+                               Logs.warn('Could not remove %r', k)
 
-                       for k in (proj['out_dir'], proj['top_dir'], proj['run_dir']):
-                               p = os.path.join(k, Options.lockfile)
-                               try:
-                                       os.remove(p)
-                               except OSError ,e:
-                                       if e.errno != errno.ENOENT:
-                                               Logs.warn('Could not remove %r', p)
+       # remove waf cache folders on the top-level
+       if not Options.commands:
+               for k in os.listdir('.'):
+                       for x in '.waf-2 waf-2 .waf3-2 waf3-2'.split():
+                               if k.startswith(x):
+                                       remove_and_log(k, shutil.rmtree)
+
+       # remove a build folder, if any
+       cur = '.'
+       if ctx.options.no_lock_in_top:
+               cur = ctx.options.out
+
+       try:
+               lst = os.listdir(cur)
+       except OSError:
+               Logs.warn('Could not read %r', cur)
+               return
+
+       if Options.lockfile in lst:
+               f = os.path.join(cur, Options.lockfile)
+               try:
+                       env = ConfigSet.ConfigSet(f)
+               except EnvironmentError:
+                       Logs.warn('Could not read %r', f)
+                       return
 
-               # remove local waf cache folders
-               if not Options.commands:
-                       for x in '.waf-1. waf-1. .waf3-1. waf3-1.'.split():
-                               if f.startswith(x):
-                                       shutil.rmtree(f, ignore_errors=True)
+               if not env.out_dir or not env.top_dir:
+                       Logs.warn('Invalid lock file %r', f)
+                       return
+
+               if env.out_dir == env.top_dir:
+                       distclean_dir(env.out_dir)
+               else:
+                       remove_and_log(env.out_dir, shutil.rmtree)
+
+               for k in (env.out_dir, env.top_dir, env.run_dir):
+                       p = os.path.join(k, Options.lockfile)
+                       remove_and_log(p, os.remove)
 
 class Dist(Context.Context):
        '''creates an archive containing the project source code'''
@@ -391,11 +389,11 @@ class Dist(Context.Context):
                        self.fatal('Valid algo types are tar.bz2, tar.gz, tar.xz or zip')
 
                try:
-                       from hashlib import sha1
+                       from hashlib import sha256
                except ImportError:
                        digest = ''
                else:
-                       digest = ' (sha=%r)' % sha1(node.read(flags='rb')).hexdigest()
+                       digest = ' (sha256=%r)' % sha256(node.read(flags='rb')).hexdigest()
 
                Logs.info('New archive created: %s%s', self.arch_name, digest)
 
@@ -424,11 +422,8 @@ class Dist(Context.Context):
                tinfo.gname = 'root'
 
                if os.path.isfile(p):
-                       fu = open(p, 'rb')
-                       try:
-                               tar.addfile(tinfo, fileobj=fu)
-                       finally:
-                               fu.close()
+                       with open(p, 'rb') as f:
+                               tar.addfile(tinfo, fileobj=f)
                else:
                        tar.addfile(tinfo)
 
@@ -490,7 +485,7 @@ class Dist(Context.Context):
                try:
                        return self.excl
                except AttributeError:
-                       self.excl = Node.exclude_regs + ' **/waf-1.8.* **/.waf-1.8* **/waf3-1.8.* **/.waf3-1.8* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
+                       self.excl = Node.exclude_regs + ' **/waf-2.* **/.waf-2.* **/waf3-2.* **/.waf3-2.* **/*~ **/*.rej **/*.orig **/*.pyc **/*.pyo **/*.bak **/*.swp **/.lock-w*'
                        if Context.out_dir:
                                nd = self.root.find_node(Context.out_dir)
                                if nd:
@@ -523,11 +518,7 @@ def dist(ctx):
        pass
 
 class DistCheck(Dist):
-       """
-       Creates an archive of the project, then attempts to build the project in a temporary directory::
-
-               $ waf distcheck
-       """
+       """creates an archive with dist, then tries to build it"""
        fun = 'distcheck'
        cmd = 'distcheck'
 
@@ -554,12 +545,9 @@ class DistCheck(Dist):
                """
                import tempfile, tarfile
 
-               try:
-                       t = tarfile.open(self.get_arch_name())
+               with tarfile.open(self.get_arch_name()) as t:
                        for x in t:
                                t.extract(x)
-               finally:
-                       t.close()
 
                instdir = tempfile.mkdtemp('.inst', self.get_base_name())
                cmd = self.make_distcheck_cmd(instdir)
@@ -613,7 +601,8 @@ def autoconfigure(execute_method):
                        cmd = env.config_cmd or 'configure'
                        if Configure.autoconfig == 'clobber':
                                tmp = Options.options.__dict__
-                               Options.options.__dict__ = env.options
+                               if env.options:
+                                       Options.options.__dict__ = env.options
                                try:
                                        run_command(cmd)
                                finally:
@@ -625,3 +614,4 @@ def autoconfigure(execute_method):
                        return execute_method(self)
        return execute
 Build.BuildContext.execute = autoconfigure(Build.BuildContext.execute)
+
index 44db70c..89a7325 100644 (file)
@@ -4,13 +4,13 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Tasks represent atomic operations such as processes.
 """
 
-import os, re, sys, tempfile
+import os, re, sys, tempfile, traceback
 from waflib import Utils, Logs, Errors
 
 # task states
@@ -26,6 +26,9 @@ CRASHED = 2
 EXCEPTION = 3
 """An exception occurred in the task execution"""
 
+CANCELED = 4
+"""A dependency for the task is missing so it was cancelled"""
+
 SKIPPED = 8
 """The task did not have to be executed"""
 
@@ -41,6 +44,9 @@ SKIP_ME = -2
 RUN_ME = -3
 """The task must be executed"""
 
+CANCEL_ME = -4
+"""The task cannot be executed because of a dependency problem"""
+
 COMPILE_TEMPLATE_SHELL = '''
 def f(tsk):
        env = tsk.env
@@ -90,8 +96,7 @@ class store_task_type(type):
                super(store_task_type, cls).__init__(name, bases, dict)
                name = cls.__name__
 
-               if name != 'evil' and name != 'TaskBase':
-                       global classes
+               if name != 'evil' and name != 'Task':
                        if getattr(cls, 'run_str', None):
                                # if a string is provided, convert it to a method
                                (f, dvars) = compile_fun(cls.run_str, cls.shell)
@@ -112,20 +117,21 @@ class store_task_type(type):
 evil = store_task_type('evil', (object,), {})
 "Base class provided to avoid writing a metaclass, so the code can run in python 2.6 and 3.x unmodified"
 
-class TaskBase(evil):
+class Task(evil):
        """
-       Base class for all Waf tasks, which should be seen as an interface.
-       For illustration purposes, instances of this class will execute the attribute
-       'fun' in :py:meth:`waflib.Task.TaskBase.run`. When in doubt, create
-       subclasses of :py:class:`waflib.Task.Task` instead.
+       This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
+       uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
+       the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
+       nodes (if present).
+       """
+       vars = []
+       """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
 
-       Subclasses must override these methods:
+       always_run = False
+       """Specify whether task instances must always be executed or not (class attribute)"""
 
-       #. __str__: string to display to the user
-       #. runnable_status: ask the task if it should be run, skipped, or if we have to ask later
-       #. run: what to do to execute the task
-       #. post_run: what to do after the task has been executed
-       """
+       shell = False
+       """Execute the command with the shell (class attribute)"""
 
        color = 'GREEN'
        """Color for the console display, see :py:const:`waflib.Logs.colors_lst`"""
@@ -142,7 +148,7 @@ class TaskBase(evil):
        after = []
        """List of task class names to execute after instances of this class"""
 
-       hcode = ''
+       hcode = Utils.SIG_NIL
        """String representing an additional hash for the class representation"""
 
        keep_last_cmd = False
@@ -150,32 +156,51 @@ class TaskBase(evil):
        This may be useful for certain extensions but it can a lot of memory.
        """
 
-       __slots__ = ('hasrun', 'generator')
+       weight = 0
+       """Optional weight to tune the priority for task instances.
+       The higher, the earlier. The weight only applies to single task objects."""
+
+       tree_weight = 0
+       """Optional weight to tune the priority of task instances and whole subtrees.
+       The higher, the earlier."""
+
+       prio_order = 0
+       """Priority order set by the scheduler on instances during the build phase.
+       You most likely do not need to set it.
+       """
+
+       __slots__ = ('hasrun', 'generator', 'env', 'inputs', 'outputs', 'dep_nodes', 'run_after')
 
        def __init__(self, *k, **kw):
-               """
-               The base task class requires a task generator (set to *self* if missing)
-               """
                self.hasrun = NOT_RUN
                try:
                        self.generator = kw['generator']
                except KeyError:
                        self.generator = self
 
-       def __repr__(self):
-               return '\n\t{task %r: %s %s}' % (self.__class__.__name__, id(self), str(getattr(self, 'fun', '')))
+               self.env = kw['env']
+               """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
 
-       def __str__(self):
-               "String to display to the user"
-               if hasattr(self, 'fun'):
-                       return self.fun.__name__
-               return self.__class__.__name__
+               self.inputs  = []
+               """List of input nodes, which represent the files used by the task instance"""
 
-       def keyword(self):
-               "Display keyword used to prettify the console outputs"
-               if hasattr(self, 'fun'):
-                       return 'Function'
-               return 'Processing'
+               self.outputs = []
+               """List of output nodes, which represent the files created by the task instance"""
+
+               self.dep_nodes = []
+               """List of additional nodes to depend on"""
+
+               self.run_after = set()
+               """Set of tasks that must be executed before this one"""
+
+       def __lt__(self, other):
+               return self.priority() > other.priority()
+       def __le__(self, other):
+               return self.priority() >= other.priority()
+       def __gt__(self, other):
+               return self.priority() < other.priority()
+       def __ge__(self, other):
+               return self.priority() <= other.priority()
 
        def get_cwd(self):
                """
@@ -209,6 +234,15 @@ class TaskBase(evil):
                        x = '"%s"' % x
                return x
 
+       def priority(self):
+               """
+               Priority of execution; the higher, the earlier
+
+               :return: the priority value
+               :rtype: a tuple of numeric values
+               """
+               return (self.weight + self.prio_order, - getattr(self.generator, 'tg_idx_count', 0))
+
        def split_argfile(self, cmd):
                """
                Splits a list of process commands into the executable part and its list of arguments
@@ -229,6 +263,13 @@ class TaskBase(evil):
                :type cmd: list of string (best) or string (process will use a shell)
                :return: the return code
                :rtype: int
+
+               Optional parameters:
+
+               #. cwd: current working directory (Node or string)
+               #. stdout: set to None to prevent waf from capturing the process standard output
+               #. stderr: set to None to prevent waf from capturing the process standard error
+               #. timeout: timeout value (Python 3)
                """
                if not 'cwd' in kw:
                        kw['cwd'] = self.get_cwd()
@@ -240,13 +281,18 @@ class TaskBase(evil):
                        env = kw['env'] = dict(kw.get('env') or self.env.env or os.environ)
                        env['PATH'] = self.env.PATH if isinstance(self.env.PATH, str) else os.pathsep.join(self.env.PATH)
 
+               if hasattr(self, 'stdout'):
+                       kw['stdout'] = self.stdout
+               if hasattr(self, 'stderr'):
+                       kw['stderr'] = self.stderr
+
                # workaround for command line length limit:
                # http://support.microsoft.com/kb/830473
                if not isinstance(cmd, str) and (len(repr(cmd)) >= 8192 if Utils.is_win32 else len(cmd) > 200000):
                        cmd, args = self.split_argfile(cmd)
                        try:
                                (fd, tmp) = tempfile.mkstemp()
-                               os.write(fd, '\r\n'.join(args))
+                               os.write(fd, '\r\n'.join(args).encode())
                                os.close(fd)
                                if Logs.verbose:
                                        Logs.debug('argfile: @%r -> %r', tmp, args)
@@ -260,36 +306,16 @@ class TaskBase(evil):
                else:
                        return self.generator.bld.exec_command(cmd, **kw)
 
-       def runnable_status(self):
-               """
-               Returns the Task status
-
-               :return: a task state in :py:const:`waflib.Task.RUN_ME`, :py:const:`waflib.Task.SKIP_ME` or :py:const:`waflib.Task.ASK_LATER`.
-               :rtype: int
-               """
-               return RUN_ME
-
-       def uid(self):
-               """
-               Computes a unique identifier for the task
-
-               :rtype: string or bytes
-               """
-               return Utils.SIG_NIL
-
        def process(self):
                """
-               Assume that the task has had a ``master`` which is an instance of :py:class:`waflib.Runner.Parallel`.
-               Execute the task and then put it back in the queue :py:attr:`waflib.Runner.Parallel.out` (may be replaced by subclassing).
+               Runs the task and handles errors
 
                :return: 0 or None if everything is fine
                :rtype: integer
                """
                # remove the task signature immediately before it is executed
-               # in case of failure the task will be executed again
-               m = self.generator.bld.producer
+               # so that the task will be executed again in case of failure
                try:
-                       # TODO another place for this?
                        del self.generator.bld.task_sigs[self.uid()]
                except KeyError:
                        pass
@@ -297,44 +323,29 @@ class TaskBase(evil):
                try:
                        ret = self.run()
                except Exception:
-                       self.err_msg = Utils.ex_stack()
+                       self.err_msg = traceback.format_exc()
                        self.hasrun = EXCEPTION
-
-                       # TODO cleanup
-                       m.error_handler(self)
-                       return
-
-               if ret:
-                       self.err_code = ret
-                       self.hasrun = CRASHED
                else:
-                       try:
-                               self.post_run()
-                       except Errors.WafError:
-                               pass
-                       except Exception:
-                               self.err_msg = Utils.ex_stack()
-                               self.hasrun = EXCEPTION
+                       if ret:
+                               self.err_code = ret
+                               self.hasrun = CRASHED
                        else:
-                               self.hasrun = SUCCESS
-               if self.hasrun != SUCCESS:
-                       m.error_handler(self)
-
-       def run(self):
-               """
-               Called by threads to execute the tasks. The default is empty and meant to be overridden in subclasses.
-
-               .. warning:: It is a bad idea to create nodes in this method, so avoid :py:meth:`waflib.Node.Node.ant_glob`
-
-               :rtype: int
-               """
-               if hasattr(self, 'fun'):
-                       return self.fun(self)
-               return 0
+                               try:
+                                       self.post_run()
+                               except Errors.WafError:
+                                       pass
+                               except Exception:
+                                       self.err_msg = traceback.format_exc()
+                                       self.hasrun = EXCEPTION
+                               else:
+                                       self.hasrun = SUCCESS
 
-       def post_run(self):
-               "Update build data after successful Task execution. Override in subclasses."
-               pass
+               if self.hasrun != SUCCESS and self.scan:
+                       # rescan dependencies on next run
+                       try:
+                               del self.generator.bld.imp_sigs[self.uid()]
+                       except KeyError:
+                               pass
 
        def log_display(self, bld):
                "Writes the execution status on the context logger"
@@ -367,10 +378,7 @@ class TaskBase(evil):
 
                def cur():
                        # the current task position, computed as late as possible
-                       tmp = -1
-                       if hasattr(master, 'ready'):
-                               tmp -= master.ready.qsize()
-                       return master.processed + tmp
+                       return master.processed - master.ready.qsize()
 
                if self.generator.bld.progress_bar == 1:
                        return self.generator.bld.progress_line(cur(), master.total, col1, col2)
@@ -406,9 +414,7 @@ class TaskBase(evil):
                :return: a hash value
                :rtype: string
                """
-               cls = self.__class__
-               tup = (str(cls.before), str(cls.after), str(cls.ext_in), str(cls.ext_out), cls.__name__, cls.hcode)
-               return hash(tup)
+               return (tuple(self.before), tuple(self.after), tuple(self.ext_in), tuple(self.ext_out), self.__class__.__name__, self.hcode)
 
        def format_error(self):
                """
@@ -432,6 +438,8 @@ class TaskBase(evil):
                                return ' -> task in %r failed%s' % (name, msg)
                elif self.hasrun == MISSING:
                        return ' -> missing files in %r%s' % (name, msg)
+               elif self.hasrun == CANCELED:
+                       return ' -> %r canceled because of missing dependencies' % name
                else:
                        return 'invalid status for task in %r: %r' % (name, self.hasrun)
 
@@ -442,12 +450,12 @@ class TaskBase(evil):
 
                The results will be slightly different if FOO_ST is a list, for example::
 
-                       env.FOO_ST = ['-a', '-b']
+                       env.FOO    = ['p1', 'p2']
                        env.FOO_ST = '-I%s'
                        # ${FOO_ST:FOO} returns
                        ['-Ip1', '-Ip2']
 
-                       env.FOO    = ['p1', 'p2']
+                       env.FOO_ST = ['-a', '-b']
                        # ${FOO_ST:FOO} returns
                        ['-a', '-b', 'p1', '-a', '-b', 'p2']
                """
@@ -468,40 +476,6 @@ class TaskBase(evil):
                                lst.append(y)
                        return lst
 
-class Task(TaskBase):
-       """
-       This class deals with the filesystem (:py:class:`waflib.Node.Node`). The method :py:class:`waflib.Task.Task.runnable_status`
-       uses a hash value (from :py:class:`waflib.Task.Task.signature`) which is persistent from build to build. When the value changes,
-       the task has to be executed. The method :py:class:`waflib.Task.Task.post_run` will assign the task signature to the output
-       nodes (if present).
-       """
-       vars = []
-       """ConfigSet variables that should trigger a rebuild (class attribute used for :py:meth:`waflib.Task.Task.sig_vars`)"""
-
-       always_run = False
-       """Specify whether task instances must always be executed or not (class attribute)"""
-
-       shell = False
-       """Execute the command with the shell (class attribute)"""
-
-       def __init__(self, *k, **kw):
-               TaskBase.__init__(self, *k, **kw)
-
-               self.env = kw['env']
-               """:py:class:`waflib.ConfigSet.ConfigSet` object (make sure to provide one)"""
-
-               self.inputs  = []
-               """List of input nodes, which represent the files used by the task instance"""
-
-               self.outputs = []
-               """List of output nodes, which represent the files created by the task instance"""
-
-               self.dep_nodes = []
-               """List of additional nodes to depend on"""
-
-               self.run_after = set()
-               """Set of tasks that must be executed before this one"""
-
        def __str__(self):
                "string to display to the user"
                name = self.__class__.__name__
@@ -517,14 +491,14 @@ class Task(TaskBase):
 
                src_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.inputs])
                tgt_str = ' '.join([a.path_from(a.ctx.launch_node()) for a in self.outputs])
-               if self.outputs: sep = ' -> '
-               else: sep = ''
+               if self.outputs:
+                       sep = ' -> '
+               else:
+                       sep = ''
                return '%s: %s%s%s' % (self.__class__.__name__, src_str, sep, tgt_str)
 
        def keyword(self):
-               """
-               See :py:meth:`waflib.Task.TaskBase`
-               """
+               "Display keyword used to prettify the console outputs"
                name = self.__class__.__name__
                if name.endswith(('lib', 'program')):
                        return 'Linking'
@@ -581,8 +555,10 @@ class Task(TaskBase):
                :param inp: input nodes
                :type inp: node or list of nodes
                """
-               if isinstance(inp, list): self.inputs += inp
-               else: self.inputs.append(inp)
+               if isinstance(inp, list):
+                       self.inputs += inp
+               else:
+                       self.inputs.append(inp)
 
        def set_outputs(self, out):
                """
@@ -591,8 +567,10 @@ class Task(TaskBase):
                :param out: output nodes
                :type out: node or list of nodes
                """
-               if isinstance(out, list): self.outputs += out
-               else: self.outputs.append(out)
+               if isinstance(out, list):
+                       self.outputs += out
+               else:
+                       self.outputs.append(out)
 
        def set_run_after(self, task):
                """
@@ -601,7 +579,7 @@ class Task(TaskBase):
                :param task: task
                :type task: :py:class:`waflib.Task.Task`
                """
-               assert isinstance(task, TaskBase)
+               assert isinstance(task, Task)
                self.run_after.add(task)
 
        def signature(self):
@@ -650,13 +628,22 @@ class Task(TaskBase):
 
        def runnable_status(self):
                """
-               See :py:meth:`waflib.Task.TaskBase.runnable_status`
+               Returns the Task status
+
+               :return: a task state in :py:const:`waflib.Task.RUN_ME`,
+                       :py:const:`waflib.Task.SKIP_ME`, :py:const:`waflib.Task.CANCEL_ME` or :py:const:`waflib.Task.ASK_LATER`.
+               :rtype: int
                """
-               #return 0 # benchmarking
+               bld = self.generator.bld
+               if bld.is_install < 0:
+                       return SKIP_ME
 
                for t in self.run_after:
                        if not t.hasrun:
                                return ASK_LATER
+                       elif t.hasrun < SKIPPED:
+                               # a dependency has an error
+                               return CANCEL_ME
 
                # first compute the signature
                try:
@@ -665,7 +652,6 @@ class Task(TaskBase):
                        return ASK_LATER
 
                # compare the signature to a signature computed previously
-               bld = self.generator.bld
                key = self.uid()
                try:
                        prev_sig = bld.task_sigs[key]
@@ -733,10 +719,11 @@ class Task(TaskBase):
                                        continue
 
                                for v in d:
-                                       if isinstance(v, bld.root.__class__):
+                                       try:
                                                v = v.get_bld_sig()
-                                       elif hasattr(v, '__call__'):
-                                               v = v() # dependency is a function, call it
+                                       except AttributeError:
+                                               if hasattr(v, '__call__'):
+                                                       v = v() # dependency is a function, call it
                                        upd(v)
 
        def sig_vars(self):
@@ -869,10 +856,10 @@ if sys.hexversion > 0x3000000:
                try:
                        return self.uid_
                except AttributeError:
-                       m = Utils.md5(self.__class__.__name__.encode('iso8859-1', 'xmlcharrefreplace'))
+                       m = Utils.md5(self.__class__.__name__.encode('latin-1', 'xmlcharrefreplace'))
                        up = m.update
                        for x in self.inputs + self.outputs:
-                               up(x.abspath().encode('iso8859-1', 'xmlcharrefreplace'))
+                               up(x.abspath().encode('latin-1', 'xmlcharrefreplace'))
                        self.uid_ = m.digest()
                        return self.uid_
        uid.__doc__ = Task.uid.__doc__
@@ -889,9 +876,9 @@ def is_before(t1, t2):
                waflib.Task.is_before(t1, t2) # True
 
        :param t1: Task object
-       :type t1: :py:class:`waflib.Task.TaskBase`
+       :type t1: :py:class:`waflib.Task.Task`
        :param t2: Task object
-       :type t2: :py:class:`waflib.Task.TaskBase`
+       :type t2: :py:class:`waflib.Task.Task`
        """
        to_list = Utils.to_list
        for k in to_list(t2.ext_in):
@@ -911,27 +898,50 @@ def set_file_constraints(tasks):
        Updates the ``run_after`` attribute of all tasks based on the task inputs and outputs
 
        :param tasks: tasks
-       :type tasks: list of :py:class:`waflib.Task.TaskBase`
+       :type tasks: list of :py:class:`waflib.Task.Task`
        """
        ins = Utils.defaultdict(set)
        outs = Utils.defaultdict(set)
        for x in tasks:
-               for a in getattr(x, 'inputs', []) + getattr(x, 'dep_nodes', []):
-                       ins[id(a)].add(x)
-               for a in getattr(x, 'outputs', []):
-                       outs[id(a)].add(x)
+               for a in x.inputs:
+                       ins[a].add(x)
+               for a in x.dep_nodes:
+                       ins[a].add(x)
+               for a in x.outputs:
+                       outs[a].add(x)
 
        links = set(ins.keys()).intersection(outs.keys())
        for k in links:
                for a in ins[k]:
                        a.run_after.update(outs[k])
 
+
+class TaskGroup(object):
+       """
+       Wrap nxm task order constraints into a single object
+       to prevent the creation of large list/set objects
+
+       This is an optimization
+       """
+       def __init__(self, prev, next):
+               self.prev = prev
+               self.next = next
+               self.done = False
+
+       def get_hasrun(self):
+               for k in self.prev:
+                       if not k.hasrun:
+                               return NOT_RUN
+               return SUCCESS
+
+       hasrun = property(get_hasrun, None)
+
 def set_precedence_constraints(tasks):
        """
        Updates the ``run_after`` attribute of all tasks based on the after/before/ext_out/ext_in attributes
 
        :param tasks: tasks
-       :type tasks: list of :py:class:`waflib.Task.TaskBase`
+       :type tasks: list of :py:class:`waflib.Task.Task`
        """
        cstr_groups = Utils.defaultdict(list)
        for x in tasks:
@@ -957,9 +967,16 @@ def set_precedence_constraints(tasks):
                        else:
                                continue
 
-                       aval = set(cstr_groups[keys[a]])
-                       for x in cstr_groups[keys[b]]:
-                               x.run_after.update(aval)
+                       a = cstr_groups[keys[a]]
+                       b = cstr_groups[keys[b]]
+
+                       if len(a) < 2 or len(b) < 2:
+                               for x in b:
+                                       x.run_after.update(a)
+                       else:
+                               group = TaskGroup(set(a), set(b))
+                               for x in b:
+                                       x.run_after.add(group)
 
 def funex(c):
        """
@@ -1011,11 +1028,15 @@ def compile_fun_shell(line):
        app = parm.append
        for (var, meth) in extr:
                if var == 'SRC':
-                       if meth: app('tsk.inputs%s' % meth)
-                       else: app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
+                       if meth:
+                               app('tsk.inputs%s' % meth)
+                       else:
+                               app('" ".join([a.path_from(cwdx) for a in tsk.inputs])')
                elif var == 'TGT':
-                       if meth: app('tsk.outputs%s' % meth)
-                       else: app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
+                       if meth:
+                               app('tsk.outputs%s' % meth)
+                       else:
+                               app('" ".join([a.path_from(cwdx) for a in tsk.outputs])')
                elif meth:
                        if meth.startswith(':'):
                                if var not in dvars:
@@ -1043,8 +1064,10 @@ def compile_fun_shell(line):
                        if var not in dvars:
                                dvars.append(var)
                        app("p('%s')" % var)
-       if parm: parm = "%% (%s) " % (',\n\t\t'.join(parm))
-       else: parm = ''
+       if parm:
+               parm = "%% (%s) " % (',\n\t\t'.join(parm))
+       else:
+               parm = ''
 
        c = COMPILE_TEMPLATE_SHELL % (line, parm)
        Logs.debug('action: %s', c.strip().splitlines())
@@ -1136,7 +1159,7 @@ def compile_fun(line, shell=False):
        """
        Parses a string expression such as '${CC} ${SRC} -o ${TGT}' and returns a pair containing:
 
-       * The function created (compiled) for use as :py:meth:`waflib.Task.TaskBase.run`
+       * The function created (compiled) for use as :py:meth:`waflib.Task.Task.run`
        * The list of variables that must cause rebuilds when *env* data is modified
 
        for example::
@@ -1208,7 +1231,6 @@ def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[
                params['run'] = func
 
        cls = type(Task)(name, (Task,), params)
-       global classes
        classes[name] = cls
 
        if ext_in:
@@ -1222,21 +1244,6 @@ def task_factory(name, func=None, vars=None, color='GREEN', ext_in=[], ext_out=[
 
        return cls
 
+TaskBase = Task
+"Provided for compatibility reasons, TaskBase should not be used"
 
-def always_run(cls):
-       """
-       Deprecated Task class decorator (to be removed in waf 2.0)
-
-       Set all task instances of this class to be executed whenever a build is started
-       The task signature is calculated, but the result of the comparison between
-       task signatures is bypassed
-       """
-       Logs.warn('This decorator is deprecated, set always_run on the task class instead!')
-       cls.always_run = True
-       return cls
-
-def update_outputs(cls):
-       """
-       Obsolete, to be removed in waf 2.0
-       """
-       return cls
index 07e854b..1287058 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
 
 """
 Task generators
@@ -24,7 +24,7 @@ HEADER_EXTS = ['.h', '.hpp', '.hxx', '.hh']
 
 class task_gen(object):
        """
-       Instances of this class create :py:class:`waflib.Task.TaskBase` when
+       Instances of this class create :py:class:`waflib.Task.Task` when
        calling the method :py:meth:`waflib.TaskGen.task_gen.post` from the main thread.
        A few notes:
 
@@ -38,7 +38,7 @@ class task_gen(object):
        mappings = Utils.ordered_iter_dict()
        """Mappings are global file extension mappings that are retrieved in the order of definition"""
 
-       prec = Utils.defaultdict(list)
+       prec = Utils.defaultdict(set)
        """Dict that holds the precedence execution rules for task generator methods"""
 
        def __init__(self, *k, **kw):
@@ -52,7 +52,7 @@ class task_gen(object):
 
                The extra key/value elements passed in ``kw`` are set as attributes
                """
-               self.source = ''
+               self.source = []
                self.target = ''
 
                self.meths = []
@@ -80,12 +80,20 @@ class task_gen(object):
                        self.env = self.bld.env.derive()
                        self.path = self.bld.path # emulate chdir when reading scripts
 
-                       # provide a unique id
+                       # Provide a unique index per folder
+                       # This is part of a measure to prevent output file name collisions
+                       path = self.path.abspath()
                        try:
-                               self.idx = self.bld.idx[self.path] = self.bld.idx.get(self.path, 0) + 1
+                               self.idx = self.bld.idx[path] = self.bld.idx.get(path, 0) + 1
                        except AttributeError:
                                self.bld.idx = {}
-                               self.idx = self.bld.idx[self.path] = 1
+                               self.idx = self.bld.idx[path] = 1
+
+                       # Record the global task generator count
+                       try:
+                               self.tg_idx_count = self.bld.tg_idx_count = self.bld.tg_idx_count + 1
+                       except AttributeError:
+                               self.tg_idx_count = self.bld.tg_idx_count = 1
 
                for key, val in kw.items():
                        setattr(self, key, val)
@@ -190,11 +198,12 @@ class task_gen(object):
                tmp = []
                for a in keys:
                        for x in prec.values():
-                               if a in x: break
+                               if a in x:
+                                       break
                        else:
                                tmp.append(a)
 
-               tmp.sort()
+               tmp.sort(reverse=True)
 
                # topological sort
                out = []
@@ -214,13 +223,13 @@ class task_gen(object):
                                                        break
                                        else:
                                                tmp.append(x)
+                                               tmp.sort(reverse=True)
 
                if prec:
                        buf = ['Cycle detected in the method execution:']
                        for k, v in prec.items():
                                buf.append('- %s after %s' % (k, [x for x in v if x in prec]))
                        raise Errors.WafError('\n'.join(buf))
-               out.reverse()
                self.meths = out
 
                # then we run the methods in order
@@ -268,7 +277,7 @@ class task_gen(object):
                :param tgt: output nodes
                :type tgt: list of :py:class:`waflib.Tools.Node.Node`
                :return: A task object
-               :rtype: :py:class:`waflib.Task.TaskBase`
+               :rtype: :py:class:`waflib.Task.Task`
                """
                task = Task.classes[name](env=self.env.derive(), generator=self)
                if src:
@@ -434,9 +443,7 @@ def before_method(*k):
        def deco(func):
                setattr(task_gen, func.__name__, func)
                for fun_name in k:
-                       if not func.__name__ in task_gen.prec[fun_name]:
-                               task_gen.prec[fun_name].append(func.__name__)
-                               #task_gen.prec[fun_name].sort()
+                       task_gen.prec[func.__name__].add(fun_name)
                return func
        return deco
 before = before_method
@@ -463,9 +470,7 @@ def after_method(*k):
        def deco(func):
                setattr(task_gen, func.__name__, func)
                for fun_name in k:
-                       if not fun_name in task_gen.prec[func.__name__]:
-                               task_gen.prec[func.__name__].append(fun_name)
-                               #task_gen.prec[func.__name__].sort()
+                       task_gen.prec[fun_name].add(func.__name__)
                return func
        return deco
 after = after_method
@@ -491,14 +496,11 @@ def extension(*k):
                return func
        return deco
 
-# ---------------------------------------------------------------
-# The following methods are task generator methods commonly used
-# they are almost examples, the rest of waf core does not depend on them
-
 @taskgen_method
 def to_nodes(self, lst, path=None):
        """
-       Converts the input list into a list of nodes.
+       Flatten the input list of string/nodes/lists into a list of nodes.
+
        It is used by :py:func:`waflib.TaskGen.process_source` and :py:func:`waflib.TaskGen.process_rule`.
        It is designed for source files, for folders, see :py:func:`waflib.Tools.ccroot.to_incnodes`:
 
@@ -515,14 +517,16 @@ def to_nodes(self, lst, path=None):
        if isinstance(lst, Node.Node):
                lst = [lst]
 
-       # either a list or a string, convert to a list of nodes
        for x in Utils.to_list(lst):
                if isinstance(x, str):
                        node = find(x)
-               else:
+               elif hasattr(x, 'name'):
                        node = x
+               else:
+                       tmp.extend(self.to_nodes(x))
+                       continue
                if not node:
-                       raise Errors.WafError("source not found: %r in %r" % (x, self))
+                       raise Errors.WafError('source not found: %r in %r' % (x, self))
                tmp.append(node)
        return tmp
 
@@ -549,6 +553,24 @@ def process_rule(self):
 
                def build(bld):
                        bld(rule='cp ${SRC} ${TGT}', source='wscript', target='bar.txt')
+
+       Main attributes processed:
+
+       * rule: command to execute, it can be a tuple of strings for multiple commands
+       * chmod: permissions for the resulting files (integer value such as Utils.O755)
+       * shell: set to False to execute the command directly (default is True to use a shell)
+       * scan: scanner function
+       * vars: list of variables to trigger rebuilts, such as CFLAGS
+       * cls_str: string to display when executing the task
+       * cls_keyword: label to display when executing the task
+       * cache_rule: by default, try to re-use similar classes, set to False to disable
+       * source: list of Node or string objects representing the source files required by this task
+       * target: list of Node or string objects representing the files that this task creates
+       * cwd: current working directory (Node or string)
+       * stdout: standard output, set to None to prevent waf from capturing the text
+       * stderr: standard error, set to None to prevent waf from capturing the text
+       * timeout: timeout for command execution (Python 3)
+       * always: whether to always run the command (False by default)
        """
        if not getattr(self, 'rule', None):
                return
@@ -617,17 +639,21 @@ def process_rule(self):
                                return [nodes, []]
                        cls.scan = scan
 
-               # TODO use these values in the cache key if provided
-               # (may cause excessive caching)
-               for x in ('after', 'before', 'ext_in', 'ext_out'):
-                       setattr(cls, x, getattr(self, x, []))
-
                if use_cache:
                        cache[key] = cls
 
        # now create one instance
        tsk = self.create_task(name)
 
+       for x in ('after', 'before', 'ext_in', 'ext_out'):
+               setattr(tsk, x, getattr(self, x, []))
+
+       if hasattr(self, 'stdout'):
+               tsk.stdout = self.stdout
+
+       if hasattr(self, 'stderr'):
+               tsk.stderr = self.stderr
+
        if getattr(self, 'timeout', None):
                tsk.timeout = self.timeout
 
@@ -663,7 +689,6 @@ def process_rule(self):
                # methods during instance attribute look-up."
                tsk.run = functools.partial(tsk.run, tsk)
 
-
 @feature('seq')
 def sequence_order(self):
        """
@@ -721,6 +746,8 @@ class subst_pc(Task.Task):
                if getattr(self.generator, 'is_copy', None):
                        for i, x in enumerate(self.outputs):
                                x.write(self.inputs[i].read('rb'), 'wb')
+                               stat = os.stat(self.inputs[i].abspath()) # Preserve mtime of the copy
+                               os.utime(self.outputs[i].abspath(), (stat.st_atime, stat.st_mtime))
                        self.force_permissions()
                        return None
 
@@ -730,11 +757,11 @@ class subst_pc(Task.Task):
                                self.force_permissions()
                        return ret
 
-               code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
+               code = self.inputs[0].read(encoding=getattr(self.generator, 'encoding', 'latin-1'))
                if getattr(self.generator, 'subst_fun', None):
                        code = self.generator.subst_fun(self, code)
                        if code is not None:
-                               self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
+                               self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
                        self.force_permissions()
                        return None
 
@@ -749,7 +776,6 @@ class subst_pc(Task.Task):
                                lst.append(g(1))
                                return "%%(%s)s" % g(1)
                        return ''
-               global re_m4
                code = getattr(self.generator, 're_m4', re_m4).sub(repl, code)
 
                try:
@@ -765,12 +791,14 @@ class subst_pc(Task.Task):
                                d[x] = tmp
 
                code = code % d
-               self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'ISO8859-1'))
+               self.outputs[0].write(code, encoding=getattr(self.generator, 'encoding', 'latin-1'))
                self.generator.bld.raw_deps[self.uid()] = lst
 
                # make sure the signature is updated
-               try: delattr(self, 'cache_sig')
-               except AttributeError: pass
+               try:
+                       delattr(self, 'cache_sig')
+               except AttributeError:
+                       pass
 
                self.force_permissions()
 
@@ -783,9 +811,9 @@ class subst_pc(Task.Task):
                upd = self.m.update
 
                if getattr(self.generator, 'fun', None):
-                       upd(Utils.h_fun(self.generator.fun))
+                       upd(Utils.h_fun(self.generator.fun).encode())
                if getattr(self.generator, 'subst_fun', None):
-                       upd(Utils.h_fun(self.generator.subst_fun))
+                       upd(Utils.h_fun(self.generator.subst_fun).encode())
 
                # raw_deps: persistent custom values returned by the scanner
                vars = self.generator.bld.raw_deps.get(self.uid(), [])
@@ -867,21 +895,17 @@ def process_subst(self):
                if not a:
                        raise Errors.WafError('could not find %r for %r' % (x, self))
 
-               has_constraints = False
                tsk = self.create_task('subst', a, b)
                for k in ('after', 'before', 'ext_in', 'ext_out'):
                        val = getattr(self, k, None)
                        if val:
-                               has_constraints = True
                                setattr(tsk, k, val)
 
                # paranoid safety measure for the general case foo.in->foo.h with ambiguous dependencies
-               if not has_constraints:
-                       global HEADER_EXTS
-                       for xt in HEADER_EXTS:
-                               if b.name.endswith(xt):
-                                       tsk.before = [k for k in ('c', 'cxx') if k in Task.classes]
-                                       break
+               for xt in HEADER_EXTS:
+                       if b.name.endswith(xt):
+                               tsk.ext_in = tsk.ext_in + ['.h']
+                               break
 
                inst_to = getattr(self, 'install_path', None)
                if inst_to:
@@ -889,3 +913,4 @@ def process_subst(self):
                                install_from=b, chmod=getattr(self, 'chmod', Utils.O644))
 
        self.source = []
+
index 8766ecb..94cf66f 100644 (file)
@@ -4,4 +4,4 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2005-2016 (ita)
+# Thomas Nagy, 2005-2018 (ita)
index 2ee1a08..bf5220d 100644 (file)
@@ -4,7 +4,7 @@
 
 #!/usr/bin/env python
 # encoding: utf-8
-# Thomas Nagy, 2006-2016 (ita)
+# Thomas Nagy, 2006-2018 (ita)
 # Ralf Habacker, 2006 (rh)
 
 """