Merge remote-tracking branch 'upstream/master' into overlayfs-store

This commit is contained in:
John Ericson 2024-04-05 16:32:02 -04:00
commit c99c80f075
200 changed files with 6007 additions and 1158 deletions

2
.github/CODEOWNERS vendored
View file

@ -14,4 +14,4 @@
src/libexpr/primops.cc @roberth src/libexpr/primops.cc @roberth
# Libstore layer # Libstore layer
/src/libstore @thufschmitt /src/libstore @thufschmitt @ericson2314

View file

@ -21,7 +21,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Create backport PRs - name: Create backport PRs
# should be kept in sync with `version` # should be kept in sync with `version`
uses: zeebe-io/backport-action@v2.4.1 uses: zeebe-io/backport-action@v2.5.0
with: with:
# Config README: https://github.com/zeebe-io/backport-action#backport-action # Config README: https://github.com/zeebe-io/backport-action#backport-action
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -159,3 +159,11 @@ jobs:
# deprecated 2024-02-24 # deprecated 2024-02-24
docker tag nix:$NIX_VERSION $IMAGE_ID:master docker tag nix:$NIX_VERSION $IMAGE_ID:master
docker push $IMAGE_ID:master docker push $IMAGE_ID:master
vm_tests:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix build -L .#hydraJobs.tests.githubFlakes .#hydraJobs.tests.tarballFlakes

4
.gitignore vendored
View file

@ -49,6 +49,9 @@ perl/Makefile.config
/src/libexpr/tests /src/libexpr/tests
/tests/unit/libexpr/libnixexpr-tests /tests/unit/libexpr/libnixexpr-tests
# /src/libfetchers
/tests/unit/libfetchers/libnixfetchers-tests
# /src/libstore/ # /src/libstore/
*.gen.* *.gen.*
/src/libstore/tests /src/libstore/tests
@ -142,6 +145,7 @@ GTAGS
# auto-generated compilation database # auto-generated compilation database
compile_commands.json compile_commands.json
*.compile_commands.json
nix-rust/target nix-rust/target

View file

@ -18,6 +18,9 @@ makefiles = \
src/libexpr/local.mk \ src/libexpr/local.mk \
src/libcmd/local.mk \ src/libcmd/local.mk \
src/nix/local.mk \ src/nix/local.mk \
src/libutil-c/local.mk \
src/libstore-c/local.mk \
src/libexpr-c/local.mk \
src/resolve-system-dependencies/local.mk \ src/resolve-system-dependencies/local.mk \
scripts/local.mk \ scripts/local.mk \
misc/bash/local.mk \ misc/bash/local.mk \
@ -34,6 +37,7 @@ makefiles += \
tests/unit/libutil-support/local.mk \ tests/unit/libutil-support/local.mk \
tests/unit/libstore/local.mk \ tests/unit/libstore/local.mk \
tests/unit/libstore-support/local.mk \ tests/unit/libstore-support/local.mk \
tests/unit/libfetchers/local.mk \
tests/unit/libexpr/local.mk \ tests/unit/libexpr/local.mk \
tests/unit/libexpr-support/local.mk tests/unit/libexpr-support/local.mk
endif endif
@ -60,6 +64,10 @@ ifeq ($(ENABLE_INTERNAL_API_DOCS), yes)
makefiles-late += doc/internal-api/local.mk makefiles-late += doc/internal-api/local.mk
endif endif
ifeq ($(ENABLE_EXTERNAL_API_DOCS), yes)
makefiles-late += doc/external-api/local.mk
endif
# Miscellaneous global Flags # Miscellaneous global Flags
OPTIMIZE = 1 OPTIMIZE = 1
@ -124,3 +132,10 @@ internal-api-html:
@echo "Internal API docs are disabled. Configure with '--enable-internal-api-docs', or avoid calling 'make internal-api-html'." @echo "Internal API docs are disabled. Configure with '--enable-internal-api-docs', or avoid calling 'make internal-api-html'."
@exit 1 @exit 1
endif endif
ifneq ($(ENABLE_EXTERNAL_API_DOCS), yes)
.PHONY: external-api-html
external-api-html:
@echo "External API docs are disabled. Configure with '--enable-external-api-docs', or avoid calling 'make external-api-html'."
@exit 1
endif

View file

@ -12,6 +12,7 @@ ENABLE_BUILD = @ENABLE_BUILD@
ENABLE_DOC_GEN = @ENABLE_DOC_GEN@ ENABLE_DOC_GEN = @ENABLE_DOC_GEN@
ENABLE_FUNCTIONAL_TESTS = @ENABLE_FUNCTIONAL_TESTS@ ENABLE_FUNCTIONAL_TESTS = @ENABLE_FUNCTIONAL_TESTS@
ENABLE_INTERNAL_API_DOCS = @ENABLE_INTERNAL_API_DOCS@ ENABLE_INTERNAL_API_DOCS = @ENABLE_INTERNAL_API_DOCS@
ENABLE_EXTERNAL_API_DOCS = @ENABLE_EXTERNAL_API_DOCS@
ENABLE_S3 = @ENABLE_S3@ ENABLE_S3 = @ENABLE_S3@
ENABLE_UNIT_TESTS = @ENABLE_UNIT_TESTS@ ENABLE_UNIT_TESTS = @ENABLE_UNIT_TESTS@
GTEST_LIBS = @GTEST_LIBS@ GTEST_LIBS = @GTEST_LIBS@

View file

