news 2026/9/20 1:36:50

Nixpkgs 全局配置(config.nix)完全指南:从默认安装限制到声明式包管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nixpkgs 全局配置(config.nix)完全指南:从默认安装限制到声明式包管理

Nixpkgs 全局配置(config.nix)完全指南:从默认安装限制到声明式包管理

【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs

导读

Nixpkgs 根据包的元数据(meta)对"能装什么、不能装什么"有一套默认策略:损坏(broken)、平台不支持(unsupported)、许可证非自由(unfree)、存在已知安全漏洞(insecure)的包默认都被拒绝安装。本文基于 Nixpkgs 手册的《Global configuration》章节,结合仓库中的 配置选项定义、元数据检查实现 与 problems 机制实现 等源码,系统讲解用户级配置文件~/.config/nixpkgs/config.nix的查找规则、各类"允许安装"开关(环境变量 / 配置项 / 谓词函数)、problems 处理机制,以及如何用packageOverrides实现声明式包管理。读完本文,你将能精准掌控本机 Nixpkgs 的安装策略,并能搭建一套属于自己的声明式用户环境。

默认安装限制:Nix 为何拒绝某些包

Nix 本身带有关于"哪些包可以安装、哪些包不可以安装"的默认判断,依据是包的元数据。默认情况下,只要满足以下任一条件,Nix 就会阻止安装:

  • 包被认为已损坏:meta.broken被设置为true
  • 包不适用于当前系统:meta.platforms中没有与当前系统匹配的条目;
  • 包的meta.license属于被视为非自由(unfree)的许可证;
  • 包存在已知安全漏洞且因故无法(或尚未)更新,其meta.knownVulnerabilities中记录了一系列问题;
  • 包存在必须被用户知晓的问题,例如弃用(deprecation)通告。

注意:以上所有检查在求值(evaluation)阶段就已执行,且检查范围包括一切被求值的包。特别是,所有构建期依赖也会被检查

这五类限制的每一条都可以在 Nixpkgs 配置中被调整。其中"包存在问题"这一类(第 5 条)对应的正是本仓库中 problems.nix 所实现的meta.problems机制,将在后文详述。

从源码看,上述默认策略的落地位置在 check-meta.nix 的checkValidity函数中(第 343 行起):它会依次检查非自由许可证(unfree)、许可证黑名单(blocklisted)、非源码构建(non-source)、平台不支持(unsupported)与不安全(insecure),一旦命中即返回带reasonmsg的错误结构;而assertValidity(第 656 行起)则把checkValidity的结果与 problems 检查结果合并,最终决定包的valid状态是"yes""warn"还是"no"

用户配置文件的位置与查找顺序

用户的 Nixpkgs 配置存放在用户专属的配置文件~/.config/nixpkgs/config.nix中。最简单的例子:

{ allowUnfree = true; }

::: {.caution} 非自由软件在 Nixpkgs 持续集成(CI)中不会被测试或构建,因此也没有缓存。大多数非自由许可证禁止执行或分发该软件,启用前请自行确认许可条款。 :::

NIXPKGS_CONFIG环境变量可以覆盖配置文件的位置。Nixpkgs 按以下顺序解析配置:

  1. $NIXPKGS_CONFIG(若已设置且文件存在);
  2. ~/.config/nixpkgs/config.nix(若存在);
  3. ~/.nixpkgs/config.nix(旧式路径,若存在);
  4. 空配置{}

这段查找逻辑在 pkgs/top-level/impure.nix 中直接对应:它依次读取builtins.getEnv "NIXPKGS_CONFIG"homeDir + "/.config/nixpkgs/config.nix"homeDir + "/.nixpkgs/config.nix"(注释里明确标注了 obsolete),逐一用builtins.pathExists判断后import第一个存在的文件,全部不存在则回退为{ }

