garden-rs / garden

Garden grows and cultivates collections of Git trees ~ Official mirror of https://gitlab.com/garden-rs/garden
https://garden-rs.gitlab.io
MIT License
64 stars 9 forks source link

is it possible to at start and end to a command #3

Closed mrosm20 closed 2 years ago

mrosm20 commented 2 years ago

Scenario:

Let's say you have 5 tree in a group thats part of a garden. The name of the command you want to run is call" "Build". So you make a global command. Perfect!

When you go to run that command over the garden you first need to install something. Then after all the trees a built you want to uninstall something.

Is that possible?

$ garden Build

davvid commented 2 years ago

That's a good one. There is one way to make this work but I think we can do more to make it better.

You currently have a global build command. The trick to make this work would be to define local install and uininstall commands that are scoped to just a single tree. We can then use garden cmd to run all three commands in one shot and it won't do anything in the other trees that don't define the install and uninstall commands.

If commands is configured in tree scope then only that tree will have that command. Trees that don't have the command will be a no-op and nothing will run when those trees are visited. The invocation looks like this:

garden cmd my-garden install build uninstall

The room-for-improvement is that this relies on install and uninstall being local commands that have to be attached to very specific trees (the first and last trees, respectively). You'd need to attach the install command to the very first tree in the garden, and uninstall to the very last tree in the garden/group.

The reason is that the way we loop over trees when running commands goes like this:

for each tree in the query:
    for each command:
        run command in the resolved context for this tree

so it's going to run (install, build, uninstall) over each tree: (tree1:install, tree1:build, tree1:uninstall), (tree2:install, tree2:build, tree2:uninstall).

I've also had cases where I've wanted to loop inside out so that it would instead do: (tree1:install, tree2:install, tree1: build, tree2: build, tree1: uninstall, tree2:uninstall).

This is kinda like the choice between breath-first or depth-first traversal. This does seem like it could be a command-line option to switch the loop order to be:

for each command:
    for each tree in the query:
        run command in the resolved context for this tree

I'm not sure what I would call the command-line flags, though. What do you think about this?

-d, --depth-first      run all commands over each tree before visiting the next tree
-b, --breadth-first   run a command across all trees before running the next command

I'm totally open to changing the default to --depth-first. Right now we basically do --breadth-first.

It almost seems like depth-first is a better default because usually we'd want to "build them all", and then "test them all", or "clean them all", etc.

I've actually run into this usability wrinkle myself. I think I've just about convinced myself to change the default and add these options.

What do you think?

davvid commented 2 years ago

Here's another detail that might be helpful. Since you have a global build command you can actually do without an uninstall command because you can basically extend the build command for the very last tree, and then when build is executing it is going to run the global build steps and then it will run the local build steps afterwards.

In other words, you can simplify it down to,

garden cmd my-garden install build

.. by extending the tree definition for the last tree in the garden/group to include its own build command. This local build command is considered an extension of the global command. The steps in that tree's context always run after the global command.


Note that the iteration options will only make it so that we don't have to attach install to specific trees -- at that point we can attach it to any tree (1 or more, in case different trees have different install steps) and we'll be ensured that all of the install steps will be run first.

After we add options for iteration another enhancement might be to introduce a garden-commands block that can be attached to garden definitions.

The semantics would be that we could define install (and uninstall) in a garden-command and it would only get executed once in the context of that garden, whereas the build command would get executed once for each tree.

The invocation would still look the same: garden cmd my-garden install build uninstall but we would instead define install and uninstall alongside the garden rather than attaching it to an arbitrary tree.


NOTE: the names "install" and "uninstall" are probably not the best choice of names because they may be confused for traditional "make install / uninstall" commands which normally happen after "make all" (build) steps.

If it helps, when reading the above replace install with setup and uninstall with teardown.

garden cmd my-garden setup build teardown might make more sense to someone reading along.

davvid commented 2 years ago

Another note in case it helps.. it should hopefully be obvious, but running:

garden setup my-garden
garden build my-garden
garden teardown my-garden   # or extend the `build` command for the last tree as noted above

Would also work today.

This issue will remain open until we implement the traversal options.

At that point we'll also submit a new issue to add the garden-level pre and post-commands. "pre" commands run before tree-level commands, and "post" commands run after tree-level commands. Please let me know what you think.

mrosm20 commented 2 years ago

thank you, i got it to work like this:

make a separate tree that not part of the garden group name it the same as the garden create the command with the same name then recursively call the garden, not pretty but it works

the command would be:

garden build @

command: build:

I testing this out with a group of developers, because I like the design and we have a use-case of 10 repos that need to be part of a garden, but i don't want to make the commands themselves to complex. The garden yaml is where i would prefer all the complexities. i like the idea of teardown and setup. Maybe something like this:

setup build:

davvid commented 2 years ago

I've implemented a -b / --breadth-first option to garden cmd that runs commands in breadth-first order.

The default traversal is depth-first.

Let's say we have two trees, tree1 and tree2 in a garden called my-garden. These trees provide custom setup, build and teardown commands.

Here is what happens with the default depth-first traversal -- all of the commands are run before visiting the next tree.

garden cmd my-garden setup build teardown

The commands are run in this order:

tree1:setup tree1:build tree1:teardown
tree2:setup tree2:build tree2:teardown

The new feature is that we can use the -b option to enable the --breadth-first traversal mode.

garden cmd -b my-garden setup build teardown

When in this mode the commands are run in this order:

tree1:setup tree2:setup
tree1:build tree2:build
tree1:teardown tree2:teardown

As before, if a tree doesn't define setup or teardown commands then they are skipped and are effectively no-ops.

The traversal modes seems like they should be sufficient for this use case so I've closed this issue as complete.

I haven't added separate commands_pre or commands_post features at this time since it seems like we won't strictly need them (especially alongside the breadth-first traversal mode) but I'm open to reconsidering and reopening this issue if it would simplify workflows.

Thanks for the sug, and let me know what you think.

davvid commented 8 months ago

In addition to the breadth-first opiton, I'm adding one last feature which will make this very convenient.

I'm adding a concept of "pre-commands" and "post-commands".

These are specified as the names of other commands to run before or after a named command.

For example:

commands:
  custom-cmd: echo custom-cmd
  custom-cmd<: before-cmd
  custom-cmd>:
    - after-cmd1
    - after-cmd2
  before-cmd: echo before
  after-cmd1: echo after1
  after-cmd2: echo after2

Running garden custom-cmd will product the following output:

before
custom-cmd
after1
after2

The custom-cmd: echo custom-cmd part is optional -- specifying just the custom-cmd< pre-commands and/or custom-cmd> post-commands is sufficient.

The values configured for cmd< and cmd> must be references to other named commands -- they are not commands themselves.

This is analogous to Makefile targets where one target can depend on another target.