@ -150,6 +150,11 @@ AC_ARG_ENABLE(unit-tests, AS_HELP_STRING([--disable-unit-tests],[Do not build th
ENABLE_UNIT_TESTS=$enableval, ENABLE_UNIT_TESTS=$ENABLE_BUILD) ENABLE_UNIT_TESTS=$enableval, ENABLE_UNIT_TESTS=$ENABLE_BUILD)
AC_SUBST(ENABLE_UNIT_TESTS) AC_SUBST(ENABLE_UNIT_TESTS)
# Build external API docs by default
AC_ARG_ENABLE(external_api_docs, AS_HELP_STRING([--enable-external-api-docs],[Build API docs for Nix's C interface]),
external_api_docs=$enableval, external_api_docs=yes)
AC_SUBST(external_api_docs)
AS_IF( AS_IF(
[test "$ENABLE_BUILD" == "no" && test "$ENABLE_UNIT_TESTS" == "yes"], [test "$ENABLE_BUILD" == "no" && test "$ENABLE_UNIT_TESTS" == "yes"],
[AC_MSG_ERROR([Cannot enable unit tests when building overall is disabled. Please do not pass '--enable-unit-tests' or do not pass '--disable-build'.])]) [AC_MSG_ERROR([Cannot enable unit tests when building overall is disabled. Please do not pass '--enable-unit-tests' or do not pass '--disable-build'.])])
@ -172,6 +177,10 @@ AC_ARG_ENABLE(internal-api-docs, AS_HELP_STRING([--enable-internal-api-docs],[Bu
ENABLE_INTERNAL_API_DOCS=$enableval, ENABLE_INTERNAL_API_DOCS=no) ENABLE_INTERNAL_API_DOCS=$enableval, ENABLE_INTERNAL_API_DOCS=no)
AC_SUBST(ENABLE_INTERNAL_API_DOCS) AC_SUBST(ENABLE_INTERNAL_API_DOCS)
AC_ARG_ENABLE(external-api-docs, AS_HELP_STRING([--enable-external-api-docs],[Build API docs for Nix's external unstable C interfaces]),
ENABLE_EXTERNAL_API_DOCS=$enableval, ENABLE_EXTERNAL_API_DOCS=no)
AC_SUBST(ENABLE_EXTERNAL_API_DOCS)
AS_IF( AS_IF(
[test "$ENABLE_FUNCTIONAL_TESTS" == "yes" || test "$ENABLE_DOC_GEN" == "yes"], [test "$ENABLE_FUNCTIONAL_TESTS" == "yes" || test "$ENABLE_DOC_GEN" == "yes"],
[NEED_PROG(jq, jq)]) [NEED_PROG(jq, jq)])

3
doc/external-api/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
/doxygen.cfg
/html
/latex

108
doc/external-api/README.md Normal file
View file

@ -0,0 +1,108 @@
# Getting started
> **Warning** These bindings are **experimental**, which means they can change
> at any time or be removed outright; nevertheless the plan is to provide a
> stable external C API to the Nix language and the Nix store.
The language library allows evaluating Nix expressions and interacting with Nix
language values. The Nix store API is still rudimentary, and only allows
initialising and connecting to a store for the Nix language evaluator to
interact with.
Currently there are two ways to interface with the Nix language evaluator
programmatically:
1. Embedding the evaluator
2. Writing language plug-ins
Embedding means you link the Nix C libraries in your program and use them from
there. Adding a plug-in means you make a library that gets loaded by the Nix
language evaluator, specified through a configuration option.
Many of the components and mechanisms involved are not yet documented, therefore
please refer to the [Nix source code](https://github.com/NixOS/nix/) for
details. Additions to in-code documentation and the reference manual are highly
appreciated.
The following examples, for simplicity, don't include error handling. See the
[Handling errors](@ref errors) section for more information.
# Embedding the Nix Evaluator
In this example we programmatically start the Nix language evaluator with a
dummy store (that has no store paths and cannot be written to), and evaluate the
Nix expression `builtins.nixVersion`.
**main.c:**
```C
#include <nix_api_util.h>
#include <nix_api_expr.h>
#include <nix_api_value.h>
#include <stdio.h>
// NOTE: This example lacks all error handling. Production code must check for
// errors, as some return values will be undefined.
int main() {
nix_libexpr_init(NULL);
Store* store = nix_store_open(NULL, "dummy://", NULL);
EvalState* state = nix_state_create(NULL, NULL, store); // empty search path (NIX_PATH)
Value *value = nix_alloc_value(NULL, state);
nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value);
nix_value_force(NULL, state, value);
printf("Nix version: %s\n", nix_get_string(NULL, value));
nix_gc_decref(NULL, value);
nix_state_free(state);
nix_store_free(store);
return 0;
}
```
**Usage:**
```ShellSession
$ gcc main.c $(pkg-config nix-expr-c --libs --cflags) -o main
$ ./main
Nix version: 2.17
```
# Writing a Nix language plug-in
In this example we add a custom primitive operation (_primop_) to `builtins`. It
will increment the argument if it is an integer and throw an error otherwise.
**plugin.c:**
```C
#include <nix_api_util.h>
#include <nix_api_expr.h>
#include <nix_api_value.h>
void increment(void* user_data, nix_c_context* ctx, EvalState* state, Value** args, Value* v) {
nix_value_force(NULL, state, args[0]);
if (nix_get_type(NULL, args[0]) == NIX_TYPE_INT) {
nix_init_int(NULL, v, nix_get_int(NULL, args[0]) + 1);
} else {
nix_set_err_msg(ctx, NIX_ERR_UNKNOWN, "First argument should be an integer.");
}
}
void nix_plugin_entry() {
const char* args[] = {"n", NULL};
PrimOp *p = nix_alloc_primop(NULL, increment, 1, "increment", args, "Example custom built-in function: increments an integer", NULL);
nix_register_primop(NULL, p);
nix_gc_decref(NULL, p);
}
```
**Usage:**
```ShellSession
$ gcc plugin.c $(pkg-config nix-expr-c --libs --cflags) -shared -o plugin.so
$ nix --plugin-files ./plugin.so repl
nix-repl> builtins.increment 1
2
```

View file

@ -0,0 +1,57 @@
# Doxyfile 1.9.5
# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
# double-quotes, unless you are using Doxywizard) that should identify the
# project for which the documentation is generated. This name is used in the
# title of most generated pages and in a few other places.
# The default value is: My Project.
PROJECT_NAME = "Nix"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = @PACKAGE_VERSION@
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF = "Nix, the purely functional package manager: C API (experimental)"
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = NO
# The INPUT tag is used to specify the files and/or directories that contain
# documented source files. You may enter file names like myfile.cpp or
# directories like /usr/src/myproject. Separate the files or directories with
# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING
# Note: If this tag is empty the current directory is searched.
# FIXME Make this list more maintainable somehow. We could maybe generate this
# in the Makefile, but we would need to change how `.in` files are preprocessed
# so they can expand variables despite configure variables.
INPUT = \
src/libutil-c \
src/libexpr-c \
src/libstore-c \
doc/external-api/README.md
FILE_PATTERNS = nix_api_*.h *.md
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH = @RAPIDCHECK_HEADERS@
EXCLUDE_PATTERNS = *_internal.h
GENERATE_TREEVIEW = YES
OPTIMIZE_OUTPUT_FOR_C = YES
USE_MDFILE_AS_MAINPAGE = doc/external-api/README.md

View file

@ -0,0 +1,7 @@
$(docdir)/external-api/html/index.html $(docdir)/external-api/latex: $(d)/doxygen.cfg
mkdir -p $(docdir)/external-api
{ cat $< ; echo "OUTPUT_DIRECTORY=$(docdir)/external-api" ; } | doxygen -
# Generate the HTML API docs for Nix's unstable C bindings
.PHONY: external-api-html
external-api-html: $(docdir)/external-api/html/index.html

View file

@ -1,3 +1,25 @@
:root {
--sidebar-width: 23em;
}
h1.menu-title::before {
content: "";
background-image: url("./favicon.svg");
padding: 1.25em;
background-position: center center;
background-size: 2em;
background-repeat: no-repeat;
}
h1.menu-title {
padding: 0.5em;
}
.sidebar .sidebar-scrollbox {
padding: 1em;
}
h1:not(:first-of-type) { h1:not(:first-of-type) {
margin-top: 1.3em; margin-top: 1.3em;
} }

View file

@ -175,6 +175,16 @@ $(d)/src/SUMMARY-rl-next.md: $(d)/src/release-notes/rl-next.md
# Generate the HTML manual. # Generate the HTML manual.
.PHONY: manual-html .PHONY: manual-html
manual-html: $(docdir)/manual/index.html manual-html: $(docdir)/manual/index.html
# Open the built HTML manual in the default browser.
manual-html-open: $(docdir)/manual/index.html
@echo " OPEN " $<; \
xdg-open $< \
|| open $< \
|| { \
echo "Could not open the manual in a browser. Please open '$<'" >&2; \
false; \
}
install: $(docdir)/manual/index.html install: $(docdir)/manual/index.html
# Generate 'nix' manpages. # Generate 'nix' manpages.
@ -207,7 +217,7 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
# `@docroot@` is to be preserved for documenting the mechanism # `@docroot@` is to be preserved for documenting the mechanism
# FIXME: maybe contributing guides should live right next to the code # FIXME: maybe contributing guides should live right next to the code
# instead of in the manual # instead of in the manual
$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/store/types $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md $(d)/src/language/builtin-constants.md $(d)/src/release-notes/rl-next.md $(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/store/types $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md $(d)/src/language/builtin-constants.md $(d)/src/release-notes/rl-next.md $(d)/src/figures $(d)/src/favicon.png $(d)/src/favicon.svg
$(trace-gen) \ $(trace-gen) \
tmp="$$(mktemp -d)"; \ tmp="$$(mktemp -d)"; \
cp -r doc/manual "$$tmp"; \ cp -r doc/manual "$$tmp"; \

View file

@ -0,0 +1,8 @@
---
synopsis: Remove experimental repl-flake
significance: significant
issues: 10103
prs: 10299
---
The `repl-flake` experimental feature has been removed. The `nix repl` command now works like the rest of the new CLI in that `nix repl {path}` now tries to load a flake at `{path}` (or fails if the `flakes` experimental feature isn't enabled).*

View file

@ -119,7 +119,7 @@
- [Experimental Features](contributing/experimental-features.md) - [Experimental Features](contributing/experimental-features.md)
- [CLI guideline](contributing/cli-guideline.md) - [CLI guideline](contributing/cli-guideline.md)
- [C++ style guide](contributing/cxx.md) - [C++ style guide](contributing/cxx.md)
- [Release Notes](release-notes/index.md) - [Releases](release-notes/index.md)
{{#include ./SUMMARY-rl-next.md}} {{#include ./SUMMARY-rl-next.md}}
- [Release 2.21 (2024-03-11)](release-notes/rl-2.21.md) - [Release 2.21 (2024-03-11)](release-notes/rl-2.21.md)
- [Release 2.20 (2024-01-29)](release-notes/rl-2.20.md) - [Release 2.20 (2024-01-29)](release-notes/rl-2.20.md)

View file

@ -27,11 +27,9 @@ and open `./result-doc/share/doc/nix/manual/index.html`.
To build the manual incrementally, [enter the development shell](./hacking.md) and run: To build the manual incrementally, [enter the development shell](./hacking.md) and run:
```console ```console
make manual-html -j $NIX_BUILD_CORES make manual-html-open -j $NIX_BUILD_CORES
``` ```
and open `./outputs/out/share/doc/nix/manual/language/index.html`.
In order to reflect changes to the [Makefile for the manual], clear all generated files before re-building: In order to reflect changes to the [Makefile for the manual], clear all generated files before re-building:
[Makefile for the manual]: https://github.com/NixOS/nix/blob/master/doc/manual/local.mk [Makefile for the manual]: https://github.com/NixOS/nix/blob/master/doc/manual/local.mk

View file

@ -258,10 +258,10 @@ See [supported compilation environments](#compilation-environments) and instruct
To use the LSP with your editor, you first need to [set up `clangd`](https://clangd.llvm.org/installation#project-setup) by running: To use the LSP with your editor, you first need to [set up `clangd`](https://clangd.llvm.org/installation#project-setup) by running:
```console ```console
make clean && bear -- make -j$NIX_BUILD_CORES default check install make compile_commands.json
``` ```
Configure your editor to use the `clangd` from the shell, either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration). Configure your editor to use the `clangd` from the `.#native-clangStdenvPackages` shell. You can do that either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration).
> **Note** > **Note**
> >

BIN
doc/manual/src/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="587.11" height="516.604" viewBox="0 0 550.416 484.317"><defs><linearGradient id="a"><stop offset="0" style="stop-color:#699ad7;stop-opacity:1"/><stop offset=".243" style="stop-color:#7eb1dd;stop-opacity:1"/><stop offset="1" style="stop-color:#7ebae4;stop-opacity:1"/></linearGradient><linearGradient id="b"><stop offset="0" style="stop-color:#415e9a;stop-opacity:1"/><stop offset=".232" style="stop-color:#4a6baf;stop-opacity:1"/><stop offset="1" style="stop-color:#5277c3;stop-opacity:1"/></linearGradient><linearGradient xlink:href="#a" id="c" x1="200.597" x2="290.087" y1="351.411" y2="506.188" gradientTransform="translate(70.65 -1055.151)" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#b" id="e" x1="-584.199" x2="-496.297" y1="782.336" y2="937.714" gradientTransform="translate(864.696 -1491.34)" gradientUnits="userSpaceOnUse"/></defs><g style="display:inline;opacity:1" transform="translate(-132.651 958.04)"><path id="d" d="m309.549-710.388 122.197 211.675-56.157.527-32.624-56.87-32.856 56.566-27.903-.011-14.29-24.69 46.81-80.49-33.23-57.826z" style="opacity:1;fill:url(#c);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"/><use xlink:href="#d" width="100%" height="100%" transform="rotate(60 407.112 -715.787)"/><use xlink:href="#d" width="100%" height="100%" transform="rotate(-60 407.312 -715.7)"/><use xlink:href="#d" width="100%" height="100%" transform="rotate(180 407.419 -715.756)"/><path id="f" d="m309.549-710.388 122.197 211.675-56.157.527-32.624-56.87-32.856 56.566-27.903-.011-14.29-24.69 46.81-80.49-33.23-57.826z" style="color:#000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000;solid-opacity:1;fill:url(#e);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"/><use xlink:href="#f" width="100%" height="100%" style="display:inline" transform="rotate(120 407.34 -716.084)"/><use xlink:href="#f" width="100%" height="100%" style="display:inline" transform="rotate(-120 407.288 -715.87)"/></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -28,7 +28,7 @@ $ sudo su
## macOS multi-user ## macOS multi-user
```console ```console
$ sudo nix-env --install --file '<nixpkgs>' --attr nix -I nixpkgs=channel:nixpkgs-unstable $ sudo nix-env --install --file '<nixpkgs>' --attr nix cacert -I nixpkgs=channel:nixpkgs-unstable
$ sudo launchctl remove org.nixos.nix-daemon $ sudo launchctl remove org.nixos.nix-daemon
$ sudo launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist $ sudo launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist
``` ```

View file

@ -1,7 +1,13 @@
# Nix Language # Nix Language
The Nix language is designed for conveniently creating and composing *derivations* precise descriptions of how contents of existing files are used to derive new files. The Nix language is designed for conveniently creating and composing *derivations* precise descriptions of how contents of existing files are used to derive new files.
It is:
> **Tip**
>
> These pages are written as a reference.
> If you are learning Nix, nix.dev has a good [introduction to the Nix language](https://nix.dev/tutorials/nix-language).
The language is:
- *domain-specific* - *domain-specific*

View file

@ -1,12 +1,13 @@
# Nix Release Notes # Nix Release Notes
The Nix release cycle is calendar-based as follows:
Nix has a release cycle of roughly 6 weeks. Nix has a release cycle of roughly 6 weeks.
Notable changes and additions are announced in the release notes for each version. Notable changes and additions are announced in the release notes for each version.
Bugfixes can be backported on request to previous Nix releases. The supported Nix versions are:
We typically backport only as far back as the Nix version used in the latest NixOS release, which is announced in the [NixOS release notes](https://nixos.org/manual/nixos/stable/release-notes.html#ch-release-notes). - The latest release
- The version used in the stable NixOS release, which is announced in the [NixOS release notes](https://nixos.org/manual/nixos/stable/release-notes.html#ch-release-notes).
Backports never skip releases.
If a feature is backported to version `x.y`, it must also be available in version `x.(y+1)`.
This ensures that upgrading from an older version with backports is still safe and no backported functionality will go missing.
Bugfixes and security issues are backported to every supported version.
Patch releases are published as needed.

View file

@ -285,6 +285,13 @@
enableInternalAPIDocs = true; enableInternalAPIDocs = true;
}; };
# API docs for Nix's C bindings.
external-api-docs = nixpkgsFor.x86_64-linux.native.callPackage ./package.nix {
inherit fileset;
doBuild = false;
enableExternalAPIDocs = true;
};
# System tests. # System tests.
tests = import ./tests/nixos { inherit lib nixpkgs nixpkgsFor; } // { tests = import ./tests/nixos { inherit lib nixpkgs nixpkgsFor; } // {

View file

@ -2,9 +2,14 @@ GLOBAL_CXXFLAGS += -Wno-deprecated-declarations -Werror=switch
# Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers. # Allow switch-enum to be overridden for files that do not support it, usually because of dependency headers.
ERROR_SWITCH_ENUM = -Werror=switch-enum ERROR_SWITCH_ENUM = -Werror=switch-enum
$(foreach i, config.h $(wildcard src/lib*/*.hh), \ $(foreach i, config.h $(wildcard src/lib*/*.hh) $(wildcard src/lib*/*.h $(filter-out %_internal.h, $(wildcard src/lib*c/*.h))), \
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644))) $(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
ifdef HOST_UNIX
$(foreach i, $(wildcard src/lib*/unix/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix, 0644)))
endif
$(GCH): src/libutil/util.hh config.h $(GCH): src/libutil/util.hh config.h
GCH_CXXFLAGS = -I src/libutil GCH_CXXFLAGS = $(INCLUDE_libutil)

View file

@ -0,0 +1,11 @@
compile-commands-json-files :=
define write-compile-commands
_srcs := $$(sort $$(foreach src, $$($(1)_SOURCES), $$(src)))
$(1)_COMPILE_COMMANDS_JSON := $$(addprefix $(buildprefix), $$(addsuffix .compile_commands.json, $$(basename $$(_srcs))))
compile-commands-json-files += $$($(1)_COMPILE_COMMANDS_JSON)
clean-files += $$($(1)_COMPILE_COMMANDS_JSON)
endef

View file

@ -68,6 +68,7 @@ include mk/patterns.mk
include mk/templates.mk include mk/templates.mk
include mk/cxx-big-literal.mk include mk/cxx-big-literal.mk
include mk/tests.mk include mk/tests.mk
include mk/compilation-database.mk
# Include all sub-Makefiles. # Include all sub-Makefiles.
@ -97,6 +98,13 @@ $(foreach test-group, $(install-tests-groups), \
$(eval $(call run-test,$(test),$(install_test_init))) \ $(eval $(call run-test,$(test),$(install_test_init))) \
$(eval $(test-group).test-group: $(test).test))) $(eval $(test-group).test-group: $(test).test)))
# Compilation database.
$(foreach lib, $(libraries), $(eval $(call write-compile-commands,$(lib))))
$(foreach prog, $(programs), $(eval $(call write-compile-commands,$(prog))))
compile_commands.json: $(compile-commands-json-files)
@jq --slurp '.' $^ >$@
# Include makefiles requiring built programs. # Include makefiles requiring built programs.
$(foreach mf, $(makefiles-late), $(eval $(call include-sub-makefile,$(mf)))) $(foreach mf, $(makefiles-late), $(eval $(call include-sub-makefile,$(mf))))

View file

@ -1,11 +1,41 @@
# These are the complete command lines we use to compile C and C++ files.
# - $< is the source file.
# - $1 is the object file to create.
CC_CMD=$(CC) -o $1 -c $< $(CPPFLAGS) $(GLOBAL_CFLAGS) $(CFLAGS) $($1_CFLAGS) -MMD -MF $(call filename-to-dep,$1) -MP
CXX_CMD=$(CXX) -o $1 -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($1_CXXFLAGS) $(ERROR_SWITCH_ENUM) -MMD -MF $(call filename-to-dep,$1) -MP
# We use COMPILE_COMMANDS_JSON_CMD to turn a compilation command (like CC_CMD
# or CXX_CMD above) into a comple_commands.json file. We rely on bash native
# word splitting to define the positional arguments.
# - $< is the source file being compiled.
COMPILE_COMMANDS_JSON_CMD=jq --null-input '{ directory: $$ENV.PWD, file: "$<", arguments: $$ARGS.positional }' --args --
$(buildprefix)%.o: %.cc $(buildprefix)%.o: %.cc
@mkdir -p "$(dir $@)" @mkdir -p "$(dir $@)"
$(trace-cxx) $(CXX) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($@_CXXFLAGS) $(ERROR_SWITCH_ENUM) -MMD -MF $(call filename-to-dep, $@) -MP $(trace-cxx) $(call CXX_CMD,$@)
$(buildprefix)%.o: %.cpp $(buildprefix)%.o: %.cpp
@mkdir -p "$(dir $@)" @mkdir -p "$(dir $@)"
$(trace-cxx) $(CXX) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CXXFLAGS_PCH) $(GLOBAL_CXXFLAGS) $(CXXFLAGS) $($@_CXXFLAGS) $(ERROR_SWITCH_ENUM) -MMD -MF $(call filename-to-dep, $@) -MP $(trace-cxx) $(call CXX_CMD,$@)
$(buildprefix)%.o: %.c $(buildprefix)%.o: %.c
@mkdir -p "$(dir $@)" @mkdir -p "$(dir $@)"
$(trace-cc) $(CC) -o $@ -c $< $(CPPFLAGS) $(GLOBAL_CFLAGS) $(CFLAGS) $($@_CFLAGS) -MMD -MF $(call filename-to-dep, $@) -MP $(trace-cc) $(call CC_CMD,$@)
# In the following we need to replace the .compile_commands.json extension in $@ with .o
# to make the object file. This is needed because CC_CMD and CXX_CMD do further expansions
# based on the object file name (i.e. *_CXXFLAGS and filename-to-dep).
$(buildprefix)%.compile_commands.json: %.cc
@mkdir -p "$(dir $@)"
$(trace-jq) $(COMPILE_COMMANDS_JSON_CMD) $(call CXX_CMD,$(@:.compile_commands.json=.o)) > $@
$(buildprefix)%.compile_commands.json: %.cpp
@mkdir -p "$(dir $@)"
$(trace-jq) $(COMPILE_COMMANDS_JSON_CMD) $(call CXX_CMD,$(@:.compile_commands.json=.o)) > $@
$(buildprefix)%.compile_commands.json: %.c
@mkdir -p "$(dir $@)"
$(trace-jq) $(COMPILE_COMMANDS_JSON_CMD) $(call CC_CMD,$(@:.compile_commands.json=.o)) > $@

View file

@ -10,6 +10,8 @@ ifeq ($(V), 0)
trace-install = @echo " INST " $@; trace-install = @echo " INST " $@;
trace-mkdir = @echo " MKDIR " $@; trace-mkdir = @echo " MKDIR " $@;
trace-test = @echo " TEST " $@; trace-test = @echo " TEST " $@;
trace-sh = @echo " SH " $@;
trace-jq = @echo " JQ " $@;
suppress = @ suppress = @

View file

@ -5,6 +5,7 @@
, autoreconfHook , autoreconfHook
, aws-sdk-cpp , aws-sdk-cpp
, boehmgc , boehmgc
, buildPackages
, nlohmann_json , nlohmann_json
, bison , bison
, boost , boost
@ -75,7 +76,10 @@
# sounds so long as evaluation just takes places within short-lived # sounds so long as evaluation just takes places within short-lived
# processes. (When the process exits, the memory is reclaimed; it is # processes. (When the process exits, the memory is reclaimed; it is
# only leaked *within* the process.) # only leaked *within* the process.)
, enableGC ? true #
# Temporarily disabled on Windows because the `GC_throw_bad_alloc`
# symbol is missing during linking.
, enableGC ? !stdenv.hostPlatform.isWindows
# Whether to enable Markdown rendering in the Nix binary. # Whether to enable Markdown rendering in the Nix binary.
, enableMarkdown ? !stdenv.hostPlatform.isWindows , enableMarkdown ? !stdenv.hostPlatform.isWindows
@ -88,9 +92,10 @@
# - readline # - readline
, readlineFlavor ? if stdenv.hostPlatform.isWindows then "readline" else "editline" , readlineFlavor ? if stdenv.hostPlatform.isWindows then "readline" else "editline"
# Whether to build the internal API docs, can be done separately from # Whether to build the internal/external API docs, can be done separately from
# everything else. # everything else.
, enableInternalAPIDocs ? false , enableInternalAPIDocs ? false
, enableExternalAPIDocs ? false
# Whether to install unit tests. This is useful when cross compiling # Whether to install unit tests. This is useful when cross compiling
# since we cannot run them natively during the build, but can do so # since we cannot run them natively during the build, but can do so
@ -179,6 +184,9 @@ in {
./doc/manual ./doc/manual
] ++ lib.optionals enableInternalAPIDocs [ ] ++ lib.optionals enableInternalAPIDocs [
./doc/internal-api ./doc/internal-api
] ++ lib.optionals enableExternalAPIDocs [
./doc/external-api
] ++ lib.optionals (enableInternalAPIDocs || enableExternalAPIDocs) [
# Source might not be compiled, but still must be available # Source might not be compiled, but still must be available
# for Doxygen to gather comments. # for Doxygen to gather comments.
./src ./src
@ -196,7 +204,7 @@ in {
++ lib.optional doBuild "dev" ++ lib.optional doBuild "dev"
# If we are doing just build or just docs, the one thing will use # If we are doing just build or just docs, the one thing will use
# "out". We only need additional outputs if we are doing both. # "out". We only need additional outputs if we are doing both.
++ lib.optional (doBuild && (enableManual || enableInternalAPIDocs)) "doc" ++ lib.optional (doBuild && (enableManual || enableInternalAPIDocs || enableExternalAPIDocs)) "doc"
++ lib.optional installUnitTests "check"; ++ lib.optional installUnitTests "check";
nativeBuildInputs = [ nativeBuildInputs = [
@ -218,7 +226,7 @@ in {
] ++ lib.optionals (doInstallCheck || enableManual) [ ] ++ lib.optionals (doInstallCheck || enableManual) [
jq # Also for custom mdBook preprocessor. jq # Also for custom mdBook preprocessor.
] ++ lib.optional stdenv.hostPlatform.isLinux util-linux ] ++ lib.optional stdenv.hostPlatform.isLinux util-linux
++ lib.optional enableInternalAPIDocs doxygen ++ lib.optional (enableInternalAPIDocs || enableExternalAPIDocs) doxygen
; ;
buildInputs = lib.optionals doBuild [ buildInputs = lib.optionals doBuild [
@ -282,6 +290,7 @@ in {
(lib.enableFeature buildUnitTests "unit-tests") (lib.enableFeature buildUnitTests "unit-tests")
(lib.enableFeature doInstallCheck "functional-tests") (lib.enableFeature doInstallCheck "functional-tests")
(lib.enableFeature enableInternalAPIDocs "internal-api-docs") (lib.enableFeature enableInternalAPIDocs "internal-api-docs")
(lib.enableFeature enableExternalAPIDocs "external-api-docs")
(lib.enableFeature enableManual "doc-gen") (lib.enableFeature enableManual "doc-gen")
(lib.enableFeature enableGC "gc") (lib.enableFeature enableGC "gc")
(lib.enableFeature enableMarkdown "markdown") (lib.enableFeature enableMarkdown "markdown")
@ -306,7 +315,8 @@ in {
makeFlags = "profiledir=$(out)/etc/profile.d PRECOMPILE_HEADERS=1"; makeFlags = "profiledir=$(out)/etc/profile.d PRECOMPILE_HEADERS=1";
installTargets = lib.optional doBuild "install" installTargets = lib.optional doBuild "install"
++ lib.optional enableInternalAPIDocs "internal-api-html"; ++ lib.optional enableInternalAPIDocs "internal-api-html"
++ lib.optional enableExternalAPIDocs "external-api-html";
installFlags = "sysconfdir=$(out)/etc"; installFlags = "sysconfdir=$(out)/etc";
@ -333,6 +343,16 @@ in {
'' + lib.optionalString enableInternalAPIDocs '' '' + lib.optionalString enableInternalAPIDocs ''
mkdir -p ''${!outputDoc}/nix-support mkdir -p ''${!outputDoc}/nix-support
echo "doc internal-api-docs $out/share/doc/nix/internal-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products echo "doc internal-api-docs $out/share/doc/nix/internal-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products
''
+ lib.optionalString enableExternalAPIDocs ''
mkdir -p ''${!outputDoc}/nix-support
echo "doc external-api-docs $out/share/doc/nix/external-api/html" >> ''${!outputDoc}/nix-support/hydra-build-products
'';
# So the check output gets links for DLLs in the out output.
preFixup = lib.optionalString (stdenv.hostPlatform.isWindows && builtins.elem "check" finalAttrs.outputs) ''
ln -s "$check/lib/"*.dll "$check/bin"
ln -s "$out/bin/"*.dll "$check/bin"
''; '';
doInstallCheck = attrs.doInstallCheck; doInstallCheck = attrs.doInstallCheck;

View file

@ -28,7 +28,7 @@ else
end end
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if test -n "$NIX_SSH_CERT_FILE" if test -n "$NIX_SSL_CERT_FILE"
: # Allow users to override the NIX_SSL_CERT_FILE : # Allow users to override the NIX_SSL_CERT_FILE
else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch
set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
@ -44,7 +44,7 @@ else if test -e "$NIX_LINK/etc/ca-bundle.crt" # old cacert in Nix profile
set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt" set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
else else
# Fall back to what is in the nix profiles, favouring whatever is defined last. # Fall back to what is in the nix profiles, favouring whatever is defined last.
for i in $NIX_PROFILES for i in (string split ' ' $NIX_PROFILES)
if test -e "$i/etc/ssl/certs/ca-bundle.crt" if test -e "$i/etc/ssl/certs/ca-bundle.crt"
set --export NIX_SSL_CERT_FILE "$i/etc/ssl/certs/ca-bundle.crt" set --export NIX_SSL_CERT_FILE "$i/etc/ssl/certs/ca-bundle.crt"
end end

View file

@ -202,7 +202,7 @@ static int main_build_remote(int argc, char * * argv)
else else
drvstr = "<unknown>"; drvstr = "<unknown>";
auto error = HintFmt(errorText); auto error = HintFmt::fromFormatString(errorText);
error error
% drvstr % drvstr
% neededSystem % neededSystem

View file

@ -6,7 +6,7 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc) libcmd_SOURCES := $(wildcard $(d)/*.cc)
libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers libcmd_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libexpr) $(INCLUDE_libmain)
libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS) libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS)

View file

@ -50,7 +50,7 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
if (!rndr_res) if (!rndr_res)
throw Error("allocation error while rendering Markdown"); throw Error("allocation error while rendering Markdown");
return filterANSIEscapes(std::string(buf->data, buf->size), !shouldANSI()); return filterANSIEscapes(std::string(buf->data, buf->size), !isTTY());
#else #else
return std::string(markdown); return std::string(markdown);
#endif #endif

View file

@ -0,0 +1,186 @@
#include <cstdio>
#ifdef USE_READLINE
#include <readline/history.h>
#include <readline/readline.h>
#else
// editline < 1.15.2 don't wrap their API for C++ usage
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
// This results in linker errors due to to name-mangling of editline C symbols.
// For compatibility with these versions, we wrap the API here
// (wrapping multiple times on newer versions is no problem).
extern "C" {
#include <editline.h>
}
#endif
#include "signals.hh"
#include "finally.hh"
#include "repl-interacter.hh"
#include "file-system.hh"
#include "libcmd/repl.hh"
namespace nix {
namespace {
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
void sigintHandler(int signo)
{
g_signal_received = signo;
}
};
static detail::ReplCompleterMixin * curRepl; // ugly
static char * completionCallback(char * s, int * match)
{
auto possible = curRepl->completePrefix(s);
if (possible.size() == 1) {
*match = 1;
auto * res = strdup(possible.begin()->c_str() + strlen(s));
if (!res)
throw Error("allocation failure");
return res;
} else if (possible.size() > 1) {
auto checkAllHaveSameAt = [&](size_t pos) {
auto & first = *possible.begin();
for (auto & p : possible) {
if (p.size() <= pos || p[pos] != first[pos])
return false;
}
return true;
};
size_t start = strlen(s);
size_t len = 0;
while (checkAllHaveSameAt(start + len))
++len;
if (len > 0) {
*match = 1;
auto * res = strdup(std::string(*possible.begin(), start, len).c_str());
if (!res)
throw Error("allocation failure");
return res;
}
}
*match = 0;
return nullptr;
}
static int listPossibleCallback(char * s, char *** avp)
{
auto possible = curRepl->completePrefix(s);
if (possible.size() > (INT_MAX / sizeof(char *)))
throw Error("too many completions");
int ac = 0;
char ** vp = nullptr;
auto check = [&](auto * p) {
if (!p) {
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(vp);
}
throw Error("allocation failure");
}
return p;
};
vp = check((char **) malloc(possible.size() * sizeof(char *)));
for (auto & p : possible)
vp[ac++] = check(strdup(p.c_str()));
*avp = vp;
return ac;
}
ReadlineLikeInteracter::Guard ReadlineLikeInteracter::init(detail::ReplCompleterMixin * repl)
{
// Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl";
try {
createDirs(dirOf(historyFile));
} catch (SystemError & e) {
logWarning(e.info());
}
#ifndef USE_READLINE
el_hist_size = 1000;
#endif
read_history(historyFile.c_str());
auto oldRepl = curRepl;
curRepl = repl;
Guard restoreRepl([oldRepl] { curRepl = oldRepl; });
#ifndef USE_READLINE
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
#endif
return restoreRepl;
}
static constexpr const char * promptForType(ReplPromptType promptType)
{
switch (promptType) {
case ReplPromptType::ReplPrompt:
return "nix-repl> ";
case ReplPromptType::ContinuationPrompt:
return " ";
}
assert(false);
}
bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptType)
{
struct sigaction act, old;
sigset_t savedSignalMask, set;
auto setupSignals = [&]() {
act.sa_handler = sigintHandler;
sigfillset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGINT, &act, &old))
throw SysError("installing handler for SIGINT");
sigemptyset(&set);
sigaddset(&set, SIGINT);
if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
throw SysError("unblocking SIGINT");
};
auto restoreSignals = [&]() {
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
setupSignals();
char * s = readline(promptForType(promptType));
Finally doFree([&]() { free(s); });
restoreSignals();
if (g_signal_received) {
g_signal_received = 0;
input.clear();
return true;
}
if (!s)
return false;
input += s;
input += '\n';
return true;
}
ReadlineLikeInteracter::~ReadlineLikeInteracter()
{
write_history(historyFile.c_str());
}
};

View file

@ -0,0 +1,48 @@
#pragma once
/// @file
#include "finally.hh"
#include "types.hh"
#include <functional>
#include <string>
namespace nix {
namespace detail {
/** Provides the completion hooks for the repl, without exposing its complete
* internals. */
struct ReplCompleterMixin {
virtual StringSet completePrefix(const std::string & prefix) = 0;
};
};
enum class ReplPromptType {
ReplPrompt,
ContinuationPrompt,
};
class ReplInteracter
{
public:
using Guard = Finally<std::function<void()>>;
virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
/** Returns a boolean of whether the interacter got EOF */
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
virtual ~ReplInteracter(){};
};
class ReadlineLikeInteracter : public virtual ReplInteracter
{
std::string historyFile;
public:
ReadlineLikeInteracter(std::string historyFile)
: historyFile(historyFile)
{
}
virtual Guard init(detail::ReplCompleterMixin * repl) override;
virtual bool getLine(std::string & input, ReplPromptType promptType) override;
virtual ~ReadlineLikeInteracter() override;
};
};

View file

@ -3,32 +3,17 @@
#include <cstring> #include <cstring>
#include <climits> #include <climits>
#include <setjmp.h> #include "libcmd/repl-interacter.hh"
#ifdef USE_READLINE
#include <readline/history.h>
#include <readline/readline.h>
#else
// editline < 1.15.2 don't wrap their API for C++ usage
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
// This results in linker errors due to to name-mangling of editline C symbols.
// For compatibility with these versions, we wrap the API here
// (wrapping multiple times on newer versions is no problem).
extern "C" {
#include <editline.h>
}
#endif
#include "repl.hh" #include "repl.hh"
#include "ansicolor.hh" #include "ansicolor.hh"
#include "signals.hh"
#include "shared.hh" #include "shared.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-cache.hh" #include "eval-cache.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "eval-settings.hh" #include "eval-settings.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "signals.hh"
#include "store-api.hh" #include "store-api.hh"
#include "log-store.hh" #include "log-store.hh"
#include "common-eval-args.hh" #include "common-eval-args.hh"
@ -38,7 +23,6 @@ extern "C" {
#include "flake/flake.hh" #include "flake/flake.hh"
#include "flake/lockfile.hh" #include "flake/lockfile.hh"
#include "users.hh" #include "users.hh"
#include "terminal.hh"
#include "editor-for.hh" #include "editor-for.hh"
#include "finally.hh" #include "finally.hh"
#include "markdown.hh" #include "markdown.hh"
@ -75,6 +59,7 @@ enum class ProcessLineResult {
struct NixRepl struct NixRepl
: AbstractNixRepl : AbstractNixRepl
, detail::ReplCompleterMixin
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
, gc , gc
#endif #endif
@ -90,17 +75,16 @@ struct NixRepl
int displ; int displ;
StringSet varNames; StringSet varNames;
const Path historyFile; std::unique_ptr<ReplInteracter> interacter;
NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state, NixRepl(const SearchPath & searchPath, nix::ref<Store> store,ref<EvalState> state,
std::function<AnnotatedValues()> getValues); std::function<AnnotatedValues()> getValues);
virtual ~NixRepl(); virtual ~NixRepl() = default;
ReplExitStatus mainLoop() override; ReplExitStatus mainLoop() override;
void initEnv() override; void initEnv() override;
StringSet completePrefix(const std::string & prefix); virtual StringSet completePrefix(const std::string & prefix) override;
bool getLine(std::string & input, const std::string & prompt);
StorePath getDerivationPath(Value & v); StorePath getDerivationPath(Value & v);
ProcessLineResult processLine(std::string line); ProcessLineResult processLine(std::string line);
@ -144,16 +128,10 @@ NixRepl::NixRepl(const SearchPath & searchPath, nix::ref<Store> store, ref<EvalS
, debugTraceIndex(0) , debugTraceIndex(0)
, getValues(getValues) , getValues(getValues)
, staticEnv(new StaticEnv(nullptr, state->staticBaseEnv.get())) , staticEnv(new StaticEnv(nullptr, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history") , interacter(make_unique<ReadlineLikeInteracter>(getDataDir() + "/nix/repl-history"))
{ {
} }
NixRepl::~NixRepl()
{
write_history(historyFile.c_str());
}
void runNix(Path program, const Strings & args, void runNix(Path program, const Strings & args,
const std::optional<std::string> & input = {}) const std::optional<std::string> & input = {})
{ {
@ -170,79 +148,6 @@ void runNix(Path program, const Strings & args,
return; return;
} }
static NixRepl * curRepl; // ugly
static char * completionCallback(char * s, int *match) {
auto possible = curRepl->completePrefix(s);
if (possible.size() == 1) {
*match = 1;
auto *res = strdup(possible.begin()->c_str() + strlen(s));
if (!res) throw Error("allocation failure");
return res;
} else if (possible.size() > 1) {
auto checkAllHaveSameAt = [&](size_t pos) {
auto &first = *possible.begin();
for (auto &p : possible) {
if (p.size() <= pos || p[pos] != first[pos])
return false;
}
return true;
};
size_t start = strlen(s);
size_t len = 0;
while (checkAllHaveSameAt(start + len)) ++len;
if (len > 0) {
*match = 1;
auto *res = strdup(std::string(*possible.begin(), start, len).c_str());
if (!res) throw Error("allocation failure");
return res;
}
}
*match = 0;
return nullptr;
}
static int listPossibleCallback(char *s, char ***avp) {
auto possible = curRepl->completePrefix(s);
if (possible.size() > (INT_MAX / sizeof(char*)))
throw Error("too many completions");
int ac = 0;
char **vp = nullptr;
auto check = [&](auto *p) {
if (!p) {
if (vp) {
while (--ac >= 0)
free(vp[ac]);
free(vp);
}
throw Error("allocation failure");
}
return p;
};
vp = check((char **)malloc(possible.size() * sizeof(char*)));
for (auto & p : possible)
vp[ac++] = check(strdup(p.c_str()));
*avp = vp;
return ac;
}
namespace {
// Used to communicate to NixRepl::getLine whether a signal occurred in ::readline.
volatile sig_atomic_t g_signal_received = 0;
void sigintHandler(int signo) {
g_signal_received = signo;
}
}
static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt) static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
{ {
if (dt.isError) if (dt.isError)
@ -282,24 +187,7 @@ ReplExitStatus NixRepl::mainLoop()
loadFiles(); loadFiles();
// Allow nix-repl specific settings in .inputrc auto _guard = interacter->init(static_cast<detail::ReplCompleterMixin *>(this));
rl_readline_name = "nix-repl";
try {
createDirs(dirOf(historyFile));
} catch (SystemError & e) {
logWarning(e.info());
}
#ifndef USE_READLINE
el_hist_size = 1000;
#endif
read_history(historyFile.c_str());
auto oldRepl = curRepl;
curRepl = this;
Finally restoreRepl([&] { curRepl = oldRepl; });
#ifndef USE_READLINE
rl_set_complete_func(completionCallback);
rl_set_list_possib_func(listPossibleCallback);
#endif
std::string input; std::string input;
@ -308,7 +196,7 @@ ReplExitStatus NixRepl::mainLoop()
logger->pause(); logger->pause();
// When continuing input from previous lines, don't print a prompt, just align to the same // When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt. // number of chars as the prompt.
if (!getLine(input, input.empty() ? "nix-repl> " : " ")) { if (!interacter->getLine(input, input.empty() ? ReplPromptType::ReplPrompt : ReplPromptType::ContinuationPrompt)) {
// Ctrl-D should exit the debugger. // Ctrl-D should exit the debugger.
state->debugStop = false; state->debugStop = false;
logger->cout(""); logger->cout("");
@ -351,51 +239,6 @@ ReplExitStatus NixRepl::mainLoop()
} }
} }
bool NixRepl::getLine(std::string & input, const std::string & prompt)
{
struct sigaction act, old;
sigset_t savedSignalMask, set;
auto setupSignals = [&]() {
act.sa_handler = sigintHandler;
sigfillset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(SIGINT, &act, &old))
throw SysError("installing handler for SIGINT");
sigemptyset(&set);
sigaddset(&set, SIGINT);
if (sigprocmask(SIG_UNBLOCK, &set, &savedSignalMask))
throw SysError("unblocking SIGINT");
};
auto restoreSignals = [&]() {
if (sigprocmask(SIG_SETMASK, &savedSignalMask, nullptr))
throw SysError("restoring signals");
if (sigaction(SIGINT, &old, 0))
throw SysError("restoring handler for SIGINT");
};
setupSignals();
char * s = readline(prompt.c_str());
Finally doFree([&]() { free(s); });
restoreSignals();
if (g_signal_received) {
g_signal_received = 0;
input.clear();
return true;
}
if (!s)
return false;
input += s;
input += '\n';
return true;
}
StringSet NixRepl::completePrefix(const std::string & prefix) StringSet NixRepl::completePrefix(const std::string & prefix)
{ {
StringSet completions; StringSet completions;
@ -514,7 +357,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
if (line.empty()) if (line.empty())
return ProcessLineResult::PromptAgain; return ProcessLineResult::PromptAgain;
_isInterrupted = false; setInterrupted(false);
std::string command, arg; std::string command, arg;

View file

@ -3,11 +3,6 @@
#include "eval.hh" #include "eval.hh"
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
#include <gc/gc_cpp.h>
#endif
namespace nix { namespace nix {
struct AbstractNixRepl struct AbstractNixRepl

25
src/libexpr-c/local.mk Normal file
View file

@ -0,0 +1,25 @@
libraries += libexprc
libexprc_NAME = libnixexprc
libexprc_DIR := $(d)
libexprc_SOURCES := \
$(wildcard $(d)/*.cc) \
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libexprc := -I $(d)
libexprc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \
$(INCLUDE_libfetchers) \
$(INCLUDE_libstore) $(INCLUDE_libstorec) \
$(INCLUDE_libexpr) $(INCLUDE_libexprc)
libexprc_LIBS = libutil libutilc libstore libstorec libexpr
libexprc_LDFLAGS += $(THREAD_LDFLAGS)
$(eval $(call install-file-in, $(d)/nix-expr-c.pc, $(libdir)/pkgconfig, 0644))
libexprc_FORCE_INSTALL := 1

View file

@ -0,0 +1,10 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@
Name: Nix
Description: Nix Language Evaluator - C API
Version: @PACKAGE_VERSION@
Requires: nix-store-c
Libs: -L${libdir} -lnixexprc
Cflags: -I${includedir}/nix

View file

@ -0,0 +1,178 @@
#include <cstring>
#include <iostream>
#include <stdexcept>
#include <string>
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "util.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_store.h"
#include "nix_api_store_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#ifdef HAVE_BOEHMGC
#include <mutex>
#define GC_INCLUDE_NEW 1
#include "gc_cpp.h"
#endif
nix_err nix_libexpr_init(nix_c_context * context)
{
if (context)
context->last_err_code = NIX_OK;
{
auto ret = nix_libutil_init(context);
if (ret != NIX_OK)
return ret;
}
{
auto ret = nix_libstore_init(context);
if (ret != NIX_OK)
return ret;
}
try {
nix::initGC();
}
NIXC_CATCH_ERRS
}
nix_err nix_expr_eval_from_string(
nix_c_context * context, EvalState * state, const char * expr, const char * path, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::Expr * parsedExpr = state->state.parseExprFromString(expr, state->state.rootPath(nix::CanonPath(path)));
state->state.eval(parsedExpr, *(nix::Value *) value);
state->state.forceValue(*(nix::Value *) value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, Value * arg, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.callFunction(*(nix::Value *) fn, *(nix::Value *) arg, *(nix::Value *) value, nix::noPos);
state->state.forceValue(*(nix::Value *) value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_force(nix_c_context * context, EvalState * state, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.forceValue(*(nix::Value *) value, nix::noPos);
}
NIXC_CATCH_ERRS
}
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
state->state.forceValueDeep(*(nix::Value *) value);
}
NIXC_CATCH_ERRS
}
EvalState * nix_state_create(nix_c_context * context, const char ** searchPath_c, Store * store)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::Strings searchPath;
if (searchPath_c != nullptr)
for (size_t i = 0; searchPath_c[i] != nullptr; i++)
searchPath.push_back(searchPath_c[i]);
return new EvalState{nix::EvalState(nix::SearchPath::parse(searchPath), store->ptr)};
}
NIXC_CATCH_ERRS_NULL
}
void nix_state_free(EvalState * state)
{
delete state;
}
#ifdef HAVE_BOEHMGC
std::unordered_map<
const void *,
unsigned int,
std::hash<const void *>,
std::equal_to<const void *>,
traceable_allocator<std::pair<const void * const, unsigned int>>>
nix_refcounts;
std::mutex nix_refcount_lock;
nix_err nix_gc_incref(nix_c_context * context, const void * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::scoped_lock lock(nix_refcount_lock);
auto f = nix_refcounts.find(p);
if (f != nix_refcounts.end()) {
f->second++;
} else {
nix_refcounts[p] = 1;
}
}
NIXC_CATCH_ERRS
}
nix_err nix_gc_decref(nix_c_context * context, const void * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::scoped_lock lock(nix_refcount_lock);
auto f = nix_refcounts.find(p);
if (f != nix_refcounts.end()) {
if (--f->second == 0)
nix_refcounts.erase(f);
} else
throw std::runtime_error("nix_gc_decref: object was not referenced");
}
NIXC_CATCH_ERRS
}
void nix_gc_now()
{
GC_gcollect();
}
#else
nix_err nix_gc_incref(nix_c_context * context, const void *)
{
if (context)
context->last_err_code = NIX_OK;
return NIX_OK;
}
nix_err nix_gc_decref(nix_c_context * context, const void *)
{
if (context)
context->last_err_code = NIX_OK;
return NIX_OK;
}
void nix_gc_now() {}
#endif
void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd))
{
#ifdef HAVE_BOEHMGC
GC_REGISTER_FINALIZER(obj, finalizer, cd, 0, 0);
#endif
}

View file

@ -0,0 +1,213 @@
#ifndef NIX_API_EXPR_H
#define NIX_API_EXPR_H
/** @defgroup libexpr libexpr
* @brief Bindings to the Nix language evaluator
*
* Example (without error handling):
* @code{.c}
* int main() {
* nix_libexpr_init(NULL);
*
* Store* store = nix_store_open(NULL, "dummy", NULL);
* EvalState* state = nix_state_create(NULL, NULL, store); // empty nix path
* Value *value = nix_alloc_value(NULL, state);
*
* nix_expr_eval_from_string(NULL, state, "builtins.nixVersion", ".", value);
* nix_value_force(NULL, state, value);
* printf("nix version: %s\n", nix_get_string(NULL, value));
*
* nix_gc_decref(NULL, value);
* nix_state_free(state);
* nix_store_free(store);
* return 0;
* }
* @endcode
* @{
*/
/** @file
* @brief Main entry for the libexpr C bindings
*/
#include "nix_api_store.h"
#include "nix_api_util.h"
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
// Type definitions
/**
* @brief Represents a state of the Nix language evaluator.
*
* Multiple states can be created for multi-threaded
* operation.
* @struct EvalState
* @see nix_state_create
*/
typedef struct EvalState EvalState; // nix::EvalState
/**
* @brief Represents a value in the Nix language.
*
* Owned by the garbage collector.
* @struct Value
* @see value_manip
*/
typedef void Value; // nix::Value
// Function prototypes
/**
* @brief Initialize the Nix language evaluator.
*
* This function must be called at least once,
* at some point before constructing a EvalState for the first time.
* This function can be called multiple times, and is idempotent.
*
* @param[out] context Optional, stores error information
* @return NIX_OK if the initialization was successful, an error code otherwise.
*/
nix_err nix_libexpr_init(nix_c_context * context);
/**
* @brief Parses and evaluates a Nix expression from a string.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in] expr The Nix expression to parse.
* @param[in] path The file path to associate with the expression.
* This is required for expressions that contain relative paths (such as `./.`) that are resolved relative to the given
* directory.
* @param[out] value The result of the evaluation. You must allocate this
* yourself.
* @return NIX_OK if the evaluation was successful, an error code otherwise.
*/
nix_err nix_expr_eval_from_string(
nix_c_context * context, EvalState * state, const char * expr, const char * path, Value * value);
/**
* @brief Calls a Nix function with an argument.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in] fn The Nix function to call.
* @param[in] arg The argument to pass to the function.
* @param[out] value The result of the function call.
* @return NIX_OK if the function call was successful, an error code otherwise.
*/
nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, Value * arg, Value * value);
/**
* @brief Forces the evaluation of a Nix value.
*
* The Nix interpreter is lazy, and not-yet-evaluated Values can be
* of type NIX_TYPE_THUNK instead of their actual value.
*
* This function converts these Values into their final type.
*
* @note You don't need this function for basic API usage, since all functions
* that return a value call it for you. The only place you will see a
* NIX_TYPE_THUNK is in the arguments that are passed to a PrimOp function
* you supplied to nix_alloc_primop.
*
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in,out] value The Nix value to force.
* @post value is not of type NIX_TYPE_THUNK
* @return NIX_OK if the force operation was successful, an error code
* otherwise.
*/
nix_err nix_value_force(nix_c_context * context, EvalState * state, Value * value);
/**
* @brief Forces the deep evaluation of a Nix value.
*
* Recursively calls nix_value_force
*
* @see nix_value_force
* @warning Calling this function on a recursive data structure will cause a
* stack overflow.
* @param[out] context Optional, stores error information
* @param[in] state The state of the evaluation.
* @param[in,out] value The Nix value to force.
* @return NIX_OK if the deep force operation was successful, an error code
* otherwise.
*/
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, Value * value);
/**
* @brief Create a new Nix language evaluator state.
*
* @param[out] context Optional, stores error information
* @param[in] searchPath Array of strings corresponding to entries in NIX_PATH.
* @param[in] store The Nix store to use.
* @return A new Nix state or NULL on failure.
*/
EvalState * nix_state_create(nix_c_context * context, const char ** searchPath, Store * store);
/**
* @brief Frees a Nix state.
*
* Does not fail.
*
* @param[in] state The state to free.
*/
void nix_state_free(EvalState * state);
/** @addtogroup GC
* @brief Reference counting and garbage collector operations
*
* The Nix language evaluator uses a garbage collector. To ease C interop, we implement
* a reference counting scheme, where objects will be deallocated
* when there are no references from the Nix side, and the reference count kept
* by the C API reaches `0`.
*
* Functions returning a garbage-collected object will automatically increase
* the refcount for you. You should make sure to call `nix_gc_decref` when
* you're done with a value returned by the evaluator.
* @{
*/
/**
* @brief Increment the garbage collector reference counter for the given object.
*
* The Nix language evaluator C API keeps track of alive objects by reference counting.
* When you're done with a refcounted pointer, call nix_gc_decref().
*
* @param[out] context Optional, stores error information
* @param[in] object The object to keep alive
*/
nix_err nix_gc_incref(nix_c_context * context, const void * object);
/**
* @brief Decrement the garbage collector reference counter for the given object
*
* @param[out] context Optional, stores error information
* @param[in] object The object to stop referencing
*/
nix_err nix_gc_decref(nix_c_context * context, const void * object);
/**
* @brief Trigger the garbage collector manually
*
* You should not need to do this, but it can be useful for debugging.
*/
void nix_gc_now();
/**
* @brief Register a callback that gets called when the object is garbage
* collected.
* @note Objects can only have a single finalizer. This function overwrites existing values
* silently.
* @param[in] obj the object to watch
* @param[in] cd the data to pass to the finalizer
* @param[in] finalizer the callback function, called with obj and cd
*/
void nix_gc_register_finalizer(void * obj, void * cd, void (*finalizer)(void * obj, void * cd));
/** @} */
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_EXPR_H

View file

@ -0,0 +1,38 @@
#ifndef NIX_API_EXPR_INTERNAL_H
#define NIX_API_EXPR_INTERNAL_H
#include "eval.hh"
#include "attr-set.hh"
#include "nix_api_value.h"
struct EvalState
{
nix::EvalState state;
};
struct BindingsBuilder
{
nix::BindingsBuilder builder;
};
struct ListBuilder
{
nix::ListBuilder builder;
};
struct nix_string_return
{
std::string str;
};
struct nix_printer
{
std::ostream & s;
};
struct nix_string_context
{
nix::NixStringContext & ctx;
};
#endif // NIX_API_EXPR_INTERNAL_H

View file

@ -0,0 +1,198 @@
#include "attr-set.hh"
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "value.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_external.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_value.h"
#include "value/context.hh"
#include <nlohmann/json.hpp>
#ifdef HAVE_BOEHMGC
# include "gc/gc.h"
# define GC_INCLUDE_NEW 1
# include "gc_cpp.h"
#endif
void nix_set_string_return(nix_string_return * str, const char * c)
{
str->str = c;
}
nix_err nix_external_print(nix_c_context * context, nix_printer * printer, const char * c)
{
if (context)
context->last_err_code = NIX_OK;
try {
printer->s << c;
}
NIXC_CATCH_ERRS
}
nix_err nix_external_add_string_context(nix_c_context * context, nix_string_context * ctx, const char * c)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto r = nix::NixStringContextElem::parse(c);
ctx->ctx.insert(r);
}
NIXC_CATCH_ERRS
}
class NixCExternalValue : public nix::ExternalValueBase
{
NixCExternalValueDesc & desc;
void * v;
public:
NixCExternalValue(NixCExternalValueDesc & desc, void * v)
: desc(desc)
, v(v){};
void * get_ptr()
{
return v;
}
/**
* Print out the value
*/
virtual std::ostream & print(std::ostream & str) const override
{
nix_printer p{str};
desc.print(v, &p);
return str;
}
/**
* Return a simple string describing the type
*/
virtual std::string showType() const override
{
nix_string_return res;
desc.showType(v, &res);
return std::move(res.str);
}
/**
* Return a string to be used in builtins.typeOf
*/
virtual std::string typeOf() const override
{
nix_string_return res;
desc.typeOf(v, &res);
return std::move(res.str);
}
/**
* Coerce the value to a string.
*/
virtual std::string coerceToString(
nix::EvalState & state,
const nix::PosIdx & pos,
nix::NixStringContext & context,
bool copyMore,
bool copyToStore) const override
{
if (!desc.coerceToString) {
return nix::ExternalValueBase::coerceToString(state, pos, context, copyMore, copyToStore);
}
nix_string_context ctx{context};
nix_string_return res{""};
// todo: pos, errors
desc.coerceToString(v, &ctx, copyMore, copyToStore, &res);
if (res.str.empty()) {
return nix::ExternalValueBase::coerceToString(state, pos, context, copyMore, copyToStore);
}
return std::move(res.str);
}
/**
* Compare to another value of the same type.
*/
virtual bool operator==(const ExternalValueBase & b) const override
{
if (!desc.equal) {
return false;
}
auto r = dynamic_cast<const NixCExternalValue *>(&b);
if (!r)
return false;
return desc.equal(v, r->v);
}
/**
* Print the value as JSON.
*/
virtual nlohmann::json printValueAsJSON(
nix::EvalState & state, bool strict, nix::NixStringContext & context, bool copyToStore = true) const override
{
if (!desc.printValueAsJSON) {
return nix::ExternalValueBase::printValueAsJSON(state, strict, context, copyToStore);
}
nix_string_context ctx{context};
nix_string_return res{""};
desc.printValueAsJSON(v, (EvalState *) &state, strict, &ctx, copyToStore, &res);
if (res.str.empty()) {
return nix::ExternalValueBase::printValueAsJSON(state, strict, context, copyToStore);
}
return nlohmann::json::parse(res.str);
}
/**
* Print the value as XML.
*/
virtual void printValueAsXML(
nix::EvalState & state,
bool strict,
bool location,
nix::XMLWriter & doc,
nix::NixStringContext & context,
nix::PathSet & drvsSeen,
const nix::PosIdx pos) const override
{
if (!desc.printValueAsXML) {
return nix::ExternalValueBase::printValueAsXML(state, strict, location, doc, context, drvsSeen, pos);
}
nix_string_context ctx{context};
desc.printValueAsXML(
v, (EvalState *) &state, strict, location, &doc, &ctx, &drvsSeen,
*reinterpret_cast<const uint32_t *>(&pos));
}
virtual ~NixCExternalValue() override{};
};
ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto ret = new
#ifdef HAVE_BOEHMGC
(GC)
#endif
NixCExternalValue(*desc, v);
nix_gc_incref(nullptr, ret);
return (ExternalValue *) ret;
}
NIXC_CATCH_ERRS_NULL
}
void * nix_get_external_value_content(nix_c_context * context, ExternalValue * b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto r = dynamic_cast<NixCExternalValue *>((nix::ExternalValueBase *) b);
if (r)
return r->get_ptr();
return nullptr;
}
NIXC_CATCH_ERRS_NULL
}

View file

@ -0,0 +1,196 @@
#ifndef NIX_API_EXTERNAL_H
#define NIX_API_EXTERNAL_H
/** @ingroup libexpr
* @addtogroup Externals
* @brief Deal with external values
* @{
*/
/** @file
* @brief libexpr C bindings dealing with external values
*/
#include "nix_api_expr.h"
#include "nix_api_util.h"
#include "nix_api_value.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
/**
* @brief Represents a string owned by the Nix language evaluator.
* @see nix_set_owned_string
*/
typedef struct nix_string_return nix_string_return;
/**
* @brief Wraps a stream that can output multiple string pieces.
*/
typedef struct nix_printer nix_printer;
/**
* @brief A list of string context items
*/
typedef struct nix_string_context nix_string_context;
/**
* @brief Sets the contents of a nix_string_return
*
* Copies the passed string.
* @param[out] str the nix_string_return to write to
* @param[in] c The string to copy
*/
void nix_set_string_return(nix_string_return * str, const char * c);
/**
* Print to the nix_printer
*
* @param[out] context Optional, stores error information
* @param printer The nix_printer to print to
* @param[in] str The string to print
* @returns NIX_OK if everything worked
*/
nix_err nix_external_print(nix_c_context * context, nix_printer * printer, const char * str);
/**
* Add string context to the nix_string_context object
* @param[out] context Optional, stores error information
* @param[out] string_context The nix_string_context to add to
* @param[in] c The context string to add
* @returns NIX_OK if everything worked
*/
nix_err nix_external_add_string_context(nix_c_context * context, nix_string_context * string_context, const char * c);
/**
* @brief Definition for a class of external values
*
* Create and implement one of these, then pass it to nix_create_external_value
* Make sure to keep it alive while the external value lives.
*
* Optional functions can be set to NULL
*
* @see nix_create_external_value
*/
typedef struct NixCExternalValueDesc
{
/**
* @brief Called when printing the external value
*
* @param[in] self the void* passed to nix_create_external_value
* @param[out] printer The printer to print to, pass to nix_external_print
*/
void (*print)(void * self, nix_printer * printer);
/**
* @brief Called on :t
* @param[in] self the void* passed to nix_create_external_value
* @param[out] res the return value
*/
void (*showType)(void * self, nix_string_return * res);
/**
* @brief Called on `builtins.typeOf`
* @param self the void* passed to nix_create_external_value
* @param[out] res the return value
*/
void (*typeOf)(void * self, nix_string_return * res);
/**
* @brief Called on "${str}" and builtins.toString.
*
* The latter with coerceMore=true
* Optional, the default is to throw an error.
* @param[in] self the void* passed to nix_create_external_value
* @param[out] c writable string context for the resulting string
* @param[in] coerceMore boolean, try to coerce to strings in more cases
* instead of throwing an error
* @param[in] copyToStore boolean, whether to copy referenced paths to store
* or keep them as-is
* @param[out] res the return value. Not touching this, or setting it to the
* empty string, will make the conversion throw an error.
*/
void (*coerceToString)(
void * self, nix_string_context * c, int coerceMore, int copyToStore, nix_string_return * res);
/**
* @brief Try to compare two external values
*
* Optional, the default is always false.
* If the other object was not a Nix C external value, this comparison will
* also return false
* @param[in] self the void* passed to nix_create_external_value
* @param[in] other the void* passed to the other object's
* nix_create_external_value
* @returns true if the objects are deemed to be equal
*/
int (*equal)(void * self, void * other);
/**
* @brief Convert the external value to json
*
* Optional, the default is to throw an error
* @param[in] self the void* passed to nix_create_external_value
* @param[in] state The evaluator state
* @param[in] strict boolean Whether to force the value before printing
* @param[out] c writable string context for the resulting string
* @param[in] copyToStore whether to copy referenced paths to store or keep
* them as-is
* @param[out] res the return value. Gets parsed as JSON. Not touching this,
* or setting it to the empty string, will make the conversion throw an error.
*/
void (*printValueAsJSON)(
void * self, EvalState *, bool strict, nix_string_context * c, bool copyToStore, nix_string_return * res);
/**
* @brief Convert the external value to XML
*
* Optional, the default is to throw an error
* @todo The mechanisms for this call are incomplete. There are no C
* bindings to work with XML, pathsets and positions.
* @param[in] self the void* passed to nix_create_external_value
* @param[in] state The evaluator state
* @param[in] strict boolean Whether to force the value before printing
* @param[in] location boolean Whether to include position information in the
* xml
* @param[out] doc XML document to output to
* @param[out] c writable string context for the resulting string
* @param[in,out] drvsSeen a path set to avoid duplicating derivations
* @param[in] pos The position of the call.
*/
void (*printValueAsXML)(
void * self,
EvalState *,
int strict,
int location,
void * doc,
nix_string_context * c,
void * drvsSeen,
int pos);
} NixCExternalValueDesc;
/**
* @brief Create an external value, that can be given to nix_init_external
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer.
*
* @param[out] context Optional, stores error information
* @param[in] desc a NixCExternalValueDesc, you should keep this alive as long
* as the ExternalValue lives
* @param[in] v the value to store
* @returns external value, owned by the garbage collector
* @see nix_init_external
*/
ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v);
/**
* @brief Extract the pointer from a nix c external value.
* @param[out] context Optional, stores error information
* @param[in] b The external value
* @returns The pointer, or null if the external value was not from nix c.
* @see nix_get_external
*/
void * nix_get_external_value_content(nix_c_context * context, ExternalValue * b);
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_EXTERNAL_H

View file

@ -0,0 +1,530 @@
#include "attr-set.hh"
#include "config.hh"
#include "eval.hh"
#include "globals.hh"
#include "primops.hh"
#include "value.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "nix_api_value.h"
#ifdef HAVE_BOEHMGC
# include "gc/gc.h"
# define GC_INCLUDE_NEW 1
# include "gc_cpp.h"
#endif
// Helper function to throw an exception if value is null
static const nix::Value & check_value_not_null(const Value * value)
{
if (!value) {
throw std::runtime_error("Value is null");
}
return *((const nix::Value *) value);
}
static nix::Value & check_value_not_null(Value * value)
{
if (!value) {
throw std::runtime_error("Value is null");
}
return *((nix::Value *) value);
}
/**
* Helper function to convert calls from nix into C API.
*
* Deals with errors and converts arguments from C++ into C types.
*/
static void nix_c_primop_wrapper(
PrimOpFun f, void * userdata, nix::EvalState & state, const nix::PosIdx pos, nix::Value ** args, nix::Value & v)
{
nix_c_context ctx;
f(userdata, &ctx, (EvalState *) &state, (Value **) args, (Value *) &v);
/* TODO: In the future, this should throw different errors depending on the error code */
if (ctx.last_err_code != NIX_OK)
state.error<nix::EvalError>("Error from builtin function: %s", *ctx.last_err).atPos(pos).debugThrow();
}
PrimOp * nix_alloc_primop(
nix_c_context * context,
PrimOpFun fun,
int arity,
const char * name,
const char ** args,
const char * doc,
void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
using namespace std::placeholders;
auto p = new
#ifdef HAVE_BOEHMGC
(GC)
#endif
nix::PrimOp{
.name = name,
.args = {},
.arity = (size_t) arity,
.doc = doc,
.fun = std::bind(nix_c_primop_wrapper, fun, user_data, _1, _2, _3, _4)};
if (args)
for (size_t i = 0; args[i]; i++)
p->args.emplace_back(*args);
nix_gc_incref(nullptr, p);
return (PrimOp *) p;
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::RegisterPrimOp r(std::move(*((nix::PrimOp *) primOp)));
}
NIXC_CATCH_ERRS
}
Value * nix_alloc_value(nix_c_context * context, EvalState * state)
{
if (context)
context->last_err_code = NIX_OK;
try {
Value * res = state->state.allocValue();
nix_gc_incref(nullptr, res);
return res;
}
NIXC_CATCH_ERRS_NULL
}
ValueType nix_get_type(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
using namespace nix;
switch (v.type()) {
case nThunk:
return NIX_TYPE_THUNK;
case nInt:
return NIX_TYPE_INT;
case nFloat:
return NIX_TYPE_FLOAT;
case nBool:
return NIX_TYPE_BOOL;
case nString:
return NIX_TYPE_STRING;
case nPath:
return NIX_TYPE_PATH;
case nNull:
return NIX_TYPE_NULL;
case nAttrs:
return NIX_TYPE_ATTRS;
case nList:
return NIX_TYPE_LIST;
case nFunction:
return NIX_TYPE_FUNCTION;
case nExternal:
return NIX_TYPE_EXTERNAL;
}
return NIX_TYPE_NULL;
}
NIXC_CATCH_ERRS_RES(NIX_TYPE_NULL);
}
const char * nix_get_typename(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto s = nix::showType(v);
return strdup(s.c_str());
}
NIXC_CATCH_ERRS_NULL
}
bool nix_get_bool(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nBool);
return v.boolean;
}
NIXC_CATCH_ERRS_RES(false);
}
const char * nix_get_string(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nString);
return v.c_str();
}
NIXC_CATCH_ERRS_NULL
}
const char * nix_get_path_string(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nPath);
// NOTE (from @yorickvP)
// v._path.path should work but may not be how Eelco intended it.
// Long-term this function should be rewritten to copy some data into a
// user-allocated string.
// We could use v.path().to_string().c_str(), but I'm concerned this
// crashes. Looks like .path() allocates a CanonPath with a copy of the
// string, then it gets the underlying data from that.
return v._path.path;
}
NIXC_CATCH_ERRS_NULL
}
unsigned int nix_get_list_size(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nList);
return v.listSize();
}
NIXC_CATCH_ERRS_RES(0);
}
unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs);
return v.attrs->size();
}
NIXC_CATCH_ERRS_RES(0);
}
double nix_get_float(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nFloat);
return v.fpoint;
}
NIXC_CATCH_ERRS_RES(0.0);
}
int64_t nix_get_int(nix_c_context * context, const Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nInt);
return v.integer;
}
NIXC_CATCH_ERRS_RES(0);
}
ExternalValue * nix_get_external(nix_c_context * context, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nExternal);
return (ExternalValue *) v.external;
}
NIXC_CATCH_ERRS_NULL;
}
Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int ix)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nList);
auto * p = v.listElems()[ix];
nix_gc_incref(nullptr, p);
if (p != nullptr)
state->state.forceValue(*p, nix::noPos);
return (Value *) p;
}
NIXC_CATCH_ERRS_NULL
}
Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos);
return attr->value;
}
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr;
}
NIXC_CATCH_ERRS_NULL
}
bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs->get(s);
if (attr)
return true;
return false;
}
NIXC_CATCH_ERRS_RES(false);
}
Value *
nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i, const char ** name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
const nix::Attr & a = (*v.attrs)[i];
*name = ((const std::string &) (state->state.symbols[a.name])).c_str();
nix_gc_incref(nullptr, a.value);
state->state.forceValue(*a.value, nix::noPos);
return a.value;
}
NIXC_CATCH_ERRS_NULL
}
const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
const nix::Attr & a = (*v.attrs)[i];
return ((const std::string &) (state->state.symbols[a.name])).c_str();
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_init_bool(nix_c_context * context, Value * value, bool b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkBool(b);
}
NIXC_CATCH_ERRS
}
// todo string context
nix_err nix_init_string(nix_c_context * context, Value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkString(std::string_view(str));
}
NIXC_CATCH_ERRS
}
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * value, const char * str)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkPath(s->state.rootPath(nix::CanonPath(str)));
}
NIXC_CATCH_ERRS
}
nix_err nix_init_float(nix_c_context * context, Value * value, double d)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkFloat(d);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkInt(i);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_null(nix_c_context * context, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkNull();
}
NIXC_CATCH_ERRS
}
nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto r = (nix::ExternalValueBase *) val;
v.mkExternal(r);
}
NIXC_CATCH_ERRS
}
ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto builder = state->state.buildList(capacity);
return new
#if HAVE_BOEHMGC
(NoGC)
#endif
ListBuilder{std::move(builder)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & e = check_value_not_null(value);
list_builder->builder[index] = &e;
}
NIXC_CATCH_ERRS
}
void nix_list_builder_free(ListBuilder * list_builder)
{
#if HAVE_BOEHMGC
GC_FREE(list_builder);
#else
delete list_builder;
#endif
}
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkList(list_builder->builder);
}
NIXC_CATCH_ERRS
}
nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkPrimOp((nix::PrimOp *) p);
}
NIXC_CATCH_ERRS
}
nix_err nix_copy_value(nix_c_context * context, Value * value, Value * source)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
auto & s = check_value_not_null(source);
v = s;
}
NIXC_CATCH_ERRS
}
nix_err nix_make_attrs(nix_c_context * context, Value * value, BindingsBuilder * b)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
v.mkAttrs(b->builder);
}
NIXC_CATCH_ERRS
}
BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto bb = state->state.buildBindings(capacity);
return new
#if HAVE_BOEHMGC
(NoGC)
#endif
BindingsBuilder{std::move(bb)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * bb, const char * name, Value * value)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
nix::Symbol s = bb->builder.state.symbols.create(name);
bb->builder.insert(s, &v);
}
NIXC_CATCH_ERRS
}
void nix_bindings_builder_free(BindingsBuilder * bb)
{
#if HAVE_BOEHMGC
GC_FREE((nix::BindingsBuilder *) bb);
#else
delete (nix::BindingsBuilder *) bb;
#endif
}

