~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to setup/util.py

  • Committer: William Grant
  • Date: 2009-01-13 01:36:15 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:1123
Merge setup-refactor branch. This completely breaks existing installations;
every path (both filesystem and Python) has changed. Do not upgrade without
knowing what you are doing.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE - Informatics Virtual Learning Environment
 
2
# Copyright (C) 2007-2008 The University of Melbourne
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 
 
18
# Module: setup/util
 
19
# Author: Matt Giuca, Refactored by David Coles
 
20
# Date:   02/07/2008
 
21
 
 
22
# setup/util.py
 
23
# Contains a set of functions useful for the setup program.
 
24
 
 
25
import os
 
26
import shutil
 
27
import errno
 
28
import sys
 
29
import string
 
30
import stat
 
31
import optparse
 
32
import mimetypes
 
33
 
 
34
__all__ = ['PYTHON_VERSION', 'copy_file_to_jail', 'RunError',
 
35
           'action_runprog', 'action_remove', 'action_rename', 'action_mkdir',
 
36
           'action_copytree', 'action_copylist', 'action_copyfile',
 
37
           'action_symlink', 'action_append', 'action_chown',
 
38
           'action_chown_setuid', 'action_chmod_x', 'action_make_private',
 
39
           'query_user', 'filter_mutate', 'get_svn_revision', 'InstallList',
 
40
           'wwwuid']
 
41
 
 
42
# Determine which Python version (2.4 or 2.5, for example) we are running,
 
43
# and use that as the filename to the Python directory.
 
44
# Just get the first 3 characters of sys.version.
 
45
PYTHON_VERSION = sys.version[0:3]
 
46
 
 
47
# Location of standard programs
 
48
RSYNC = '/usr/bin/rsync'
 
49
 
 
50
# UID of the Webserver
 
51
wwwuid = 33
 
52
 
 
53
def copy_file_to_jail(src, dry):
 
54
    """Copies a single file from an absolute location into the same location
 
55
    within the jail. src must begin with a '/'. The jail will be located
 
56
    in a 'jail' subdirectory of the current path."""
 
57
    action_copyfile(src, 'jail' + src, dry)
 
58
 
 
59
# The actions call Python os functions but print actions and handle dryness.
 
60
# May still throw os exceptions if errors occur.
 
61
 
 
62
class RunError:
 
63
    """Represents an error when running a program (nonzero return)."""
 
64
    def __init__(self, prog, retcode):
 
65
        self.prog = prog
 
66
        self.retcode = retcode
 
67
    def __str__(self):
 
68
        return str(self.prog) + " returned " + repr(self.retcode)
 
69
 
 
70
def action_runprog(prog, args, dry):
 
71
    """Runs a unix program. Searches in $PATH. Synchronous (waits for the
 
72
    program to return). Runs in the current environment. First prints the
 
73
    action as a "bash" line.
 
74
 
 
75
    Throws a RunError with a retcode of the return value of the program,
 
76
    if the program did not return 0.
 
77
 
 
78
    prog: String. Name of the program. (No path required, if in $PATH).
 
79
    args: [String]. Arguments to the program. (Note, this does not allow you to
 
80
        set argv[0]; it will always be prog.)
 
81
    dry: Bool. If True, prints but does not execute.
 
82
    """
 
83
    print prog, string.join(args, ' ')
 
84
    if dry: return
 
85
    ret = os.spawnvp(os.P_WAIT, prog, [prog] + args)
 
86
    if ret != 0:
 
87
        raise RunError(prog, ret)
 
88
 
 
89
def action_remove(path, dry):
 
90
    """Calls rmtree, deleting the target file if it exists."""
 
91
    try:
 
92
        print "rm -r", path
 
93
        if not dry:
 
94
            shutil.rmtree(path, True)
 
95
    except OSError, (err, msg):
 
96
        if err != errno.EEXIST:
 
97
            raise
 
98
        # Otherwise, didn't exist, so we don't care
 
99
 
 
100
def action_rename(src, dst, dry):
 
101
    """Calls rename. Deletes the target if it already exists."""
 
102
    action_remove(dst, dry)
 
103
    print "mv ", src, dst
 
104
    if dry: return
 
105
    try:
 
106
        os.rename(src, dst)
 
107
    except OSError, (err, msg):
 
108
        if err != errno.EEXIST:
 
109
            raise
 
110
 
 
111
def action_mkdir(path, dry):
 
112
    """Calls mkdir. Silently ignored if the directory already exists.
 
113
    Creates all parent directories as necessary."""
 
114
    print "mkdir -p", path
 
115
    if dry: return
 
116
    try:
 
117
        os.makedirs(path)
 
118
    except OSError, (err, msg):
 
119
        if err != errno.EEXIST:
 
120
            raise
 
121
 
 
122
def action_copytree(src, dst, dry):
 
