summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2015-10-21 22:55:10 +0300
committerLars Wirzenius <liw@liw.fi>2015-10-24 17:20:53 +0300
commit939214962dfb003db3d6f322398c344e95a9134a (patch)
treebd635d38916786ac8e45eb04b812d1f5bb537b63
parent72d1ed8ece7985245a80a477dea0d9286d815e00 (diff)
downloadick-939214962dfb003db3d6f322398c344e95a9134a.tar.gz
Add ick-html program
-rw-r--r--NEWS2
-rwxr-xr-xick-html109
-rw-r--r--icklib/buildinfo.py7
-rw-r--r--icklib/project.py20
-rw-r--r--icklib/templates/index.j221
-rw-r--r--icklib/templates/project.j216
6 files changed, 172 insertions, 3 deletions
diff --git a/NEWS b/NEWS
index 0951bb1..a87846f 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ Version 0.8, released UNRELEASED
--------------------------------
* Stop creating a build directory for no-op builds.
+* Add the `ick-html` program to produce simple, ugly web pages from a
+ state directory.
Version 0.7, released 2015-10-11
--------------------------------
diff --git a/ick-html b/ick-html
new file mode 100755
index 0000000..08510c6
--- /dev/null
+++ b/ick-html
@@ -0,0 +1,109 @@
+#!/usr/bin/env python2
+# Copyright 2015 Lars Wirzenius
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+# =*= License: GPL-3+ =*=
+
+
+import glob
+import os
+import shutil
+
+import cliapp
+import jinja2
+import yaml
+
+import icklib
+
+
+class IckHTML(cliapp.Application):
+
+ def process_args(self, args):
+ ick_filename, html_dir = self.parse_command_line_args(args)
+ ick = self.read_ick_file(ick_filename)
+ self.create_html(ick_filename, ick, html_dir)
+
+ def parse_command_line_args(self, args):
+ if len(args) != 2:
+ raise cliapp.AppException('Must have exactly one argument')
+ return args[0], args[1]
+
+ def read_ick_file(self, filename):
+ with open(filename) as f:
+ return yaml.safe_load(f)
+
+ def create_html(self, ick_filename, ick, html_dir):
+ loader = jinja2.PackageLoader('icklib', 'templates')
+ env = jinja2.Environment(
+ loader=loader,
+ autoescape=lambda foo: True,
+ extensions=['jinja2.ext.autoescape'])
+
+ statedir = icklib.create_statedir_from_ick(ick)
+
+ self.create_index_html(ick_filename, ick, env, html_dir)
+ for project in self.get_projects(ick):
+ project.builds = statedir.get_build_infos(project.name)
+ self.copy_build_files_to_html_dir(statedir, project, html_dir)
+ self.create_project_html(statedir, project, env, html_dir)
+
+ def create_index_html(self, ick_filename, ick, env, html_dir):
+ vars = {
+ 'ick_filename': ick_filename,
+ 'projects': self.get_projects(ick),
+ 'statedir': icklib.create_statedir_from_ick(ick),
+ }
+
+ template = env.get_template('index.j2')
+ with open(os.path.join(html_dir, 'index.html'), 'w') as f:
+ f.write(template.render(**vars))
+
+ def get_projects(self, ick):
+ return sorted(
+ icklib.create_projects_from_ick(ick, None),
+ key=lambda p: p.name)
+
+ def copy_build_files_to_html_dir(self, statedir, project, html_dir):
+ for build in project.builds:
+ build_dir = os.path.join(
+ html_dir, project.name, str(build.build_number))
+ os.makedirs(build_dir)
+ self.copy_build_log(build, build_dir)
+ self.copy_build_artifacts(statedir, project, build, build_dir)
+
+ def copy_build_log(self, build, build_dir):
+ orig = build.get_build_log_filename()
+ target = os.path.join(build_dir, 'build_log.txt')
+ shutil.copy(orig, target)
+
+ def copy_build_artifacts(self, statedir, project, build, build_dir):
+ artifacts = build.get_artifacts_directory()
+ target = os.path.join(build_dir, 'build_log.txt')
+ for artifact in glob.glob(os.path.join(artifacts, '*')):
+ shutil.copy(artifact, build_dir)
+
+ def create_project_html(self, statedir, project, env, html_dir):
+
+ vars = {
+ 'project': project,
+ }
+
+ template = env.get_template('project.j2')
+ with open(os.path.join(html_dir, '%s.html' % project.name), 'w') as f:
+ f.write(template.render(**vars))
+
+
+if __name__ == '__main__':
+ IckHTML().run()
diff --git a/icklib/buildinfo.py b/icklib/buildinfo.py
index ad76c5f..022516b 100644
--- a/icklib/buildinfo.py
+++ b/icklib/buildinfo.py
@@ -23,7 +23,7 @@ import yaml
class BuildInformation(object):
- _attrs = ['build_number', 'commits', 'pipeline']
+ _attrs = ['build_number', 'commits', 'pipeline', 'status']
def __init__(self):
self._dirname = None
@@ -33,8 +33,11 @@ class BuildInformation(object):
def set_dirname(self, dirname):
self._dirname = dirname
+ def get_artifacts_directory(self):
+ return os.path.join(self._dirname, 'artifacts')
+
def create_artifacts_directory(self):
- dirname = os.path.join(self._dirname, 'artifacts')
+ dirname = self.get_artifacts_directory()
if not os.path.exists(dirname):
os.makedirs(dirname)
return dirname
diff --git a/icklib/project.py b/icklib/project.py
index 2c522c9..5cd4646 100644
--- a/icklib/project.py
+++ b/icklib/project.py
@@ -81,7 +81,13 @@ class Project(object):
project=self.name)
run_state.logger.important(
'Executing pipeline {name}', name=pipeline_name)
- pipeline.build(self, statedir, targets, run_state)
+ try:
+ pipeline.build(self, statedir, targets, run_state)
+ except:
+ if getattr(run_state, 'build_info', None) is not None:
+ run_state.build_info.status = 'FAILURE'
+ run_state.build_info.save()
+ raise
def run_argv_on_target(self, target, argv, displayed_cmd, remote_cwd):
self.report_running_on_target(target, displayed_cmd)
@@ -300,6 +306,7 @@ class CreateBuildInfo(icklib.BuildStep):
self.run_state.project_info.save() # Don't want to lose this if crash.
self.run_state.build_info = self.statedir.create_new_build(
self.project.name, build_number)
+ self.run_state.build_info.status = 'RUNNING'
self.pivot_to_real_build_log_file(self.run_state)
@@ -347,6 +354,14 @@ class CreateBuildInfo(icklib.BuildStep):
run_state.logger.add_output_file(f, False)
+class FinishBuildInfo(icklib.BuildStep):
+
+ def build(self):
+ if getattr(self.run_state, 'build_info', None) is not None:
+ self.run_state.build_info.status = 'SUCCESS'
+ self.run_state.build_info.save()
+
+
class RunShellCommandsOnEachTarget(icklib.BuildStep):
def build(self):
@@ -884,6 +899,7 @@ def create_projects_from_ick(ick, wanted_names):
FindNewCommitToBuildForShell,
CreateBuildInfo,
RunShellCommandsOnEachTarget,
+ FinishBuildInfo,
SaveProjectInfo,
],
'debian-ci': [
@@ -899,6 +915,7 @@ def create_projects_from_ick(ick, wanted_names):
FindDebianChangesFiles,
SetupAPTRepository,
UploadDebianPackages,
+ FinishBuildInfo,
SaveProjectInfo,
],
'debian-release': [
@@ -914,6 +931,7 @@ def create_projects_from_ick(ick, wanted_names):
FindDebianChangesFiles,
SetupAPTRepository,
UploadDebianPackages,
+ FinishBuildInfo,
SaveProjectInfo,
],
}
diff --git a/icklib/templates/index.j2 b/icklib/templates/index.j2
new file mode 100644
index 0000000..b462cae
--- /dev/null
+++ b/icklib/templates/index.j2
@@ -0,0 +1,21 @@
+<html>
+ <head>
+ <title>Ick projects</title>
+ </head>
+ <body>
+ <h1>Ick projects</h1>
+ <p>This is a list of projects built by Ick, from
+ {{ ick_filename }}.</p>
+ <ul>
+ {% for project in projects %}
+ <li><a href="{{ project.name }}.html">{{ project.name }}</a>
+ {% if statedir.get_build_infos(project.name) %}
+ (latest build: {{ statedir.get_build_infos(project.name)[-1].status }})
+ {% else %}
+ (no builds)
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+ </body>
+</html>
diff --git a/icklib/templates/project.j2 b/icklib/templates/project.j2
new file mode 100644
index 0000000..929104b
--- /dev/null
+++ b/icklib/templates/project.j2
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <title>Ick: {{ project.name }}</title>
+ </head>
+ <body>
+ <h1>{{ project.name }}</h1>
+ <ul>
+ {% for build in project.builds %}
+ <li>Build #{{ build.build_number }}: {{ build.status }}
+ <a href="{{project.name}}/{{build.build_number}}/build_log.txt"
+ >build log</a>
+ <a href="{{project.name}}/{{build.build_number}}/">artifacts</a></li>
+ {% endfor %}
+ </ul>
+ </body>
+</html>