View file

@ -0,0 +1,434 @@
#ifndef NIX_API_VALUE_H
#define NIX_API_VALUE_H
/** @addtogroup libexpr
* @{
*/
/** @file
* @brief libexpr C bindings dealing with values
*/
#include "nix_api_util.h"
#include "stdbool.h"
#include "stddef.h"
#include "stdint.h"
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
// Type definitions
typedef enum {
NIX_TYPE_THUNK,
NIX_TYPE_INT,
NIX_TYPE_FLOAT,
NIX_TYPE_BOOL,
NIX_TYPE_STRING,
NIX_TYPE_PATH,
NIX_TYPE_NULL,
NIX_TYPE_ATTRS,
NIX_TYPE_LIST,
NIX_TYPE_FUNCTION,
NIX_TYPE_EXTERNAL
} ValueType;
// forward declarations
typedef void Value;
typedef struct EvalState EvalState;
// type defs
/** @brief Stores an under-construction set of bindings
* @ingroup value_manip
*
* Do not reuse.
* @see nix_make_bindings_builder, nix_bindings_builder_free, nix_make_attrs
* @see nix_bindings_builder_insert
*/
typedef struct BindingsBuilder BindingsBuilder;
/** @brief Stores an under-construction list
* @ingroup value_manip
*
* Do not reuse.
* @see nix_make_list_builder, nix_list_builder_free, nix_make_list
* @see nix_list_builder_insert
*/
typedef struct ListBuilder ListBuilder;
/** @brief PrimOp function
* @ingroup primops
*
* Owned by the GC
* @see nix_alloc_primop, nix_init_primop
*/
typedef struct PrimOp PrimOp;
/** @brief External Value
* @ingroup Externals
*
* Owned by the GC
*/
typedef struct ExternalValue ExternalValue;
/** @defgroup primops
* @brief Create your own primops
* @{
*/
/** @brief Function pointer for primops
* When you want to return an error, call nix_set_err_msg(context, NIX_ERR_UNKNOWN, "your error message here").
*
* @param[in] user_data Arbitrary data that was initially supplied to nix_alloc_primop
* @param[out] context Stores error information.
* @param[in] state Evaluator state
* @param[in] args list of arguments. Note that these can be thunks and should be forced using nix_value_force before
* use.
* @param[out] ret return value
* @see nix_alloc_primop, nix_init_primop
*/
typedef void (*PrimOpFun)(void * user_data, nix_c_context * context, EvalState * state, Value ** args, Value * ret);
/** @brief Allocate a PrimOp
*
* Owned by the garbage collector.
* Use nix_gc_decref() when you're done with the returned PrimOp.
*
* @param[out] context Optional, stores error information
* @param[in] fun callback
* @param[in] arity expected number of function arguments
* @param[in] name function name
* @param[in] args array of argument names, NULL-terminated
* @param[in] doc optional, documentation for this primop
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called
* @return primop, or null in case of errors
* @see nix_init_primop
*/
PrimOp * nix_alloc_primop(
nix_c_context * context,
PrimOpFun fun,
int arity,
const char * name,
const char ** args,
const char * doc,
void * user_data);
/** @brief add a primop to the `builtins` attribute set
*
* Only applies to States created after this call.
*
* Moves your PrimOp content into the global evaluator
* registry, meaning your input PrimOp pointer is no longer usable.
* You are free to remove your references to it,
* after which it will be garbage collected.
*
* @param[out] context Optional, stores error information
* @return primop, or null in case of errors
*
*/
nix_err nix_register_primop(nix_c_context * context, PrimOp * primOp);
/** @} */
// Function prototypes
/** @brief Allocate a Nix value
*
* Owned by the GC. Use nix_gc_decref() when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @return value, or null in case of errors
*
*/
Value * nix_alloc_value(nix_c_context * context, EvalState * state);
/** @addtogroup value_manip Manipulating values
* @brief Functions to inspect and change Nix language values, represented by Value.
* @{
*/
/** @name Getters
*/
/**@{*/
/** @brief Get value type
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return type of nix value
*/
ValueType nix_get_type(nix_c_context * context, const Value * value);
/** @brief Get type name of value as defined in the evaluator
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return type name, owned string
* @todo way to free the result
*/
const char * nix_get_typename(nix_c_context * context, const Value * value);
/** @brief Get boolean value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return true or false, error info via context
*/
bool nix_get_bool(nix_c_context * context, const Value * value);
/** @brief Get string
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return string
* @return NULL in case of error.
*/
const char * nix_get_string(nix_c_context * context, const Value * value);
/** @brief Get path as string
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return string
* @return NULL in case of error.
*/
const char * nix_get_path_string(nix_c_context * context, const Value * value);
/** @brief Get the length of a list
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return length of list, error info via context
*/
unsigned int nix_get_list_size(nix_c_context * context, const Value * value);
/** @brief Get the element count of an attrset
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return attrset element count, error info via context
*/
unsigned int nix_get_attrs_size(nix_c_context * context, const Value * value);
/** @brief Get float value in 64 bits
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return float contents, error info via context
*/
double nix_get_float(nix_c_context * context, const Value * value);
/** @brief Get int value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return int contents, error info via context
*/
int64_t nix_get_int(nix_c_context * context, const Value * value);
/** @brief Get external reference
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @return reference to external, NULL in case of error
*/
ExternalValue * nix_get_external(nix_c_context * context, Value *);
/** @brief Get the ix'th element of a list
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] ix list element to get
* @return value, NULL in case of errors
*/
Value * nix_get_list_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int ix);
/** @brief Get an attr by name
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] name attribute name
* @return value, NULL in case of errors
*/
Value * nix_get_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name);
/** @brief Check if an attribute name exists on a value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] name attribute name
* @return value, error info via context
*/
bool nix_has_attr_byname(nix_c_context * context, const Value * value, EvalState * state, const char * name);
/** @brief Get an attribute by index in the sorted bindings
*
* Also gives you the name.
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] i attribute index
* @param[out] name will store a pointer to the attribute name
* @return value, NULL in case of errors
*/
Value *
nix_get_attr_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i, const char ** name);
/** @brief Get an attribute name by index in the sorted bindings
*
* Useful when you want the name but want to avoid evaluation.
*
* Owned by the nix EvalState
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] i attribute index
* @return name, NULL in case of errors
*/
const char * nix_get_attr_name_byidx(nix_c_context * context, const Value * value, EvalState * state, unsigned int i);
/**@}*/
/** @name Initializers
*
* Values are typically "returned" by initializing already allocated memory that serves as the return value.
* For this reason, the construction of values is not tied their allocation.
* Nix is a language with immutable values. Respect this property by only initializing Values once; and only initialize
* Values that are meant to be initialized by you. Failing to adhere to these rules may lead to undefined behavior.
*/
/**@{*/
/** @brief Set boolean value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] b the boolean value
* @return error code, NIX_OK on success.
*/
nix_err nix_init_bool(nix_c_context * context, Value * value, bool b);
/** @brief Set a string
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] str the string, copied
* @return error code, NIX_OK on success.
*/
nix_err nix_init_string(nix_c_context * context, Value * value, const char * str);
/** @brief Set a path
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] str the path string, copied
* @return error code, NIX_OK on success.
*/
nix_err nix_init_path_string(nix_c_context * context, EvalState * s, Value * value, const char * str);
/** @brief Set a float
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] d the float, 64-bits
* @return error code, NIX_OK on success.
*/
nix_err nix_init_float(nix_c_context * context, Value * value, double d);
/** @brief Set an int
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] i the int
* @return error code, NIX_OK on success.
*/
nix_err nix_init_int(nix_c_context * context, Value * value, int64_t i);
/** @brief Set null
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @return error code, NIX_OK on success.
*/
nix_err nix_init_null(nix_c_context * context, Value * value);
/** @brief Set an external value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] val the external value to set. Will be GC-referenced by the value.
* @return error code, NIX_OK on success.
*/
nix_err nix_init_external(nix_c_context * context, Value * value, ExternalValue * val);
/** @brief Create a list from a list builder
* @param[out] context Optional, stores error information
* @param[in] list_builder list builder to use. Make sure to unref this afterwards.
* @param[out] value Nix value to modify
* @return error code, NIX_OK on success.
*/
nix_err nix_make_list(nix_c_context * context, ListBuilder * list_builder, Value * value);
/** @brief Create a list builder
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @param[in] capacity how many bindings you'll add. Don't exceed.
* @return owned reference to a list builder. Make sure to unref when you're done.
*/
ListBuilder * nix_make_list_builder(nix_c_context * context, EvalState * state, size_t capacity);
/** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information
* @param[in] list_builder ListBuilder to insert into
* @param[in] index index to manipulate
* @param[in] value value to insert
* @return error code, NIX_OK on success.
*/
nix_err nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, unsigned int index, Value * value);
/** @brief Free a list builder
*
* Does not fail.
* @param[in] builder the builder to free
*/
void nix_list_builder_free(ListBuilder * list_builder);
/** @brief Create an attribute set from a bindings builder
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] b bindings builder to use. Make sure to unref this afterwards.
* @return error code, NIX_OK on success.
*/
nix_err nix_make_attrs(nix_c_context * context, Value * value, BindingsBuilder * b);
/** @brief Set primop
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] op primop, will be gc-referenced by the value
* @see nix_alloc_primop
* @return error code, NIX_OK on success.
*/
nix_err nix_init_primop(nix_c_context * context, Value * value, PrimOp * op);
/** @brief Copy from another value
* @param[out] context Optional, stores error information
* @param[out] value Nix value to modify
* @param[in] source value to copy from
* @return error code, NIX_OK on success.
*/
nix_err nix_copy_value(nix_c_context * context, Value * value, Value * source);
/**@}*/
/** @brief Create a bindings builder
* @param[out] context Optional, stores error information
* @param[in] state nix evaluator state
* @param[in] capacity how many bindings you'll add. Don't exceed.
* @return owned reference to a bindings builder. Make sure to unref when you're
done.
*/
BindingsBuilder * nix_make_bindings_builder(nix_c_context * context, EvalState * state, size_t capacity);
/** @brief Insert bindings into a builder
* @param[out] context Optional, stores error information
* @param[in] builder BindingsBuilder to insert into
* @param[in] name attribute name, copied into the symbol store
* @param[in] value value to give the binding
* @return error code, NIX_OK on success.
*/
nix_err
nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * builder, const char * name, Value * value);
/** @brief Free a bindings builder
*
* Does not fail.
* @param[in] builder the builder to free
*/
void nix_bindings_builder_free(BindingsBuilder * builder);
/**@}*/
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_VALUE_H

