makecindy / makecindy/cindy

licenses:generate 的 mobile 与全工程声明取决于本机装了哪些可选包,不同机器重跑结果不同

Open
#485 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
2.7k
Forks
395
Avg merge
21h 48m
Merged PRs (30d)
776

Description

## 问题

`scripts/generate-third-party-notices.mjs` 里三个 desktop 闭包都传了显式 target,所以产物与本机安装集合无关:

```js
const desktopWinNpm = collectClosure([DESKTOP_DIR], { os: "win32", cpu: "x64" });
const desktopMacNpm = mergeClosures(
collectClosure([DESKTOP_DIR], { os: "darwin", cpu: "x64" }),
collectClosure([DESKTOP_DIR], { os: "darwin", cpu: "arm64" }),
);
const desktopLinuxNpm = mergeClosures(
collectClosure([DESKTOP_DIR], { os: "linux", cpu: "x64", libc: "glibc" }),
collectClosure([DESKTOP_DIR], { os: "linux", cpu: "arm64", libc: "glibc" }),
);
```

但另外两个没有:

```js
const projectNpm = collectClosure([REPO_ROOT, ...discoverWorkspaceDirs()]);
const mobileNpm = collectClosure([MOBILE_DIR]);
```

`target = null` 时 `matchesTarget()` 一律放行,而 `collectClosure` 判断一个 optional dependency 是否存在靠的是 `resolvePkgDir()` —— 也就是「`node_modules` 里有没有这个目录」。于是 `mobile-ios.txt`、`mobile-android.txt`、两份 `THIRD-PARTY-NOTICES.txt` 和对应 SBOM 的内容,取决于**跑生成的那台机器装了哪些平台可选包**。

## 证据

在 `1bed48d6` 上 `pnpm install` 后直接跑 `pnpm licenses:generate`(未改任何代码),与仓库 committed 产物对比:

```
docs/legal/notices/THIRD-PARTY-NOTICES.txt | 32 +++++-
docs/legal/notices/mobile-android.txt | 6 ++--
docs/legal/notices/mobile-ios.txt | 6 ++--
docs/legal/notices/sbom/mobile-android.spdx.json | 46 +++++++++-
docs/legal/notices/sbom/mobile-ios.spdx.json | 46 +++++++++-
```

- `mobile-ios.txt` / `mobile-android.txt`:`SECTION 2: npm packages` 从 607 变 609,多出 `lightningcss-linux-arm64-musl@1.32.0`、`lightningcss-linux-x64-musl@1.32.0`
- 全工程 `THIRD-PARTY-NOTICES.txt`:从 1310 变 1324,多出 14 个 musl 变体 —— `@img/sharp-linuxmusl-{x64,arm64}`(0.34.5 与 0.35.3 两组)、`@img/sharp-libvips-linuxmusl-{x64,arm64}`、`@parcel/watcher-linux-{x64,arm64}-musl`、`@napi-rs/canvas-linux-{x64,arm64}-musl`、`@napi-rs/woff-build-linux-{x64,arm64}-musl`、`@rollup/rollup-linux-{x64,arm64}-musl`、`lightningcss-linux-{x64,arm64}-musl`
- `desktop-*` 三份产物零 diff(它们传了 target)

把这些 musl 目录从 `node_modules` 临时移出后重跑,产物与 committed 版本**完全一致**(零 diff),移回去再跑又出现同样 diff —— 确认变量就是本机安装集合,不是别的漂移源。

值得注意的是根 `package.json` 已经写了:

```json
"supportedArchitectures": { "os": ["win32","darwin","linux"], "cpu": ["x64","arm64"], "libc": ["glibc"] }
```

`libc` 只声明了 `glibc`,但 pnpm 10.33.2 在 macOS arm64 上仍把上面这些 musl 包装进了 `node_modules`(实测各目录 mtime 与 glibc 变体一致,是同一次 install 装的,不是历史残留)。所以光靠 `supportedArchitectures` 兜不住,生成器这侧不能假设「装了的就是该声明的」。

另外 `.github/workflows/` 里没有任何跑 `licenses:generate` 或校验产物一致性的步骤,所以这种漂移不会被 CI 拦下 —— 实际效果是「谁最后重跑,谁的机器决定产物内容」。

## 严重度

不是许可义务缺漏 —— 多声明几个包不构成合规缺失。实际影响是两点:

1. **产物不可复现**:任何顺手重跑 `licenses:generate` 的 PR 都会带进一批与本次改动无关的 diff。#483(修 #452)就撞上了,只能在生成时把 musl 包临时移出 `node_modules` 来对齐 committed 口径,才让 diff 收敛到 macOS 那一处。这个绕法不该成为常规操作。
2. **声明范围与分发物不符**:`mobile-ios.txt` / `mobile-android.txt` 里声明了 Linux musl 的原生二进制,而 iOS/Android 安装包并不分发它们(`lightningcss`、`@parcel/watcher`、`@rollup` 这些是构建期工具链依赖,其平台可选包不进 app bundle)。这与 `docs/legal/notices/README.md` 里「移动端 JS 生产依赖」的口径也不一致。

定级参照 #451 / #452 里同类「声明范围不准」的 P2,但这条偏工程可复现性,具体定级请按仓库标准。

## 可能的修法

没有把握哪个符合维护者的意图,列几个方向:

1. 给 `mobileNpm` 和 `projectNpm` 传显式 target(至少锁 `libc: "glibc"`),让产物与本机安装集合解耦。需要先确定 mobile 产物的正确 target 语义 —— 移动端 JS 依赖里的平台可选包本就不随 app 分发,可能该更严格地排除,而不只是锁 libc。
2. 让闭包遍历以 lockfile 为准而非「`node_modules` 里目录是否存在」,这样安装集合不再影响输出。
3. 加 CI 门禁:固定生成环境跑 `licenses:generate` 后 `git diff --exit-code`,把漂移在 PR 阶段拦下。这条与 1/2 不互斥,建议一起做 —— 否则以后还会有人在不同环境重跑并提交不一致的产物。

## 为什么没在 #483 一起改

#483 的范围是 `desktop-macos` 的 libvips 架构描述。改这里会同时改动 mobile 与全工程产物(以及可能的 target 语义讨论),diff 会远远越出那个范围,故单独记录。

## 环境

- commit:`1bed48d6`
- 平台:macOS(darwin arm64)
- pnpm 10.33.2 / node v22.23.1

## 复现步骤

1. 在 `1bed48d6` 上 `pnpm install`
2. 确认本机装上了 musl 变体:`find node_modules -maxdepth 2 -type d -name "*musl*"`
3. 不改任何代码,跑 `pnpm licenses:generate`
4. `git status --short docs/ apps/desktop/resources/` —— 出现上面列的 5 个文件 diff
5. 把 musl 目录移出 `node_modules` 后 `git checkout -- docs/ apps/desktop/resources/` 并重跑第 3 步 —— 这次零 diff

Contributor guide

Open the contributing guide

Research direction

Start with scripts/generate-third-party-notices.mjs, reading collectClosure, matchesTarget, and resolvePkgDir, then reproduce with pnpm licenses:generate on the stated commit. Compare the generated mobile notices, SBOMs, and THIRD-PARTY-NOTICES.txt with committed files, and inspect package.json and .github/workflows/ for the intended platform scope and CI checks. Done means generation is independent of locally installed optional packages and the affected outputs remain consistent.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
build-system, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.