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.mdto 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
uvinCONTRIBUTING.md - #30: added
uv,uvx, andpipxinstructions 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
dsruns 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
helptask 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
--envand--env-filecommand-line option together withenvandenv-file/env_filetask options for passing environment variables to tasks - #83: moved
env_fileloading later (during run) instead of earlier (during parsing) - #84: changed passing
envvalues 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_dirtask 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
uvworkspaces
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
--preand--postoptions 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-runcommand-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:
dsnow respects the value of theSHELLenvironment variable when running tasks - #65: tried to detect current
SHELLon 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
venvifVIRTUAL_ENVis not set - #75: added
--no-projectoption 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-configoption to suppress searching for config files - #77: removed
.ds.tomlas 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_FILEtoDS_INTERNAL__FILE
While reading bits of the uv codebase, I saw this naming convention and decided to switch to it.
Testing
- #67: added
uv runcommand 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.