Commits

Eric Anderson committed 93fd001 Draft Merge

Merged upstream changes

Comments (0)

Files changed (120)

QMTest/TestCmd.py

                        combine = 0,
                        universal_newlines = 1,
                        timeout = None):
+        self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0)
         self._cwd = os.getcwd()
         self.description_set(description)
         self.program_set(program)
         self.condition = 'no_result'
         self.workdir_set(workdir)
         self.subdir(subdir)
+        self.script_srcdir = None
 
     def __del__(self):
         self.cleanup()
     def command_args(self, program = None,
                            interpreter = None,
                            arguments = None):
-        if program:
-            if isinstance(program, str) and not os.path.isabs(program):
-                program = os.path.join(self._cwd, program)
+        if not self.external:
+            if program:
+                if isinstance(program, str) and not os.path.isabs(program):
+                    program = os.path.join(self._cwd, program)
+            else:
+                program = self.program
+                if not interpreter:
+                    interpreter = self.interpreter
         else:
-            program = self.program
-            if not interpreter:
-                interpreter = self.interpreter
+            if not program:
+                program = self.program
+                if not interpreter:
+                    interpreter = self.interpreter
         if not isinstance(program, (list, tuple)):
             program = [program]
         cmd = list(program)
     def program_set(self, program):
         """Set the executable program or script to be tested.
         """
-        if program and not os.path.isabs(program):
-            program = os.path.join(self._cwd, program)
+        if not self.external:
+            if program and not os.path.isabs(program):
+                program = os.path.join(self._cwd, program)
         self.program = program
 
     def read(self, file, mode = 'rb'):
         self.timeout = timeout
         self.timer = None
 
