【问题标题】:How to replace paths to executables in source code with Nix that are not in PATH如何用不在 PATH 中的 Nix 替换源代码中可执行文件的路径
【发布时间】:2017-12-15 00:21:54
【问题描述】:

我希望编写一些调用可执行文件的 Haskell 作为其工作的一部分;并将其安装在 nixOS 主机上。我不希望可执行文件在我的 PATH 中(并且依赖它会破坏 nix 的漂亮依赖模型)。

如果这是一个 Perl 脚本,我将有一个简单的构建器来查找特定格式的字符串,并根据 .nix 文件中声明的依赖关系将它们替换为可执行文件名称。但是对于 haskell 常见的基于阴谋集团的建筑,这似乎有点困难。

在 nix 上的 Haskell 代码中,是否有用于在构建时(包括开发期间以及安装时)对可执行文件路径进行编码的标准习惯用法?

为了一个具体的例子,这里有一个简单的“脚本”:

import System.Process ( readProcess )

main = do
  stdout <- readProcess "hostname" [] ""
  putStrLn $ "Hostname: " ++ stdout

我希望能够编译运行它(原则上)而不依赖于 PATH 中的主机名,而是用完整的 /nix/store/-inetutils-/bin/hostname 路径替换主机名,因此也在 nix 下获得依赖管理的好处。

这可以通过使用 shell(或类似)脚本来管理,该脚本使用上述定义的替换方案构建,该脚本设置了 haskell 可执行文件所期望的环境;但这仍然需要通过 cabal.mkDerivation 进行一些引导,并且由于我是 OptParse-Applicative 的 bash 完成的爱好者,所以我不愿意用另一个脚本来减慢它,每次我点击 tab 键时都会启动它。但如果这是我们需要的,那就够公平了。

我确实查看了 cabal.mkDerivation 以了解某种预构建步骤,但如果它在那里,我看不到它。

谢谢,

【问题讨论】:

  • 所有 Linux 发行版中的可执行文件不需要在路径中吗? (无论是否)
  • 不,如果您使用可执行文件的完整(绝对)名称,则不会。路径只是一种方便。
  • 你说得对,我完全忘记了绝对路径。
  • 我对@9​​87654322@ 的(初步)理解是它应该自己处理特定程序的环境设置。为什么要在 Haskell 中重新实现该逻辑?换句话说,您说您有“.nix 文件中声明的依赖项” - nix 可以在程序执行期间将这些依赖项添加到 PATH 中吗? (如果对nix 的这种理解完全错误,请随意忽略......)
  • nix 将在程序的 build 期间处理设置环境,但在程序安装后的 runtime 期间没有什么特别的事情发生(我相信您可以通过比较 env 的字符串输出(其中包含构建期间使用的字符串)与运行 env 的输出来证明这一点)。所以我的目标是将构建阶段使用的字符串(代表我希望运行的依赖可执行文件)放入可执行文件(或者可能是可执行文件拥有的固定位置的数据文件),以便它可以读取并调用它们,并且依赖项将被记录。

标签: haskell executable nix nixos


【解决方案1】:

假设您在 Nix 中构建 Haskell 应用程序,您可以通过您的 Nix 表达式修补配置文件。有关如何执行此操作的示例,请查看 this small project

关键是你可以定义一个postConfigure钩子,比如this

pkgs.haskell.lib.overrideCabal yourProject (old: {
  postConfigure = ''
    substituteInPlace src/Configuration.hs --replace 'helloPrefix = Nothing' 'helloPrefix = Just "${pkgs.hello}"'
  '';
})

【讨论】:

  • 非常感谢,罗伯特。使用那个例子,特别是关于 postConfigure 的使用,我确实有一个可行的解决方案。我做了一些更改,以便我可以在开发中(例如,在 cabal repl 下)以及在构建中使用 nix 路径。
【解决方案2】:

我在 nix1 中使用我的 xmonad 构建所做的是将可执行路径引用为诸如 @@compton@@/bin/compton 之类的东西。然后我使用这样的脚本来生成我的 default.nix 文件:

#!/usr/bin/env bash

set -eu

packages=($(grep '@@[^@]*@@' src/Main.hs | sed -e 's/.*@@\(.*\)@@.*/\1/' | sort -u))

extra_args=()
for p in "${packages[@]}"; do
    extra_args+=(--extra-arguments "$p")
