Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/51-supported-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ mcpp emit sbom -o sbom.json
It reads `mcpp.lock` rather than resolving again, because a document describing
a different graph from the one that was built is worse than no document. A
component whose licence mcpp does not know is emitted as `NOASSERTION` rather
than omitted: an absent field reads as "not examined".
than omitted: an absent field reads as "not examined". With no `mcpp.lock` the
document names the root package alone, and a note on standard error says that
nothing is recorded: that is the whole truth for a project with no dependencies,
and not for one that has never been built.

## Offline and air-gapped use

Expand Down
3 changes: 2 additions & 1 deletion docs/zh/51-supported-versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ mcpp emit sbom -o sbom.json

它读取 `mcpp.lock` 而不是重新解析,因为一份描述了与实际构建不同的依赖图的
文档,比没有文档更糟。许可证 mcpp 无法识别的组件会被写成 `NOASSERTION` 而不是
省略:缺失的字段读起来像「未检查」。
省略:缺失的字段读起来像「未检查」。没有 `mcpp.lock` 时,文档只列出根包,并在标准
错误上提示没有任何记录:这对没有依赖的工程是全部事实,对从未构建过的工程则不是。

## 离线与隔网使用

Expand Down
4 changes: 3 additions & 1 deletion src/build/prepare/records.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,10 @@ std::expected<void, std::string> step13_lockfile(PrepareState& state, BuildConte
// true and useless; which package moved, from which version to
// which, is what the reader does something about.
if (mcpp::invocation::get("MCPP_LOCKED").value_or("") == "1") {
// `load` reads a missing file as an empty lock, which would
// report every package as drift from a file that is not there.
auto prior = mcpp::lockfile::load(lockPath);
if (!prior) {
if (!prior || !std::filesystem::exists(lockPath)) {
return std::unexpected(std::format(
"--locked was given and there is no readable mcpp.lock at {}\n"
" Run the same command without --locked once to record "
Expand Down
14 changes: 9 additions & 5 deletions src/cli/cmd_sbom.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,16 @@ int cmd_sbom(const mcpplibs::cmdline::ParsedArgs& parsed) {

const auto lockPath = *root / "mcpp.lock";
std::vector<mcpp::lockfile::LockedPackage> pkgs;
if (auto lock = mcpp::lockfile::load(lockPath)) {
// Asked before loading: `load` reads a file that is not there as an empty
// lock, so its answer cannot tell "nothing recorded" from "no dependencies".
if (std::filesystem::exists(lockPath)) {
auto lock = mcpp::lockfile::load(lockPath);
if (!lock) {
mcpp::ui::error(std::format(
"mcpp.lock exists but could not be read: {}", lock.error().message));
return 2;
}
pkgs = lock->packages;
} else if (std::filesystem::exists(lockPath)) {
mcpp::ui::error(std::format(
"mcpp.lock exists but could not be read: {}", lock.error().message));
return 2;
} else {
// NOT AN ERROR, AND NOT SILENCE EITHER. A project with no
// dependencies has no lock and its bill of materials is one component,
Expand Down
27 changes: 27 additions & 0 deletions tests/e2e/333_named_runners_locked_and_sbom.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,21 @@ esac
echo " ok --locked names the package that moved and both versions"
mv mcpp.lock.bak mcpp.lock

# A LOCK THAT IS NOT THERE IS NOT A LOCK THAT DIFFERS. The refusal says what to
# do about it, and writes no lock for the next run to pass against.
mv mcpp.lock mcpp.lock.bak
if out=$("$MCPP" build --locked 2>&1); then
echo "FAIL: --locked accepted a project with no mcpp.lock"
echo "$out" | tail -3; exit 1
fi
case "$out" in
*"no readable mcpp.lock"*) ;;
*) echo "FAIL: the refusal does not say the lock is missing"; echo "$out"; exit 1 ;;
esac
test ! -e mcpp.lock || { echo "FAIL: a refused --locked wrote mcpp.lock"; exit 1; }
mv mcpp.lock.bak mcpp.lock
echo " ok --locked says a missing lock is missing"

# ── E: the bill of materials describes the RECORDED resolution ─────────────
"$MCPP" emit sbom -o sbom.json >/dev/null 2>&1 || { echo "FAIL: mcpp emit sbom"; exit 1; }
python3 - <<'PY' || exit 1
Expand Down Expand Up @@ -135,6 +150,18 @@ print(" ok sbom reads the lock rather than re-resolving")
PY
mv mcpp.lock.bak mcpp.lock

# NOTHING RECORDED IS SAID ON THE NARRATION STREAM, AND THE DOCUMENT STAYS VALID.
# A root-only bill of materials is true of a project with no dependencies and
# false of one that was never built, and only the reader knows which this is.
mv mcpp.lock mcpp.lock.bak
"$MCPP" emit sbom > sbom3.json 2> sbom3.err || { echo "FAIL: mcpp emit sbom with no lock"; exit 1; }
grep -q 'no mcpp.lock' sbom3.err \
|| { echo "FAIL: sbom did not say that nothing was recorded"; cat sbom3.err; exit 1; }
python3 -c 'import json; assert json.load(open("sbom3.json"))["components"] == []' \
|| { echo "FAIL: sbom with no lock is not a root-only document"; exit 1; }
mv mcpp.lock.bak mcpp.lock
echo " ok sbom says when no lock records the resolution"

# ── B: one board package, two environments, chosen by the consumer ─────────
#
# THE EMULATOR/HARDWARE AXIS IS A FEATURE, NOT A FORK. A board reached
Expand Down
Loading