-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
π₯π€― Great refactoring, many tests, IO task rework, etc.
Affected tickets: * Resolves #7: current task is now passed into the block (for both normal and IO tasks), with tests. * Resolves #12: using `run` in sink context now explodes properly (tested for both normal and IO tasks). * Resolves #13: no more shell injection through filenames, tested with extra tests for filenames starting with `--`. * Resolves #16: there are now helper functions to test sakefiles without the need to `.execute` tasks from within the test file. It works by creating a temp directory with a given Sakefile and running `sake` as external command (stdout, stderr, exit code and signals are checked). * Resolves #18: you can now pass an IO object instead of a Str and it will automatically dispatch to the right sub. * There is some groundwork for #17, and maybe it already works. I can't tell if it does because there are no tests for it yet. * Also some minimal groundwork for #9 (parallel execution). * Additionally, issue #14 should be more approachable now (because deps are now resolved). * Issue #15 (βdefaultβ task) was reworked a bit (with no notable functional changes). This commit splits Sake.pm6 into separate files for convenience. The hierarchy is perhaps not entirely right, but now it is much easier to refactor it further with all the tests. Because almost all lines were touched, this commit introduces some major code style changes (to my personal preference, e.g. unicode quotes). I'm not insisting on that style, so if anybody cares enough it should be possible to submit a PR with unicode stuff autoreplaced. I wish this commit was split into multiple digestable commits, but it was a single refactoring effort that I ended up shelving for a few months anyway, so I'm happy that it goes in at all.
- Loading branch information
1 parent
4972a25
commit 8d54732
Showing
22 changed files
with
711 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,73 +1,53 @@ | ||
unit module Sake; | ||
|
||
our %TASKS; | ||
|
||
class Sake-Task { | ||
has $.name = !!! 'name required'; | ||
has @.deps; # dependencies | ||
has &.body = !!! 'body required'; # code to execute for this task | ||
has &.cond = { True }; # only execute when True | ||
|
||
method execute { | ||
return unless self.cond.(); | ||
for self.deps -> $d { execute($d); } | ||
.(self) with self.body; | ||
} | ||
|
||
} | ||
|
||
sub execute($task) is export { | ||
if %TASKS{$task}:exists { | ||
sink %TASKS{$task}.execute; | ||
} else { | ||
# TODO something more awesome here | ||
$*ERR.say("No task named $task...skipping"); | ||
} | ||
use Sake::Task; | ||
use Sake::TaskStore; | ||
use Sake::Task::IO; | ||
|
||
sub EXPORT { | ||
%( | ||
Sake::Task::EXPORT::DEFAULT::, | ||
Sake::Task::IO::EXPORT::DEFAULT::, | ||
) | ||
} | ||
|
||
proto sub task(|) is export { * } | ||
|
||
my sub make-task($name, &body, :@deps=[], :&cond={True}) { | ||
die "Duplicate task $name!" if %TASKS{~$name}; | ||
%TASKS{~$name} = Sake-Task.new(:$name, :&body, :@deps, :&cond); | ||
} | ||
unit module Sake; | ||
|
||
multi sub task(Str $name, &body) { | ||
make-task($name, &body); | ||
multi execute(Str $task) { | ||
if %TASKS{$task}:!exists { | ||
note βTask β$taskβ does not existβ; | ||
note did-you-mean; | ||
exit 2 | ||
} | ||
execute %TASKS{$task} | ||
} | ||
|
||
multi sub task(Pair $name-deps, &body?) { | ||
my ($name,$deps) := $name-deps.kv; # unpack name and dependencies | ||
my @deps = $deps.list; # so that A => B and A => <B C D> both work | ||
return make-task($name, &body, :@deps); | ||
multi execute(Sake::Task $task) { | ||
my $result = $task.execute; | ||
$result ~~ Promise | ||
?? await $result | ||
!! $result | ||
} | ||
|
||
|
||
proto sub file(|) is export { * } | ||
|
||
my sub touch (Str $filename) { | ||
run βtouchβ, β--β, $filename; | ||
multi execute(*@tasks) is export { | ||
my @non-existent = @tasks.grep: { %TASKS{$_}:!exists }; | ||
if @non-existent { | ||
note βTask β$_β does not existβ for @non-existent; | ||
note did-you-mean; | ||
exit 2 | ||
} | ||
(execute $_ for @tasks) | ||
} | ||
|
||
multi sub file(Str $name, &body) { | ||
return make-task( | ||
$name, | ||
{ &body($_); touch $name }, | ||
:cond(sub { $name.path !~~ :e; }) | ||
) | ||
} | ||
sub sake-precheck(:$force = False) is export { | ||
my @errors = gather resolve-deps; | ||
if @errors { | ||
.note for @errors; | ||
|
||
multi sub file(Pair $name-deps, &body) { | ||
my ($name,$deps) := $name-deps.kv; # unpack name and dependencies | ||
my @deps = $deps.list; # so that A => B and A => <B C D> both work | ||
my $cond = { | ||
my $f = $name.path; | ||
!($f ~~ :e) || $f.modified < all(map { $_.modified }, grep { $_ ~~ IO::Path }, @deps); | ||
}; | ||
return make-task( | ||
$name, | ||
{ &body($_); touch $name }, | ||
:@deps, | ||
:cond($cond) | ||
) | ||
if $force { | ||
note ββ; | ||
note βContinuing with errors because of the --force flagβ; | ||
} else { | ||
note βExiting. Use --force flag if you want to ignore these errorsβ; | ||
exit 1 | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
use Sake::TaskStore; | ||
|
||
unit class Sake::Task; | ||
|
||
has $.name = !!! βname requiredβ; | ||
has @.deps; #= Task dependencies | ||
has &.body = !!! βbody requiredβ; #= Code to execute for this task | ||
has &.cond = { True }; #= Condition for task execution | ||
has $.modification-time = -β; | ||
has $.ready = Promise.new; | ||
has $.callframe; | ||
|
||
#| Executes the task, even if it was already executed. | ||
method execute { | ||
if $.ready.status !~~ Planned { | ||
note βWarning: re-executing task β$.nameβ per your requestβ | ||
} | ||
resolve-deps self, :live; # in case the task was added later | ||
await @.depsΒ».readify; | ||
|
||
if $.cond() { | ||
with self.body { | ||
sink .signature.ACCEPTS(\(self)) | ||
?? .(self) | ||
!! .(); | ||
} | ||
$!modification-time = now; | ||
} | ||
|
||
$.ready.keep: $.modification-time unless $.ready.status ~~ Kept; | ||
} | ||
|
||
method readify { | ||
# TODO race conditions | ||
self.execute unless $.ready.status ~~ Kept; | ||
$.ready | ||
} | ||
|
||
|
||
multi resolve-deps($task, :$live = False) is export { | ||
$task.deps .= map: { | ||
do if $_ ~~ Sake::Task { | ||
$_ # already resolved | ||
} elsif %TASKS{$_}:exists { | ||
%TASKS{$_} | ||
} else { | ||
my $msg = βTask $task.name() depends on $_ but no such task was foundβ; | ||
$live ?? note $msg !! take $msg; | ||
Empty | ||
} | ||
} | ||
} | ||
|
||
multi resolve-deps is export { | ||
my @errors = gather { resolve-deps $_ for %TASKS.values } | ||
if @errors > 0 { # TODO what if it's another error | ||
take $_ for @errors; | ||
take did-you-mean | ||
} | ||
} | ||
|
||
|
||
|
||
proto sub task(|) is export {*} | ||
|
||
multi sub task(Str $name, &body?) { | ||
make-task $name, &body, type => Sake::Task | ||
} | ||
|
||
multi sub task(Pair (Str :key($name), :value($deps)), &body?) { | ||
make-task $name, &body, type => Sake::Task, deps => $deps.list | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use Sake::Task; | ||
use Sake::TaskStore; | ||
|
||
unit class Sake::Task::IO is Sake::Task; | ||
|
||
method modification-time { | ||
$.name.IO.e | ||
?? $.name.IO.modified | ||
!! -β | ||
} | ||
|
||
method execute { | ||
resolve-deps self, :live; # in case the task was added later | ||
await @.depsΒ».readify; | ||
|
||
if $.cond() { | ||
with self.body { | ||
my $last-dep = @.depsΒ».modification-time.max; | ||
$last-dep = now if $last-dep == -β; | ||
sink .(self) if $.modification-time < $last-dep; | ||
} | ||
|
||
my &touch = -> $filename { run <touch -->, $filename }; | ||
touch $.name.IO; | ||
} | ||
$.ready.keep: $.modification-time unless $.ready; | ||
} | ||
|
||
|
||
|
||
multi sub task(IO $path, &body?) is export { | ||
make-task $path, &body, type => Sake::Task::IO | ||
} | ||
|
||
multi sub task(Pair (IO :key($path), :value($deps)), &body?) is export { | ||
make-task $path, &body, deps => $deps.list, type => Sake::Task::IO | ||
} | ||
|
||
|
||
proto sub file(|) is export {*} | ||
|
||
multi sub file(IO() $path, &body?) { | ||
task $path, &body | ||
} | ||
|
||
multi sub file(Pair (IO() :key($path), :value($deps)), &body?) { | ||
task $path => $deps, &body | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
unit module Sake::TaskStore; | ||
|
||
our %TASKS is export; | ||
|
||
sub make-task($name, &body, | ||
:@deps=[], :&cond={True}, :$type) is export { | ||
if %TASKS{$name}:exists { | ||
note βDuplicate task $nameβ; | ||
exit 2 | ||
} | ||
%TASKS{~$name} = $type.new: :$name, :&body, :@deps, | ||
:&cond, callframe => callframe | ||
} | ||
|
||
sub did-you-mean is export { | ||
return β\nNo tasks were definedβ if %TASKS == 0; | ||
β\nDid you mean one of these?\nβ | ||
~ %TASKS.keys.sortΒ».indent(4).join: β\nβ | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use v6; | ||
use lib <lib/ t/lib>; | ||
use Sake; | ||
use Test; | ||
|
||
plan 5; | ||
|
||
my $x = ββ; | ||
|
||
my $t = task βfredβ, { $x = βmethβ } | ||
$t.execute; | ||
is $x, βmethβ, βcan execute task via methodβ; | ||
|
||
$x = ββ; | ||
execute βfredβ; | ||
is $x, βmethβ, βcan execute task by nameβ; | ||
|
||
(task βdinoβ => βfredβ, { $x = βsdβ }).execute; | ||
is $x, βsdβ, βsingle dependency works fineβ; | ||
|
||
(task βbedrockβ => <fred dino>, { $x = βmdβ }).execute; | ||
is $x, βmdβ, βmultiple dependencies work fineβ; | ||
|
||
task βclear-headed-fredβ, { $x = βagainβ } | ||
(task βwilmaβ => <clear-headed-fred>).execute; | ||
is $x, βagainβ, βbody is optionalβ; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
use v6; | ||
use lib <lib/ t/lib>; | ||
use Sake; | ||
use Test; | ||
|
||
plan 11; | ||
|
||
my $x = ββ; | ||
|
||
my $t = file βfredβ, { $x = βmethβ } | ||
$t.execute; | ||
is $x, βmethβ, βcan execute file task via methodβ; | ||
ok βfredβ.IO.e, βfile existsβ; | ||
|
||
$x = ββ; | ||
βfredβ.IO.unlink; | ||
|
||
execute βfredβ; | ||
is $x, βmethβ, βcan execute file task by nameβ; | ||
ok βfredβ.IO.e, βfile existsβ; | ||
|
||
(file βdinoβ => βfredβ, { $x = βsdβ }).execute; | ||
is $x, βsdβ, βsingle file dependency works fineβ; | ||
ok βdinoβ.IO.e, βfile existsβ; | ||
|
||
(file βbedrockβ => <fred dino>, { $x = βmdβ }).execute; | ||
is $x, βmdβ, βmultiple file dependencies work fineβ; | ||
ok βbedrockβ.IO.e, βfile existsβ; | ||
|
||
file βclear-headed-fredβ, { $x = βagainβ } | ||
(file βwilmaβ => <clear-headed-fred>).execute; | ||
is $x, βagainβ, βbody is optionalβ; | ||
ok βclear-headed-fredβ.IO.e, βfile existsβ; | ||
ok βwilmaβ.IO.e, βfile existsβ; |
Oops, something went wrong.