conandeps: render missing binaries and overrides as colour-coded tables
All three sections (dependencies, missing binaries, overrides) now use the same column-aligned table layout in both the text and the HTML report. The text report gets ANSI colour on a terminal (green remote, red missing, yellow cache-only/overridden) via --color auto|always|never, honouring NO_COLOR; the table helper aligns on visible width so colour codes never break columns. Every state is still spelled out in words so nothing is conveyed by colour alone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EYNdaGDrpaqDyT8QtrTQbM
This commit is contained in:
parent
1724cb2637
commit
857500f20e
4 changed files with 220 additions and 85 deletions
|
|
@ -60,6 +60,11 @@ for what it does and how it is used.
|
||||||
header makes the file usable via `uv run conandeps.py` without a checkout.
|
header makes the file usable via `uv run conandeps.py` without a checkout.
|
||||||
- **Exit codes:** `0` fine, `1` missing binaries, `2` Conan error. CI relies
|
- **Exit codes:** `0` fine, `1` missing binaries, `2` Conan error. CI relies
|
||||||
on this.
|
on this.
|
||||||
|
- **Tables and colour, consistently.** Dependencies, missing binaries and
|
||||||
|
overrides are all rendered through the same `_table()` helper (text) and
|
||||||
|
`_html_table()` (HTML). Colour is an aid only – every state is also spelled
|
||||||
|
out in words – and `_table()` aligns on *visible* width so ANSI codes never
|
||||||
|
break columns. `--color auto` is the default (terminal + no `NO_COLOR`).
|
||||||
- **stdout is the report, stderr is progress.** Graph resolution against a
|
- **stdout is the report, stderr is progress.** Graph resolution against a
|
||||||
remote takes a while; progress lines (`_progress`) and `--verbose` live
|
remote takes a while; progress lines (`_progress`) and `--verbose` live
|
||||||
Conan output go to stderr only, so stdout can be redirected to a file.
|
Conan output go to stderr only, so stdout can be redirected to a file.
|
||||||
|
|
|
||||||
34
README.md
34
README.md
|
|
@ -72,6 +72,7 @@ conandeps CONANFILE [-r REMOTE] [-pr PROFILE] [-s KEY=VALUE] [-o PKG:KEY=VALUE]
|
||||||
| `--html FILE` | Also write a self-contained HTML report (no scripts, no external assets). |
|
| `--html FILE` | Also write a self-contained HTML report (no scripts, no external assets). |
|
||||||
| `-v` | Stream Conan's own output to stderr live. |
|
| `-v` | Stream Conan's own output to stderr live. |
|
||||||
| `-q` | Suppress the progress lines on stderr. |
|
| `-q` | Suppress the progress lines on stderr. |
|
||||||
|
| `--color auto\|always\|never` | Colour the text report. Default `auto`: only on a terminal, honours `NO_COLOR`. |
|
||||||
|
|
||||||
Progress (`[1/3] resolving graph for build_type=Release on knor ...`) goes to
|
Progress (`[1/3] resolving graph for build_type=Release on knor ...`) goes to
|
||||||
stderr; the report itself goes to stdout, so redirecting stdout to a file
|
stderr; the report itself goes to stdout, so redirecting stdout to a file
|
||||||
|
|
@ -91,22 +92,27 @@ profile: arch=x86_64, compiler=gcc, compiler.libcxx=libstdc++11, compiler.versio
|
||||||
|
|
||||||
Dependencies (4) and binary availability
|
Dependencies (4) and binary availability
|
||||||
|
|
||||||
package kind ctx Release Debug RelWithDebInfo
|
package kind ctx Release Debug RelWithDebInfo
|
||||||
-------------------------------------------------------------------------
|
----------------------------------------------------------
|
||||||
MyDepA/1.0 direct host knor knor knor
|
MyDepA/1.0 direct host knor knor knor
|
||||||
boost/1.8 indir. host knor knor knor
|
boost/1.8 indirect host knor knor knor
|
||||||
MyDepB/1.0 direct host knor knor knor
|
MyDepB/1.0 direct host knor knor knor
|
||||||
Zigma/1.0 indir. host knor knor MISSING
|
Zigma/1.0 indirect host knor knor MISSING
|
||||||
|
|
||||||
MISSING BINARIES (1 packages)
|
Missing binaries (1)
|
||||||
Zigma/1.0: missing RelWithDebInfo
|
|
||||||
RelWithDebInfo package_id 3f9c...e21a
|
package build type package_id reason
|
||||||
|
---------------------------------------------------------------------------------------
|
||||||
|
Zigma/1.0 RelWithDebInfo 3f9c2b7d0e6a4f18c5d9a0b3e7f1c2d4a5b6e21a no binary anywhere
|
||||||
|
|
||||||
Overrides (1)
|
Overrides (1)
|
||||||
Zigma/1.0 wanted boost/1.7
|
|
||||||
-> forced to boost/1.8 by your conanfile [implicit (newer direct requirement)]
|
package wanted forced to by kind
|
||||||
|
-------------------------------------------------------------------------------------
|
||||||
|
Zigma/1.0 boost/1.7 boost/1.8 your conanfile implicit (newer requirement downstream)
|
||||||
|
|
||||||
Dependency hierarchy
|
Dependency hierarchy
|
||||||
|
|
||||||
MyProject/conanfile.py
|
MyProject/conanfile.py
|
||||||
|-- MyDepA/1.0
|
|-- MyDepA/1.0
|
||||||
| `-- boost/1.8
|
| `-- boost/1.8
|
||||||
|
|
@ -115,6 +121,12 @@ MyProject/conanfile.py
|
||||||
`-- boost/1.8 [overrides boost/1.7 (implicit by your conanfile)]
|
`-- boost/1.8 [overrides boost/1.7 (implicit by your conanfile)]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
On a terminal the tables are coloured (green = on the remote, red = missing,
|
||||||
|
yellow = cache-only / overridden version); `--color never` or the `NO_COLOR`
|
||||||
|
environment variable turns that off, and redirecting stdout to a file never
|
||||||
|
produces colour codes unless you pass `--color always`. The HTML report uses
|
||||||
|
the same colour coding.
|
||||||
|
|
||||||
Reading the table:
|
Reading the table:
|
||||||
|
|
||||||
* `knor` (a remote name) – the remote has a binary for the package_id your
|
* `knor` (a remote name) – the remote has a binary for the package_id your
|
||||||
|
|
|
||||||
249
conandeps.py
249
conandeps.py
|
|
@ -37,6 +37,7 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import html
|
import html
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
@ -421,65 +422,140 @@ def _tree_lines(report: Report) -> List[str]:
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
def _cell(status: str, _package_id: str, remote: Optional[str]) -> str:
|
class _Palette:
|
||||||
|
"""ANSI colours for the terminal report; a no-op when disabled.
|
||||||
|
|
||||||
|
Colour is a readability aid only: every state is also spelled out in
|
||||||
|
words, so a colour-blind reader or a log file loses nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, enabled: bool):
|
||||||
|
self.enabled = enabled
|
||||||
|
|
||||||
|
def _wrap(self, code: str, text: str) -> str:
|
||||||
|
return "\033[%sm%s\033[0m" % (code, text) if self.enabled else text
|
||||||
|
|
||||||
|
def ok(self, text: str) -> str:
|
||||||
|
return self._wrap("32", text) # green
|
||||||
|
|
||||||
|
def bad(self, text: str) -> str:
|
||||||
|
return self._wrap("1;31", text) # bold red
|
||||||
|
|
||||||
|
def warn(self, text: str) -> str:
|
||||||
|
return self._wrap("33", text) # yellow
|
||||||
|
|
||||||
|
def dim(self, text: str) -> str:
|
||||||
|
return self._wrap("2", text)
|
||||||
|
|
||||||
|
def bold(self, text: str) -> str:
|
||||||
|
return self._wrap("1", text)
|
||||||
|
|
||||||
|
|
||||||
|
_ANSI_RE = re.compile(r"\033\[[0-9;]*m")
|
||||||
|
|
||||||
|
|
||||||
|
def _visible_len(text: str) -> int:
|
||||||
|
return len(_ANSI_RE.sub("", text))
|
||||||
|
|
||||||
|
|
||||||
|
def _table(headers: List[str], rows: List[List[str]], pal: _Palette) -> List[str]:
|
||||||
|
"""Render a column-aligned text table; cells may contain ANSI colour codes."""
|
||||||
|
widths = [len(h) for h in headers]
|
||||||
|
for row in rows:
|
||||||
|
for i, cell in enumerate(row):
|
||||||
|
widths[i] = max(widths[i], _visible_len(cell))
|
||||||
|
|
||||||
|
def fmt(cells: List[str]) -> str:
|
||||||
|
padded = [c + " " * (widths[i] - _visible_len(c)) for i, c in enumerate(cells)]
|
||||||
|
return " ".join(padded).rstrip()
|
||||||
|
|
||||||
|
lines = [pal.bold(fmt(headers)), "-" * (sum(widths) + 2 * (len(widths) - 1))]
|
||||||
|
lines.extend(fmt(r) for r in rows)
|
||||||
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _cell(status: str, _package_id: str, remote: Optional[str], pal: _Palette) -> str:
|
||||||
"""Cell text: the remote that has the binary, or why it is not available."""
|
"""Cell text: the remote that has the binary, or why it is not available."""
|
||||||
if status == "Remote":
|
if status == "Remote":
|
||||||
return remote or "remote"
|
return pal.ok(remote or "remote")
|
||||||
return _MARK.get(status, status)
|
text = _MARK.get(status, status)
|
||||||
|
if status == "Missing":
|
||||||
|
return pal.bad(text)
|
||||||
|
if status == "CacheOnly":
|
||||||
|
return pal.warn(text)
|
||||||
|
return pal.dim(text)
|
||||||
|
|
||||||
|
|
||||||
def render_text(report: Report) -> str:
|
def _override_kind(o: Override) -> str:
|
||||||
|
return "explicit (override=True)" if o.explicit else "implicit (newer requirement downstream)"
|
||||||
|
|
||||||
|
|
||||||
|
def render_text(report: Report, color: bool = False) -> str:
|
||||||
|
pal = _Palette(color)
|
||||||
out: List[str] = []
|
out: List[str] = []
|
||||||
w = out.append
|
w = out.append
|
||||||
w("conandeps report for %s" % report.conanfile)
|
w(pal.bold("conandeps report for %s" % report.conanfile))
|
||||||
w("remote : %s" % (report.remote or "(all configured remotes)"))
|
w("remote : %s" % (report.remote or "(all configured remotes)"))
|
||||||
w("profile: " + ", ".join("%s=%s" % kv for kv in report.profile.items()))
|
w("profile: " + ", ".join("%s=%s" % kv for kv in report.profile.items()))
|
||||||
w("")
|
w("")
|
||||||
|
|
||||||
# ---- table -----------------------------------------------------------
|
# ---- dependencies ----------------------------------------------------
|
||||||
w("Dependencies (%d) and binary availability" % len(report.deps))
|
w(pal.bold("Dependencies (%d) and binary availability" % len(report.deps)))
|
||||||
w("")
|
w("")
|
||||||
refw = max([len(d.ref) for d in report.deps.values()] + [len("package")])
|
rows = [
|
||||||
header = "%-*s %-6s %-4s " % (refw, "package", "kind", "ctx") + " ".join(
|
[dep.ref, "direct" if dep.direct else "indirect", dep.context]
|
||||||
"%-14s" % bt for bt in report.build_types
|
+ [_cell(*dep.binaries[bt], pal) for bt in report.build_types]
|
||||||
)
|
for dep in report.deps.values()
|
||||||
w(header)
|
]
|
||||||
w("-" * len(header))
|
out.extend(_table(["package", "kind", "ctx"] + list(report.build_types), rows, pal))
|
||||||
for dep in report.deps.values():
|
|
||||||
kind = "direct" if dep.direct else "indir."
|
|
||||||
cells = " ".join("%-14s" % _cell(*dep.binaries[bt]) for bt in report.build_types)
|
|
||||||
w("%-*s %-6s %-4s %s" % (refw, dep.ref, kind, dep.context, cells))
|
|
||||||
w("")
|
w("")
|
||||||
|
|
||||||
# ---- problems --------------------------------------------------------
|
# ---- missing binaries ------------------------------------------------
|
||||||
problems = report.problems()
|
problems = report.problems()
|
||||||
|
n_missing = sum(len(d.missing()) for d in problems)
|
||||||
|
w(pal.bold("Missing binaries (%d)" % n_missing))
|
||||||
|
w("")
|
||||||
if problems:
|
if problems:
|
||||||
w("MISSING BINARIES (%d packages)" % len(problems))
|
rows = []
|
||||||
for dep in problems:
|
for dep in problems:
|
||||||
w(" %s: missing %s" % (dep.ref, ", ".join(dep.missing())))
|
|
||||||
for bt in dep.missing():
|
for bt in dep.missing():
|
||||||
status, pid, _ = dep.binaries[bt]
|
status, pid, _ = dep.binaries[bt]
|
||||||
note = " (in local cache only, not on the remote)" if status == "CacheOnly" else ""
|
reason = (
|
||||||
w(" %s package_id %s%s" % (bt, pid, note))
|
pal.warn("not on remote, in local cache only")
|
||||||
|
if status == "CacheOnly"
|
||||||
|
else pal.bad("no binary anywhere")
|
||||||
|
)
|
||||||
|
rows.append([dep.ref, bt, pid, reason])
|
||||||
|
out.extend(_table(["package", "build type", "package_id", "reason"], rows, pal))
|
||||||
else:
|
else:
|
||||||
w("All packages have binaries on the remote for: " + ", ".join(report.build_types))
|
w(pal.ok("All packages have binaries on the remote for: " + ", ".join(report.build_types)))
|
||||||
w("")
|
w("")
|
||||||
|
|
||||||
# ---- overrides -------------------------------------------------------
|
# ---- overrides -------------------------------------------------------
|
||||||
w("Overrides (%d)" % len(report.overrides))
|
w(pal.bold("Overrides (%d)" % len(report.overrides)))
|
||||||
if not report.overrides:
|
w("")
|
||||||
w(" none detected")
|
if report.overrides:
|
||||||
for o in report.overrides:
|
rows = [
|
||||||
kind = "explicit override=True" if o.explicit else "implicit (newer direct requirement)"
|
[
|
||||||
w(" %s wanted %s" % (o.package, o.old))
|
o.package,
|
||||||
w(" -> forced to %s by %s [%s]" % (o.new, o.by, kind))
|
pal.dim(o.old),
|
||||||
if o.new in report.ranges:
|
pal.warn(o.new),
|
||||||
w(" (%s resolved from %s)" % (o.new, report.ranges[o.new]))
|
o.by,
|
||||||
|
pal.bold(_override_kind(o)) if o.explicit else _override_kind(o),
|
||||||
|
]
|
||||||
|
for o in report.overrides
|
||||||
|
]
|
||||||
|
out.extend(_table(["package", "wanted", "forced to", "by", "kind"], rows, pal))
|
||||||
|
else:
|
||||||
|
w("none detected")
|
||||||
w("")
|
w("")
|
||||||
|
|
||||||
# ---- hierarchy -------------------------------------------------------
|
# ---- hierarchy -------------------------------------------------------
|
||||||
w("Dependency hierarchy")
|
w(pal.bold("Dependency hierarchy"))
|
||||||
out.extend(_tree_lines(report))
|
w("")
|
||||||
|
for line in _tree_lines(report):
|
||||||
|
line = line.replace("MISSING:", pal.bad("MISSING:")) if color else line
|
||||||
|
w(line)
|
||||||
return "\n".join(out) + "\n"
|
return "\n".join(out) + "\n"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -489,16 +565,27 @@ def render_text(report: Report) -> str:
|
||||||
_CSS = """
|
_CSS = """
|
||||||
body{font:14px/1.4 system-ui,sans-serif;margin:2rem;color:#1b1b1b;background:#fff}
|
body{font:14px/1.4 system-ui,sans-serif;margin:2rem;color:#1b1b1b;background:#fff}
|
||||||
h1,h2{font-weight:600}table{border-collapse:collapse;margin:1rem 0}
|
h1,h2{font-weight:600}table{border-collapse:collapse;margin:1rem 0}
|
||||||
th,td{border:1px solid #bbb;padding:.3rem .6rem;text-align:left}
|
th,td{border:1px solid #bbb;padding:.3rem .6rem;text-align:left;vertical-align:top}
|
||||||
th{background:#eee}.ok{color:#1b6e1b}.ok::before{content:"\\2713 "}
|
th{background:#eee}code{font-size:.95em}
|
||||||
|
.ok{color:#1b6e1b}.ok::before{content:"\\2713 "}
|
||||||
.missing{color:#a40000;font-weight:700}.missing::before{content:"\\2717 "}
|
.missing{color:#a40000;font-weight:700}.missing::before{content:"\\2717 "}
|
||||||
.na{color:#777}pre{background:#f5f5f5;padding:1rem;overflow-x:auto}
|
.cacheonly{color:#8a5a00;font-weight:700}.cacheonly::before{content:"\\26A0 "}
|
||||||
.tag{font-size:.85em;color:#555}
|
.na{color:#777}.old{color:#777;text-decoration:line-through}.new{color:#8a5a00;font-weight:700}
|
||||||
|
.explicit{color:#1b4e8a;font-weight:700}.implicit{color:#555}
|
||||||
|
pre{background:#f5f5f5;padding:1rem;overflow-x:auto}
|
||||||
@media (prefers-color-scheme:dark){body{background:#111;color:#eee}th{background:#222}
|
@media (prefers-color-scheme:dark){body{background:#111;color:#eee}th{background:#222}
|
||||||
th,td{border-color:#444}pre{background:#1c1c1c}.ok{color:#6fd66f}.missing{color:#ff7b7b}}
|
th,td{border-color:#444}pre{background:#1c1c1c}.ok{color:#6fd66f}.missing{color:#ff7b7b}
|
||||||
|
.cacheonly{color:#ffc857}.new{color:#ffc857}.explicit{color:#8ab8f0}.na,.old,.implicit{color:#999}}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _html_table(headers: List[str], rows: List[List[str]]) -> str:
|
||||||
|
"""Rows are lists of already-escaped/markup cell strings."""
|
||||||
|
head = "".join("<th>%s</th>" % html.escape(h) for h in headers)
|
||||||
|
body = "".join("<tr>%s</tr>" % "".join("<td>%s</td>" % c for c in row) for row in rows)
|
||||||
|
return "<table><tr>%s</tr>%s</table>" % (head, body)
|
||||||
|
|
||||||
|
|
||||||
def render_html(report: Report) -> str:
|
def render_html(report: Report) -> str:
|
||||||
e = html.escape
|
e = html.escape
|
||||||
parts: List[str] = []
|
parts: List[str] = []
|
||||||
|
|
@ -515,49 +602,56 @@ def render_html(report: Report) -> str:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
w("<h2>Dependencies and binary availability</h2><table>")
|
# ---- dependencies ----------------------------------------------------
|
||||||
w(
|
w("<h2>Dependencies (%d) and binary availability</h2>" % len(report.deps))
|
||||||
"<tr><th>package</th><th>kind</th><th>context</th>"
|
rows = []
|
||||||
+ "".join("<th>%s</th>" % e(bt) for bt in report.build_types)
|
|
||||||
+ "</tr>"
|
|
||||||
)
|
|
||||||
for dep in report.deps.values():
|
for dep in report.deps.values():
|
||||||
w(
|
row = [e(dep.ref), "direct" if dep.direct else "indirect", e(dep.context)]
|
||||||
"<tr><td>%s</td><td>%s</td><td>%s</td>"
|
|
||||||
% (e(dep.ref), "direct" if dep.direct else "indirect", e(dep.context))
|
|
||||||
)
|
|
||||||
for bt in report.build_types:
|
for bt in report.build_types:
|
||||||
status, pid, remote = dep.binaries[bt]
|
status, pid, remote = dep.binaries[bt]
|
||||||
cls = "ok" if status in _AVAILABLE else "missing" if status in _PROBLEM else "na"
|
cls = {"Remote": "ok", "Missing": "missing", "CacheOnly": "cacheonly"}.get(status, "na")
|
||||||
title = ' title="package_id %s"' % e(pid)
|
text = e(_cell(status, pid, remote, _Palette(False)))
|
||||||
w('<td class="%s"%s>%s</td>' % (cls, title, e(_cell(status, pid, remote))))
|
row.append('<span class="%s" title="package_id %s">%s</span>' % (cls, e(pid), text))
|
||||||
w("</tr>")
|
rows.append(row)
|
||||||
w("</table>")
|
w(_html_table(["package", "kind", "context"] + list(report.build_types), rows))
|
||||||
|
|
||||||
|
# ---- missing binaries ------------------------------------------------
|
||||||
problems = report.problems()
|
problems = report.problems()
|
||||||
w("<h2>Missing binaries (%d)</h2>" % len(problems))
|
n_missing = sum(len(d.missing()) for d in problems)
|
||||||
|
w("<h2>Missing binaries (%d)</h2>" % n_missing)
|
||||||
if problems:
|
if problems:
|
||||||
w(
|
rows = []
|
||||||
"<ul>"
|
for dep in problems:
|
||||||
+ "".join(
|
for bt in dep.missing():
|
||||||
"<li>%s: missing %s</li>" % (e(d.ref), e(", ".join(d.missing()))) for d in problems
|
status, pid, _ = dep.binaries[bt]
|
||||||
)
|
reason = (
|
||||||
+ "</ul>"
|
'<span class="cacheonly">not on remote, in local cache only</span>'
|
||||||
)
|
if status == "CacheOnly"
|
||||||
|
else '<span class="missing">no binary anywhere</span>'
|
||||||
|
)
|
||||||
|
rows.append([e(dep.ref), e(bt), "<code>%s</code>" % e(pid), reason])
|
||||||
|
w(_html_table(["package", "build type", "package_id", "reason"], rows))
|
||||||
else:
|
else:
|
||||||
w("<p>All packages have binaries for %s.</p>" % e(", ".join(report.build_types)))
|
w(
|
||||||
|
'<p class="ok">All packages have binaries on the remote for %s.</p>'
|
||||||
|
% e(", ".join(report.build_types))
|
||||||
|
)
|
||||||
|
|
||||||
|
# ---- overrides -------------------------------------------------------
|
||||||
w("<h2>Overrides (%d)</h2>" % len(report.overrides))
|
w("<h2>Overrides (%d)</h2>" % len(report.overrides))
|
||||||
if report.overrides:
|
if report.overrides:
|
||||||
w("<ul>")
|
rows = [
|
||||||
for o in report.overrides:
|
[
|
||||||
kind = "explicit override=True" if o.explicit else "implicit"
|
e(o.package),
|
||||||
w(
|
'<span class="old">%s</span>' % e(o.old),
|
||||||
"<li>%s wanted <code>%s</code> → forced to <code>%s</code> by %s "
|
'<span class="new">%s</span>' % e(o.new),
|
||||||
'<span class="tag">[%s]</span></li>'
|
e(o.by),
|
||||||
% (e(o.package), e(o.old), e(o.new), e(o.by), kind)
|
'<span class="%s">%s</span>'
|
||||||
)
|
% ("explicit" if o.explicit else "implicit", e(_override_kind(o))),
|
||||||
w("</ul>")
|
]
|
||||||
|
for o in report.overrides
|
||||||
|
]
|
||||||
|
w(_html_table(["package", "wanted", "forced to", "by", "kind"], rows))
|
||||||
else:
|
else:
|
||||||
w("<p>None detected.</p>")
|
w("<p>None detected.</p>")
|
||||||
|
|
||||||
|
|
@ -618,7 +712,16 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||||
"-v", "--verbose", action="store_true", help="stream Conan's own output to stderr live"
|
"-v", "--verbose", action="store_true", help="stream Conan's own output to stderr live"
|
||||||
)
|
)
|
||||||
p.add_argument("-q", "--quiet", action="store_true", help="no progress lines on stderr")
|
p.add_argument("-q", "--quiet", action="store_true", help="no progress lines on stderr")
|
||||||
|
p.add_argument(
|
||||||
|
"--color",
|
||||||
|
choices=("auto", "always", "never"),
|
||||||
|
default="auto",
|
||||||
|
help="colour the text report (default: auto = only on a terminal, honours NO_COLOR)",
|
||||||
|
)
|
||||||
args = p.parse_args(argv)
|
args = p.parse_args(argv)
|
||||||
|
color = args.color == "always" or (
|
||||||
|
args.color == "auto" and sys.stdout.isatty() and not os.environ.get("NO_COLOR")
|
||||||
|
)
|
||||||
|
|
||||||
build_types = [b.strip() for b in args.build_types.split(",") if b.strip()]
|
build_types = [b.strip() for b in args.build_types.split(",") if b.strip()]
|
||||||
try:
|
try:
|
||||||
|
|
@ -637,7 +740,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
||||||
sys.stderr.write("conandeps: error: %s\n" % exc)
|
sys.stderr.write("conandeps: error: %s\n" % exc)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
sys.stdout.write(render_text(report))
|
sys.stdout.write(render_text(report, color))
|
||||||
if args.html:
|
if args.html:
|
||||||
with open(args.html, "w", encoding="utf-8") as fh:
|
with open(args.html, "w", encoding="utf-8") as fh:
|
||||||
fh.write(render_html(report))
|
fh.write(render_html(report))
|
||||||
|
|
|
||||||
|
|
@ -259,6 +259,7 @@ def test_cache_only_is_not_available(conan_env):
|
||||||
|
|
||||||
text = conandeps.render_text(report)
|
text = conandeps.render_text(report)
|
||||||
assert "not available" in text and "in local cache only" in text
|
assert "not available" in text and "in local cache only" in text
|
||||||
|
assert 'class="cacheonly"' in conandeps.render_html(report)
|
||||||
_conan(env, "remove", "libbar/1.1@", "-f") # leave the cache as other tests expect
|
_conan(env, "remove", "libbar/1.1@", "-f") # leave the cache as other tests expect
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -273,12 +274,26 @@ def test_overrides(report):
|
||||||
def test_text_and_html_render(report, tmp_path):
|
def test_text_and_html_render(report, tmp_path):
|
||||||
text = conandeps.render_text(report)
|
text = conandeps.render_text(report)
|
||||||
assert "libbar/1.1" in text and "MISSING" in text
|
assert "libbar/1.1" in text and "MISSING" in text
|
||||||
table = text.split("MISSING BINARIES")[0]
|
table = text.split("Missing binaries")[0]
|
||||||
assert table.count("test") >= 4 * 3 - 2 # every available cell names the remote
|
assert table.count("test") >= 4 * 3 - 2 # every available cell names the remote
|
||||||
assert "overrides libbar/1.0 (explicit" in text and "explicit override=True" in text
|
assert "\033[" not in text # no ANSI codes unless asked for
|
||||||
|
# Missing binaries and overrides are tables with a header row.
|
||||||
|
assert "package build type package_id" in text
|
||||||
|
assert "libbar/1.1 Debug 8bfd7e15c6920f4673ca8dee419ab5320ae445e4 no binary" in text
|
||||||
|
assert "package wanted forced to by kind" in text
|
||||||
|
assert "libfoo/1.0 libbar/1.0 libbar/1.1 your conanfile explicit (override=True)" in text
|
||||||
|
assert "overrides libbar/1.0 (explicit" in text # tree edge annotation
|
||||||
|
|
||||||
|
coloured = conandeps.render_text(report, color=True)
|
||||||
|
assert "\033[1;31mMISSING\033[0m" in coloured and "\033[32mtest\033[0m" in coloured
|
||||||
|
# Colour codes must not break column alignment: same visible layout.
|
||||||
|
strip = conandeps._ANSI_RE.sub
|
||||||
|
assert strip("", coloured) == text
|
||||||
assert "`-- " in text or "|-- " in text # tree drawn
|
assert "`-- " in text or "|-- " in text # tree drawn
|
||||||
page = conandeps.render_html(report)
|
page = conandeps.render_html(report)
|
||||||
assert page.startswith("<!DOCTYPE html>") and 'class="missing"' in page
|
assert page.startswith("<!DOCTYPE html>") and 'class="missing"' in page
|
||||||
|
assert page.count("<table>") == 3 # dependencies, missing binaries, overrides
|
||||||
|
assert 'class="explicit"' in page and 'class="implicit"' in page
|
||||||
assert "<script" not in page # self-contained, static
|
assert "<script" not in page # self-contained, static
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue