pyinvoke / invoke

Pythonic task management & command execution.
http://pyinvoke.org
BSD 2-Clause "Simplified" License
4.42k stars 370 forks source link

Run tasks relative to `tasks.py` #972

Open ShaiAvr opened 1 year ago

ShaiAvr commented 1 year ago

Is there a way to have a consistent initial working directory when running tasks? In my tasks.py file, I have a task to serve sphinx documentation with sphinx-autobuild:

from invoke import task, Context

@task
def serve_docs(ctx: Context):
    ctx.run("sphinx-autobuild ./docs/source/ ./docs/build/html --watch ./src/")

If I run this task from the project root (where tasks.py is) using invoke serve-docs, then it works fine. However, if I change directory into docs or src or any other directory in my project and run the same command invoke serve-docs, it fails since this command only works from the project root.

I expected that tasks always run with the project root as the initial working directory, which can be changed in the tasks with ctx.cd(), but it seems the initial working directory is the current working directory in the command line. I find that more confusing than helpful, especially since invoke --list still works and displays all the available tasks even if I am not in the project root near the tasks.py file. Since invoke --list works from any subdirectory of the project and I can also run tasks from any subdirectory, I would expect that the tasks will be ran with the same working directory so I get consistent results regardless of where I run the task from.

Is there a configuration option to make tasks run relative to tasks.py regardless of where I execute invoke from. If not, is there a way to make the task itself aware of the location tasks.py so I can execute commands relative to it?

pyhedgehog commented 1 year ago

ctx.config._project_prefix

achekery commented 8 months ago

You could use a workaround like this in your task:

from pathlib import Path
PROJECT_HOME = Path(__file__).parent
@task
def demo(ctx):
    with ctx.cd(PROJECT_HOME.as_posix()):
        ctx.run("echo Now we are in the right spot")

To implement this more elegantly, my first thought would be to subclass Task with extra functionality you want based on if the task config keys are in the loaded config tree at runtime.

D3f0 commented 1 week ago

If it happens to be a git repo, I usually do something like

@lru_cache
def get_git_root_directory() -> str:
    """
    Gets the top level directory of the git repository
    """
    try:
        return (
            check_output(split("git rev-parse --show-toplevel")).decode("utf-8").strip()
        )
    except SubprocessError:
        sys.exit(
            "This command should be run in a git repository. Create one with git init"
        )

and then you ctx.cd(get_git_root_directory())