diff --git a/lib/spack/spack/cmd/install.py b/lib/spack/spack/cmd/install.py
index 12c6551716..81f84dd1a2 100644
--- a/lib/spack/spack/cmd/install.py
+++ b/lib/spack/spack/cmd/install.py
@@ -191,7 +191,8 @@ def install(parser, args, **kwargs):
tty.warn("Deprecated option: --run-tests: use --test=all instead")
# 1. Abstract specs from cli
- reporter = spack.report.collect_info(args.log_format)
+ reporter = spack.report.collect_info(args.log_format,
+ ' '.join(args.package))
if args.log_file:
reporter.filename = args.log_file
diff --git a/lib/spack/spack/report.py b/lib/spack/spack/report.py
index 9251efed5f..404799604a 100644
--- a/lib/spack/spack/report.py
+++ b/lib/spack/spack/report.py
@@ -27,13 +27,19 @@
import functools
import itertools
import os.path
+import platform
+import re
+import socket
import time
import traceback
+import xml.sax.saxutils
import llnl.util.lang
import spack.build_environment
import spack.fetch_strategy
import spack.package
+from spack.util.log_parse import parse_log_events
+
templates = {
'junit': os.path.join('reports', 'junit.xml'),
@@ -51,7 +57,7 @@
def fetch_package_log(pkg):
try:
- with open(pkg.build_log_path, 'r') as f:
+ with codecs.open(pkg.build_log_path, 'r', 'utf-8') as f:
return ''.join(f.readlines())
except Exception:
return 'Cannot open build log for {0}'.format(
@@ -250,8 +256,17 @@ class collect_info(object):
Raises:
ValueError: when ``format_name`` is not in ``valid_formats``
"""
- def __init__(self, format_name):
+ def __init__(self, format_name, install_command):
self.format_name = format_name
+ # Consider setting these properties in a more CDash specific place.
+ self.install_command = install_command
+ self.hostname = socket.gethostname()
+ self.osname = platform.system()
+ self.starttime = int(time.time())
+ # TODO: remove hardcoded use of Experimental here.
+ # Make the submission model configurable.
+ self.buildstamp = time.strftime("%Y%m%d-%H%M-Experimental",
+ time.localtime(self.starttime))
# Check that the format is valid
if format_name not in itertools.chain(valid_formats, [None]):
@@ -263,14 +278,102 @@ def __enter__(self):
self.collector = InfoCollector(self.specs)
self.collector.__enter__()
+ def cdash_initialize_report(self, report_data):
+ if not os.path.exists(self.filename):
+ os.mkdir(self.filename)
+ report_data['install_command'] = self.install_command
+ report_data['buildstamp'] = self.buildstamp
+ report_data['hostname'] = self.hostname
+ report_data['osname'] = self.osname
+
+ def cdash_build_report(self, report_data):
+ self.cdash_initialize_report(report_data)
+
+ # Mapping Spack phases to the corresponding CTest/CDash phase.
+ map_phases_to_cdash = {
+ 'autoreconf': 'configure',
+ 'cmake': 'configure',
+ 'configure': 'configure',
+ 'edit': 'configure'
+ }
+
+ # Initialize data structures common to each phase's report.
+ cdash_phases = set(map_phases_to_cdash.values())
+ for phase in cdash_phases:
+ report_data[phase] = {}
+ report_data[phase]['log'] = ""
+ report_data[phase]['status'] = 0
+ report_data[phase]['starttime'] = self.starttime
+ report_data[phase]['endtime'] = self.starttime
+
+ # Track the phases we perform so we know what reports to create.
+ phases_encountered = []
+
+ # Parse output phase-by-phase.
+ phase_regexp = re.compile(r"Executing phase: '(.*)'")
+ for spec in self.collector.specs:
+ for package in spec['packages']:
+ if 'stdout' in package:
+ current_phase = ''
+ for line in package['stdout'].splitlines():
+ match = phase_regexp.search(line)
+ if match:
+ current_phase = match.group(1)
+ if current_phase not in map_phases_to_cdash:
+ current_phase = ''
+ continue
+ beginning_of_phase = True
+ else:
+ if beginning_of_phase:
+ cdash_phase = \
+ map_phases_to_cdash[current_phase]
+ if cdash_phase not in phases_encountered:
+ phases_encountered.append(cdash_phase)
+ report_data[cdash_phase]['log'] += \
+ text_type("{0} output for {1}:\n".format(
+ cdash_phase, package['name']))
+ beginning_of_phase = False
+ report_data[cdash_phase]['log'] += \
+ xml.sax.saxutils.escape(line) + "\n"
+
+ for phase in phases_encountered:
+ errors, warnings = parse_log_events(
+ report_data[phase]['log'].splitlines())
+ nerrors = len(errors)
+
+ if phase == 'configure' and nerrors > 0:
+ report_data[phase]['status'] = 1
+
+ # Write the report.
+ report_name = phase.capitalize() + ".xml"
+ phase_report = os.path.join(self.filename, report_name)
+
+ with open(phase_report, 'w') as f:
+ env = spack.tengine.make_environment()
+ site_template = os.path.join(templates[self.format_name],
+ 'Site.xml')
+ t = env.get_template(site_template)
+ f.write(t.render(report_data))
+
+ phase_template = os.path.join(templates[self.format_name],
+ report_name)
+ t = env.get_template(phase_template)
+ f.write(t.render(report_data))
+
def __exit__(self, exc_type, exc_val, exc_tb):
if self.format_name:
# Close the collector and restore the
# original PackageBase.do_install
self.collector.__exit__(exc_type, exc_val, exc_tb)
- # Write the report
- with open(self.filename, 'w') as f:
- env = spack.tengine.make_environment()
- t = env.get_template(templates[self.format_name])
- f.write(t.render({'specs': self.collector.specs}))
+ report_data = {'specs': self.collector.specs}
+
+ if self.format_name == 'cdash':
+ # CDash reporting results are split across multiple files.
+ self.cdash_build_report(report_data)
+ else:
+ # Write the report
+ with open(self.filename, 'w') as f:
+ env = spack.tengine.make_environment()
+ t = env.get_template(templates[self.format_name])
+ f.write(t.render(report_data))
diff --git a/templates/reports/cdash/Configure.xml b/templates/reports/cdash/Configure.xml
new file mode 100644
index 0000000000..0451279563
--- /dev/null
+++ b/templates/reports/cdash/Configure.xml
@@ -0,0 +1,8 @@
+
+ {{ configure.starttime }}
+ {{ install_command }}
+ {{ configure.log }}
+ {{ configure.status }}
+ {{ configure.endtime }}
+
+
diff --git a/templates/reports/cdash/Site.xml b/templates/reports/cdash/Site.xml
new file mode 100644
index 0000000000..a47ffd34e6
--- /dev/null
+++ b/templates/reports/cdash/Site.xml
@@ -0,0 +1,7 @@
+
+
+