From 42c1194f0a8a0d41a9145dbd7b1f012db9723ea3 Mon Sep 17 00:00:00 2001 From: Ihar Hrachyshka Date: Wed, 24 Sep 2025 17:22:36 -0400 Subject: [PATCH] feat: Use nixpkgs to resolve `with` liveness Since 7683220f we always mark nested `with` expressions alive. This is technically sound because, indeed, there's no general way to determine whether a particular attribute comes from one of the nested `with`s. Sadly, this change made it impossible to detect common problematic patterns in nixpkgs tree like: ``` meta = with lib; { maintainers = with lib.maintainers; [ ... ]; }; ``` This would be very useful in our ongoing attempt to automate cleanup of these patterns from the whole `nixpkgs` tree, see: https://github.com/technowledgy/refactor-tractor/pull/14 This change attempts to address this problem. It is observed that nixd completion controllers already use nixpkgs attrset and heuristics to look for known attributes in `pkgs` and `lib`, assuming these belong to nixpkgs. We can do the same when analysing `with` scopes. 1. This patch reuses the existing nixpkgs client infrastructure for `Sema/VariableLookup.cpp`. Since variable analysis is part of `libnixf` while nixpkgs client and LSP are part of `nixd`, we have to relocate a number of modules plus the `nixd-attrset-eval` tool outside the `nixd` directory under `common`. 2. The `nixd-attrset-eval` tool was truncating the number of returned values by 30, which is useful for interactive use in completion helper context, but is not enough for comprehensive variable scope analysis. For this reason, the json protocol for the tool was extended to support `MaxItems` attribute that can be set to `0` to indicate that no truncation should occur. 3. On performance optimizations: The new nixpkgs client is initialized once per call to `VLA`. It will load and cache discovered attributes as needed for the `with` resolution procedure. (In the future, it may be used for other needs as needed.) Note: It could be beneficial to allow reuse of the same nixpkgs client with its cached results across multiple calls to `VLA`. This is left for a future exercise, if and when consuming tools like `nixd-tidy` gain support for multi-file operation. 4. On build system: Because the project supports "global" meson build from the root directory as well as component specific builds for `libnixf` and `nixd`, we have to setup scaffolding to reuse the `common` library in both. This is achieved with subprojects and a bunch of symlinks. 5. Because `libnixf` / `nixd-tiny` can be used interactively, and each call to `nixd-attrset-eval` is logged in extreme details (including complete json protocol comms between the tool and its driver), we have to suppress this output not to pollute the caller's stdout with these debug messages. Some adjustments were done to allow to disable these log messages when the tool is called from `VLA` nixpkgs client. 6. VLA test timeout is bumped from 30s to 120s because it now has to run nixd-attrset-eval. The Sema test now takes time comparable to nixd regression suite for this reason. 120s is the timeout taken from nixd regression suite. --- .../include/nixd/CommandLine/Options.h | 0 .../include/nixd/Eval/AttrSetClient.h | 4 + .../include/nixd/Eval/AttrSetProvider.h | 0 common/include/nixd/Eval/Spawn.h | 15 +++ .../include/nixd/Protocol/AttrSet.h | 1 + .../include/nixd/Support/AutoCloseFD.h | 0 .../include/nixd/Support/ForkPiped.h | 0 .../include/nixd/Support/PipedProc.h | 0 .../include/nixd/Support/StreamProc.h | 0 {nixd => common}/lib/CommandLine/Options.cpp | 0 {nixd => common}/lib/Eval/AttrSetClient.cpp | 0 {nixd => common}/lib/Eval/AttrSetProvider.cpp | 10 +- common/lib/Eval/Spawn.cpp | 24 +++++ {nixd => common}/lib/Protocol/AttrSet.cpp | 12 ++- {nixd => common}/lib/Support/AutoCloseFD.cpp | 0 {nixd => common}/lib/Support/ForkPiped.cpp | 0 {nixd => common}/lib/Support/StreamProc.cpp | 0 common/meson.build | 84 +++++++++++++++ common/subprojects/libnixt | 1 + common/subprojects/nixd-lspserver | 1 + common/tools/meson.build | 11 ++ {nixd => common}/tools/nixd-attrset-eval.cpp | 0 flake.nix | 4 +- libnixf/default.nix | 8 +- libnixf/include/nixf/Basic/Nodes/Expr.h | 24 +++++ libnixf/include/nixf/Sema/VariableLookup.h | 19 ++++ libnixf/meson.build | 2 + libnixf/src/Sema/VariableLookup.cpp | 102 +++++++++++++++++- libnixf/src/meson.build | 6 +- libnixf/subprojects/common | 1 + libnixf/test/Sema/VariableLookup.cpp | 64 +++++++++++ libnixf/test/meson.build | 24 ++++- libnixt/lib/meson.build | 2 + meson.build | 95 ++-------------- nixd/include/nixd/Eval/Launch.h | 2 +- nixd/lib/Eval/Launch.cpp | 6 +- nixd/lib/meson.build | 9 +- nixd/lspserver/include/lspserver/Connection.h | 10 ++ nixd/lspserver/include/lspserver/LSPServer.h | 22 +++- nixd/lspserver/meson.build | 64 ++++++++--- nixd/lspserver/src/Connection.cpp | 19 +++- nixd/lspserver/src/LSPServer.cpp | 14 +-- nixd/meson.build | 15 ++- nixd/subprojects/common | 1 + nixd/subprojects/libnixf | 1 + nixd/subprojects/libnixt | 1 + nixd/tools/meson.build | 26 +++-- nixd/tools/nixd/test/completion/nixpkgs.md | 4 +- nixd/tools/nixd/test/completion/select-lib.md | 2 +- nixd/tools/nixd/test/completion/select.md | 2 +- .../nixd/test/completion/with-expr-select.md | 4 +- .../tools/nixd/test/completion/with-select.md | 4 +- subprojects/common | 1 + subprojects/libnixf | 1 + subprojects/libnixt | 1 + subprojects/nixd | 1 + 56 files changed, 559 insertions(+), 165 deletions(-) rename {nixd => common}/include/nixd/CommandLine/Options.h (100%) rename {nixd => common}/include/nixd/Eval/AttrSetClient.h (96%) rename {nixd => common}/include/nixd/Eval/AttrSetProvider.h (100%) create mode 100644 common/include/nixd/Eval/Spawn.h rename {nixd => common}/include/nixd/Protocol/AttrSet.h (99%) rename {nixd => common}/include/nixd/Support/AutoCloseFD.h (100%) rename {nixd => common}/include/nixd/Support/ForkPiped.h (100%) rename {nixd => common}/include/nixd/Support/PipedProc.h (100%) rename {nixd => common}/include/nixd/Support/StreamProc.h (100%) rename {nixd => common}/lib/CommandLine/Options.cpp (100%) rename {nixd => common}/lib/Eval/AttrSetClient.cpp (100%) rename {nixd => common}/lib/Eval/AttrSetProvider.cpp (97%) create mode 100644 common/lib/Eval/Spawn.cpp rename {nixd => common}/lib/Protocol/AttrSet.cpp (92%) rename {nixd => common}/lib/Support/AutoCloseFD.cpp (100%) rename {nixd => common}/lib/Support/ForkPiped.cpp (100%) rename {nixd => common}/lib/Support/StreamProc.cpp (100%) create mode 100644 common/meson.build create mode 120000 common/subprojects/libnixt create mode 120000 common/subprojects/nixd-lspserver create mode 100644 common/tools/meson.build rename {nixd => common}/tools/nixd-attrset-eval.cpp (100%) create mode 120000 libnixf/subprojects/common create mode 120000 nixd/subprojects/common create mode 120000 nixd/subprojects/libnixf create mode 120000 nixd/subprojects/libnixt create mode 120000 subprojects/common create mode 120000 subprojects/libnixf create mode 120000 subprojects/libnixt create mode 120000 subprojects/nixd diff --git a/nixd/include/nixd/CommandLine/Options.h b/common/include/nixd/CommandLine/Options.h similarity index 100% rename from nixd/include/nixd/CommandLine/Options.h rename to common/include/nixd/CommandLine/Options.h diff --git a/nixd/include/nixd/Eval/AttrSetClient.h b/common/include/nixd/Eval/AttrSetClient.h similarity index 96% rename from nixd/include/nixd/Eval/AttrSetClient.h rename to common/include/nixd/Eval/AttrSetClient.h index 11931d9b4..e7bd04399 100644 --- a/nixd/include/nixd/Eval/AttrSetClient.h +++ b/common/include/nixd/Eval/AttrSetClient.h @@ -67,6 +67,10 @@ class AttrSetClient : public lspserver::LSPServer { void exit() { Exit(nullptr); } + void setLoggingEnabled(bool Enabled) { + LSPServer::setLoggingEnabled(Enabled); + } + /// Get executable path for launching the server. /// \returns null terminated string. static const char *getExe(); diff --git a/nixd/include/nixd/Eval/AttrSetProvider.h b/common/include/nixd/Eval/AttrSetProvider.h similarity index 100% rename from nixd/include/nixd/Eval/AttrSetProvider.h rename to common/include/nixd/Eval/AttrSetProvider.h diff --git a/common/include/nixd/Eval/Spawn.h b/common/include/nixd/Eval/Spawn.h new file mode 100644 index 000000000..b63ac8d29 --- /dev/null +++ b/common/include/nixd/Eval/Spawn.h @@ -0,0 +1,15 @@ +#pragma once + +#include "nixd/Eval/AttrSetClient.h" + +#include +#include + +namespace nixd { + +/// Launches an attrset evaluator process whose stderr is redirected to +/// `WorkerStderr`. +void spawnAttrSetEval(std::string_view WorkerStderr, + std::unique_ptr &Worker); + +} // namespace nixd diff --git a/nixd/include/nixd/Protocol/AttrSet.h b/common/include/nixd/Protocol/AttrSet.h similarity index 99% rename from nixd/include/nixd/Protocol/AttrSet.h rename to common/include/nixd/Protocol/AttrSet.h index 7db9c8255..bb55d3b7c 100644 --- a/nixd/include/nixd/Protocol/AttrSet.h +++ b/common/include/nixd/Protocol/AttrSet.h @@ -108,6 +108,7 @@ struct AttrPathCompleteParams { Selector Scope; /// \brief Search for packages prefixed with this "prefix" std::string Prefix; + std::optional MaxItems; }; llvm::json::Value toJSON(const AttrPathCompleteParams &Params); diff --git a/nixd/include/nixd/Support/AutoCloseFD.h b/common/include/nixd/Support/AutoCloseFD.h similarity index 100% rename from nixd/include/nixd/Support/AutoCloseFD.h rename to common/include/nixd/Support/AutoCloseFD.h diff --git a/nixd/include/nixd/Support/ForkPiped.h b/common/include/nixd/Support/ForkPiped.h similarity index 100% rename from nixd/include/nixd/Support/ForkPiped.h rename to common/include/nixd/Support/ForkPiped.h diff --git a/nixd/include/nixd/Support/PipedProc.h b/common/include/nixd/Support/PipedProc.h similarity index 100% rename from nixd/include/nixd/Support/PipedProc.h rename to common/include/nixd/Support/PipedProc.h diff --git a/nixd/include/nixd/Support/StreamProc.h b/common/include/nixd/Support/StreamProc.h similarity index 100% rename from nixd/include/nixd/Support/StreamProc.h rename to common/include/nixd/Support/StreamProc.h diff --git a/nixd/lib/CommandLine/Options.cpp b/common/lib/CommandLine/Options.cpp similarity index 100% rename from nixd/lib/CommandLine/Options.cpp rename to common/lib/CommandLine/Options.cpp diff --git a/nixd/lib/Eval/AttrSetClient.cpp b/common/lib/Eval/AttrSetClient.cpp similarity index 100% rename from nixd/lib/Eval/AttrSetClient.cpp rename to common/lib/Eval/AttrSetClient.cpp diff --git a/nixd/lib/Eval/AttrSetProvider.cpp b/common/lib/Eval/AttrSetProvider.cpp similarity index 97% rename from nixd/lib/Eval/AttrSetProvider.cpp rename to common/lib/Eval/AttrSetProvider.cpp index 125c5af46..d5cc4d987 100644 --- a/nixd/lib/Eval/AttrSetProvider.cpp +++ b/common/lib/Eval/AttrSetProvider.cpp @@ -162,9 +162,9 @@ void fillOptionDescription(nix::EvalState &State, nix::Value &V, std::vector completeNames(nix::Value &Scope, const nix::EvalState &State, - std::string_view Prefix) { - int Num = 0; + std::string_view Prefix, int Limit) { std::vector Names; + const bool Unlimited = Limit <= 0; // FIXME: we may want to use "Trie" to speedup the string searching. // However as my (roughtly) profiling the critical in this loop is @@ -174,10 +174,9 @@ std::vector completeNames(nix::Value &Scope, const nix::Attr &Attr = *AttrPtr; const std::string_view Name = State.symbols[Attr.name]; if (Name.starts_with(Prefix)) { - ++Num; Names.emplace_back(Name); // We set this a very limited number as to speedup - if (Num > MaxItems) + if (!Unlimited && Names.size() >= static_cast(Limit)) break; } } @@ -288,7 +287,8 @@ void AttrSetProvider::onAttrPathComplete( return; } - return Reply(completeNames(Scope, state(), Params.Prefix)); + const int Limit = Params.MaxItems.value_or(MaxItems); + return Reply(completeNames(Scope, state(), Params.Prefix, Limit)); } catch (const nix::BaseError &Err) { return Reply(error(Err.info().msg.str())); } catch (const std::exception &Err) { diff --git a/common/lib/Eval/Spawn.cpp b/common/lib/Eval/Spawn.cpp new file mode 100644 index 000000000..bbb77947c --- /dev/null +++ b/common/lib/Eval/Spawn.cpp @@ -0,0 +1,24 @@ +#include "nixd/Eval/Spawn.h" + +#include + +#include +#include + +namespace nixd { + +namespace { +constexpr std::string_view NullDevice{"/dev/null"}; +} + +void spawnAttrSetEval(std::string_view WorkerStderr, + std::unique_ptr &Worker) { + std::string Path = WorkerStderr.empty() ? std::string(NullDevice) + : std::string(WorkerStderr); + Worker = std::make_unique([Path = std::move(Path)]() { + freopen(Path.c_str(), "w", stderr); + return execl(AttrSetClient::getExe(), "nixd-attrset-eval", nullptr); + }); +} + +} // namespace nixd diff --git a/nixd/lib/Protocol/AttrSet.cpp b/common/lib/Protocol/AttrSet.cpp similarity index 92% rename from nixd/lib/Protocol/AttrSet.cpp rename to common/lib/Protocol/AttrSet.cpp index cf3dc2e8a..e6d023bcb 100644 --- a/nixd/lib/Protocol/AttrSet.cpp +++ b/common/lib/Protocol/AttrSet.cpp @@ -112,15 +112,17 @@ bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathInfoResponse &R, } Value nixd::toJSON(const AttrPathCompleteParams &Params) { - return Object{{"Scope", Params.Scope}, {"Prefix", Params.Prefix}}; + return Object{{"Scope", Params.Scope}, + {"Prefix", Params.Prefix}, + {"MaxItems", Params.MaxItems}}; } bool nixd::fromJSON(const llvm::json::Value &Params, AttrPathCompleteParams &R, llvm::json::Path P) { ObjectMapper O(Params, P); - return O // - && O.map("Scope", R.Scope) // - && O.map("Prefix", R.Prefix) // - ; + return O // + && O.map("Scope", R.Scope) // + && O.map("Prefix", R.Prefix) // + && O.mapOptional("MaxItems", R.MaxItems); // } llvm::json::Value nixd::toJSON(const ValueDescription &Params) { diff --git a/nixd/lib/Support/AutoCloseFD.cpp b/common/lib/Support/AutoCloseFD.cpp similarity index 100% rename from nixd/lib/Support/AutoCloseFD.cpp rename to common/lib/Support/AutoCloseFD.cpp diff --git a/nixd/lib/Support/ForkPiped.cpp b/common/lib/Support/ForkPiped.cpp similarity index 100% rename from nixd/lib/Support/ForkPiped.cpp rename to common/lib/Support/ForkPiped.cpp diff --git a/nixd/lib/Support/StreamProc.cpp b/common/lib/Support/StreamProc.cpp similarity index 100% rename from nixd/lib/Support/StreamProc.cpp rename to common/lib/Support/StreamProc.cpp diff --git a/common/meson.build b/common/meson.build new file mode 100644 index 000000000..c76784dff --- /dev/null +++ b/common/meson.build @@ -0,0 +1,84 @@ +project('nixd-common', ['cpp'], default_options : ['cpp_std=gnu++20'], version : 'nightly') + +config_h = configuration_data() + +git = find_program('git', required : false) +if git.found() + res = run_command([git, 'describe', '--tags', '--always'], capture : true, check : false) + describe = res.stdout().strip() + if describe != '' + config_h.set_quoted('NIXD_VCS_TAG', describe) + endif +endif + +config_h.set_quoted('NIXD_VERSION', meson.project_version()) +config_h.set_quoted('NIXD_LIBEXEC', get_option('prefix') / get_option('libexecdir')) + +configure_file( + output : 'nixd-config.h', + configuration : config_h, +) + +cpp = meson.get_compiler('cpp') + +add_project_arguments([ + '-I' + meson.project_build_root(), + cpp.get_supported_arguments( + '-Werror=documentation', + '-Werror=delete-non-abstract-non-virtual-dtor', + '-Werror=pragma-once-outside-header', + '-Wno-unused-parameter', + '-Wno-missing-field-initializers' + ), +], language : 'cpp') + +if get_option('buildtype').startswith('debug') + add_project_arguments([ + cpp.get_supported_arguments('-Werror=return-type'), + ], language : 'cpp') +endif + +if get_option('buildtype').startswith('release') + add_project_arguments([ + cpp.get_supported_arguments( + '-Wno-return-type', + '-Wno-maybe-uninitialized', + '-Wno-unused-variable' + ), + ], language : 'cpp') +endif + +llvm = dependency('llvm') +nixd_lsp_server = dependency('nixd-lspserver', fallback : ['nixd-lspserver', 'nixd_lsp_server']) +nixt = dependency('nixt', fallback : ['libnixt', 'nixt']) + +common_inc = include_directories('include') + +common_deps = [nixd_lsp_server, llvm, nixt] + +nixd_common_lib = library( + 'nixd-common', + 'lib/Eval/AttrSetClient.cpp', + 'lib/Eval/AttrSetProvider.cpp', + 'lib/Eval/Spawn.cpp', + 'lib/Protocol/AttrSet.cpp', + 'lib/CommandLine/Options.cpp', + 'lib/Support/AutoCloseFD.cpp', + 'lib/Support/ForkPiped.cpp', + 'lib/Support/StreamProc.cpp', + include_directories : common_inc, + dependencies : common_deps, + install : true, +) + +subdir('tools') + +nixd_common = declare_dependency( + include_directories : common_inc, + link_with : nixd_common_lib, + dependencies : common_deps, +) + +meson.override_dependency('nixd-common', nixd_common) + +install_subdir('include', install_dir : 'include') diff --git a/common/subprojects/libnixt b/common/subprojects/libnixt new file mode 120000 index 000000000..5be85ff97 --- /dev/null +++ b/common/subprojects/libnixt @@ -0,0 +1 @@ +../../libnixt \ No newline at end of file diff --git a/common/subprojects/nixd-lspserver b/common/subprojects/nixd-lspserver new file mode 120000 index 000000000..ebb89fb5d --- /dev/null +++ b/common/subprojects/nixd-lspserver @@ -0,0 +1 @@ +../../nixd/lspserver \ No newline at end of file diff --git a/common/tools/meson.build b/common/tools/meson.build new file mode 100644 index 000000000..c23cd6d3a --- /dev/null +++ b/common/tools/meson.build @@ -0,0 +1,11 @@ +nixd_attrset_eval = executable( + 'nixd-attrset-eval', + 'nixd-attrset-eval.cpp', + include_directories : common_inc, + link_with : nixd_common_lib, + dependencies : common_deps, + install : true, + install_dir : get_option('libexecdir'), +) + +meson.override_find_program('nixd-attrset-eval', nixd_attrset_eval) diff --git a/nixd/tools/nixd-attrset-eval.cpp b/common/tools/nixd-attrset-eval.cpp similarity index 100% rename from nixd/tools/nixd-attrset-eval.cpp rename to common/tools/nixd-attrset-eval.cpp diff --git a/flake.nix b/flake.nix index e703e52ad..d81392471 100644 --- a/flake.nix +++ b/flake.nix @@ -35,7 +35,9 @@ ; nixComponents = nixVersions.nixComponents_2_30; llvmPackages = llvmPackages_19; - nixf = callPackage ./libnixf { }; + nixf = callPackage ./libnixf { + inherit llvmPackages nixComponents; + }; nixt = callPackage ./libnixt { inherit nixComponents; }; nixd = callPackage ./nixd { inherit nixComponents nixf nixt; diff --git a/libnixf/default.nix b/libnixf/default.nix index 5d2813bb4..f823697be 100644 --- a/libnixf/default.nix +++ b/libnixf/default.nix @@ -9,6 +9,8 @@ boost, nlohmann_json, python312, + llvmPackages, + nixComponents, }: stdenv.mkDerivation { @@ -39,8 +41,12 @@ stdenv.mkDerivation { nativeCheckInputs = [ lit ]; buildInputs = [ - gtest boost + gtest + llvmPackages.llvm + nixComponents.nix-cmd + nixComponents.nix-expr + nixComponents.nix-main nlohmann_json ]; diff --git a/libnixf/include/nixf/Basic/Nodes/Expr.h b/libnixf/include/nixf/Basic/Nodes/Expr.h index 963e094ea..57dac23b9 100644 --- a/libnixf/include/nixf/Basic/Nodes/Expr.h +++ b/libnixf/include/nixf/Basic/Nodes/Expr.h @@ -175,6 +175,30 @@ class ExprWith : public Expr { [[nodiscard]] ChildVector children() const override { return {KwWith.get(), TokSemi.get(), With.get(), E.get()}; } + + [[nodiscard]] std::vector selector() const { + std::vector Parts; + for (const auto &Child : With->children()) { + if (!Child) + continue; + switch (Child->kind()) { + case Node::NK_ExprVar: { + const auto &Var = static_cast(*Child); + Parts.emplace_back(Var.id().name()); + break; + } + case Node::NK_AttrPath: { + const auto &Path = static_cast(*Child); + for (const auto &Name : Path.names()) + Parts.emplace_back(Name->id()->name()); + break; + } + default: + break; + } + } + return Parts; + } }; } // namespace nixf diff --git a/libnixf/include/nixf/Sema/VariableLookup.h b/libnixf/include/nixf/Sema/VariableLookup.h index 7b0843f3e..3a0eb434f 100644 --- a/libnixf/include/nixf/Sema/VariableLookup.h +++ b/libnixf/include/nixf/Sema/VariableLookup.h @@ -18,8 +18,15 @@ #include #include #include +#include +#include #include +namespace nixd { +class AttrSetClient; +class AttrSetClientProc; +} // namespace nixd + namespace nixf { /// \brief Represents a definition @@ -162,8 +169,16 @@ class VariableLookupAnalysis { std::map Results; + std::unique_ptr NixpkgsEval; + nixd::AttrSetClient *NixpkgsClient = nullptr; + std::unordered_map> + NixpkgsKnownAttrs; + const std::unordered_set * + ensureNixpkgsKnownAttrsCached(const std::vector &Scope); + public: VariableLookupAnalysis(std::vector &Diags); + ~VariableLookupAnalysis(); /// \brief Perform variable lookup analysis (def-use) on AST. /// \note This method should be invoked after any other method called. @@ -196,6 +211,10 @@ class VariableLookupAnalysis { } const EnvNode *env(const Node *N) const; + + [[nodiscard]] nixd::AttrSetClient *nixpkgsClient() const { + return NixpkgsClient; + } }; } // namespace nixf diff --git a/libnixf/meson.build b/libnixf/meson.build index 5b2481fca..53c8b7dfe 100644 --- a/libnixf/meson.build +++ b/libnixf/meson.build @@ -9,6 +9,8 @@ boost = dependency('boost') gtest = dependency('gtest') gtest_main = dependency('gtest_main') +nixd_common = dependency('nixd-common', fallback : ['common', 'nixd_common']) + pkgconfig = import('pkgconfig') diff --git a/libnixf/src/Sema/VariableLookup.cpp b/libnixf/src/Sema/VariableLookup.cpp index a4bf681a6..c2be54ac3 100644 --- a/libnixf/src/Sema/VariableLookup.cpp +++ b/libnixf/src/Sema/VariableLookup.cpp @@ -3,6 +3,16 @@ #include "nixf/Basic/Nodes/Attrs.h" #include "nixf/Basic/Nodes/Lambda.h" +#include "nixd/Eval/AttrSetClient.h" +#include "nixd/Eval/Spawn.h" +#include "nixd/Protocol/AttrSet.h" + +#include + +#include +#include +#include + using namespace nixf; namespace { @@ -74,6 +84,25 @@ void VariableLookupAnalysis::emitEnvLivenessWarning( } } +static constexpr std::array KnownIdioms = { + "pkgs", + "lib", +}; + +std::string joinScope(const std::vector &Scope) { + std::string Key; + bool First = true; + for (const auto &Part : Scope) { + if (Part.empty()) + continue; + if (!First) + Key.push_back('.'); + First = false; + Key.append(Part); + } + return Key; +} + void VariableLookupAnalysis::lookupVar(const ExprVar &Var, const std::shared_ptr &Env) { const auto &Name = Var.id().name(); @@ -91,8 +120,9 @@ void VariableLookupAnalysis::lookupVar(const ExprVar &Var, // with builtins; // generators <--- this variable may come from "lib" | "builtins" // - // We cannot determine where it precisely come from, thus mark all Envs - // alive. + // In general, we cannot determine where it precisely come from, thus mark + // all Envs alive. (We'll make an attempt to resolve the variable with + // nixpkgs client below.) if (CurEnv->isWith()) { WithEnvs.emplace_back(CurEnv); } @@ -105,6 +135,23 @@ void VariableLookupAnalysis::lookupVar(const ExprVar &Var, for (const auto *WithEnv : WithEnvs) { Def = WithDefs.at(WithEnv->syntax()); Def->usedBy(Var); + + // Attempt to resolve the variable with nixpkgs client. + bool IsKnown = false; + const auto &With = static_cast(*WithEnv->syntax()); + + auto Selector = With.selector(); + if (Selector.empty()) + continue; + for (std::string_view Idiom : KnownIdioms) { + if (Selector.front() != Idiom) + continue; + if (const auto *Known = ensureNixpkgsKnownAttrsCached(Selector)) + IsKnown = Known->contains(Name); + break; + } + if (IsKnown) + break; } Results.insert({&Var, LookupResult{LookupResultKind::FromWith, Def}}); } else { @@ -475,7 +522,56 @@ void VariableLookupAnalysis::runOnAST(const Node &Root) { } VariableLookupAnalysis::VariableLookupAnalysis(std::vector &Diags) - : Diags(Diags) {} + : Diags(Diags) { + spawnAttrSetEval({}, NixpkgsEval); + if (NixpkgsEval) + NixpkgsClient = NixpkgsEval->client(); + if (NixpkgsClient) + NixpkgsClient->setLoggingEnabled(false); +} + +const std::unordered_set * +VariableLookupAnalysis::ensureNixpkgsKnownAttrsCached( + const std::vector &Scope) { + if (!NixpkgsClient) + return nullptr; + + std::string FullPath = joinScope(Scope); + if (NixpkgsKnownAttrs.contains(FullPath)) + return &NixpkgsKnownAttrs.at(FullPath); + + if (NixpkgsKnownAttrs.empty()) { + std::binary_semaphore Ready(0); + NixpkgsClient->evalExpr( + "import { }", + [&Ready](llvm::Expected> Resp) { + Ready.release(); + }); + Ready.acquire(); + } + + nixd::AttrPathCompleteParams Params; + Params.Scope = Scope; + Params.Prefix = ""; + Params.MaxItems = 0; + + std::binary_semaphore Ready(0); + std::vector Names; + NixpkgsClient->attrpathComplete( + Params, + [&Names, &Ready](llvm::Expected Resp) { + if (Resp) + Names = *Resp; + Ready.release(); + }); + Ready.acquire(); + + auto &Bucket = NixpkgsKnownAttrs[FullPath]; + Bucket.insert(Names.begin(), Names.end()); + return &Bucket; +} + +VariableLookupAnalysis::~VariableLookupAnalysis() = default; const EnvNode *VariableLookupAnalysis::env(const Node *N) const { if (!Envs.contains(N)) diff --git a/libnixf/src/meson.build b/libnixf/src/meson.build index 8bdb7868b..98f4e17b7 100644 --- a/libnixf/src/meson.build +++ b/libnixf/src/meson.build @@ -1,7 +1,7 @@ -libnixf_deps = [ boost, nlohmann_json ] - libnixf_inc = include_directories('../include') +libnixf_deps = [ boost, nlohmann_json, nixd_common ] + python3 = find_program('python3') diagnostic_enum_h = custom_target( @@ -77,3 +77,5 @@ nixf = declare_dependency( install_subdir('../include/nixf', install_dir: 'include') + +meson.override_dependency('nixf', nixf) diff --git a/libnixf/subprojects/common b/libnixf/subprojects/common new file mode 120000 index 000000000..dc879abe9 --- /dev/null +++ b/libnixf/subprojects/common @@ -0,0 +1 @@ +../../common \ No newline at end of file diff --git a/libnixf/test/Sema/VariableLookup.cpp b/libnixf/test/Sema/VariableLookup.cpp index 4939b09b8..c2111d5be 100644 --- a/libnixf/test/Sema/VariableLookup.cpp +++ b/libnixf/test/Sema/VariableLookup.cpp @@ -350,4 +350,68 @@ __curPos ASSERT_EQ(Diags.size(), 0); } +TEST_F(VLATest, NestedWith_KnownMaintainer) { + const char *Src = R"( +{ lib }: { + meta = with lib; { + maintainers = with lib.maintainers; [ booxter ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 1); +} + +TEST_F(VLATest, NestedWith_UnknownMaintainer) { + const char *Src = R"( +{ lib }: { + meta = with lib; { + maintainers = with lib.maintainers; [ booxter-missing ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 0); +} + +TEST_F(VLATest, NestedWith_Pkgs_KnownMaintainer) { + const char *Src = R"( +{ lib, pkgs }: { + meta = with lib; { + maintainers = with pkgs.lib.maintainers; [ booxter ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 1); +} + +TEST_F(VLATest, NestedWith_Pkgs_UnknownMaintainer) { + const char *Src = R"( +{ lib, pkgs }: { + meta = with lib; { + maintainers = with pkgs.lib.maintainers; [ booxter-missing ]; + }; +} + )"; + + std::shared_ptr AST = parse(Src, Diags); + VariableLookupAnalysis VLA(Diags); + VLA.runOnAST(*AST); + + ASSERT_EQ(Diags.size(), 0); +} + } // namespace diff --git a/libnixf/test/meson.build b/libnixf/test/meson.build index 931c1ffbb..0ffce298b 100644 --- a/libnixf/test/meson.build +++ b/libnixf/test/meson.build @@ -20,6 +20,25 @@ test('unit/libnixf/Parse', ) ) +common_sp = subproject('common', required : false) +if common_sp.found() + nixd_attrset_eval_prog = common_sp.get_variable('nixd_attrset_eval') + nixd_attrset_eval_dep = [nixd_attrset_eval_prog] +else + nixd_attrset_eval_prog = find_program('nixd-attrset-eval', required : true) + nixd_attrset_eval_dep = [] +endif + +fs = import('fs') +attrset_eval_dir = fs.parent(nixd_attrset_eval_prog.full_path()) + +libnixf_test_env = environment() + +libnixf_test_env.prepend('PATH', attrset_eval_dir) +libnixf_test_env.prepend('PATH', meson.current_build_dir()) +libnixf_test_env.set('MESON_BUILD_ROOT', meson.current_build_dir()) +libnixf_test_env.set('NIXD_ATTRSET_EVAL', nixd_attrset_eval_prog.full_path()) + test('unit/libnixf/Sema', executable('unit-libnixf-sema', 'Sema/SemaActions.cpp', @@ -27,5 +46,8 @@ test('unit/libnixf/Sema', 'Sema/ParentMap.cpp', dependencies: [ nixf, gtest_main ], include_directories: [ '../src/Sema' ] # Private headers - ) + ), + timeout: 120, + env: libnixf_test_env, + depends: nixd_attrset_eval_dep, ) diff --git a/libnixt/lib/meson.build b/libnixt/lib/meson.build index d4d4ec310..1fdc76f3f 100644 --- a/libnixt/lib/meson.build +++ b/libnixt/lib/meson.build @@ -24,3 +24,5 @@ nixt = declare_dependency(include_directories: libnixd_inc, ) install_subdir('../include/nixt', install_dir: 'include') + +meson.override_dependency('nixt', nixt) diff --git a/meson.build b/meson.build index c3911e812..7c6a96018 100644 --- a/meson.build +++ b/meson.build @@ -1,87 +1,14 @@ -project( 'nixd' - , ['c', 'cpp'] - , default_options : ['cpp_std=gnu++20'] - , version: 'nightly' - ) - -config_h = configuration_data() - -git = find_program('git', required : false) -if git.found() - res = run_command(['git', 'describe', '--tags', '--always'], capture : true, check : false) - describe = res.stdout().strip() - config_h.set_quoted('NIXD_VCS_TAG', describe) -endif - -config_h.set_quoted('NIXD_VERSION', meson.project_version()) -config_h.set_quoted('NIXD_LIBEXEC', get_option('prefix') / get_option('libexecdir')) - -configure_file( - output: 'nixd-config.h', - configuration: config_h, -) - -cpp = meson.get_compiler('cpp') - -add_project_arguments([ - '-I' + meson.project_build_root(), - cpp.get_supported_arguments( - '-Werror=documentation', - '-Werror=delete-non-abstract-non-virtual-dtor', - '-Werror=pragma-once-outside-header', - '-Wno-unused-parameter', - '-Wno-missing-field-initializers', - ), -], language: 'cpp') - -if get_option('buildtype').startswith('debug') - add_project_arguments([ - cpp.get_supported_arguments( - '-Werror=return-type', - ), - ], language: 'cpp') -endif - -if get_option('buildtype').startswith('release') - add_project_arguments([ - cpp.get_supported_arguments( - '-Wno-return-type', - '-Wno-maybe-uninitialized', - '-Wno-unused-variable', - ), - ], language: 'cpp') -endif - - -gtest = dependency('gtest') -gtest_main = dependency('gtest_main') - -llvm = dependency('llvm') -boost = dependency('boost') -nlohmann_json = dependency('nlohmann_json') - -lit = find_program('lit', required: false) - -pkgconfig = import('pkgconfig') - - -subdir('libnixf/src') -subdir('libnixf/tools') -subdir('libnixf/test') - - -nix_main = dependency('nix-main') -nix_expr = dependency('nix-expr') -nix_cmd = dependency('nix-cmd') -nix_flake = dependency('nix-flake') - - -subdir('libnixt/lib') -subdir('libnixt/test') - -subdir('nixd/lspserver') -subdir('nixd/lib') -subdir('nixd/tools') +project('nixd', ['c', 'cpp'], default_options : ['cpp_std=gnu++20'], version : 'nightly') +# Wire up the component projects via subproject fallbacks so the entire tree can +# still be built from the repository root. +nixd_common = dependency('nixd-common', fallback : ['common', 'nixd_common']) +nixf = dependency('nixf', fallback : ['libnixf', 'nixf']) +nixt = dependency('nixt', fallback : ['libnixt', 'nixt']) +# Configure the language server itself. This instantiates the standalone nixd +# project without duplicating library builds thanks to the overrides above. +subproject('nixd') +# Keep references alive to avoid "unused variable" warnings in Meson. +_ = [nixd_common, nixf, nixt] diff --git a/nixd/include/nixd/Eval/Launch.h b/nixd/include/nixd/Eval/Launch.h index d6e1fcf66..15e7d677e 100644 --- a/nixd/include/nixd/Eval/Launch.h +++ b/nixd/include/nixd/Eval/Launch.h @@ -1,6 +1,6 @@ #pragma once -#include "AttrSetClient.h" +#include "nixd/Eval/AttrSetClient.h" namespace nixd { diff --git a/nixd/lib/Eval/Launch.cpp b/nixd/lib/Eval/Launch.cpp index eeb2c05fc..54d78bbfd 100644 --- a/nixd/lib/Eval/Launch.cpp +++ b/nixd/lib/Eval/Launch.cpp @@ -1,5 +1,6 @@ #include "nixd/Eval/Launch.h" #include "nixd/CommandLine/Options.h" +#include "nixd/Eval/Spawn.h" #include @@ -23,10 +24,7 @@ opt NixpkgsWorkerStderr{ void nixd::startAttrSetEval(const std::string &Name, std::unique_ptr &Worker) { - Worker = std::make_unique([&Name]() { - freopen(Name.c_str(), "w", stderr); - return execl(AttrSetClient::getExe(), "nixd-attrset-eval", nullptr); - }); + spawnAttrSetEval(Name, Worker); } void nixd::startNixpkgs(std::unique_ptr &NixpkgsEval) { diff --git a/nixd/lib/meson.build b/nixd/lib/meson.build index 701850a24..a8c393ba1 100644 --- a/nixd/lib/meson.build +++ b/nixd/lib/meson.build @@ -1,11 +1,10 @@ libnixd_include = include_directories('../include') -libnixd_deps = [ nixd_lsp_server, nixf, llvm, nixt ] +libnixd_deps = [ nixd_common, nixf, llvm, nixt ] libnixd_lib = library( 'nixd', 'CommandLine/Configuration.cpp', - 'CommandLine/Options.cpp', 'Controller/AST.cpp', 'Controller/CodeAction.cpp', 'Controller/Completion.cpp', @@ -26,16 +25,10 @@ libnixd_lib = library( 'Controller/SemanticTokens.cpp', 'Controller/Support.cpp', 'Controller/TextDocumentSync.cpp', - 'Eval/AttrSetClient.cpp', - 'Eval/AttrSetProvider.cpp', 'Eval/Launch.cpp', - 'Protocol/AttrSet.cpp', 'Protocol/Protocol.cpp', - 'Support/AutoCloseFD.cpp', 'Support/AutoRemoveShm.cpp', - 'Support/ForkPiped.cpp', 'Support/JSON.cpp', - 'Support/StreamProc.cpp', dependencies: libnixd_deps, include_directories: libnixd_include, install: true diff --git a/nixd/lspserver/include/lspserver/Connection.h b/nixd/lspserver/include/lspserver/Connection.h index 030b16d03..6b4510f5f 100644 --- a/nixd/lspserver/include/lspserver/Connection.h +++ b/nixd/lspserver/include/lspserver/Connection.h @@ -35,10 +35,13 @@ class InboundPort { private: std::atomic Close; + template void maybeLog(const char *Fmt, Ts &&...Vals); + public: int In; JSONStreamStyle StreamStyle = JSONStreamStyle::Standard; + bool LoggingEnabled = true; /// Read one message as specified in the LSP standard. /// A Language Server Protocol message starts with a set of @@ -51,6 +54,8 @@ class InboundPort { /// \brief Notify the inbound port to close the connection void close() { Close = true; } + void setLoggingEnabled(bool Enabled) { LoggingEnabled = Enabled; } + InboundPort(int In = STDIN_FILENO, JSONStreamStyle StreamStyle = JSONStreamStyle::Standard) : Close(false), In(In), StreamStyle(StreamStyle) {}; @@ -76,6 +81,9 @@ class OutboundPort { std::mutex Mutex; bool Pretty = false; + bool LoggingEnabled = true; + + template void maybeLog(const char *Fmt, Ts &&...Vals); public: explicit OutboundPort(bool Pretty = false) @@ -88,6 +96,8 @@ class OutboundPort { void reply(llvm::json::Value ID, llvm::Expected Result); void sendMessage(llvm::json::Value Message); + + void setLoggingEnabled(bool Enabled) { LoggingEnabled = Enabled; } }; } // namespace lspserver diff --git a/nixd/lspserver/include/lspserver/LSPServer.h b/nixd/lspserver/include/lspserver/LSPServer.h index 14a946aa6..3c17886b6 100644 --- a/nixd/lspserver/include/lspserver/LSPServer.h +++ b/nixd/lspserver/include/lspserver/LSPServer.h @@ -9,6 +9,7 @@ #include #include +#include namespace lspserver { @@ -19,6 +20,13 @@ class LSPServer : public MessageHandler { std::unique_ptr In; std::unique_ptr Out; + bool LogsEnabled = true; + + template void maybeLog(const char *Fmt, Ts &&...Vals) const { + if (LogsEnabled) + log(Fmt, std::forward(Vals)...); + } + bool onNotify(llvm::StringRef Method, llvm::json::Value) override; bool onCall(llvm::StringRef Method, llvm::json::Value Params, llvm::json::Value ID) override; @@ -47,7 +55,7 @@ class LSPServer : public MessageHandler { void callMethod(llvm::StringRef Method, llvm::json::Value Params, Callback CB, OutboundPort *O) { llvm::json::Value ID(bindReply(std::move(CB))); - log("--> call {0}({1})", Method, ID.getAsInteger()); + maybeLog("--> call {0}({1})", Method, ID.getAsInteger()); O->call(Method, Params, ID); } @@ -58,8 +66,8 @@ class LSPServer : public MessageHandler { mkOutNotifiction(llvm::StringRef Method, OutboundPort *O = nullptr) { if (!O) O = Out.get(); - return [=](const T &Params) { - log("--> notify {0}", Method); + return [=, this](const T &Params) { + maybeLog("--> notify {0}", Method); O->notify(Method, Params); }; } @@ -87,6 +95,14 @@ class LSPServer : public MessageHandler { LSPServer(std::unique_ptr In, std::unique_ptr Out) : In(std::move(In)), Out(std::move(Out)) {}; + void setLoggingEnabled(bool Enabled) { + LogsEnabled = Enabled; + if (In) + In->setLoggingEnabled(Enabled); + if (Out) + Out->setLoggingEnabled(Enabled); + } + /// \brief Close the inbound port. void closeInbound() { In->close(); } void run(); diff --git a/nixd/lspserver/meson.build b/nixd/lspserver/meson.build index 512a42e1c..051b807a3 100644 --- a/nixd/lspserver/meson.build +++ b/nixd/lspserver/meson.build @@ -1,21 +1,51 @@ -nixd_lsp_server_deps = [ llvm ] +project('nixd-lspserver', ['cpp'], default_options : ['cpp_std=gnu++20'], version : 'nightly') + +cpp = meson.get_compiler('cpp') + +add_project_arguments([ + cpp.get_supported_arguments( + '-Werror=documentation', + '-Werror=delete-non-abstract-non-virtual-dtor', + '-Werror=pragma-once-outside-header', + '-Wno-unused-parameter', + '-Wno-missing-field-initializers' + ), +], language : 'cpp') + +if get_option('buildtype').startswith('release') + add_project_arguments([ + cpp.get_supported_arguments( + '-Wno-maybe-uninitialized', + ), + ], language: 'cpp') +endif + +llvm = dependency('llvm') nixd_lsp_server_inc = include_directories('include') -nixd_lsp_server_lib = library('nixd-lspserver' -, [ 'src/Connection.cpp' - , 'src/DraftStore.cpp' - , 'src/LSPServer.cpp' - , 'src/Logger.cpp' - , 'src/Protocol.cpp' - , 'src/SourceCode.cpp' - , 'src/URI.cpp' - ] -, include_directories: nixd_lsp_server_inc -, dependencies: nixd_lsp_server_deps -, install: true +nixd_lsp_server_lib = library( + 'nixd-lspserver', + [ + 'src/Connection.cpp', + 'src/DraftStore.cpp', + 'src/LSPServer.cpp', + 'src/Logger.cpp', + 'src/Protocol.cpp', + 'src/SourceCode.cpp', + 'src/URI.cpp', + ], + include_directories : nixd_lsp_server_inc, + dependencies : [llvm], + install : true, ) -nixd_lsp_server = declare_dependency( link_with: nixd_lsp_server_lib - , include_directories: nixd_lsp_server_inc - , dependencies: nixd_lsp_server_deps - ) + +nixd_lsp_server = declare_dependency( + link_with : nixd_lsp_server_lib, + include_directories : nixd_lsp_server_inc, + dependencies : [llvm], +) + +meson.override_dependency('nixd-lspserver', nixd_lsp_server) + +install_subdir('include', install_dir : 'include') diff --git a/nixd/lspserver/src/Connection.cpp b/nixd/lspserver/src/Connection.cpp index 9d6ffd206..c4a98d027 100644 --- a/nixd/lspserver/src/Connection.cpp +++ b/nixd/lspserver/src/Connection.cpp @@ -15,6 +15,7 @@ #include #include #include +#include namespace { @@ -50,6 +51,20 @@ char ReadEOF::ID; namespace lspserver { +template +void OutboundPort::maybeLog(const char *Fmt, Ts &&...Vals) { + if (!LoggingEnabled) + return; + vlog(Fmt, std::forward(Vals)...); +} + +template +void InboundPort::maybeLog(const char *Fmt, Ts &&...Vals) { + if (!LoggingEnabled) + return; + vlog(Fmt, std::forward(Vals)...); +} + static llvm::json::Object encodeError(llvm::Error Error) { std::string Message; ErrorCode Code = ErrorCode::UnknownErrorCode; @@ -112,7 +127,7 @@ void OutboundPort::reply(llvm::json::Value ID, void OutboundPort::sendMessage(llvm::json::Value Message) { // Make sure our outputs are not interleaving between messages (json) - vlog(">>> {0}", Message); + maybeLog(">>> {0}", Message); std::lock_guard Guard(Mutex); OutputBuffer.clear(); llvm::raw_svector_ostream SVecOS(OutputBuffer); @@ -320,7 +335,7 @@ void InboundPort::loop(MessageHandler &Handler) { for (;;) { if (auto Message = readMessage(Buffer)) { - vlog("<<< {0}", jsonToString(*Message)); + maybeLog("<<< {0}", jsonToString(*Message)); if (!dispatch(*Message, Handler)) return; } else { diff --git a/nixd/lspserver/src/LSPServer.cpp b/nixd/lspserver/src/LSPServer.cpp index abb8177e6..f07f10702 100644 --- a/nixd/lspserver/src/LSPServer.cpp +++ b/nixd/lspserver/src/LSPServer.cpp @@ -15,33 +15,33 @@ namespace lspserver { void LSPServer::run() { In->loop(*this); } bool LSPServer::onNotify(llvm::StringRef Method, llvm::json::Value Params) { - log("<-- {0}", Method); + maybeLog("<-- {0}", Method); if (Method == "exit") return false; auto Handler = Registry.NotificationHandlers.find(Method); if (Handler != Registry.NotificationHandlers.end()) { Handler->second(std::move(Params)); } else { - log("unhandled notification {0}", Method); + maybeLog("unhandled notification {0}", Method); } return true; } bool LSPServer::onCall(llvm::StringRef Method, llvm::json::Value Params, llvm::json::Value ID) { - log("<-- {0}({1})", Method, ID); + maybeLog("<-- {0}({1})", Method, ID); auto Handler = Registry.MethodHandlers.find(Method); if (Handler != Registry.MethodHandlers.end()) Handler->second(std::move(Params), [=, Method = std::string(Method), this](llvm::Expected Response) mutable { if (Response) { - log("--> reply:{0}({1})", Method, ID); + maybeLog("--> reply:{0}({1})", Method, ID); Out->reply(std::move(ID), std::move(Response)); } else { llvm::Error Err = Response.takeError(); - log("--> reply:{0}({1}) {2:ms}, error: {3}", Method, ID, - Err); + maybeLog("--> reply:{0}({1}) {2:ms}, error: {3}", + Method, ID, Err); Out->reply(std::move(ID), std::move(Err)); } }); @@ -52,7 +52,7 @@ bool LSPServer::onCall(llvm::StringRef Method, llvm::json::Value Params, bool LSPServer::onReply(llvm::json::Value ID, llvm::Expected Result) { - log("<-- reply({0})", ID); + maybeLog("<-- reply({0})", ID); std::optional> CB; if (auto OptI = ID.getAsInteger()) { diff --git a/nixd/meson.build b/nixd/meson.build index 90f3c039c..eb3395f82 100644 --- a/nixd/meson.build +++ b/nixd/meson.build @@ -27,15 +27,24 @@ add_project_arguments([ ), ], language: 'cpp') +if get_option('buildtype').startswith('release') + add_project_arguments([ + cpp.get_supported_arguments( + '-Wno-maybe-uninitialized', + ), + ], language: 'cpp') +endif + gtest = dependency('gtest') gtest_main = dependency('gtest_main') -nixf = dependency('nixf') -nixt = dependency('nixt') llvm = dependency('llvm') +nixf = dependency('nixf', fallback : ['libnixf', 'nixf']) +nixt = dependency('nixt', fallback : ['libnixt', 'nixt']) +nixd_common = dependency('nixd-common', fallback : ['common', 'nixd_common']) + pkgconfig = import('pkgconfig') -subdir('lspserver') subdir('lib') subdir('tools') diff --git a/nixd/subprojects/common b/nixd/subprojects/common new file mode 120000 index 000000000..dc879abe9 --- /dev/null +++ b/nixd/subprojects/common @@ -0,0 +1 @@ +../../common \ No newline at end of file diff --git a/nixd/subprojects/libnixf b/nixd/subprojects/libnixf new file mode 120000 index 000000000..949449029 --- /dev/null +++ b/nixd/subprojects/libnixf @@ -0,0 +1 @@ +../../libnixf \ No newline at end of file diff --git a/nixd/subprojects/libnixt b/nixd/subprojects/libnixt new file mode 120000 index 000000000..5be85ff97 --- /dev/null +++ b/nixd/subprojects/libnixt @@ -0,0 +1 @@ +../../libnixt \ No newline at end of file diff --git a/nixd/tools/meson.build b/nixd/tools/meson.build index 072c21239..6774f6bd2 100644 --- a/nixd/tools/meson.build +++ b/nixd/tools/meson.build @@ -7,19 +7,24 @@ nixd = executable( lit = find_program('lit', required: false) -nixd_attrset_eval = executable( - 'nixd-attrset-eval', - 'nixd-attrset-eval.cpp', - install: true, - dependencies: libnixd, - install_dir: get_option('libexecdir'), -) +common_sp = subproject('common', required : false) +if common_sp.found() + nixd_attrset_eval_prog = common_sp.get_variable('nixd_attrset_eval') + nixd_attrset_eval_dep = [nixd_attrset_eval_prog] +else + nixd_attrset_eval_prog = find_program('nixd-attrset-eval', required : true) + nixd_attrset_eval_dep = [] +endif + +fs = import('fs') +attrset_eval_dir = fs.parent(nixd_attrset_eval_prog.full_path()) regression_controller_env = environment() +regression_controller_env.prepend('PATH', attrset_eval_dir) regression_controller_env.prepend('PATH', meson.current_build_dir()) regression_controller_env.set('MESON_BUILD_ROOT', meson.current_build_dir()) -regression_controller_env.set('NIXD_ATTRSET_EVAL', nixd_attrset_eval.path()) +regression_controller_env.set('NIXD_ATTRSET_EVAL', nixd_attrset_eval_prog.full_path()) if lit.found() @@ -32,12 +37,13 @@ if lit.found() meson.current_source_dir() + '/nixd/test' ], timeout: 120, - depends: [ nixd, nixd_attrset_eval ] ) + depends: [ nixd ] + nixd_attrset_eval_dep ) endif regression_worker_env = environment() regression_worker_env.append('PATH', meson.current_build_dir()) +regression_worker_env.append('PATH', attrset_eval_dir) regression_worker_env.set('MESON_BUILD_ROOT', meson.current_build_dir()) regression_worker_env.set('ASAN_OPTIONS', 'detect_leaks=0') @@ -50,5 +56,5 @@ if lit.found() '-vv', meson.current_source_dir() + '/nixd-attrset-eval/test' ], - depends: [ nixd_attrset_eval ] ) + depends: nixd_attrset_eval_dep ) endif diff --git a/nixd/tools/nixd/test/completion/nixpkgs.md b/nixd/tools/nixd/test/completion/nixpkgs.md index 6300d7ba0..ba4fea541 100644 --- a/nixd/tools/nixd/test/completion/nixpkgs.md +++ b/nixd/tools/nixd/test/completion/nixpkgs.md @@ -58,13 +58,13 @@ CHECK-NEXT: "label": "abort", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"a\",\"Scope\":[]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"a\",\"Scope\":[]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "ax", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"a\",\"Scope\":[]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"a\",\"Scope\":[]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "ay", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/select-lib.md b/nixd/tools/nixd/test/completion/select-lib.md index 8b42510e5..8cef5e456 100644 --- a/nixd/tools/nixd/test/completion/select-lib.md +++ b/nixd/tools/nixd/test/completion/select-lib.md @@ -52,7 +52,7 @@ lib.hel CHECK: "isIncomplete": false, CHECK-NEXT: "items": [ CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"hel\",\"Scope\":[\"lib\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"hel\",\"Scope\":[\"lib\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "hello", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/select.md b/nixd/tools/nixd/test/completion/select.md index d68f279e9..e61bfc47b 100644 --- a/nixd/tools/nixd/test/completion/select.md +++ b/nixd/tools/nixd/test/completion/select.md @@ -52,7 +52,7 @@ pkgs.hel CHECK: "isIncomplete": false, CHECK-NEXT: "items": [ CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"hel\",\"Scope\":[]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"hel\",\"Scope\":[]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "hello", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/with-expr-select.md b/nixd/tools/nixd/test/completion/with-expr-select.md index f0ae8d107..081c88c4d 100644 --- a/nixd/tools/nixd/test/completion/with-expr-select.md +++ b/nixd/tools/nixd/test/completion/with-expr-select.md @@ -46,13 +46,13 @@ with pkgs.ax; [ b ] ``` ``` - CHECK: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", + CHECK: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "bx", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "by", CHECK-NEXT: "score": 0 diff --git a/nixd/tools/nixd/test/completion/with-select.md b/nixd/tools/nixd/test/completion/with-select.md index 35c072ef6..b8fc41ee8 100644 --- a/nixd/tools/nixd/test/completion/with-select.md +++ b/nixd/tools/nixd/test/completion/with-select.md @@ -52,13 +52,13 @@ CHECK-NEXT: "result": { CHECK-NEXT: "isIncomplete": false, CHECK-NEXT: "items": [ CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "bx", CHECK-NEXT: "score": 0 CHECK-NEXT: }, CHECK-NEXT: { -CHECK-NEXT: "data": "{\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", +CHECK-NEXT: "data": "{\"MaxItems\":null,\"Prefix\":\"b\",\"Scope\":[\"ax\"]}", CHECK-NEXT: "kind": 5, CHECK-NEXT: "label": "by", CHECK-NEXT: "score": 0 diff --git a/subprojects/common b/subprojects/common new file mode 120000 index 000000000..60d3b0a6a --- /dev/null +++ b/subprojects/common @@ -0,0 +1 @@ +../common \ No newline at end of file diff --git a/subprojects/libnixf b/subprojects/libnixf new file mode 120000 index 000000000..6961983fe --- /dev/null +++ b/subprojects/libnixf @@ -0,0 +1 @@ +../libnixf \ No newline at end of file diff --git a/subprojects/libnixt b/subprojects/libnixt new file mode 120000 index 000000000..7b39a8328 --- /dev/null +++ b/subprojects/libnixt @@ -0,0 +1 @@ +../libnixt \ No newline at end of file diff --git a/subprojects/nixd b/subprojects/nixd new file mode 120000 index 000000000..b097c357a --- /dev/null +++ b/subprojects/nixd @@ -0,0 +1 @@ +../nixd \ No newline at end of file