View file

@ -581,7 +581,7 @@ std::string AttrCursor::getString()
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nString && v.type() != nPath) if (v.type() != nString && v.type() != nPath)
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), showType(v)).debugThrow();
return v.type() == nString ? v.c_str() : v.path().to_string(); return v.type() == nString ? v.c_str() : v.path().to_string();
} }
@ -630,7 +630,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath) else if (v.type() == nPath)
return {v.path().to_string(), {}}; return {v.path().to_string(), {}};
else else
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow(); root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), showType(v)).debugThrow();
} }
bool AttrCursor::getBool() bool AttrCursor::getBool()

View file

@ -76,9 +76,10 @@ struct EvalSettings : Config
- Restrict file system and network access to files specified by cryptographic hash - Restrict file system and network access to files specified by cryptographic hash
- Disable impure constants: - Disable impure constants:
- [`bultins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) - [`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem)
- [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime) - [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
- [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath) - [`builtins.nixPath`](@docroot@/language/builtin-constants.md#builtins-nixPath)
- [`builtins.storePath`](@docroot@/language/builtin-constants.md#builtins-storePath)
)" )"
}; };

View file

@ -435,7 +435,14 @@ EvalState::EvalState(
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes"); static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
vEmptyList.mkList(0); vEmptyList.mkList(buildList(0));
vNull.mkNull();
vTrue.mkBool(true);
vFalse.mkBool(false);
vStringRegular.mkString("regular");
vStringDirectory.mkString("directory");
vStringSymlink.mkString("symlink");
vStringUnknown.mkString("unknown");
/* Initialise the Nix expression search path. */ /* Initialise the Nix expression search path. */
if (!evalSettings.pureEval) { if (!evalSettings.pureEval) {
@ -890,7 +897,6 @@ void Value::mkStringMove(const char * s, const NixStringContext & context)
copyContextToValue(*this, context); copyContextToValue(*this, context);
} }
void Value::mkPath(const SourcePath & path) void Value::mkPath(const SourcePath & path)
{ {
mkPath(&*path.accessor, makeImmutableString(path.path.abs())); mkPath(&*path.accessor, makeImmutableString(path.path.abs()));
@ -923,14 +929,16 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
} }
} }
void EvalState::mkList(Value & v, size_t size) ListBuilder::ListBuilder(EvalState & state, size_t size)
: size(size)
, elems(size <= 2 ? inlineElems : (Value * *) allocBytes(size * sizeof(Value *)))
{ {
v.mkList(size); state.nrListElems += size;
if (size > 2)
v.bigList.elems = (Value * *) allocBytes(size * sizeof(Value *));
nrListElems += size;
} }
Value * EvalState::getBool(bool b) {
return b ? &vTrue : &vFalse;
}
unsigned long nrThunks = 0; unsigned long nrThunks = 0;
@ -1353,9 +1361,10 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
void ExprList::eval(EvalState & state, Env & env, Value & v) void ExprList::eval(EvalState & state, Env & env, Value & v)
{ {
state.mkList(v, elems.size()); auto list = state.buildList(elems.size());
for (auto [n, v2] : enumerate(v.listItems())) for (const auto & [n, v2] : enumerate(list))
const_cast<Value * &>(v2) = elems[n]->maybeThunk(state, env); v2 = elems[n]->maybeThunk(state, env);
v.mkList(list);
} }
@ -1655,7 +1664,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
try { try {
fn->fun(*this, vCur.determinePos(noPos), args, vCur); fn->fun(*this, vCur.determinePos(noPos), args, vCur);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); if (fn->addTrace)
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw; throw;
} }
@ -1703,7 +1713,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
// so the debugger allows to inspect the wrong parameters passed to the builtin. // so the debugger allows to inspect the wrong parameters passed to the builtin.
fn->fun(*this, vCur.determinePos(noPos), vArgs, vCur); fn->fun(*this, vCur.determinePos(noPos), vArgs, vCur);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name); if (fn->addTrace)
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw; throw;
} }
@ -1945,7 +1956,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
} }
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx) void EvalState::concatLists(Value & v, size_t nrLists, Value * const * lists, const PosIdx pos, std::string_view errorCtx)
{ {
nrListConcats++; nrListConcats++;
@ -1963,14 +1974,15 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Po
return; return;
} }
mkList(v, len); auto list = buildList(len);
auto out = v.listElems(); auto out = list.elems;
for (size_t n = 0, pos = 0; n < nrLists; ++n) { for (size_t n = 0, pos = 0; n < nrLists; ++n) {
auto l = lists[n]->listSize(); auto l = lists[n]->listSize();
if (l) if (l)
memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *)); memcpy(out + pos, lists[n]->listElems(), l * sizeof(Value *));
pos += l; pos += l;
} }
v.mkList(list);
} }

View file