+    def parse_path(self, path, suppress_current=False):
+        """Return a list with the single path components of path.
+        """
+        head, tail = os.path.split(path)
+        result = []
+        if not tail:
+            if head == path:
+                return [head]
+        else:
+            result.append(tail)
+        head, tail = os.path.split(head)
+        while head and tail:
+            result.append(tail)
+            head, tail = os.path.split(head)
+        result.append(head or tail)
+        result.reverse()
+        
+        return result        
+
+    def dir_fixture(self, srcdir, dstdir=None):
+        """Copies the contents of the specified folder srcdir from
+        the directory of the called  script, to the current
+        working directory.
+        The srcdir name may be a list, in which case the elements are
+        concatenated with the os.path.join() method.  The dstdir is
+        assumed to be under the temporary working directory, it gets
+        created automatically, if it does not already exist.
+        """
+        if srcdir and self.script_srcdir and not os.path.isabs(srcdir):
+            spath = os.path.join(self.script_srcdir, srcdir)
+        else:
+            spath = srcdir
+        if dstdir:
+            dstdir = self.canonicalize(dstdir)
+        else:
+            dstdir = '.'            
+
+        if dstdir != '.' and not os.path.exists(dstdir):
+            dstlist = self.parse_path(dstdir)
+            if len(dstlist) > 0 and dstlist[0] == ".":
+                dstlist = dstlist[1:]
+            for idx in range(len(dstlist)):
+                self.subdir(dstlist[:idx+1])
+
+        if dstdir and self.workdir:
+            dstdir = os.path.join(self.workdir, dstdir)
+
+        for entry in os.listdir(spath):
+            epath = os.path.join(spath, entry)
+            dpath = os.path.join(dstdir, entry)
+            if os.path.isdir(epath):
+                # Copy the subfolder
+                shutil.copytree(epath, dpath)
+            else:
+                shutil.copy(epath, dpath)
+
+    def file_fixture(self, srcfile, dstfile=None):
+        """Copies the file srcfile from the directory of
+        the called script, to the current working directory.
+        The dstfile is assumed to be under the temporary working
+        directory unless it is an absolute path name.
+        If dstfile is specified its target directory gets created
+        automatically, if it does not already exist.
+        """
+        srcpath, srctail = os.path.split(srcfile)
+        if srcpath:
+            if self.script_srcdir and not os.path.isabs(srcpath):
+                spath = os.path.join(self.script_srcdir, srcfile)
+            else:
+                spath = srcfile
+        else:
+            spath = os.path.join(self.script_srcdir, srcfile)
+        if not dstfile:
+            if srctail:
+                dpath = os.path.join(self.workdir, srctail)
+            else:
+                return
+        else:
+            dstpath, dsttail = os.path.split(dstfile)
+            if dstpath:
+                if not os.path.exists(os.path.join(self.workdir, dstpath)):
+                    dstlist = self.parse_path(dstpath)
+                    if len(dstlist) > 0 and dstlist[0] == ".":
+                        dstlist = dstlist[1:]
+                    for idx in range(len(dstlist)):
+                        self.subdir(dstlist[:idx+1])
+                    
+            dpath = os.path.join(self.workdir, dstfile)
+        shutil.copy(spath, dpath)
+
     def start(self, program = None,
                     interpreter = None,
                     arguments = None,
         The specified program will have the original directory
         prepended unless it is enclosed in a [list].
         """
+        if self.external:
+            if not program:
+                program = self.program
+            if not interpreter:
+                interpreter = self.interpreter
+        
         if chdir:
             oldcwd = os.getcwd()
             if not os.path.isabs(chdir):

QMTest/TestCmdTests.py

                                       ['a\\n', 'c\\n', 'd\\n', 'e\\n', 'f2\\n'])
 result = list(result)
 expect = [
-    '***  \\n',
-    '---  \\n',
+    '*** \\n',
+    '--- \\n',
     '***************\\n',
     '*** 1,5 ****\\n',
     '  a\\n',
                                       ['a\\n', 'c\\n', 'd\\n', 'e\\n', 'f2\\n'])
 result = list(result)
 expect = [
-    '---  \\n',
-    '+++  \\n',
+    '--- \\n',
+    '+++ \\n',
     '@@ -1,5 +1,5 @@\\n',
     ' a\\n',
     '-b\\n',

QMTest/TestCommon.py

 import os
 import stat
 import sys
+import glob
 
 try:
     from collections import UserList
 def is_List(e):
     return isinstance(e, (list, UserList))
 
+def is_Tuple(e):
+    return isinstance(e, tuple)
+
+def is_Sequence(e):
+    return (not hasattr(e, "strip") and
+            hasattr(e, "__getitem__") or
+            hasattr(e, "__iter__"))
+
 def is_writable(f):
     mode = os.stat(f)[stat.ST_MODE]
     return mode & stat.S_IWUSR
             print "Missing files: `%s'" % "', `".join(missing)
             self.fail_test(missing)
 
+    def must_exist_one_of(self, files):
+        """Ensures that at least one of the specified file(s) exists.
+        The filenames can be given as a list, where each entry may be
+        a single path string, or a tuple of folder names and the final
+        filename that get concatenated.
+        Supports wildcard names like 'foo-1.2.3-*.rpm'.
+        Exits FAILED if none of the files exists.
+        """
+        missing = []
+        for x in files:
+            if is_List(x) or is_Tuple(x):
+                xpath = os.path.join(*x)
+            else:
+                xpath = is_Sequence(x) and os.path.join(x) or x
+            if glob.glob(xpath):
+                return
+            missing.append(xpath)
+        print "Missing one of: `%s'" % "', `".join(missing)
+        self.fail_test(missing)
+
     def must_match(self, file, expect, mode = 'rb'):
         """Matches the contents of the specified file (first argument)
         against the expected contents (second argument).  The expected
             print "Unexpected files exist: `%s'" % "', `".join(existing)
             self.fail_test(existing)
 
+    def must_not_exist_any_of(self, files):
+        """Ensures that none of the specified file(s) exists.
+        The filenames can be given as a list, where each entry may be
+        a single path string, or a tuple of folder names and the final
+        filename that get concatenated.
+        Supports wildcard names like 'foo-1.2.3-*.rpm'.
+        Exits FAILED if any of the files exists.
+        """
+        existing = []
+        for x in files:
+            if is_List(x) or is_Tuple(x):
+                xpath = os.path.join(*x)
+            else:
+                xpath = is_Sequence(x) and os.path.join(x) or x
+            if glob.glob(xpath):
+                existing.append(xpath)
+        if existing:
+            print "Unexpected files exist: `%s'" % "', `".join(existing)
+            self.fail_test(existing)
 
     def must_not_be_writable(self, *files):
         """Ensures that the specified file(s) exist and are not writable.

QMTest/TestCommonTests.py

         stderr = run_env.stderr()
         assert stderr == "PASSED\n", stderr
 
+class must_exist_one_of_TestCase(TestCommonTestCase):
+    def test_success(self):
+        """Test must_exist_one_of():  success"""
+        run_env = self.run_env
 
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.write('file1', "file1\\n")
+        tc.must_exist_one_of(['file1'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_failure(self):
+        """Test must_exist_one_of():  failure"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.must_exist_one_of(['file1'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "Missing one of: `file1'\n", stdout
+        stderr = run_env.stderr()
+        assert stderr.find("FAILED") != -1, stderr
+
+    def test_files_specified_as_list(self):
+        """Test must_exist_one_of():  files specified as list"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.write('file1', "file1\\n")
+        tc.must_exist_one_of(['file2', 'file1'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_files_specified_with_wildcards(self):
+        """Test must_exist_one_of():  files specified with wildcards"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.write('file7', "file7\\n")
+        tc.must_exist_one_of(['file?'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_file_given_as_list(self):
+        """Test must_exist_one_of():  file given as list"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.subdir('sub')
+        tc.write(['sub', 'file1'], "sub/file1\\n")
+        tc.must_exist_one_of(['file2',
+                              ['sub', 'file1']])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_file_given_as_sequence(self):
+        """Test must_exist_one_of():  file given as sequence"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.subdir('sub')
+        tc.write(['sub', 'file1'], "sub/file1\\n")
+        tc.must_exist_one_of(['file2',
+                              ('sub', 'file1')])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
 
 class must_match_TestCase(TestCommonTestCase):
     def test_success(self):
         stderr = run_env.stderr()
         assert stderr.find("FAILED") != -1, stderr
 
+class must_not_exist_any_of_TestCase(TestCommonTestCase):
+    def test_success(self):
+        """Test must_not_exist_any_of():  success"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.must_not_exist_any_of(['file1'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_failure(self):
+        """Test must_not_exist_any_of():  failure"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.write('file1', "file1\\n")
+        tc.must_not_exist_any_of(['file1'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "Unexpected files exist: `file1'\n", stdout
+        stderr = run_env.stderr()
+        assert stderr.find("FAILED") != -1, stderr
+
+    def test_files_specified_as_list(self):
+        """Test must_not_exist_any_of():  files specified as list"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.must_not_exist_any_of(['file2', 'file1'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_files_specified_with_wildcards(self):
+        """Test must_not_exist_any_of():  files specified with wildcards"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.write('file7', "file7\\n")
+        tc.must_not_exist_any_of(['files?'])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_file_given_as_list(self):
+        """Test must_not_exist_any_of():  file given as list"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.subdir('sub')
+        tc.write(['sub', 'file1'], "sub/file1\\n")
+        tc.must_not_exist_any_of(['file2',
+                              ['sub', 'files*']])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
+
+    def test_file_given_as_sequence(self):
+        """Test must_not_exist_any_of():  file given as sequence"""
+        run_env = self.run_env
+
+        script = lstrip("""\
+        from TestCommon import TestCommon
+        tc = TestCommon(workdir='')
+        tc.subdir('sub')
+        tc.write(['sub', 'file1'], "sub/file1\\n")
+        tc.must_not_exist_any_of(['file2',
+                              ('sub', 'files?')])
+        tc.pass_test()
+        """)
+        run_env.run(program=sys.executable, stdin=script)
+        stdout = run_env.stdout()
+        assert stdout == "", stdout
+        stderr = run_env.stderr()
+        assert stderr == "PASSED\n", stderr
 
 class run_TestCase(TestCommonTestCase):
     def test_argument_handling(self):
         must_contain_exactly_lines_TestCase,
         must_contain_lines_TestCase,
         must_exist_TestCase,
+        must_exist_one_of_TestCase,
         must_match_TestCase,
         must_not_be_writable_TestCase,
         must_not_contain_TestCase,
         must_not_contain_any_line_TestCase,
         must_not_contain_lines_TestCase,
         must_not_exist_TestCase,
+        must_not_exist_any_of_TestCase,
         run_TestCase,
         start_TestCase,
         skip_test_TestCase,

QMTest/TestRuntest.py

 
         dirs = [os.environ.get('SCONS_RUNTEST_DIR', orig_cwd)]
         
-        spe = os.environ.get('SCONS_SOURCE_PATH_EXECUTABLE', orig_cwd)
-        for d in spe.split(os.pathsep):
-            dirs.append(os.path.join(d, 'build'))
-            dirs.append(d)
-
         for thing in things_to_copy:
             for dir in dirs:
                 t = os.path.join(dir, thing)
 
         self.program_set(self.workpath(kw['program']))
 
-        for key in os.environ.keys():
-            if key[:5] == 'AEGIS':
-                os.environ[key] = ''
-
         os.environ['PYTHONPATH'] = ''
-        os.environ['SCONS_SOURCE_PATH_EXECUTABLE'] = ''
 
     def write_fake_scons_source_tree(self):
         os.mkdir('src')

QMTest/TestSCons.py

         is not necessary.
         """
         self.orig_cwd = os.getcwd()
-        try:
-            script_dir = os.environ['SCONS_SCRIPT_DIR']
-        except KeyError:
-            pass
-        else:
-            os.chdir(script_dir)
+        self.external = os.environ.get('SCONS_EXTERNAL_TEST', 0)
+
+        if not self.external:
+            try:
+                script_dir = os.environ['SCONS_SCRIPT_DIR']
+            except KeyError:
+                pass
+            else:
+                os.chdir(script_dir)
         if 'program' not in kw:
             kw['program'] = os.environ.get('SCONS')
             if not kw['program']:
-                if os.path.exists('scons'):
+                if not self.external:
+                    if os.path.exists('scons'):
+                        kw['program'] = 'scons'
+                    else:
+                        kw['program'] = 'scons.py'
+                else:
                     kw['program'] = 'scons'
-                else:
-                    kw['program'] = 'scons.py'
-            elif not os.path.isabs(kw['program']):
+                    kw['interpreter'] = ''
+            elif not self.external and not os.path.isabs(kw['program']):
                 kw['program'] = os.path.join(self.orig_cwd, kw['program'])
         if 'interpreter' not in kw and not os.environ.get('SCONS_EXEC'):
             kw['interpreter'] = [python, '-tt']
 
         TestCommon.__init__(self, **kw)
 
-        import SCons.Node.FS
-        if SCons.Node.FS.default_fs is None:
-            SCons.Node.FS.default_fs = SCons.Node.FS.FS()
+        if not self.external:
+            import SCons.Node.FS
+            if SCons.Node.FS.default_fs is None:
+                SCons.Node.FS.default_fs = SCons.Node.FS.FS()
+
+        try:
+            self.script_srcdir = os.environ['PYTHON_SCRIPT_DIR']
+        except KeyError:
+            pass
 
     def Environment(self, ENV=None, *args, **kw):
         """
         Return a construction Environment that optionally overrides
         the default external environment with the specified ENV.
         """
-        import SCons.Environment
-        import SCons.Errors
-        if not ENV is None:
-            kw['ENV'] = ENV
-        try:
-            return SCons.Environment.Environment(*args, **kw)
-        except (SCons.Errors.UserError, SCons.Errors.InternalError):
-            return None
+        if not self.external:
+            import SCons.Environment
+            import SCons.Errors
+            if not ENV is None:
+                kw['ENV'] = ENV
+            try:
+                return SCons.Environment.Environment(*args, **kw)
+            except (SCons.Errors.UserError, SCons.Errors.InternalError):
+                return None
+
+        return None
 
     def detect(self, var, prog=None, ENV=None, norm=None):
         """
         used as prog.
         """
         env = self.Environment(ENV)
-        v = env.subst('$'+var)
-        if not v:
-            return None
-        if prog is None:
-            prog = v
-        if v != prog:
-            return None
-        result = env.WhereIs(prog)
-        if norm and os.sep != '/':
-            result = result.replace(os.sep, '/')
-        return result
+        if env:
+            v = env.subst('$'+var)
+            if not v:
+                return None
+            if prog is None:
+                prog = v
+            if v != prog:
+                return None
+            result = env.WhereIs(prog)
+            if norm and os.sep != '/':
+                result = result.replace(os.sep, '/')
+            return result
+        
+        return self.where_is(prog)
 
     def detect_tool(self, tool, prog=None, ENV=None):
         """
     def where_is(self, prog, path=None):
         """
         Given a program, search for it in the specified external PATH,
-        or in the actual external PATH is none is specified.
+        or in the actual external PATH if none is specified.
         """
-        import SCons.Environment
-        env = SCons.Environment.Environment()
         if path is None:
             path = os.environ['PATH']
-        return env.WhereIs(prog, path)
+        if self.external:
+            if isinstance(prog, str):
+                prog = [prog]
+            import stat
+            paths = path.split(os.pathsep)
+            for p in prog:
+                for d in paths:
+                    f = os.path.join(d, p)
+                    if os.path.isfile(f):
+                        try:
+                            st = os.stat(f)
+                        except OSError:
+                            # os.stat() raises OSError, not IOError if the file
+                            # doesn't exist, so in this case we let IOError get
+                            # raised so as to not mask possibly serious disk or
+                            # network issues.
+                            continue
+                        if stat.S_IMODE(st[stat.ST_MODE]) & 0111:
+                            return os.path.normpath(f)
+        else:
+            import SCons.Environment
+            env = SCons.Environment.Environment()
+            return env.WhereIs(prog, path)
+
+        return None
 
     def wrap_stdout(self, build_str = "", read_str = "", error = 0, cleaning = 0):
         """Wraps standard output string(s) in the normal
         Initialize with a default external environment that uses a local
         Java SDK in preference to whatever's found in the default PATH.
         """
-        try:
-            return self._java_env[version]['ENV']
-        except AttributeError:
-            self._java_env = {}
-        except KeyError:
-            pass
+        if not self.external:
+            try:
+                return self._java_env[version]['ENV']
+            except AttributeError:
+                self._java_env = {}
+            except KeyError:
+                pass
+    
+            import SCons.Environment
+            env = SCons.Environment.Environment()
+            self._java_env[version] = env
+    
+    
+            if version:
+                patterns = [
+                    '/usr/java/jdk%s*/bin'    % version,
+                    '/usr/lib/jvm/*-%s*/bin' % version,
+                    '/usr/local/j2sdk%s*/bin' % version,
+                ]
+                java_path = self.paths(patterns) + [env['ENV']['PATH']]
+            else:
+                patterns = [
+                    '/usr/java/latest/bin',
+                    '/usr/lib/jvm/*/bin',
+                    '/usr/local/j2sdk*/bin',
+                ]
+                java_path = self.paths(patterns) + [env['ENV']['PATH']]
+    
+            env['ENV']['PATH'] = os.pathsep.join(java_path)
+            return env['ENV']
 
-        import SCons.Environment
-        env = SCons.Environment.Environment()
-        self._java_env[version] = env
-
-
-        if version:
-            patterns = [
-                '/usr/java/jdk%s*/bin'    % version,
-                '/usr/lib/jvm/*-%s*/bin' % version,
-                '/usr/local/j2sdk%s*/bin' % version,
-            ]
-            java_path = self.paths(patterns) + [env['ENV']['PATH']]
-        else:
-            patterns = [
-                '/usr/java/latest/bin',
-                '/usr/lib/jvm/*/bin',
-                '/usr/local/j2sdk*/bin',
-            ]
-            java_path = self.paths(patterns) + [env['ENV']['PATH']]
-
-        env['ENV']['PATH'] = os.pathsep.join(java_path)
-        return env['ENV']
-
+        return None
+        
     def java_where_includes(self,version=None):
         """
         Return java include paths compiling java jni code
         import sys
         if not version:
             version=''
-            frame = '/System/Library/Frameworks/JavaVM.framework/Headers/jni.h'
+            jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Headers/jni.h',
+                        '/usr/lib/jvm/default-java/include/jni.h']
         else:
