Don't show compile minutes when no nested minutes
[cds-indico.git] / fabfile.py
blob2e35b8f146ac1ad86c07d6df09e427ad6f64fcc1
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 """
18 fabfile for Indico development operations
19 """
21 import os
22 import re
23 import sys
24 import glob
25 import shutil
26 import requests
27 import json
28 import getpass
29 from contextlib import contextmanager
30 import operator
32 from fabric.api import local, lcd, task, env
33 from fabric.context_managers import prefix, settings
34 from fabric.colors import red, green, yellow, cyan
35 from fabric.contrib import console
36 from fabric.operations import put, run
39 ASSET_TYPES = ['js', 'sass', 'css']
40 DOC_DIRS = ['guides']
41 RECIPES = {}
42 DEFAULT_REQUEST_ERROR_MSG = 'UNDEFINED ERROR (no error description from server)'
43 CONF_FILE_NAME = 'fabfile.conf'
44 SOURCE_DIR = os.path.dirname(__file__)
46 execfile(CONF_FILE_NAME, {}, env)
48 env.update({
49 'conf': CONF_FILE_NAME,
50 'src_dir': SOURCE_DIR,
51 'ext_dir': os.path.join(SOURCE_DIR, env.ext_dirname),
52 'target_dir': os.path.join(SOURCE_DIR, env.target_dirname),
53 'node_env_path': os.path.join(SOURCE_DIR, env.node_env_dirname)
57 def recipe(name):
58 def _wrapper(f):
59 RECIPES[name] = f
60 return _wrapper
63 # Decorators
65 @contextmanager
66 def node_env():
67 if env.system_node:
68 yield
69 else:
70 with prefix('. {0}'.format(os.path.join(env.node_env_path, 'bin/activate'))):
71 yield
74 @contextmanager
75 def pyenv_env(version):
76 cmd_dir = os.path.join(env.pyenv_dir, 'versions', 'indico-build-{0}'.format(version), 'bin')
77 with prefix('PATH={0}:$PATH'.format(cmd_dir)):
78 yield
81 def pyenv_cmd(cmd, **kwargs):
82 cmd_dir = os.path.join(env.pyenv_dir, 'bin')
83 return local('{0}/pyenv {1}'.format(cmd_dir, cmd), **kwargs)
86 # Util functions
88 def _yes_no_input(message, default):
89 c = '? '
90 if default.lower() == 'y':
91 c = ' [Y/n]? '
92 elif default.lower() == 'n':
93 c = ' [y/N]? '
94 s = raw_input(message+c) or default
95 if s.lower() == 'y':
96 return True
97 else:
98 return False
101 def _putl(source_file, dest_dir):
103 To be used instead of put, since it doesn't support symbolic links
106 put(source_file, '/')
107 run("mkdir -p {0}".format(dest_dir))
108 run("mv -f /{0} {1}".format(os.path.basename(source_file), dest_dir))
111 def create_node_env():
112 with settings(warn_only=True):
113 local('nodeenv -c -n {0} {1}'.format(env.node_version, env.node_env_path))
116 def lib_dir(src_dir, dtype):
117 target_dir = os.path.join(src_dir, 'indico', 'htdocs')
118 return os.path.join(target_dir, dtype, 'lib')
121 def _check_pyenv(py_versions):
123 Check that pyenv and pyenv-virtualenv are installed and set up the
124 compilers/virtual envs in case they do not exist
127 if os.system('which pyenv'):
128 print red("Can't find pyenv!")
129 print yellow("Are you sure you have installed it?")
130 sys.exit(-2)
131 elif os.system('which pyenv-virtualenv'):
132 print red("Can't find pyenv-virtualenv!")
133 print yellow("Are you sure you have installed it?")
134 sys.exit(-2)
136 # list available pyenv versions
137 av_versions = os.listdir(os.path.join(env.pyenv_dir, 'versions'))
139 for py_version in py_versions:
140 if py_version not in av_versions:
141 print green('Installing Python {0}'.format(py_version))
142 pyenv_cmd('install {0}'.format(py_version), capture=True)
144 local("echo \'y\' | pyenv virtualenv {0} indico-build-{0}".format(py_version))
146 with pyenv_env(py_version):
147 local("pip install -r requirements.dev.txt")
150 def _check_present(executable, message="Please install it first."):
152 Check that executable exists in $PATH
155 with settings(warn_only=True):
156 if local('which {0} > /dev/null && echo $?'.format(executable), capture=True) != '0':
157 print red('{0} is not available in this system. {1}'.format(executable, message))
158 sys.exit(-2)
161 def _safe_rm(path, recursive=False, ask=True):
162 if path[0] != '/':
163 path = os.path.join(env.lcwd, path)
164 if ask:
165 files = glob.glob(path)
166 if files:
167 print yellow("The following files are going to be deleted:\n ") + '\n '.join(files)
168 if console.confirm(cyan("Are you sure you want to delete them?")):
169 local('rm {0}{1}'.format('-rf ' if recursive else '', path))
170 else:
171 print red("Delete operation cancelled")
172 else:
173 local('rm {0}{1}'.format('-rf ' if recursive else '', path))
176 def _cp_tree(dfrom, dto, exclude=[]):
178 Simple copy with exclude option
180 if dfrom[0] != '/':
181 dfrom = os.path.join(env.lcwd, dfrom)
182 if dto[0] != '/':
183 dto = os.path.join(env.lcwd, dto)
185 print "{0} -> {1}".format(dfrom, dto)
187 shutil.copytree(dfrom, dto, ignore=shutil.ignore_patterns(*exclude))
190 def _find_most_recent(path, cmp=operator.gt, maxt=0):
191 for dirpath, __, fnames in os.walk(path):
192 for fname in fnames:
194 # ignore hidden files and ODTs
195 if fname.startswith(".") or fname.endswith(".odt"):
196 continue
198 mtime = os.stat(os.path.join(dirpath, fname)).st_mtime
199 if cmp(mtime, maxt):
200 maxt = mtime
201 return maxt
204 def _find_least_recent(path):
205 return _find_most_recent(path, cmp=operator.lt, maxt=sys.maxint)
208 def _install_dependencies(mod_name, sub_path, dtype, dest_subpath=None):
209 l_dir = lib_dir(env.src_dir, dtype)
210 dest_dir = os.path.join(l_dir, dest_subpath) if dest_subpath else l_dir
211 local('mkdir -p {0}'.format(dest_dir))
212 local('cp -R {0} {1}/'.format(
213 os.path.join(env.ext_dir, mod_name, sub_path),
214 dest_dir))
217 # Recipes
219 @recipe('angular')
220 def install_angular():
221 with node_env():
222 with lcd(os.path.join(env.ext_dir, 'angular')):
223 local('npm install')
224 local('grunt clean buildall copy write compress')
225 dest_dir_js = lib_dir(env.src_dir, 'js')
226 dest_dir_css = lib_dir(env.src_dir, 'css')
227 local('mkdir -p {0}'.format(dest_dir_js))
228 local('cp build/angular.js {0}/'.format(dest_dir_js))
229 local('cp build/angular-resource.js {0}/'.format(dest_dir_js))
230 local('cp build/angular-sanitize.js {0}/'.format(dest_dir_js))
231 local('cp css/angular.css {0}'.format(dest_dir_css))
234 @recipe('ui-sortable')
235 def install_ui_sortable():
236 with node_env():
237 with lcd(os.path.join(env.ext_dir, 'ui-sortable')):
238 dest_dir_js = lib_dir(env.src_dir, 'js')
239 local('mkdir -p {0}'.format(dest_dir_js))
240 local('cp src/sortable.js {0}/'.format(dest_dir_js))
243 @recipe('compass')
244 def install_compass():
245 _install_dependencies('compass', 'frameworks/compass/stylesheets/*', 'sass', 'compass')
248 @recipe('jquery')
249 def install_jquery():
250 with node_env():
251 with lcd(os.path.join(env.ext_dir, 'jquery')):
252 local('npm install')
253 local('grunt')
254 dest_dir = lib_dir(env.src_dir, 'js')
255 local('mkdir -p {0}'.format(dest_dir))
256 local('cp dist/jquery.js {0}/'.format(dest_dir))
259 @recipe('jed')
260 def install_jed():
261 with lcd(os.path.join(env.ext_dir, 'Jed')):
262 dest_dir = lib_dir(env.src_dir, 'js')
263 local('mkdir -p {0}'.format(dest_dir))
264 local('cp jed.js {0}/'.format(dest_dir))
267 @recipe('jqplot')
268 def install_jqplot():
269 """Install jQPlot from Git"""
270 plugins = ['axis', 'bar', 'cursor', 'highlighter', 'points', 'text']
271 with lcd(os.path.join(env.ext_dir, 'jqplot')):
272 dest_dir_js = os.path.join(lib_dir(env.src_dir, 'js'), 'jqplot')
273 dest_dir_css = lib_dir(env.src_dir, 'css')
274 dest_dir_js_core = os.path.join(dest_dir_js, 'core')
275 dest_dir_js_plugins = os.path.join(dest_dir_js, 'plugins')
276 local('mkdir -p {0} {1}'.format(dest_dir_js_core, dest_dir_css))
277 local('cp src/core/*.js {0}'.format(dest_dir_js_core))
278 local('cp src/core/*.css {0}'.format(dest_dir_css))
279 for plugin_name in plugins:
280 dest = os.path.join(dest_dir_js_plugins, plugin_name)
281 local('mkdir -p {0}'.format(dest))
282 local('cp src/plugins/{0}/* {1}'.format(plugin_name, dest))
285 @recipe('underscore')
286 def install_underscore():
288 Install underscore from Git
290 _install_dependencies('underscore', 'underscore.js', 'js')
293 @recipe('rrule') # rrule.js
294 def install_rrule():
296 Install rrule from Git
298 _install_dependencies('rrule', 'lib/rrule.js', 'js')
301 @recipe('qtip2')
302 def install_qtip2():
303 with node_env():
304 with lcd(os.path.join(env.ext_dir, 'qtip2')):
305 local('npm install')
306 local('grunt --plugins="tips modal viewport svg" init clean concat:dist concat:css concat:libs replace')
307 dest_dir_js, dest_dir_css = lib_dir(env.src_dir, 'js'), lib_dir(env.src_dir, 'css')
308 local('mkdir -p {0} {1}'.format(dest_dir_js, dest_dir_css))
309 local('cp dist/jquery.qtip.js {0}/'.format(dest_dir_js))
310 local('cp dist/jquery.qtip.css {0}/'.format(dest_dir_css))
313 @recipe('jquery-ui-multiselect')
314 def install_jquery_ui_multiselect():
315 with node_env():
316 with lcd(os.path.join(env.ext_dir, 'jquery-ui-multiselect')):
317 dest_dir_js = lib_dir(env.src_dir, 'js')
318 dest_dir_css = lib_dir(env.src_dir, 'css')
319 local('mkdir -p {0} {1}'.format(dest_dir_js, dest_dir_css))
320 local('cp src/jquery.multiselect.js {0}/'.format(dest_dir_js))
321 local('cp src/jquery.multiselect.filter.js {0}/'.format(dest_dir_js))
322 local('cp jquery.multiselect.css {0}/'.format(dest_dir_css))
323 local('cp jquery.multiselect.filter.css {0}/'.format(dest_dir_css))
326 @recipe('MathJax')
327 def install_mathjax():
328 dest_dir = os.path.join(lib_dir(env.src_dir, 'js'), 'mathjax/')
329 mathjax_js = os.path.join(dest_dir, 'MathJax.js')
331 with lcd(os.path.join(env.ext_dir, 'mathjax')):
332 local('rm -rf {0}'.format(os.path.join(dest_dir)))
333 _cp_tree('unpacked/', dest_dir, exclude=["AM*", "MML*", "Accessible*", "Safe*"])
334 _cp_tree('images/', os.path.join(dest_dir, 'images'))
335 _cp_tree('fonts/', os.path.join(dest_dir, 'fonts'), exclude=["png"])
337 with open(mathjax_js, 'r') as f:
338 data = f.read()
339 # Uncomment 'isPacked = true' line
340 data = re.sub(r'//\s*(MathJax\.isPacked\s*=\s*true\s*;)', r'\1', data, re.MULTILINE)
342 with open(mathjax_js, 'w') as f:
343 f.write(data)
346 @recipe('PageDown')
347 def install_pagedown():
348 with lcd(os.path.join(env.ext_dir, 'pagedown')):
349 dest_dir = os.path.join(lib_dir(env.src_dir, 'js'), 'pagedown/')
350 local('mkdir -p {0}'.format(dest_dir))
351 local('cp *.js {0}'.format(dest_dir))
354 @recipe('ZeroClipboard')
355 def install_zeroclipboard():
357 Install ZeroClipboard from Git
359 with lcd(os.path.join(env.ext_dir, 'zeroclipboard')):
360 dest_dir = os.path.join(lib_dir(env.src_dir, 'js'), 'zeroclipboard/')
361 local('mkdir -p {0}'.format(dest_dir))
362 local('cp dist/ZeroClipboard.js {0}/'.format(dest_dir))
363 local('cp dist/ZeroClipboard.swf {0}/'.format(dest_dir))
366 @recipe('dropzone.js')
367 def install_dropzone_js():
369 Install Dropzone from Git
371 with lcd(os.path.join(env.ext_dir, 'dropzone')):
372 dest_js_dir = os.path.join(lib_dir(env.src_dir, 'js'), 'dropzone.js/')
373 dest_css_dir = os.path.join(lib_dir(env.src_dir, 'css'), 'dropzone.js/')
374 local('mkdir -p {0} {1}'.format(dest_js_dir, dest_css_dir))
375 local('cp dist/dropzone.js {0}/'.format(dest_js_dir))
376 local('cp dist/dropzone.css {0}/'.format(dest_css_dir))
379 @recipe('selectize.js')
380 def install_selectize_js():
381 with lcd(os.path.join(env.ext_dir, 'selectize.js')):
382 dest_js_dir = os.path.join(lib_dir(env.src_dir, 'js'), 'selectize.js/')
383 dest_css_dir = os.path.join(lib_dir(env.src_dir, 'css'), 'selectize.js/')
384 local('mkdir -p {0} {1}'.format(dest_js_dir, dest_css_dir))
385 local('cp dist/js/standalone/selectize.js {0}/'.format(dest_js_dir))
386 local('cp dist/css/selectize.css {0}/'.format(dest_css_dir))
387 local('cp dist/css/selectize.default.css {0}/'.format(dest_css_dir))
390 # Tasks
392 @task
393 def install(recipe_name):
395 Install a module given the recipe name
397 RECIPES[recipe_name]()
400 @task
401 def init_submodules(src_dir='.'):
403 Initialize submodules (fetch them from external Git repos)
406 print green("Initializing submodules")
407 with lcd(src_dir):
408 local('pwd')
409 local('git submodule update --init --recursive')
412 def _install_deps():
414 Install asset dependencies
416 print green("Installing asset dependencies...")
417 for recipe_name in RECIPES:
418 print cyan("Installing {0}".format(recipe_name))
419 install(recipe_name)
422 @task
423 def setup_deps(n_env=None, n_version=None, src_dir=None, system_node=None):
425 Setup (fetch and install) dependencies for Indico assets
428 src_dir = src_dir or env.src_dir
429 n_env = n_env or env.node_env_path
430 system_node = system_node.lower() in ('1', 'true') if system_node is not None else env.system_node
432 # initialize submodules if they haven't yet been
433 init_submodules(src_dir)
435 ext_dir = os.path.join(src_dir, 'ext_modules')
437 _check_present('curl')
439 with settings(node_env_path=n_env or os.path.join(ext_dir, 'node_env'),
440 node_version=n_version or env.node_version,
441 system_node=system_node,
442 src_dir=src_dir,
443 ext_dir=ext_dir):
445 if not system_node and not os.path.exists(n_env):
446 create_node_env()
448 with node_env():
449 local('npm install -g grunt-cli')
451 _install_deps()
454 @task
455 def clean_deps(src_dir=None):
457 Clean up generated files
460 for dtype in ASSET_TYPES:
461 _safe_rm('{0}/*'.format(lib_dir(src_dir or env.src_dir, dtype)), recursive=True)
464 @task
465 def cleanup(build_dir=None, force=False):
467 Clean up build environment
469 _safe_rm('{0}'.format(build_dir or env.build_dir), recursive=True, ask=(not force))
472 @task
473 def tarball(src_dir=None):
475 Create a source Indico distribution (tarball)
478 src_dir = src_dir or env.src_dir
480 make_docs(src_dir)
482 setup_deps(n_env=os.path.join(src_dir, 'ext_modules', 'node_env'),
483 src_dir=src_dir)
484 local('python setup.py -q sdist')
487 @task
488 def egg(py_versions=None):
490 Create a binary Indico distribution (egg)
493 for py_version in py_versions:
494 cmd_dir = os.path.join(env.pyenv_dir, 'versions', 'indico-build-{0}'.format(py_version), 'bin')
495 local('{0} setup.py -q bdist_egg'.format(os.path.join(cmd_dir, 'python')))
496 print green(local('ls -lah dist/', capture=True))
499 @task
500 def make_docs(src_dir=None, build_dir=None, force=False):
502 Generate Indico docs
505 src_dir = src_dir or env.src_dir
506 doc_src_dir = os.path.join(src_dir, 'doc')
508 if build_dir is None:
509 target_dir = os.path.join(src_dir, 'indico', 'htdocs', 'ihelp')
510 else:
511 target_dir = os.path.join(build_dir or env.build_dir, 'indico', 'htdocs', 'ihelp')
513 if not force:
514 print yellow("Checking if docs need to be generated... "),
515 if _find_most_recent(target_dir) > _find_most_recent(doc_src_dir):
516 print green("Nope.")
517 return
519 print red("Yes :(")
520 _check_present('pdflatex')
522 print green('Generating documentation')
523 with lcd(doc_src_dir):
524 for d in DOC_DIRS:
525 with lcd(d):
526 local('make html')
527 local('make latex')
528 local('rm -rf {0}/*'.format(os.path.join(target_dir, 'html')))
529 local('mv build/html/* {0}'.format(os.path.join(target_dir, 'html')))
531 with lcd(os.path.join('guides', 'build', 'latex')):
532 local('make all-pdf')
533 local('mv *.pdf {0}'.format(os.path.join(target_dir, 'pdf')))
535 print green('Cleaning up')
536 for d in DOC_DIRS:
537 with lcd(d):
538 local('make clean')
541 def _check_request_error(r):
542 if r.status_code >= 400:
543 j = r.json()
544 msg = j.get('message', DEFAULT_REQUEST_ERROR_MSG)
545 print red("ERROR: {0} ({1})".format(msg, r.status_code))
546 sys.exit(-2)
549 def _valid_github_credentials(auth):
550 url = "https://api.github.com/repos/{0}/{1}".format(env.github['org'], env.github['repo'])
551 r = requests.get(url, auth=(env.github['user'], auth))
552 if (r.status_code == 401) and (r.json().get('message') == 'Bad credentials'):
553 print red('Invalid Github credentials for user \'{0}\''.format(env.github['user']))
554 return False
556 return True
559 def _release_exists(tag_name, auth):
560 url = "https://api.github.com/repos/{0}/{1}/releases".format(env.github['org'], env.github['repo'])
561 r = requests.get(url, auth=(env.github['user'], auth))
562 _check_request_error(r)
563 parsed = r.json()
564 for release in parsed:
565 if release.get('tag_name') == tag_name:
566 rel_id = release.get('id')
567 return (True, rel_id, release)
569 return (False, 0, None)
572 def _asset_exists(rel_id, name, auth):
573 url = "https://api.github.com/repos/{0}/{1}/releases/{2}/assets" \
574 .format(env.github['org'], env.github['repo'], rel_id)
575 r = requests.get(url, auth=(env.github['user'], auth))
576 _check_request_error(r)
577 parsed = r.json()
578 for j in parsed:
579 if j.get('name') == name:
580 asset_id = j.get('id')
581 return (True, asset_id)
583 return (False, 0)
586 @task
587 def upload_github(build_dir=None, tag_name=None, auth_token=None,
588 overwrite=None, indico_version='master'):
590 build_dir = build_dir or env.build_dir
591 auth_token = auth_token or env.github['auth_token']
593 while (auth_token is None) or (not _valid_github_credentials(auth_token)):
594 auth_token = getpass.getpass(
595 'Insert the Github password/OAuth token for user \'{0}\': '.format(env.github['user']))
597 auth_creds = (env.github['user'], auth_token)
599 overwrite = overwrite or env.github['overwrite']
601 # Create a new release
602 tag_name = tag_name or indico_version
603 url = "https://api.github.com/repos/{0}/{1}/releases".format(env.github['org'], env.github['repo'])
604 payload = {
605 'tag_name': tag_name,
606 'target_commitish': indico_version,
607 'name': 'Indico {0}'.format(tag_name),
608 'draft': True
611 (exists, rel_id, release_data) = _release_exists(tag_name, auth_token)
613 if exists:
614 if overwrite is None:
615 overwrite = _yes_no_input('Release already exists, do you want to overwrite', 'n')
616 if overwrite:
617 release_id = rel_id
618 else:
619 return
620 else:
621 # We will need to get a new release id from github
622 r = requests.post(url, auth=auth_creds, data=json.dumps(payload))
623 _check_request_error(r)
624 release_data = r.json()
625 release_id = release_data.get('id')
627 # Upload binaries to the new release
628 binaries_dir = os.path.join(build_dir, 'indico', 'dist')
630 # awful way to handle this, but a regex seems like too much
631 url = release_data['upload_url'][:-7]
633 for f in os.listdir(binaries_dir):
635 # jump over hidden/system files
636 if f.startswith('.'):
637 continue
639 if os.path.isfile(os.path.join(binaries_dir, f)):
640 (exists, asset_id) = _asset_exists(release_id, f, auth_token)
642 if exists:
643 # delete previous version
644 del_url = "https://api.github.com/repos/{0}/{1}/releases/assets/{2}" \
645 .format(env.github['org'], env.github['repo'], asset_id)
646 r = requests.delete(del_url, auth=auth_creds)
647 _check_request_error(r)
649 with open(os.path.join(binaries_dir, f), 'rb') as ff:
650 data = ff.read()
651 extension = os.path.splitext(f)[1]
653 # upload eggs using zip mime type
654 if extension == '.gz':
655 headers = {'Content-Type': 'application/x-gzip'}
656 elif extension == '.egg':
657 headers = {'Content-Type': 'application/zip'}
659 headers['Accept'] = 'application/vnd.github.v3+json'
660 headers['Content-Length'] = len(data)
661 params = {'name': f}
663 print green("Uploading \'{0}\' to Github".format(f))
664 r = requests.post(url, auth=auth_creds, headers=headers, data=data, params=params, verify=False)
665 _check_request_error(r)
668 @task
669 def upload_ssh(build_dir=None, server_host=None, server_port=None,
670 ssh_user=None, ssh_key=None, dest_dir=None):
672 build_dir = build_dir or env.build_dir
673 server_host = server_host or env.ssh['host']
674 server_port = server_port or env.ssh['port']
675 ssh_user = ssh_user or env.ssh['user']
676 ssh_key = ssh_key or env.ssh['key']
677 dest_dir = dest_dir or env.ssh['dest_dir']
679 env.host_string = server_host + ':' + server_port
680 env.user = ssh_user
681 env.key_filename = ssh_key
683 binaries_dir = os.path.join(build_dir, 'indico', 'dist')
684 for f in os.listdir(binaries_dir):
685 if os.path.isfile(os.path.join(binaries_dir, f)):
686 _putl(os.path.join(binaries_dir, f), dest_dir)
689 @task
690 def _package_release(build_dir, py_versions, system_node):
691 # Build source tarball
692 with settings(system_node=system_node):
693 print green('Generating '), cyan('tarball')
694 tarball(build_dir)
696 # Build binaries (EGG)
697 print green('Generating '), cyan('eggs')
698 egg(py_versions)
701 @task
702 def package_release(py_versions=None, build_dir=None, system_node=False,
703 indico_version=None, upstream=None, tag_name=None,
704 github_auth=None, overwrite=None, ssh_server_host=None,
705 ssh_server_port=None, ssh_user=None, ssh_key=None,
706 ssh_dest_dir=None, no_clean=False, force_clean=False,
707 upload_to=None, build_here=False):
709 Create an Indico release - source and binary distributions
712 DEVELOP_REQUIRES = ['pojson>=0.4', 'termcolor', 'werkzeug', 'nodeenv', 'fabric',
713 'sphinx', 'repoze.sphinx.autointerface']
715 py_versions = py_versions.split('/') if py_versions else env.py_versions
716 upload_to = upload_to.split('/') if upload_to else []
718 build_dir = build_dir or env.build_dir
719 upstream = upstream or env.github['upstream']
721 ssh_server_host = ssh_server_host or env.ssh['host']
722 ssh_server_port = ssh_server_port or env.ssh['port']
723 ssh_user = ssh_user or env.ssh['user']
724 ssh_key = ssh_key or env.ssh['key']
725 ssh_dest_dir = ssh_dest_dir or env.ssh['dest_dir']
727 indico_version = indico_version or 'master'
729 local('mkdir -p {0}'.format(build_dir))
731 _check_pyenv(py_versions)
733 if build_here:
734 _package_release(os.path.dirname(__file__), py_versions, system_node)
735 else:
736 with lcd(build_dir):
737 if os.path.exists(os.path.join(build_dir, 'indico')):
738 print yellow("Repository seems to already exist.")
739 with lcd('indico'):
740 local('git fetch {0}'.format(upstream))
741 local('git reset --hard FETCH_HEAD')
742 if not no_clean:
743 local('git clean -df')
744 else:
745 local('git clone {0}'.format(upstream))
746 with lcd('indico'):
747 print green("Checking out branch \'{0}\'".format(indico_version))
748 local('git checkout {0}'.format(indico_version))
750 _package_release(os.path.join(build_dir, 'indico'), py_versions, system_node)
752 for u in upload_to:
753 if u == 'github':
754 upload_github(build_dir, tag_name, github_auth, overwrite, indico_version)
755 elif u == 'ssh':
756 upload_ssh(build_dir, ssh_server_host, ssh_server_port, ssh_user, ssh_key, ssh_dest_dir)
758 if not build_here and force_clean:
759 cleanup(build_dir, force=True)