123
    """Copies an entire directory tree. Symlinks are seen as normal files and
 
124
    copies of the entire file (not the link) are made. Creates all parent
 
125
    directories as necessary.
 
126
 
 
127
    See shutil.copytree."""
 
128
    # Allow copying over itself
 
129
    if (os.path.normpath(os.path.join(os.getcwd(),src)) ==
 
130
        os.path.normpath(os.path.join(os.getcwd(),dst))):
 
131
        return
 
132
    
 
133
    # Try to do the copy with rsync, if that fails just copy
 
134
    try:
 
135
        action_runprog(RSYNC, ['-a','--delete',src + '/',dst], dry)
 
136
    except RunError:
 
137
        if dry: return
 
138
        action_remove(dst, dry)
 
139
        print "cp -r", src, dst
 
140
        shutil.copytree(src, dst, True)
 
141
 
 
142
def action_copylist(srclist, dst, dry, srcdir=".", onlybasename=False):
 
143
    """Copies all files in a list to a new location. The files in the list
 
144
    are read relative to the current directory, and their destinations are the
 
145
    same paths relative to dst. Creates all parent directories as necessary.
 
146
    srcdir is "." by default, can be overridden.
 
147
 
 
148
    If onlybasename is True, only the basename of the source is appended to
 
149
    the destination.
 
150
    """
 
151
    for srcfile in srclist:
 
152
        if onlybasename:
 
153
            dstfile = os.path.join(dst, os.path.basename(srcfile))
 
154
        else:
 
155
            dstfile = os.path.join(dst, srcfile)
 
156
        srcfile = os.path.join(srcdir, srcfile)
 
157
        dstdir = os.path.split(dstfile)[0]
 
158
        if not os.path.isdir(dstdir):
 
159
            action_mkdir(dstdir, dry)
 
160
        print "cp -f", srcfile, dstfile
 
161
        if not dry:
 
162
            try:
 
163
                shutil.copyfile(srcfile, dstfile)
 
164
                shutil.copymode(srcfile, dstfile)
 
165
            except shutil.Error:
 
166
                pass
 
167
 
 
168
def action_copyfile(src, dst, dry):
 
169
    """Copies one file to a new location. Creates all parent directories
 
170
    as necessary.
 
171
    Warn if file not found.
 
172
    """
 
173
    dstdir = os.path.split(dst)[0]
 
174
    if not os.path.isdir(dstdir):
 
175
        action_mkdir(dstdir, dry)
 
176
    print "cp -f", src, dst
 
177
    if not dry:
 
178
        try:
 
179
            shutil.copyfile(src, dst)
 
180
            shutil.copymode(src, dst)
 
181
        except (shutil.Error, IOError), e:
 
182
            print "Warning: " + str(e)
 
183
 
 
184
def action_symlink(src, dst, dry):
 
185
    """Creates a symlink in a given location. Creates all parent directories
 
186
    as necessary.
 
187
    """
 
188
    dstdir = os.path.split(dst)[0]
 
189
    if not os.path.isdir(dstdir):
 
190
        action_mkdir(dstdir, dry)
 
191
    # Delete existing file
 
192
    if os.path.exists(dst):
 
193
        os.remove(dst)
 
194
    print "ln -fs", src, dst
 
195
    if not dry:
 
196
        os.symlink(src, dst)
 
197
 
 
198
def action_append(ivle_pth, ivle_www):
 
199
    file = open(ivle_pth, 'a+')
 
200
    file.write(ivle_www + '\n')
 
201
    file.close()
 
202
 
 
203
def action_chown_setuid(file, dry):
 
204
    """Chowns a file to root, and sets the setuid bit on the file.
 
205
    Calling this function requires the euid to be root.
 
206
    The actual mode of path is set to: rws--s--s
 
207
    """
 
208
    print "chown root:root", file
 
209
    if not dry:
 
210
        os.chown(file, 0, 0)
 
211
    print "chmod a+xs", file
 
212
    print "chmod u+rw", file
 
213
    if not dry:
 
214
        os.chmod(file, stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
 
215
            | stat.S_ISUID | stat.S_IRUSR | stat.S_IWUSR)
 
216
 
 
217
def action_chmod_x(file, dry):
 
218
    """Chmod 755 a file (sets permissions to rwxr-xr-x)."""
 
219
    print "chmod 755", file
 
220
    if not dry:
 
221
        os.chmod(file, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR
 
222
            | stat.S_IXGRP | stat.S_IRGRP | stat.S_IXOTH | stat.S_IROTH)
 
223
 
 
224
def action_chown(file, uid, gid, dry):
 
225
    """Chowns a file to the specified numeric UID and GID."""
 
226
    print "chown %s:%s %s"%(uid, gid, file)
 
227
    if not dry:
 
228
        os.chown(file, uid, gid)
 
229
 
 
230
def action_make_private(file, dry):
 
231
    """Ensures that a file is private to IVLE (chowns to www-data and chmod to 
 
232
    600)"""
 
