~canonical-launchpad-branches/launchpad-buildd/trunk

« back to all changes in this revision

Viewing changes to lpbuildd/pottery/intltool.py

  • Committer: Colin Watson
  • Date: 2017-05-11 08:34:07 UTC
  • mfrom: (215.1.1 extended-snap-status)
  • Revision ID: cjwatson@canonical.com-20170511083407-2jvw6phrd50strdk
[r=wgrant] Record the branch revision used to build a snap and return it along with other XML-RPC status information (LP: #1679157).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 
1
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
 
"""Functions to build PO templates on the builder."""
 
4
"""Functions to build PO templates on the build slave."""
5
5
 
6
6
__metaclass__ = type
7
7
__all__ = [
13
13
    'find_potfiles_in',
14
14
    ]
15
15
 
 
16
from contextlib import contextmanager
 
17
import errno
16
18
import os.path
17
19
import re
18
 
import subprocess
19
 
import tempfile
20
 
 
21
 
 
22
 
def find_potfiles_in(backend, package_dir):
23
 
    """Search `package_dir` and its subdirectories for POTFILES.in.
24
 
 
25
 
    :param backend: The `Backend` where work is done.
26
 
    :param package_dir: The directory to search.
27
 
    :returns: A list of names of directories that contain a file
28
 
        POTFILES.in, relative to `package_dir`.
 
20
from subprocess import call
 
21
 
 
22
 
 
23
def find_potfiles_in():
 
24
    """Search the current directory and its subdirectories for POTFILES.in.
 
25
 
 
26
    :returns: A list of names of directories that contain a file POTFILES.in.
29
27
    """
30
 
    paths = backend.find(
31
 
        package_dir, include_directories=False, name="POTFILES.in")
32
 
    return [os.path.dirname(path) for path in paths]
33
 
 
34
 
 
35
 
def check_potfiles_in(backend, path):
 
28
    result_dirs = []
 
29
    for dirpath, dirnames, dirfiles in os.walk("."):
 
30
        if "POTFILES.in" in dirfiles:
 
31
            result_dirs.append(dirpath)
 
32
    return result_dirs
 
33
 
 
34
 
 
35
def check_potfiles_in(path):
36
36
    """Check if the files listed in the POTFILES.in file exist.
37
37
 
38
38
    Running 'intltool-update -m' will perform this check and also take a
45
45
    all listed files exist. The presence of the 'notexist' file tells us
46
46
    that.
47
47
 
48
 
    :param backend: The `Backend` where work is done.
49
48
    :param path: The directory where POTFILES.in resides.
50
49
    :returns: False if the directory does not exist, if an error occurred
51
50
        when executing intltool-update or if files are missing from
52
51
        POTFILES.in. True if all went fine and all files in POTFILES.in
53
52
        actually exist.
54
53
    """
55
 
    # Abort nicely if the directory does not exist.
56
 
    if not backend.isdir(path):
 
54
    current_path = os.getcwd()
 
55
 
 
56
    try:
 
57
        os.chdir(path)
 
58
    except OSError as e:
 
59
        # Abort nicely if the directory does not exist.
 
60
        if e.errno == errno.ENOENT:
 
61
            return False
 
62
        raise
 
63
    try:
 
64
        # Remove stale files from a previous run of intltool-update -m.
 
65
        for unlink_name in ['missing', 'notexist']:
 
66
            try:
 
67
                os.unlink(unlink_name)
 
68
            except OSError as e:
 
69
                # It's ok if the files are missing.
 
70
                if e.errno != errno.ENOENT:
 
71
                    raise
 
72
        devnull = open("/dev/null", "w")
 
73
        returncode = call(
 
74
            ["/usr/bin/intltool-update", "-m"],
 
75
            stdout=devnull, stderr=devnull)
 
76
        devnull.close()
 
77
    finally:
 
78
        os.chdir(current_path)
 
79
 
 
80
    if returncode != 0:
 
81
        # An error occurred when executing intltool-update.
57
82
        return False
58
 
    # Remove stale files from a previous run of intltool-update -m.
59
 
    backend.run(
60
 
        ["rm", "-f"] +
61
 
        [os.path.join(path, name) for name in ("missing", "notexist")])
62
 
    with open("/dev/null", "w") as devnull:
63
 
        try:
64
 
            backend.run(
65
 
                ["/usr/bin/intltool-update", "-m"],
66
 
                stdout=devnull, stderr=devnull, cwd=path)
67
 
        except subprocess.CalledProcessError:
68
 
            return False
69
 
 
70
 
    return not backend.path_exists(os.path.join(path, "notexist"))
71
 
 
72
 
 
73
 
def find_intltool_dirs(backend, package_dir):
 
83
 
 
84
    notexist = os.path.join(path, "notexist")
 
85
    return not os.access(notexist, os.R_OK)
 
86
 
 
87
 
 
88
def find_intltool_dirs():
74
89
    """Search for directories with intltool structure.
75
90
 
76
 
    `package_dir` and its subdirectories are searched. An 'intltool
 
91
    The current directory and its subdiretories are searched. An 'intltool
77
92
    structure' is a directory that contains a POFILES.in file and where all
78
93
    files listed in that POTFILES.in do actually exist. The latter
79
94
    condition makes sure that the file is not stale.
80
95
 
81
 
    :param backend: The `Backend` where work is done.
82
 
    :param package_dir: The directory to search.
83
 
    :returns: A list of directory names, relative to `package_dir`.
 
96
    :returns: A list of directory names.
84
97
    """
85
 
    return sorted(
86
 
        podir for podir in find_potfiles_in(backend, package_dir)
87
 
        if check_potfiles_in(backend, os.path.join(package_dir, podir)))
 
98
    return sorted(filter(check_potfiles_in, find_potfiles_in()))
88
99
 
89
100
 
90
101
def _get_AC_PACKAGE_NAME(config_file):
114
125
        config_files = config_files[:-1]
115
126
    for config_file in reversed(config_files):
116
127
        subst_value = config_file.getVariable(substitution.name)
117
 
        if subst_value is None and substitution.name == "PACKAGE":
118
 
            subst_value = _get_AC_PACKAGE_NAME(config_file)
119
128
        if subst_value is not None:
120
129
            # Substitution found.
121
130
            break
125
134
    return substitution.replace(subst_value)
126
135
 
127
136
 
128
 
def get_translation_domain(backend, dirname):
 
137
def get_translation_domain(dirname):
129
138
    """Get the translation domain for this PO directory.
130
139
 
131
140
    Imitates some of the behavior of intltool-update to find out which
154
163
    config_files = []
155
164
    for filename, varname, keep_trying in locations:
156
165
        path = os.path.join(dirname, filename)
157
 
        if not backend.path_exists(path):
 
166
        if not os.access(path, os.R_OK):
158
167
            # Skip non-existent files.
159
168
            continue
160
 
        with tempfile.NamedTemporaryFile() as local_file:
161
 
            backend.copy_out(path, local_file.name)
162
 
            config_files.append(ConfigFile(local_file.file))
 
169
        config_files.append(ConfigFile(path))
163
170
        new_value = config_files[-1].getVariable(varname)
164
171
        if new_value is not None:
165
172
            value = new_value
181
188
    return value
182
189
 
183
190
 
184
 
def generate_pot(backend, podir, domain):
 
191
@contextmanager
 
192
def chdir(directory):
 
193
    cwd = os.getcwd()
 
194
    os.chdir(directory)
 
195
    yield
 
196
    os.chdir(cwd)
 
197
 
 
198
 
 
199
def generate_pot(podir, domain):
185
200
    """Generate one PO template using intltool.
186
201
 
187
202
    Although 'intltool-update -p' can try to find out the translation domain
190
205
    "has an additional effect: the name of current working directory is no
191
206
    more  limited  to 'po' or 'po-*'." We don't want that limit either.
192
207
 
193
 
    :param backend: The `Backend` where work is done.
194
208
    :param podir: The PO directory in which to build template.
195
209
    :param domain: The translation domain to use as the name of the template.
196
210
      If it is None or empty, 'messages.pot' will be used.
197
 
    :return: The effective domain if generation succeeded, otherwise None.
 
211
    :return: True if generation succeeded.
198
212
    """
199
213
    if domain is None or domain.strip() == "":
200
214
        domain = "messages"
201
 
    with open("/dev/null", "w") as devnull:
202
 
        try:
203
 
            backend.run(
 
215
    with chdir(podir):
 
216
        with open("/dev/null", "w") as devnull:
 
217
            returncode = call(
204
218
                ["/usr/bin/intltool-update", "-p", "-g", domain],
205
 
                stdout=devnull, stderr=devnull, cwd=podir)
206
 
            return domain
207
 
        except subprocess.CalledProcessError:
208
 
            return None
209
 
 
210
 
 
211
 
def generate_pots(backend, package_dir):
 
219
                stdout=devnull, stderr=devnull)
 
220
    return returncode == 0
 
221
 
 
222
 
 
223
def generate_pots(package_dir='.'):
212
224
    """Top-level function to generate all PO templates in a package."""
213
225
    potpaths = []
214
 
    for podir in find_intltool_dirs(backend, package_dir):
215
 
        full_podir = os.path.join(package_dir, podir)
216
 
        domain = get_translation_domain(backend, full_podir)
217
 
        effective_domain = generate_pot(backend, full_podir, domain)
218
 
        if effective_domain is not None:
219
 
            potpaths.append(os.path.join(podir, effective_domain + ".pot"))
 
226
    with chdir(package_dir):
 
227
        for podir in find_intltool_dirs():
 
228
            domain = get_translation_domain(podir)
 
229
            if generate_pot(podir, domain):
 
230
                potpaths.append(os.path.join(podir, domain + ".pot"))
220
231
    return potpaths
221
232
 
222
233
 
223
 
class ConfigFile:
 
234
class ConfigFile(object):
224
235
    """Represent a config file and return variables defined in it."""
225
236
 
226
237
    def __init__(self, file_or_name):
227
 
        if isinstance(file_or_name, str):
228
 
            with open(file_or_name) as conf_file:
229
 
                self.content = conf_file.read()
230
 
        else:
231
 
            self.content = file_or_name.read()
 
238
        with open(file_or_name) as conf_file:
 
239
            self.content = conf_file.read()
232
240
 
233
241
    def _stripQuotes(self, identifier):
234
242
        """Strip surrounding quotes from `identifier`, if present.
283
291
    style) or preceded by a $ sign with optional () (make style).
284
292
 
285
293
    This class identifies a single such substitution in a variable text and
286
 
    extracts the name of the variable whose value is to be inserted. It also
 
294
    extract the name of the variable who's value is to be inserted. It also
287
295
    facilitates the actual replacement so that caller does not have to worry
288
296
    about the substitution style that is being used.
289
297
    """