|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 """ |
|
4 Example Usage |
|
5 ============= |
|
6 |
|
7 The following commands can be run from the root directory of the Mercurial |
|
8 repo. To run ``paver``, however, you'll need to do ``easy_install Paver``. |
|
9 Most of the following commands accept other arguments; see ``command --help`` |
|
10 for more information, or ``paver help`` for a list of all the valid commands. |
|
11 |
|
12 ``paver build`` |
|
13 Builds the project. This essentially just runs a bunch of other tasks, |
|
14 like ``pylint``, ``django_zip`` and ``tinymce_zip``, etc. |
|
15 ``paver pylint`` |
|
16 Runs PyLint on the project. |
|
17 ``paver django_zip`` |
|
18 Builds the Django zip file. |
|
19 ``paver tinymce_zip`` |
|
20 Builds the TinyMCE zip file. |
|
21 |
|
22 If you specify ``--dry-run`` before a task, then the action of that task will |
|
23 not actually be carried out, although logging output will be displayed as if |
|
24 it were. For example, you could run ``paver --dry-run django_zip`` to see what |
|
25 files would be added to the ``django.zip`` file, etc. |
|
26 """ |
|
27 |
|
28 from cStringIO import StringIO |
|
29 import sys |
|
30 import zipfile |
|
31 |
|
32 import paver |
|
33 import paver.easy |
|
34 import paver.tasks |
|
35 from paver.easy import * |
|
36 from paver.path import path |
|
37 |
|
38 |
|
39 # Paver comes with Jason Orendorff's 'path' module; this makes path |
|
40 # manipulation easy and far more readable. |
|
41 PROJECT_DIR = path(__file__).dirname() |
|
42 |
|
43 |
|
44 # Set some default options. Having the options at the top of the file cleans |
|
45 # the whole thing up and makes the behaviour a lot more configurable. |
|
46 options( |
|
47 build = Bunch( |
|
48 project_dir = PROJECT_DIR, |
|
49 app_build = PROJECT_DIR / 'build', |
|
50 app_folder = PROJECT_DIR / 'app', |
|
51 app_files = ['app.yaml', 'cron.yaml', 'index.yaml', 'main.py', |
|
52 'settings.py', 'shell.py', 'urls.py', 'gae_django.py'], |
|
53 app_dirs = ['soc', 'ghop', 'gsoc', 'feedparser', 'python25src', |
|
54 'reflistprop', 'jquery', 'ranklist', 'shell', 'json', |
|
55 'htmlsanitizer', 'taggable-mixin', 'gviz'], |
|
56 zip_files = ['tiny_mce.zip'], |
|
57 skip_pylint = False, |
|
58 ) |
|
59 ) |
|
60 |
|
61 # The second call to options allows us to re-use some of the constants defined |
|
62 # in the first call. |
|
63 options( |
|
64 clean_build = options.build, |
|
65 tinymce_zip = options.build, |
|
66 |
|
67 django_zip = Bunch( |
|
68 prune_dirs = ['.svn', 'gis', 'admin', 'localflavor', 'mysql', |
|
69 'mysql_old', 'oracle', 'postgresql', |
|
70 'postgresql_psycopg2', 'sqlite3', 'test'], |
|
71 **options.build |
|
72 ), |
|
73 |
|
74 pylint = Bunch( |
|
75 check_modules = ['soc', 'reflistprop', 'settings.py', 'urls.py', |
|
76 'main.py'], |
|
77 quiet = False, |
|
78 quiet_args = ['--disable-msg=W0511,R0401', '--reports=no', |
|
79 '--disable-checker=similarities'], |
|
80 pylint_args = [], |
|
81 ignore = False, |
|
82 **options.build |
|
83 ) |
|
84 ) |
|
85 |
|
86 |
|
87 # Utility functions |
|
88 |
|
89 def django_zip_files(django_src_dir): |
|
90 """Yields each filename which should go into ``django.zip``.""" |
|
91 for filename in django_src_dir.walkfiles(): |
|
92 # The following seems unnecessarily unreadable, but unfortunately |
|
93 # it seems it cannot be unobfuscated any more (if someone finds a |
|
94 # nicer way of writing it, please tell me). |
|
95 if not (filename.ext in ['.pyc', '.pyo', '.po', '.mo'] or |
|
96 any(name in filename.splitall() |
|
97 for name in options.django_zip.prune_dirs)): |
|
98 # The filename is suitable to be added to the archive. In this |
|
99 # case, we yield the filename and the name it should be given in |
|
100 # the Zip archive. |
|
101 paver.tasks.environment.info( |
|
102 '%-4sdjango.zip <- %s', '', filename) |
|
103 arcname = path('django') / django_src_dir.relpathto(filename) |
|
104 yield filename, arcname |
|
105 |
|
106 |
|
107 def tinymce_zip_files(tiny_mce_dir): |
|
108 """Yields each filename which should go into ``tiny_mce.zip``.""" |
|
109 for filename in tiny_mce_dir.walkfiles(): |
|
110 if '.svn' not in filename.splitall(): |
|
111 # In this case, `tiny_mce/*` is in the root of the zip file, so |
|
112 # we do not need to prefix `arcname` with 'tinymce/' (like we did |
|
113 # with `django.zip`). |
|
114 paver.tasks.environment.info( |
|
115 '%-4stiny_mce.zip <- %s', '', filename) |
|
116 arcname = tiny_mce_dir.relpathto(filename) |
|
117 yield filename, arcname |
|
118 |
|
119 |
|
120 def write_zip_file(zip_file_handle, files): |
|
121 if paver.tasks.environment.dry_run: |
|
122 for args in files: |
|
123 pass |
|
124 return |
|
125 zip_file = zipfile.ZipFile(zip_file_handle, mode='w') |
|
126 for args in files: |
|
127 zip_file.write(*args) |
|
128 zip_file.close() |
|
129 |
|
130 |
|
131 def symlink(target, link_name): |
|
132 if hasattr(target, 'symlink'): |
|
133 target.symlink(link_name) |
|
134 else: |
|
135 # If we are on a platform where symlinks are not supported (such as |
|
136 # Windows), simply copy the files across. |
|
137 target.copy(link_name) |
|
138 |
|
139 |
|
140 # Tasks |
|
141 |
|
142 |
|
143 @task |
|
144 @cmdopts([ |
|
145 ('app-folder=', 'a', 'App folder directory (default /app)'), |
|
146 ('pylint-command=', 'c', 'Specify a custom pylint executable'), |
|
147 ('quiet', 'q', 'Disables a lot of the pylint output'), |
|
148 ('ignore', 'i', 'Ignore PyLint errors') |
|
149 ]) |
|
150 def pylint(options): |
|
151 """Check the source code using PyLint.""" |
|
152 from pylint import lint |
|
153 |
|
154 # Initial command. |
|
155 arguments = [] |
|
156 |
|
157 if options.quiet: |
|
158 arguments.extend(options.quiet_args) |
|
159 if 'pylint_args' in options: |
|
160 arguments.extend(list(options.pylint_args)) |
|
161 |
|
162 # Add the list of paths containing the modules to check using PyLint. |
|
163 arguments.extend( |
|
164 str(options.app_folder / module) for module in options.check_modules) |
|
165 |
|
166 # By placing run_pylint into its own function, it allows us to do dry runs |
|
167 # without actually running PyLint. |
|
168 def run_pylint(): |
|
169 # Add app folder to path. |
|
170 sys.path.insert(0, options.app_folder.abspath()) |
|
171 # Add google_appengine directory to path. |
|
172 sys.path.insert(0, |
|
173 options.project_dir.abspath() / |
|
174 'thirdparty' / 'google_appengine') |
|
175 |
|
176 # Specify PyLint RC file. |
|
177 arguments.append('--rcfile=' + |
|
178 options.project_dir.abspath() / |
|
179 'scripts' / 'pylint' / 'pylintrc') |
|
180 |
|
181 # `lint.Run.__init__` runs the PyLint command. |
|
182 try: |
|
183 lint.Run(arguments) |
|
184 # PyLint will `sys.exit()` when it has finished, so we need to catch |
|
185 # the exception and process it accordingly. |
|
186 except SystemExit, exc: |
|
187 return_code = exc.args[0] |
|
188 if return_code != 0 and (not options.pylint.ignore): |
|
189 raise paver.tasks.BuildFailure( |
|
190 'PyLint finished with a non-zero exit code') |
|
191 |
|
192 return dry('pylint ' + ' '.join(arguments), run_pylint) |
|
193 |
|
194 |
|
195 @task |
|
196 @cmdopts([ |
|
197 ('app-build=', 'b', 'App build directory (default /build)'), |
|
198 ('app-folder=', 'a', 'App folder directory (default /app)'), |
|
199 ('skip-pylint', 's', 'Skip PyLint checker'), |
|
200 ('ignore-pylint', 'i', 'Ignore results of PyLint (but run it anyway)'), |
|
201 ('quiet-pylint', 'q', 'Make PyLint run quietly'), |
|
202 ]) |
|
203 def build(options): |
|
204 """Build the project.""" |
|
205 # If `--skip-pylint` is not provided, run PyLint. |
|
206 if not options.skip_pylint: |
|
207 # If `--ignore-pylint` is provided, act as if `paver pylint --ignore` |
|
208 # was run. Likewise for `--quiet-pylint`. |
|
209 if options.get('ignore_pylint', False): |
|
210 options.pylint.ignore = True |
|
211 if options.get('quiet_pylint', False): |
|
212 options.pylint.quiet = True |
|
213 pylint(options) |
|
214 |
|
215 # Clean old generated zip files from the app folder. |
|
216 clean_zip(options) |
|
217 |
|
218 # Clean the App build directory by removing and re-creating it. |
|
219 clean_build(options) |
|
220 |
|
221 # Build the django.zip file. |
|
222 django_zip(options) |
|
223 |
|
224 # Build the tiny_mce.zip file. |
|
225 tinymce_zip(options) |
|
226 |
|
227 # Make the necessary symlinks between the app and build directories. |
|
228 build_symlinks(options) |
|
229 |
|
230 |
|
231 @task |
|
232 @cmdopts([ |
|
233 ('app-build=', 'b', 'App build directory (default /build)'), |
|
234 ('app-folder=', 'a', 'App folder directory (default /app)'), |
|
235 ]) |
|
236 def build_symlinks(options): |
|
237 """Build symlinks between the app and build folders.""" |
|
238 # Create the symbolic links from the app folder to the build folder. |
|
239 for filename in options.app_files + options.app_dirs + options.zip_files: |
|
240 # The `symlink()` function handles discrepancies between platforms. |
|
241 target = path(options.app_folder) / filename |
|
242 link = path(options.app_build) / filename |
|
243 dry( |
|
244 '%-4s%-20s <- %s' % ('', target, link), |
|
245 lambda: symlink(target, link)) |
|
246 |
|
247 |
|
248 @task |
|
249 @cmdopts([ |
|
250 ('app-build=', 'b', 'App build directory (default /build)'), |
|
251 ]) |
|
252 def clean_build(options): |
|
253 """Clean the build folder.""" |
|
254 # Not checking this could cause an error when trying to remove a |
|
255 # non-existent file. |
|
256 if path(options.app_build).exists(): |
|
257 path(options.app_build).rmtree() |
|
258 path(options.app_build).makedirs() |
|
259 |
|
260 |
|
261 @task |
|
262 @cmdopts([ |
|
263 ('app-folder=', 'a', 'App folder directory (default /app)'), |
|
264 ]) |
|
265 def clean_zip(options): |
|
266 """Remove all the generated zip files from the app folder.""" |
|
267 for zip_file in options.zip_files + ['django.zip']: |
|
268 zip_path = path(options.app_folder) / zip_file |
|
269 if zip_path.exists(): |
|
270 zip_path.remove() |
|
271 |
|
272 |
|
273 @task |
|
274 @cmdopts([ |
|
275 ('app-build=', 'b', 'App build directory (default /build)'), |
|
276 ('app-folder=', 'a', 'App folder directory (default /app)'), |
|
277 ]) |
|
278 def django_zip(options): |
|
279 """Create the zip file containing Django (minus unwanted stuff).""" |
|
280 # Write the `django.zip` file. This requires finding all of the necessary |
|
281 # files and writing them to a `zipfile.ZipFile` instance. Python's |
|
282 # stdlib `zipfile` module is written in C, so it's fast and doesn't incur |
|
283 # much overhead. |
|
284 django_src_dir = path(options.app_folder) / 'django' |
|
285 django_zip_filename = path(options.app_build) / 'django.zip' |
|
286 if paver.tasks.environment.dry_run: |
|
287 django_zip_fp = StringIO() |
|
288 else: |
|
289 # Ensure the parent directories exist. |
|
290 django_zip_filename.dirname().makedirs() |
|
291 django_zip_fp = open(django_zip_filename, mode='w') |
|
292 |
|
293 # Write the zip file to disk; this uses the `write_zip_file()` function |
|
294 # defined above. The try/except/finally makes sure the `django.zip` file |
|
295 # handle is properly closed no matter what happens. |
|
296 try: |
|
297 write_zip_file(django_zip_fp, django_zip_files(django_src_dir)) |
|
298 except Exception, exc: |
|
299 # Close and delete the (possibly corrupted) `django.zip` file. |
|
300 django_zip_fp.close() |
|
301 django_zip_filename.remove() |
|
302 # Raise the error, causing Paver to exit with a non-zero exit code. |
|
303 raise paver.tasks.BuildFailure( |
|
304 'Error occurred creating django.zip: %r' % (exc,)) |
|
305 finally: |
|
306 # Close the file handle if it isn't already. |
|
307 if not django_zip_fp.closed: |
|
308 django_zip_fp.close() |
|
309 |
|
310 |
|
311 @task |
|
312 @cmdopts([ |
|
313 ('app-folder=', 'a', 'App folder directory (default /app)'), |
|
314 ]) |
|
315 def tinymce_zip(options): |
|
316 """Create the zip file containing TinyMCE.""" |
|
317 # This is very similar to django_zip; see the comments there for |
|
318 # explanations. |
|
319 tinymce_dir = path(options.app_folder) / 'tiny_mce' |
|
320 tinymce_zip_filename = path(options.app_folder) / 'tiny_mce.zip' |
|
321 if paver.tasks.environment.dry_run: |
|
322 tinymce_zip_fp = StringIO() |
|
323 else: |
|
324 # Ensure the parent directories exist. |
|
325 tinymce_zip_filename.dirname().makedirs() |
|
326 tinymce_zip_fp = open(tinymce_zip_filename, mode='w') |
|
327 |
|
328 try: |
|
329 write_zip_file(tinymce_zip_fp, tinymce_zip_files(tinymce_dir)) |
|
330 except Exception, exc: |
|
331 tinymce_zip_fp.close() |
|
332 tinymce_zip_filename.remove() |
|
333 raise paver.tasks.BuildFailure( |
|
334 'Error occurred creating tinymce.zip: %r' % (exc,)) |
|
335 finally: |
|
336 if not tinymce_zip_fp.closed: |
|
337 tinymce_zip_fp.close() |