File: builder.py

package info (click to toggle)
reinteract 0.5.0-3
  • links: PTS, VCS
  • area: main
  • in suites: squeeze, wheezy
  • size: 1,884 kB
  • ctags: 1,244
  • sloc: python: 9,276; sh: 3,883; xml: 780; objc: 311; makefile: 253; ansic: 201
file content (240 lines) | stat: -rw-r--r-- 9,440 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# Copyright 2008 Owen Taylor
#
# This file is part of Reinteract and distributed under the terms
# of the BSD license. See the file COPYING in the Reinteract
# distribution for full details.
#
########################################################################

import glob
import logging
import os
import re
import shutil
import tempfile

from am_parser import AMParser
from utils import check_call

_logger = logging.getLogger("Builder")

class Builder(object):
    """
    The Builder class contains code for assembling a tree of files from various
    sources (arbitrary files on the filesystem, Python modules from the Python
    path, files in the Reinteract source tree.) This is then subclassed to build
    that tree of files into an installer.
    """

    def __init__(self, topdir, treesubdir='Reinteract'):
        self.topdir = topdir
        self.file_attributes = {}
        self.main_am = None

        self.tempdir = tempfile.mkdtemp("", "reinteract_build.")
        self.treedir = os.path.join(self.tempdir, treesubdir)

        _logger.debug("Top source directory is %s", topdir)
        _logger.info("Temporary directory is %s", self.tempdir)

    def cleanup(self):
        """Remove temporary files"""

        try:
            shutil.rmtree(self.tempdir)
        except:
            pass

    def add_file(self, source, directory, **attributes):
        """
        Add a file into the temporary tree

        @param source: the file to add. If relative, it is taken to be relative
          to the top of the Reinteract source distribution.
        @param directory: directory to add it in (relative to the top of the temporary tree)
        @param attributes: attributes to store for the file

        """
        absdir = os.path.join(self.treedir, directory)
        if os.path.isabs(source):
            abssource = source
        else:
            abssource = os.path.join(self.topdir, source)
        _logger.debug("Copying %s to %s", abssource, directory)
        if not os.path.isdir(absdir):
            os.makedirs(absdir)
        absdest = os.path.join(absdir, os.path.basename(source))
        shutil.copy(abssource, absdest)

        relative = os.path.normpath(os.path.join(directory, os.path.basename(source)))

        self.file_attributes[relative] = attributes

    def add_files_from_directory(self, sourcedir, directory, **attributes):
        """
        Add a directory of files into the temporary tree

        @param source: the directory to add. If relative, it is taken to be relative
          to the top of the Reinteract source distribution.
        @param directory: directory put files into (relative to the top of the temporary tree)
        @param attributes: attributes passed to add_file() for each file

        """

        if os.path.isabs(sourcedir):
            abssourcedir = sourcedir
        else:
            abssourcedir = os.path.join(self.topdir, sourcedir)

        for f in os.listdir(abssourcedir):
            absf = os.path.join(abssourcedir, f)
            if os.path.isdir(absf):
                self.add_files_from_directory(absf, os.path.join(directory, f), **attributes)
            else:
                self.add_file(absf, directory, **attributes)

    def add_matching_files(self, sourcedir, patterns, directory, **attributes):
        """
        Add files that set a list of patterns into the temprary tree

        @param sourcedir: source directory that the patterns are relative (must be absolute)
        @param patterns: list of patterns. These can contain shell-style '*' globs
        @param directory: directory put files into (relative to the top of the temporary tree)
        @param attributes: attributes passed to add_file() for each file

        """

        for f in patterns:
            absf = os.path.join(sourcedir, f)
            if f.find('*') >= 0:
                for ff in glob.iglob(absf):
                    relative = ff[len(sourcedir) + 1:]
                    if os.path.isdir(ff):
                        self.add_files_from_directory(ff, os.path.join(directory, relative), **attributes)
                    else:
                        destdir = os.path.join(directory, os.path.dirname(relative))
                        self.add_file(ff, destdir, **attributes)
            elif os.path.isdir(absf):
                self.add_files_from_directory(absf, os.path.join(directory, f), **attributes)
            else:
                destdir = os.path.join(directory, os.path.dirname(f))
                self.add_file(absf, destdir, **attributes)

    def add_external_module(self, module_name, directory, **attributes):
        """
        Add files from a Python module in the current Python path into the temporary tree

        If the python module is a package, all files in the package directory are added;
        as well as Python files, byte-code compiled python files, and shared libraries,
        this might include data files, examples, and so forth.

        @param module_name: name of the module to import
        @param directory: directory to copy module into (relative to treedir)
        @param attributes: attributes passed to add_file() for each file

        """

        mod = __import__(module_name)
        f = mod.__file__
        if f.endswith('__init__.pyc') or f.endswith('__init__.pyo') or f.endswith('__init__.py'):
            dir = os.path.dirname(f)
            self.add_files_from_directory(dir, os.path.join(directory, os.path.basename(dir)), **attributes)
        else:
            if f.endswith('.pyc') or f.endswith('.pyo'):
                # Don't worry about getting the compiled files, we'll recompile anyways
                f = f[:-3] + "py"
            self.add_file(f, directory, **attributes)

    def add_files_from_am(self, relative, directory, **attributes):
        """
        Add files listed in a Makefile.am into the temporary tree

        The files that are added are files that are listed as _DATA or _PYTHON.
        Recursion is done via SUBDIRS. automake conditionals are ignored.

        @param relative: path relative to the Reinteract source topdir of the
          directory where the Makefile.am lives
        @param directory: directory to copy files into (relative to tempory tree)
        @param attributes: attributes passed to add_file() for each file

        """

        am_file = os.path.join(self.topdir, relative, 'Makefile.am')
        am_parser = AMParser(am_file,
           {
                'bindir' : 'bin',
                'docdir' : 'doc',
                'examplesdir' : 'examples',
                'pkgdatadir' : '.',
                'datadir' : '.',
                'pythondir' : 'python',
                'REINTERACT_PACKAGE_DIR' : 'python/reinteract',

                # Some config variables irrelevant for our purposes
                'PYTHON_INCLUDES' : '',
                'PYTHON_LIBS' : '',
                'WRAPPER_CFLAGS' : ''
           })
        if relative == '':
            self.main_am = am_parser

        for k, v in am_parser.iteritems():
            if k.endswith("_DATA"):
                base = k[:-5]
                dir = am_parser[base + 'dir']
                for x in v.split():
                    self.add_file(os.path.join(relative, x), os.path.join(directory, dir), **attributes)
            elif k.endswith("_PYTHON"):
                base = k[:-7]
                dir = am_parser[base + 'dir']
                for x in v.split():
                    self.add_file(os.path.join(relative, x), os.path.join(directory, dir), **attributes)

        if 'SUBDIRS' in am_parser:
            for subdir in am_parser['SUBDIRS'].split():
                if subdir == '.':
                    continue
                self.add_files_from_am(os.path.join(relative, subdir), directory, **attributes)


    def compile_python(self):
        """
        Byte-compile all Python files in the tree to .pyc and .pyo
        """

        # I'm not really sure that there is a point in having the .pyc files distributed
        # but it matches what distutils and Fedora RPM packaging do.
        check_call(['python', os.path.join(self.topdir, 'tools', 'compiledir.py'), self.treedir])
        check_call(['python', "-O", os.path.join(self.topdir, 'tools', 'compiledir.py'), self.treedir])

    def get_file_attributes(self, relative):
        """
        Get the attributes dictionary for a file

        @param relative: location of the file in the temporary tree

        """
        # .pyc/.pyo are added when we byte-compile the .py files, so they
        # may not be in file_attributes[], so look for the base .py instead
        # to figure out the right feature
        if relative.endswith(".pyc") or relative.endswith(".pyo"):
            relative = relative[:-3] + "py"
            # Handle byte compiled .pyw, though they don't seem to be
            # generated in practice
            if not relative in self.file_attributes:
                relative += "w"

        return self.file_attributes[relative]

    def get_version(self):
        """
        Get the Reinteract version from configure.ac
        """

        ac_file = os.path.join(self.topdir, 'configure.ac')
        f = open(ac_file, "r")
        contents = f.read()
        f.close()
        m = re.search(r'^\s*AC_INIT\s*\(\s*[A-Za-z0-9_.-]+\s*,\s*([0-9.]+)\s*\)\s*$', contents, re.MULTILINE)
        assert m
        return m.group(1)