Example #1
0
  def _generate_html(testsuites):
    values = {
      'total_tests': 0,
      'total_errors': 0,
      'total_failures': 0,
      'total_skipped': 0,
      'total_time': 0.0
    }

    for testsuite in testsuites:
      values['total_tests'] += testsuite.tests
      values['total_errors'] += testsuite.errors
      values['total_failures'] += testsuite.failures
      values['total_skipped'] += testsuite.skipped
      values['total_time'] += testsuite.time

    values['total_success'] = ReportTestSuite.success_rate(values['total_tests'],
                                                           values['total_errors'],
                                                           values['total_failures'],
                                                           values['total_skipped'])
    values['summary_icon_class'] = ReportTestSuite.icon_class(values['total_tests'],
                                                              values['total_errors'],
                                                              values['total_failures'],
                                                              values['total_skipped'])
    values['testsuites'] = map(lambda ts: ts.as_dict(), testsuites)

    package_name, _, _ = __name__.rpartition('.')
    renderer = MustacheRenderer(package_name=package_name)
    html = renderer.render_name('junit_report.html', values)
    return html
 def _do_render(self, filename, args):
   package_name, _, _ = __name__.rpartition('.')
   renderer = MustacheRenderer(package_name=package_name)
   output_path = os.path.join(self._outdir, os.path.basename(filename))
   self.context.log.info('Generating {}'.format(output_path))
   html = renderer.render_name(filename, args)
   with safe_open(output_path, 'w') as outfile:
     outfile.write(html.encode('utf8'))
Example #3
0
    def generate_html(self, testsuites):
        values = {"total_tests": 0, "total_errors": 0, "total_failures": 0, "total_skipped": 0, "total_time": 0.0}

        for testsuite in testsuites:
            values["total_tests"] += testsuite.tests
            values["total_errors"] += testsuite.errors
            values["total_failures"] += testsuite.failures
            values["total_skipped"] += testsuite.skipped
            values["total_time"] += testsuite.time

        values["total_success"] = ReportTestSuite.success_rate(
            values["total_tests"], values["total_errors"], values["total_failures"], values["total_skipped"]
        )
        values["summary_icon_class"] = ReportTestSuite.icon_class(
            values["total_tests"], values["total_errors"], values["total_failures"], values["total_skipped"]
        )
        values["testsuites"] = map(lambda ts: ts.as_dict(), testsuites)

        package_name, _, _ = __name__.rpartition(".")
        renderer = MustacheRenderer(package_name=package_name)
        html = renderer.render_name("junit_report.html", values)
        return html