@ -17,6 +17,7 @@
#include <optional> #include <optional>
#include <unordered_map> #include <unordered_map>
#include <mutex> #include <mutex>
#include <functional>
namespace nix { namespace nix {
@ -69,10 +70,17 @@ struct PrimOp
*/ */
const char * doc = nullptr; const char * doc = nullptr;
/**
* Add a trace item, `while calling the '<name>' builtin`
*
* This is used to remove the redundant item for `builtins.addErrorContext`.
*/
bool addTrace = true;
/** /**
* Implementation of the primop. * Implementation of the primop.
*/ */
PrimOpFun fun; std::function<std::remove_pointer<PrimOpFun>::type> fun;
/** /**
* Optional experimental for this to be gated on. * Optional experimental for this to be gated on.
@ -186,6 +194,36 @@ public:
*/ */
Value vEmptyList; Value vEmptyList;
/**
* `null` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
Value vNull;
/**
* `true` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
Value vTrue;
/**
* `true` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
Value vFalse;
/** `"regular"` */
Value vStringRegular;
/** `"directory"` */
Value vStringDirectory;
/** `"symlink"` */
Value vStringSymlink;
/** `"unknown"` */
Value vStringUnknown;
/** /**
* The accessor for the root filesystem. * The accessor for the root filesystem.
*/ */
@ -615,7 +653,16 @@ public:
return BindingsBuilder(*this, allocBindings(capacity)); return BindingsBuilder(*this, allocBindings(capacity));
} }
void mkList(Value & v, size_t length); ListBuilder buildList(size_t size)
{
return ListBuilder(*this, size);
}
/**
* Return a boolean `Value *` without allocating.
*/
Value *getBool(bool b);
void mkThunk_(Value & v, Expr * expr); void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos); void mkPos(Value & v, PosIdx pos);
@ -662,7 +709,7 @@ public:
const SingleDerivedPath & p, const SingleDerivedPath & p,
Value & v); Value & v);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx); void concatLists(Value & v, size_t nrLists, Value * const * lists, const PosIdx pos, std::string_view errorCtx);
/** /**
* Print statistics, if enabled. * Print statistics, if enabled.
@ -756,6 +803,7 @@ private:
friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v);
friend struct Value; friend struct Value;
friend class ListBuilder;
}; };
struct DebugTraceStacker { struct DebugTraceStacker {

View file

@ -10,6 +10,7 @@
#include "finally.hh" #include "finally.hh"
#include "fetch-settings.hh" #include "fetch-settings.hh"
#include "value-to-json.hh" #include "value-to-json.hh"
#include "local-fs-store.hh"
namespace nix { namespace nix {
@ -755,7 +756,17 @@ void callFlake(EvalState & state,
auto lockedNode = node.dynamic_pointer_cast<const LockedNode>(); auto lockedNode = node.dynamic_pointer_cast<const LockedNode>();
auto [storePath, subdir] = state.store->toStorePath(sourcePath.path.abs()); // FIXME: This is a hack to support chroot stores. Remove this
// once we can pass a sourcePath rather than a storePath to
// call-flake.nix.
auto path = sourcePath.path.abs();
if (auto store = state.store.dynamic_pointer_cast<LocalFSStore>()) {
auto realStoreDir = store->getRealStoreDir();
if (isInDir(path, realStoreDir))
path = store->storeDir + path.substr(realStoreDir.size());
}
auto [storePath, subdir] = state.store->toStorePath(path);
emitTreeAttrs( emitTreeAttrs(
state, state,

View file

@ -57,11 +57,10 @@ class JSONSax : nlohmann::json_sax<json> {
ValueVector values; ValueVector values;
std::unique_ptr<JSONState> resolve(EvalState & state) override std::unique_ptr<JSONState> resolve(EvalState & state) override
{ {
Value & v = parent->value(state); auto list = state.buildList(values.size());
state.mkList(v, values.size()); for (const auto & [n, v2] : enumerate(list))
for (size_t n = 0; n < values.size(); ++n) { v2 = values[n];
v.listElems()[n] = values[n]; parent->value(state).mkList(list);
}
return std::move(parent); return std::move(parent);
} }
void add() override { void add() override {

View file

@ -11,8 +11,11 @@ libexpr_SOURCES := \
$(wildcard $(d)/flake/*.cc) \ $(wildcard $(d)/flake/*.cc) \
$(d)/lexer-tab.cc \ $(d)/lexer-tab.cc \
$(d)/parser-tab.cc $(d)/parser-tab.cc
# Not just for this library itself, but also for downstream libraries using this library
libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libmain -I src/libexpr INCLUDE_libexpr := -I $(d)
libexpr_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers) $(INCLUDE_libmain) $(INCLUDE_libexpr)
libexpr_LIBS = libutil libstore libfetchers libexpr_LIBS = libutil libstore libfetchers

View file

@ -187,13 +187,13 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
NixStringContextElem::DrvDeep { .drvPath = *storePath }, NixStringContextElem::DrvDeep { .drvPath = *storePath },
}); });
attrs.alloc(state.sName).mkString(drv.env["name"]); attrs.alloc(state.sName).mkString(drv.env["name"]);
auto & outputsVal = attrs.alloc(state.sOutputs);
state.mkList(outputsVal, drv.outputs.size());
auto list = state.buildList(drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) { for (const auto & [i, o] : enumerate(drv.outputs)) {
mkOutputString(state, attrs, *storePath, o); mkOutputString(state, attrs, *storePath, o);
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first); (list[i] = state.allocValue())->mkString(o.first);
} }
attrs.alloc(state.sOutputs).mkList(list);
auto w = state.allocValue(); auto w = state.allocValue();
w->mkAttrs(attrs); w->mkAttrs(attrs);
@ -694,10 +694,10 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
} }
/* Create the result list. */ /* Create the result list. */
state.mkList(v, res.size()); auto list = state.buildList(res.size());
unsigned int n = 0; for (const auto & [n, i] : enumerate(res))
for (auto & i : res) list[n] = i;
v.listElems()[n++] = i; v.mkList(list);
} }
static RegisterPrimOp primop_genericClosure(PrimOp { static RegisterPrimOp primop_genericClosure(PrimOp {
@ -826,7 +826,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
auto message = state.coerceToString(pos, *args[0], context, auto message = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.addErrorContext", "while evaluating the error message passed to builtins.addErrorContext",
false, false).toOwned(); false, false).toOwned();
e.addTrace(nullptr, HintFmt(message)); e.addTrace(nullptr, HintFmt(message), TracePrint::Always);
throw; throw;
} }
} }
@ -834,6 +834,8 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
static RegisterPrimOp primop_addErrorContext(PrimOp { static RegisterPrimOp primop_addErrorContext(PrimOp {
.name = "__addErrorContext", .name = "__addErrorContext",
.arity = 2, .arity = 2,
// The normal trace item is redundant
.addTrace = false,
.fun = prim_addErrorContext, .fun = prim_addErrorContext,
}); });
@ -896,10 +898,11 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
try { try {
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
attrs.insert(state.sValue, args[0]); attrs.insert(state.sValue, args[0]);
attrs.alloc("success").mkBool(true); attrs.insert(state.symbols.create("success"), &state.vTrue);
} catch (AssertionError & e) { } catch (AssertionError & e) {
attrs.alloc(state.sValue).mkBool(false); // `value = false;` is unfortunate but removing it is a breaking change.
attrs.alloc("success").mkBool(false); attrs.insert(state.sValue, &state.vFalse);
attrs.insert(state.symbols.create("success"), &state.vFalse);
} }
// restore the debugRepl pointer if we saved it earlier. // restore the debugRepl pointer if we saved it earlier.
@ -1124,7 +1127,7 @@ drvName, Bindings * attrs, Value & v)
bool contentAddressed = false; bool contentAddressed = false;
bool isImpure = false; bool isImpure = false;
std::optional<std::string> outputHash; std::optional<std::string> outputHash;
std::string outputHashAlgo; std::optional<HashAlgorithm> outputHashAlgo;
std::optional<ContentAddressMethod> ingestionMethod; std::optional<ContentAddressMethod> ingestionMethod;
StringSet outputs; StringSet outputs;
@ -1223,7 +1226,7 @@ drvName, Bindings * attrs, Value & v)
else if (i->name == state.sOutputHash) else if (i->name == state.sOutputHash)
outputHash = state.forceStringNoCtx(*i->value, pos, context_below); outputHash = state.forceStringNoCtx(*i->value, pos, context_below);
else if (i->name == state.sOutputHashAlgo) else if (i->name == state.sOutputHashAlgo)
outputHashAlgo = state.forceStringNoCtx(*i->value, pos, context_below); outputHashAlgo = parseHashAlgoOpt(state.forceStringNoCtx(*i->value, pos, context_below));
else if (i->name == state.sOutputHashMode) else if (i->name == state.sOutputHashMode)
handleHashMode(state.forceStringNoCtx(*i->value, pos, context_below)); handleHashMode(state.forceStringNoCtx(*i->value, pos, context_below));
else if (i->name == state.sOutputs) { else if (i->name == state.sOutputs) {
@ -1241,7 +1244,7 @@ drvName, Bindings * attrs, Value & v)
if (i->name == state.sBuilder) drv.builder = std::move(s); if (i->name == state.sBuilder) drv.builder = std::move(s);
else if (i->name == state.sSystem) drv.platform = std::move(s); else if (i->name == state.sSystem) drv.platform = std::move(s);
else if (i->name == state.sOutputHash) outputHash = std::move(s); else if (i->name == state.sOutputHash) outputHash = std::move(s);
else if (i->name == state.sOutputHashAlgo) outputHashAlgo = std::move(s); else if (i->name == state.sOutputHashAlgo) outputHashAlgo = parseHashAlgoOpt(s);
else if (i->name == state.sOutputHashMode) handleHashMode(s); else if (i->name == state.sOutputHashMode) handleHashMode(s);
else if (i->name == state.sOutputs) else if (i->name == state.sOutputs)
handleOutputs(tokenizeString<Strings>(s)); handleOutputs(tokenizeString<Strings>(s));
@ -1324,7 +1327,7 @@ drvName, Bindings * attrs, Value & v)
"multiple outputs are not supported in fixed-output derivations" "multiple outputs are not supported in fixed-output derivations"
).atPos(v).debugThrow(); ).atPos(v).debugThrow();
auto h = newHashAllowEmpty(*outputHash, parseHashAlgoOpt(outputHashAlgo)); auto h = newHashAllowEmpty(*outputHash, outputHashAlgo);
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat); auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
@ -1344,7 +1347,7 @@ drvName, Bindings * attrs, Value & v)
state.error<EvalError>("derivation cannot be both content-addressed and impure") state.error<EvalError>("derivation cannot be both content-addressed and impure")
.atPos(v).debugThrow(); .atPos(v).debugThrow();
auto ha = parseHashAlgoOpt(outputHashAlgo).value_or(HashAlgorithm::SHA256); auto ha = outputHashAlgo.value_or(HashAlgorithm::SHA256);
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive); auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
for (auto & i : outputs) { for (auto & i : outputs) {
@ -1567,23 +1570,50 @@ static RegisterPrimOp primop_pathExists({
.fun = prim_pathExists, .fun = prim_pathExists,
}); });
// Ideally, all trailing slashes should have been removed, but it's been like this for
// almost a decade as of writing. Changing it will affect reproducibility.
static std::string_view legacyBaseNameOf(std::string_view path)
{
if (path.empty())
return "";
auto last = path.size() - 1;
if (path[last] == '/' && last > 0)
last -= 1;
auto pos = path.rfind('/', last);
if (pos == path.npos)
pos = 0;
else
pos += 1;
return path.substr(pos, last - pos + 1);
}
/* Return the base name of the given string, i.e., everything /* Return the base name of the given string, i.e., everything
following the last slash. */ following the last slash. */
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
NixStringContext context; NixStringContext context;
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, v.mkString(legacyBaseNameOf(*state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.baseNameOf", "while evaluating the first argument passed to builtins.baseNameOf",
false, false)), context); false, false)), context);
} }
static RegisterPrimOp primop_baseNameOf({ static RegisterPrimOp primop_baseNameOf({
.name = "baseNameOf", .name = "baseNameOf",
.args = {"s"}, .args = {"x"},
.doc = R"( .doc = R"(
Return the *base name* of the string *s*, that is, everything Return the *base name* of either a [path value](@docroot@/language/values.md#type-path) *x* or a string *x*, depending on which type is passed, and according to the following rules.
following the final slash in the string. This is similar to the GNU
`basename` command. For a path value, the *base name* is considered to be the part of the path after the last directory separator, including any file extensions.
This is the simple case, as path values don't have trailing slashes.
When the argument is a string, a more involved logic applies. If the string ends with a `/`, only this one final slash is removed.
After this, the *base name* is returned as previously described, assuming `/` as the directory separator. (Note that evaluation must be platform independent.)
This is somewhat similar to the [GNU `basename`](https://www.gnu.org/software/coreutils/manual/html_node/basename-invocation.html) command, but GNU `basename` will strip any number of trailing slashes.
)", )",
.fun = prim_baseNameOf, .fun = prim_baseNameOf,
}); });
@ -1775,20 +1805,20 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile, .fun = prim_hashFile,
}); });
static std::string_view fileTypeToString(InputAccessor::Type type) static Value * fileTypeToString(EvalState & state, InputAccessor::Type type)
{ {
return return
type == InputAccessor::Type::tRegular ? "regular" : type == InputAccessor::Type::tRegular ? &state.vStringRegular :
type == InputAccessor::Type::tDirectory ? "directory" : type == InputAccessor::Type::tDirectory ? &state.vStringDirectory :
type == InputAccessor::Type::tSymlink ? "symlink" : type == InputAccessor::Type::tSymlink ? &state.vStringSymlink :
"unknown"; &state.vStringUnknown;
} }
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto path = realisePath(state, pos, *args[0], std::nullopt); auto path = realisePath(state, pos, *args[0], std::nullopt);
/* Retrieve the directory entry type and stringize it. */ /* Retrieve the directory entry type and stringize it. */
v.mkString(fileTypeToString(path.lstat().type)); v = *fileTypeToString(state, path.lstat().type);
} }
static RegisterPrimOp primop_readFileType({ static RegisterPrimOp primop_readFileType({
@ -1819,8 +1849,8 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
Value * readFileType = nullptr; Value * readFileType = nullptr;
for (auto & [name, type] : entries) { for (auto & [name, type] : entries) {
auto & attr = attrs.alloc(name);
if (!type) { if (!type) {
auto & attr = attrs.alloc(name);
// Some filesystems or operating systems may not be able to return // Some filesystems or operating systems may not be able to return
// detailed node info quickly in this case we produce a thunk to // detailed node info quickly in this case we produce a thunk to
// query the file type lazily. // query the file type lazily.
@ -1832,7 +1862,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
} else { } else {
// This branch of the conditional is much more likely. // This branch of the conditional is much more likely.
// Here we just stringize the directory entry type. // Here we just stringize the directory entry type.
attr.mkString(fileTypeToString(*type)); attrs.insert(state.symbols.create(name), fileTypeToString(state, *type));
} }
} }
@ -2193,11 +2223,8 @@ bool EvalState::callPathFilter(
Value arg1; Value arg1;
arg1.mkString(pathArg); arg1.mkString(pathArg);
Value arg2;
// assert that type is not "unknown" // assert that type is not "unknown"
arg2.mkString(fileTypeToString(st.type)); Value * args []{&arg1, fileTypeToString(*this, st.type)};
Value * args []{&arg1, &arg2};
Value res; Value res;
callFunction(*filterFun, 2, args, res, pos); callFunction(*filterFun, 2, args, res, pos);
@ -2423,14 +2450,15 @@ static void prim_attrNames(EvalState & state, const PosIdx pos, Value * * args,
{ {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames"); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrNames");
state.mkList(v, args[0]->attrs->size()); auto list = state.buildList(args[0]->attrs->size());
size_t n = 0; for (const auto & [n, i] : enumerate(*args[0]->attrs))
for (auto & i : *args[0]->attrs) (list[n] = state.allocValue())->mkString(state.symbols[i.name]);
(v.listElems()[n++] = state.allocValue())->mkString(state.symbols[i.name]);
std::sort(v.listElems(), v.listElems() + n, std::sort(list.begin(), list.end(),
[](Value * v1, Value * v2) { return strcmp(v1->c_str(), v2->c_str()) < 0; }); [](Value * v1, Value * v2) { return strcmp(v1->c_str(), v2->c_str()) < 0; });
v.mkList(list);
} }
static RegisterPrimOp primop_attrNames({ static RegisterPrimOp primop_attrNames({
@ -2450,21 +2478,22 @@ static void prim_attrValues(EvalState & state, const PosIdx pos, Value * * args,
{ {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues"); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.attrValues");
state.mkList(v, args[0]->attrs->size()); auto list = state.buildList(args[0]->attrs->size());
unsigned int n = 0; for (const auto & [n, i] : enumerate(*args[0]->attrs))
for (auto & i : *args[0]->attrs) list[n] = (Value *) &i;
v.listElems()[n++] = (Value *) &i;
std::sort(v.listElems(), v.listElems() + n, std::sort(list.begin(), list.end(),
[&](Value * v1, Value * v2) { [&](Value * v1, Value * v2) {
std::string_view s1 = state.symbols[((Attr *) v1)->name], std::string_view s1 = state.symbols[((Attr *) v1)->name],
s2 = state.symbols[((Attr *) v2)->name]; s2 = state.symbols[((Attr *) v2)->name];
return s1 < s2; return s1 < s2;
}); });
for (unsigned int i = 0; i < n; ++i) for (auto & v : list)
v.listElems()[i] = ((Attr *) v.listElems()[i])->value; v = ((Attr *) v)->value;
v.mkList(list);
} }
static RegisterPrimOp primop_attrValues({ static RegisterPrimOp primop_attrValues({
@ -2805,9 +2834,10 @@ static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, V
res[found++] = i->value; res[found++] = i->value;
} }
state.mkList(v, found); auto list = state.buildList(found);
for (unsigned int n = 0; n < found; ++n) for (unsigned int n = 0; n < found; ++n)
v.listElems()[n] = res[n]; list[n] = res[n];
v.mkList(list);
} }
static RegisterPrimOp primop_catAttrs({ static RegisterPrimOp primop_catAttrs({
@ -2844,8 +2874,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size()); auto attrs = state.buildBindings(args[0]->lambda.fun->formals->formals.size());
for (auto & i : args[0]->lambda.fun->formals->formals) for (auto & i : args[0]->lambda.fun->formals->formals)
// !!! should optimise booleans (allocate only once) attrs.insert(i.name, state.getBool(i.def), i.pos);
attrs.alloc(i.name, i.pos).mkBool(i.def);
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
@ -2908,43 +2937,50 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
// attribute with the merge function application. this way we need not // attribute with the merge function application. this way we need not
// use (slightly slower) temporary storage the GC does not know about. // use (slightly slower) temporary storage the GC does not know about.
std::map<Symbol, std::pair<size_t, Value * *>> attrsSeen; struct Item
{
size_t size = 0;
size_t pos = 0;
std::optional<ListBuilder> list;
};
std::map<Symbol, Item> attrsSeen;
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.zipAttrsWith");
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith"); state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.zipAttrsWith");
const auto listSize = args[1]->listSize(); const auto listItems = args[1]->listItems();
const auto listElems = args[1]->listElems();
for (unsigned int n = 0; n < listSize; ++n) { for (auto & vElem : listItems) {
Value * vElem = listElems[n];
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith"); state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
for (auto & attr : *vElem->attrs) for (auto & attr : *vElem->attrs)
attrsSeen[attr.name].first++; attrsSeen.try_emplace(attr.name).first->second.size++;
}
for (auto & [sym, elem] : attrsSeen)
elem.list.emplace(state.buildList(elem.size));
for (auto & vElem : listItems) {
for (auto & attr : *vElem->attrs) {
auto & item = attrsSeen.at(attr.name);
(*item.list)[item.pos++] = attr.value;
}
} }
auto attrs = state.buildBindings(attrsSeen.size()); auto attrs = state.buildBindings(attrsSeen.size());
for (auto & [sym, elem] : attrsSeen) { for (auto & [sym, elem] : attrsSeen) {
auto & list = attrs.alloc(sym);
state.mkList(list, elem.first);
elem.second = list.listElems();
}
v.mkAttrs(attrs.alreadySorted());
for (unsigned int n = 0; n < listSize; ++n) {
Value * vElem = listElems[n];
for (auto & attr : *vElem->attrs)
*attrsSeen[attr.name].second++ = attr.value;
}
for (auto & attr : *v.attrs) {
auto name = state.allocValue(); auto name = state.allocValue();
name->mkString(state.symbols[attr.name]); name->mkString(state.symbols[sym]);
auto call1 = state.allocValue(); auto call1 = state.allocValue();
call1->mkApp(args[0], name); call1->mkApp(args[0], name);
auto call2 = state.allocValue(); auto call2 = state.allocValue();
call2->mkApp(call1, attr.value); auto arg = state.allocValue();
attr.value = call2; arg->mkList(*elem.list);
call2->mkApp(call1, arg);
attrs.insert(sym, call2);
} }
v.mkAttrs(attrs.alreadySorted());
} }
static RegisterPrimOp primop_zipAttrsWith({ static RegisterPrimOp primop_zipAttrsWith({
@ -3055,9 +3091,10 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
if (args[0]->listSize() == 0) if (args[0]->listSize() == 0)
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow(); state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
state.mkList(v, args[0]->listSize() - 1); auto list = state.buildList(args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n) for (const auto & [n, v] : enumerate(list))
v.listElems()[n] = args[0]->listElems()[n + 1]; v = args[0]->listElems()[n + 1];
v.mkList(list);
} }
static RegisterPrimOp primop_tail({ static RegisterPrimOp primop_tail({
@ -3088,10 +3125,11 @@ static void prim_map(EvalState & state, const PosIdx pos, Value * * args, Value
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.map");
state.mkList(v, args[1]->listSize()); auto list = state.buildList(args[1]->listSize());
for (unsigned int n = 0; n < v.listSize(); ++n) for (const auto & [n, v] : enumerate(list))
(v.listElems()[n] = state.allocValue())->mkApp( (v = state.allocValue())->mkApp(
args[0], args[1]->listElems()[n]); args[0], args[1]->listElems()[n]);
v.mkList(list);
} }
static RegisterPrimOp primop_map({ static RegisterPrimOp primop_map({
@ -3140,8 +3178,9 @@ static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Val
if (same) if (same)
v = *args[1]; v = *args[1];
else { else {
state.mkList(v, k); auto list = state.buildList(k);
for (unsigned int n = 0; n < k; ++n) v.listElems()[n] = vs[n]; for (const auto & [n, v] : enumerate(list)) v = vs[n];
v.mkList(list);
} }
} }
@ -3316,12 +3355,13 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
// as evaluating map without accessing any values makes little sense. // as evaluating map without accessing any values makes little sense.
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList"); state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
state.mkList(v, len); auto list = state.buildList(len);
for (unsigned int n = 0; n < (unsigned int) len; ++n) { for (const auto & [n, v] : enumerate(list)) {
auto arg = state.allocValue(); auto arg = state.allocValue();
arg->mkInt(n); arg->mkInt(n);
(v.listElems()[n] = state.allocValue())->mkApp(args[0], arg); (v = state.allocValue())->mkApp(args[0], arg);
} }
v.mkList(list);
} }
static RegisterPrimOp primop_genList({ static RegisterPrimOp primop_genList({
@ -3355,19 +3395,20 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.sort"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.sort");
state.mkList(v, len); auto list = state.buildList(len);
for (unsigned int n = 0; n < len; ++n) { for (const auto & [n, v] : enumerate(list))
state.forceValue(*args[1]->listElems()[n], pos); state.forceValue(*(v = args[1]->listElems()[n]), pos);
v.listElems()[n] = args[1]->listElems()[n];
}
auto comparator = [&](Value * a, Value * b) { auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass /* Optimization: if the comparator is lessThan, bypass
callFunction. */ callFunction. */
/* TODO: (layus) this is absurd. An optimisation like this /* TODO: (layus) this is absurd. An optimisation like this
should be outside the lambda creation */ should be outside the lambda creation */
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan) if (args[0]->isPrimOp()) {
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b); auto ptr = args[0]->primOp->fun.target<decltype(&prim_lessThan)>();
if (ptr && *ptr == prim_lessThan)
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
}
Value * vs[] = {a, b}; Value * vs[] = {a, b};
Value vBool; Value vBool;
@ -3378,7 +3419,9 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
/* FIXME: std::sort can segfault if the comparator is not a strict /* FIXME: std::sort can segfault if the comparator is not a strict
weak ordering. What to do? std::stable_sort() seems more weak ordering. What to do? std::stable_sort() seems more
resilient, but no guarantees... */ resilient, but no guarantees... */
std::stable_sort(v.listElems(), v.listElems() + len, comparator); std::stable_sort(list.begin(), list.end(), comparator);
v.mkList(list);
} }
static RegisterPrimOp primop_sort({ static RegisterPrimOp primop_sort({
@ -3424,17 +3467,17 @@ static void prim_partition(EvalState & state, const PosIdx pos, Value * * args,
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
auto & vRight = attrs.alloc(state.sRight);
auto rsize = right.size(); auto rsize = right.size();
state.mkList(vRight, rsize); auto rlist = state.buildList(rsize);
if (rsize) if (rsize)
memcpy(vRight.listElems(), right.data(), sizeof(Value *) * rsize); memcpy(rlist.elems, right.data(), sizeof(Value *) * rsize);
attrs.alloc(state.sRight).mkList(rlist);
auto & vWrong = attrs.alloc(state.sWrong);
auto wsize = wrong.size(); auto wsize = wrong.size();
state.mkList(vWrong, wsize); auto wlist = state.buildList(wsize);
if (wsize) if (wsize)
memcpy(vWrong.listElems(), wrong.data(), sizeof(Value *) * wsize); memcpy(wlist.elems, wrong.data(), sizeof(Value *) * wsize);
attrs.alloc(state.sWrong).mkList(wlist);
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
@ -3481,10 +3524,10 @@ static void prim_groupBy(EvalState & state, const PosIdx pos, Value * * args, Va
auto attrs2 = state.buildBindings(attrs.size()); auto attrs2 = state.buildBindings(attrs.size());
for (auto & i : attrs) { for (auto & i : attrs) {
auto & list = attrs2.alloc(i.first);
auto size = i.second.size(); auto size = i.second.size();
state.mkList(list, size); auto list = state.buildList(size);
memcpy(list.listElems(), i.second.data(), sizeof(Value *) * size); memcpy(list.elems, i.second.data(), sizeof(Value *) * size);
attrs2.alloc(i.first).mkList(list);
} }
v.mkAttrs(attrs2.alreadySorted()); v.mkAttrs(attrs2.alreadySorted());
@ -3531,14 +3574,15 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
len += lists[n].listSize(); len += lists[n].listSize();
} }
state.mkList(v, len); auto list = state.buildList(len);
auto out = v.listElems(); auto out = list.elems;
for (unsigned int n = 0, pos = 0; n < nrLists; ++n) { for (unsigned int n = 0, pos = 0; n < nrLists; ++n) {
auto l = lists[n].listSize(); auto l = lists[n].listSize();
if (l) if (l)
memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *)); memcpy(out + pos, lists[n].listElems(), l * sizeof(Value *));
pos += l; pos += l;
} }
v.mkList(list);
} }
static RegisterPrimOp primop_concatMap({ static RegisterPrimOp primop_concatMap({
@ -3820,7 +3864,7 @@ static RegisterPrimOp primop_stringLength({
.name = "__stringLength", .name = "__stringLength",
.args = {"e"}, .args = {"e"},
.doc = R"( .doc = R"(
Return the length of the string *e*. If *e* is not a string, Return the number of bytes of the string *e*. If *e* is not a string,
evaluation is aborted. evaluation is aborted.
)", )",
.fun = prim_stringLength, .fun = prim_stringLength,
@ -3986,14 +4030,13 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} }
// the first match is the whole string // the first match is the whole string
const size_t len = match.size() - 1; auto list = state.buildList(match.size() - 1);
state.mkList(v, len); for (const auto & [i, v2] : enumerate(list))
for (size_t i = 0; i < len; ++i) { if (!match[i + 1].matched)
if (!match[i+1].matched) v2 = &state.vNull;
(v.listElems()[i] = state.allocValue())->mkNull();
else else
(v.listElems()[i] = state.allocValue())->mkString(match[i + 1].str()); (v2 = state.allocValue())->mkString(match[i + 1].str());
} v.mkList(list);
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
@ -4062,11 +4105,12 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
// Any matches results are surrounded by non-matching results. // Any matches results are surrounded by non-matching results.
const size_t len = std::distance(begin, end); const size_t len = std::distance(begin, end);
state.mkList(v, 2 * len + 1); auto list = state.buildList(2 * len + 1);
size_t idx = 0; size_t idx = 0;
if (len == 0) { if (len == 0) {
v.listElems()[idx++] = args[1]; list[0] = args[1];
v.mkList(list);
return; return;
} }
@ -4075,28 +4119,31 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto match = *i; auto match = *i;
// Add a string for non-matched characters. // Add a string for non-matched characters.
(v.listElems()[idx++] = state.allocValue())->mkString(match.prefix().str()); (list[idx++] = state.allocValue())->mkString(match.prefix().str());
// Add a list for matched substrings. // Add a list for matched substrings.
const size_t slen = match.size() - 1; const size_t slen = match.size() - 1;
auto elem = v.listElems()[idx++] = state.allocValue();
// Start at 1, beacause the first match is the whole string. // Start at 1, beacause the first match is the whole string.
state.mkList(*elem, slen); auto list2 = state.buildList(slen);
for (size_t si = 0; si < slen; ++si) { for (const auto & [si, v2] : enumerate(list2)) {
if (!match[si + 1].matched) if (!match[si + 1].matched)
(elem->listElems()[si] = state.allocValue())->mkNull(); v2 = &state.vNull;
else else
(elem->listElems()[si] = state.allocValue())->mkString(match[si + 1].str()); (v2 = state.allocValue())->mkString(match[si + 1].str());
} }
(list[idx++] = state.allocValue())->mkList(list2);
// Add a string for non-matched suffix characters. // Add a string for non-matched suffix characters.
if (idx == 2 * len) if (idx == 2 * len)
(v.listElems()[idx++] = state.allocValue())->mkString(match.suffix().str()); (list[idx++] = state.allocValue())->mkString(match.suffix().str());
} }
assert(idx == 2 * len + 1); assert(idx == 2 * len + 1);
v.mkList(list);
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
@ -4316,9 +4363,10 @@ static void prim_splitVersion(EvalState & state, const PosIdx pos, Value * * arg
break; break;
components.emplace_back(component); components.emplace_back(component);
} }
state.mkList(v, components.size()); auto list = state.buildList(components.size());
for (const auto & [n, component] : enumerate(components)) for (const auto & [n, component] : enumerate(components))
(v.listElems()[n] = state.allocValue())->mkString(std::move(component)); (list[n] = state.allocValue())->mkString(std::move(component));
v.mkList(list);
} }
static RegisterPrimOp primop_splitVersion({ static RegisterPrimOp primop_splitVersion({
@ -4411,8 +4459,7 @@ void EvalState::createBaseEnv()
)", )",
}); });
v.mkNull(); addConstant("null", &vNull, {
addConstant("null", v, {
.type = nNull, .type = nNull,
.doc = R"( .doc = R"(
Primitive value. Primitive value.
@ -4559,14 +4606,14 @@ void EvalState::createBaseEnv()
}); });
/* Add a value containing the current Nix expression search path. */ /* Add a value containing the current Nix expression search path. */
mkList(v, searchPath.elements.size()); auto list = buildList(searchPath.elements.size());
int n = 0; for (const auto & [n, i] : enumerate(searchPath.elements)) {
for (auto & i : searchPath.elements) {
auto attrs = buildBindings(2); auto attrs = buildBindings(2);
attrs.alloc("path").mkString(i.path.s); attrs.alloc("path").mkString(i.path.s);
attrs.alloc("prefix").mkString(i.prefix.s); attrs.alloc("prefix").mkString(i.prefix.s);
(v.listElems()[n++] = allocValue())->mkAttrs(attrs); (list[n] = allocValue())->mkAttrs(attrs);
} }
v.mkList(list);
addConstant("__nixPath", v, { addConstant("__nixPath", v, {
.type = nList, .type = nList,
.doc = R"( .doc = R"(

View file

@ -207,10 +207,10 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
if (info.second.allOutputs) if (info.second.allOutputs)
infoAttrs.alloc(sAllOutputs).mkBool(true); infoAttrs.alloc(sAllOutputs).mkBool(true);
if (!info.second.outputs.empty()) { if (!info.second.outputs.empty()) {
auto & outputsVal = infoAttrs.alloc(state.sOutputs); auto list = state.buildList(info.second.outputs.size());
state.mkList(outputsVal, info.second.outputs.size());
for (const auto & [i, output] : enumerate(info.second.outputs)) for (const auto & [i, output] : enumerate(info.second.outputs))
(outputsVal.listElems()[i] = state.allocValue())->mkString(output); (list[i] = state.allocValue())->mkString(output);
infoAttrs.alloc(state.sOutputs).mkList(list);
} }
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs); attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
} }

View file

@ -38,10 +38,10 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
{ {
auto array = toml::get<std::vector<toml::value>>(t); auto array = toml::get<std::vector<toml::value>>(t);
size_t size = array.size(); auto list = state.buildList(array.size());
state.mkList(v, size); for (const auto & [n, v] : enumerate(list))
for (size_t i = 0; i < size; ++i) visit(*(v = state.allocValue()), array[n]);
visit(*(v.listElems()[i] = state.allocValue()), array[i]); v.mkList(list);
} }
break;; break;;
case toml::value_t::boolean: case toml::value_t::boolean:

View file

@ -44,7 +44,7 @@ SearchPath::Elem SearchPath::Elem::parse(std::string_view rawElem)
} }
SearchPath parseSearchPath(const Strings & rawElems) SearchPath SearchPath::parse(const Strings & rawElems)
{ {
SearchPath res; SearchPath res;
for (auto & rawElem : rawElems) for (auto & rawElem : rawElems)

View file

@ -18,6 +18,7 @@
namespace nix { namespace nix {
struct Value;
class BindingsBuilder; class BindingsBuilder;
@ -134,6 +135,34 @@ class ExternalValueBase
std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
class ListBuilder
{
const size_t size;
Value * inlineElems[2] = {nullptr, nullptr};
public:
Value * * elems;
ListBuilder(EvalState & state, size_t size);
ListBuilder(ListBuilder && x)
: size(x.size)
, inlineElems{x.inlineElems[0], x.inlineElems[1]}
, elems(size <= 2 ? inlineElems : x.elems)
{ }
Value * & operator [](size_t n)
{
return elems[n];
}
typedef Value * * iterator;
iterator begin() { return &elems[0]; }
iterator end() { return &elems[size]; }
friend struct Value;
};
struct Value struct Value
{ {
private: private:
@ -217,7 +246,7 @@ public:
Bindings * attrs; Bindings * attrs;
struct { struct {
size_t size; size_t size;
Value * * elems; Value * const * elems;
} bigList; } bigList;
Value * smallList[2]; Value * smallList[2];
ClosureThunk thunk; ClosureThunk thunk;
@ -299,6 +328,7 @@ public:
} }
void mkPath(const SourcePath & path); void mkPath(const SourcePath & path);
void mkPath(std::string_view path);
inline void mkPath(InputAccessor * accessor, const char * path) inline void mkPath(InputAccessor * accessor, const char * path)
{ {
@ -323,16 +353,20 @@ public:
Value & mkAttrs(BindingsBuilder & bindings); Value & mkAttrs(BindingsBuilder & bindings);
inline void mkList(size_t size) void mkList(const ListBuilder & builder)
{ {
clearValue(); clearValue();
if (size == 1) if (builder.size == 1) {
smallList[0] = builder.inlineElems[0];
internalType = tList1; internalType = tList1;
else if (size == 2) } else if (builder.size == 2) {
smallList[0] = builder.inlineElems[0];
smallList[1] = builder.inlineElems[1];
internalType = tList2; internalType = tList2;
else { } else {
bigList.size = builder.size;
bigList.elems = builder.elems;
internalType = tListN; internalType = tListN;
bigList.size = size;
} }
} }
@ -392,7 +426,7 @@ public:
return internalType == tList1 || internalType == tList2 || internalType == tListN; return internalType == tList1 || internalType == tList2 || internalType == tListN;
} }
Value * * listElems() Value * const * listElems()
{ {
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems; return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
} }

View file

@ -78,7 +78,6 @@ struct FetchSettings : public Config
)", )",
{}, true, Xp::Flakes}; {}, true, Xp::Flakes};
Setting<bool> useRegistries{this, true, "use-registries", Setting<bool> useRegistries{this, true, "use-registries",
"Whether to use flake registries to resolve flake references.", "Whether to use flake registries to resolve flake references.",
{}, true, Xp::Flakes}; {}, true, Xp::Flakes};
@ -94,6 +93,22 @@ struct FetchSettings : public Config
empty, the summary is generated based on the action performed. empty, the summary is generated based on the action performed.
)", )",
{}, true, Xp::Flakes}; {}, true, Xp::Flakes};
Setting<bool> trustTarballsFromGitForges{
this, true, "trust-tarballs-from-git-forges",
R"(
If enabled (the default), Nix will consider tarballs from
GitHub and similar Git forges to be locked if a Git revision
is specified,
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f`.
This requires Nix to trust that the provider will return the
correct contents for the specified Git revision.
If disabled, such tarballs are only considered locked if a
`narHash` attribute is specified,
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`.
)"};
}; };
// FIXME: don't use a global variable. // FIXME: don't use a global variable.

View file

@ -21,6 +21,7 @@
#include <git2/refs.h> #include <git2/refs.h>
#include <git2/remote.h> #include <git2/remote.h>
#include <git2/repository.h> #include <git2/repository.h>
#include <git2/revparse.h>
#include <git2/status.h> #include <git2/status.h>
#include <git2/submodule.h> #include <git2/submodule.h>
#include <git2/tree.h> #include <git2/tree.h>
@ -199,27 +200,10 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
Hash resolveRef(std::string ref) override Hash resolveRef(std::string ref) override
{ {
// Handle revisions used as refs. Object object;
{ if (git_revparse_single(Setter(object), *this, ref.c_str()))
git_oid oid;
if (git_oid_fromstr(&oid, ref.c_str()) == 0)
return toHash(oid);
}
// Resolve short names like 'master'.
Reference ref2;
if (!git_reference_dwim(Setter(ref2), *this, ref.c_str()))
ref = git_reference_name(ref2.get());
// Resolve full references like 'refs/heads/master'.
Reference ref3;
if (git_reference_lookup(Setter(ref3), *this, ref.c_str()))
throw Error("resolving Git reference '%s': %s", ref, git_error_last()->message); throw Error("resolving Git reference '%s': %s", ref, git_error_last()->message);
auto oid = git_object_id(object.get());
auto oid = git_reference_target(ref3.get());
if (!oid)
throw Error("cannot get OID for Git reference '%s'", git_reference_name(ref3.get()));
return toHash(*oid); return toHash(*oid);
} }
@ -364,7 +348,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
{ {
auto act = (Activity *) payload; auto act = (Activity *) payload;
act->result(resFetchStatus, trim(std::string_view(str, len))); act->result(resFetchStatus, trim(std::string_view(str, len)));
return _isInterrupted ? -1 : 0; return getInterrupted() ? -1 : 0;
} }
static int transferProgressCallback(const git_indexer_progress * stats, void * payload) static int transferProgressCallback(const git_indexer_progress * stats, void * payload)
@ -377,7 +361,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
stats->indexed_deltas, stats->indexed_deltas,
stats->total_deltas, stats->total_deltas,
stats->received_bytes / (1024.0 * 1024.0))); stats->received_bytes / (1024.0 * 1024.0)));
return _isInterrupted ? -1 : 0; return getInterrupted() ? -1 : 0;
} }
void fetch( void fetch(

View file

@ -294,7 +294,9 @@ struct GitArchiveInputScheme : InputScheme
Git revision alone, we also require a NAR hash for Git revision alone, we also require a NAR hash for
locking. FIXME: in the future, we may want to require a Git locking. FIXME: in the future, we may want to require a Git
tree hash instead of a NAR hash. */ tree hash instead of a NAR hash. */
return input.getRev().has_value() && input.getNarHash().has_value(); return input.getRev().has_value()
&& (fetchSettings.trustTarballsFromGitForges ||
input.getNarHash().has_value());
} }
std::optional<ExperimentalFeature> experimentalFeature() const override std::optional<ExperimentalFeature> experimentalFeature() const override

View file

@ -5,8 +5,18 @@ libfetchers_NAME = libnixfetchers
libfetchers_DIR := $(d) libfetchers_DIR := $(d)
libfetchers_SOURCES := $(wildcard $(d)/*.cc) libfetchers_SOURCES := $(wildcard $(d)/*.cc)
ifdef HOST_UNIX
libfetchers_SOURCES += $(wildcard $(d)/unix/*.cc)
endif
libfetchers_CXXFLAGS += -I src/libutil -I src/libstore # Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libfetchers := -I $(d)
ifdef HOST_UNIX
INCLUDE_libfetchers += -I $(d)/unix
endif
libfetchers_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libfetchers)
libfetchers_LDFLAGS += $(THREAD_LDFLAGS) $(LIBGIT2_LIBS) -larchive libfetchers_LDFLAGS += $(THREAD_LDFLAGS) $(LIBGIT2_LIBS) -larchive

View file

@ -147,9 +147,12 @@ std::vector<PublicKey> getPublicKeys(const Attrs & attrs)
{ {
std::vector<PublicKey> publicKeys; std::vector<PublicKey> publicKeys;
if (attrs.contains("publicKeys")) { if (attrs.contains("publicKeys")) {
nlohmann::json publicKeysJson = nlohmann::json::parse(getStrAttr(attrs, "publicKeys")); auto pubKeysJson = nlohmann::json::parse(getStrAttr(attrs, "publicKeys"));
ensureType(publicKeysJson, nlohmann::json::value_t::array); auto & pubKeys = getArray(pubKeysJson);
publicKeys = publicKeysJson.get<std::vector<PublicKey>>();
for (auto & key : pubKeys) {
publicKeys.push_back(key);
}
} }
if (attrs.contains("publicKey")) if (attrs.contains("publicKey"))
publicKeys.push_back(PublicKey{maybeGetStrAttr(attrs, "keytype").value_or("ssh-ed25519"),getStrAttr(attrs, "publicKey")}); publicKeys.push_back(PublicKey{maybeGetStrAttr(attrs, "keytype").value_or("ssh-ed25519"),getStrAttr(attrs, "publicKey")});
@ -585,7 +588,7 @@ struct GitInputScheme : InputScheme
repoInfo.url repoInfo.url
); );
} else } else
input.attrs.insert_or_assign("rev", Hash::parseAny(chomp(readFile(localRefFile)), HashAlgorithm::SHA1).gitRev()); input.attrs.insert_or_assign("rev", repo->resolveRef(ref).gitRev());
// cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in the remainder // cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in the remainder
} }

View file

@ -5,8 +5,13 @@ libmain_NAME = libnixmain
libmain_DIR := $(d) libmain_DIR := $(d)
libmain_SOURCES := $(wildcard $(d)/*.cc) libmain_SOURCES := $(wildcard $(d)/*.cc)
ifdef HOST_UNIX
libmain_SOURCES += $(wildcard $(d)/unix/*.cc)
endif
libmain_CXXFLAGS += -I src/libutil -I src/libstore INCLUDE_libmain := -I $(d)
libmain_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libmain)
libmain_LDFLAGS += $(OPENSSL_LIBS) libmain_LDFLAGS += $(OPENSSL_LIBS)

View file

@ -123,14 +123,18 @@ public:
} }
void pause() override { void pause() override {
state_.lock()->paused = true; auto state (state_.lock());
writeToStderr("\r\e[K"); state->paused = true;
if (state->active)
writeToStderr("\r\e[K");
} }
void resume() override { void resume() override {
state_.lock()->paused = false; auto state (state_.lock());
writeToStderr("\r\e[K"); state->paused = false;
state_.lock()->haveUpdate = true; if (state->active)
writeToStderr("\r\e[K");
state->haveUpdate = true;
updateCV.notify_one(); updateCV.notify_one();
} }
@ -162,9 +166,7 @@ public:
writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n"); writeToStderr("\r\e[K" + filterANSIEscapes(s, !isTTY) + ANSI_NORMAL "\n");
draw(state); draw(state);
} else { } else {
auto s2 = s + ANSI_NORMAL "\n"; writeToStderr(filterANSIEscapes(s, !isTTY) + "\n");
if (!isTTY) s2 = filterANSIEscapes(s2, true);
writeToStderr(s2);
} }
} }
@ -519,7 +521,7 @@ public:
std::optional<char> ask(std::string_view msg) override std::optional<char> ask(std::string_view msg) override
{ {
auto state(state_.lock()); auto state(state_.lock());
if (!state->active || !isatty(STDIN_FILENO)) return {}; if (!state->active) return {};
std::cerr << fmt("\r\e[K%s ", msg); std::cerr << fmt("\r\e[K%s ", msg);
auto s = trim(readLine(STDIN_FILENO)); auto s = trim(readLine(STDIN_FILENO));
if (s.size() != 1) return {}; if (s.size() != 1) return {};
@ -535,7 +537,7 @@ public:
Logger * makeProgressBar() Logger * makeProgressBar()
{ {
return new ProgressBar(shouldANSI()); return new ProgressBar(isTTY());
} }
void startProgressBar() void startProgressBar()

View file

@ -121,7 +121,7 @@ void initNix()
initLibStore(); initLibStore();
startSignalHandlerThread(); unix::startSignalHandlerThread();
/* Reset SIGCHLD to its default. */ /* Reset SIGCHLD to its default. */
struct sigaction act; struct sigaction act;
@ -308,7 +308,7 @@ void printVersion(const std::string & programName)
void showManPage(const std::string & name) void showManPage(const std::string & name)
{ {
restoreProcessContext(); restoreProcessContext();
setenv("MANPATH", settings.nixManDir.c_str(), 1); setEnv("MANPATH", settings.nixManDir.c_str());
execlp("man", "man", name.c_str(), nullptr); execlp("man", "man", name.c_str(), nullptr);
throw SysError("command 'man %1%' failed", name.c_str()); throw SysError("command 'man %1%' failed", name.c_str());
} }
@ -369,7 +369,7 @@ RunPager::RunPager()
if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1) if (dup2(toPager.readSide.get(), STDIN_FILENO) == -1)
throw SysError("dupping stdin"); throw SysError("dupping stdin");
if (!getenv("LESS")) if (!getenv("LESS"))
setenv("LESS", "FRSXMK", 1); setEnv("LESS", "FRSXMK");
restoreProcessContext(); restoreProcessContext();
if (pager) if (pager)
execl("/bin/sh", "sh", "-c", pager, nullptr); execl("/bin/sh", "sh", "-c", pager, nullptr);

21
src/libstore-c/local.mk Normal file
View file

@ -0,0 +1,21 @@
libraries += libstorec
libstorec_NAME = libnixstorec
libstorec_DIR := $(d)
libstorec_SOURCES := $(wildcard $(d)/*.cc)
libstorec_LIBS = libutil libstore libutilc
libstorec_LDFLAGS += $(THREAD_LDFLAGS)
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libstorec := -I $(d)
libstorec_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc) \
$(INCLUDE_libstore) $(INCLUDE_libstorec)
$(eval $(call install-file-in, $(d)/nix-store-c.pc, $(libdir)/pkgconfig, 0644))
libstorec_FORCE_INSTALL := 1

View file

@ -0,0 +1,9 @@
prefix=@prefix@
libdir=@libdir@
includedir=@includedir@
Name: Nix
Description: Nix Store - C API
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -lnixstorec -lnixutilc
Cflags: -I${includedir}/nix

View file

@ -0,0 +1,134 @@
#include "nix_api_store.h"
#include "nix_api_store_internal.h"
#include "nix_api_util.h"
#include "nix_api_util_internal.h"
#include "path.hh"
#include "store-api.hh"
#include "build-result.hh"
#include "globals.hh"
nix_err nix_libstore_init(nix_c_context * context)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::initLibStore();
}
NIXC_CATCH_ERRS
}
nix_err nix_init_plugins(nix_c_context * context)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::initPlugins();
}
NIXC_CATCH_ERRS
}
Store * nix_store_open(nix_c_context * context, const char * uri, const char *** params)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::string uri_str = uri ? uri : "";
if (uri_str.empty())
return new Store{nix::openStore()};
if (!params)
return new Store{nix::openStore(uri_str)};
nix::Store::Params params_map;
for (size_t i = 0; params[i] != nullptr; i++) {
params_map[params[i][0]] = params[i][1];
}
return new Store{nix::openStore(uri_str, params_map)};
}
NIXC_CATCH_ERRS_NULL
}
void nix_store_free(Store * store)
{
delete store;
}
nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto res = store->ptr->getUri();
return call_nix_get_string_callback(res, callback, user_data);
}
NIXC_CATCH_ERRS
}
nix_err nix_store_get_version(nix_c_context * context, Store * store, void * callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto res = store->ptr->getVersion();
return call_nix_get_string_callback(res.value_or(""), callback, user_data);
}
NIXC_CATCH_ERRS
}
bool nix_store_is_valid_path(nix_c_context * context, Store * store, StorePath * path)
{
if (context)
context->last_err_code = NIX_OK;
try {
return store->ptr->isValidPath(path->path);
}
NIXC_CATCH_ERRS_RES(false);
}
StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::StorePath s = store->ptr->parseStorePath(path);
return new StorePath{std::move(s)};
}
NIXC_CATCH_ERRS_NULL
}
nix_err nix_store_realise(
nix_c_context * context,
Store * store,
StorePath * path,
void * userdata,
void (*callback)(void * userdata, const char *, const char *))
{
if (context)
context->last_err_code = NIX_OK;
try {
const std::vector<nix::DerivedPath> paths{nix::DerivedPath::Built{
.drvPath = nix::makeConstantStorePathRef(path->path), .outputs = nix::OutputsSpec::All{}}};
const auto nixStore = store->ptr;
auto results = nixStore->buildPathsWithResults(paths, nix::bmNormal, nixStore);
if (callback) {
for (const auto & result : results) {
for (const auto & [outputName, realisation] : result.builtOutputs) {
auto op = store->ptr->printStorePath(realisation.outPath);
callback(userdata, outputName.c_str(), op.c_str());
}
}
}
}
NIXC_CATCH_ERRS
}
void nix_store_path_free(StorePath * sp)
{
delete sp;
}

View file

@ -0,0 +1,148 @@
#ifndef NIX_API_STORE_H
#define NIX_API_STORE_H
/**
* @defgroup libstore libstore
* @brief C bindings for nix libstore
*
* libstore is used for talking to a Nix store
* @{
*/
/** @file
* @brief Main entry for the libstore C bindings
*/
#include "nix_api_util.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
/** @brief Reference to a Nix store */
typedef struct Store Store;
/** @brief Nix store path */
typedef struct StorePath StorePath;
/**
* @brief Initializes the Nix store library
*
* This function should be called before creating a Store
* This function can be called multiple times.
*
* @param[out] context Optional, stores error information
* @return NIX_OK if the initialization was successful, an error code otherwise.
*/
nix_err nix_libstore_init(nix_c_context * context);
/**
* @brief Loads the plugins specified in Nix's plugin-files setting.
*
* Call this once, after calling your desired init functions and setting
* relevant settings.
*
* @param[out] context Optional, stores error information
* @return NIX_OK if the initialization was successful, an error code otherwise.
*/
nix_err nix_init_plugins(nix_c_context * context);
/**
* @brief Open a nix store
* Store instances may share state and resources behind the scenes.
* @param[out] context Optional, stores error information
* @param[in] uri URI of the nix store, copied
* @param[in] params optional, array of key-value pairs, {{"endpoint",
* "https://s3.local"}}
* @return a Store pointer, NULL in case of errors
* @see nix_store_free
*/
Store * nix_store_open(nix_c_context *, const char * uri, const char *** params);
/**
* @brief Deallocate a nix store and free any resources if not also held by other Store instances.
*
* Does not fail.
*
* @param[in] store the store to free
*/
void nix_store_free(Store * store);
/**
* @brief get the URI of a nix store
* @param[out] context Optional, stores error information
* @param[in] store nix store reference
* @param[in] callback Called with the URI.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @see nix_get_string_callback
* @return error code, NIX_OK on success.
*/
nix_err nix_store_get_uri(nix_c_context * context, Store * store, void * callback, void * user_data);
// returns: owned StorePath*
/**
* @brief Parse a Nix store path into a StorePath
*
* @note Don't forget to free this path using nix_store_path_free()!
* @param[out] context Optional, stores error information
* @param[in] store nix store reference
* @param[in] path Path string to parse, copied
* @return owned store path, NULL on error
*/
StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path);
/** @brief Deallocate a StorePath
*
* Does not fail.
* @param[in] p the path to free
*/
void nix_store_path_free(StorePath * p);
/**
* @brief Check if a StorePath is valid (i.e. that corresponding store object and its closure of references exists in
* the store)
* @param[out] context Optional, stores error information
* @param[in] store Nix Store reference
* @param[in] path Path to check
* @return true or false, error info in context
*/
bool nix_store_is_valid_path(nix_c_context * context, Store * store, StorePath * path);
// nix_err nix_store_ensure(Store*, const char*);
// nix_err nix_store_build_paths(Store*);
/**
* @brief Realise a Nix store path
*
* Blocking, calls callback once for each realised output
*
* @param[out] context Optional, stores error information
* @param[in] store Nix Store reference
* @param[in] path Path to build
* @param[in] userdata data to pass to every callback invocation
* @param[in] callback called for every realised output
*/
nix_err nix_store_realise(
nix_c_context * context,
Store * store,
StorePath * path,
void * userdata,
void (*callback)(void * userdata, const char * outname, const char * out));
/**
* @brief get the version of a nix store.
* If the store doesn't have a version (like the dummy store), returns an empty string.
* @param[out] context Optional, stores error information
* @param[in] store nix store reference
* @param[in] callback Called with the version.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @see nix_get_string_callback
* @return error code, NIX_OK on success.
*/
nix_err nix_store_get_version(nix_c_context * context, Store * store, void * callback, void * user_data);
// cffi end
#ifdef __cplusplus
}
#endif
/**
* @}
*/
#endif // NIX_API_STORE_H

View file

@ -0,0 +1,15 @@
#ifndef NIX_API_STORE_INTERNAL_H
#define NIX_API_STORE_INTERNAL_H
#include "store-api.hh"
struct Store
{
nix::ref<nix::Store> ptr;
};
struct StorePath
{
nix::StorePath path;
};
#endif

View file

@ -124,14 +124,6 @@ void BinaryCacheStore::writeNarInfo(ref<NarInfo> narInfo)
diskCache->upsertNarInfo(getUri(), std::string(narInfo->path.hashPart()), std::shared_ptr<NarInfo>(narInfo)); diskCache->upsertNarInfo(getUri(), std::string(narInfo->path.hashPart()), std::shared_ptr<NarInfo>(narInfo));
} }
AutoCloseFD openFile(const Path & path)
{
auto fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
if (!fd)
throw SysError("opening file '%1%'", path);
return fd;
}
ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon( ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs, Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs,
std::function<ValidPathInfo(HashResult)> mkInfo) std::function<ValidPathInfo(HashResult)> mkInfo)

