Skip to content

Commit

Permalink
Merge pull request #3349 from hjoliver/ref-graph-no-gtk
Browse files Browse the repository at this point in the history
Cylc 7 ref graphs without pygtk
  • Loading branch information
hjoliver committed Sep 5, 2019
2 parents ce88a2b + c61579a commit de7d938
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 5 deletions.
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ pages**.
- cylc-8 (master branch, Python 3 - not yet released) does not bundle Jinja2,
and uses the fixed version 2.10.1.

-------------------------------------------------------------------------------
## __cylc-7.8.5 (2019-Q4?)__


### Enhancements

[#3349](https://github.com/cylc/cylc-flow/pull/3349) - new command `cylc
ref-graph` to generate text-format "reference graphs" without PyGTK (back-port
from Python 3 master for Cylc 8).


-------------------------------------------------------------------------------
## __cylc-7.8.4 (2019-09-04)__

Expand Down
4 changes: 4 additions & 0 deletions bin/cylc
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ help_util() {
local COMMAND="${CYLC_HOME_BIN}/cylc-graph"
exec "${COMMAND}" "--help"
fi
if [[ "$@" == "ref-graph" ]]; then
local COMMAND="${CYLC_HOME_BIN}/cylc-ref-graph"
exec "${COMMAND}" "--help"
fi
# For help command/option with no args
if [[ $# == 0 && "${HELP_OPTS[*]} " == *"$UTIL"* ]]; then
exec "${CYLC_HOME_BIN}/cylc-help"
Expand Down
6 changes: 5 additions & 1 deletion bin/cylc-graph
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
Plot SUITE dependencies to a file FILE with a extension-derived format.
If FILE endswith ".png", output in PNG format, etc.
Plot suite dependency graphs in an interactive graph viewer.
Plot suite dependency graphs in an interactive graph viewer, or (with
"--output-file") directly to image file.
See also "cylc ref-graph" to generate the plain text "reference" graph format
without the need for PyGTK to be installed.
If START is given it overrides "[visualization] initial cycle point" to
determine the start point of the graph, which defaults to the suite initial
Expand Down
2 changes: 2 additions & 0 deletions bin/cylc-help
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ preparation_commands['5to6'] = ['5to6']
preparation_commands['list'] = ['list', 'ls']
preparation_commands['search'] = ['search', 'grep']
preparation_commands['graph'] = ['graph']
preparation_commands['ref-graph'] = ['ref-graph']
preparation_commands['graph-diff'] = ['graph-diff']
preparation_commands['diff'] = ['diff', 'compare']
preparation_commands['jobscript'] = ['jobscript']
Expand Down Expand Up @@ -380,6 +381,7 @@ comsum['validate'] = 'Parse and validate suite definitions'
comsum['5to6'] = 'Improve the cylc 6 compatibility of a cylc 5 suite file'
comsum['search'] = 'Search in suite definitions'
comsum['graph'] = 'Plot suite dependency graphs and runtime hierarchies'
comsum['ref-graph'] = 'Print text-format "reference graphs" without GTK'
comsum['graph-diff'] = 'Compare two suite dependencies or runtime hierarchies'
comsum['diff'] = 'Compare two suite definitions and print differences'
# information
Expand Down
238 changes: 238 additions & 0 deletions bin/cylc-ref-graph
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
#!/usr/bin/env python2
#
# THIS FILE IS PART OF THE CYLC SUITE ENGINE.
# Copyright (C) 2008-2019 NIWA & British Crown (Met Office) & Contributors.
#
# 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/>.
"""Usage:
cylc ref-graph SUITE [START] [STOP]
Implement the old ``cylc graph --reference command`` for producing a
text-format representation of a suite graph.
The `--reference` flag is optional here.
THIS IS A BACK-PORT OF the Python 3 bin/cylc-graph FOR CYLC 8, TO GENERATE
TEXT-FORMAT "REFERENCE GRAPHS" WITHOUT THE NEED FOR PYGTK TO BE INSTALLED."""

from cylc.config import SuiteConfig
from cylc.cycling.loader import get_point
from cylc.option_parsers import CylcOptionParser as COP
from cylc.suite_srv_files_mgr import (
SuiteSrvFilesManager, SuiteServiceFileError)
from cylc.templatevars import load_template_vars


def sort_integer_node(item):
"""Return sort tokens for nodes with cyclepoints in integer format.
Example:
>>> sort_integer_node('foo.11')
('foo', 11)
"""
name, point = item.split('.')
return (name, int(point))


def sort_integer_edge(item):
"""Return sort tokens for edges with cyclepoints in integer format.
Example:
>>> sort_integer_edge(('foo.11', 'foo.12', None))
(('foo', 11), ('foo', 12))
>>> sort_integer_edge(('foo.11', None , None))
(('foo', 11), ('', 0))
"""

return (
sort_integer_node(item[0]),
sort_integer_node(item[1]) if item[1] else ('', 0)
)


def sort_datetime_edge(item):
"""Return sort tokens for edges with cyclepoints in ISO8601 format.
Example:
>>> sort_datetime_edge(('a', None, None))
('a', '')
"""
return (item[0], item[1] or '')


def get_cycling_bounds(config, start_point=None, stop_point=None):
"""Determine the start and stop points for graphing a suite."""
# default start and stop points to values in the visualization section
if not start_point:
start_point = config.cfg['visualization']['initial cycle point']
if not stop_point:
viz_stop_point = config.cfg['visualization']['final cycle point']
if viz_stop_point:
stop_point = viz_stop_point

# don't allow stop_point before start_point
if stop_point is not None:
if get_point(stop_point) < get_point(start_point):
# NOTE: we need to cast with get_point for this comparison due to
# ISO8061 extended datetime formats
stop_point = start_point

return start_point, stop_point


def graph_workflow(config, start_point=None, stop_point=None, ungrouped=False,
show_suicide=False):
"""Implement ``cylc-graph --reference``."""
# set sort keys based on cycling mode
if config.cfg['scheduling']['cycling mode'] == 'integer':
# integer sorting
node_sort = sort_integer_node
edge_sort = sort_integer_edge
else:
# datetime sorting
node_sort = None # lexicographically sortable
edge_sort = sort_datetime_edge

# get graph
start_point, stop_point = get_cycling_bounds(
config, start_point, stop_point)
graph = config.get_graph_raw(
start_point, stop_point, ungroup_all=ungrouped)
if not graph:
return

edges = (
(left, right)
for left, right, _, suicide, _ in graph
if right
if show_suicide or not suicide
)
for left, right in sorted(set(edges), key=edge_sort):
print('edge "%s" "%s"' % (left, right))

print('graph')

# print nodes
nodes = (
node
for left, right, _, suicide, _ in graph
for node in (left, right)
if node
if show_suicide or not suicide
)
for node in sorted(set(nodes), key=node_sort):
print('node "%s" "%s"' % (node, node.replace('.', r'\n')))

print('stop')


def graph_inheritance(config):
"""Implement ``cylc-graph --reference --namespaces``."""
edges = set()
nodes = set()
for namespace, tasks in config.get_parent_lists().items():
for task in tasks:
edges.add((task, namespace))
nodes.add(task)

for namespace in config.get_parent_lists():
nodes.add(namespace)

for edge in sorted(edges):
print('edge %s %s' % edge)

print('graph')

for node in sorted(nodes):
print('node %s %s' % (node, node))

print('stop')


def get_config(suite, opts, template_vars=None):
"""Return a SuiteConfig object for the provided reg / path."""
try:
suiterc = SuiteSrvFilesManager().get_suite_rc(suite)
except SuiteServiceFileError:
# could not find suite, assume we have been given a path instead
suiterc = suite
suite = 'test'
return SuiteConfig(suite, suiterc, opts, template_vars=template_vars)


def get_option_parser():
"""CLI."""
parser = COP(
__doc__, jset=True, prep=True,
argdoc=[
('[SUITE]', 'Suite name or path'),
('[START]', 'Initial cycle point '
'(default: suite initial point)'),
('[STOP]', 'Final cycle point '
'(default: initial + 3 points)')])

parser.add_option(
'-u', '--ungrouped',
help='Start with task families ungrouped (the default is grouped).',
action='store_true', default=False, dest='ungrouped')

parser.add_option(
'-n', '--namespaces',
help='Plot the suite namespace inheritance hierarchy '
'(task run time properties).',
action='store_true', default=False, dest='namespaces')

parser.add_option(
'-r', '--reference',
help='Output in a sorted plain text format for comparison purposes.',
action='store_true', default=True, dest='reference')

parser.add_option(
'--show-suicide',
help='Show suicide triggers. They are not shown by default, unless '
'toggled on with the tool bar button.',
action='store_true', default=False, dest='show_suicide')

parser.add_option(
'--icp', action='store', default=None, metavar='CYCLE_POINT', help=(
'Set initial cycle point. Required if not defined in suite.rc.'))

return parser


def main(opts, suite=None, start=None, stop=None):
"""Implement ``cylc graph``."""
if opts.ungrouped and opts.namespaces:
raise Exception('Cannot combine --ungrouped and --namespaces.')
if not opts.reference:
raise Exception('Only the --reference use cases are supported')

template_vars = load_template_vars(
opts.templatevars, opts.templatevars_file)

config = get_config(suite, opts, template_vars=template_vars)
if opts.namespaces:
graph_inheritance(config)
else:
graph_workflow(config, start, stop, ungrouped=opts.ungrouped,
show_suicide=opts.show_suicide)


if __name__ == '__main__':
options, args = get_option_parser().parse_args()
suite, _ = SuiteSrvFilesManager().parse_suite_arg(options, args[0])
main(options, suite)
21 changes: 18 additions & 3 deletions doc/src/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,21 @@ The following packages are highly recommended, but are technically optional as
you can construct and run suites without dependency graph visualisation or
the Cylc GUIs:

- `PyGTK <http://www.pygtk.org>`_ - GUI toolkit.
- `PyGTK <http://www.pygtk.org>`_ - Python bindings for the GTK+ GUI toolkit.

.. note::

PyGTK typically comes with your system Python. It is allegedly quite
difficult to install if you need to do so for another Python version.
PyGTK typically comes with your system Python 2 version. It is allegedly
quite difficult to install if you need to do so for another Python
version. At time of writing, for instance, there are no functional PyGTK
conda packages available.

Note that **we need to do ``import gtk`` in Python, not ``import pygtk``**.

In Centos 7.6, for example, the Cylc GUIs run "out of the box" with the
system-installed Python 2.7.5. Under the hood, the Python “gtk” package is
provided by the “pygtk2” yum package. (The “pygtk” Python module, which we
don't need, is supplied by the “pygobject2” yum package).

- `Graphviz <http://www.graphviz.org>`_ - graph layout engine (tested 2.36.0)
- `Pygraphviz <http://pygraphviz.github.io/>`_ - Python Graphviz interface
Expand All @@ -39,6 +48,12 @@ the Cylc GUIs:
- python-devel
- graphviz-devel

.. note::

The ``cylc graph`` command for static workflow visualization requires
PyGTK, but we provide a separate ``cylc ref-graph`` command to print
out a simple text-format "reference graph" without PyGTK.

The Cylc Review service does not need any additional packages.

The following packages are necessary for running all the tests in Cylc:
Expand Down
14 changes: 13 additions & 1 deletion tests/cylc-graph-diff/00-simple.t
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# Test cylc graph-diff for two suites.
. $(dirname $0)/test_header
#-------------------------------------------------------------------------------
set_test_number 24
set_test_number 27
#-------------------------------------------------------------------------------
install_suite $TEST_NAME_BASE-control $TEST_NAME_BASE-control
CONTROL_SUITE_NAME=$SUITE_NAME
Expand Down Expand Up @@ -160,6 +160,18 @@ node "foo.20140810T0000Z" "foo\n20140810T0000Z"
stop
__OUT__
cmp_ok "$TEST_NAME.stderr" </dev/null

#-------------------------------------------------------------------------------
# Check that the GTK-free "cylc ref-graph" command generates the same result as
# the original "cylc graph --reference" command.
TEST_NAME="${TEST_NAME_BASE}-ref1"
run_ok "${TEST_NAME}" cylc graph --reference "${CONTROL_SUITE_NAME}"
ref1="${TEST_NAME}.stdout"
TEST_NAME="${TEST_NAME_BASE}-ref2"
run_ok "${TEST_NAME}" cylc ref-graph "${CONTROL_SUITE_NAME}"
ref2="${TEST_NAME}.stdout"
cmp_ok "${ref1}" "${ref2}"

#-------------------------------------------------------------------------------
purge_suite $DIFF_SUITE_NAME
purge_suite $SAME_SUITE_NAME
Expand Down

0 comments on commit de7d938

Please sign in to comment.