Example #4
0
class HtmlReporter(Reporter):
  """HTML reporting to files.

  The files are intended to be served by the ReportingServer,
  not accessed directly from the filesystem.
  """

  # HTML reporting settings.
  #   html_dir: Where the report files go.
  #   template_dir: Where to find mustache templates.
  Settings = namedtuple('Settings', Reporter.Settings._fields + ('html_dir', 'template_dir'))

  def __init__(self, run_tracker, settings):
    Reporter.__init__(self, run_tracker, settings)
     # The main report, and associated tool outputs, go under this dir.
    self._html_dir = settings.html_dir

    # We render HTML from mustache templates.
    self._renderer = MustacheRenderer(settings.template_dir, __name__)

    # We serve files relative to the build root.
    self._buildroot = get_buildroot()
    self._html_path_base = os.path.relpath(self._html_dir, self._buildroot)

    # We write the main report body to this file object.
    self._report_file = None

    # We redirect stdout, stderr etc. of tool invocations to these files.
    self._output_files = defaultdict(dict)  # workunit_id -> {path -> fileobj}.
    self._linkify_memo = {}

  def report_path(self):
    """The path to the main report file."""
    return os.path.join(self._html_dir, 'build.html')

  def open(self):
    """Implementation of Reporter callback."""
    safe_mkdir(os.path.dirname(self._html_dir))
    self._report_file = open(self.report_path(), 'w')

  def close(self):
    """Implementation of Reporter callback."""
    self._report_file.close()
    # Make sure everything's closed.
    for files in self._output_files.values():
      for f in files.values():
        f.close()

  def start_workunit(self, workunit):
    """Implementation of Reporter callback."""
    # We use these properties of the workunit to decide how to render information about it.
    is_bootstrap = workunit.has_label(WorkUnit.BOOTSTRAP)
    is_tool = workunit.has_label(WorkUnit.TOOL)
    is_multitool = workunit.has_label(WorkUnit.MULTITOOL)
    is_test = workunit.has_label(WorkUnit.TEST)

    # Get useful properties from the workunit.
    workunit_dict = workunit.to_dict()
    if workunit_dict['cmd']:
      workunit_dict['cmd'] = linkify(self._buildroot, workunit_dict['cmd'].replace('$', '\\\\$'),
                                     self._linkify_memo)

    # Create the template arguments.
    args = { 'indent': len(workunit.ancestors()) * 10,
             'html_path_base': self._html_path_base,
             'workunit': workunit_dict,
             'header_text': workunit.name,
             'initially_open': is_test or not (is_bootstrap or is_tool or is_multitool),
             'is_tool': is_tool,
             'is_multitool': is_multitool }
    args.update({ 'collapsible': lambda x: self._renderer.render_callable('collapsible', x, args) })

    # Render the workunit's div.
    s = self._renderer.render_name('workunit_start', args)

    if is_tool:
      # This workunit is a tool invocation, so render the appropriate content.
      # We use the same args, slightly modified.
      del args['initially_open']
      if is_test:
        # Have test framework stdout open by default, but not that of other tools.
        # This is an arbitrary choice, but one that turns out to be useful to users in practice.
        args['stdout_initially_open'] = True
      s += self._renderer.render_name('tool_invocation_start', args)

    # ... and we're done.
    self._emit(s)

  # CSS classes from pants.css that we use to style the header text to reflect the outcome.
  _outcome_css_classes = ['aborted', 'failure', 'warning', 'success', 'unknown']

  def end_workunit(self, workunit):
    """Implementation of Reporter callback."""
    # Create the template arguments.
    duration = workunit.duration()
    timing = '{:.3f}'.format(duration)
    unaccounted_time = None
    # Background work may be idle a lot, no point in reporting that as unaccounted.
    if self.is_under_main_root(workunit):
      unaccounted_time_secs = workunit.unaccounted_time()
      if unaccounted_time_secs >= 1 and unaccounted_time_secs > 0.05 * duration:
        unaccounted_time = '{:.3f}'.format(unaccounted_time_secs)
    args = { 'workunit': workunit.to_dict(),
             'status': HtmlReporter._outcome_css_classes[workunit.outcome()],
             'timing': timing,
             'unaccounted_time': unaccounted_time,
             'aborted': workunit.outcome() == WorkUnit.ABORTED }

    s = ''
    if workunit.has_label(WorkUnit.TOOL):
      s += self._renderer.render_name('tool_invocation_end', args)
    s += self._renderer.render_name('workunit_end', args)
    self._emit(s)

    # Update the timings.
    def render_timings(timings):
      timings_dict = timings.get_all()
      for item in timings_dict:
        item['timing_string'] = '{:.3f}'.format(item['timing'])
      args = {
        'timings': timings_dict
      }
      return self._renderer.render_name('aggregated_timings', args)

    self._overwrite('cumulative_timings', render_timings(self.run_tracker.cumulative_timings))
    self._overwrite('self_timings', render_timings(self.run_tracker.self_timings))

    # Update the artifact cache stats.
    def render_cache_stats(artifact_cache_stats):
      def fix_detail_id(e, _id):
        return e if isinstance(e, string_types) else e + (_id, )

      msg_elements = []
      for cache_name, stat in artifact_cache_stats.stats_per_cache.items():
        msg_elements.extend([
          cache_name + ' artifact cache: ',
          # Explicitly set the detail ids, so their displayed/hidden state survives a refresh.
          fix_detail_id(items_to_report_element(stat.hit_targets, 'hit'), 'cache-hit-details'),
          ', ',
          fix_detail_id(items_to_report_element(stat.miss_targets, 'miss'), 'cache-miss-details'),
          '.'
        ])
      if not msg_elements:
        msg_elements = ['No artifact cache use.']
      return self._render_message(*msg_elements)

    self._overwrite('artifact_cache_stats',
                    render_cache_stats(self.run_tracker.artifact_cache_stats))

    for f in self._output_files[workunit.id].values():
      f.close()

  def handle_output(self, workunit, label, s):
    """Implementation of Reporter callback."""
    if os.path.exists(self._html_dir):  # Make sure we're not immediately after a clean-all.
      path = os.path.join(self._html_dir, '{}.{}'.format(workunit.id, label))
      output_files = self._output_files[workunit.id]
      if path not in output_files:
        f = open(path, 'w')
        output_files[path] = f
      else:
        f = output_files[path]
      f.write(self._htmlify_text(s).encode('utf-8'))
      # We must flush in the same thread as the write.
      f.flush()

  _log_level_css_map = {
    Report.FATAL: 'fatal',
    Report.ERROR: 'error',
    Report.WARN:  'warn',
    Report.INFO:  'info',
    Report.DEBUG: 'debug'
  }
  def do_handle_log(self, workunit, level, *msg_elements):
    """Implementation of Reporter callback."""
    content = '<span class="{}">{}</span>'.format(
              HtmlReporter._log_level_css_map[level], self._render_message(*msg_elements))

    # Generate some javascript that appends the content to the workunit's div.
    args = {
      'content_id': uuid.uuid4(),  # Identifies this content.
      'workunit_id': workunit.id,  # The workunit this reporting content belongs to.
      'content': content,  # The content to append.
      }
    s = self._renderer.render_name('append_to_workunit', args)

    # Emit that javascript to the main report body.
    self._emit(s)

  def _render_message(self, *msg_elements):
    elements = []
    detail_ids = []
    for element in msg_elements:
      # Each element can be a message or a (message, detail) pair, as received by handle_log().
      #
      # However, as an internal implementation detail, we also allow an element to be a tuple
      # (message, detail, detail_initially_visible[, detail_id])
      #
      # - If the detail exists, clicking on the text will toggle display of the detail and close
      #   all other details in this message.
      # - If detail_initially_visible is True, the detail will be displayed by default.
      #
      # Toggling is managed via detail_ids: when clicking on a detail, it closes all details
      # in this message with detail_ids different than that of the one being clicked on.
      # We allow detail_id to be explicitly specified, so that the open/closed state can be
      # preserved through refreshes. For example, when looking at the artifact cache stats,
      # if "hits" are open and "misses" are closed, we want to remember that even after
      # the cache stats are updated and the message re-rendered.
      if isinstance(element, string_types):
        element = [element]
      defaults = ('', None, None, False)
      # Map assumes None for missing values, so this will pick the default for those.
      (text, detail, detail_id, detail_initially_visible) = \
        map(lambda x, y: x or y, element, defaults)
      element_args = {'text': self._htmlify_text(text) }
      if detail is not None:
        detail_id = detail_id or uuid.uuid4()
        detail_ids.append(detail_id)
        element_args.update({
          'detail': self._htmlify_text(detail),
          'detail_initially_visible': detail_initially_visible,
          'detail-id': detail_id
        })
      elements.append(element_args)
    args = { 'elements': elements,
             'all-detail-ids': detail_ids }
    return self._renderer.render_name('message', args)

  def _emit(self, s):
    """Append content to the main report file."""
    if os.path.exists(self._html_dir):  # Make sure we're not immediately after a clean-all.
      self._report_file.write(s)
      self._report_file.flush()  # We must flush in the same thread as the write.

  def _overwrite(self, filename, s):
    """Overwrite a file with the specified contents."""
    if os.path.exists(self._html_dir):  # Make sure we're not immediately after a clean-all.
      with open(os.path.join(self._html_dir, filename), 'w') as f:
        f.write(s)

  def _htmlify_text(self, s):
    """Make text HTML-friendly."""
    colored = self._handle_ansi_color_codes(cgi.escape(s.decode('utf-8')))
    return linkify(self._buildroot, colored, self._linkify_memo).replace('\n', '</br>')

  _ANSI_COLOR_CODE_RE = re.compile(r'\033\[((?:\d|;)*)m')

  def _handle_ansi_color_codes(self, s):
    """Replace ansi escape sequences with spans of appropriately named css classes."""
    parts = HtmlReporter._ANSI_COLOR_CODE_RE.split(s)
    ret = []
    span_depth = 0
    # Note that len(parts) is always odd: text, code, text, code, ..., text.
    for i in range(0, len(parts), 2):
      ret.append(parts[i])
      if i + 1 < len(parts):
        for code in parts[i + 1].split(';'):
          if code == 0:  # Reset.
            while span_depth > 0:
              ret.append('</span>')
              span_depth -= 1
          else:
            ret.append('<span class="ansi-{}">'.format(code))
            span_depth += 1
    while span_depth > 0:
      ret.append('</span>')
      span_depth -= 1

    return ''.join(ret)
