Description
Describe the problem you are trying to solve
Base Problem Statement:
Some executables depend on the presence of other executables at "installed runtime", so that they can execute these "executable dependencies" as separate subprocesses. That dependency is an API that needs versioning for effective deployment/installation.
As a contrived example, imagine a commandline rust refactoring tool requires being able to execute cargo-config
.
Variant 1:
In addition to the Base Problem Statement, the user must be able to execute the dependency executable directly.
Example: Whenever a user installs cargo install cargo-super-audit-helper
they are guaranteed to be able to run cargo-audit
after that, since cargo-super-audit-helper
has an "executable dependency" on cargo-audit
.
Variant 2:
In addition to the Base Problem Statement it's important to not expose the user to the dependency executables for hygiene / clean interfaces / clean separation of concerns.
Describe the solution you'd like
My personal preference is to solve Variant 1 along these lines:
- Introduce a new
Cargo.toml
section[executable-dependencies]
whose entries are similar to[dependencies]
entries. - If crate
A
hasB = <v1spec>
in[executable-dependencies]
thencargo install A
behaves the same as runningcargo install --version <v1spec> B && cargo install A
.
Note that final &&
command is intended to be precise when it comes to name/version collisions, so if there's a name/version collision with B = <v1spec>
that will behave exactly the same as if the user executed cargo install --version <v1spec> B
directly (and no attempt to install A is attempted). This might make name/version collisions cumbersome for the user, but my intuition is at the very least it will be easy for all cargo users to understand what's happening and why.
I prefer this approach because I believe it's relatively simple for all cargo users to understand.
Notes
-
My preferred solution this assumes that if an executable from
A
is on the executable search$PATH
then the executables fromB
are also, so that the code inA
can do something likestd::process::Command("B").output()
successfully. -
A solution to Variant 2 might build on my proposed solution by placing the dependency binary in a private location and somehow expose the path to the code in
A
perhaps an environment variable. -
Related Tickets: I believe my proposed solution addresses Enable 'cargo install' to install binary-crates according to a spec of dependencies #5120. I don't think it addresses Make commands in dev-dependencies available to run #2267 even though that seems similar. I think the build script approach makes sense for Make commands in dev-dependencies available to run #2267, unless we want to also introduce
[dev-executable-dependencies]
. -
In all of this discussion of course, I am assuming all dependencies are crates and that executable crates provide 1 or more executables (perhaps transitively). This feature wouldn't facilitate depending on non-cargo binaries as a simplification.
-
Variant 2 seems pretty similar to just having the dependency provide a public API that the crate consumes as a normal library. It's different for two reasons: First, an executable commandline interface is an API. Not every dependency ensures they provide a library whose interface matches the executable perfectly. Second, the process abstraction matters for many reasons: resource management, concurrency, error handling, I/O, etc...