View file

@ -17,7 +17,6 @@
#include "cgroup.hh" #include "cgroup.hh"
#include "personality.hh" #include "personality.hh"
#include "current-process.hh" #include "current-process.hh"
#include "namespaces.hh"
#include "child.hh" #include "child.hh"
#include "unix-domain-socket.hh" #include "unix-domain-socket.hh"
#include "posix-fs-canonicalise.hh" #include "posix-fs-canonicalise.hh"
@ -40,18 +39,19 @@
/* Includes required for chroot support. */ /* Includes required for chroot support. */
#if __linux__ #if __linux__
#include <sys/ioctl.h> # include <sys/ioctl.h>
#include <net/if.h> # include <net/if.h>
#include <netinet/ip.h> # include <netinet/ip.h>
#include <sys/mman.h> # include <sys/mman.h>
#include <sched.h> # include <sched.h>
#include <sys/param.h> # include <sys/param.h>
#include <sys/mount.h> # include <sys/mount.h>
#include <sys/syscall.h> # include <sys/syscall.h>
#if HAVE_SECCOMP # include "namespaces.hh"
#include <seccomp.h> # if HAVE_SECCOMP
#endif # include <seccomp.h>
#define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old)) # endif
# define pivot_root(new_root, put_old) (syscall(SYS_pivot_root, new_root, put_old))
#endif #endif
#if __APPLE__ #if __APPLE__
@ -488,7 +488,7 @@ void LocalDerivationGoal::startBuilder()
/* Create a temporary directory where the build will take /* Create a temporary directory where the build will take
place. */ place. */
tmpDir = createTempDir("", "nix-build-" + std::string(drvPath.name()), false, false, 0700); tmpDir = createTempDir(settings.buildDir.get().value_or(""), "nix-build-" + std::string(drvPath.name()), false, false, 0700);
chownToBuilder(tmpDir); chownToBuilder(tmpDir);
@ -2053,13 +2053,13 @@ void LocalDerivationGoal::runChild()
i.first, i.second.source); i.first, i.second.source);
std::string path = i.first; std::string path = i.first;
struct stat st; auto optSt = maybeLstat(path.c_str());
if (lstat(path.c_str(), &st)) { if (!optSt) {
if (i.second.optional && errno == ENOENT) if (i.second.optional)
continue; continue;
throw SysError("getting attributes of path '%s", path); throw SysError("getting attributes of required path '%s", path);
} }
if (S_ISDIR(st.st_mode)) if (S_ISDIR(optSt->st_mode))
sandboxProfile += fmt("\t(subpath \"%s\")\n", path); sandboxProfile += fmt("\t(subpath \"%s\")\n", path);
else else
sandboxProfile += fmt("\t(literal \"%s\")\n", path); sandboxProfile += fmt("\t(literal \"%s\")\n", path);
@ -2089,11 +2089,12 @@ void LocalDerivationGoal::runChild()
bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking"); bool allowLocalNetworking = parsedDrv->getBoolAttr("__darwinAllowLocalNetworking");
/* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms /* The tmpDir in scope points at the temporary build directory for our derivation. Some packages try different mechanisms
to find temporary directories, so we want to open up a broader place for them to dump their files, if needed. */ to find temporary directories, so we want to open up a broader place for them to put their files, if needed. */
Path globalTmpDir = canonPath(getEnvNonEmpty("TMPDIR").value_or("/tmp"), true); Path globalTmpDir = canonPath(defaultTempDir(), true);
/* They don't like trailing slashes on subpath directives */ /* They don't like trailing slashes on subpath directives */
if (globalTmpDir.back() == '/') globalTmpDir.pop_back(); while (!globalTmpDir.empty() && globalTmpDir.back() == '/')
globalTmpDir.pop_back();
if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") { if (getEnv("_NIX_TEST_NO_SANDBOX") != "1") {
builder = "/usr/bin/sandbox-exec"; builder = "/usr/bin/sandbox-exec";
@ -2270,14 +2271,12 @@ SingleDrvOutputs LocalDerivationGoal::registerOutputs()
continue; continue;
} }
struct stat st; auto optSt = maybeLstat(actualPath.c_str());
if (lstat(actualPath.c_str(), &st) == -1) { if (!optSt)
if (errno == ENOENT) throw BuildError(
throw BuildError( "builder for '%s' failed to produce output path for output '%s' at '%s'",
"builder for '%s' failed to produce output path for output '%s' at '%s'", worker.store.printStorePath(drvPath), outputName, actualPath);
worker.store.printStorePath(drvPath), outputName, actualPath); struct stat & st = *optSt;
throw SysError("getting attributes of path '%s'", actualPath);
}
#ifndef __CYGWIN__ #ifndef __CYGWIN__
/* Check that the output is not group or world writable, as /* Check that the output is not group or world writable, as

View file

@ -64,9 +64,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
continue; continue;
else if (S_ISDIR(srcSt.st_mode)) { else if (S_ISDIR(srcSt.st_mode)) {
struct stat dstSt; auto dstStOpt = maybeLstat(dstFile.c_str());
auto res = lstat(dstFile.c_str(), &dstSt); if (dstStOpt) {
if (res == 0) { auto & dstSt = *dstStOpt;
if (S_ISDIR(dstSt.st_mode)) { if (S_ISDIR(dstSt.st_mode)) {
createLinks(state, srcFile, dstFile, priority); createLinks(state, srcFile, dstFile, priority);
continue; continue;
@ -82,14 +82,13 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
createLinks(state, srcFile, dstFile, priority); createLinks(state, srcFile, dstFile, priority);
continue; continue;
} }
} else if (errno != ENOENT) }
throw SysError("getting status of '%1%'", dstFile);
} }
else { else {
struct stat dstSt; auto dstStOpt = maybeLstat(dstFile.c_str());
auto res = lstat(dstFile.c_str(), &dstSt); if (dstStOpt) {
if (res == 0) { auto & dstSt = *dstStOpt;
if (S_ISLNK(dstSt.st_mode)) { if (S_ISLNK(dstSt.st_mode)) {
auto prevPriority = state.priorities[dstFile]; auto prevPriority = state.priorities[dstFile];
if (prevPriority == priority) if (prevPriority == priority)
@ -104,8 +103,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw SysError("unlinking '%1%'", dstFile); throw SysError("unlinking '%1%'", dstFile);
} else if (S_ISDIR(dstSt.st_mode)) } else if (S_ISDIR(dstSt.st_mode))
throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile); throw Error("collision between non-directory '%1%' and directory '%2%'", srcFile, dstFile);
} else if (errno != ENOENT) }
throw SysError("getting status of '%1%'", dstFile);
} }
createSymlink(srcFile, dstFile); createSymlink(srcFile, dstFile);

View file

@ -1,5 +1,6 @@
#include "daemon.hh" #include "daemon.hh"
#include "monitor-fd.hh" #include "monitor-fd.hh"
#include "signals.hh"
#include "worker-protocol.hh" #include "worker-protocol.hh"
#include "worker-protocol-impl.hh" #include "worker-protocol-impl.hh"
#include "build-result.hh" #include "build-result.hh"
@ -254,7 +255,7 @@ struct ClientSettings
else if (setSubstituters(settings.substituters)) else if (setSubstituters(settings.substituters))
; ;
else else
debug("ignoring the client-specified setting '%s', because it is a restricted setting and you are not a trusted user", name); warn("ignoring the client-specified setting '%s', because it is a restricted setting and you are not a trusted user", name);
} catch (UsageError & e) { } catch (UsageError & e) {
warn(e.what()); warn(e.what());
} }
@ -1038,7 +1039,7 @@ void processConnection(
unsigned int opCount = 0; unsigned int opCount = 0;
Finally finally([&]() { Finally finally([&]() {
_isInterrupted = false; setInterrupted(false);
printMsgUsing(prevLogger, lvlDebug, "%d operations", opCount); printMsgUsing(prevLogger, lvlDebug, "%d operations", opCount);
}); });

View file

@ -1239,16 +1239,14 @@ DerivationOutput DerivationOutput::fromJSON(
const ExperimentalFeatureSettings & xpSettings) const ExperimentalFeatureSettings & xpSettings)
{ {
std::set<std::string_view> keys; std::set<std::string_view> keys;
ensureType(_json, nlohmann::detail::value_t::object); auto & json = getObject(_json);
auto json = (std::map<std::string, nlohmann::json>) _json;
for (const auto & [key, _] : json) for (const auto & [key, _] : json)
keys.insert(key); keys.insert(key);
auto methodAlgo = [&]() -> std::pair<ContentAddressMethod, HashAlgorithm> { auto methodAlgo = [&]() -> std::pair<ContentAddressMethod, HashAlgorithm> {
std::string hashAlgoStr = json["hashAlgo"]; auto & str = getString(valueAt(json, "hashAlgo"));
// remaining to parse, will be mutated by parsers std::string_view s = str;
std::string_view s = hashAlgoStr;
ContentAddressMethod method = ContentAddressMethod::parsePrefix(s); ContentAddressMethod method = ContentAddressMethod::parsePrefix(s);
if (method == TextIngestionMethod {}) if (method == TextIngestionMethod {})
xpSettings.require(Xp::DynamicDerivations); xpSettings.require(Xp::DynamicDerivations);
@ -1258,7 +1256,7 @@ DerivationOutput DerivationOutput::fromJSON(
if (keys == (std::set<std::string_view> { "path" })) { if (keys == (std::set<std::string_view> { "path" })) {
return DerivationOutput::InputAddressed { return DerivationOutput::InputAddressed {
.path = store.parseStorePath((std::string) json["path"]), .path = store.parseStorePath(getString(valueAt(json, "path"))),
}; };
} }
@ -1267,10 +1265,10 @@ DerivationOutput DerivationOutput::fromJSON(
auto dof = DerivationOutput::CAFixed { auto dof = DerivationOutput::CAFixed {
.ca = ContentAddress { .ca = ContentAddress {
.method = std::move(method), .method = std::move(method),
.hash = Hash::parseNonSRIUnprefixed((std::string) json["hash"], hashAlgo), .hash = Hash::parseNonSRIUnprefixed(getString(valueAt(json, "hash")), hashAlgo),
}, },
}; };
if (dof.path(store, drvName, outputName) != store.parseStorePath((std::string) json["path"])) if (dof.path(store, drvName, outputName) != store.parseStorePath(getString(valueAt(json, "path"))))
throw Error("Path doesn't match derivation output"); throw Error("Path doesn't match derivation output");
return dof; return dof;
} }
@ -1357,20 +1355,19 @@ nlohmann::json Derivation::toJSON(const StoreDirConfig & store) const
Derivation Derivation::fromJSON( Derivation Derivation::fromJSON(
const StoreDirConfig & store, const StoreDirConfig & store,
const nlohmann::json & json, const nlohmann::json & _json,
const ExperimentalFeatureSettings & xpSettings) const ExperimentalFeatureSettings & xpSettings)
{ {
using nlohmann::detail::value_t; using nlohmann::detail::value_t;
Derivation res; Derivation res;
ensureType(json, value_t::object); auto & json = getObject(_json);
res.name = ensureType(valueAt(json, "name"), value_t::string); res.name = getString(valueAt(json, "name"));
try { try {
auto & outputsObj = ensureType(valueAt(json, "outputs"), value_t::object); for (auto & [outputName, output] : getObject(valueAt(json, "outputs"))) {
for (auto & [outputName, output] : outputsObj.items()) {
res.outputs.insert_or_assign( res.outputs.insert_or_assign(
outputName, outputName,
DerivationOutput::fromJSON(store, res.name, outputName, output)); DerivationOutput::fromJSON(store, res.name, outputName, output));
@ -1381,8 +1378,7 @@ Derivation Derivation::fromJSON(
} }
try { try {
auto & inputsList = ensureType(valueAt(json, "inputSrcs"), value_t::array); for (auto & input : getArray(valueAt(json, "inputSrcs")))
for (auto & input : inputsList)
res.inputSrcs.insert(store.parseStorePath(static_cast<const std::string &>(input))); res.inputSrcs.insert(store.parseStorePath(static_cast<const std::string &>(input)));
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while reading key 'inputSrcs'"); e.addTrace({}, "while reading key 'inputSrcs'");
@ -1391,18 +1387,17 @@ Derivation Derivation::fromJSON(
try { try {
std::function<DerivedPathMap<StringSet>::ChildNode(const nlohmann::json &)> doInput; std::function<DerivedPathMap<StringSet>::ChildNode(const nlohmann::json &)> doInput;
doInput = [&](const auto & json) { doInput = [&](const auto & _json) {
auto & json = getObject(_json);
DerivedPathMap<StringSet>::ChildNode node; DerivedPathMap<StringSet>::ChildNode node;
node.value = static_cast<const StringSet &>( node.value = getStringSet(valueAt(json, "outputs"));
ensureType(valueAt(json, "outputs"), value_t::array)); for (auto & [outputId, childNode] : getObject(valueAt(json, "dynamicOutputs"))) {
for (auto & [outputId, childNode] : ensureType(valueAt(json, "dynamicOutputs"), value_t::object).items()) {
xpSettings.require(Xp::DynamicDerivations); xpSettings.require(Xp::DynamicDerivations);
node.childMap[outputId] = doInput(childNode); node.childMap[outputId] = doInput(childNode);
} }
return node; return node;
}; };
auto & inputDrvsObj = ensureType(valueAt(json, "inputDrvs"), value_t::object); for (auto & [inputDrvPath, inputOutputs] : getObject(valueAt(json, "inputDrvs")))
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items())
res.inputDrvs.map[store.parseStorePath(inputDrvPath)] = res.inputDrvs.map[store.parseStorePath(inputDrvPath)] =
doInput(inputOutputs); doInput(inputOutputs);
} catch (Error & e) { } catch (Error & e) {
@ -1410,10 +1405,10 @@ Derivation Derivation::fromJSON(
throw; throw;
} }
res.platform = ensureType(valueAt(json, "system"), value_t::string); res.platform = getString(valueAt(json, "system"));
res.builder = ensureType(valueAt(json, "builder"), value_t::string); res.builder = getString(valueAt(json, "builder"));
res.args = ensureType(valueAt(json, "args"), value_t::array); res.args = getStringList(valueAt(json, "args"));
res.env = ensureType(valueAt(json, "env"), value_t::object); res.env = getStringMap(valueAt(json, "env"));
return res; return res;
} }

View file

@ -1,5 +1,4 @@
#include "filetransfer.hh" #include "filetransfer.hh"
#include "namespaces.hh"
#include "globals.hh" #include "globals.hh"
#include "store-api.hh" #include "store-api.hh"
#include "s3.hh" #include "s3.hh"
@ -12,6 +11,10 @@
#include <aws/core/client/ClientConfiguration.h> #include <aws/core/client/ClientConfiguration.h>
#endif #endif
#if __linux__
# include "namespaces.hh"
#endif
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
@ -255,11 +258,11 @@ struct curlFileTransfer : public FileTransfer
int progressCallback(double dltotal, double dlnow) int progressCallback(double dltotal, double dlnow)
{ {
try { try {
act.progress(dlnow, dltotal); act.progress(dlnow, dltotal);
} catch (nix::Interrupted &) { } catch (nix::Interrupted &) {
assert(_isInterrupted); assert(getInterrupted());
} }
return _isInterrupted; return getInterrupted();
} }
static int progressCallbackWrapper(void * userp, double dltotal, double dlnow, double ultotal, double ulnow) static int progressCallbackWrapper(void * userp, double dltotal, double dlnow, double ultotal, double ulnow)
@ -463,7 +466,7 @@ struct curlFileTransfer : public FileTransfer
if (errorSink) if (errorSink)
response = std::move(errorSink->s); response = std::move(errorSink->s);
auto exc = auto exc =
code == CURLE_ABORTED_BY_CALLBACK && _isInterrupted code == CURLE_ABORTED_BY_CALLBACK && getInterrupted()
? FileTransferError(Interrupted, std::move(response), "%s of '%s' was interrupted", request.verb(), request.uri) ? FileTransferError(Interrupted, std::move(response), "%s of '%s' was interrupted", request.verb(), request.uri)
: httpStatus != 0 : httpStatus != 0
? FileTransferError(err, ? FileTransferError(err,
@ -568,7 +571,9 @@ struct curlFileTransfer : public FileTransfer
stopWorkerThread(); stopWorkerThread();
}); });
#if __linux__
unshareFilesystem(); unshareFilesystem();
#endif
std::map<CURL *, std::shared_ptr<TransferItem>> items; std::map<CURL *, std::shared_ptr<TransferItem>> items;

View file

@ -2,7 +2,6 @@
#include "current-process.hh" #include "current-process.hh"
#include "archive.hh" #include "archive.hh"
#include "args.hh" #include "args.hh"
#include "users.hh"
#include "abstract-setting-to-json.hh" #include "abstract-setting-to-json.hh"
#include "compute-levels.hh" #include "compute-levels.hh"
@ -57,7 +56,7 @@ Settings::Settings()
, nixManDir(canonPath(NIX_MAN_DIR)) , nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH))) , nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
{ {
buildUsersGroup = getuid() == 0 ? "nixbld" : ""; buildUsersGroup = isRootUser() ? "nixbld" : "";
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1"; allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or("")); auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
@ -346,6 +345,12 @@ void initPlugins()
dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL); dlopen(file.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
throw Error("could not dynamically open plugin file '%s': %s", file, dlerror()); throw Error("could not dynamically open plugin file '%s': %s", file, dlerror());
/* Older plugins use a statically initialized object to run their code.
Newer plugins can also export nix_plugin_entry() */
void (*nix_plugin_entry)() = (void (*)())dlsym(handle, "nix_plugin_entry");
if (nix_plugin_entry)
nix_plugin_entry();
} }
} }
@ -404,6 +409,7 @@ void assertLibStoreInitialized() {
} }
void initLibStore() { void initLibStore() {
if (initLibStoreDone) return;
initLibUtil(); initLibUtil();
@ -415,7 +421,7 @@ void initLibStore() {
sshd). This breaks build users because they don't have access sshd). This breaks build users because they don't have access
to the TMPDIR, in particular in nix-store --serve. */ to the TMPDIR, in particular in nix-store --serve. */
#if __APPLE__ #if __APPLE__
if (hasPrefix(getEnv("TMPDIR").value_or("/tmp"), "/var/folders/")) if (hasPrefix(defaultTempDir(), "/var/folders/"))
unsetenv("TMPDIR"); unsetenv("TMPDIR");
#endif #endif

View file

@ -5,6 +5,7 @@
#include "config.hh" #include "config.hh"
#include "environment-variables.hh" #include "environment-variables.hh"
#include "experimental-features.hh" #include "experimental-features.hh"
#include "users.hh"
#include <map> #include <map>
#include <limits> #include <limits>
@ -665,7 +666,7 @@ public:
Setting<bool> sandboxFallback{this, true, "sandbox-fallback", Setting<bool> sandboxFallback{this, true, "sandbox-fallback",
"Whether to disable sandboxing when the kernel doesn't allow it."}; "Whether to disable sandboxing when the kernel doesn't allow it."};
Setting<bool> requireDropSupplementaryGroups{this, getuid() == 0, "require-drop-supplementary-groups", Setting<bool> requireDropSupplementaryGroups{this, isRootUser(), "require-drop-supplementary-groups",
R"( R"(
Following the principle of least privilege, Following the principle of least privilege,
Nix will attempt to drop supplementary groups when building with sandboxing. Nix will attempt to drop supplementary groups when building with sandboxing.
@ -687,16 +688,36 @@ public:
Setting<std::string> sandboxShmSize{ Setting<std::string> sandboxShmSize{
this, "50%", "sandbox-dev-shm-size", this, "50%", "sandbox-dev-shm-size",
R"( R"(
This option determines the maximum size of the `tmpfs` filesystem *Linux only*
mounted on `/dev/shm` in Linux sandboxes. For the format, see the
description of the `size` option of `tmpfs` in mount(8). The default This option determines the maximum size of the `tmpfs` filesystem
is `50%`. mounted on `/dev/shm` in Linux sandboxes. For the format, see the
description of the `size` option of `tmpfs` in mount(8). The default
is `50%`.
)"}; )"};
Setting<Path> sandboxBuildDir{this, "/build", "sandbox-build-dir", Setting<Path> sandboxBuildDir{this, "/build", "sandbox-build-dir",
"The build directory inside the sandbox."}; R"(
*Linux only*
The build directory inside the sandbox.
This directory is backed by [`build-dir`](#conf-build-dir) on the host.
)"};
#endif #endif
Setting<std::optional<Path>> buildDir{this, std::nullopt, "build-dir",
R"(
The directory on the host, in which derivations' temporary build directories are created.
If not set, Nix will use the system temporary directory indicated by the `TMPDIR` environment variable.
Note that builds are often performed by the Nix daemon, so its `TMPDIR` is used, and not that of the Nix command line interface.
This is also the location where [`--keep-failed`](@docroot@/command-ref/opt-common.md#opt-keep-failed) leaves its files.
If Nix runs without sandbox, or if the platform does not support sandboxing with bind mounts (e.g. macOS), then the [`builder`](@docroot@/language/derivations.md#attr-builder)'s environment will contain this directory, instead of the virtual location [`sandbox-build-dir`](#conf-sandbox-build-dir).
)"};
Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps", Setting<PathSet> allowedImpureHostPrefixes{this, {}, "allowed-impure-host-deps",
"Which prefixes to allow derivations to ask for access to (primarily for Darwin)."}; "Which prefixes to allow derivations to ask for access to (primarily for Darwin)."};
@ -1136,9 +1157,10 @@ public:
this, {}, "plugin-files", this, {}, "plugin-files",
R"( R"(
A list of plugin files to be loaded by Nix. Each of these files will A list of plugin files to be loaded by Nix. Each of these files will
be dlopened by Nix, allowing them to affect execution through static be dlopened by Nix. If they contain the symbol `nix_plugin_entry()`,
initialization. In particular, these plugins may construct static this symbol will be called. Alternatively, they can affect execution
instances of RegisterPrimOp to add new primops or constants to the through static initialization. In particular, these plugins may construct
static instances of RegisterPrimOp to add new primops or constants to the
expression language, RegisterStoreImplementation to add new store expression language, RegisterStoreImplementation to add new store
implementations, RegisterCommand to add new subcommands to the `nix` implementations, RegisterCommand to add new subcommands to the `nix`
command, and RegisterSetting to add new nix config settings. See the command, and RegisterSetting to add new nix config settings. See the

View file

@ -49,7 +49,7 @@ public:
, BinaryCacheStore(params) , BinaryCacheStore(params)
, cacheUri(scheme + "://" + _cacheUri) , cacheUri(scheme + "://" + _cacheUri)
{ {
if (cacheUri.back() == '/') while (!cacheUri.empty() && cacheUri.back() == '/')
cacheUri.pop_back(); cacheUri.pop_back();
diskCache = getNarInfoDiskCache(); diskCache = getNarInfoDiskCache();

View file

@ -16,6 +16,7 @@
#include "posix-fs-canonicalise.hh" #include "posix-fs-canonicalise.hh"
#include "posix-source-accessor.hh" #include "posix-source-accessor.hh"
#include "keys.hh" #include "keys.hh"
#include "users.hh"
#include <iostream> #include <iostream>
#include <algorithm> #include <algorithm>
@ -223,7 +224,7 @@ LocalStore::LocalStore(const Params & params)
/* Optionally, create directories and set permissions for a /* Optionally, create directories and set permissions for a
multi-user install. */ multi-user install. */
if (getuid() == 0 && settings.buildUsersGroup != "") { if (isRootUser() && settings.buildUsersGroup != "") {
mode_t perm = 01775; mode_t perm = 01775;
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
@ -558,6 +559,19 @@ void LocalStore::openDB(State & state, bool create)
sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK) sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK)
SQLiteError::throw_(db, "setting journal mode"); SQLiteError::throw_(db, "setting journal mode");
if (mode == "wal") {
/* persist the WAL files when the db connection is closed. This allows
for read-only connections without write permissions on the
containing directory to succeed on a closed db. Setting the
journal_size_limit to 2^40 bytes results in the WAL files getting
truncated to 0 on exit and limits the on disk size of the WAL files
to 2^40 bytes following a checkpoint */
if (sqlite3_exec(db, "pragma main.journal_size_limit = 1099511627776;", 0, 0, 0) == SQLITE_OK) {
int enable = 1;
sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &enable);
}
}
/* Increase the auto-checkpoint interval to 40000 pages. This /* Increase the auto-checkpoint interval to 40000 pages. This
seems enough to ensure that instantiating the NixOS system seems enough to ensure that instantiating the NixOS system
derivation is done in a single fsync(). */ derivation is done in a single fsync(). */
@ -579,7 +593,7 @@ void LocalStore::openDB(State & state, bool create)
void LocalStore::makeStoreWritable() void LocalStore::makeStoreWritable()
{ {
#if __linux__ #if __linux__
if (getuid() != 0) return; if (!isRootUser()) return;
/* Check if /nix/store is on a read-only mount. */ /* Check if /nix/store is on a read-only mount. */
struct statvfs stat; struct statvfs stat;
if (statvfs(realStoreDir.get().c_str(), &stat) != 0) if (statvfs(realStoreDir.get().c_str(), &stat) != 0)
@ -1588,7 +1602,7 @@ static void makeMutable(const Path & path)
/* Upgrade from schema 6 (Nix 0.15) to schema 7 (Nix >= 1.3). */ /* Upgrade from schema 6 (Nix 0.15) to schema 7 (Nix >= 1.3). */
void LocalStore::upgradeStore7() void LocalStore::upgradeStore7()
{ {
if (getuid() != 0) return; if (!isRootUser()) return;
printInfo("removing immutable bits from the Nix store (this may take a while)..."); printInfo("removing immutable bits from the Nix store (this may take a while)...");
makeMutable(realStoreDir); makeMutable(realStoreDir);
} }

View file

@ -5,12 +5,15 @@ libstore_NAME = libnixstore
libstore_DIR := $(d) libstore_DIR := $(d)
libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc) libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
ifdef HOST_UNIX
libstore_SOURCES += $(wildcard $(d)/unix/*.cc)
endif
libstore_LIBS = libutil libstore_LIBS = libutil
libstore_LDFLAGS += $(SQLITE3_LIBS) $(LIBCURL_LIBS) $(THREAD_LDFLAGS) libstore_LDFLAGS += $(SQLITE3_LIBS) $(LIBCURL_LIBS) $(THREAD_LDFLAGS)
ifdef HOST_LINUX ifdef HOST_LINUX
libstore_LDFLAGS += -ldl libstore_LDFLAGS += -ldl
endif endif
$(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox))) $(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox)))
@ -27,8 +30,15 @@ ifeq ($(HAVE_SECCOMP), 1)
libstore_LDFLAGS += $(LIBSECCOMP_LIBS) libstore_LDFLAGS += $(LIBSECCOMP_LIBS)
endif endif
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libstore := -I $(d) -I $(d)/build
ifdef HOST_UNIX
INCLUDE_libstore += -I $(d)/unix
endif
libstore_CXXFLAGS += \ libstore_CXXFLAGS += \
-I src/libutil -I src/libstore -I src/libstore/build \ $(INCLUDE_libutil) $(INCLUDE_libstore) $(INCLUDE_libstore) \
-DNIX_PREFIX=\"$(prefix)\" \ -DNIX_PREFIX=\"$(prefix)\" \
-DNIX_STORE_DIR=\"$(storedir)\" \ -DNIX_STORE_DIR=\"$(storedir)\" \
-DNIX_DATA_DIR=\"$(datadir)\" \ -DNIX_DATA_DIR=\"$(datadir)\" \

View file

@ -2,6 +2,7 @@
#include "file-system.hh" #include "file-system.hh"
#include "globals.hh" #include "globals.hh"
#include "pathlocks.hh" #include "pathlocks.hh"
#include "users.hh"
#include <pwd.h> #include <pwd.h>
#include <grp.h> #include <grp.h>
@ -192,10 +193,10 @@ std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useUserNamespace)
bool useBuildUsers() bool useBuildUsers()
{ {
#if __linux__ #if __linux__
static bool b = (settings.buildUsersGroup != "" || settings.autoAllocateUids) && getuid() == 0; static bool b = (settings.buildUsersGroup != "" || settings.autoAllocateUids) && isRootUser();
return b; return b;
#elif __APPLE__ #elif __APPLE__
static bool b = settings.buildUsersGroup != "" && getuid() == 0; static bool b = settings.buildUsersGroup != "" && isRootUser();
return b; return b;
#else #else
return false; return false;

View file

@ -172,19 +172,18 @@ NarInfo NarInfo::fromJSON(
}; };
if (json.contains("url")) if (json.contains("url"))
res.url = ensureType(valueAt(json, "url"), value_t::string); res.url = getString(valueAt(json, "url"));
if (json.contains("compression")) if (json.contains("compression"))
res.compression = ensureType(valueAt(json, "compression"), value_t::string); res.compression = getString(valueAt(json, "compression"));
if (json.contains("downloadHash")) if (json.contains("downloadHash"))
res.fileHash = Hash::parseAny( res.fileHash = Hash::parseAny(
static_cast<const std::string &>( getString(valueAt(json, "downloadHash")),
ensureType(valueAt(json, "downloadHash"), value_t::string)),
std::nullopt); std::nullopt);
if (json.contains("downloadSize")) if (json.contains("downloadSize"))
res.fileSize = ensureType(valueAt(json, "downloadSize"), value_t::number_integer); res.fileSize = getInteger(valueAt(json, "downloadSize"));
return res; return res;
} }

View file

@ -190,23 +190,18 @@ nlohmann::json UnkeyedValidPathInfo::toJSON(
UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON( UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(
const Store & store, const Store & store,
const nlohmann::json & json) const nlohmann::json & _json)
{ {
using nlohmann::detail::value_t;
UnkeyedValidPathInfo res { UnkeyedValidPathInfo res {
Hash(Hash::dummy), Hash(Hash::dummy),
}; };
ensureType(json, value_t::object); auto & json = getObject(_json);
res.narHash = Hash::parseAny( res.narHash = Hash::parseAny(getString(valueAt(json, "narHash")), std::nullopt);
static_cast<const std::string &>( res.narSize = getInteger(valueAt(json, "narSize"));
ensureType(valueAt(json, "narHash"), value_t::string)),
std::nullopt);
res.narSize = ensureType(valueAt(json, "narSize"), value_t::number_integer);
try { try {
auto & references = ensureType(valueAt(json, "references"), value_t::array); auto references = getStringList(valueAt(json, "references"));
for (auto & input : references) for (auto & input : references)
res.references.insert(store.parseStorePath(static_cast<const std::string &> res.references.insert(store.parseStorePath(static_cast<const std::string &>
(input))); (input)));
@ -216,20 +211,16 @@ UnkeyedValidPathInfo UnkeyedValidPathInfo::fromJSON(
} }
if (json.contains("ca")) if (json.contains("ca"))
res.ca = ContentAddress::parse( res.ca = ContentAddress::parse(getString(valueAt(json, "ca")));
static_cast<const std::string &>(
ensureType(valueAt(json, "ca"), value_t::string)));
if (json.contains("deriver")) if (json.contains("deriver"))
res.deriver = store.parseStorePath( res.deriver = store.parseStorePath(getString(valueAt(json, "deriver")));
static_cast<const std::string &>(
ensureType(valueAt(json, "deriver"), value_t::string)));
if (json.contains("registrationTime")) if (json.contains("registrationTime"))
res.registrationTime = ensureType(valueAt(json, "registrationTime"), value_t::number_integer); res.registrationTime = getInteger(valueAt(json, "registrationTime"));
if (json.contains("ultimate")) if (json.contains("ultimate"))
res.ultimate = ensureType(valueAt(json, "ultimate"), value_t::boolean); res.ultimate = getBoolean(valueAt(json, "ultimate"));
if (json.contains("signatures")) if (json.contains("signatures"))
res.sigs = valueAt(json, "signatures"); res.sigs = valueAt(json, "signatures");

View file

@ -308,7 +308,7 @@ std::string optimisticLockProfile(const Path & profile)
Path profilesDir() Path profilesDir()
{ {
auto profileRoot = auto profileRoot =
(getuid() == 0) isRootUser()
? rootProfilesDir() ? rootProfilesDir()
: createNixStateDir() + "/profiles"; : createNixStateDir() + "/profiles";
createDirs(profileRoot); createDirs(profileRoot);
@ -332,7 +332,7 @@ Path getDefaultProfile()
// Backwards compatibiliy measure: Make root's profile available as // Backwards compatibiliy measure: Make root's profile available as
// `.../default` as it's what NixOS and most of the init scripts expect // `.../default` as it's what NixOS and most of the init scripts expect
Path globalProfileLink = settings.nixStateDir + "/profiles/default"; Path globalProfileLink = settings.nixStateDir + "/profiles/default";
if (getuid() == 0 && !pathExists(globalProfileLink)) { if (isRootUser() && !pathExists(globalProfileLink)) {
replaceSymlink(profile, globalProfileLink); replaceSymlink(profile, globalProfileLink);
} }
return absPath(readLink(profileLink), dirOf(profileLink)); return absPath(readLink(profileLink), dirOf(profileLink));

View file

@ -7,6 +7,7 @@
#include <sqlite3.h> #include <sqlite3.h>
#include <atomic> #include <atomic>
#include <thread>
namespace nix { namespace nix {
@ -256,10 +257,8 @@ void handleSQLiteBusy(const SQLiteBusy & e, time_t & nextWarning)
/* Sleep for a while since retrying the transaction right away /* Sleep for a while since retrying the transaction right away
is likely to fail again. */ is likely to fail again. */
checkInterrupt(); checkInterrupt();
struct timespec t; /* <= 0.1s */
t.tv_sec = 0; std::this_thread::sleep_for(std::chrono::milliseconds { rand() % 100 });
t.tv_nsec = (random() % 100) * 1000 * 1000; /* <= 0.1s */
nanosleep(&t, 0);
} }
} }

View file

@ -1307,7 +1307,7 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
#if __linux__ #if __linux__
else if (!pathExists(stateDir) else if (!pathExists(stateDir)
&& params.empty() && params.empty()
&& getuid() != 0 && !isRootUser()
&& !getEnv("NIX_STORE_DIR").has_value() && !getEnv("NIX_STORE_DIR").has_value()
&& !getEnv("NIX_STATE_DIR").has_value()) && !getEnv("NIX_STATE_DIR").has_value())
{ {

18
src/libutil-c/local.mk Normal file
View file

@ -0,0 +1,18 @@
libraries += libutilc
libutilc_NAME = libnixutilc
libutilc_DIR := $(d)
libutilc_SOURCES := $(wildcard $(d)/*.cc)
# Not just for this library itself, but also for downstream libraries using this library
INCLUDE_libutilc := -I $(d)
libutilc_CXXFLAGS += $(INCLUDE_libutil) $(INCLUDE_libutilc)
libutilc_LIBS = libutil
libutilc_LDFLAGS += $(THREAD_LDFLAGS)
libutilc_FORCE_INSTALL := 1

View file

@ -0,0 +1,148 @@
#include "nix_api_util.h"
#include "config.hh"
#include "error.hh"
#include "nix_api_util_internal.h"
#include "util.hh"
#include <cxxabi.h>
#include <typeinfo>
nix_c_context * nix_c_context_create()
{
return new nix_c_context();
}
void nix_c_context_free(nix_c_context * context)
{
delete context;
}
nix_err nix_context_error(nix_c_context * context)
{
if (context == nullptr) {
throw;
}
try {
throw;
} catch (nix::Error & e) {
/* Storing this exception is annoying, take what we need here */
context->last_err = e.what();
context->info = e.info();
int status;
const char * demangled = abi::__cxa_demangle(typeid(e).name(), 0, 0, &status);
if (demangled) {
context->name = demangled;
// todo: free(demangled);
} else {
context->name = typeid(e).name();
}
context->last_err_code = NIX_ERR_NIX_ERROR;
return context->last_err_code;
} catch (const std::exception & e) {
context->last_err = e.what();
context->last_err_code = NIX_ERR_UNKNOWN;
return context->last_err_code;
}
// unreachable
}
nix_err nix_set_err_msg(nix_c_context * context, nix_err err, const char * msg)
{
if (context == nullptr) {
// todo last_err_code
throw nix::Error("Nix C api error: %s", msg);
}
context->last_err_code = err;
context->last_err = msg;
return err;
}
const char * nix_version_get()
{
return PACKAGE_VERSION;
}
// Implementations
nix_err nix_setting_get(nix_c_context * context, const char * key, void * callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::map<std::string, nix::AbstractConfig::SettingInfo> settings;
nix::globalConfig.getSettings(settings);
if (settings.contains(key)) {
return call_nix_get_string_callback(settings[key].value, callback, user_data);
} else {
return nix_set_err_msg(context, NIX_ERR_KEY, "Setting not found");
}
}
NIXC_CATCH_ERRS
}
nix_err nix_setting_set(nix_c_context * context, const char * key, const char * value)
{
if (context)
context->last_err_code = NIX_OK;
if (nix::globalConfig.set(key, value))
return NIX_OK;
else {
return nix_set_err_msg(context, NIX_ERR_KEY, "Setting not found");
}
}
nix_err nix_libutil_init(nix_c_context * context)
{
if (context)
context->last_err_code = NIX_OK;
try {
nix::initLibUtil();
return NIX_OK;
}
NIXC_CATCH_ERRS
}
const char * nix_err_msg(nix_c_context * context, const nix_c_context * read_context, unsigned int * n)
{
if (context)
context->last_err_code = NIX_OK;
if (read_context->last_err) {
if (n)
*n = read_context->last_err->size();
return read_context->last_err->c_str();
}
nix_set_err_msg(context, NIX_ERR_UNKNOWN, "No error message");
return nullptr;
}
nix_err nix_err_name(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
if (read_context->last_err_code != NIX_ERR_NIX_ERROR) {
return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Last error was not a nix error");
}
return call_nix_get_string_callback(read_context->name, callback, user_data);
}
nix_err nix_err_info_msg(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data)
{
if (context)
context->last_err_code = NIX_OK;
if (read_context->last_err_code != NIX_ERR_NIX_ERROR) {
return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Last error was not a nix error");
}
return call_nix_get_string_callback(read_context->info->msg.str(), callback, user_data);
}
nix_err nix_err_code(const nix_c_context * read_context)
{
return read_context->last_err_code;
}
// internal
nix_err call_nix_get_string_callback(const std::string str, void * callback, void * user_data)
{
((nix_get_string_callback) callback)(str.c_str(), str.size(), user_data);
return NIX_OK;
}

View file

@ -0,0 +1,301 @@
#ifndef NIX_API_UTIL_H
#define NIX_API_UTIL_H
/**
* @defgroup libutil libutil
* @brief C bindings for nix libutil
*
* libutil is used for functionality shared between
* different Nix modules.
* @{
*/
/** @file
* @brief Main entry for the libutil C bindings
*
* Also contains error handling utilities
*/
#ifdef __cplusplus
extern "C" {
#endif
// cffi start
/** @defgroup errors Handling errors
* @brief Dealing with errors from the Nix side
*
* To handle errors that can be returned from the Nix API,
* a nix_c_context can be passed to any function that potentially returns an
* error.
*
* Error information will be stored in this context, and can be retrieved
* using nix_err_code and nix_err_msg.
*
* Passing NULL instead will cause the API to throw C++ errors.
*
* Example:
* @code{.c}
* int main() {
* nix_c_context* ctx = nix_c_context_create();
* nix_libutil_init(ctx);
* if (nix_err_code(ctx) != NIX_OK) {
* printf("error: %s\n", nix_err_msg(NULL, ctx, NULL));
* return 1;
* }
* return 0;
* }
* @endcode
* @{
*/
// Error codes
/**
* @brief Type for error codes in the NIX system
*
* This type can have one of several predefined constants:
* - NIX_OK: No error occurred (0)
* - NIX_ERR_UNKNOWN: An unknown error occurred (-1)
* - NIX_ERR_OVERFLOW: An overflow error occurred (-2)
* - NIX_ERR_KEY: A key error occurred (-3)
* - NIX_ERR_NIX_ERROR: A generic Nix error occurred (-4)
*/
typedef int nix_err;
/**
* @brief No error occurred.
*
* This error code is returned when no error has occurred during the function
* execution.
*/
#define NIX_OK 0
/**
* @brief An unknown error occurred.
*
* This error code is returned when an unknown error occurred during the
* function execution.
*/
#define NIX_ERR_UNKNOWN -1
/**
* @brief An overflow error occurred.
*
* This error code is returned when an overflow error occurred during the
* function execution.
*/
#define NIX_ERR_OVERFLOW -2
/**
* @brief A key error occurred.
*
* This error code is returned when a key error occurred during the function
* execution.
*/
#define NIX_ERR_KEY -3
/**
* @brief A generic Nix error occurred.
*
* This error code is returned when a generic Nix error occurred during the
* function execution.
*/
#define NIX_ERR_NIX_ERROR -4
/**
* @brief This object stores error state.
* @struct nix_c_context
*
* Passed as a first parameter to functions that can fail, to store error
* information.
*
* Optional wherever it can be used, passing NULL instead will throw a C++
* exception.
*
* The struct is laid out so that it can also be cast to nix_err* to inspect
* directly:
* @code{.c}
* assert(*(nix_err*)ctx == NIX_OK);
* @endcode
* @note These can be reused between different function calls,
* but make sure not to use them for multiple calls simultaneously (which can
* happen in callbacks).
*/
typedef struct nix_c_context nix_c_context;
/**
* @brief Called to get the value of a string owned by Nix.
*
* @param[in] start the string to copy.
* @param[in] n the string length.
* @param[in] user_data optional, arbitrary data, passed to the nix_get_string_callback when it's called.
*/
typedef void (*nix_get_string_callback)(const char * start, unsigned int n, void * user_data);
// Function prototypes
/**
* @brief Allocate a new nix_c_context.
* @throws std::bad_alloc
* @return allocated nix_c_context, owned by the caller. Free using
* `nix_c_context_free`.
*/
nix_c_context * nix_c_context_create();
/**
* @brief Free a nix_c_context. Does not fail.
* @param[out] context The context to free, mandatory.
*/
void nix_c_context_free(nix_c_context * context);
/**
* @}
*/
/**
* @brief Initializes nix_libutil and its dependencies.
*
* This function can be called multiple times, but should be called at least
* once prior to any other nix function.
*
* @param[out] context Optional, stores error information
* @return NIX_OK if the initialization is successful, or an error code
* otherwise.
*/
nix_err nix_libutil_init(nix_c_context * context);
/** @defgroup settings
* @{
*/
/**
* @brief Retrieves a setting from the nix global configuration.
*
* This function requires nix_libutil_init() to be called at least once prior to
* its use.
*
* @param[out] context optional, Stores error information
* @param[in] key The key of the setting to retrieve.
* @param[in] callback Called with the setting value.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @see nix_get_string_callback
* @return NIX_ERR_KEY if the setting is unknown, or NIX_OK if the setting was retrieved
* successfully.
*/
nix_err nix_setting_get(nix_c_context * context, const char * key, void * callback, void * user_data);
/**
* @brief Sets a setting in the nix global configuration.
*
* Use "extra-<setting name>" to append to the setting's value.
*
* Settings only apply for new State%s. Call nix_plugins_init() when you are
* done with the settings to load any plugins.
*
* @param[out] context optional, Stores error information
* @param[in] key The key of the setting to set.
* @param[in] value The value to set for the setting.
* @return NIX_ERR_KEY if the setting is unknown, or NIX_OK if the setting was
* set successfully.
*/
nix_err nix_setting_set(nix_c_context * context, const char * key, const char * value);
/**
* @}
*/
// todo: nix_plugins_init()
/**
* @brief Retrieves the nix library version.
*
* Does not fail.
* @return A static string representing the version of the nix library.
*/
const char * nix_version_get();
/** @addtogroup errors
* @{
*/
/**
* @brief Retrieves the most recent error message from a context.
*
* @pre This function should only be called after a previous nix function has
* returned an error.
*
* @param[out] context optional, the context to store errors in if this function
* fails
* @param[in] ctx the context to retrieve the error message from
* @param[out] n optional: a pointer to an unsigned int that is set to the
* length of the error.
* @return nullptr if no error message was ever set,
* a borrowed pointer to the error message otherwise.
*/
const char * nix_err_msg(nix_c_context * context, const nix_c_context * ctx, unsigned int * n);
/**
* @brief Retrieves the error message from errorInfo in a context.
*
* Used to inspect nix Error messages.
*
* @pre This function should only be called after a previous nix function has
* returned a NIX_ERR_NIX_ERROR
*
* @param[out] context optional, the context to store errors in if this function
* fails
* @param[in] read_context the context to retrieve the error message from.
* @param[in] callback Called with the error message.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @see nix_get_string_callback
* @return NIX_OK if there were no errors, an error code otherwise.
*/
nix_err
nix_err_info_msg(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data);
/**
* @brief Retrieves the error name from a context.
*
* Used to inspect nix Error messages.
*
* @pre This function should only be called after a previous nix function has
* returned a NIX_ERR_NIX_ERROR
*
* @param context optional, the context to store errors in if this function
* fails
* @param[in] read_context the context to retrieve the error message from
* @param[in] callback Called with the error name.
* @param[in] user_data optional, arbitrary data, passed to the callback when it's called.
* @see nix_get_string_callback
* @return NIX_OK if there were no errors, an error code otherwise.
*/
nix_err nix_err_name(nix_c_context * context, const nix_c_context * read_context, void * callback, void * user_data);
/**
* @brief Retrieves the most recent error code from a nix_c_context
*
* Equivalent to reading the first field of the context.
*
* Does not fail
*
* @param[in] read_context the context to retrieve the error message from
* @return most recent error code stored in the context.
*/
nix_err nix_err_code(const nix_c_context * read_context);
/**
* @brief Set an error message on a nix context.
*
* This should be used when you want to throw an error from a PrimOp callback.
*
* All other use is internal to the API.
*
* @param context context to write the error message to, or NULL
* @param err The error code to set and return
* @param msg The error message to set.
* @returns the error code set
*/
nix_err nix_set_err_msg(nix_c_context * context, nix_err err, const char * msg);
/**
* @}
*/
// cffi end
#ifdef __cplusplus
}
#endif
/** @} */
#endif // NIX_API_UTIL_H

View file

@ -0,0 +1,49 @@
#ifndef NIX_API_UTIL_INTERNAL_H
#define NIX_API_UTIL_INTERNAL_H
#include <string>
#include <optional>
#include "error.hh"
#include "nix_api_util.h"
struct nix_c_context
{
nix_err last_err_code = NIX_OK;
std::optional<std::string> last_err = {};
std::optional<nix::ErrorInfo> info = {};
std::string name = "";
};
nix_err nix_context_error(nix_c_context * context);
/**
* Internal use only.
*
* Helper to invoke nix_get_string_callback
* @param context optional, the context to store errors in if this function
* fails
* @param str The string to observe
* @param callback Called with the observed string.
* @param user_data optional, arbitrary data, passed to the callback when it's called.
* @return NIX_OK if there were no errors.
* @see nix_get_string_callback
*/
nix_err call_nix_get_string_callback(const std::string str, void * callback, void * user_data);
#define NIXC_CATCH_ERRS \
catch (...) \
{ \
return nix_context_error(context); \
} \
return NIX_OK;
#define NIXC_CATCH_ERRS_RES(def) \
catch (...) \
{ \
nix_context_error(context); \
return def; \
}
#define NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_RES(nullptr)
#endif // NIX_API_UTIL_INTERNAL_H

View file

@ -285,7 +285,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
std::string line; std::string line;
std::getline(stream,line); std::getline(stream,line);
static const std::string commentChars("#/\\%@*-"); static const std::string commentChars("#/\\%@*-(");
std::string shebangContent; std::string shebangContent;
while (std::getline(stream,line) && !line.empty() && commentChars.find(line[0]) != std::string::npos){ while (std::getline(stream,line) && !line.empty() && commentChars.find(line[0]) != std::string::npos){
line = chomp(line); line = chomp(line);

View file

@ -12,8 +12,6 @@
#include <brotli/decode.h> #include <brotli/decode.h>
#include <brotli/encode.h> #include <brotli/encode.h>
#include <iostream>
namespace nix { namespace nix {
static const int COMPRESSION_LEVEL_DEFAULT = -1; static const int COMPRESSION_LEVEL_DEFAULT = -1;
@ -40,20 +38,26 @@ struct ArchiveDecompressionSource : Source
{ {
std::unique_ptr<TarArchive> archive = 0; std::unique_ptr<TarArchive> archive = 0;
Source & src; Source & src;
ArchiveDecompressionSource(Source & src) : src(src) {} std::optional<std::string> compressionMethod;
ArchiveDecompressionSource(Source & src, std::optional<std::string> compressionMethod = std::nullopt)
: src(src)
, compressionMethod(std::move(compressionMethod))
{
}
~ArchiveDecompressionSource() override {} ~ArchiveDecompressionSource() override {}
size_t read(char * data, size_t len) override { size_t read(char * data, size_t len) override
{
struct archive_entry * ae; struct archive_entry * ae;
if (!archive) { if (!archive) {
archive = std::make_unique<TarArchive>(src, true); archive = std::make_unique<TarArchive>(src, /*raw*/ true, compressionMethod);
this->archive->check(archive_read_next_header(this->archive->archive, &ae), this->archive->check(archive_read_next_header(this->archive->archive, &ae), "failed to read header (%s)");
"failed to read header (%s)");
if (archive_filter_count(this->archive->archive) < 2) { if (archive_filter_count(this->archive->archive) < 2) {
throw CompressionError("input compression not recognized"); throw CompressionError("input compression not recognized");
} }
} }
ssize_t result = archive_read_data(this->archive->archive, data, len); ssize_t result = archive_read_data(this->archive->archive, data, len);
if (result > 0) return result; if (result > 0)
return result;
if (result == 0) { if (result == 0) {
throw EndOfFile("reached end of compressed file"); throw EndOfFile("reached end of compressed file");
} }
@ -67,16 +71,19 @@ struct ArchiveCompressionSink : CompressionSink
Sink & nextSink; Sink & nextSink;
struct archive * archive; struct archive * archive;
ArchiveCompressionSink(Sink & nextSink, std::string format, bool parallel, int level = COMPRESSION_LEVEL_DEFAULT) : nextSink(nextSink) ArchiveCompressionSink(Sink & nextSink, std::string format, bool parallel, int level = COMPRESSION_LEVEL_DEFAULT)
: nextSink(nextSink)
{ {
archive = archive_write_new(); archive = archive_write_new();
if (!archive) throw Error("failed to initialize libarchive"); if (!archive)
throw Error("failed to initialize libarchive");
check(archive_write_add_filter_by_name(archive, format.c_str()), "couldn't initialize compression (%s)"); check(archive_write_add_filter_by_name(archive, format.c_str()), "couldn't initialize compression (%s)");
check(archive_write_set_format_raw(archive)); check(archive_write_set_format_raw(archive));
if (parallel) if (parallel)
check(archive_write_set_filter_option(archive, format.c_str(), "threads", "0")); check(archive_write_set_filter_option(archive, format.c_str(), "threads", "0"));
if (level != COMPRESSION_LEVEL_DEFAULT) if (level != COMPRESSION_LEVEL_DEFAULT)
check(archive_write_set_filter_option(archive, format.c_str(), "compression-level", std::to_string(level).c_str())); check(archive_write_set_filter_option(
archive, format.c_str(), "compression-level", std::to_string(level).c_str()));
// disable internal buffering // disable internal buffering
check(archive_write_set_bytes_per_block(archive, 0)); check(archive_write_set_bytes_per_block(archive, 0));
// disable output padding // disable output padding
@ -86,7 +93,8 @@ struct ArchiveCompressionSink : CompressionSink
~ArchiveCompressionSink() override ~ArchiveCompressionSink() override
{ {
if (archive) archive_write_free(archive); if (archive)
archive_write_free(archive);
} }
void finish() override void finish() override
@ -106,7 +114,8 @@ struct ArchiveCompressionSink : CompressionSink
void writeUnbuffered(std::string_view data) override void writeUnbuffered(std::string_view data) override
{ {
ssize_t result = archive_write_data(archive, data.data(), data.length()); ssize_t result = archive_write_data(archive, data.data(), data.length());
if (result <= 0) check(result); if (result <= 0)
check(result);
} }
private: private:
@ -130,13 +139,20 @@ private:
struct NoneSink : CompressionSink struct NoneSink : CompressionSink
{ {
Sink & nextSink; Sink & nextSink;
NoneSink(Sink & nextSink, int level = COMPRESSION_LEVEL_DEFAULT) : nextSink(nextSink) NoneSink(Sink & nextSink, int level = COMPRESSION_LEVEL_DEFAULT)
: nextSink(nextSink)
{ {
if (level != COMPRESSION_LEVEL_DEFAULT) if (level != COMPRESSION_LEVEL_DEFAULT)
warn("requested compression level '%d' not supported by compression method 'none'", level); warn("requested compression level '%d' not supported by compression method 'none'", level);
} }
void finish() override { flush(); } void finish() override
void writeUnbuffered(std::string_view data) override { nextSink(data); } {
flush();
}
void writeUnbuffered(std::string_view data) override
{
nextSink(data);
}
}; };
struct BrotliDecompressionSink : ChunkedCompressionSink struct BrotliDecompressionSink : ChunkedCompressionSink
@ -145,7 +161,8 @@ struct BrotliDecompressionSink : ChunkedCompressionSink
BrotliDecoderState * state; BrotliDecoderState * state;
bool finished = false; bool finished = false;
BrotliDecompressionSink(Sink & nextSink) : nextSink(nextSink) BrotliDecompressionSink(Sink & nextSink)
: nextSink(nextSink)
{ {
state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr); state = BrotliDecoderCreateInstance(nullptr, nullptr, nullptr);
if (!state) if (!state)
@ -173,10 +190,7 @@ struct BrotliDecompressionSink : ChunkedCompressionSink
while (!finished && (!data.data() || avail_in)) { while (!finished && (!data.data() || avail_in)) {
checkInterrupt(); checkInterrupt();
if (!BrotliDecoderDecompressStream(state, if (!BrotliDecoderDecompressStream(state, &avail_in, &next_in, &avail_out, &next_out, nullptr))
&avail_in, &next_in,
&avail_out, &next_out,
nullptr))
throw CompressionError("error while decompressing brotli file"); throw CompressionError("error while decompressing brotli file");
if (avail_out < sizeof(outbuf) || avail_in == 0) { if (avail_out < sizeof(outbuf) || avail_in == 0) {
@ -206,8 +220,8 @@ std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Si
else if (method == "br") else if (method == "br")
return std::make_unique<BrotliDecompressionSink>(nextSink); return std::make_unique<BrotliDecompressionSink>(nextSink);
else else
return sourceToSink([&](Source & source) { return sourceToSink([method, &nextSink](Source & source) {
auto decompressionSource = std::make_unique<ArchiveDecompressionSource>(source); auto decompressionSource = std::make_unique<ArchiveDecompressionSource>(source, method);
decompressionSource->drainInto(nextSink); decompressionSource->drainInto(nextSink);
}); });
} }
@ -219,7 +233,8 @@ struct BrotliCompressionSink : ChunkedCompressionSink
BrotliEncoderState * state; BrotliEncoderState * state;
bool finished = false; bool finished = false;
BrotliCompressionSink(Sink & nextSink) : nextSink(nextSink) BrotliCompressionSink(Sink & nextSink)
: nextSink(nextSink)
{ {
state = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr); state = BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
if (!state) if (!state)
@ -247,11 +262,9 @@ struct BrotliCompressionSink : ChunkedCompressionSink
while (!finished && (!data.data() || avail_in)) { while (!finished && (!data.data() || avail_in)) {
checkInterrupt(); checkInterrupt();
if (!BrotliEncoderCompressStream(state, if (!BrotliEncoderCompressStream(
data.data() ? BROTLI_OPERATION_PROCESS : BROTLI_OPERATION_FINISH, state, data.data() ? BROTLI_OPERATION_PROCESS : BROTLI_OPERATION_FINISH, &avail_in, &next_in,
&avail_in, &next_in, &avail_out, &next_out, nullptr))
&avail_out, &next_out,
nullptr))
throw CompressionError("error while compressing brotli compression"); throw CompressionError("error while compressing brotli compression");
if (avail_out < sizeof(outbuf) || avail_in == 0) { if (avail_out < sizeof(outbuf) || avail_in == 0) {
@ -267,9 +280,8 @@ struct BrotliCompressionSink : ChunkedCompressionSink
ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel, int level) ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel, int level)
{ {
std::vector<std::string> la_supports = { std::vector<std::string> la_supports = {"bzip2", "compress", "grzip", "gzip", "lrzip", "lz4",
"bzip2", "compress", "grzip", "gzip", "lrzip", "lz4", "lzip", "lzma", "lzop", "xz", "zstd" "lzip", "lzma", "lzop", "xz", "zstd"};
};
if (std::find(la_supports.begin(), la_supports.end(), method) != la_supports.end()) { if (std::find(la_supports.begin(), la_supports.end(), method) != la_supports.end()) {
return make_ref<ArchiveCompressionSink>(nextSink, method, parallel, level); return make_ref<ArchiveCompressionSink>(nextSink, method, parallel, level);
} }

View file

@ -11,7 +11,7 @@ namespace nix {
struct CompressionSink : BufferedSink, FinishSink struct CompressionSink : BufferedSink, FinishSink
{ {
using BufferedSink::operator (); using BufferedSink::operator();
using BufferedSink::writeUnbuffered; using BufferedSink::writeUnbuffered;
using FinishSink::finish; using FinishSink::finish;
}; };
@ -22,7 +22,8 @@ std::unique_ptr<FinishSink> makeDecompressionSink(const std::string & method, Si
std::string compress(const std::string & method, std::string_view in, const bool parallel = false, int level = -1); std::string compress(const std::string & method, std::string_view in, const bool parallel = false, int level = -1);
ref<CompressionSink> makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false, int level = -1); ref<CompressionSink>
makeCompressionSink(const std::string & method, Sink & nextSink, const bool parallel = false, int level = -1);
MakeError(UnknownCompressionMethod, Error); MakeError(UnknownCompressionMethod, Error);

View file

@ -124,7 +124,7 @@ static void applyConfigInner(const std::string & contents, const std::string & p
auto p = absPath(tokens[1], dirOf(path)); auto p = absPath(tokens[1], dirOf(path));
if (pathExists(p)) { if (pathExists(p)) {
try { try {
std::string includedContents = readFile(path); std::string includedContents = readFile(p);
applyConfigInner(includedContents, p, parsedContents); applyConfigInner(includedContents, p, parsedContents);
} catch (SystemError &) { } catch (SystemError &) {
// TODO: Do we actually want to ignore this? Or is it better to fail? // TODO: Do we actually want to ignore this? Or is it better to fail?

View file

@ -2,7 +2,6 @@
#include <cstring> #include <cstring>
#include "current-process.hh" #include "current-process.hh"
#include "namespaces.hh"
#include "util.hh" #include "util.hh"
#include "finally.hh" #include "finally.hh"
#include "file-system.hh" #include "file-system.hh"
@ -17,6 +16,7 @@
# include <mutex> # include <mutex>
# include <sys/resource.h> # include <sys/resource.h>
# include "cgroup.hh" # include "cgroup.hh"
# include "namespaces.hh"
#endif #endif
#include <sys/mount.h> #include <sys/mount.h>
@ -82,9 +82,11 @@ void setStackSize(rlim_t stackSize)
void restoreProcessContext(bool restoreMounts) void restoreProcessContext(bool restoreMounts)
{ {
restoreSignals(); unix::restoreSignals();
if (restoreMounts) { if (restoreMounts) {
#if __linux__
restoreMountNamespace(); restoreMountNamespace();
#endif
} }
if (savedStackSize) { if (savedStackSize) {

View file

@ -32,7 +32,6 @@ std::map<std::string, std::string> getEnv()
return env; return env;
} }
void clearEnv() void clearEnv()
{ {
for (auto & name : getEnv()) for (auto & name : getEnv())
@ -43,7 +42,7 @@ void replaceEnv(const std::map<std::string, std::string> & newEnv)
{ {
clearEnv(); clearEnv();
for (auto & newEnvVar : newEnv) for (auto & newEnvVar : newEnv)
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1); setEnv(newEnvVar.first.c_str(), newEnvVar.second.c_str());
} }
} }

View file

@ -28,6 +28,14 @@ std::optional<std::string> getEnvNonEmpty(const std::string & key);
*/ */
std::map<std::string, std::string> getEnv(); std::map<std::string, std::string> getEnv();
/**
* Like POSIX `setenv`, but always overrides.
*
* We don't need the non-overriding version, and this is easier to
* reimplement on Windows.
*/
int setEnv(const char * name, const char * value);
/** /**
* Clear the environment. * Clear the environment.
*/ */

Some files were not shown because too many files have changed in this diff Show more