Skip to content

Commit

Permalink
Merge 87bb420 into bbde073
Browse files Browse the repository at this point in the history
  • Loading branch information
arcivanov committed Mar 25, 2018
2 parents bbde073 + 87bb420 commit caea4d8
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 43 deletions.
3 changes: 2 additions & 1 deletion build.py
Expand Up @@ -78,7 +78,7 @@
]
url = "http://pybuilder.github.io"
license = "Apache License"
version = "0.11.14"
version = "0.11.15"

requires_python = ">=2.6,!=3.0,!=3.1,!=3.2,<3.7"

Expand All @@ -100,6 +100,7 @@ def initialize(project):
project.depends_on("pip", ">=7.1")
project.depends_on("setuptools", "~=36.0")
project.depends_on("wheel", "~=0.29.0")
project.depends_on("tailer")

project.set_property("verbose", True)

Expand Down
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
#
# This file is part of PyBuilder
#
# Copyright 2011-2015 PyBuilder Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest

from integrationtest_support import IntegrationTestSupport
from pybuilder.errors import CircularTaskDependencyException


class Test(IntegrationTestSupport):
def test(self):
self.write_build_file("""
from pybuilder.core import task, depends, dependents
@task
@depends("task_c")
def task_a(project):
project.set_property("a", False)
@task
@depends("task_a")
def task_b(project):
project.set_property("a", True)
@task
@depends("task_a", "task_b")
def task_c(project):
project.set_property("c", True)
""")
reactor = self.prepare_reactor()
self.assertRaises(CircularTaskDependencyException, reactor.build, ["task_c", "task_a", "task_b"])


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion src/main/python/pybuilder/cli.py
Expand Up @@ -416,10 +416,10 @@ def main(*args):
raise PyBuilderException("Build aborted")

except (Exception, SystemExit) as e:
successful = False
failure_message = str(e)
if options.debug:
traceback.print_exc(file=sys.stderr)
successful = False

finally:
end = datetime.datetime.now()
Expand Down
19 changes: 11 additions & 8 deletions src/main/python/pybuilder/errors.py
Expand Up @@ -21,6 +21,8 @@
Defines all possible errors that can arise during the execution of PyBuilder.
"""

from pprint import pformat


class PyBuilderException(Exception):
def __init__(self, message, *arguments):
Expand All @@ -47,14 +49,15 @@ def __init__(self, name):


class CircularTaskDependencyException(PyBuilderException):
def __init__(self, first, second=None, message=None):
if message:
super(CircularTaskDependencyException, self).__init__(message)
elif second:
super(CircularTaskDependencyException, self).__init__("Circular task dependency detected between %s and %s",
first, second)
self.first = first
self.second = second
def __init__(self, message, *args):
if isinstance(message, (list, tuple)):
cycles = message
super(CircularTaskDependencyException, self).__init__("Circular task dependencies detected:\n%s",
"\n".join("\t" + pformat(cycle) for cycle in cycles)
)
else:
super(CircularTaskDependencyException, self).__init__(message,
*args)


class MissingPrerequisiteException(PyBuilderException):
Expand Down
14 changes: 7 additions & 7 deletions src/main/python/pybuilder/execution.py
Expand Up @@ -37,7 +37,7 @@
MissingActionDependencyException,
NoSuchTaskException,
RequiredTaskExclusionException)
from pybuilder.graph_utils import Graph, GraphHasCycles
from pybuilder.graph_utils import Graph
from pybuilder.utils import as_list, Timer, odict

if sys.version_info[0] < 3: # if major is less than 3
Expand Down Expand Up @@ -391,10 +391,10 @@ def build_execution_plan(self, task_names):
dependency_edges = {}
for task in self.collect_all_transitive_tasks(as_list(task_names)):
dependency_edges[task.name] = [dependency.name for dependency in task.dependencies]
try:
Graph(dependency_edges).assert_no_cycles_present()
except GraphHasCycles as cycles:
raise CircularTaskDependencyException(str(cycles))

cycles = Graph(dependency_edges).assert_no_cycles_present()
if cycles:
raise CircularTaskDependencyException(cycles)

for task_name in as_list(task_names):
self._enqueue_task(execution_plan, task_name)
Expand All @@ -417,8 +417,8 @@ def build_shortest_execution_plan(self, task_names):

if self._current_task and self._current_task in shortest_plan:
raise CircularTaskDependencyException("Task '%s' attempted to invoke tasks %s, "
"resulting in plan %s, creating circular dependency" %
(self._current_task, task_names, shortest_plan))
"resulting in plan %s, creating circular dependency",
self._current_task, task_names, shortest_plan)
return shortest_plan

def _enqueue_task(self, execution_plan, task_name):
Expand Down
19 changes: 3 additions & 16 deletions src/main/python/pybuilder/graph_utils.py
Expand Up @@ -20,13 +20,6 @@
"""


class GraphHasCycles(Exception):
"""
To be raised when a graph has one or more cycles
"""
pass


