Idea: Introduce/standardize project development scripts

Agreed. Even with that caveat, though, I think there’s potential merit in a spec that allows standardising the following:

  • A way for generic multi-project development tools (such as IDEs) to determine what the expected orchestrator for a project is (either the orchestrator itself if it is written in Python, or an interface adaptor for non-Python orchestrators like make, meson, CMake, etc), as well as any additional supporting libraries that may be needed (such as tox, pdm, and tox-pdm for a project that uses both tox and pdm)
  • A standard way to query the specified task runner to get a list of defined commands and the Python entry point object references to invoke them
  • A standard way to invoke a defined task based on the information returned from the command query

The bare bones version of that would consist of just a [task-runner] table, intentionally modelled on the way [build-system] works (since we’ve had plenty of positive experience with that approach):

[task-runner]
requires = ["pdm", "tox", "tox-pdm"]
task-backend = "tox.task_runner" # Hypothetical submodule name!

The initial version of the spec could include a single query API:

from os import PathLike
from typing import Mapping

def get_tasks(project_dir: PathLike[str]) -> Mapping[str, TaskSpec]:
    """Return task names and specifications for given project directory"""
    ...

# The `project_dir` is referenced rather than specifically `pyproject.toml`
# because many orchestrators don't use that for task configuration.

# Returns a mapping to make it clear that duplicate task names are
# not permitted
from typing import Any, Callable, TypedDict

class TaskSpec(TypedDict):
    name: str                  # The short name of the task
    description: str           # An explanation of what the task does
    target_ref: str            # Entry point object ref for the task API
    config: Mapping[str, Any]  # Runner-defined task config details
    project_dir: PathLike[str] # Location of project defining the task

# Expected signature for task API targets
# Return value is expected to be usable as a process exit code
type TaskTarget = Callable[[TaskSpec], int]

# `target_ref` is defined for each task to allow task runners freedom
# to choose between returning different target refs for each task or
# a single standard target ref that looks up the task based on its name.
# Either way, the call signature is just to pass the task spec back in.

# `project_dir` is included to allow the `config` to include values that are
# only valid for that project directory. Even `target_ref` may be project
# specific if it depends on how the command is defined in that project.

This keeps us out of the business of trying to define the UX of the individual orchestrators, and purely in the space we want standardisation to occupy: facilitating communication between multiple frontends (in this case, mostly IDEs and downstream system integrator build systems), and multiple backends (in this case, task orchestration and project management tools)