In a setup with two Nix Flakes, where one provides a plugin for the other's application, "path <…> is not valid". How to fix that?

huangapple go评论74阅读模式
英文:

In a setup with two Nix Flakes, where one provides a plugin for the other's application, "path <…> is not valid". How to fix that?

问题

我有两个 Nix Flakes:一个包含一个应用程序,另一个包含该应用程序的插件。当我使用插件构建应用程序时,我遇到了以下错误:

error: path '/nix/store/3b7djb5pr87zbscggsr7vnkriw3yp21x-mainapp-go-modules' is not valid

我不知道这个错误的含义以及如何修复它,但我可以在 macOS 和 Linux 上重现这个错误。问题中提到的路径是由 buildGoModule 的第一步生成的 vendor 目录。

要重现这个错误,需要一堆文件,所以我提供了一个带有注释的 bash 脚本,你可以在一个空文件夹中执行它来重新创建我的设置:

#!/bin/bash

# 我有两个 flakes:主应用程序和一个插件。
# mainapp 需要在插件目录中,这样 nix 才不会抱怨路径:mainapp
# 引用超出了父级的根目录。
mkdir -p plugin/mainapp

# 每个都是一个具有最小设置的 go 模块
tee plugin/mainapp/go.mod &lt;&lt;EOF &gt;/dev/null
module example.com/mainapp

go 1.16
EOF
tee plugin/go.mod &lt;&lt;EOF &gt;/dev/null
module example.com/plugin

go 1.16
EOF

# 每个都包含最小的 Go 代码
tee plugin/mainapp/main.go &lt;&lt;EOF &gt;/dev/null
package main

import &quot;fmt&quot;

func main() {
	fmt.Println(&quot;Hello, World!&quot;)
}
EOF
tee plugin/main.go &lt;&lt;EOF &gt;/dev/null
package plugin

import log

func init() {
	fmt.Println(&quot;initializing plugin&quot;)
}
EOF

# mainapp 是一个 flake,提供了一个构建应用程序的函数,
# 以及一个默认包,即没有任何插件的应用程序。
tee plugin/mainapp/flake.nix &lt;&lt;&#39;EOF&#39; &gt;/dev/null
{
  description = &quot;main application&quot;;
  inputs = {
    nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
    flake-utils.url = github:numtide/flake-utils;
  };
  outputs = {self, nixpkgs, flake-utils}:
  let
    # buildApp 从插件列表构建应用程序。
    # 插件会导致 vendorSha256 发生变化,因此它作为附加参数给出。
    buildApp = { pkgs, vendorSha256, plugins ? [] }:
      let
        # 这将附加到 mainapp 的 go.mod 中,以便它
        # 知道插件以及在哪里找到它。
        requirePlugin = plugin: &#39;&#39;
          require ${plugin.goPlugin.goModName} v0.0.0
          replace ${plugin.goPlugin.goModName} =&gt; ${plugin.outPath}/src
        &#39;&#39;;
        # 由于 buildGoModule 两次使用源代码 -
        # 首先用于 vendoring,然后用于构建 -
        # 我们对源代码进行必要的修改,
        # 然后将其传递给 buildGoModule。
        sources = pkgs.stdenvNoCC.mkDerivation {
          name = &quot;mainapp-with-plugins-source&quot;;
          src = self;
          phases = [ &quot;unpackPhase&quot; &quot;buildPhase&quot; &quot;installPhase&quot; ];
		  # 编写一个 plugins.go 文件,通过引用插件的包来引用插件
		  #   _ = "&lt;module path&gt;"
          PLUGINS_GO = &#39;&#39;
            package main
              
            // Code generated by Nix. DO NOT EDIT.
          
            import (
            	${builtins.foldl&#39; (a: b: a + &quot;\n\t_ = \&quot;${b.goPlugin.goModName}\&quot;&quot;) &quot;&quot; plugins}
            )
          &#39;&#39;;
          GO_MOD_APPEND = builtins.foldl&#39; (a: b: a + &quot;${requirePlugin b}\n&quot;) &quot;&quot; plugins;
          buildPhase = &#39;&#39;
            printenv PLUGINS_GO &gt;plugins.go
            printenv GO_MOD_APPEND &gt;&gt;go.mod
          &#39;&#39;;
          installPhase = &#39;&#39;
            mkdir -p $out
            cp -r -t $out *
          &#39;&#39;;
        };
      in pkgs.buildGoModule {
        name = &quot;mainapp&quot;;
        src = builtins.trace &quot;sources at ${sources}&quot; sources;
        inherit vendorSha256;
        nativeBuildInputs = plugins;
      };
  in (flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in rec {
      defaultPackage = buildApp {
        inherit pkgs;
		# 这可能因你的 nixpkgs 而异,如果是这样,请更改它。
        vendorSha256 = &quot;sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=&quot;;
      };
    }
  )) // {
    lib = {
      inherit buildApp;
	  # 解析 go.mod 文件以获取模块名称的辅助函数
      pluginMetadata = goModFile: {
        goModName = with builtins; head
          (match &quot;module ([^[:space:]]+).*&quot; (readFile goModFile));
      };
    };
  };
}
EOF

