Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cylc 7 ref graphs without pygtk #3349

Merged
merged 7 commits into from
Sep 5, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
240 changes: 240 additions & 0 deletions bin/cylc-ref-graph
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
#!/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 graph SUITE [START] [STOP]
hjoliver marked this conversation as resolved.
Show resolved Hide resolved

Implement the old ``cylc graph --reference command`` for producing a
text-format representation of a suite graph.

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
else:
stop_point = stop_point
hjoliver marked this conversation as resolved.
Show resolved Hide resolved
else:
stop_point = None
hjoliver marked this conversation as resolved.
Show resolved Hide resolved

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:
sadielbartholomew marked this conversation as resolved.
Show resolved Hide resolved
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