diff --git a/README.md b/README.md index f22f79b0a..9a756d921 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ parameters: codeception: ~ composer: ~ composer_script: ~ + deptrac: ~ doctrine_orm: ~ gherkin: ~ git_blacklist: ~ diff --git a/doc/tasks.md b/doc/tasks.md index 47e35881f..1ce7a0696 100644 --- a/doc/tasks.md +++ b/doc/tasks.md @@ -14,6 +14,7 @@ parameters: clover_coverage: ~ codeception: ~ composer: ~ + deptrac: ~ composer_script: ~ doctrine_orm: ~ gherkin: ~ @@ -61,6 +62,7 @@ Every task has it's own default configuration. It is possible to overwrite the p - [Composer](tasks/composer.md) - [Composer Script](tasks/composer_script.md) - [Doctrine ORM](tasks/doctrine_orm.md) +- [Deptrac](tasks/deptrac.md) - [Gherkin](tasks/gherkin.md) - [Git blacklist](tasks/git_blacklist.md) - [Git branch name](tasks/git_branch_name.md) diff --git a/doc/tasks/deptrac.md b/doc/tasks/deptrac.md new file mode 100644 index 000000000..7c8764ea4 --- /dev/null +++ b/doc/tasks/deptrac.md @@ -0,0 +1,57 @@ +# Deptrac + +Follow the [installation instructions](https://github.com/sensiolabs-de/deptrac#installation) to add deptrac to your +project. + +The Deptrac task will check for dependencies between the software layers of your project. It lives under the `deptrac` +namespace and has following configurable parameters: + + +```yaml +# grumphp.yml +parameters: + tasks: + deptrac: + depfile: ~ + formatter_graphviz: ~ + formatter_graphviz_display: ~ + formatter_graphviz_dump_image: ~ + formatter_graphviz_dump_dot: ~ + formatter_graphviz_dump_html: ~ +``` + +**depfile** + +*Default: null* + +Set path to deptrac configuration file. Example: `/var/www/src/depfile.yml` + +**formatter_graphviz** + +*Default: false* + +Set to `true` to enable the graphviz formatter. + +**formatter_graphviz_display** + +*Default: false* + +Open the generated graphviz image. Set to `true` to activate. + +**formatter_graphviz_dump_image** + +*Default: null* + +Set path to a dumped png file. + +**formatter_graphviz_dump_dot** + +*Default: null* + +Set path to a dumped dot file. + +**formatter_graphviz_dump_html** + +*Default: null* + +Set path to a dumped html file. diff --git a/resources/config/tasks.yml b/resources/config/tasks.yml index b6982ed5d..48355c4ef 100644 --- a/resources/config/tasks.yml +++ b/resources/config/tasks.yml @@ -71,6 +71,15 @@ services: tags: - {name: grumphp.task, config: composer_script} + task.deptrac: + class: GrumPHP\Task\Deptrac + arguments: + - '@config' + - '@process_builder' + - '@formatter.raw_process' + tags: + - {name: grumphp.task, config: deptrac} + task.doctrine_orm: class: GrumPHP\Task\DoctrineOrm arguments: diff --git a/spec/Task/DeptracSpec.php b/spec/Task/DeptracSpec.php new file mode 100644 index 000000000..e9a3fa093 --- /dev/null +++ b/spec/Task/DeptracSpec.php @@ -0,0 +1,110 @@ +getTaskConfiguration('deptrac')->willReturn([]); + $this->beConstructedWith($grumPHP, $processBuilder, $formatter); + } + + function it_is_initializable() + { + $this->shouldHaveType(Deptrac::class); + } + + function it_should_have_a_name() + { + $this->getName()->shouldBe('deptrac'); + } + + function it_should_have_configurable_options() + { + $options = $this->getConfigurableOptions(); + $options->shouldBeAnInstanceOf(OptionsResolver::class); + $options->getDefinedOptions()->shouldContain('depfile'); + $options->getDefinedOptions()->shouldContain('formatter_graphviz'); + $options->getDefinedOptions()->shouldContain('formatter_graphviz_display'); + $options->getDefinedOptions()->shouldContain('formatter_graphviz_dump_image'); + $options->getDefinedOptions()->shouldContain('formatter_graphviz_dump_dot'); + $options->getDefinedOptions()->shouldContain('formatter_graphviz_dump_html'); + } + + function it_should_run_in_git_pre_commit_context(GitPreCommitContext $context) + { + $this->canRunInContext($context)->shouldReturn(true); + } + + function it_should_run_in_run_context(RunContext $context) + { + $this->canRunInContext($context)->shouldReturn(true); + } + + function it_does_not_do_anything_if_there_are_no_files(ProcessBuilder $processBuilder, ContextInterface $context) + { + $processBuilder->createArgumentsForCommand('deptrac')->shouldNotBeCalled(); + $processBuilder->buildProcess()->shouldNotBeCalled(); + $context->getFiles()->willReturn(new FilesCollection()); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->getResultCode()->shouldBe(TaskResult::SKIPPED); + } + + function it_runs_the_suite(ProcessBuilder $processBuilder, Process $process, ContextInterface $context) + { + $arguments = new ProcessArgumentsCollection(); + $processBuilder->createArgumentsForCommand('deptrac')->willReturn($arguments); + $processBuilder->buildProcess($arguments)->willReturn($process); + + $process->run()->shouldBeCalled(); + $process->isSuccessful()->willReturn(true); + + $context->getFiles()->willReturn(new FilesCollection([ + new SplFileInfo('test1.php', '.', 'test2.php') + ])); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(true); + } + + function it_throws_an_exception_if_the_process_fails( + ProcessBuilder $processBuilder, + Process $process, + ContextInterface $context + ) { + $arguments = new ProcessArgumentsCollection(); + $processBuilder->createArgumentsForCommand('deptrac')->willReturn($arguments); + $processBuilder->buildProcess($arguments)->willReturn($process); + + $process->run()->shouldBeCalled(); + $process->isSuccessful()->willReturn(false); + + $context->getFiles()->willReturn(new FilesCollection([ + new SplFileInfo('test1.php', '.', 'test2.php') + ])); + + $result = $this->run($context); + $result->shouldBeAnInstanceOf(TaskResultInterface::class); + $result->isPassed()->shouldBe(false); + } +} diff --git a/src/Task/Deptrac.php b/src/Task/Deptrac.php new file mode 100644 index 000000000..3f21a4b79 --- /dev/null +++ b/src/Task/Deptrac.php @@ -0,0 +1,94 @@ +setDefaults([ + 'depfile' => null, + 'formatter_graphviz' => false, + 'formatter_graphviz_display' => false, + 'formatter_graphviz_dump_image' => null, + 'formatter_graphviz_dump_dot' => null, + 'formatter_graphviz_dump_html' => null, + ]); + + $resolver->addAllowedTypes('depfile', ['null', 'string']); + $resolver->addAllowedTypes('formatter_graphviz', ['bool']); + $resolver->addAllowedTypes('formatter_graphviz_display', ['bool']); + $resolver->addAllowedTypes('formatter_graphviz_dump_image', ['null', 'string']); + $resolver->addAllowedTypes('formatter_graphviz_dump_dot', ['null', 'string']); + $resolver->addAllowedTypes('formatter_graphviz_dump_html', ['null', 'string']); + + return $resolver; + } + + /** + * This methods specifies if a task can run in a specific context. + * + * @param ContextInterface $context + * + * @return bool + */ + public function canRunInContext(ContextInterface $context) + { + return ($context instanceof GitPreCommitContext || $context instanceof RunContext); + } + + /** + * @param ContextInterface $context + * + * @return TaskResultInterface + */ + public function run(ContextInterface $context) + { + $config = $this->getConfiguration(); + + $files = $context->getFiles()->name('*.php'); + if (0 === count($files)) { + return TaskResult::createSkipped($this, $context); + } + + $arguments = $this->processBuilder->createArgumentsForCommand('deptrac'); + $arguments->add('analyze'); + $arguments->add('--formatter-graphviz=' . (int)$config['formatter_graphviz']); + $arguments->addOptionalArgument('--formatter-graphviz-display=%s', $config['formatter_graphviz_display']); + $arguments->addOptionalArgument('--formatter-graphviz-dump-image=%s', $config['formatter_graphviz_dump_image']); + $arguments->addOptionalArgument('--formatter-graphviz-dump-dot=%s', $config['formatter_graphviz_dump_dot']); + $arguments->addOptionalArgument('--formatter-graphviz-dump-html=%s', $config['formatter_graphviz_dump_html']); + $arguments->addOptionalArgument('%s', $config['depfile']); + + $process = $this->processBuilder->buildProcess($arguments); + $process->run(); + + if (!$process->isSuccessful()) { + return TaskResult::createFailed($this, $context, $this->formatter->format($process)); + } + + return TaskResult::createPassed($this, $context); + } +}