# 插件是一个依赖于 mainapp 的 flake,输出一个插件包,
# 以及使用此插件编译的 mainapp 包。
tee plugin/flake.nix &lt;&lt;&#39;EOF&#39; &gt;/dev/null
{
  description = &quot;mainapp plugin&quot;;
  inputs = {
    nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
    flake-utils.url = github:numtide/flake-utils;
    nix-filter.url = github:numtide/nix-filter;
    mainapp.url = path:mainapp;
    mainapp.inputs = {
      nixpkgs.follows = &quot;nixpkgs&quot;;
      flake-utils.follows = &quot;flake-utils&quot;;
    };
  };
  outputs = {self, nixpkgs, flake-utils, nix-filter, mainapp}:
  flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in rec {
      packages = rec {
        plugin = pkgs.stdenvNoCC.mkDerivation {
          pname = &quot;mainapp-plugin&quot;;
          version = &quot;0.1.0&quot;;
          src = nix-filter.lib.filter {
            root = ./.;
            exclude = [ ./mainapp ./flake.nix ./flake.lock ];  
          };
          # 为了让 mainapp 将其识别为插件所需
          passthru.goPlugin = mainapp.lib.pluginMetadata ./go.mod;
          phases = [ &quot;unpackPhase&quot; &quot;installPhase&quot; ];
          installPhase = &#39;&#39;
            mkdir -p $out/src
            cp -r -t $out/src *
          &#39;&#39;;
        };
        app = mainapp.lib.buildApp {
          inherit pkgs;
		  # 这可能因你的 nixpkgs 而异,如果是这样,请更改它。
          vendorSha256 = &quot;sha256-a6HFGFs1Bu9EkXwI+DxH5QY2KBcdPzgP7WX6byai4hw=&quot;;
          plugins = [ plugin ];
        };
      };
      defaultPackage = packages.app;
    }
  );
}
EOF

要重现错误,需要安装支持 Flake 的 Nix。在此脚本创建的 plugin 文件夹中,执行以下命令:

$ nix build
trace: sources at /nix/store/d5arinbiaspyjjc4ypk4h5dsjx22pcsf-mainapp-with-plugins-source
error: path '/nix/store/3b7djb5pr87zbscggsr7vnkriw3yp21x-mainapp-go-modules' is not valid

(如果出现哈希不匹配的情况,请使用正确的哈希更新 flakes;我不确定在将 flakes 扩展到存储库之外时,哈希是否可重现。)

源代码目录(在跟踪中显示)确实存在,并且看起来没问题。错误消息中给出的路径也存在,并且包含了预期内容的 modules.txt

mainapp 文件夹中,nix build 可以成功运行,这将构建没有插件的应用程序。那么,我在插件中做了什么导致路径无效?

英文:

I have two Nix Flakes: One contains an application, and the other contains a plugin for that application. When I build the application with the plugin, I get the error

error: path &#39;/nix/store/3b7djb5pr87zbscggsr7vnkriw3yp21x-mainapp-go-modules&#39; is not valid