class Graph(object):
"""
A graph using an edge dictionary as an internal representation.
Expand All @@ -45,10 +38,10 @@ def assert_no_cycles_present(self, include_trivial_cycles=True):
# contains at least one directed cycle, so len()>1 is a showstopper

if cycles:
raise self.error_with_cycles(cycles)
return cycles

if include_trivial_cycles:
self.assert_no_trivial_cycles_present()
return self.assert_no_trivial_cycles_present()

def assert_no_trivial_cycles_present(self):
trivial_cycles = []
Expand All @@ -57,13 +50,7 @@ def assert_no_trivial_cycles_present(self):
trivial_cycles.append((source, source))

if trivial_cycles:
raise self.error_with_cycles(trivial_cycles)

def error_with_cycles(self, cycles):
error_message = "Found cycle(s):\n"
for cycle in cycles:
error_message += "\tThese nodes form a cycle : " + str(cycle) + "\n"
return GraphHasCycles(error_message)
return trivial_cycles


def tarjan_scc(graph):
Expand Down
4 changes: 2 additions & 2 deletions src/main/python/pybuilder/plugins/python/distutils_plugin.py
Expand Up @@ -143,8 +143,8 @@ def set_description(project, logger):
try:
assert_can_execute(["pandoc", "--version"], "pandoc", "distutils")
doc_convert(project, logger)
except MissingPrerequisiteException:
logger.warn("Was unable to find pandoc and did not convert the documentation")
except (MissingPrerequisiteException, ImportError):
logger.warn("Was unable to find pandoc or pypandoc and did not convert the documentation")


@after("package")
Expand Down
6 changes: 5 additions & 1 deletion src/main/python/pybuilder/plugins/python/sphinx_plugin.py
Expand Up @@ -66,7 +66,11 @@ def initialize_sphinx_plugin(project):
default_project_name = project.name
default_doc_author = ", ".join([author.name for author in project.authors])

project.plugin_depends_on("sphinx")
if sys.version_info[:2] in ((2, 6), (3, 3)):
project.plugin_depends_on("sphinx", "~=1.4.0")
else:
project.plugin_depends_on("sphinx")

project.set_property_if_unset(
"sphinx_source_dir", SCAFFOLDING.DEFAULT_DOCS_DIRECTORY)
project.set_property_if_unset(
Expand Down
14 changes: 7 additions & 7 deletions src/unittest/python/graph_utils_tests.py
Expand Up @@ -17,22 +17,22 @@
# limitations under the License.

from unittest import TestCase
from pybuilder.graph_utils import Graph, GraphHasCycles
from pybuilder.graph_utils import Graph


class GraphUtilsTests(TestCase):

def test_should_find_trivial_cycle_in_graph_when_there_is_one(self):
graph_with_trivial_cycle = Graph({"a": "a"})
self.assertRaises(GraphHasCycles, graph_with_trivial_cycle.assert_no_trivial_cycles_present)
self.assertTrue(graph_with_trivial_cycle.assert_no_trivial_cycles_present() is not None)

def test_should_find_trivial_cycle_in_graph_when_there_are_two(self):
graph_with_trivial_cycles = Graph({"a": "a", "b": "b"})
self.assertRaises(GraphHasCycles, graph_with_trivial_cycles.assert_no_trivial_cycles_present)
self.assertTrue(graph_with_trivial_cycles.assert_no_trivial_cycles_present() is not None)

def test_should_find_trivial_cycle_in_graph_when_searching_for_cycles(self):
graph_with_trivial_cycle = Graph({"a": "a"})
self.assertRaises(GraphHasCycles, graph_with_trivial_cycle.assert_no_cycles_present)
self.assertTrue(graph_with_trivial_cycle.assert_no_cycles_present() is not None)

def test_should_not_find_trivial_cycles_in_graph_when_there_are_none(self):
graph_without_trivial_cycle = Graph({"a": "b", "b": "c", "d": "e"})
Expand All @@ -44,12 +44,12 @@ def test_should_not_find_cycles_in_graph_when_there_are_none(self):

def test_should_find_simple_nontrivial_cycle_in_graph_when_there_is_one(self):
graph_with_simple_cycle = Graph({"a": "b", "b": "a"})
self.assertRaises(GraphHasCycles, graph_with_simple_cycle.assert_no_cycles_present)
self.assertTrue(graph_with_simple_cycle.assert_no_cycles_present() is not None)

def test_should_find_long_nontrivial_cycle_in_graph_when_there_is_one(self):
graph_with_long_cycle = Graph({"a": "b", "b": "c", "c": "d", "d": "b"})
self.assertRaises(GraphHasCycles, graph_with_long_cycle.assert_no_cycles_present)
self.assertTrue(graph_with_long_cycle.assert_no_cycles_present() is not None)

def test_should_find_long_nontrivial_cycle_in_graph_when_there_are_two(self):
graph_with_long_cycle = Graph({"a": "b", "b": "c", "c": "a", "d": "e", "e": "f", "f": "d"})
self.assertRaises(GraphHasCycles, graph_with_long_cycle.assert_no_cycles_present)
self.assertTrue(graph_with_long_cycle.assert_no_cycles_present() is not None)

0 comments on commit caea4d8

Please sign in to comment.