-            frame = '/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h'%version
-        jni_dirs = ['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version,
-                    '/usr/java/jdk%s*/include/jni.h'%version,
-		    frame,
-                    ]
+            jni_dirs = ['/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/jni.h'%version]
+        jni_dirs.extend(['/usr/lib/jvm/java-*-sun-%s*/include/jni.h'%version,
+                         '/usr/lib/jvm/java-%s*-openjdk/include/jni.h'%version,
+                         '/usr/java/jdk%s*/include/jni.h'%version])
         dirs = self.paths(jni_dirs)
         if not dirs:
             return None

QMTest/test-framework.rst

+=======================
+SCons Testing Framework
+=======================
+
+SCons uses extensive automated tests to try to ensure quality. The primary goal
+is that users should be able to upgrade from version to version without any surprise
+changes in behavior.
+
+In general, no change goes into SCons unless it has one or more new or modified
+tests that demonstrably exercise the bug being fixed or the feature being added.
+There are exceptions to this guideline, but they should be just that, ''exceptions''.
+When in doubt, make sure it's tested.
+
+Test Organization
+=================
+
+There are three types of SCons tests:
+
+*End-to-End Tests*
+  End-to-end tests of SCons are all Python scripts (``*.py``) underneath
+  the ``test/`` subdirectory.  They use the test infrastructure modules in the
+  ``QMTest`` subdirectory.
+
+*Unit Tests*
+  Unit tests for individual SCons modules live underneath the
+  ``src/engine/`` subdirectory and are the same base name as the module
+  with ``Tests.py`` appended--for example, the unit tests for the
+  ``Builder.py`` module are in the ``BuilderTests.py`` script.
+
+*External Tests*
+  For the support of external Tools (in the form of packages, preferably), the
+  testing framework got extended, such that it can run in standalone mode.
+  You can start it from the top-level folder of your Tool's source tree,
+  where it then finds all Python scripts (``*.py``) underneath the
+  local ``test/`` directory.
+  This implies that Tool tests have to be kept in a folder named ``test``,
+  like for the SCons core.
+  
+
+Contrasting End-to-End and Unit Tests
+#####################################
+
+In general, anything that we've put into an end-to-end test script should
+be considered a hardened part of the interface (that is, it's something
+that a user might do) and should not be broken.  Unit tests are now
+considered more malleable, more for testing internal interfaces that
+can change so long as we don't break users' ``SConscript`` files.  (This
+wasn't always the case, and there's a lot of meaty code in many of the
+unit test scripts that does, in fact, capture external interface
+behavior.  In general, we should try to move those things to end-to-end
+scripts as we find them.)
+
+It's more difficult to debug end-to-end tests.  You can actually go
+straight into the Python debugger on the unit test scripts by using the
+``runtest.py --pdb`` option, but the end-to-end tests treat an SCons
+invocation as a "black box" and just look for external effects.
+Simple ``print`` statements within the SCons code itself often don't help
+debug end-to-end because they end up in SCons output that gets compared
+against expected output and cause a test failure.  Probably the most
+effective technique is to use the internal ``SCons.Debug.Trace()`` function,
+which prints output to ``/dev/tty`` on Linux/UNIX systems and ``con`` on
+Windows systems, so you can see what's going on.
+
+Naming conventions
+##################
+
+The end-to-end tests, more or less, stick to the following naming conventions:
+
+1. All tests end with a .py suffix.
+
+2. In the *General* form we use
+
+   ``Feature.py``
+       for the test of a specified feature; try to
+       keep this description reasonably short
+
+   ``Feature-x.py``
+       for the test of a specified feature using
+       option ``x``
+
+3. The *command line option* tests take the form
+
+   ``option-x.py``
+       for a lower-case single-letter option
+
+   ``option--X.py``
+       upper-case single-letter option
+       (with an extra hyphen, so the file names will
+       be unique on case-insensitive systems)
+
+   ``option--lo.py``  
+       long option; abbreviate the long
+       option name to a few characters
+
+
+Running Tests
+=============
+
+The standard set of SCons tests are run from the top-level source directory
+by the ``runtest.py`` script.
+There is a ``--qmtest`` option that checks whether the ``QMTest`` package
+is installed on your system. If it can be found, then the ``runtest.py`` script
+will use it to carry out the tests.
+
+Help is available through the ``-h`` option:
+
+::
+
+  $ python runtest.py -h
+
+To simply run all the tests, use the ``-a`` option:
+
+::
+
+  $ python runtest.py -a
+
+By default, ``runtest.py`` prints a count and percentage message for each test
+case, along with the name of the test file.
+If you need the output to be more silent, have a look at the ``-q``, ``-s`` and
+``-k`` options.
+
+You may specifically list one or more tests to be run:
+
+::
+
+  $ python runtest.py src/engine/SCons/BuilderTests.py
+  $ python runtest.py test/option-j.py test/Program.py
+
+Folder names are allowed arguments as well, so you can do a
+
+::
+
+  $ python runtest.py test/SWIG
+
+to run all SWIG tests only.
+
+You can also use the ``-f`` option to execute just the tests listed in a specified
+text file:
+
+::
+
+  $ cat testlist.txt
+  test/option-j.py
+  test/Program.py
+  $ python runtest.py -f testlist.txt
+
+
+One test must be listed per line, and any lines that begin with '#'
+will be ignored (the intent being to allow you, for example,
+to comment out tests that
+are currently passing and then uncomment all of the tests in the file
+for a final validation run).
+
+If more than one test is run, the ``runtest.py`` script prints a summary
+of how many tests passed, failed, or yielded no result, and lists any
+unsuccessful tests.
+
+The above invocations all test directly the files underneath the ``src/``
+subdirectory, and do not require that a packaging build be performed first.
+The ``runtest.py`` script supports additional options to run tests against
+unpacked packages in the ``build/test-*/`` subdirectories.
+
+If you are testing a separate Tool outside of the SCons source tree, you have
+to call the ``runtest.py`` script in *external* (stand-alone) mode::
+
+  $ python ~/scons/runtest.py -e -a
+
+.  This ensures that the testing framework doesn't try to access SCons classes
+needed for some of the *internal* test cases.
+
+Note, that the actual tests are carried out in a temporary folder each, which gets
+deleted afterwards. This ensures that your source directories don't get clobbered
+with temporary files from the test runs. It also means that you can't simply change
+into a folder to "debug things" after a test has gone wrong. For a way around this,
+check out the ``PRESERVE`` environment variable. It can be seen in action in
+`How to convert old tests`_ below.
+
+Not Running Tests
+=================
+
+If you simply want to check which tests would get executed, you can call the
+``runtest.py`` script with the ``-l`` option::
+
+  $ python runtest.py -l
+
+Then there is also the ``-n`` option, which prints the command line for each
+single test, but doesn't actually execute them::
+
+  $ python runtest.py -n
+
+Finding Tests
+=============
+
+When started in *standard* mode
+
+::
+
+  $ python runtest.py -a
+
+
+, ``runtest.py`` assumes that it is run from the SCons top-level source directory.
+It then dives into the ``src`` and ``test`` folders, where it tries to find filenames
+
+    ``*Test.py``
+        for the ``src`` directory, and
+  
+    ``*.py``
+        for the ``test`` folder.
+
+When using fixtures, you may quickly end up in a position where you have supporting
+Python script files in a subfolder, but they shouldn't get picked up as test scripts.
+In this case you have two options:
+
+1. Add a file with the name ``sconstest.skip`` to your subfolder. This lets
+   ``runtest.py`` skip the contents of the directory completely.
+2. Create a file ``.exclude_tests`` in each folder in question, and in it list
+   line-by-line the files to get excluded from testing.
+
+The same rules apply when testing external Tools by using the ``-e`` option.
+
+
+"Hello, world!" SCons Test Script
+=================================
+
+To illustrate how the end-to-end test scripts work,
+let's walk through a simple "Hello, world!" example:
+
+::
+
+  #!python
+  import TestSCons
+
+  test = TestSCons.TestSCons()
+
+  test.write('SConstruct', """\
+  Program('hello.c')
+  """)
+
+  test.write('hello.c', """\
+  int
+  main(int argc, char *argv[])
+  {
+        printf("Hello, world!\\n");
+        exit (0);
+  }
+  """)
+
+  test.run()
+
+  test.run(program='./hello', stdout="Hello, world!\n")
+
+  test.pass_test()
+
+
+``import TestSCons``  
+  Imports the main infrastructure for writing SCons tests.  This is normally the only part of the infrastructure that needs importing.  Sometimes other Python modules are necessary or helpful, and get imported before this line.
+
+``test = TestSCons.TestSCons()``
+  This initializes an object for testing.  A fair amount happens under the covers when the object is created, including:
+
+    * A temporary directory is created for all the in-line files that will get created.
+    * The temporary directory's removal is arranged for when the test is finished.
+    * We ``os.chdir()`` to the temporary directory.
+
+``test.write('SConstruct', ...``
+  This line creates an ``SConstruct`` file in the temporary directory, to be used as input to the ``scons`` run(s) that we're testing.  Note the use of the Python triple-quote syntax for the contents of the ``SConstruct`` file.  Because input files for tests are all created from in-line data like this, the tests can sometimes get a little confusing to read, because some of the Python code is found
+
+``test.write('hello.c', ...``
+  This lines creates an ``hello.c`` file in the temporary directory.  Note that we have to escape the ``\\n`` in the ``"Hello, world!\\n"`` string so that it ends up as a single backslash in the ``hello.c`` file on disk.
+
+``test.run()``
+  This actually runs SCons.  Like the object initialization, things happen under the covers:
+
+    * The exit status is verified; the test exits with a failure if the exit status is not zero.
+    * The error output is examined, and the test exits with a failure if there is any
+
+``test.run(program='./hello', stdout="Hello, world!\n")``
+  This shows use of the ``TestSCons.run()`` method to execute a program other than ``scons``, in this case the ``hello`` program we just presumably built.  The ``stdout=`` keyword argument also tells the ``TestSCons.run()`` method to fail if the program output does not match the expected string ``"Hello, world!\n"``.  Like the previous ``test.run()`` line, it will also fail the test if the exit status is non-zero, or there is any error output.
+
+``test.pass_test()``
+  This is always the last line in a test script.  It prints ``PASSED`` on the screen and makes sure we exit with a ``0`` status to indicate the test passed.  As a side effect of destroying the ``test`` object, the created temporary directory will be removed.
+
+Working with fixtures
+=====================
+
+In the simple example above, we have seen how to create files in the temporary test directory.
+We give a filename to the ``TestSCons.write()`` method, together with its contents, and it gets
+written to the test folder right before its start.
+
+This technique can still be seen throughout most of the end-to-end tests, but there is a better
+way. It's much easier to edit, create and maintain real files, instead of copy/pasting
+content to/from a Python script. If the test files get longer, the test script
+gets longer and is harder to read.
+
+Against this, we now have the possibility to copy single files or the contents of a
+local folder to the test directory. Since we can reuse these files/folders to setup
+several tests, we call them *fixtures* in the following.
+
+Directory fixtures
+##################
+
+The function ``dir_fixture(self, srcdir, dstdir=None)`` in the ``TestCmd`` class
+copies the contents of the specified folder ``srcdir`` from
+the directory of the called test script, to the current
+temporary test directory.
+The ``srcdir`` name may be a list, in which case the elements are
+concatenated with the ``os.path.join()`` method.  The ``dstdir`` is
+assumed to be under the temporary working directory, it gets
+created automatically, if it does not already exist.
+
+A short syntax example::
+
+  test = TestSCons.TestSCons()
+  test.dir_fixture('image')
+  test.run()  
+
+would copy all files and subfolders from the local ``image`` folder, to
+the temporary directory for the current test.
+
+If you'd like to see a real example for this in action, refer to the test
+named ``test/packaging/convenience-functions/convenience-functions.py``.
+  
+File fixtures
+#############
+
+Like for directory fixtures, ``file_fixture(self, srcfile, dstfile=None)``
+copies the file ``srcfile`` from the directory of
+the called script, to the temporary test directory.
+The ``dstfile`` is assumed to be under the temporary working
+directory, unless it is an absolute path name.
+If ``dstfile`` is specified, its target directory gets created
+automatically if it doesn't already exist.
+
+With a::
+
+  test = TestSCons.TestSCons()
+  test.file_fixture('SConstruct')
+  test.file_fixture(['src','main.cpp'],['src','main.cpp'])
+  test.run()  
+
+you would copy the files ``SConstruct`` and ``src/main.cpp`` to the temporary
+test folder, prior to running the test itself.
+
+Again, a reference example can be found in the current *default* revision of
+SCons, it is ``test/packaging/sandbox-test/sandbox-test.py``.
+
+For even more examples you should check out one of the external Tools, e.g. the
+*Qt4* Tool at https://bitbucket.org/dirkbaechle/scons_qt4. Also visit the SCons
+Tools Index at http://www.scons.org/wiki/ToolsIndex for a complete
+list of available Tools, though not all may have tests yet.
+
+How to convert old tests
+########################
+
+We now show how to convert a test, still using the ``TestSCons.write()`` method, to
+the fixture based approach. For this, we need to get at the files as they
+are written to each temporary test folder.
+
+Luckily, ``runtest.py`` checks for the existence of an environment variable named
+``PRESERVE``. If it is set to a non-zero value, the testing framework doesn't delete
+the test folder as ususal, but prints its name to the screen.
+
+So, you should be able to give the commands 
+
+::
+
+  $ export PRESERVE=1
+  $ python runtest.py test/packaging/sandbox-test.py
+
+, assuming Linux and a bash-like shell.
+  
+The output should then look something like this::
+
+  1/1 (100.00%) /usr/bin/python -tt test/packaging/sandbox-test.py
+  PASSED
+  Preserved directory /tmp/testcmd.4060.twlYNI
+
+and you see that the test files have been kept in the folder ``/tmp/testcmd.4060.twlYNI``, 
+where you can now copy them from to your new *fixture* folder. Then, in the test
+script you simply remove all the tedious ``TestSCons.write()`` statements and
+replace them by a single ``TestSCons.dir_fixture()``.
+
+Finally, you shouldn't forget to clean up and remove the temporary test directory. ``;)``
+
+Test Infrastructure
+===================
+
+The test API is in ``QMTest/TestSCons.py``.  ``TestSCons`` is a subclass of
+``TestCommon``, which is a subclass of ``TestCmd``; all those python files are
+in ``QMTest``. Start in ``QMTest/TestCmd.py`` for the base API definitions,
+like how to create files (``test.write()``) and run commands (``test.run()``).
+
+You want to use ``TestSCons`` for the end-to-end tests in ``test``, but ``TestCmd``
+for the unit tests in the ``src`` folder.
+
+The match functions work like this:
+
+TestSCons.match_re:: match each line with a RE
+  * Splits the lines into a list (unless they already are)
+  * splits the REs at newlines (unless already a list) and puts ^..$ around each
+  * then each RE must match each line.  This means there must be as many REs as lines.
+
+TestSCons.match_re_dotall:: match all the lines against a single RE
+  * Joins the lines with newline (unless already a string)
+  * joins the REs with newline (unless it's a string) and puts ^..$ around the whole thing
+  * then whole thing must match with python re.DOTALL.
+
+Use them in a test like this::
+
+  test.run(..., match=TestSCons.match_re, ...)
+
+or::
+
+  test.must_match(..., match=TestSCons.match_re, ...)
+
+Avoiding Tests based on Tool existence
+======================================
+
+Here's an easy sample::
+
+  #!python
+  intelc = test.detect_tool('intelc', prog='icpc')
+  if not intelc:
+      test.skip_test("Could not load 'intelc' Tool; skipping test(s).\n")
+
+See ``QMTest/TestSCons.py`` for the ``detect_tool`` method.  It calls the tool's
+``generate()`` method, and then looks for the given prog (tool name by default) in
+``env['ENV']['PATH']``.
+
+
         Anthony Roach
         Terrel Shumway
 
+
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
+#
+# After updating this file, run bin/update-release-info.py <MODE>.
+# 
+
 # The version tuple that will be used for the release.  The fields are
 # (major, minor, micro, type, patchlevel).  The release level is one of
 # 'alpha', 'beta', 'candidate', or 'final'.  If the release type is not
 # 'final', the patchlevel is set to the release date.  This value is
 # manatory and must be present in this file.
-version_tuple = (2, 1, 0, 'alpha', 0)
+version_tuple = (2, 2, 0, 'final', 0)
 
 # Python versions prior to unsupported_python_version cause a fatal error
 # when that version is used.  Python versions prior to deprecate_python_version
 #month_year = 'December 2012'
 
 # If copyright years is not given, the release year is used as the end.
-#copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010'
+#copyright_years = '2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012'
 
 # Local Variables:
 # tab-width:4
     contents = contents.replace('__COPYRIGHT' + '__', env['COPYRIGHT'])
     contents = contents.replace('__DATE'      + '__', env['DATE'])
     contents = contents.replace('__DEVELOPER' + '__', env['DEVELOPER'])
-    contents = contents.replace('__FILE'      + '__', str(source[0]))
+    contents = contents.replace('__FILE'      + '__', str(source[0]).replace('\\', '/'))
     contents = contents.replace('__MONTH_YEAR'+ '__', env['MONTH_YEAR'])
     contents = contents.replace('__REVISION'  + '__', env['REVISION'])
     contents = contents.replace('__VERSION'   + '__', env['VERSION'])
     if dh_builddeb and fakeroot:
         # Our Debian packaging builds directly into build/dist,
         # so we don't need to Install() the .debs.
-        deb = os.path.join(build_dir, 'dist', "%s_%s-1_all.deb" % (pkg, version))
+        deb = os.path.join(build_dir, 'dist', "%s_%s_all.deb" % (pkg, version))
         for d in p['debian_deps']:
             b = env.SCons_revision(os.path.join(build, d), d)
             env.Depends(deb, b)
         commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY sdist --formats=%s" %  \
                             ','.join(distutils_formats))
 
-    commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_wininst")
+    commands.append("$PYTHON $PYTHONFLAGS $SETUP_PY bdist_wininst --plat-name win32 --user-access-control auto")
 
     env.Command(distutils_targets, build_src_files, commands)
 

bin/update-release-info.py

 
 if mode == 'develop' and version_tuple[3] != 'alpha':
     version_tuple ==  version_tuple[:3] + ('alpha', 0)
-if version_tuple[3] != 'final':
+if len(version_tuple) > 3 and version_tuple[3] != 'final':
     if mode == 'develop':
         version_tuple = version_tuple[:4] + ('yyyymmdd',)
     else:
         yyyy,mm,dd,_,_,_ = release_date
         version_tuple = version_tuple[:4] + ((yyyy*100 + mm)*100 + dd,)
 version_string = '.'.join(map(str, version_tuple))
-version_type = version_tuple[3]
+if len(version_tuple) > 3:
+    version_type = version_tuple[3]
+else:
+    version_type = 'final'
 if DEBUG: print 'version string', version_string
 
 if version_type not in ['alpha', 'beta', 'candidate', 'final']:

bin/upload-release-files.sh

+#!/bin/sh
+
+if [ $# -lt 2 ]; then
+	echo Usage: $0 VERSION SF_USERNAME
+	exit 1
+fi
+
+VERSION=$1; shift
+SF_USER=$1; shift
+
+RSYNC='rsync'
+RSYNCOPTS='-v -e ssh'
+SF_MACHINE='frs.sourceforge.net'
+SF_TOPDIR='/home/frs/project/scons'
+
+# the build products are here:
+cd build/dist
+cp -f ../../src/CHANGES.txt ../../src/RELEASE.txt ../../src/Announce.txt .
+
+set -x
+
+# Upload main scons release files:
+$RSYNC $RSYNCOPTS \
+  scons-$VERSION-1.noarch.rpm \
+  scons-$VERSION-1.src.rpm \
+  scons-$VERSION-setup.exe \
+  scons-$VERSION.tar.gz \
+  scons-$VERSION.zip \
+  Announce.txt CHANGES.txt RELEASE.txt \
+  $SF_USER@$SF_MACHINE:$SF_TOPDIR/scons/$VERSION/
+
+# Local packages:
+$RSYNC $RSYNCOPTS \
+  scons-local-$VERSION.tar.gz \
+  scons-local-$VERSION.zip \
+  Announce.txt CHANGES.txt RELEASE.txt \
+  $SF_USER@$SF_MACHINE:$SF_TOPDIR/scons-local/$VERSION/
+
+# Source packages:
+$RSYNC $RSYNCOPTS \
+  scons-src-$VERSION.tar.gz \
+  scons-src-$VERSION.zip \
+  Announce.txt CHANGES.txt RELEASE.txt \
+  $SF_USER@$SF_MACHINE:$SF_TOPDIR/scons-src/$VERSION/
+
+
+#
+# scons.org stuff:
+# 
+# Doc: copy the doc tgz over; we'll unpack later
+$RSYNC $RSYNCOPTS \
+  scons-doc-$VERSION.tar.gz \
+  scons@scons.org:public_html/production/doc/$VERSION/
+# Copy the changelog
+$RSYNC $RSYNCOPTS \
+  CHANGES.txt \
+  scons@scons.org:public_html/production/
+# Note that Announce.txt gets copied over to RELEASE.txt.
+# This should be fixed at some point.
+$RSYNC $RSYNCOPTS \
+  Announce.txt \
+  scons@scons.org:public_html/production/RELEASE.txt
+# Unpack the doc and repoint doc symlinks:
+ssh scons@scons.org "
+  cd public_html/production/doc
+  cd $VERSION
+  tar xvf scons-doc-$VERSION.tar.gz
+  cd ..
+  rm latest; ln -s $VERSION latest
+  rm production; ln -s $VERSION production
+  for f in HTML PDF PS TEXT; do rm $f; ln -s $VERSION/$f $f; done
+"
+echo '*****'
+echo '***** Now manually update index.php, includes/versions.php and news-raw.xhtml on scons.org.'
+echo '*****'
+scons (2.2.0) unstable; urgency=low
+
+  * Maintenance release.
+
+ -- Gary Oberbrunner <garyo@oberbrunner.com>  Sun, 12 Aug 2012 09:00:00 -0500
+
+
 scons (0.97) unstable; urgency=low
 
   * Eighth beta release.
 fi
 
 NAME=scons
-PYTHON_VERSION=`python -V 2>&1| cut -d" " -f2 | cut -d"." -f0-2`
+PYTHON_VERSION=`python -V 2>&1| cut -d" " -f2 | cut -d"." -f1-2`
 PYTHON_EXE_NAME=`which python$PYTHON_VERSION`
 case "$1" in
         configure|abort-upgrade|abort-remove|abort-deconfigure)
 # Overridable variables added to support building test .deb files
 # as part of routine SCons builds.  --SK
 BUILDDEB_OPTIONS=
-PYTHON_VERSION=`python -V 2>&1| cut -d" " -f2 | cut -d"." -f0-2`
+PYTHON_VERSION=`python -V 2>&1| cut -d" " -f2 | cut -d"." -f1-2`
 PYTHON_PATH=/usr/bin/python
 PYTHON=$(PYTHON_PATH)$(PYTHON_VERSION)
 #######
 	@########
 	mkdir -p debian/scons/usr/lib/python$(PYTHON_VERSION)/site-packages/
 	rm -rf debian/scons/usr/lib/python$(PYTHON_VERSION)/site-packages/SCons
-	cp -r build/lib/SCons debian/scons/usr/lib/python$(PYTHON_VERSION)/site-packages/
+	cp -r build/lib*/SCons debian/scons/usr/lib/python$(PYTHON_VERSION)/site-packages/
 
 	mkdir -p debian/scons/usr/bin/
 	rm -f debian/scons/usr/bin/scons
 
         tar_deps.extend([ps, text])
         tar_list.extend([ps, text])
+    else:
+        print "doc: WARNING: no groff, skipping text and PostScript versions of man pages"
 
     if man2html:
         html = os.path.join(build, 'HTML' , '%s-man.html' % man)
 
         tar_deps.append(html)
         tar_list.append(html)
+    else:
+        print "doc: WARNING: no man2html, skipping HTML versions of man pages"
 
 if not epydoc_cli:
     try:

doc/user/MANIFEST

 factories.xml
 file-removal.xml
 functions.xml
+gettext.xml
 hierarchy.xml
 install.xml
 java.xml

doc/user/gettext.in

+<!--
+
+  __COPYRIGHT__
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+
+  <para>
+  The &t-link-gettext; toolset supports internationalization and localization
+  of SCons-based projects. Builders provided by &t-link-gettext; automatize
+  generation and updates of translation files. You can manage translations and
+  translation templates similarly to how it's done with autotools.
+  </para>
+
+  <section>
+  <title>Prerequisites</title>
+  <para>
+    To follow examples provided in this chapter set up your operating system to
+    support two or more languages. In following examples we use locales
+    <literal>en_US</literal>, <literal>de_DE</literal>, and
+    <literal>pl_PL</literal>.
+  </para>
+
+  <para>
+    Ensure, that you have <ulink
+    url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed on your system.
+  </para>
+
+  <para>
+    To edit translation files you may wish to install <ulink
+    url="http://www.poedit.net/">poedit</ulink> editor.
+  </para>
+  </section>
+
+  <section>
+  <title>Simple project</title>
+    <para>
+    Let's start with a very simple project, the "Hello world" program 
+    for example
+    <scons_example name="ex1">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    int main(int argc, char* argv[])
+    {
+      printf("Hello world\n");
+      return 0;
+    }
+    </file>
+    </scons_example>
+
+    Prepare a <filename>SConstruct</filename> to compile the program
+    as usual.
+    <scons_example name="ex2">
+    <file name="SConstruct" printme="1">
+    # SConstruct
+    env = Environment()
+    hello = Program(["hello.c"])
+    </file>
+    </scons_example>
+    </para>
+
+    <para>
+    Now we'll convert the project to a multi-lingual one. If you don't
+    already have <ulink
+    url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed, install them from your preffered
+    package repository, or download from <ulink
+    url="http://ftp.gnu.org/gnu/gettext/">
+    http://ftp.gnu.org/gnu/gettext/</ulink>. For the purpose of this example,
+    you should have following three locales installed on your system:
+    <literal>en_US</literal>, <literal>de_DE</literal> and
+    <literal>pl_PL</literal>. On debian, for example, you may enable certain
+    locales through <command>dpkg-reconfigure locales</command>.
+    </para>
+
+    <para>
+    First prepare the <filename>hello.c</filename> program for
+    internationalization. Change the previous code so it reads as follows:
+    <scons_example name="ex3">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      return 0;
+    }
+    </file>
+    </scons_example>
+    Detailed recipes for such conversion can
+    be found at <ulink
+    url="http://www.gnu.org/software/gettext/manual/gettext.html#Sources">
+    http://www.gnu.org/software/gettext/manual/gettext.html#Sources</ulink>.
+    The <function>gettext("...")</function> has two purposes.
+    First, it marks messages for the <command>xgettext(1)</command> program, which
+    we will use to extract from the sources the messages for localization.
+    Second, it calls the <literal>gettext</literal> library internals to
+    translate the message at runtime.
+    </para>
+
+    <para> 
+    Now we shall instruct SCons how to generate and maintain translation files.
+    For that, use the &b-link-Translate; builder and &b-link-MOFiles; builder.
+    The first one takes source files, extracts internationalized
+    messages from them, creates so-called <literal>POT</literal> file
+    (translation template), and then creates <literal>PO</literal> translation
+    files, one for each requested language. Later, during the development
+    lifecycle, the builder keeps all these files up-to date. The
+    &b-link-MOFiles; builder compiles the <literal>PO</literal> files to binary
+    form. Then install the <literal>MO</literal> files under directory
+    called <filename>locale</filename>.
+    </para>
+
+    <para>  The completed 
+    <filename>SConstruct</filename> is as follows:
+    <scons_example name="ex4">
+    <file name="SConstruct" printme="1">
+    # SConstruct
+    env = Environment( tools = ['default', 'gettext'] )
+    hello = env.Program(["hello.c"])
+    env['XGETTEXTFLAGS'] = [
+      '--package-name=%s' % 'hello',
+      '--package-version=%s' % '1.0',
+    ]
+    po = env.Translate(["pl","en", "de"], ["hello.c"], POAUTOINIT = 1)
+    mo = env.MOFiles(po)
+    InstallAs(["locale/en/LC_MESSAGES/hello.mo"], ["en.mo"])
+    InstallAs(["locale/pl/LC_MESSAGES/hello.mo"], ["pl.mo"])
+    InstallAs(["locale/de/LC_MESSAGES/hello.mo"], ["de.mo"])
+    </file>
+    </scons_example>
+    </para>
+    <para>
+    Generate the translation files with <command>scons po-update</command>.
+    You should see the output from SCons simillar to this:
+    <screen>
+    user@host:$ scons po-update
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (new file)
+    msginit --no-translator -l pl -i messages.pot -o pl.po
+    Created pl.po.
+    msginit --no-translator -l en -i messages.pot -o en.po
+    Created en.po.
+    msginit --no-translator -l de -i messages.pot -o de.po
+    Created de.po.
+    scons: done building targets.
+    </screen>
+    </para>
+
+    <para>
+    If everything is right, you should see following new files.
+    <screen>
+    user@host:$ ls *.po*
+    de.po  en.po  messages.pot  pl.po
+    </screen>
+    </para>
+
+    <para>
+    Open <filename>en.po</filename> in <command>poedit</command> and provide
+    the English translation to message <literal>"Hello world\n"</literal>. Do the
+    same for <filename>de.po</filename> (deutsch) and
+    <filename>pl.po</filename> (polish). Let the translations be, for example:
+    <itemizedlist>
+      <listitem><para>
+        <literal>en: "Welcome to beautiful world!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>de: "Hallo Welt!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>pl: "Witaj swiecie!\n"</literal>
+      </para></listitem>
+    </itemizedlist>
+    </para>
+    <para>
+    Now compile the project by executing <command>scons</command>. The
+    output should be similar to this:
+    <screen>
+    user@host:$ scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    msgfmt -c -o de.mo de.po
+    msgfmt -c -o en.mo en.po
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    Install file: "de.mo" as "locale/de/LC_MESSAGES/hello.mo"
+    Install file: "en.mo" as "locale/en/LC_MESSAGES/hello.mo"
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    SCons automatically compiled the <literal>PO</literal> files to binary format
+    <literal>MO</literal>, and the <literal>InstallAs</literal> lines installed
+    these files under <filename>locale</filename> folder.
+    </para>
+    <para>
+    Your program should be now ready. You may try it as follows (linux):
+    <screen>
+    user@host:$ LANG=en_US.UTF-8 ./hello
+    Welcome to beautiful world
+    </screen>
+    <screen>
+    user@host:$ LANG=de_DE.UTF-8 ./hello
+    Hallo Welt
+    </screen>
+    <screen>
+    user@host:$ LANG=pl_PL.UTF-8 ./hello
+    Witaj swiecie
+    </screen>
+    </para>
+    <para>
+    To demonstrate the further life of translation files, let's change Polish
+    translation (<command>poedit pl.po</command>) to <literal>"Witaj drogi
+    swiecie\n"</literal>. Run <command>scons</command> to see how scons
+    reacts to this
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    </para>
+    <para>
+    Now, open <filename>hello.c</filename> and add another one
+    <literal>printf</literal> line with new message.
+    <scons_example name="ex5">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      printf(gettext("and good bye\n"));
+      return 0;
+    }
+    </file>
+    </scons_example>
+    </para>
+    <para>
+    Compile project with <command>scons</command>. This time, the
+    <command>msgmerge(1)</command> program is used by SCons to update
+    <literal>PO</literal> file. The output from compilation is like:
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (messages in file were outdated)
+    msgmerge --update de.po messages.pot
+    ... done.
+    msgfmt -c -o de.mo de.po
+    msgmerge --update en.po messages.pot
+    ... done.
+    msgfmt -c -o en.mo en.po
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    Install file: "de.mo" as "locale/de/LC_MESSAGES/hello.mo"
+    Install file: "en.mo" as "locale/en/LC_MESSAGES/hello.mo"
+    msgmerge --update pl.po messages.pot
+    ... done.
+    msgfmt -c -o pl.mo pl.po
+    Install file: "pl.mo" as "locale/pl/LC_MESSAGES/hello.mo"
+    scons: done building targets.
+    </screen>
+    </para>
+    <para>
+    The next example demonstrates what happens if we change the source code
+    in such way that the internationalized messages do not change. The answer
+    is that none of translation files (<literal>POT</literal>,
+    <literal>PO</literal>) are touched (i.e. no content changes, no
+    creation/modification time changed and so on). Let's append another
+    line to the program (after the last printf), so its code becomes:
+    <scons_example name="ex6">
+    <file name="hello.c" printme="1">
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      printf(gettext("and good bye\n"));
+      printf("----------------\n");
+      return a;
+    }
+    </file>
+    </scons_example>
+    Compile the project. You'll see on your screen
+    <screen>
+    user@host:$scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Not writting 'messages.pot' (messages in file found to be up-to-date)
+    gcc -o hello.o -c hello.c
+    gcc -o hello hello.o
+    scons: done building targets.
+    </screen>
+    As you see, the internationalized messages ditn't change, so the
+    <literal>POT</literal> and the rest of translation files have not
+    even been touched.
+    </para>
+  </section>

