ds 1.3.0
Run commands without activating virtual environments; support for Makefile and more.
ds 1.3.0
is available. This release represents a shift from only supporting the overlap of all file formats to specific parsers for each supported format.
To install ds
:
python -m pip install ds-run
# or, if you use uv:
uv tool install ds-run
# or, install the standalone version:
wget -O ~/.local/bin/ds -N https://github.com/metaist/ds/releases/latest/download/ds
chmod +x ~/.local/bin/ds
Combined Release Notes 1.1.0, 1.2.0, 1.3.0 - 2024-08-29T13:08:58Z #
Unlike the official Changelog, these notes are organized by feature.
Documentation #
- #53: changed
README.md
to have a quicker start section
I got some feedback from Shalev NessAiver that many HackerNews comments often complain that it takes too long to see real examples, so I tried to illustrate a bunch of salient examples up top.
- #69: added documentation for why branch coverage is disabled
I often strive to get 100% branch coverage in unit tests, so if I disable branch coverage, I want to remember why (e.g., hard to model catching CTRL+C
).
Logging #
- #76: fixed logging in normal and debug modes
- #79: added more helpful debug messages (e.g., how to enable / disable options)
There are three kinds of logging I use:
- Tracing execution flow: Where did execution reach?
- Reporting changes of state: What is the state of the system?
- Providing information content: What useful things should you know?
One thing I think is interesting is providing some guidance (usually behind a --debug
flag) is providing information about how to enable or disable particular behavior. This is particularly helpful when you wonder about why the program is behaving a certain way.
Install #
- #14: added instructions for using
uv
inCONTRIBUTING.md
- #30: added
uv
,uvx
, andpipx
instructions toREADME.md
uv
is emerging as the dominant alternative to pip
and all the other python project management tools.
Shalev NessAiver helped me figure out the two things I needed to build a truly cross-platform (Linux, macOS, Windows) binary executable (using Cosmopolitan), which is actually just a very carefully crafted zip file:
- The special zip commands to add files to the packaged python.
- How to add arguments so that
ds
runs instead of the python REPL.
With this release ds
can be installed in three ways: via pip
, as a uv tool
, and as a standalone binary.
Read more about packaging python projects with Cosmopolitan.
Task Description #
- #58: added
help
task option to display description when using--list
- #61: improved command wrapping for
--list
help
is inspired by pdm
; adding the command wrapping made it somewhat nicer to look at the output of ds
as it's running. I wrote a related post on figuring out how to do wrap bash commands.
Task Environment #
- #51: added
--env
and--env-file
command-line option together withenv
andenv-file
/env_file
task options for passing environment variables to tasks - #83: moved
env_file
loading later (during run) instead of earlier (during parsing) - #84: changed passing
env
values tostr
Moving env_file
loading later lets you generate that file in some task and letting another task read that file.
- #60: added
cwd
/working_dir
task option for where tasks should run
The purpose of this feature is to let you use a config file in one directory and executes the tasks in another directory (i.e. not the directory containing the config file).
Task Formats #
Joe Hostyk convinced me to support a minimal subset of the Makefile
format. Many people use make
as a way to alias long commands with a short name. I implemented the aliasing and composites and a few other features when they were relatively easy to implement.
- #71: fixed allowing
composite
(prerequisites) andshell
(recipe) within a single task
While I was implementing the Makefile
format, I realized that while I allow both composite
and cmd/shell
properties on tasks when running the task, I didn't allow them in the parser. So I fixed that to more clearly communicate the intent that composite
represents perquisites that run before cmd/shell
.
- #59: added support for
pdm
-style{args}
during argument interpolation
This style turned out to be helpful for formats like Makefile
which use $@
to mean something else.
- #66: added support for
uv
workspaces
Inspired by Cargo
and easy to support.
- #82: added support for
poetry
poetry
only really supports a single version of the python call
format. Previously, I was was against supporting call
-type tasks because they are language-specific and I had a universal parser for all the file formats. However, once I implemented #77 and every file format got its own parser, I could reintroduce the concept of language-specific features, and so I added poetry
back in.
Task Runner #
- #24: added
--pre
and--post
options to run pre-/post- tasks
Several of the formats support some kind of lifecycle event. I was reluctant to add support for these because they obscure the flow of execution. However, I ended up deciding that I could have people explicitly opt-in to running additional tasks with command-line options. See my separate post on how I changed my mind about lifecycle events.
- #55: added
--dry-run
command-line option to show which tasks would be run
Unlike --list
, --dry-run
will show you the actual command that is about to run (will full argument interpolation, etc.).
- #57: added support for glob-like task selector from the command line and in composite tasks
I really didn't like pnpm
's solution to use a regex to specify which tasks to run and using globs has felt very natural.
- #64: changed allowing shell commands directly when calling ds, e.g.,
ds 'echo hello'
Originally, I thought this was a weird thing to allow and had a special flag to disable it. But now with #73 and #74, I routinely just want to execute a command in the context of the project (e.g., ds 'echo $PATH'
).
- #65:
ds
now respects the value of theSHELL
environment variable when running tasks - #65: tried to detect current
SHELL
on Windows
Detecting the SHELL
turned out to be surprisingly easy for POSIX machines and hilariously difficult on Windows. I've kept the Windows code as "EXPERIMENTAL" because I don't have a good way of testing it.
- #73: added search for nearby
node_modules/.bin
- #74, #78: added search for nearby
venv
ifVIRTUAL_ENV
is not set - #75: added
--no-project
option to suppress searching project dependencies - #87: moved project detection (
venv
,node_modules/.bin
) earlier (right before top-level task run) instead of later (right before command run)
Shalev NessAiver provided some feedback to the 1.2.0 release suggesting that the project-specific folders should be on the PATH
without having to activate or otherwise specify them. Currently only project folders for node
and python
are supported, but this could easily extend to php
. One question that is still somewhat open is under which conditions the searches should take place. I'd also like a way to move the logic of the search into the respective file formats, but I don't have a clean way to do that yet.
- #77: refactored parsers, runner; each file format now has its own parser
This was the most significant bit of work for this release that barely added any new features. The main goal was to try and parse each file format as strictly as possible while supporting the general goal. A few features that got added were call
for pdm
/rye
/composer
and argument sharing from pdm
. I wrote a post about how I changed my mind about call
-style tasks.
- #75: added
--no-config
option to suppress searching for config files - #77: removed
.ds.toml
as a supported file format name - #80: changed config file search order
During work on #77, I cleaned up the search order and removed the never-used .ds.toml
file.
- #81: renamed environment variable
_DS_CURRENT_FILE
toDS_INTERNAL__FILE
While reading bits of the uv
codebase, I saw this naming convention and decided to switch to it.
Testing
- #67: added
uv run
command that runs tests against all supported Python versions
This became the topic of my most-liked tweet. The point isn't to run this all the time, but to simulate what GitHub Actions does when running all my tests on every supported version of python.
I know you're supposed to use `nox` to run your unit tests on multiple versions of python, but I just tried using `uv run` and it works! (2 min for 5 versions of python) Now the bottleneck is pytest (and mypy/pyright, but ruff will eventually do this, right @charliermarsh?) pic.twitter.com/koFzxoyLQp
— Metaist (@TheMetaist) August 21, 2024
- #70: simplified
CI.yaml
Just some clean up.