I have no idea what this error means and how to fix it, but I can reproduce it on both macOS and Linux. The path in question is the vendor directory generated by the first step of buildGoModule.

The minimal setup to reproduce the error requires a bunch of files, so I provide a commented bash script that you can execute in an empty folder to recreate my setup:

#!/bin/bash

# I have two flakes: the main application and a plugin.
# the mainapp needs to be inside the plugin directory
# so that nix doesn&#39;t complain about the path:mainapp
# reference being outside the parent&#39;s root.
mkdir -p plugin/mainapp

# each is a go module with minimal setup
tee plugin/mainapp/go.mod &lt;&lt;EOF &gt;/dev/null
module example.com/mainapp

go 1.16
EOF
tee plugin/go.mod &lt;&lt;EOF &gt;/dev/null
module example.com/plugin

go 1.16
EOF

# each contain minimal Go code
tee plugin/mainapp/main.go &lt;&lt;EOF &gt;/dev/null
package main

import &quot;fmt&quot;

func main() {
	fmt.Println(&quot;Hello, World!&quot;)
}
EOF
tee plugin/main.go &lt;&lt;EOF &gt;/dev/null
package plugin

import log

func init() {
	fmt.Println(&quot;initializing plugin&quot;)
}
EOF

# the mainapp is a flake that provides a function for building
# the app, as well as a default package that is the app
# without any plugins.
tee plugin/mainapp/flake.nix &lt;&lt;&#39;EOF&#39; &gt;/dev/null
{
  description = &quot;main application&quot;;
  inputs = {
    nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
    flake-utils.url = github:numtide/flake-utils;
  };
  outputs = {self, nixpkgs, flake-utils}:
  let
    # buildApp builds the application from a list of plugins.
    # plugins cause the vendorSha256 to change, hence it is
    # given as additional parameter.
    buildApp = { pkgs, vendorSha256, plugins ? [] }:
      let
        # this is appended to the mainapp&#39;s go.mod so that it
        # knows about the plugin and where to find it.
        requirePlugin = plugin: &#39;&#39;
          require ${plugin.goPlugin.goModName} v0.0.0
          replace ${plugin.goPlugin.goModName} =&gt; ${plugin.outPath}/src
        &#39;&#39;;
        # since buildGoModule consumes the source two times –
        # first for vendoring, and then for building –
        # we do the necessary modifications to the sources in an
        # own derivation and then hand that to buildGoModule.
        sources = pkgs.stdenvNoCC.mkDerivation {
          name = &quot;mainapp-with-plugins-source&quot;;
          src = self;
          phases = [ &quot;unpackPhase&quot; &quot;buildPhase&quot; &quot;installPhase&quot; ];
		  # write a plugins.go file that references the plugin&#39;s package via
		  #   _ = &quot;&lt;module path&gt;&quot;
          PLUGINS_GO = &#39;&#39;
            package main
              
            // Code generated by Nix. DO NOT EDIT.
          
            import (
            	${builtins.foldl&#39; (a: b: a + &quot;\n\t_ = \&quot;${b.goPlugin.goModName}\&quot;&quot;) &quot;&quot; plugins}
            )
          &#39;&#39;;
          GO_MOD_APPEND = builtins.foldl&#39; (a: b: a + &quot;${requirePlugin b}\n&quot;) &quot;&quot; plugins;
          buildPhase = &#39;&#39;
            printenv PLUGINS_GO &gt;plugins.go
            printenv GO_MOD_APPEND &gt;&gt;go.mod
          &#39;&#39;;
          installPhase = &#39;&#39;
            mkdir -p $out
            cp -r -t $out *
          &#39;&#39;;
        };
      in pkgs.buildGoModule {
        name = &quot;mainapp&quot;;
        src = builtins.trace &quot;sources at ${sources}&quot; sources;
        inherit vendorSha256;
        nativeBuildInputs = plugins;
      };
  in (flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in rec {
      defaultPackage = buildApp {
        inherit pkgs;
		# this may be different depending on your nixpkgs; if it is, just change it.
        vendorSha256 = &quot;sha256-pQpattmS9VmO3ZIQUFn66az8GSmB4IvYhTTCFn6SUmo=&quot;;
      };
    }
  )) // {
    lib = {
      inherit buildApp;
	  # helper that parses a go.mod file for the module&#39;s name
      pluginMetadata = goModFile: {
        goModName = with builtins; head
          (match &quot;module ([^[:space:]]+).*&quot; (readFile goModFile));
      };
    };
  };
}
EOF