doc/user/gettext.xml

+<!--
+
+  __COPYRIGHT__
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be included
+  in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+  KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-->
+
+  <para>
+  The &t-link-gettext; toolset supports internationalization and localization
+  of SCons-based projects. Builders provided by &t-link-gettext; automatize
+  generation and updates of translation files. You can manage translations and
+  translation templates similarly to how it's done with autotools.
+  </para>
+
+  <section>
+  <title>Prerequisites</title>
+  <para>
+    To follow examples provided in this chapter set up your operating system to
+    support two or more languages. In following examples we use locales
+    <literal>en_US</literal>, <literal>de_DE</literal>, and
+    <literal>pl_PL</literal>.
+  </para>
+
+  <para>
+    Ensure, that you have <ulink url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed on your system.
+  </para>
+
+  <para>
+    To edit translation files you may wish to install <ulink url="http://www.poedit.net/">poedit</ulink> editor.
+  </para>
+  </section>
+
+  <section>
+  <title>Simple project</title>
+    <para>
+    Let's start with a very simple project, the "Hello world" program 
+    for example
+    <programlisting>
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    int main(int argc, char* argv[])
+    {
+      printf("Hello world\n");
+      return 0;
+    }
+    </programlisting>
+
+    Prepare a <filename>SConstruct</filename> to compile the program
+    as usual.
+    <programlisting>
+    # SConstruct
+    env = Environment()
+    hello = Program(["hello.c"])
+    </programlisting>
+    </para>
+
+    <para>
+    Now we'll convert the project to a multi-lingual one. If you don't
+    already have <ulink url="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext
+    utilities</ulink> installed, install them from your preffered
+    package repository, or download from <ulink url="http://ftp.gnu.org/gnu/gettext/">
+    http://ftp.gnu.org/gnu/gettext/</ulink>. For the purpose of this example,
+    you should have following three locales installed on your system:
+    <literal>en_US</literal>, <literal>de_DE</literal> and
+    <literal>pl_PL</literal>. On debian, for example, you may enable certain
+    locales through <command>dpkg-reconfigure locales</command>.
+    </para>
+
+    <para>
+    First prepare the <filename>hello.c</filename> program for
+    internationalization. Change the previous code so it reads as follows:
+    <programlisting>
+    /* hello.c */
+    #include &lt;stdio.h&gt;
+    #include &lt;libintl.h&gt;
+    #include &lt;locale.h&gt;
+    int main(int argc, char* argv[])
+    {
+      bindtextdomain("hello", "locale");
+      setlocale(LC_ALL, "");
+      textdomain("hello");
+      printf(gettext("Hello world\n"));
+      return 0;
+    }
+    </programlisting>
+    Detailed recipes for such conversion can
+    be found at <ulink url="http://www.gnu.org/software/gettext/manual/gettext.html#Sources">
+    http://www.gnu.org/software/gettext/manual/gettext.html#Sources</ulink>.
+    The <function>gettext("...")</function> has two purposes.
+    First, it marks messages for the <command>xgettext(1)</command> program, which
+    we will use to extract from the sources the messages for localization.
+    Second, it calls the <literal>gettext</literal> library internals to
+    translate the message at runtime.
+    </para>
+
+    <para> 
+    Now we shall instruct SCons how to generate and maintain translation files.
+    For that, use the &b-link-Translate; builder and &b-link-MOFiles; builder.
+    The first one takes source files, extracts internationalized
+    messages from them, creates so-called <literal>POT</literal> file
+    (translation template), and then creates <literal>PO</literal> translation
+    files, one for each requested language. Later, during the development
+    lifecycle, the builder keeps all these files up-to date. The
+    &b-link-MOFiles; builder compiles the <literal>PO</literal> files to binary
+    form. Then install the <literal>MO</literal> files under directory
+    called <filename>locale</filename>.
+    </para>
+
+    <para>  The completed 
+    <filename>SConstruct</filename> is as follows:
+    <programlisting>
+    # SConstruct
+    env = Environment( tools = ['default', 'gettext'] )
+    hello = env.Program(["hello.c"])
+    env['XGETTEXTFLAGS'] = [
+      '--package-name=%s' % 'hello',
+      '--package-version=%s' % '1.0',
+    ]
+    po = env.Translate(["pl","en", "de"], ["hello.c"], POAUTOINIT = 1)
+    mo = env.MOFiles(po)
+    InstallAs(["locale/en/LC_MESSAGES/hello.mo"], ["en.mo"])
+    InstallAs(["locale/pl/LC_MESSAGES/hello.mo"], ["pl.mo"])
+    InstallAs(["locale/de/LC_MESSAGES/hello.mo"], ["de.mo"])
+    </programlisting>
+    </para>
+    <para>
+    Generate the translation files with <command>scons po-update</command>.
+    You should see the output from SCons simillar to this:
+    <screen>
+    user@host:$ scons po-update
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.
+    scons: Building targets ...
+    Entering '/home/ptomulik/projects/tmp'
+    xgettext --package-name=hello --package-version=1.0 -o - hello.c
+    Leaving '/home/ptomulik/projects/tmp'
+    Writting 'messages.pot' (new file)
+    msginit --no-translator -l pl -i messages.pot -o pl.po
+    Created pl.po.
+    msginit --no-translator -l en -i messages.pot -o en.po
+    Created en.po.
+    msginit --no-translator -l de -i messages.pot -o de.po
+    Created de.po.
+    scons: done building targets.
+    </screen>
+    </para>
+
+    <para>
+    If everything is right, you should see following new files.
+    <screen>
+    user@host:$ ls *.po*
+    de.po  en.po  messages.pot  pl.po
+    </screen>
+    </para>
+
+    <para>
+    Open <filename>en.po</filename> in <command>poedit</command> and provide
+    the English translation to message <literal>"Hello world\n"</literal>. Do the
+    same for <filename>de.po</filename> (deutsch) and
+    <filename>pl.po</filename> (polish). Let the translations be, for example:
+    <itemizedlist>
+      <listitem><para>
+        <literal>en: "Welcome to beautiful world!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>de: "Hallo Welt!\n"</literal>
+      </para></listitem>
+      <listitem><para>
+        <literal>pl: "Witaj swiecie!\n"</literal>
+      </para></listitem>
+    </itemizedlist>
+    </para>
+    <para>
+    Now compile the project by executing <command>scons</command>. The
+    output should be similar to this:
+    <screen>
+    user@host:$ scons
+    scons: Reading SConscript files ...
+    scons: done reading SConscript files.