done

cabal2nix . "${extra_args[@]}" \
    | head -n-1

echo "  patchPhase = ''";
echo "    substituteInPlace src/Main.hs \\"
for p in "${packages[@]}"; do
    echo "      --replace '@@$p@@' '\${$p}' \\"
done
echo "  '';"

echo "}"

它的作用是通过src/Main.hs grep(可以很容易地更改为查找所有haskell 文件或某些特定配置模块)并挑选出所有@@ 包围的标签,例如@@some-package-name@@。然后它对它们做了两件事:

  1. 将它们作为它生成的 nix 表达式的额外参数传递给 cabal2nix
  2. 对来自 cabal2nix 的 nix 表达式输出进行后处理以添加补丁阶段,该阶段将 Haskell 源文件中的 @@some-package-name@@ 标记替换为派生的实际路径。2

这会生成一个像这样的 nix 表达式:

{ mkDerivation, base, compton, networkmanagerapplet, notify-osd
, powerline, setxkbmap, stdenv, synapse, system-config-printer
, taffybar, udiskie, unix, X11, xmonad, xmonad-contrib
}:
mkDerivation {
  pname = "xmonad-custom";
  version = "0.0.0.0";
  src = ./.;
  isLibrary = false;
  isExecutable = true;
  executableHaskellDepends = [
    base taffybar unix X11 xmonad xmonad-contrib
  ];
  description = "My XMonad build";
  license = stdenv.lib.licenses.bsd3;
  patchPhase = ''
    substituteInPlace src/Main.hs \
      --replace '@@compton@@' '${compton}' \
      --replace '@@networkmanagerapplet@@' '${networkmanagerapplet}' \
      --replace '@@notify-osd@@' '${notify-osd}' \
      --replace '@@powerline@@' '${powerline}' \
      --replace '@@setxkbmap@@' '${setxkbmap}' \
      --replace '@@synapse@@' '${synapse}' \
      --replace '@@system-config-printer@@' '${system-config-printer}' \
      --replace '@@udiskie@@' '${udiskie}' \
  '';
}

最终结果是我可以只编写 Haskell 代码和一个 cabal 包文件;我也不必太担心维护 nix 包文件,只有在我的依赖项发生变化时才重新运行我的 generate-nix 脚本。

在我的 Haskell 代码中,我只是编写可执行文件的路径,就好像 @@the-nix-package-name@@ 是安装该软件包的文件夹的绝对路径,并且一切都可以神奇地工作。

安装的 xmonad 二进制文件最终包含对我调用的可执行文件的绝对路径的硬编码引用,这就是 nix 喜欢的工作方式(例如,这意味着它会在垃圾收集期间自动知道依赖关系)。而且我不必担心将我调用的内容保留在我的交互式环境的PATH 中,或者维护一个为这个可执行文件设置PATH 的包装器。


1 我将它设置为一个 cabal 项目,该项目被构建并安装到 nix 商店中,而不是让它从 ~/.xmonad/xmonad.hs 动态重新编译自己

2 第 2 步是一个小元数据,因为我使用 bash 脚本生成带有嵌入式 bash 脚本的 nix 代码

【讨论】:

    【解决方案3】:

    这不是答案,但如果我在评论部分发布它,它的格式会很丑。

    此外,我不确定这种 hack 是否是完成这项工作的正确方法。

    我注意到,如果我使用 nix-shell,我可以获得 nix 商店的完整路径

    假设哈希值始终相同,我相信它是 AFAIK,您可以使用它在构建配方中进行硬编码。

    $ which bash
    /run/current-system/sw/bin/bash
    [wizzup@ ~] 
    $ nix-shell -p bash
    
    [nix-shell:~]$ which bash
    /nix/store/wb34dgkpmnssjkq7yj4qbjqxpnapq0lw-bash-4.4-p12/bin/bash
    

    最后,如果你使用buildInput,我怀疑你是否必须这样做,它应该是相同的路径。

    【讨论】:

    • 谢谢 wizzup,但这与我的问题有点相干——我确实可以在构建阶段从环境中找到完整路径,它是存储它们的位置/方式,以便在运行时可执行文件可以检索他们在骗我。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-06
    • 1970-01-01
    • 2018-12-12
    • 2016-09-15
    • 1970-01-01
    • 2020-11-25
    • 1970-01-01
    相关资源
    最近更新 更多