Example #5
0
class HtmlReporter(Reporter):
  """HTML reporting to files.

  The files are intended to be served by the ReportingServer,
  not accessed directly from the filesystem.
  """

  # HTML reporting settings.
  #   html_dir: Where the report files go.
  #   template_dir: Where to find mustache templates.
  Settings = namedtuple('Settings', Reporter.Settings._fields + ('html_dir', 'template_dir'))

  def __init__(self, run_tracker, settings):
    Reporter.__init__(self, run_tracker, settings)
     # The main report, and associated tool outputs, go under this dir.
    self._html_dir = settings.html_dir

    # We render HTML from mustache templates.
    self._renderer = MustacheRenderer(settings.template_dir, __name__)

    # We serve files relative to the build root.
    self._buildroot = get_buildroot()
    self._html_path_base = os.path.relpath(self._html_dir, self._buildroot)

    # We write the main report body to this file object.
    self._report_file = None

    # We redirect stdout, stderr etc. of tool invocations to these files.
    self._output_files = defaultdict(dict)  # workunit_id -> {path -> fileobj}.

  def report_path(self):
    """The path to the main report file."""
    return os.path.join(self._html_dir, 'build.html')

  def open(self):
    """Implementation of Reporter callback."""
    safe_mkdir(os.path.dirname(self._html_dir))
    self._report_file = open(self.report_path(), 'w')

  def close(self):
    """Implementation of Reporter callback."""
    self._report_file.close()
    # Make sure everything's closed.
    for files in self._output_files.values():
      for f in files.values():
        f.close()

  def start_workunit(self, workunit):
    """Implementation of Reporter callback."""
    # We use these properties of the workunit to decide how to render information about it.
    is_bootstrap = workunit.has_label(WorkUnit.BOOTSTRAP)
    is_tool = workunit.has_label(WorkUnit.TOOL)
    is_multitool = workunit.has_label(WorkUnit.MULTITOOL)
    is_test = workunit.has_label(WorkUnit.TEST)

    # Get useful properties from the workunit.
    workunit_dict = workunit.to_dict()
    if workunit_dict['cmd']:
      workunit_dict['cmd'] = linkify(self._buildroot, workunit_dict['cmd'].replace('$', '\\\\$'))

    # Create the template arguments.
    args = { 'indent': len(workunit.ancestors()) * 10,
             'html_path_base': self._html_path_base,
             'workunit': workunit_dict,
             'header_text': workunit.name,
             'initially_open': is_test or not (is_bootstrap or is_tool or is_multitool),
             'is_tool': is_tool,
             'is_multitool': is_multitool }
    args.update({ 'collapsible': lambda x: self._renderer.render_callable('collapsible', x, args) })

    # Render the workunit's div.
    s = self._renderer.render_name('workunit_start', args)

    if is_tool:
      # This workunit is a tool invocation, so render the appropriate content.
      # We use the same args, slightly modified.
      del args['initially_open']
      if is_test:
        # Have test framework stdout open by default, but not that of other tools.
        # This is an arbitrary choice, but one that turns out to be useful to users in practice.
        args['stdout_initially_open'] = True
      s += self._renderer.render_name('tool_invocation_start', args)

    # ... and we're done.
    self._emit(s)

  # CSS classes from pants.css that we use to style the header text to reflect the outcome.
  _outcome_css_classes = ['aborted', 'failure', 'warning', 'success', 'unknown']

  def end_workunit(self, workunit):
    """Implementation of Reporter callback."""
    # Create the template arguments.
    duration = workunit.duration()
    timing = '%.3f' % duration
    unaccounted_time = None
    # Background work may be idle a lot, no point in reporting that as unaccounted.
    if self.is_under_main_root(workunit):
      unaccounted_time_secs = workunit.unaccounted_time()
      if unaccounted_time_secs >= 1 and unaccounted_time_secs > 0.05 * duration:
        unaccounted_time = '%.3f' % unaccounted_time_secs
    args = { 'workunit': workunit.to_dict(),
             'status': workunit.choose(*HtmlReporter._outcome_css_classes),
             'timing': timing,
             'unaccounted_time': unaccounted_time,
             'aborted': workunit.outcome() == WorkUnit.ABORTED }

    s = ''
    if workunit.has_label(WorkUnit.TOOL):
      s += self._renderer.render_name('tool_invocation_end', args)
    s += self._renderer.render_name('workunit_end', args)
    self._emit(s)

    # Update the timings.
    def render_timings(timings):
      timings_dict = timings.get_all()
      for item in timings_dict:
        item['timing_string'] = '%.3f' % item['timing']
      args = {
        'timings': timings_dict
      }
      return self._renderer.render_name('aggregated_timings', args)

    self._overwrite('cumulative_timings', render_timings(self.run_tracker.cumulative_timings))
    self._overwrite('self_timings', render_timings(self.run_tracker.self_timings))

    # Update the artifact cache stats.
    def render_cache_stats(artifact_cache_stats):
      def fix_detail_id(e, _id):
        return e if isinstance(e, basestring) else e + (_id, )

      msg_elements = []
      for cache_name, stat in artifact_cache_stats.stats_per_cache.items():
        msg_elements.extend([
          cache_name + ' artifact cache: ',
          # Explicitly set the detail ids, so their displayed/hidden state survives a refresh.
          fix_detail_id(items_to_report_element(stat.hit_targets, 'hit'), 'cache-hit-details'),
          ', ',
          fix_detail_id(items_to_report_element(stat.miss_targets, 'miss'), 'cache-miss-details'),
          '.'
        ])
      if not msg_elements:
        msg_elements = ['No artifact cache use.']
      return self._render_message(*msg_elements)

    self._overwrite('artifact_cache_stats',
                    render_cache_stats(self.run_tracker.artifact_cache_stats))

    for f in self._output_files[workunit.id].values():
      f.close()

  def handle_output(self, workunit, label, s):
    """Implementation of Reporter callback."""
    if os.path.exists(self._html_dir):  # Make sure we're not immediately after a clean-all.
      path = os.path.join(self._html_dir, '%s.%s' % (workunit.id, label))
      output_files = self._output_files[workunit.id]
      if path not in output_files:
        f = open(path, 'w')
        output_files[path] = f
      else:
        f = output_files[path]
      f.write(self._htmlify_text(s).encode('utf-8'))
      # We must flush in the same thread as the write.
      f.flush()

  _log_level_css_map = {
    Report.FATAL: 'fatal',
    Report.ERROR: 'error',
    Report.WARN:  'warn',
    Report.INFO:  'info',
    Report.DEBUG: 'debug'
  }
  def do_handle_log(self, workunit, level, *msg_elements):
    """Implementation of Reporter callback."""
    content = '<span class="%s">%s</span>' % \
              (HtmlReporter._log_level_css_map[level], self._render_message(*msg_elements))

    # Generate some javascript that appends the content to the workunit's div.
    args = {
      'content_id': uuid.uuid4(),  # Identifies this content.
      'workunit_id': workunit.id,  # The workunit this reporting content belongs to.
      'content': content,  # The content to append.
      }
    s = self._renderer.render_name('append_to_workunit', args)

    # Emit that javascript to the main report body.
    self._emit(s)

  def _render_message(self, *msg_elements):
    elements = []
    detail_ids = []
    for element in msg_elements:
      # Each element can be a message or a (message, detail) pair, as received by handle_log().
      #
      # However, as an internal implementation detail, we also allow an element to be a tuple
      # (message, detail, detail_initially_visible[, detail_id])
      #
      # - If the detail exists, clicking on the text will toggle display of the detail and close
      #   all other details in this message.
      # - If detail_initially_visible is True, the detail will be displayed by default.
      #
      # Toggling is managed via detail_ids: when clicking on a detail, it closes all details
      # in this message with detail_ids different than that of the one being clicked on.
      # We allow detail_id to be explicitly specified, so that the open/closed state can be
      # preserved through refreshes. For example, when looking at the artifact cache stats,
      # if "hits" are open and "misses" are closed, we want to remember that even after
      # the cache stats are updated and the message re-rendered.
      if isinstance(element, basestring):
        element = [element]
      defaults = ('', None, None, False)
      # Map assumes None for missing values, so this will pick the default for those.
      (text, detail, detail_id, detail_initially_visible) = \
        map(lambda x, y: x or y, element, defaults)
      element_args = {'text': self._htmlify_text(text) }
      if detail is not None:
        detail_id = detail_id or uuid.uuid4()
        detail_ids.append(detail_id)
        element_args.update({
          'detail': self._htmlify_text(detail),
          'detail_initially_visible': detail_initially_visible,
          'detail-id': detail_id
        })
      elements.append(element_args)
    args = { 'elements': elements,
             'all-detail-ids': detail_ids }
    return self._renderer.render_name('message', args)

  def _emit(self, s):
    """Append content to the main report file."""
    if os.path.exists(self._html_dir):  # Make sure we're not immediately after a clean-all.
      self._report_file.write(s)
      self._report_file.flush()  # We must flush in the same thread as the write.

  def _overwrite(self, filename, s):
    """Overwrite a file with the specified contents."""
    if os.path.exists(self._html_dir):  # Make sure we're not immediately after a clean-all.
      with open(os.path.join(self._html_dir, filename), 'w') as f:
        f.write(s)

  def _htmlify_text(self, s):
    """Make text HTML-friendly."""
    colored = self._handle_ansi_color_codes(cgi.escape(s.decode('utf-8')))
    return linkify(self._buildroot, colored).replace('\n', '</br>')

  _ANSI_COLOR_CODE_RE = re.compile(r'\033\[((?:\d|;)*)m')

  def _handle_ansi_color_codes(self, s):
    """Replace ansi escape sequences with spans of appropriately named css classes."""
    parts = HtmlReporter._ANSI_COLOR_CODE_RE.split(s)
    ret = []
    span_depth = 0
    # Note that len(parts) is always odd: text, code, text, code, ..., text.
    for i in range(0, len(parts), 2):
      ret.append(parts[i])
      if i + 1 < len(parts):
        for code in parts[i + 1].split(';'):
          if code == 0:  # Reset.
            while span_depth > 0:
              ret.append('</span>')
              span_depth -= 1
          else:
            ret.append('<span class="ansi-%s">' % code)
            span_depth += 1
    while span_depth > 0:
      ret.append('</span>')
      span_depth -= 1

    return ''.join(ret)
