summaryrefslogtreecommitdiff
path: root/doc/bst2html.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/bst2html.py')
-rwxr-xr-xdoc/bst2html.py303
1 files changed, 188 insertions, 115 deletions
diff --git a/doc/bst2html.py b/doc/bst2html.py
index 33e86e05c..9c5aaaf9a 100755
--- a/doc/bst2html.py
+++ b/doc/bst2html.py
@@ -25,13 +25,18 @@
# https://github.com/Kronuz/ansi2html.git
#
import os
+import sys
import re
+import shlex
import subprocess
+from collections import Mapping
+from contextlib import contextmanager
from tempfile import TemporaryDirectory
import click
from buildstream import _yaml
+from buildstream._exceptions import BstError
_ANSI2HTML_STYLES = {}
@@ -169,20 +174,163 @@ def ansi2html(text, palette='solarized'):
return sub
-# FIXME: Workaround a setuptools bug which fails to include symbolic
-# links in the source distribution.
+# workdir()
#
-# Remove this hack once setuptools is fixed
-def workaround_setuptools_bug(project):
- os.makedirs(os.path.join(project, "files", "links"), exist_ok=True)
- try:
- os.symlink(os.path.join("usr", "lib"), os.path.join(project, "files", "links", "lib"))
- os.symlink(os.path.join("usr", "bin"), os.path.join(project, "files", "links", "bin"))
- os.symlink(os.path.join("usr", "etc"), os.path.join(project, "files", "links", "etc"))
- except FileExistsError:
- # If the files exist, we're running from a git checkout and
- # not a source distribution, no need to complain
- pass
+# Sets up a new temp directory with a config file
+#
+# Args:
+# work_directory (str): The directory where to create a tempdir first
+# source_cache (str): The directory of a source cache to share with, or None
+#
+# Yields:
+# The buildstream.conf full path
+#
+@contextmanager
+def workdir(source_cache=None):
+ with TemporaryDirectory(prefix='run-bst-', dir=os.getcwd()) as tempdir:
+
+ bst_config_file = os.path.join(tempdir, 'buildstream.conf')
+ config = {
+ 'sourcedir': source_cache,
+ 'artifactdir': os.path.join(tempdir, 'artifacts'),
+ 'logdir': os.path.join(tempdir, 'logs'),
+ 'builddir': os.path.join(tempdir, 'build'),
+ }
+ _yaml.dump(config, bst_config_file)
+
+ yield (tempdir, bst_config_file)
+
+
+# run_command()
+#
+# Runs a command
+#
+# Args:
+# config_file (str): The path to the config file to use
+# directory (str): The project directory
+# command (str): A command string
+#
+# Returns:
+# (str): The colorized combined stdout/stderr of BuildStream
+#
+def run_command(config_file, directory, command):
+ click.echo("Running command in directory '{}': bst {}".format(directory, command), err=True)
+
+ argv = ['bst', '--colors', '--config', config_file] + shlex.split(command)
+ p = subprocess.Popen(argv, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ out, _ = p.communicate()
+ return out.decode('utf-8').strip()
+
+
+# generate_html
+#
+# Generate html based on the output
+#
+# Args:
+# output (str): The output of the BuildStream command
+# directory (str): The project directory
+# config_file (str): The config file
+# source_cache (str): The source cache
+# tempdir (str): The base work directory
+# palette (str): The rendering color style
+# command (str): The command
+#
+# Returns:
+# (str): The html formatted output
+#
+def generate_html(output, directory, config_file, source_cache, tempdir, palette, command):
+
+ test_base_name = os.path.basename(directory)
+ show_command = 'bst ' + command
+
+ # Substitute some things we want normalized for the docs
+ output = re.sub(os.environ.get('HOME'), '/home/user', output)
+ output = re.sub(config_file, '/home/user/.config/buildstream.conf', output)
+ output = re.sub(source_cache, '/home/user/.cache/buildstream/sources', output)
+ output = re.sub(tempdir, '/home/user/.cache/buildstream', output)
+ output = re.sub(directory, '/home/user/{}'.format(test_base_name), output)
+
+ # Now convert to HTML and add some surrounding sugar
+ output = ansi2html(output, palette=palette)
+
+ # Finally format it nicely into a <div>
+ output = '<!--\n' + \
+ ' WARNING: This file was generated with bst2html.py\n' + \
+ '-->\n' + \
+ '<div class="highlight" style="font-size:x-small">' + \
+ '<pre>\n' + \
+ '<span style="color:#C4A000;font-weight:bold">user@host</span>:' + \
+ '<span style="color:#3456A4;font-weight:bold">~/{}</span>$ '.format(test_base_name) + \
+ show_command + '\n\n' + \
+ output + '\n' + \
+ '</pre></div>\n'
+
+ return output
+
+
+def run_session(description, tempdir, source_cache, palette, config_file):
+
+ desc = _yaml.load(description, shortname=os.path.basename(description))
+ desc_dir = os.path.dirname(description)
+
+ # FIXME: Workaround a setuptools bug where the symlinks
+ # we store in git dont get carried into a release
+ # tarball. This workaround lets us build docs from
+ # a source distribution tarball.
+ #
+ symlinks = _yaml.node_get(desc, Mapping, 'workaround-symlinks', default_value={})
+ for symlink, target in _yaml.node_items(symlinks):
+
+ # Resolve real path to where symlink should be
+ symlink = os.path.join(desc_dir, symlink)
+
+ # Ensure dir exists
+ symlink_dir = os.path.dirname(symlink)
+ os.makedirs(symlink_dir, exist_ok=True)
+
+ click.echo("Generating symlink at: {} (target: {})".format(symlink, target), err=True)
+
+ # Generate a symlink
+ try:
+ os.symlink(target, symlink)
+ except FileExistsError:
+ # If the files exist, we're running from a git checkout and
+ # not a source distribution, no need to complain
+ pass
+
+ # Run commands
+ #
+ commands = _yaml.node_get(desc, list, 'commands')
+ for c in commands:
+ command = _yaml.node_get(desc, Mapping, 'commands', indices=[commands.index(c)])
+
+ # Get the directory where this command should be run
+ directory = _yaml.node_get(command, str, 'directory')
+ directory = os.path.join(desc_dir, directory)
+ directory = os.path.realpath(directory)
+
+ # Run the command
+ command_str = _yaml.node_get(command, str, 'command')
+ command_out = run_command(config_file, directory, command_str)
+
+ # Encode and save the output if that was asked for
+ output = _yaml.node_get(command, str, 'output', default_value=None)
+ if output is not None:
+
+ # Convert / Generate a nice <div>
+ converted = generate_html(command_out, directory, config_file,
+ source_cache, tempdir, palette,
+ command_str)
+
+ # Save it
+ filename = os.path.join(desc_dir, output)
+ filename = os.path.realpath(filename)
+ output_dir = os.path.dirname(filename)
+ os.makedirs(output_dir, exist_ok=True)
+ with open(filename, 'wb') as f:
+ f.write(converted.encode('utf-8'))
+
+ click.echo("Saved session at '{}'".format(filename), err=True)
@click.command(short_help="Run a bst command and capture stdout/stderr in html")
@@ -205,121 +353,42 @@ def workaround_setuptools_bug(project):
def run_bst(directory, source_cache, description, palette, output, command):
"""Run a bst command and capture stdout/stderr in html
- This command normally takes a description yaml file, the format
- of that file is as follows:
-
- \b
- # A relative path to the project, from the description file itself
- directory: path/to/project
-
- \b
- # A list of commands to run in preparation
- prepare-commands:
- - fetch hello.bst
-
- \b
- # The command to generate html output for
- command: build hello.bst
+ This command normally takes a description yaml file, see the HACKING
+ file for information on it's format.
"""
- prepare_commands = []
-
- if description:
- desc = _yaml.load(description, shortname=os.path.basename(description))
- desc_dir = os.path.dirname(description)
+ if not source_cache and os.environ.get('BST_SOURCE_CACHE'):
+ source_cache = os.environ['BST_SOURCE_CACHE']
- command_str = _yaml.node_get(desc, str, 'command')
- command = command_str.split()
+ with workdir(source_cache=source_cache) as (tempdir, config_file):
- # The directory should be relative to where the description file was
- # stored
- directory_str = _yaml.node_get(desc, str, 'directory')
- directory = os.path.join(desc_dir, directory_str)
- directory = os.path.realpath(directory)
-
- prepare = _yaml.node_get(desc, list, 'prepare-commands', default_value=[])
- for prepare_command in prepare:
- prepare_commands.append(prepare_command)
+ if not source_cache:
+ source_cache = os.path.join(tempdir, 'sources')
- else:
- if not command:
- command = []
+ if description:
+ run_session(description, tempdir, source_cache, palette, config_file)
+ return
+ # Run a command specified on the CLI
+ #
if not directory:
directory = os.getcwd()
else:
directory = os.path.abspath(directory)
directory = os.path.realpath(directory)
- # FIXME: Here we just setup a files/links subdir with
- # the symlinks we want for usrmerge, ideally
- # we dont need this anymore once setuptools gets
- # fixed.
- #
- workaround_setuptools_bug(directory)
-
- test_base_name = os.path.basename(directory)
-
- if not source_cache and os.environ.get('BST_SOURCE_CACHE'):
- source_cache = os.environ['BST_SOURCE_CACHE']
-
- with TemporaryDirectory(prefix='run-bst-', dir=directory) as tempdir:
- bst_config_file = os.path.join(tempdir, 'buildstream.conf')
- final_command = ['bst', '--colors', '--config', bst_config_file]
- final_command += command
-
- show_command = ['bst']
- show_command += command
- show_command_string = ' '.join(show_command)
-
- if not source_cache:
- source_cache = os.path.join(tempdir, 'sources')
-
- config = {
- 'sourcedir': source_cache,
- 'artifactdir': os.path.join(tempdir, 'artifacts'),
- 'logdir': os.path.join(tempdir, 'logs'),
- 'builddir': os.path.join(tempdir, 'build'),
- }
- _yaml.dump(config, bst_config_file)
+ if not command:
+ command = []
+ command_str = " ".join(command)
- # Run some prepare commands if they were specified
+ # Run the command
#
- for prepare_command_str in prepare_commands:
- prepare_command = ['bst', '--config', bst_config_file] + prepare_command_str.split()
- p = subprocess.Popen(prepare_command, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- _, _ = p.communicate()
+ command_out = run_command(config_file, directory, command_str)
- # Run BuildStream and collect the output in a single string,
- # with the ANSI escape sequences forced enabled.
+ # Generate a nice html div for this output
#
- p = subprocess.Popen(final_command, cwd=directory, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
- out, _ = p.communicate()
- decoded = out.decode('utf-8').strip()
-
- # Substitute some things we want normalized for the docs
- decoded = re.sub(os.environ.get('HOME'), '/home/user', decoded)
- decoded = re.sub(bst_config_file, '/home/user/.config/buildstream.conf', decoded)
- decoded = re.sub(source_cache, '/home/user/.cache/buildstream/sources', decoded)
- decoded = re.sub(tempdir, '/home/user/.cache/buildstream', decoded)
- decoded = re.sub(directory, '/home/user/{}'.format(test_base_name), decoded)
-
- # Now convert to HTML and add some surrounding sugar
- div_style = 'font-size:x-small'
- converted = ansi2html(decoded, palette=palette)
- converted = '<div class="highlight" style="{}">'.format(div_style) + \
- '<pre>\n' + \
- '<span style="color:#C4A000;font-weight:bold">user@host</span>:' + \
- '<span style="color:#3456A4;font-weight:bold">~/{}</span>$ '.format(test_base_name) + \
- show_command_string + '\n\n' + \
- converted + '\n' + \
- '</pre></div>\n'
-
- # Prepend a warning
- #
- converted = '<!--\n' + \
- ' WARNING: This file was generated with bst2html.py\n' + \
- '-->\n' + \
- converted
+ converted = generate_html(command_out, directory, config_file,
+ source_cache, tempdir, palette,
+ command_str)
if output is None:
click.echo(converted)
@@ -329,7 +398,11 @@ def run_bst(directory, source_cache, description, palette, output, command):
with open(output, 'wb') as f:
f.write(converted.encode('utf-8'))
- return p.returncode
+ return 0
if __name__ == '__main__':
- run_bst()
+ try:
+ run_bst()
+ except BstError as e:
+ click.echo("Error: {}".format(e), err=True)
+ sys.exit(-1)