build_handler.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. """Tornado handlers for frontend config storage."""
  2. # Copyright (c) Jupyter Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. from concurrent.futures import ThreadPoolExecutor
  5. import json
  6. from threading import Event
  7. from notebook.base.handlers import APIHandler
  8. from tornado import gen, web
  9. from tornado.concurrent import run_on_executor
  10. from .commands import build, clean, build_check
  11. class Builder(object):
  12. building = False
  13. executor = ThreadPoolExecutor(max_workers=5)
  14. canceled = False
  15. _canceling = False
  16. _kill_event = None
  17. _future = None
  18. def __init__(self, log, core_mode, app_dir):
  19. self.log = log
  20. self.core_mode = core_mode
  21. self.app_dir = app_dir
  22. @gen.coroutine
  23. def get_status(self):
  24. if self.core_mode:
  25. raise gen.Return(dict(status='stable', message=''))
  26. if self.building:
  27. raise gen.Return(dict(status='building', message=''))
  28. try:
  29. messages = yield self._run_build_check(self.app_dir, self.log)
  30. status = 'needed' if messages else 'stable'
  31. if messages:
  32. self.log.warn('Build recommended')
  33. [self.log.warn(m) for m in messages]
  34. else:
  35. self.log.info('Build is up to date')
  36. except ValueError as e:
  37. self.log.warn(
  38. 'Could not determine jupyterlab build status without nodejs'
  39. )
  40. status = 'stable'
  41. messages = []
  42. raise gen.Return(dict(status=status, message='\n'.join(messages)))
  43. @gen.coroutine
  44. def build(self):
  45. if self._canceling:
  46. raise ValueError('Cancel in progress')
  47. if not self.building:
  48. self.canceled = False
  49. self._future = future = gen.Future()
  50. self.building = True
  51. self._kill_event = evt = Event()
  52. try:
  53. yield self._run_build(self.app_dir, self.log, evt)
  54. future.set_result(True)
  55. except Exception as e:
  56. if str(e) == 'Aborted':
  57. future.set_result(False)
  58. else:
  59. future.set_exception(e)
  60. finally:
  61. self.building = False
  62. try:
  63. yield self._future
  64. except Exception as e:
  65. raise e
  66. @gen.coroutine
  67. def cancel(self):
  68. if not self.building:
  69. raise ValueError('No current build')
  70. self._canceling = True
  71. yield self._future
  72. self._canceling = False
  73. self.canceled = True
  74. @run_on_executor
  75. def _run_build_check(self, app_dir, logger):
  76. return build_check(app_dir=app_dir, logger=logger)
  77. @run_on_executor
  78. def _run_build(self, app_dir, logger, kill_event):
  79. kwargs = dict(app_dir=app_dir, logger=logger, kill_event=kill_event)
  80. try:
  81. return build(**kwargs)
  82. except Exception as e:
  83. if self._kill_event.is_set():
  84. return
  85. self.log.warn('Build failed, running a clean and rebuild')
  86. clean(app_dir)
  87. return build(**kwargs)
  88. class BuildHandler(APIHandler):
  89. def initialize(self, builder):
  90. self.builder = builder
  91. @web.authenticated
  92. @gen.coroutine
  93. def get(self):
  94. data = yield self.builder.get_status()
  95. self.finish(json.dumps(data))
  96. @web.authenticated
  97. @gen.coroutine
  98. def delete(self):
  99. self.log.warn('Canceling build')
  100. try:
  101. yield self.builder.cancel()
  102. except Exception as e:
  103. raise web.HTTPError(500, str(e))
  104. self.set_status(204)
  105. @web.authenticated
  106. @gen.coroutine
  107. def post(self):
  108. self.log.debug('Starting build')
  109. try:
  110. yield self.builder.build()
  111. except Exception as e:
  112. raise web.HTTPError(500, str(e))
  113. if self.builder.canceled:
  114. raise web.HTTPError(400, 'Build canceled')
  115. self.log.debug('Build succeeded')
  116. self.set_status(200)
  117. # The path for lab build.
  118. build_path = r"/lab/api/build"