summaryrefslogtreecommitdiff
path: root/src/engine/SCons/Tool/GettextCommon.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/SCons/Tool/GettextCommon.py')
-rw-r--r--src/engine/SCons/Tool/GettextCommon.py429
1 files changed, 429 insertions, 0 deletions
diff --git a/src/engine/SCons/Tool/GettextCommon.py b/src/engine/SCons/Tool/GettextCommon.py
new file mode 100644
index 0000000..b2d848c
--- /dev/null
+++ b/src/engine/SCons/Tool/GettextCommon.py
@@ -0,0 +1,429 @@
+"""SCons.Tool.GettextCommon module
+
+Used by several tools of `gettext` toolset.
+"""
+
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 The SCons Foundation
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+__revision__ = "src/engine/SCons/Tool/GettextCommon.py issue-2856:2676:d23b7a2f45e8 2012/08/05 15:38:28 garyo"
+
+import SCons.Warnings
+import re
+
+#############################################################################
+class XgettextToolWarning(SCons.Warnings.Warning): pass
+class XgettextNotFound(XgettextToolWarning): pass
+class MsginitToolWarning(SCons.Warnings.Warning): pass
+class MsginitNotFound(MsginitToolWarning): pass
+class MsgmergeToolWarning(SCons.Warnings.Warning): pass
+class MsgmergeNotFound(MsgmergeToolWarning): pass
+class MsgfmtToolWarning(SCons.Warnings.Warning): pass
+class MsgfmtNotFound(MsgfmtToolWarning): pass
+#############################################################################
+SCons.Warnings.enableWarningClass(XgettextToolWarning)
+SCons.Warnings.enableWarningClass(XgettextNotFound)
+SCons.Warnings.enableWarningClass(MsginitToolWarning)
+SCons.Warnings.enableWarningClass(MsginitNotFound)
+SCons.Warnings.enableWarningClass(MsgmergeToolWarning)
+SCons.Warnings.enableWarningClass(MsgmergeNotFound)
+SCons.Warnings.enableWarningClass(MsgfmtToolWarning)
+SCons.Warnings.enableWarningClass(MsgfmtNotFound)
+#############################################################################
+
+#############################################################################
+class _POTargetFactory(object):
+ """ A factory of `PO` target files.
+
+ Factory defaults differ from these of `SCons.Node.FS.FS`. We set `precious`
+ (this is required by builders and actions gettext) and `noclean` flags by
+ default for all produced nodes.
+ """
+ def __init__( self, env, nodefault = True, alias = None, precious = True
+ , noclean = True ):
+ """ Object constructor.
+
+ **Arguments**
+
+ - *env* (`SCons.Environment.Environment`)
+ - *nodefault* (`boolean`) - if `True`, produced nodes will be ignored
+ from default target `'.'`
+ - *alias* (`string`) - if provided, produced nodes will be automatically
+ added to this alias, and alias will be set as `AlwaysBuild`
+ - *precious* (`boolean`) - if `True`, the produced nodes will be set as
+ `Precious`.
+ - *noclen* (`boolean`) - if `True`, the produced nodes will be excluded
+ from `Clean`.
+ """
+ self.env = env
+ self.alias = alias
+ self.precious = precious
+ self.noclean = noclean
+ self.nodefault = nodefault
+
+ def _create_node(self, name, factory, directory = None, create = 1):
+ """ Create node, and set it up to factory settings. """
+ import SCons.Util
+ node = factory(name, directory, create)
+ node.set_noclean(self.noclean)
+ node.set_precious(self.precious)
+ if self.nodefault:
+ self.env.Ignore('.', node)
+ if self.alias:
+ self.env.AlwaysBuild(self.env.Alias(self.alias, node))
+ return node
+
+ def Entry(self, name, directory = None, create = 1):
+ """ Create `SCons.Node.FS.Entry` """
+ return self._create_node(name, self.env.fs.Entry, directory, create)
+
+ def File(self, name, directory = None, create = 1):
+ """ Create `SCons.Node.FS.File` """
+ return self._create_node(name, self.env.fs.File, directory, create)
+#############################################################################
+
+#############################################################################
+_re_comment = re.compile(r'(#[^\n\r]+)$', re.M)
+_re_lang = re.compile(r'([a-zA-Z0-9_]+)', re.M)
+#############################################################################
+def _read_linguas_from_files(env, linguas_files = None):
+ """ Parse `LINGUAS` file and return list of extracted languages """
+ import SCons.Util
+ import SCons.Environment
+ global _re_comment
+ global _re_lang
+ if not SCons.Util.is_List(linguas_files) \
+ and not SCons.Util.is_String(linguas_files) \
+ and not isinstance(linguas_files, SCons.Node.FS.Base) \
+ and linguas_files:
+ # If, linguas_files==True or such, then read 'LINGUAS' file.
+ linguas_files = [ 'LINGUAS' ]
+ if linguas_files is None:
+ return []
+ fnodes = env.arg2nodes(linguas_files)
+ linguas = []
+ for fnode in fnodes:
+ contents = _re_comment.sub("", fnode.get_text_contents())
+ ls = [ l for l in _re_lang.findall(contents) if l ]
+ linguas.extend(ls)
+ return linguas
+#############################################################################
+
+#############################################################################
+from SCons.Builder import BuilderBase
+#############################################################################
+class _POFileBuilder(BuilderBase):
+ """ `PO` file builder.
+
+ This is multi-target single-source builder. In typical situation the source
+ is single `POT` file, e.g. `messages.pot`, and there are multiple `PO`
+ targets to be updated from this `POT`. We must run
+ `SCons.Builder.BuilderBase._execute()` separatelly for each target to track
+ dependencies separatelly for each target file.
+
+ **NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)`
+ with target being list of all targets, all targets would be rebuilt each time
+ one of the targets from this list is missing. This would happen, for example,
+ when new language `ll` enters `LINGUAS_FILE` (at this moment there is no
+ `ll.po` file yet). To avoid this, we override
+ `SCons.Builder.BuilerBase._execute()` and call it separatelly for each
+ target. Here we also append to the target list the languages read from
+ `LINGUAS_FILE`.
+ """
+ #
+ #* The argument for overriding _execute(): We must use environment with
+ # builder overrides applied (see BuilderBase.__init__(). Here it comes for
+ # free.
+ #* The argument against using 'emitter': The emitter is called too late
+ # by BuilderBase._execute(). If user calls, for example:
+ #
+ # env.POUpdate(LINGUAS_FILE = 'LINGUAS')
+ #
+ # the builder throws error, because it is called with target=None,
+ # source=None and is trying to "generate" sources or target list first.
+ # If user calls
+ #
+ # env.POUpdate(['foo', 'baz'], LINGUAS_FILE = 'LINGUAS')
+ #
+ # the env.BuilderWrapper() calls our builder with target=None,
+ # source=['foo', 'baz']. The BuilderBase._execute() then splits execution
+ # and execute iterativelly (recursion) self._execute(None, source[i]).
+ # After that it calls emitter (which is quite too late). The emitter is
+ # also called in each iteration, what makes things yet worse.
+ def __init__(self, env, **kw):
+ if not 'suffix' in kw:
+ kw['suffix'] = '$POSUFFIX'
+ if not 'src_suffix' in kw:
+ kw['src_suffix'] = '$POTSUFFIX'
+ if not 'src_builder' in kw:
+ kw['src_builder'] = '_POTUpdateBuilder'
+ if not 'single_source' in kw:
+ kw['single_source'] = True
+ alias = None
+ if 'target_alias' in kw:
+ alias = kw['target_alias']
+ del kw['target_alias']
+ if not 'target_factory' in kw:
+ kw['target_factory'] = _POTargetFactory(env, alias=alias).File
+ BuilderBase.__init__(self, **kw)
+
+ def _execute(self, env, target, source, *args, **kw):
+ """ Execute builder's actions.
+
+ Here we append to `target` the languages read from `$LINGUAS_FILE` and
+ apply `SCons.Builder.BuilderBase._execute()` separatelly to each target.
+ The arguments and return value are same as for
+ `SCons.Builder.BuilderBase._execute()`.
+ """
+ import SCons.Util
+ import SCons.Node
+ linguas_files = None
+ if env.has_key('LINGUAS_FILE') and env['LINGUAS_FILE']:
+ linguas_files = env['LINGUAS_FILE']
+ # This prevents endless recursion loop (we'll be invoked once for
+ # each target appended here, we must not extend the list again).
+ env['LINGUAS_FILE'] = None
+ linguas = _read_linguas_from_files(env,linguas_files)
+ if SCons.Util.is_List(target):
+ target.extend(linguas)
+ elif target is not None:
+ target = [target] + linguas
+ else:
+ target = linguas
+ if not target:
+ # Let the SCons.BuilderBase to handle this patologic situation
+ return BuilderBase._execute( self, env, target, source, *args, **kw)
+ # The rest is ours
+ if not SCons.Util.is_List(target):
+ target = [ target ]
+ result = []
+ for tgt in target:
+ r = BuilderBase._execute( self, env, [tgt], source, *args, **kw)
+ result.extend(r)
+ if linguas_files is not None:
+ env['LINGUAS_FILE'] = linguas_files
+ return SCons.Node.NodeList(result)
+#############################################################################
+
+import SCons.Environment
+#############################################################################
+def _translate(env, target=[], source=SCons.Environment._null, *args, **kw):
+ """ Function for `Translate()` pseudo-builder """
+ pot = env.POTUpdate(None, source, *args, **kw)
+ po = env.POUpdate(target, pot, *args, **kw)
+ return po
+#############################################################################
+
+#############################################################################
+class RPaths(object):
+ """ Callable object, which returns pathnames relative to SCons current
+ working directory.
+
+ It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths
+ for nodes that are outside of current working directory (`env.fs.getcwd()`).
+ Here, we often have `SConscript`, `POT` and `PO` files within `po/`
+ directory and source files (e.g. `*.c`) outside of it. When generating `POT`
+ template file, references to source files are written to `POT` template, so
+ a translator may later quickly jump to appropriate source file and line from
+ its `PO` editor (e.g. `poedit`). Relative paths in `PO` file are usually
+ interpreted by `PO` editor as paths relative to the place, where `PO` file
+ lives. The absolute paths would make resultant `POT` file nonportable, as
+ the references would be correct only on the machine, where `POT` file was
+ recently re-created. For such reason, we need a function, which always
+ returns relative paths. This is the purpose of `RPaths` callable object.
+
+ The `__call__` method returns paths relative to current woking directory, but
+ we assume, that *xgettext(1)* is run from the directory, where target file is
+ going to be created.
+
+ Note, that this may not work for files distributed over several hosts or
+ across different drives on windows. We assume here, that single local
+ filesystem holds both source files and target `POT` templates.
+
+ Intended use of `RPaths` - in `xgettext.py`::
+
+ def generate(env):
+ from GettextCommon import RPaths
+ ...
+ sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET, SOURCES)} $)'
+ env.Append(
+ ...
+ XGETTEXTCOM = 'XGETTEXT ... ' + sources,
+ ...
+ XgettextRPaths = RPaths(env)
+ )
+ """
+ # NOTE: This callable object returns pathnames of dirs/files relative to
+ # current working directory. The pathname remains relative also for entries
+ # that are outside of current working directory (node, that
+ # SCons.Node.FS.File and siblings return absolute path in such case). For
+ # simplicity we compute path relative to current working directory, this
+ # seems be enough for our purposes (don't need TARGET variable and
+ # SCons.Defaults.Variable_Caller stuff).
+
+ def __init__(self, env):
+ """ Initialize `RPaths` callable object.
+
+ **Arguments**:
+
+ - *env* - a `SCons.Environment.Environment` object, defines *current
+ working dir*.
+ """
+ self.env = env
+
+ # FIXME: I'm not sure, how it should be implemented (what the *args are in
+ # general, what is **kw).
+ def __call__(self, nodes, *args, **kw):
+ """ Return nodes' paths (strings) relative to current working directory.
+
+ **Arguments**:
+
+ - *nodes* ([`SCons.Node.FS.Base`]) - list of nodes.
+ - *args* - currently unused.
+ - *kw* - currently unused.
+
+ **Returns**:
+
+ - Tuple of strings, which represent paths relative to current working
+ directory (for given environment).
+ """
+ # os.path.relpath is available only on python >= 2.6. We use our own
+ # implementation. It's taken from BareNecessities package:
+ # http://jimmyg.org/work/code/barenecessities/index.html
+ from posixpath import curdir
+ def relpath(path, start=curdir):
+ import posixpath
+ """Return a relative version of a path"""
+ if not path:
+ raise ValueError("no path specified")
+ start_list = posixpath.abspath(start).split(posixpath.sep)
+ path_list = posixpath.abspath(path).split(posixpath.sep)
+ # Work out how much of the filepath is shared by start and path.
+ i = len(posixpath.commonprefix([start_list, path_list]))
+ rel_list = [posixpath.pardir] * (len(start_list)-i) + path_list[i:]
+ if not rel_list:
+ return posixpath.curdir
+ return posixpath.join(*rel_list)
+ import os
+ import SCons.Node.FS
+ rpaths = ()
+ cwd = self.env.fs.getcwd().get_abspath()
+ for node in nodes:
+ rpath = None
+ if isinstance(node, SCons.Node.FS.Base):
+ rpath = relpath(node.get_abspath(), cwd)
+ # FIXME: Other types possible here?
+ if rpath is not None:
+ rpaths += (rpath,)
+ return rpaths
+#############################################################################
+
+#############################################################################
+def _init_po_files(target, source, env):
+ """ Action function for `POInit` builder. """
+ nop = lambda target, source, env : 0
+ if env.has_key('POAUTOINIT'):
+ autoinit = env['POAUTOINIT']
+ else:
+ autoinit = False
+ # Well, if everything outside works well, this loop should do single
+ # iteration. Otherwise we are rebuilding all the targets even, if just
+ # one has changed (but is this out fault?).
+ for tgt in target:
+ if not tgt.exists():
+ if autoinit:
+ action = SCons.Action.Action('$MSGINITCOM', '$MSGINITCOMSTR')
+ else:
+ msg = 'File ' + repr(str(tgt)) + ' does not exist. ' \
+ + 'If you are a translator, you can create it through: \n' \
+ + '$MSGINITCOM'
+ action = SCons.Action.Action(nop, msg)
+ status = action([tgt], source, env)
+ if status: return status
+ return 0
+#############################################################################
+
+#############################################################################
+def _detect_xgettext(env):
+ """ Detects *xgettext(1)* binary """
+ if env.has_key('XGETTEXT'):
+ return env['XGETTEXT']
+ xgettext = env.Detect('xgettext');
+ if xgettext:
+ return xgettext
+ raise SCons.Errors.StopError(XgettextNotFound,"Could not detect xgettext")
+ return None
+#############################################################################
+def _xgettext_exists(env):
+ return _detect_xgettext(env)
+#############################################################################
+
+#############################################################################
+def _detect_msginit(env):
+ """ Detects *msginit(1)* program. """
+ if env.has_key('MSGINIT'):
+ return env['MSGINIT']
+ msginit = env.Detect('msginit');
+ if msginit:
+ return msginit
+ raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit")
+ return None
+#############################################################################
+def _msginit_exists(env):
+ return _detect_msginit(env)
+#############################################################################
+
+#############################################################################
+def _detect_msgmerge(env):
+ """ Detects *msgmerge(1)* program. """
+ if env.has_key('MSGMERGE'):
+ return env['MSGMERGE']
+ msgmerge = env.Detect('msgmerge');
+ if msgmerge:
+ return msgmerge
+ raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge")
+ return None
+#############################################################################
+def _msgmerge_exists(env):
+ return _detect_msgmerge(env)
+#############################################################################
+
+#############################################################################
+def _detect_msgfmt(env):
+ """ Detects *msgmfmt(1)* program. """
+ if env.has_key('MSGFMT'):
+ return env['MSGFMT']
+ msgfmt = env.Detect('msgfmt');
+ if msgfmt:
+ return msgfmt
+ raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt")
+ return None
+#############################################################################
+def _msgfmt_exists(env):
+ return _detect_msgfmt(env)
+#############################################################################
+
+#############################################################################
+def tool_list(platform, env):
+ """ List tools that shall be generated by top-level `gettext` tool """
+ return [ 'xgettext', 'msginit', 'msgmerge', 'msgfmt' ]
+#############################################################################
+