几点需要特别注意的适用范围:

  • 在 NixOS 上,NIXPKGS_CONFIG系统级地指向/etc/nix/nixpkgs-config.nix。把配置文件放到该路径即可让nix-envnix-shell等用户级命令生效;NixOS不会自动创建这个文件;
  • NixOS 模块系统中的nixpkgs.config选项不影响nix-envnix-shell等用户级命令;
  • 这套查找规则仅适用于非 flake 用法(channel 与<nixpkgs>)。Flakes 会忽略该查找,需要在import nixpkgs时直接传入config,例如import nixpkgs { config = { allowUnfree = true; }; }

从源码结构可以确认,用户配置最终会被并入 pkgs/top-level/config.nix 所声明的config选项中。该文件为整个config定义了模块化结构,声明了allowUnfreeallowBrokenallowUnsupportedSystemproblems等全部选项,并允许通过freeformType接受未声明的键(配合warnUndeclaredOptions可对未声明选项发出警告)。

安装损坏(broken)的包

有几种方式可以尝试编译被标记为 broken 的包。

方式一:单次允许(环境变量)——为某一次 nix 工具调用放行:

$ export NIXPKGS_ALLOW_BROKEN=1

方式二:按包名永久放行(problems.handlers)——在用户配置文件中为特定包设置对应问题的处理方式:

{ problems.handlers.hello.broken = "warn"; # 或 "ignore" }

方式三:全局永久放行(配置项)——在用户配置文件中添加:

{ allowBroken = true; }

从 config.nix 的选项定义看,allowBroken的默认值是false,其defaultTextfalse || builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1"——也就是说,环境变量NIXPKGS_ALLOW_BROKEN=1实际等效于运行时把该选项置真。这一判断的getEnv部分在 problems.nix 的broken自动问题条件中实现(config.allowBroken || builtins.getEnv "NIXPKGS_ALLOW_BROKEN" == "1")。同文件还指出,旧选项config.allowBrokenPredicate已弃用,建议改用config.problems.handlers.我的包.broken = "warn"对单个包放行。

仓库测试 pkgs/test/problems/cases/allow-broken-env 验证了这一机制:其default.nix声明了一个meta.broken = true的包,而env.nix设置NIXPKGS_ALLOW_BROKEN = 1,测试期望的 stderr 中不再出现拒绝求值的报错,证明环境变量确实生效。

安装平台不支持(unsupported)的包

同样有两种方式尝试编译在给定系统上被标记为不支持的包。

方式一:单次允许(环境变量)

$ export NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1

方式二:永久允许(配置项)

{ allowUnsupportedSystem = true; }

"不支持"与"损坏"之间的界限确实有些模糊。如果一个程序理应能在某平台上工作却没有,那么该平台应被包含进meta.platforms,但包应被标记为 broken,例如meta.broken = !hostPlatform.isWindows。当然,什么叫"理应"最终由包维护者决定。

在 check-meta.nix 中,hasUnsupportedPlatform(第 140 行起)给出了精确判定:当allowUnsupportedSystem为真时恒返回 false;否则检查pkg.meta.platforms是否包含当前hostPlatform.system(先做快速字符串包含判断,再用platformMatch做属性集匹配),以及pkg.meta.badPlatforms是否命中当前平台。meta.badPlatforms是与meta.platforms互补的"明确禁止的平台"字段,同样来自 metaTypes 定义 中的badPlatforms = platforms;

安装非自由(unfree)的包

Nixpkgs 的用户都是自由软件用户,很多人(包括开发者)希望严格控制自己对非自由软件的暴露;与此同时,许多人确实需要(或希望)运行某些专有软件。Nixpkgs 为此也收录了一些非自由软件包的表达式。默认情况下非自由软件无法安装,也不会出现在搜索结果中

有几种方式可以调整 Nix 对非自由包的处理策略。

方式一:临时放行全部非自由包(环境变量)

$ export NIXPKGS_ALLOW_UNFREE=1

方式二:按谓词永久放行个别非自由包(allowUnfreePredicate)——保持默认阻止非自由包的同时,放行特定包。该选项是一个接收包为参数、返回布尔值的函数。下面这个配置接受包并恒返回 false(即不允许任何非自由包):

{ allowUnfreePredicate = (pkg: false); }

更有用的例子——只放行名为 roon-server 和 Visual Studio Code 的非自由包:

{ allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ "roon-server" "vscode" ]; }