# the plugin is a flake depending on the mainapp that outputs a plugin package,
# and also a package that is the mainapp compiled with this plugin.
tee plugin/flake.nix &lt;&lt;&#39;EOF&#39; &gt;/dev/null
{
  description = &quot;mainapp plugin&quot;;
  inputs = {
    nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
    flake-utils.url = github:numtide/flake-utils;
    nix-filter.url = github:numtide/nix-filter;
    mainapp.url = path:mainapp;
    mainapp.inputs = {
      nixpkgs.follows = &quot;nixpkgs&quot;;
      flake-utils.follows = &quot;flake-utils&quot;;
    };
  };
  outputs = {self, nixpkgs, flake-utils, nix-filter, mainapp}:
  flake-utils.lib.eachDefaultSystem (system:
    let
      pkgs = nixpkgs.legacyPackages.${system};
    in rec {
      packages = rec {
        plugin = pkgs.stdenvNoCC.mkDerivation {
          pname = &quot;mainapp-plugin&quot;;
          version = &quot;0.1.0&quot;;
          src = nix-filter.lib.filter {
            root = ./.;
            exclude = [ ./mainapp ./flake.nix ./flake.lock ];  
          };
          # needed for mainapp to recognize this as plugin
          passthru.goPlugin = mainapp.lib.pluginMetadata ./go.mod;
          phases = [ &quot;unpackPhase&quot; &quot;installPhase&quot; ];
          installPhase = &#39;&#39;
            mkdir -p $out/src
            cp -r -t $out/src *
          &#39;&#39;;
        };
        app = mainapp.lib.buildApp {
          inherit pkgs;
		  # this may be different depending on your nixpkgs; if it is, just change it.
          vendorSha256 = &quot;sha256-a6HFGFs1Bu9EkXwI+DxH5QY2KBcdPzgP7WX6byai4hw=&quot;;
          plugins = [ plugin ];
        };
      };
      defaultPackage = packages.app;
    }
  );
}
EOF

You need Nix with Flake support installed to reproduce the error.
In the plugin folder created by this script, execute

$ nix build
trace: sources at /nix/store/d5arinbiaspyjjc4ypk4h5dsjx22pcsf-mainapp-with-plugins-source
error: path &#39;/nix/store/3b7djb5pr87zbscggsr7vnkriw3yp21x-mainapp-go-modules&#39; is not valid

(If you get hash mismatches, just update the flakes with the correct hash; I am not quite sure whether hashing when spreading flakes outside of a repository is reproducible.)

The sources directory (shown by trace) does exist and looks okay. The path given in the error message also exists and contains modules.txt with expected content.

In the folder mainapp, nix build does run successfully, which builds the app without plugins. So what is it that I do with the plugin that makes the path invalid?

答案1

得分: 0

原因是作为供应商的一部分生成的文件modules.txt在这种情况下将包含replace指令中的Nix存储路径。vendor目录是一个固定的输出派生物,因此不能依赖于任何其他派生物。这违反了modules.txt中的引用。

只有通过将插件的源代码复制到sources派生物中才能修复此问题-这样,replace路径可以是相对路径,因此不引用任何其他Nix存储路径。

英文:

The reason is that the file modules.txt generated as part of vendoring will contain the nix store path in the replace directive in this scenario. The vendor directory is a fixed output derivation and thus must not depend on any other derivations. This is violated by the reference in modules.txt.

This can only be fixed by copying the plugin's sources into the sources derivation – that way, the replace path can be relative and thus references no other nix store path.

huangapple
  • 本文由 发表于 2022年1月2日 23:35:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/70557296.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定