CliDeps.Check (CliDeps v0.1.1)

Copy Markdown View Source

Runs a tool's availability and version check.

Three things here are deliberate and easy to get wrong:

A non-zero exit fails the check, even when the output contains a version. The first version of this took any parsed version as proof of presence, on the theory that some tools exit non-zero for --version — and that theory inverted the check: failure output routinely contains a dotted number. A broken install dying in the dynamic loader was reported as version "265.215.0", read out of libx265.215.dylib in the error message. Tools that genuinely report their version with a non-zero exit (ssh -V exits 255) opt in with allow_nonzero: true.

The version subprocess has a timeout, ten seconds by default. A tool that ignores its version flag and waits on stdin would otherwise hang the check forever — and the moduledoc places checks on a request path behind an expiring cache, which is exactly where "forever" is unaffordable. On timeout the process is killed, not abandoned.

Nothing is executed unless the executable exists. System.find_executable/1 is consulted first, so a missing tool costs a PATH scan rather than a process spawn.

Summary

Types

The outcome of checking one tool.

Functions

A human-readable explanation of a result, including the install hint.

Extracts a version string from command output.

Checks one tool, without consulting or populating any cache.

Types

result()

@type result() ::
  {:ok, String.t() | :unknown}
  | {:error,
     :not_found
     | {:version_mismatch, String.t(), String.t()}
     | {:unparseable_version, String.t()}
     | {:exec_failed, term()}
     | {:check_failed, non_neg_integer(), String.t()}
     | {:timeout, pos_integer()}}

The outcome of checking one tool.

  • {:ok, version} — present, and satisfying the requirement if one was declared
  • {:ok, :unknown} — present, but no version could be parsed and none was required
  • {:error, :not_found} — not on PATH
  • {:error, {:version_mismatch, found, requirement}} — present but too old
  • {:error, {:unparseable_version, output}} — present, a version was required, none found
  • {:error, {:exec_failed, reason}} — the executable exists but could not be run
  • {:error, {:check_failed, status, output}} — the version command exited non-zero
  • {:error, {:timeout, ms}} — the version command did not finish in time

Functions

explain(tool, arg)

@spec explain(CliDeps.Tool.t(), result()) :: String.t()

A human-readable explanation of a result, including the install hint.

Written for whoever has to fix it, which is usually not the person who wrote the declaration.

extract_version(tool, output)

@spec extract_version(CliDeps.Tool.t(), String.t()) :: String.t() | nil

Extracts a version string from command output.

Returns a value Version.parse/1 accepts, padding a two-part version such as 7.1 to 7.1.0 — tools print both forms and Version requires three.

run(tool)

@spec run(CliDeps.Tool.t()) :: result()

Checks one tool, without consulting or populating any cache.