233
    action_chown(file, wwwuid, wwwuid, dry)
 
234
    print "chmod 600", file
 
235
    if not dry:
 
236
        os.chmod(file, stat.S_IRUSR | stat.S_IWUSR)
 
237
 
 
238
def query_user(default, prompt):
 
239
    """Prompts the user for a string, which is read from a line of stdin.
 
240
    Exits silently if EOF is encountered. Returns the string, with spaces
 
241
    removed from the beginning and end.
 
242
 
 
243
    Returns default if a 0-length line (after spaces removed) was read.
 
244
    """
 
245
    if default is None:
 
246
        # A default of None means the value will be computed specially, so we
 
247
        # can't really tell you what it is
 
248
        defaultstr = "computed"
 
249
    elif isinstance(default, basestring):
 
250
        defaultstr = '"%s"' % default
 
251
    else:
 
252
        defaultstr = repr(default)
 
253
    sys.stdout.write('%s\n    (default: %s)\n>' % (prompt, defaultstr))
 
254
    try:
 
255
        val = sys.stdin.readline()
 
256
    except KeyboardInterrupt:
 
257
        # Ctrl+C
 
258
        sys.stdout.write("\n")
 
259
        sys.exit(1)
 
260
    sys.stdout.write("\n")
 
261
    # If EOF, exit
 
262
    if val == '': sys.exit(1)
 
263
    # If empty line, return default
 
264
    val = val.strip()
 
265
    if val == '': return default
 
266
    return val
 
267
 
 
268
def filter_mutate(function, list):
 
269
    """Like built-in filter, but mutates the given list instead of returning a
 
270
    new one. Returns None."""
 
271
    i = len(list)-1
 
272
    while i >= 0:
 
273
        # Delete elements which do not match
 
274
        if not function(list[i]):
 
275
            del list[i]
 
276
        i -= 1
 
277
 
 
278
def get_svn_revision():
 
279
    """Returns either the current SVN revision of this build, or None"""
 
280
    import pysvn
 
281
    try:
 
282
        svn = pysvn.Client()
 
283
        entry = svn.info('.')
 
284
        revnum = entry.revision.number
 
285
    except pysvn.ClientError, e:
 
286
        revnum = None
 
287
    return revnum
 
288
 
 
289
### InstallList and helpers ###
 
290
 
 
291
# Mime types which will automatically be placed in the list by InstallList.
 
292
installlist_mimetypes = ['text/x-python', 'text/html',
 
293
    'application/x-javascript', 'application/javascript',
 
294
    'text/css', 'image/png', 'image/gif', 'application/xml']
 
295
 
 
296
def build_list_py_files(dir, no_top_level=False):
 
297
    """Builds a list of all py files found in a directory and its
 
298
    subdirectories. Returns this as a list of strings.
 
299
    no_top_level=True means the file paths will not include the top-level
 
300
    directory.
 
301
    """
 
302
    pylist = []
 
303
    for (dirpath, dirnames, filenames) in os.walk(dir):
 
304
        # Exclude directories beginning with a '.' (such as '.svn')
 
305
        filter_mutate(lambda x: x[0] != '.', dirnames)
 
306
        # All *.py files are added to the list
 
307
        pylist += [os.path.join(dirpath, item) for item in filenames
 
308
            if mimetypes.guess_type(item)[0] in installlist_mimetypes]
 
309
    if no_top_level:
 
310
        for i in range(0, len(pylist)):
 
311
            _, pylist[i] = pylist[i].split(os.sep, 1)
 
312
    return pylist
 
313
 
 
314
class InstallList(object):
 
315
    # We build two separate lists, by walking www and console
 
316
    list_www = property(lambda self: build_list_py_files('www'))
 
317
 
 
318
    list_ivle_lib = property(lambda self: build_list_py_files('ivle'))
 
319
 
 
320
    list_subjects = property(lambda self: build_list_py_files('subjects',
 
321
                             no_top_level=True))
 
322
 
 
323
    list_exercises = property(lambda self: build_list_py_files('exercises',
 
324
                              no_top_level=True))
 
325
 
 
326
    list_services = [
 
327
        "services/python-console",
 
328
        "services/fileservice",
 
329
        "services/serveservice",
 
330
        "services/interpretservice",
 
331
        "services/usrmgt-server",
 
332
        "services/diffservice",
 
333
        "services/svnlogservice",
 
334
    ]
 
335
 
 
336
    list_user_binaries = [
 
337
        "bin/ivle-enrol",
 
338
        "bin/ivle-enrolallusers",
 
339
        "bin/ivle-listusers",
 
340
        "bin/ivle-makeuser",
 
341
        "bin/ivle-marks",
 
342
        "bin/ivle-mountallusers",
 
343
        "bin/ivle-remakeuser",
 
344
        "bin/ivle-showenrolment",
 
345
    ]