diff --git a/src/libexpr/c/meson.build b/src/libexpr/c/meson.build new file mode 100644 index 000000000..648b037d0 --- /dev/null +++ b/src/libexpr/c/meson.build @@ -0,0 +1,31 @@ +pkg = import('pkgconfig') + +libexprc_sources = files( + 'nix_api_expr.cc', + 'nix_api_external.cc', + 'nix_api_value.cc', +) + +all_sources += { + 'libexprc': libexprc_sources +} + +libexprc = library( + 'nix-expr-c', + libexprc_sources, + dependencies : [ + liblixutil, + liblixstore, + liblixexpr, + liblixutilc, + liblixstorec, + boehm, + ], +) + +pkg.generate(libexprc, requires: [ 'nix-store-c' ]) + +liblixexprc = declare_dependency( + include_directories : include_directories('.'), + link_with : libexprc, +) diff --git a/src/libexpr/c/nix_api_expr.cc b/src/libexpr/c/nix_api_expr.cc new file mode 100644 index 000000000..f18ef399b --- /dev/null +++ b/src/libexpr/c/nix_api_expr.cc @@ -0,0 +1,178 @@ +#include +#include +#include +#include + +#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 +#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, + std::equal_to, + traceable_allocator>> + 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 +void nix_gc_incref(nix_c_context * context, const void *) +{ + if (context) + context->last_err_code = NIX_OK; + return NIX_OK; +} +void 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 +} diff --git a/src/libexpr/c/nix_api_expr.h b/src/libexpr/c/nix_api_expr.h new file mode 100644 index 000000000..7504b5d7a --- /dev/null +++ b/src/libexpr/c/nix_api_expr.h @@ -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 diff --git a/src/libexpr/c/nix_api_expr_internal.h b/src/libexpr/c/nix_api_expr_internal.h new file mode 100644 index 000000000..2b066ecff --- /dev/null +++ b/src/libexpr/c/nix_api_expr_internal.h @@ -0,0 +1,64 @@ +#ifndef NIX_API_EXPR_INTERNAL_H +#define NIX_API_EXPR_INTERNAL_H + +#include "eval.hh" +#include "attr-set.hh" +#include "nix_api_value.h" + +class CListBuilder +{ +private: + std::vector values; + +public: + CListBuilder(size_t capacity) + { + values.reserve(capacity); + } + + void push_back(nix::Value * value) + { + values.push_back(value); + } + + Value * finish(nix::EvalState * state, nix::Value * list) + { + state->mkList(*list, values.size()); + for (size_t n = 0; n < list->listSize(); ++n) { + list->listElems()[n] = values[n]; + } + return list; + } +}; + +struct EvalState +{ + nix::EvalState state; +}; + +struct BindingsBuilder +{ + nix::BindingsBuilder builder; +}; + +struct ListBuilder +{ + CListBuilder 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 diff --git a/src/libexpr/c/nix_api_external.cc b/src/libexpr/c/nix_api_external.cc new file mode 100644 index 000000000..c237cfb70 --- /dev/null +++ b/src/libexpr/c/nix_api_external.cc @@ -0,0 +1,198 @@ +#include "attr-set.hh" +#include "config.hh" +#include "eval.hh" +#include "gc/gc.h" +#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 + +#ifdef HAVE_BOEHMGC +#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(&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(&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((nix::ExternalValueBase *) b); + if (r) + return r->get_ptr(); + return nullptr; + } + NIXC_CATCH_ERRS_NULL +} diff --git a/src/libexpr/c/nix_api_external.h b/src/libexpr/c/nix_api_external.h new file mode 100644 index 000000000..12ea00407 --- /dev/null +++ b/src/libexpr/c/nix_api_external.h @@ -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 diff --git a/src/libexpr/c/nix_api_value.cc b/src/libexpr/c/nix_api_value.cc new file mode 100644 index 000000000..f8292ec63 --- /dev/null +++ b/src/libexpr/c/nix_api_value.cc @@ -0,0 +1,521 @@ +#include "attr-set.hh" +#include "config.hh" +#include "eval.hh" +#include "gc/gc.h" +#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 +#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("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.string.s; + } + 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); + return v._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(NAN); +} + +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); + 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 = CListBuilder(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, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + list_builder->builder.push_back((nix::Value *) value); + } + NIXC_CATCH_ERRS +} + +void nix_list_builder_free(ListBuilder * bb) +{ +#if HAVE_BOEHMGC + GC_FREE(bb); +#else + delete bb; +#endif +} + +nix_err nix_make_list(nix_c_context * context, EvalState * s, ListBuilder * list_builder, Value * value) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto & v = check_value_not_null(value); + list_builder->builder.finish(&(s->state), &v); + } + 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 +} diff --git a/src/libexpr/c/nix_api_value.h b/src/libexpr/c/nix_api_value.h new file mode 100644 index 000000000..b7115c27d --- /dev/null +++ b/src/libexpr/c/nix_api_value.h @@ -0,0 +1,433 @@ +#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, EvalState * s, 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] value value to insert + * @return error code, NIX_OK on success. + */ +nix_err nix_list_builder_insert(nix_c_context * context, ListBuilder * list_builder, Value * value); + +/** @brief Free a list builder + * + * Does not fail. + * @param[in] builder the builder to free + */ +void nix_list_builder_free(ListBuilder * 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 diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build index a77c86453..d26f83dc9 100644 --- a/src/libexpr/meson.build +++ b/src/libexpr/meson.build @@ -87,3 +87,5 @@ liblixexpr = declare_dependency( include_directories : include_directories('.'), link_with : libexpr, ) + +subdir('c') diff --git a/src/libstore/c/meson.build b/src/libstore/c/meson.build new file mode 100644 index 000000000..3082e14fb --- /dev/null +++ b/src/libstore/c/meson.build @@ -0,0 +1,26 @@ +pkg = import('pkgconfig') + +libstorec_sources = files( + 'nix_api_store.cc', +) + +all_sources += { + 'libstorec': libstorec_sources, +} + +libstorec = library( + 'nix-store-c', + libstorec_sources, + dependencies : [ + liblixutil, # Internal. + liblixstore, # Internal. + liblixutilc, # Internal. + ], +) + +pkg.generate(libstorec, requires : [ 'nix-util-c' ]) + +liblixstorec = declare_dependency( + include_directories : include_directories('.'), + link_with : libstorec, +) diff --git a/src/libstore/c/nix_api_store.cc b/src/libstore/c/nix_api_store.cc new file mode 100644 index 000000000..199f5526a --- /dev/null +++ b/src/libstore/c/nix_api_store.cc @@ -0,0 +1,136 @@ +#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, char * dest, unsigned int n) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto res = store->ptr->getUri(); + return nix_export_std_string(context, res, dest, n); + } + NIXC_CATCH_ERRS +} + +nix_err nix_store_get_version(nix_c_context * context, Store * store, char * dest, unsigned int n) +{ + if (context) + context->last_err_code = NIX_OK; + try { + auto res = store->ptr->getVersion(); + if (!res) + res = ""; + return nix_export_std_string(context, *res, dest, n); + } + 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 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; +} diff --git a/src/libstore/c/nix_api_store.h b/src/libstore/c/nix_api_store.h new file mode 100644 index 000000000..28544fa90 --- /dev/null +++ b/src/libstore/c/nix_api_store.h @@ -0,0 +1,146 @@ +#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 + +#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[out] dest The allocated area to write the string to. + * @param[in] n Maximum size of the returned string. + * @return error code, NIX_OK on success. + */ +nix_err nix_store_get_uri(nix_c_context * context, Store * store, char * dest, unsigned int n); + +// 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[out] dest The allocated area to write the string to. + * @param[in] n Maximum size of the returned string. + * @return error code, NIX_OK on success. + */ +nix_err nix_store_get_version(nix_c_context *, Store * store, char * dest, unsigned int n); + +// cffi end +#ifdef __cplusplus +} +#endif +/** + * @} + */ +#endif // NIX_API_STORE_H diff --git a/src/libstore/c/nix_api_store_internal.h b/src/libstore/c/nix_api_store_internal.h new file mode 100644 index 000000000..13db0c07c --- /dev/null +++ b/src/libstore/c/nix_api_store_internal.h @@ -0,0 +1,15 @@ +#ifndef NIX_API_STORE_INTERNAL_H +#define NIX_API_STORE_INTERNAL_H +#include "store-api.hh" + +struct Store +{ + nix::ref ptr; +}; + +struct StorePath +{ + nix::StorePath path; +}; + +#endif diff --git a/src/libstore/meson.build b/src/libstore/meson.build index fc53a1bab..d4e00b6fe 100644 --- a/src/libstore/meson.build +++ b/src/libstore/meson.build @@ -120,3 +120,5 @@ liblixstore = declare_dependency( include_directories : include_directories('.'), link_with : libstore, ) + +subdir('c') diff --git a/src/libutil/c/meson.build b/src/libutil/c/meson.build new file mode 100644 index 000000000..db1393e17 --- /dev/null +++ b/src/libutil/c/meson.build @@ -0,0 +1,25 @@ +pkg = import('pkgconfig') + +libutilc_sources = files( + 'nix_api_util.cc', +) + +all_sources += { + 'libutilc': libutilc_sources, +} + +libutilc = library( + 'nix-util-c', + libutilc_sources, + dependencies : [ + liblixutil, + ], + implicit_include_directories : true, +) + +pkg.generate(libutilc) + +liblixutilc = declare_dependency( + include_directories : include_directories('.'), + link_with : libutilc +) diff --git a/src/libutil/c/nix_api_util.cc b/src/libutil/c/nix_api_util.cc new file mode 100644 index 000000000..100e3b21d --- /dev/null +++ b/src/libutil/c/nix_api_util.cc @@ -0,0 +1,151 @@ +#include "nix_api_util.h" +#include "config.hh" +#include "error.hh" +#include "nix_api_util_internal.h" +#include "util.hh" + +#include +#include + +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, char * value, int n) +{ + if (context) + context->last_err_code = NIX_OK; + try { + std::map settings; + nix::globalConfig.getSettings(settings); + if (settings.contains(key)) + return nix_export_std_string(context, settings[key].value, value, n); + 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, char * value, int n) +{ + 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 nix_export_std_string(context, read_context->name, value, n); +} + +nix_err nix_err_info_msg(nix_c_context * context, const nix_c_context * read_context, char * value, int n) +{ + 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 nix_export_std_string(context, read_context->info->msg.str(), value, n); +} + +nix_err nix_err_code(const nix_c_context * read_context) +{ + return read_context->last_err_code; +} + +// internal +nix_err nix_export_std_string(nix_c_context * context, const std::string_view str, char * dest, unsigned int n) +{ + size_t i = str.copy(dest, n - 1); + dest[i] = 0; + if (i == n - 1) { + return nix_set_err_msg(context, NIX_ERR_OVERFLOW, "Provided buffer too short"); + } else + return NIX_OK; +} diff --git a/src/libutil/c/nix_api_util.h b/src/libutil/c/nix_api_util.h new file mode 100644 index 000000000..c288654fd --- /dev/null +++ b/src/libutil/c/nix_api_util.h @@ -0,0 +1,290 @@ +#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; + +// 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[out] value A pointer to a buffer where the value of the setting will + * be stored. + * @param[in] n The size of the buffer pointed to by value. + * @return NIX_ERR_KEY if the setting is unknown, NIX_ERR_OVERFLOW if the + * provided buffer is too short, or NIX_OK if the setting was retrieved + * successfully. + */ +nix_err nix_setting_get(nix_c_context * context, const char * key, char * value, int n); + +/** + * @brief Sets a setting in the nix global configuration. + * + * Use "extra-" 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[out] value The allocated area to write the error string to. + * @param[in] n Maximum size of the returned string. + * @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, char * value, int n); + +/** + * @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[out] value The allocated area to write the error string to. + * @param[in] n Maximum size of the returned string. + * @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, char * value, int n); + +/** + * @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 diff --git a/src/libutil/c/nix_api_util_internal.h b/src/libutil/c/nix_api_util_internal.h new file mode 100644 index 000000000..53c260e35 --- /dev/null +++ b/src/libutil/c/nix_api_util_internal.h @@ -0,0 +1,49 @@ +#ifndef NIX_API_UTIL_INTERNAL_H +#define NIX_API_UTIL_INTERNAL_H + +#include +#include + +#include "error.hh" +#include "nix_api_util.h" + +struct nix_c_context +{ + nix_err last_err_code = NIX_OK; + std::optional last_err = {}; + std::optional info = {}; + std::string name = ""; +}; + +nix_err nix_context_error(nix_c_context * context); + +/** + * Internal use only. + * + * Export a std::string across the C api boundary + * @param context optional, the context to store errors in if this function + * fails + * @param str The string to export + * @param value The allocated area to write the string to. + * @param n Maximum size of the returned string. + * @return NIX_OK if there were no errors, NIX_ERR_OVERFLOW if the string length + * exceeds `n`. + */ +nix_err nix_export_std_string(nix_c_context * context, const std::string_view str, char * dest, unsigned int n); + +#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 diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 57812d8a4..08dd40a47 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -54,3 +54,6 @@ liblixutil = declare_dependency( include_directories : include_directories('.'), link_with : libutil ) + +subdir('c') +