class EnsimeGen(IdeGen):
    """Create an Ensime project from the given targets."""
    @classmethod
    def register_options(cls, register):
        super(EnsimeGen, cls).register_options(register)
        register(
            '--excluded-deps',
            type=list,
            advanced=True,
            help=
            'Exclude these targets from dependency resolution during workspace generation'
        )
        register(
            '--template-directory',
            type=str,
            advanced=True,
            default=None,
            help=
            'Exclude these targets from dependency resolution during workspace generation'
        )

    def __init__(self, *args, **kwargs):
        super(EnsimeGen, self).__init__(*args, **kwargs)

        self._renderer = MustacheRenderer(
            self.get_options().template_directory, __name__)
        self.project_filename = os.path.join(self.cwd, '.ensime')
        self.ensime_output_dir = os.path.join(self.gen_project_workdir, 'out')

    def resolve_jars(self, targets):
        excluded_targets = set()

        for exclusion in self.get_options().excluded_deps:
            for target in self.context.resolve(exclusion):
                excluded_targets.add(target)

        synthetic_target = self.context.add_new_target(address=Address(
            '', 'exlusions'),
                                                       target_type=JavaLibrary,
                                                       dependencies=list(),
                                                       sources=list(),
                                                       excludes=[])
        filtered_targets = [
            target for target in targets if not target in excluded_targets
        ] + [synthetic_target]

        return super(EnsimeGen, self).resolve_jars(filtered_targets)

    def generate_project(self, project):
        def linked_folder_id(source_set):
            return source_set.source_base.replace(os.path.sep, '.')

        def base_path(source_set):
            return os.path.join(source_set.root_dir, source_set.source_base,
                                source_set.path)

        def create_source_base_template(source_set):
            source_base = base_path(source_set)
            return SourceBase(id=linked_folder_id(source_set),
                              path=source_base)

        source_sets = project.sources[:]
        if project.has_python:
            source_sets.extend(project.py_sources)

        source_bases = frozenset(map(create_source_base_template, source_sets))

        libs = []

        def add_jarlibs(classpath_entries):
            for classpath_entry in classpath_entries:
                libs.append((classpath_entry.jar, classpath_entry.source_jar))

        add_jarlibs(project.internal_jars)
        add_jarlibs(project.external_jars)
        scala_full_version = scala_platform.scala_build_info[
            self.context.options['scala-platform']['version']].full_version
        scala = {
            'language_level': scala_full_version,
            'compiler_classpath': project.scala_compiler_classpath
        }

        outdir = os.path.abspath(self.ensime_output_dir)
        if not os.path.exists(outdir):
            os.makedirs(outdir)

        java_platform = JvmPlatform.global_instance().default_platform
        jdk_home = JvmPlatform.preferred_jvm_distribution([java_platform],
                                                          strict=True).home

        configured_project = {
            'name':
            self.project_name,
            'java_home':
            jdk_home,
            'scala':
            scala,
            'source_bases':
            source_bases,
            'has_tests':
            project.has_tests,
            'internal_jars':
            [cp_entry.jar for cp_entry in project.internal_jars],
            'internal_source_jars': [
                cp_entry.source_jar for cp_entry in project.internal_jars
                if cp_entry.source_jar
            ],
            'external_jars':
            [cp_entry.jar for cp_entry in project.external_jars],
            'external_javadoc_jars': [
                cp_entry.javadoc_jar for cp_entry in project.external_jars
                if cp_entry.javadoc_jar
            ],
            'external_source_jars': [
                cp_entry.source_jar for cp_entry in project.external_jars
                if cp_entry.source_jar
            ],
            'libs':
            libs,
            'outdir':
            os.path.relpath(outdir, get_buildroot()),
            'root_dir':
            get_buildroot(),
            'cache_dir':
            os.path.join(self.cwd, '.ensime_cache')
        }

        with safe_open(self.project_filename, 'w') as output:
            output.write(
                self._renderer.render_name(_TEMPLATE,
                                           {'project': configured_project}))

        print('\nGenerated ensime project at {}{}'.format(
            self.gen_project_workdir, os.sep))