Ok, so I finally understand what’s happening: grpc-tools is originally a C++ program, but it was packaged in nodejs with a trivial program that downloads pre-built C++ binaries. Nix does not like this, so they packaged grpc-tools themself starting from the C++ code directly. So my first trick was to remove grpc-tools from the package.json and include grpc-tools
in the native build inputs. The second trick was to realize that nix forgot to also create the binary packages created by npm (these are trivial wrappers grpc-node/packages/grpc-tools/bin/protoc.js at 179dbfaeccc19bce786788a6b8e986990ab51329 · grpc/grpc-node · GitHub), so I manually added them like:
diff --git a/pkgs/by-name/gr/grpc-tools/package.nix b/pkgs/by-name/gr/grpc-tools/package.nix
index 5bf09b7f0b94..221727e5296a 100644
--- a/pkgs/by-name/gr/grpc-tools/package.nix
+++ b/pkgs/by-name/gr/grpc-tools/package.nix
@@ -25,6 +25,17 @@ stdenv.mkDerivation rec {
installPhase = ''
install -Dm755 -t $out/bin grpc_node_plugin
install -Dm755 -t $out/bin deps/protobuf/protoc
+ # The node script creates two additional binaries that just forward their inputs to the above programs,
+ # but it seems unnecessary to install node etc just for this. So let's fake it using regular bash.
+ # https://github.com/grpc/grpc-node/blob/179dbfaeccc19bce786788a6b8e986990ab51329/packages/grpc-tools/package.json#L19-L21
+ cat >$out/bin/grpc_tools_node_protoc <<EOL
+ #/usr/bin/env bash
+ $out/bin/protoc --plugin=protoc-gen-grpc=$out/bin/grpc_node_plugin "\$@"
+ EOL
+ chmod +x $out/bin/grpc_tools_node_protoc
+ # grpc_tools_node_protoc_plugin seems to literally be the same as grpc_node_plugin
+ # except with a node wrapper
+ ln -s $out/bin/grpc_node_plugin $out/bin/grpc_tools_node_protoc_plugin
'';
So I finally managed to get a fully working robotframework-browser packaged purely in nix:
{
lib,
stdenv,
buildPythonPackage,
fetchPypi,
pythonOlder,
robotframework,
robotframework-pythonlibcore,
robotframework-assertion-engine,
grpcio,
protobuf,
overrides,
click,
seedir,
wrapt,
npmHooks,
nodejs_latest,
fetchNpmDeps,
fetchFromGitHub,
node-pre-gyp,
grpc-tools,
grpcio-tools, # needed to generate --grpc_python_out + imported
# For inv build
invoke, # cmake-like for python
mypy-protobuf,
robotstatuschecker,
pytest,
beautifulsoup4,
# To provide the binaries
playwright-driver,
}:
let
version = "19.6.0";
pname = "robotframework-browser";
src_orig = fetchFromGitHub {
owner = "MarketSquare";
repo = "robotframework-browser";
rev = "v${version}";
hash = "sha256-kOifD+Fa1WCYbNZSRX89FlZio9oJ7Rh6aXQGVmVPXRI=";
};
src = stdenv.mkDerivation {
inherit pname;
inherit version;
src = src_orig;
patches = [
# Cf details on the above identical patch
./pin_playwright_to_nix_playwright_driver_version.diff
];
buildPhase = "";
installPhase = ''
mkdir -p $out
cp -Ra . $out
cat $out/package.json
'';
};
in
buildPythonPackage rec {
inherit pname;
inherit version;
disabled = pythonOlder "3.9";
nativeBuildInputs = [
#importNpmLock.npmConfigHook
npmHooks.npmConfigHook
# npmHooks.npmInstallHook
nodejs_latest
node-pre-gyp
grpc-tools
# For inv build
invoke # cmake-like for python
grpcio-tools # needed to generate --grpc_python_out
mypy-protobuf
robotstatuschecker
pytest
beautifulsoup4
];
npmDeps = fetchNpmDeps {
inherit src;
hash = "sha256-5EYZ5t24aPWM12nGbBBu1nwb5QS27eWPCXT76JMIpoA="; # after patch
};
inherit src;
# We fake a run of rfbrowser init
# + grpc_tools_node_protoc already includes the plugin and is not runnable via npm
# since nix directly builds it from C++ sources to avoid to use pre-build binaries
# downloaded by npm
patchPhase = ''
runHook prePatch
substituteInPlace ./Browser/playwright.py \
--replace-fail '(installation_dir / "node_modules").is_dir()' 'True'
substituteInPlace ./tasks.py \
--replace-fail 'c.run("pip install -U pip")' 'return' \
--replace-fail 'npm run grpc_tools_node_protoc' 'grpc_tools_node_protoc' \
--replace-fail ' -- ' ' '
substituteInPlace ./package.json \
--replace-fail '"grpc-tools": "^1.13.0",' ' '
sed -i '/--plugin=protoc-gen-grpc/d' ./tasks.py
runHook postPatch
'';
dependencies = [
robotframework
robotframework-pythonlibcore
robotframework-assertion-engine
grpcio
grpcio-tools
protobuf
overrides
click
seedir
wrapt
];
preBuild = ''
inv build -d -e
'';
# makeWrapperArgs can't be used (not a script here but a library)
postInstall = ''
cp -r node_modules $out/lib
cat >> $out/lib/python3.13/site-packages/Browser/__init__.py <<EOF
import os
if not "PLAYWRIGHT_BROWSERS_PATH" in os.environ:
os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "${playwright-driver.browsers}"
if not "PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS" in os.environ:
os.environ["PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS"] = "true"
EOF
'';
meta = with lib; {
description = "Robot Framework Browser library powered by Playwright. ";
homepage = "https://robotframework-browser.org/";
license = licenses.asl20;
maintainers = with maintainers; [ tobiasBora ];
};
}
Next step is to see if I can get a cleaner solution to synchronize the version of pkgs.playwright-driver.browsers
and the playwright version used in robotframework… and do a PR eventually!