方式三:按许可证清单放行/阻止(allowlistedLicenses 与 blocklistedLicenses)

下面的配置把amdwtfpl许可证加入允许清单:

{ allowlistedLicenses = with lib.licenses; [ amd wtfpl ]; }

下面的配置把gpl3Onlyagpl3Only许可证加入阻止清单:

{ blocklistedLicenses = with lib.licenses; [ agpl3Only gpl3Only ]; }

需要注意:allowlistedLicenses只作用于非自由许可证(除非同时启用allowUnfree),它并不是对所有许可证类型的通用白名单;而blocklistedLicenses作用于所有许可证。许可证的完整清单可在仓库的 lib/licenses/licenses.nix 中查阅。

在 check-meta.nix 的实现中,这些选项的优先级逻辑清晰可见:

  • hasDeniedUnfreeLicense(第 178 行起):当allowUnfree(含环境变量)为真时恒返回 false;否则依次考虑config.allowUnfreePackages(按lib.getName匹配的名字清单)与config.allowUnfreePredicate两个谓词,只要包名命中其中之一即放行;
  • 即使包被判定为 unfree,若nonEmptyAllowList && hasAllowlistedLicense attrs成立(即许可证在允许清单中)则仍可通过(第 372 行的checkValidity分支);
  • areLicenseListsValid(第 90 行)强制要求允许清单与阻止清单互斥,否则直接throw,从机制上杜绝了配置自相矛盾。

此外,config.nix 还提供了与allowUnfreePredicate互补的allowUnfreePackages选项(默认[ ]),它以加法方式合并,便于在多个模块中就近声明需要放行的非自由包,而无需集中声明或全局开启allowUnfree

安装不安全(insecure)的包

有几种方式可以调整 Nix 对被标记为不安全的包的处理策略。

方式一:临时放行全部不安全包(环境变量)

$ export NIXPKGS_ALLOW_INSECURE=1

方式二:按名字永久放行个别不安全包(permittedInsecurePackages)——下面的配置允许安装(假设的)不安全包hello1.2.3版本:

{ permittedInsecurePackages = [ "hello-1.2.3" ]; }

方式三:自定义安全策略(allowInsecurePredicate)——与allowUnfreePredicate类似,它是一个接收包并返回布尔值的函数。下面的配置放行ovftool包的任何版本:

{ allowInsecurePredicate = pkg: builtins.elem (lib.getName pkg) [ "ovftool" ]; }

注意:只有未指定allowInsecurePredicate时,permittedInsecurePackages才会被检查

源码层面的判定逻辑在 check-meta.nix 的hasDisallowedInsecure(第 202 行起):allowInsecure(环境变量)为真则全部放行;否则若配置了allowInsecurePredicate,则只放行谓词返回 true 的包;若配置了permittedInsecurePackages,则内部把它编译成一个elem (getNameWithVersion x) permittedInsecurePackages的谓词(注意这里匹配的是带版本的完整名字,如"hello-1.2.3");两者都未配置时,只要meta.knownVulnerabilities非空即拒绝。isMarkedInsecure(第 162 行)则定义了"不安全"的判定标准:attrs ? meta.knownVulnerabilities && attrs.meta.knownVulnerabilities != []

仓库测试 pkgs/test/config.nix 专门验证了一个回归场景:permittedInsecurePackages必须允许使用pkgs获取部分信息(该测试用builtins.seq pkgs'.glibc.version [ ]构造清单),确认了此选项在求值期间的可用性边界。

存在问题的包(Packages with problems)

一个包可能关联多个"问题(problem)"。问题既可以手动声明在meta.problems中,也可以根据包的其他meta属性自动生成。每个问题有一个名字、一种"kind"、一条消息,以及可选的 URL 列表。并非所有 kind 都能在meta.problems中手动指定,某些 kind 每个包至多只能出现一次。

目前已知的问题 kind 如下(未来还会预留更多):

  • "removal":该包计划在将来某个时间被移除。唯一(unique),每个包至多一个;
  • "deprecated":该包依赖的软件已到达生命周期终点(end of life);
  • "maintainerless":当meta.maintainers == []时自动生成。唯一,不可手动指定
  • "broken":当meta.broken = true时自动生成。

每个问题都有一个处理它的 handler,取值可为"error""warn""ignore""error"将禁止求值该包,"warn"则仅在日志中打印一条消息。

在 problems.nix 中,kinds属性集给出了每个 kind 的完整定义:例如maintainerless的自动生成条件是"meta.maintainersmeta.teams均为空、不是固定输出派生(FOD,即无outputHash)、且有meta.description"——后两个启发式条件用于避免误报 fetcher 等内部派生(文件注释明确说明:定义outputHash即 FOD,如 fetcher 的输出;未定义description的大概率不是包);broken的自动条件即meta.broken为真(且未被allowBroken/ 环境变量放行);removaldeprecated没有自动条件(automatic = null),只能手动声明。manualKinds/uniqueKinds则分别过滤出允许手动声明的 kind 与必须唯一的 kind,供meta.problems的类型校验使用。

为具体问题指定 handler

特定包、特定问题的 handler 可以用如下语法指定:

config.problems.handlers.${packageName}.${problemName} = "${handler}";

例如把hello包的broken问题降级为警告:

{ problems.handlers.hello.broken = "warn"; }

problems.handlers的配置结构在 problems.nix 的configOptions中定义(handlers = attrsOf (attrsOf handlerType)),其优先级高于problems.matchers

用 matchers 批量匹配问题

还可以指定通用 matcher,一次为多个包、多个问题设置 handler。这通过config.problems.matchers选项实现:

{ problems.matchers = [ # 任何即将被移除的包都直接构建失败 { kind = "removal"; handler = "error"; } # 使用没有声明维护者的包时给出警告 { kind = "maintainerless"; handler = "warn"; } # 你非常关心 hello 这个包,想绝对掌握它的任何问题 { package = "hello"; handler = "error"; } ]; }

matcher 可以匹配包名、问题名或问题 kind 中的一项或多项;如果设置了多个条件,则全部满足才匹配。如果多个 matcher 同时匹配一个问题,会选用最高严重级别的 handler。当前默认值中包含{ kind = "removal"; handler = "warn"; },即提前通知用户包的移除计划。

从 problems.nix 的实现看:

  • matcher 的packagenamekind三个字段均默认为null(不限制),handler必填;
  • 配置断言禁止同时设置packagename(提示改用problems.handlers);
  • config.nix 中实际注入的默认 matchers 为:{ kind = "broken"; handler = "error"; }{ kind = "removal"; handler = "warn"; },以及当旧选项showDerivationWarnings = [ "maintainerless" ]被设置时补充的 maintainerless 警告(并给出迁移提示);
  • genHandlerSwitch(第 358 行起)把所有matchers(优先级 0)与handlers(优先级 1)折叠成一个按kind → name → package三级分层的查找结构,匹配时取最高优先级与最高严重级别;handlerForProblem即为查询入口,handlers.levels定义了ignore < warn < error的严重级别次序;
  • processProblems(第 537 行起)把待处理问题按 handler 分组,warn组生成警告消息,error组生成拒绝求值的错误,并在 remediation 中提示如何通过problems.handlers放行。

包名的获取规则

无论problems.handlers还是problems.matchers,包名都取自lib.getName:它优先看pname,找不到时才从name属性中解析出 "pname" 部分。其定义位于 lib/strings.nix:

getName = let parse = drv: (parseDrvName drv).name; in x: if isString x then parse x else x.pname or (parse x.name);

因此getName "youtube-dl-2016.01.01"getName pkgs.youtube-dl都会得到"youtube-dl"。这正是配置里写problems.handlers.hello.broken(不带版本号)的原因。

packageOverrides修改包

可以在本地~/.config/nixpkgs/config.nix中定义packageOverrides函数来覆写 Nix 包。它必须是接收pkgs作为参数、返回一组修改后包的函数:

{ packageOverrides = pkgs: rec { foo = pkgs.foo.override { # ... }; }; }

pkgs.foo.override是 Nixpkgs 提供的能力覆写机制,常用于修改依赖、开关特性或更换编译器;配合packageOverrides即可把覆写固化到用户级配置中,对所有基于该配置的求值生效。

声明式包管理

利用packageOverrides,可以实现声明式包管理:把所有需要的包列在一份声明式 Nix 表达式里。

构建一个环境

例如,要在~/.config/nixpkgs/config.nix中声明 aspell、bc、ffmpeg、coreutils、gdb、nix、emscripten、jq、nox 和 silver-searcher:

{ packageOverrides = pkgs: with pkgs; { myPackages = pkgs.buildEnv { name = "my-packages"; paths = [ aspell bc coreutils gdb ffmpeg nix emscripten jq nox silver-searcher ]; }; }; }

安装进环境只需运行nix-env -iA nixpkgs.myPackages;若想从一份 nixpkgs 工作副本构建这些包,则运行nix-env -f . -iA myPackages

装完可以查看~/.nix-profile/里的内容,会发现安装了大量东西,有些有用、有些没必要。可以告诉 Nixpkgs 只链接我们想要的路径,通过pathsToLink实现:

{ packageOverrides = pkgs: with pkgs; { myPackages = pkgs.buildEnv { name = "my-packages"; paths = [ aspell bc coreutils gdb ffmpeg nix emscripten jq nox silver-searcher ]; pathsToLink = [ "/share" "/bin" ]; }; }; }

pathsToLink告诉 Nixpkgs 只链接列出的路径,从而去掉 profile 里的多余内容。/bin/share是用户环境的不错默认值。如果运行的是 macOS 上的 Nix,可能还想加上/Applications,让 GUI 应用可用。

获取文档

构建好新环境后,检查~/.nix-profile确保需要的都在。细心的读者会发现有些文件缺失:查看~/.nix-profile/share/man/man1/会发现没有任何 Nix 工具的 man page!这是因为有些包(如 Nix 本身)把文档等内容拆成了多个输出(output)。让我们把文档也装上:

{ packageOverrides = pkgs: with pkgs; { myPackages = pkgs.buildEnv { name = "my-packages"; paths = [ aspell bc coreutils ffmpeg nix emscripten jq nox silver-searcher ]; pathsToLink = [ "/share/man" "/share/doc" "/bin" ]; extraOutputsToInstall = [ "man" "doc" ]; }; }; }

extraOutputsToInstall指定除默认输出外还要安装的派生输出,这里补装了mandoc

不过,若想让 man 真正找到这些 man page,还需要配置环境。这件事同样可以在 Nix 表达式中管理:

{ packageOverrides = pkgs: { myProfile = pkgs.writeText "my-profile" '' export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin export MANPATH=$HOME/.nix-profile/share/man:/nix/var/nix/profiles/default/share/man:/usr/share/man ''; myPackages = pkgs.buildEnv { name = "my-packages"; paths = with pkgs; [ (runCommand "profile" { } '' mkdir -p $out/etc/profile.d cp ${myProfile} $out/etc/profile.d/my-profile.sh '') aspell bc coreutils ffmpeg man nix emscripten jq nox silver-searcher ]; pathsToLink = [ "/share/man" "/share/doc" "/bin" "/etc" ]; extraOutputsToInstall = [ "man" "doc" ]; }; }; }

要让这一切完整生效,还需要在登录时 source 这个脚本。可以往~/.profile里添加如下内容:

#!/bin/sh if [ -d "${HOME}/.nix-profile/etc/profile.d" ]; then for i in "${HOME}/.nix-profile/etc/profile.d/"*.sh; do if [ -r "$i" ]; then . "$i" fi done fi

然后运行. "${HOME}/.profile",就可以从环境中加载 man page 了。

GNU info 配置

配置 GNU info 比 man page 稍麻烦些:info 需要生成数据库才能正常工作。这可以通过对环境脚本做少量修改实现:

{ packageOverrides = pkgs: { myProfile = pkgs.writeText "my-profile" '' export PATH=$HOME/.nix-profile/bin:/nix/var/nix/profiles/default/bin:/sbin:/bin:/usr/sbin:/usr/bin export MANPATH=$HOME/.nix-profile/share/man:/nix/var/nix/profiles/default/share/man:/usr/share/man export INFOPATH=$HOME/.nix-profile/share/info:/nix/var/nix/profiles/default/share/info:/usr/share/info ''; myPackages = pkgs.buildEnv { name = "my-packages"; paths = with pkgs; [ (runCommand "profile" { } '' mkdir -p $out/etc/profile.d cp ${myProfile} $out/etc/profile.d/my-profile.sh '') aspell bc coreutils ffmpeg man nix emscripten jq nox silver-searcher texinfoInteractive ]; pathsToLink = [ "/share/man" "/share/doc" "/share/info" "/bin" "/etc" ]; extraOutputsToInstall = [ "man" "doc" "info" ]; postBuild = '' if [ -x $out/bin/install-info -a -w $out/share/info ]; then shopt -s nullglob for i in $out/share/info/*.info $out/share/info/*.info.gz; do $out/bin/install-info $i $out/share/info/dir done fi ''; }; }; }

postBuild告诉 Nixpkgs 在构建环境后运行一条命令。这里的install-info把安装的 info 页加入dir——GNU info 的默认根节点。注意texinfoInteractive被加入环境,正是为了提供install-info这个命令。

配置选项速查与测试验证

本文涉及的核心配置选项均定义于 pkgs/top-level/config.nix,汇总如下:

配置选项类型默认值作用
allowUnfreeboolfalse(或NIXPKGS_ALLOW_UNFREE=1是否允许非自由包
allowUnfreePredicate包 → bool未设置按谓词放行非自由包
allowUnfreePackageslistOf str[ ]按包名放行非自由包,可加法合并
allowlistedLicenses许可证列表[ ]允许清单中的许可证(仅作用于非自由许可)
blocklistedLicenses许可证列表[ ]阻止清单中的许可证(作用于所有许可)
allowBrokenboolfalse(或NIXPKGS_ALLOW_BROKEN=1是否允许损坏的包
allowUnsupportedSystemboolfalse(或NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1是否允许平台不支持的包
permittedInsecurePackageslistOf str未设置按"名称-版本"放行不安全包
allowInsecurePredicate包 → bool未设置按谓词放行不安全包(优先于上一项)
problems.handlersattrsOf (attrsOf handler){ }按"包名.问题名"指定 handler
problems.matchersmatcher 列表broken→error、removal→warn按 kind/name/package 批量指定 handler
packageOverridespkgs → pkgs'未设置覆写/扩展包集合

上述机制均有仓库测试覆盖:配置结构测试在 pkgs/test/config.nix(可用nix-build -A tests.config运行,该文件头部有明确说明);problems 机制在 pkgs/test/problems 下以"用例目录 + 期望 stderr"的形式逐场景验证(如allow-brokenallow-broken-envremoval-warnmaintainerless-warnpackage-name-matcher等,运行入口为nix-build -A tests.problems,problems.nix 文件头同样注明)。这些用例把每个 kind 的 error / warn / ignore 行为、环境变量放行路径以及 matcher 匹配规则都固化为可回归的测试,是理解与验证本文所述行为的最终参考。

【免费下载链接】nixpkgsNix Packages collection & NixOS项目地址: https://gitcode.com/GitHub_Trending/ni/nixpkgs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 1:36:48

MATLAB直接序列扩频DSSS仿真:处理增益与干扰容限分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 1:34:28

使用 Fleet 按漏洞过滤软件:按严重级别与已知利用优先修复补丁

使用 Fleet 按漏洞过滤软件&#xff1a;按严重级别与已知利用优先修复补丁 【免费下载链接】fleet Open device management 项目地址: https://gitcode.com/GitHub_Trending/fl/fleet Fleet 从 4.56 版本开始提供按漏洞过滤软件的能力&#xff0c;允许管理员在软件清单中…

作者头像 李华
网站建设 2026/9/20 1:33:40

计算机测配色从建库到配方:K/S值、色差公式与盲打命中率

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华