Compare commits
26 Commits
string-dat
...
replace-lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4958bf040b | ||
|
|
09d6847490 | ||
|
|
53af1119fb | ||
|
|
68d2292f3a | ||
|
|
16f0279d4f | ||
|
|
8165419a0c | ||
|
|
7721fa6df4 | ||
|
|
cb5d97a607 | ||
|
|
436bc1f39e | ||
|
|
fcf3bdcac8 | ||
|
|
4b97f1130a | ||
|
|
614e143a20 | ||
|
|
77982c55b2 | ||
|
|
acacdf87b4 | ||
|
|
d5b6e1a0fc | ||
|
|
3511a919b4 | ||
|
|
f6aa8c0486 | ||
|
|
cd5cac0c40 | ||
|
|
958866b9a6 | ||
|
|
d07c24f4c8 | ||
|
|
95da93c05b | ||
|
|
bae1ca257a | ||
|
|
f8141a2c26 | ||
|
|
bdeaf976bd | ||
|
|
cdba2534cf | ||
|
|
8642c0a9a2 |
@@ -13,6 +13,7 @@ project(
|
||||
)
|
||||
|
||||
# Internal Libraries
|
||||
subproject('libcmarkcpp')
|
||||
subproject('libutil')
|
||||
subproject('libstore')
|
||||
subproject('libfetchers')
|
||||
|
||||
@@ -269,6 +269,8 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
|
||||
CXX_LD = "mold";
|
||||
};
|
||||
|
||||
dontUseCmakeConfigure = true;
|
||||
|
||||
mesonFlags =
|
||||
map (transformFlag "libutil") (ignoreCrossFile pkgs.nixComponents2.nix-util.mesonFlags)
|
||||
++ map (transformFlag "libstore") (ignoreCrossFile pkgs.nixComponents2.nix-store.mesonFlags)
|
||||
|
||||
@@ -174,24 +174,6 @@ rec {
|
||||
|
||||
buildNoTests = forAllSystems (system: nixpkgsFor.${system}.native.nixComponents2.nix-cli);
|
||||
|
||||
# Toggles some settings for better coverage. Windows needs these
|
||||
# library combinations, and Debian build Nix with GNU readline too.
|
||||
buildReadlineNoMarkdown =
|
||||
let
|
||||
components = forAllSystems (
|
||||
system:
|
||||
nixpkgsFor.${system}.native.nixComponents2.overrideScope (
|
||||
self: super: {
|
||||
nix-cmd = super.nix-cmd.override {
|
||||
enableMarkdown = false;
|
||||
readlineFlavor = "readline";
|
||||
};
|
||||
}
|
||||
)
|
||||
);
|
||||
in
|
||||
forAllPackages (pkgName: forAllSystems (system: components.${system}.${pkgName}));
|
||||
|
||||
# Perl bindings for various platforms.
|
||||
perlBindings = forAllSystems (system: nixpkgsFor.${system}.native.nixComponents2.nix-perl-bindings);
|
||||
|
||||
|
||||
17
src/libcmarkcpp/LICENSE.md
Normal file
17
src/libcmarkcpp/LICENSE.md
Normal file
@@ -0,0 +1,17 @@
|
||||
Copyright (c) 2008, Natacha Porté
|
||||
Copyright (c) 2011, Vicent Martí
|
||||
Copyright (c) 2014, Xavier Mendez, Devin Torres and the Hoedown authors
|
||||
Copyright (c) 2016--2023, Kristaps Dzonsons
|
||||
Copyright (c) 2025, Obsidian Systems
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose
|
||||
with or without fee is hereby granted, provided that the above copyright notice
|
||||
and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||
THIS SOFTWARE.
|
||||
36
src/libcmarkcpp/README.md
Normal file
36
src/libcmarkcpp/README.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# libcmarkcpp
|
||||
|
||||
A C++ terminal renderer for CommonMark documents.
|
||||
|
||||
## Overview
|
||||
|
||||
libcmarkcpp provides a terminal renderer for CommonMark (Markdown) documents using the cmark library. It renders formatted, colored output suitable for display in ANSI-capable terminals.
|
||||
|
||||
## Features
|
||||
|
||||
- ANSI color styling and text formatting (bold, italic, underline)
|
||||
- Intelligent text wrapping and indentation
|
||||
- Support for:
|
||||
- Headers with hierarchical styling
|
||||
- Lists (ordered and unordered)
|
||||
- Code blocks (fenced and indented)
|
||||
- Blockquotes
|
||||
- Links with OSC8 hyperlink support
|
||||
- Inline code, bold, and italic
|
||||
- Horizontal rules
|
||||
- Images
|
||||
- Terminal width detection and adaptive wrapping
|
||||
- Configurable margins, padding, and styling options
|
||||
|
||||
## Origin
|
||||
|
||||
This library is a C++ port of the terminal renderer from [lowdown](https://github.com/kristapsdz/lowdown) by Kristaps Dzonsons, adapted to work with the [cmark](https://github.com/commonmark/cmark) CommonMark implementation.
|
||||
|
||||
## License
|
||||
|
||||
ISC License (same as lowdown)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- cmark >= 0.31.0
|
||||
- C++20 compiler
|
||||
1090
src/libcmarkcpp/cmark-terminal.cc
Normal file
1090
src/libcmarkcpp/cmark-terminal.cc
Normal file
File diff suppressed because it is too large
Load Diff
89
src/libcmarkcpp/include/cmark/cmark-cpp.hh
Normal file
89
src/libcmarkcpp/include/cmark/cmark-cpp.hh
Normal file
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
/**
|
||||
* @file cmark-cpp.hh
|
||||
* @brief C++ wrappers for the cmark CommonMark library
|
||||
*/
|
||||
|
||||
#include <cmark.h>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <cassert>
|
||||
|
||||
namespace cmark {
|
||||
|
||||
using Node = struct cmark_node;
|
||||
using NodeType = cmark_node_type;
|
||||
using ListType = cmark_list_type;
|
||||
|
||||
using Iter = struct cmark_iter;
|
||||
|
||||
struct Deleter
|
||||
{
|
||||
void operator()(Node * ptr)
|
||||
{
|
||||
cmark_node_free(ptr);
|
||||
}
|
||||
|
||||
void operator()(Iter * ptr)
|
||||
{
|
||||
cmark_iter_free(ptr);
|
||||
}
|
||||
|
||||
void operator()(char * ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using UniquePtr = std::unique_ptr<Node, Deleter>;
|
||||
|
||||
static inline void parse_document(Node & root, std::string_view s, int options)
|
||||
{
|
||||
cmark_parser * parser = cmark_parser_new_with_mem_into_root(options, cmark_get_default_mem_allocator(), &root);
|
||||
cmark_parser_feed(parser, s.data(), s.size());
|
||||
(void) cmark_parser_finish(parser);
|
||||
cmark_parser_free(parser);
|
||||
}
|
||||
|
||||
static inline UniquePtr<Node> parse_document(std::string_view s, int options)
|
||||
{
|
||||
return UniquePtr<Node>{cmark_parse_document(s.data(), s.size(), options)};
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<char, Deleter> render_commonmark(Node & root, int options, int width)
|
||||
{
|
||||
return std::unique_ptr<char, Deleter>{cmark_render_commonmark(&root, options, width)};
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<char, Deleter> render_xml(Node & root, int options)
|
||||
{
|
||||
return std::unique_ptr<char, Deleter>{cmark_render_xml(&root, options)};
|
||||
}
|
||||
|
||||
static inline UniquePtr<Node> node_new(NodeType type)
|
||||
{
|
||||
return UniquePtr<Node>{cmark_node_new(type)};
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent takes ownership
|
||||
*/
|
||||
static inline Node & node_append_child(Node & node, UniquePtr<Node> child)
|
||||
{
|
||||
auto status = (bool) cmark_node_append_child(&node, &*child);
|
||||
assert(status);
|
||||
return *child.release();
|
||||
}
|
||||
|
||||
static inline bool node_set_literal(Node & node, const char * content)
|
||||
{
|
||||
return (bool) cmark_node_set_literal(&node, content);
|
||||
}
|
||||
|
||||
static inline bool node_set_list_type(Node & node, ListType type)
|
||||
{
|
||||
return (bool) cmark_node_set_list_type(&node, type);
|
||||
}
|
||||
|
||||
} // namespace cmark
|
||||
108
src/libcmarkcpp/include/cmark/cmark-terminal.hh
Normal file
108
src/libcmarkcpp/include/cmark/cmark-terminal.hh
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "cmark-cpp.hh"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace cmark {
|
||||
|
||||
/**
|
||||
* Terminal rendering options
|
||||
*/
|
||||
struct TerminalOptions
|
||||
{
|
||||
/** Terminal width in columns */
|
||||
size_t cols = 80;
|
||||
|
||||
/** Content width (0 = auto, max 80 or cols) */
|
||||
size_t width = 0;
|
||||
|
||||
/** Horizontal margin (left padding) */
|
||||
size_t hmargin = 0;
|
||||
|
||||
/** Horizontal padding (additional left padding) */
|
||||
size_t hpadding = 4;
|
||||
|
||||
/** Vertical margin (blank lines before/after) */
|
||||
size_t vmargin = 0;
|
||||
|
||||
/** Center content */
|
||||
bool centre = false;
|
||||
|
||||
/** Disable ANSI escape sequences */
|
||||
bool noAnsi = false;
|
||||
|
||||
/** Disable ANSI colors only */
|
||||
bool noColor = false;
|
||||
|
||||
/** Don't show any link URLs */
|
||||
bool noLink = false;
|
||||
|
||||
/** Don't show relative link URLs */
|
||||
bool noRelLink = false;
|
||||
|
||||
/** Shorten long URLs */
|
||||
bool shortLink = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Style attributes for terminal output
|
||||
*/
|
||||
struct Style
|
||||
{
|
||||
bool italic = false;
|
||||
bool strike = false;
|
||||
bool bold = false;
|
||||
bool under = false;
|
||||
size_t bcolour = 0; // Background color (ANSI code)
|
||||
size_t colour = 0; // Foreground color (ANSI code)
|
||||
int override = 0; // Override flags
|
||||
|
||||
static constexpr int OVERRIDE_UNDER = 0x01;
|
||||
static constexpr int OVERRIDE_BOLD = 0x02;
|
||||
|
||||
bool hasStyle() const
|
||||
{
|
||||
return colour || bold || italic || under || strike || bcolour || override;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Terminal renderer for CommonMark documents
|
||||
*
|
||||
* Renders a CMark AST to ANSI terminal output with styling, wrapping,
|
||||
* and proper indentation.
|
||||
*/
|
||||
class TerminalRenderer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Create a new terminal renderer with the given options
|
||||
*/
|
||||
explicit TerminalRenderer(const TerminalOptions & opts = TerminalOptions{});
|
||||
|
||||
~TerminalRenderer();
|
||||
|
||||
// Non-copyable
|
||||
TerminalRenderer(const TerminalRenderer &) = delete;
|
||||
TerminalRenderer & operator=(const TerminalRenderer &) = delete;
|
||||
|
||||
/**
|
||||
* Render a CMark node tree to a string
|
||||
*/
|
||||
std::string render(cmark::Node & root);
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience function to render a CMark document to terminal output
|
||||
*/
|
||||
std::string renderTerminal(cmark::Node & root, const TerminalOptions & opts = TerminalOptions{});
|
||||
|
||||
} // namespace cmark
|
||||
57
src/libcmarkcpp/meson.build
Normal file
57
src/libcmarkcpp/meson.build
Normal file
@@ -0,0 +1,57 @@
|
||||
project(
|
||||
'cmark-cpp',
|
||||
'cpp',
|
||||
version : '0.1',
|
||||
default_options : [
|
||||
'cpp_std=c++20',
|
||||
'warning_level=1',
|
||||
],
|
||||
meson_version : '>= 1.1',
|
||||
license : 'ISC',
|
||||
)
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
# CMark dependency
|
||||
cmark = dependency('libcmark', required : true)
|
||||
|
||||
sources = files(
|
||||
'cmark-terminal.cc',
|
||||
)
|
||||
|
||||
headers = files(
|
||||
'include/cmark/cmark-cpp.hh',
|
||||
'include/cmark/cmark-terminal.hh',
|
||||
)
|
||||
|
||||
include_dirs = include_directories('include')
|
||||
|
||||
libcmarkcpp = library(
|
||||
'cmarkcpp',
|
||||
sources,
|
||||
dependencies : cmark,
|
||||
include_directories : include_dirs,
|
||||
install : true,
|
||||
version : meson.project_version(),
|
||||
)
|
||||
|
||||
install_headers(headers, subdir : 'cmark')
|
||||
|
||||
# Make this available as a dependency for meson projects
|
||||
meson.override_dependency(
|
||||
'cmark-cpp',
|
||||
declare_dependency(
|
||||
include_directories : include_dirs,
|
||||
link_with : libcmarkcpp,
|
||||
dependencies : cmark,
|
||||
),
|
||||
)
|
||||
|
||||
# Pkg-config file
|
||||
pkg = import('pkgconfig')
|
||||
pkg.generate(
|
||||
libcmarkcpp,
|
||||
name : 'cmark-cpp',
|
||||
description : 'C++ terminal renderer for CommonMark',
|
||||
url : 'https://github.com/NixOS/nix',
|
||||
)
|
||||
@@ -34,7 +34,7 @@ EvalSettings evalSettings{
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string{rest}, {}, true, false);
|
||||
debug("fetching flake search path element '%s''", rest);
|
||||
auto [accessor, lockedRef] =
|
||||
flakeRef.resolve(fetchSettings, state.store).lazyFetch(fetchSettings, state.store);
|
||||
flakeRef.resolve(fetchSettings, *state.store).lazyFetch(fetchSettings, *state.store);
|
||||
auto storePath = nix::fetchToStore(
|
||||
state.fetchSettings,
|
||||
*state.store,
|
||||
@@ -180,7 +180,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir)
|
||||
{
|
||||
if (EvalSettings::isPseudoUrl(s)) {
|
||||
auto accessor = fetchers::downloadTarball(state.store, state.fetchSettings, EvalSettings::resolvePseudoUrl(s));
|
||||
auto accessor = fetchers::downloadTarball(*state.store, state.fetchSettings, EvalSettings::resolvePseudoUrl(s));
|
||||
auto storePath = fetchToStore(state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy);
|
||||
return state.storePath(storePath);
|
||||
}
|
||||
@@ -188,7 +188,8 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
|
||||
else if (hasPrefix(s, "flake:")) {
|
||||
experimentalFeatureSettings.require(Xp::Flakes);
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
|
||||
auto [accessor, lockedRef] = flakeRef.resolve(fetchSettings, state.store).lazyFetch(fetchSettings, state.store);
|
||||
auto [accessor, lockedRef] =
|
||||
flakeRef.resolve(fetchSettings, *state.store).lazyFetch(fetchSettings, *state.store);
|
||||
auto storePath = nix::fetchToStore(
|
||||
state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
|
||||
state.allowPath(storePath);
|
||||
|
||||
@@ -410,7 +410,7 @@ void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::strin
|
||||
Args::completeDir(completions, 0, prefix);
|
||||
|
||||
/* Look for registry entries that match the prefix. */
|
||||
for (auto & registry : fetchers::getRegistries(fetchSettings, store)) {
|
||||
for (auto & registry : fetchers::getRegistries(fetchSettings, *store)) {
|
||||
for (auto & entry : registry->entries) {
|
||||
auto from = entry.from.to_string();
|
||||
if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
|
||||
|
||||
@@ -1,79 +1,38 @@
|
||||
#include "nix/cmd/markdown.hh"
|
||||
#include "nix/util/environment-variables.hh"
|
||||
#include "nix/util/error.hh"
|
||||
#include "nix/util/finally.hh"
|
||||
#include "nix/util/terminal.hh"
|
||||
|
||||
#include "cmd-config-private.hh"
|
||||
|
||||
#if HAVE_LOWDOWN
|
||||
# include <sys/queue.h>
|
||||
# include <lowdown.h>
|
||||
#endif
|
||||
#include <cmark/cmark-cpp.hh>
|
||||
#include <cmark/cmark-terminal.hh>
|
||||
|
||||
namespace nix {
|
||||
|
||||
#if HAVE_LOWDOWN
|
||||
static std::string doRenderMarkdownToTerminal(std::string_view markdown)
|
||||
{
|
||||
int windowWidth = getWindowSize().second;
|
||||
|
||||
# if HAVE_LOWDOWN_1_4
|
||||
struct lowdown_opts_term opts_term{
|
||||
.cols = (size_t) std::max(windowWidth - 5, 60),
|
||||
.hmargin = 0,
|
||||
.vmargin = 0,
|
||||
};
|
||||
# endif
|
||||
struct lowdown_opts opts{
|
||||
.type = LOWDOWN_TERM,
|
||||
# if HAVE_LOWDOWN_1_4
|
||||
.term = opts_term,
|
||||
# endif
|
||||
.maxdepth = 20,
|
||||
# if !HAVE_LOWDOWN_1_4
|
||||
.cols = (size_t) std::max(windowWidth - 5, 60),
|
||||
.hmargin = 0,
|
||||
.vmargin = 0,
|
||||
# endif
|
||||
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
|
||||
.oflags =
|
||||
# if HAVE_LOWDOWN_1_4
|
||||
LOWDOWN_TERM_NORELLINK // To render full links while skipping relative ones
|
||||
# else
|
||||
LOWDOWN_TERM_NOLINK
|
||||
# endif
|
||||
};
|
||||
// Set up terminal rendering options
|
||||
::cmark::TerminalOptions opts;
|
||||
opts.cols = std::max(windowWidth - 5, 60);
|
||||
opts.hmargin = 0;
|
||||
opts.vmargin = 0;
|
||||
opts.noRelLink = true; // Skip rendering relative links
|
||||
|
||||
if (!isTTY())
|
||||
opts.oflags |= LOWDOWN_TERM_NOANSI;
|
||||
opts.noAnsi = true;
|
||||
|
||||
auto doc = lowdown_doc_new(&opts);
|
||||
// Parse the markdown document
|
||||
auto doc = ::cmark::parse_document(markdown, CMARK_OPT_DEFAULT);
|
||||
if (!doc)
|
||||
throw Error("cannot allocate Markdown document");
|
||||
Finally freeDoc([&]() { lowdown_doc_free(doc); });
|
||||
|
||||
size_t maxn = 0;
|
||||
auto node = lowdown_doc_parse(doc, &maxn, markdown.data(), markdown.size(), nullptr);
|
||||
if (!node)
|
||||
throw Error("cannot parse Markdown document");
|
||||
Finally freeNode([&]() { lowdown_node_free(node); });
|
||||
|
||||
auto renderer = lowdown_term_new(&opts);
|
||||
if (!renderer)
|
||||
throw Error("cannot allocate Markdown renderer");
|
||||
Finally freeRenderer([&]() { lowdown_term_free(renderer); });
|
||||
|
||||
auto buf = lowdown_buf_new(16384);
|
||||
if (!buf)
|
||||
throw Error("cannot allocate Markdown output buffer");
|
||||
Finally freeBuffer([&]() { lowdown_buf_free(buf); });
|
||||
|
||||
int rndr_res = lowdown_term_rndr(buf, renderer, node);
|
||||
if (!rndr_res)
|
||||
throw Error("allocation error while rendering Markdown");
|
||||
|
||||
return std::string(buf->data, buf->size);
|
||||
try {
|
||||
// Render to terminal
|
||||
return ::cmark::renderTerminal(*doc, opts);
|
||||
} catch (const std::exception & e) {
|
||||
throw Error("error rendering Markdown: %s", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown)
|
||||
@@ -84,11 +43,4 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
|
||||
return doRenderMarkdownToTerminal(markdown);
|
||||
}
|
||||
|
||||
#else
|
||||
std::string renderMarkdownToTerminal(std::string_view markdown)
|
||||
{
|
||||
return std::string(markdown);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace nix
|
||||
|
||||
@@ -26,25 +26,13 @@ deps_public_maybe_subproject = [
|
||||
dependency('nix-expr'),
|
||||
dependency('nix-flake'),
|
||||
dependency('nix-main'),
|
||||
dependency('cmark-cpp'),
|
||||
]
|
||||
subdir('nix-meson-build-support/subprojects')
|
||||
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
lowdown = dependency(
|
||||
'lowdown',
|
||||
version : '>= 0.9.0',
|
||||
required : get_option('markdown'),
|
||||
)
|
||||
deps_private += lowdown
|
||||
configdata.set('HAVE_LOWDOWN', lowdown.found().to_int())
|
||||
# The API changed slightly around terminal initialization.
|
||||
configdata.set(
|
||||
'HAVE_LOWDOWN_1_4',
|
||||
lowdown.version().version_compare('>= 1.4.0').to_int(),
|
||||
)
|
||||
|
||||
readline_flavor = get_option('readline-flavor')
|
||||
if readline_flavor == 'editline'
|
||||
editline = dependency('libeditline', 'editline', version : '>=1.14')
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
# vim: filetype=meson
|
||||
|
||||
option(
|
||||
'markdown',
|
||||
type : 'feature',
|
||||
description : 'Enable Markdown rendering in the Nix binary (requires lowdown)',
|
||||
)
|
||||
|
||||
option(
|
||||
'readline-flavor',
|
||||
type : 'combo',
|
||||
|
||||
@@ -11,16 +11,12 @@
|
||||
nix-main,
|
||||
editline,
|
||||
readline,
|
||||
lowdown,
|
||||
nlohmann_json,
|
||||
|
||||
# Configuration Options
|
||||
|
||||
version,
|
||||
|
||||
# Whether to enable Markdown rendering in the Nix binary.
|
||||
enableMarkdown ? !stdenv.hostPlatform.isWindows,
|
||||
|
||||
# Which interactive line editor library to use for Nix's repl.
|
||||
#
|
||||
# Currently supported choices are:
|
||||
@@ -53,8 +49,7 @@ mkMesonLibrary (finalAttrs: {
|
||||
|
||||
buildInputs = [
|
||||
({ inherit editline readline; }.${readlineFlavor})
|
||||
]
|
||||
++ lib.optional enableMarkdown lowdown;
|
||||
];
|
||||
|
||||
propagatedBuildInputs = [
|
||||
nix-util
|
||||
@@ -67,7 +62,6 @@ mkMesonLibrary (finalAttrs: {
|
||||
];
|
||||
|
||||
mesonFlags = [
|
||||
(lib.mesonEnable "markdown" enableMarkdown)
|
||||
(lib.mesonOption "readline-flavor" readlineFlavor)
|
||||
];
|
||||
|
||||
|
||||
@@ -739,7 +739,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||
|
||||
auto flakeRef = parseFlakeRef(fetchSettings, flakeRefS, cwd.string(), true);
|
||||
if (evalSettings.pureEval && !flakeRef.input.isLocked(fetchSettings))
|
||||
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
throw Error("cannot use ':load-flake' on unlocked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||
|
||||
Value v;
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ bool Value::isTrivial() const
|
||||
{
|
||||
return !isa<tApp, tPrimOpApp>()
|
||||
&& (!isa<tThunk>()
|
||||
|| (dynamic_cast<ExprAttrs *>(thunk().expr) && ((ExprAttrs *) thunk().expr)->dynamicAttrs.empty())
|
||||
|| (dynamic_cast<ExprAttrs *>(thunk().expr) && ((ExprAttrs *) thunk().expr)->dynamicAttrs->empty())
|
||||
|| dynamic_cast<ExprLambda *>(thunk().expr) || dynamic_cast<ExprList *>(thunk().expr));
|
||||
}
|
||||
|
||||
@@ -517,15 +517,16 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
||||
if (primOp.arity == 0) {
|
||||
primOp.arity = 1;
|
||||
auto vPrimOp = allocValue();
|
||||
vPrimOp->mkPrimOp(new PrimOp(primOp));
|
||||
vPrimOp->mkPrimOp(new PrimOp(std::move(primOp)));
|
||||
Value v;
|
||||
v.mkApp(vPrimOp, vPrimOp);
|
||||
auto & primOp1 = *vPrimOp->primOp();
|
||||
return addConstant(
|
||||
primOp.name,
|
||||
primOp1.name,
|
||||
v,
|
||||
{
|
||||
.type = nThunk, // FIXME
|
||||
.doc = primOp.doc,
|
||||
.doc = primOp1.doc ? primOp1.doc->c_str() : nullptr,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -565,13 +566,14 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||
{
|
||||
if (v.isPrimOp()) {
|
||||
auto v2 = &v;
|
||||
if (auto * doc = v2->primOp()->doc)
|
||||
auto & primOp = *v2->primOp();
|
||||
if (primOp.doc)
|
||||
return Doc{
|
||||
.pos = {},
|
||||
.name = v2->primOp()->name,
|
||||
.arity = v2->primOp()->arity,
|
||||
.args = v2->primOp()->args,
|
||||
.doc = doc,
|
||||
.name = primOp.name,
|
||||
.arity = primOp.arity,
|
||||
.args = primOp.args,
|
||||
.doc = primOp.doc->c_str(),
|
||||
};
|
||||
}
|
||||
if (v.isLambda()) {
|
||||
@@ -1224,26 +1226,26 @@ Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
||||
|
||||
void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
auto bindings = state.buildBindings(attrs.size() + dynamicAttrs.size());
|
||||
auto bindings = state.buildBindings(attrs->size() + dynamicAttrs->size());
|
||||
auto dynamicEnv = &env;
|
||||
bool sort = false;
|
||||
|
||||
if (recursive) {
|
||||
/* Create a new environment that contains the attributes in
|
||||
this `rec'. */
|
||||
Env & env2(state.mem.allocEnv(attrs.size()));
|
||||
Env & env2(state.mem.allocEnv(attrs->size()));
|
||||
env2.up = &env;
|
||||
dynamicEnv = &env2;
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
||||
|
||||
AttrDefs::iterator overrides = attrs.find(state.s.overrides);
|
||||
bool hasOverrides = overrides != attrs.end();
|
||||
AttrDefs::iterator overrides = attrs->find(state.s.overrides);
|
||||
bool hasOverrides = overrides != attrs->end();
|
||||
|
||||
/* The recursive attributes are evaluated in the new
|
||||
environment, while the inherited attributes are evaluated
|
||||
in the original environment. */
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs) {
|
||||
for (auto & i : *attrs) {
|
||||
Value * vAttr;
|
||||
if (hasOverrides && i.second.kind != AttrDef::Kind::Inherited) {
|
||||
vAttr = state.allocValue();
|
||||
@@ -1270,8 +1272,8 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
"while evaluating the `__overrides` attribute");
|
||||
bindings.grow(state.buildBindings(bindings.capacity() + vOverrides->attrs()->size()));
|
||||
for (auto & i : *vOverrides->attrs()) {
|
||||
AttrDefs::iterator j = attrs.find(i.name);
|
||||
if (j != attrs.end()) {
|
||||
AttrDefs::iterator j = attrs->find(i.name);
|
||||
if (j != attrs->end()) {
|
||||
(*bindings.bindings)[j->second.displ] = i;
|
||||
env2.values[j->second.displ] = i.value;
|
||||
} else
|
||||
@@ -1283,13 +1285,13 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||
|
||||
else {
|
||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env) : nullptr;
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
bindings.insert(
|
||||
i.first, i.second.e->maybeThunk(state, *i.second.chooseByKind(&env, &env, inheritEnv)), i.second.pos);
|
||||
}
|
||||
|
||||
/* Dynamic attrs apply *after* rec and __overrides. */
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
Value nameVal;
|
||||
i.nameExpr->eval(state, *dynamicEnv, nameVal);
|
||||
state.forceValue(nameVal, i.pos);
|
||||
@@ -1323,7 +1325,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
|
||||
{
|
||||
/* Create a new environment that contains the attributes in this
|
||||
`let'. */
|
||||
Env & env2(state.mem.allocEnv(attrs->attrs.size()));
|
||||
Env & env2(state.mem.allocEnv(attrs->attrs->size()));
|
||||
env2.up = &env;
|
||||
|
||||
Env * inheritEnv = attrs->inheritFromExprs ? attrs->buildInheritFromEnv(state, env2) : nullptr;
|
||||
@@ -1332,7 +1334,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
|
||||
while the inherited attributes are evaluated in the original
|
||||
environment. */
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs->attrs) {
|
||||
for (auto & i : *attrs->attrs) {
|
||||
env2.values[displ++] = i.second.e->maybeThunk(state, *i.second.chooseByKind(&env2, &env, inheritEnv));
|
||||
}
|
||||
|
||||
@@ -3188,7 +3190,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||
|
||||
if (EvalSettings::isPseudoUrl(value)) {
|
||||
try {
|
||||
auto accessor = fetchers::downloadTarball(store, fetchSettings, EvalSettings::resolvePseudoUrl(value));
|
||||
auto accessor = fetchers::downloadTarball(*store, fetchSettings, EvalSettings::resolvePseudoUrl(value));
|
||||
auto storePath = fetchToStore(fetchSettings, *store, SourcePath(accessor), FetchMode::Copy);
|
||||
return finish(this->storePath(storePath));
|
||||
} catch (Error & e) {
|
||||
|
||||
@@ -108,7 +108,7 @@ struct PrimOp
|
||||
/**
|
||||
* Optional free-form documentation about the primop.
|
||||
*/
|
||||
const char * doc = nullptr;
|
||||
std::optional<std::string> doc;
|
||||
|
||||
/**
|
||||
* Add a trace item, while calling the `<name>` builtin.
|
||||
|
||||
@@ -395,9 +395,13 @@ struct ExprAttrs : Expr
|
||||
}
|
||||
};
|
||||
|
||||
typedef std::map<Symbol, AttrDef> AttrDefs;
|
||||
AttrDefs attrs;
|
||||
std::unique_ptr<std::vector<Expr *>> inheritFromExprs;
|
||||
typedef std::pmr::map<Symbol, AttrDef> AttrDefs;
|
||||
/**
|
||||
* attrs will never be null. we use std::optional so that we can call emplace() to re-initialize the value with a
|
||||
* new pmr::map using a different allocator (move assignment will copy into the old allocator)
|
||||
*/
|
||||
std::optional<AttrDefs> attrs;
|
||||
std::unique_ptr<std::pmr::vector<Expr *>> inheritFromExprs;
|
||||
|
||||
struct DynamicAttrDef
|
||||
{
|
||||
@@ -409,13 +413,20 @@ struct ExprAttrs : Expr
|
||||
, pos(pos) {};
|
||||
};
|
||||
|
||||
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
|
||||
DynamicAttrDefs dynamicAttrs;
|
||||
typedef std::pmr::vector<DynamicAttrDef> DynamicAttrDefs;
|
||||
/**
|
||||
* dynamicAttrs will never be null. See comment on AttrDefs above.
|
||||
*/
|
||||
std::optional<DynamicAttrDefs> dynamicAttrs;
|
||||
ExprAttrs(const PosIdx & pos)
|
||||
: recursive(false)
|
||||
, pos(pos) {};
|
||||
, pos(pos)
|
||||
, attrs(AttrDefs{})
|
||||
, dynamicAttrs(DynamicAttrDefs{}) {};
|
||||
ExprAttrs()
|
||||
: recursive(false) {};
|
||||
: recursive(false)
|
||||
, attrs(AttrDefs{})
|
||||
, dynamicAttrs(DynamicAttrDefs{}) {};
|
||||
|
||||
PosIdx getPos() const override
|
||||
{
|
||||
|
||||
@@ -126,8 +126,8 @@ inline void ParserState::addAttr(
|
||||
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
|
||||
ExprAttrs * nested;
|
||||
if (i->symbol) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
|
||||
if (j != attrs->attrs.end()) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs->find(i->symbol);
|
||||
if (j != attrs->attrs->end()) {
|
||||
nested = dynamic_cast<ExprAttrs *>(j->second.e);
|
||||
if (!nested) {
|
||||
attrPath.erase(i + 1, attrPath.end());
|
||||
@@ -135,11 +135,11 @@ inline void ParserState::addAttr(
|
||||
}
|
||||
} else {
|
||||
nested = exprs.add<ExprAttrs>();
|
||||
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
|
||||
(*attrs->attrs)[i->symbol] = ExprAttrs::AttrDef(nested, pos);
|
||||
}
|
||||
} else {
|
||||
nested = exprs.add<ExprAttrs>();
|
||||
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
|
||||
attrs->dynamicAttrs->push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
|
||||
}
|
||||
attrs = nested;
|
||||
}
|
||||
@@ -148,7 +148,7 @@ inline void ParserState::addAttr(
|
||||
if (i->symbol) {
|
||||
addAttr(attrs, attrPath, i->symbol, ExprAttrs::AttrDef(e, pos));
|
||||
} else {
|
||||
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
|
||||
attrs->dynamicAttrs->push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
|
||||
}
|
||||
|
||||
auto it = lexerState.positionToDocComment.find(pos);
|
||||
@@ -165,8 +165,8 @@ inline void ParserState::addAttr(
|
||||
inline void
|
||||
ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def)
|
||||
{
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(symbol);
|
||||
if (j != attrs->attrs.end()) {
|
||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs->find(symbol);
|
||||
if (j != attrs->attrs->end()) {
|
||||
// This attr path is already defined. However, if both
|
||||
// e and the expr pointed by the attr path are two attribute sets,
|
||||
// we want to merge them.
|
||||
@@ -181,8 +181,8 @@ ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symb
|
||||
// See https://github.com/NixOS/nix/issues/9020.
|
||||
if (jAttrs && ae) {
|
||||
if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
|
||||
jAttrs->inheritFromExprs = std::make_unique<std::vector<Expr *>>();
|
||||
for (auto & ad : ae->attrs) {
|
||||
jAttrs->inheritFromExprs = std::make_unique<std::pmr::vector<Expr *>>();
|
||||
for (auto & ad : *ae->attrs) {
|
||||
if (ad.second.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
|
||||
auto & sel = dynamic_cast<ExprSelect &>(*ad.second.e);
|
||||
auto & from = dynamic_cast<ExprInheritFrom &>(*sel.e);
|
||||
@@ -192,12 +192,12 @@ ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symb
|
||||
addAttr(jAttrs, attrPath, ad.first, std::move(ad.second));
|
||||
attrPath.pop_back();
|
||||
}
|
||||
ae->attrs.clear();
|
||||
jAttrs->dynamicAttrs.insert(
|
||||
jAttrs->dynamicAttrs.end(),
|
||||
std::make_move_iterator(ae->dynamicAttrs.begin()),
|
||||
std::make_move_iterator(ae->dynamicAttrs.end()));
|
||||
ae->dynamicAttrs.clear();
|
||||
ae->attrs->clear();
|
||||
jAttrs->dynamicAttrs->insert(
|
||||
jAttrs->dynamicAttrs->end(),
|
||||
std::make_move_iterator(ae->dynamicAttrs->begin()),
|
||||
std::make_move_iterator(ae->dynamicAttrs->end()));
|
||||
ae->dynamicAttrs->clear();
|
||||
if (ae->inheritFromExprs) {
|
||||
jAttrs->inheritFromExprs->insert(
|
||||
jAttrs->inheritFromExprs->end(),
|
||||
@@ -210,7 +210,7 @@ ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symb
|
||||
}
|
||||
} else {
|
||||
// This attr path is not defined. Let's create it.
|
||||
attrs->attrs.emplace(symbol, def);
|
||||
attrs->attrs->emplace(symbol, def);
|
||||
def.e->setName(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ deps_public_maybe_subproject = [
|
||||
dependency('nix-util'),
|
||||
dependency('nix-store'),
|
||||
dependency('nix-fetchers'),
|
||||
dependency('cmark-cpp'),
|
||||
]
|
||||
subdir('nix-meson-build-support/subprojects')
|
||||
subdir('nix-meson-build-support/big-objs')
|
||||
|
||||
@@ -74,9 +74,9 @@ void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const
|
||||
|
||||
void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) const
|
||||
{
|
||||
typedef const decltype(attrs)::value_type * Attr;
|
||||
typedef const AttrDefs::value_type * Attr;
|
||||
std::vector<Attr> sorted;
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
sorted.push_back(&i);
|
||||
std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
|
||||
std::string_view sa = symbols[a->first], sb = symbols[b->first];
|
||||
@@ -122,7 +122,7 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
|
||||
str << "; ";
|
||||
}
|
||||
}
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
str << "\"${";
|
||||
i.nameExpr->show(symbols, str);
|
||||
str << "}\" = ";
|
||||
@@ -401,15 +401,26 @@ ExprAttrs::bindInheritSources(EvalState & es, const std::shared_ptr<const Static
|
||||
|
||||
void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
// Move storage into the Exprs arena
|
||||
{
|
||||
auto arena = es.mem.exprs.alloc;
|
||||
AttrDefs newAttrs{std::move(*attrs), arena};
|
||||
attrs.emplace(std::move(newAttrs), arena);
|
||||
DynamicAttrDefs newDynamicAttrs{std::move(*dynamicAttrs), arena};
|
||||
dynamicAttrs.emplace(std::move(newDynamicAttrs), arena);
|
||||
if (inheritFromExprs)
|
||||
inheritFromExprs = std::make_unique<std::pmr::vector<Expr *>>(std::move(*inheritFromExprs), arena);
|
||||
}
|
||||
|
||||
if (es.debugRepl)
|
||||
es.exprEnvs.insert(std::make_pair(this, env));
|
||||
|
||||
if (recursive) {
|
||||
auto newEnv = [&]() -> std::shared_ptr<const StaticEnv> {
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs.size());
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->size());
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
return newEnv;
|
||||
}();
|
||||
@@ -417,20 +428,20 @@ void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
|
||||
// No need to sort newEnv since attrs is in sorted order.
|
||||
|
||||
auto inheritFromEnv = bindInheritSources(es, newEnv);
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
i.second.e->bindVars(es, i.second.chooseByKind(newEnv, env, inheritFromEnv));
|
||||
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, newEnv);
|
||||
i.valueExpr->bindVars(es, newEnv);
|
||||
}
|
||||
} else {
|
||||
auto inheritFromEnv = bindInheritSources(es, env);
|
||||
|
||||
for (auto & i : attrs)
|
||||
for (auto & i : *attrs)
|
||||
i.second.e->bindVars(es, i.second.chooseByKind(env, env, inheritFromEnv));
|
||||
|
||||
for (auto & i : dynamicAttrs) {
|
||||
for (auto & i : *dynamicAttrs) {
|
||||
i.nameExpr->bindVars(es, env);
|
||||
i.valueExpr->bindVars(es, env);
|
||||
}
|
||||
@@ -486,10 +497,10 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
|
||||
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
|
||||
{
|
||||
auto newEnv = [&]() -> std::shared_ptr<const StaticEnv> {
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs.size());
|
||||
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs->size());
|
||||
|
||||
Displacement displ = 0;
|
||||
for (auto & i : attrs->attrs)
|
||||
for (auto & i : *attrs->attrs)
|
||||
newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
|
||||
return newEnv;
|
||||
}();
|
||||
@@ -497,7 +508,7 @@ void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
|
||||
// No need to sort newEnv since attrs->attrs is in sorted order.
|
||||
|
||||
auto inheritFromEnv = attrs->bindInheritSources(es, newEnv);
|
||||
for (auto & i : attrs->attrs)
|
||||
for (auto & i : *attrs->attrs)
|
||||
i.second.e->bindVars(es, i.second.chooseByKind(newEnv, env, inheritFromEnv));
|
||||
|
||||
if (es.debugRepl)
|
||||
|
||||
@@ -211,7 +211,7 @@ expr_function
|
||||
| WITH expr ';' expr_function
|
||||
{ $$ = state->exprs.add<ExprWith>(CUR_POS, $2, $4); }
|
||||
| LET binds IN_KW expr_function
|
||||
{ if (!$2->dynamicAttrs.empty())
|
||||
{ if (!$2->dynamicAttrs->empty())
|
||||
throw ParseError({
|
||||
.msg = HintFmt("dynamic attributes not allowed in let"),
|
||||
.pos = state->positions[CUR_POS]
|
||||
@@ -413,9 +413,9 @@ binds1
|
||||
| binds[accum] INHERIT attrs ';'
|
||||
{ $$ = $accum;
|
||||
for (auto & [i, iPos] : $attrs) {
|
||||
if ($accum->attrs.find(i.symbol) != $accum->attrs.end())
|
||||
state->dupAttr(i.symbol, iPos, $accum->attrs[i.symbol].pos);
|
||||
$accum->attrs.emplace(
|
||||
if ($accum->attrs->find(i.symbol) != $accum->attrs->end())
|
||||
state->dupAttr(i.symbol, iPos, (*$accum->attrs)[i.symbol].pos);
|
||||
$accum->attrs->emplace(
|
||||
i.symbol,
|
||||
ExprAttrs::AttrDef(state->exprs.add<ExprVar>(iPos, i.symbol), iPos, ExprAttrs::AttrDef::Kind::Inherited));
|
||||
}
|
||||
@@ -423,13 +423,13 @@ binds1
|
||||
| binds[accum] INHERIT '(' expr ')' attrs ';'
|
||||
{ $$ = $accum;
|
||||
if (!$accum->inheritFromExprs)
|
||||
$accum->inheritFromExprs = std::make_unique<std::vector<Expr *>>();
|
||||
$accum->inheritFromExprs = std::make_unique<std::pmr::vector<Expr *>>();
|
||||
$accum->inheritFromExprs->push_back($expr);
|
||||
auto from = state->exprs.add<ExprInheritFrom>(state->at(@expr), $accum->inheritFromExprs->size() - 1);
|
||||
for (auto & [i, iPos] : $attrs) {
|
||||
if ($accum->attrs.find(i.symbol) != $accum->attrs.end())
|
||||
state->dupAttr(i.symbol, iPos, $accum->attrs[i.symbol].pos);
|
||||
$accum->attrs.emplace(
|
||||
if ($accum->attrs->find(i.symbol) != $accum->attrs->end())
|
||||
state->dupAttr(i.symbol, iPos, (*$accum->attrs)[i.symbol].pos);
|
||||
$accum->attrs->emplace(
|
||||
i.symbol,
|
||||
ExprAttrs::AttrDef(
|
||||
state->exprs.add<ExprSelect>(state->exprs.alloc, iPos, from, i.symbol),
|
||||
|
||||
@@ -81,7 +81,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value ** ar
|
||||
attrs.insert_or_assign("rev", rev->gitRev());
|
||||
auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
|
||||
|
||||
auto [storePath, input2] = input.fetchToStore(state.fetchSettings, state.store);
|
||||
auto [storePath, input2] = input.fetchToStore(state.fetchSettings, *state.store);
|
||||
|
||||
auto attrs2 = state.buildBindings(8);
|
||||
state.mkStorePathString(storePath, attrs2.alloc(state.s.outPath));
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "nix/fetchers/registry.hh"
|
||||
#include "nix/fetchers/tarball.hh"
|
||||
#include "nix/util/url.hh"
|
||||
#include "nix/util/cmark-cpp.hh"
|
||||
#include "nix/expr/value-to-json.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
#include "nix/fetchers/input-cache.hh"
|
||||
@@ -194,7 +195,7 @@ static void fetchTree(
|
||||
}
|
||||
|
||||
if (!state.settings.pureEval && !input.isDirect() && experimentalFeatureSettings.isEnabled(Xp::Flakes))
|
||||
input = lookupInRegistries(state.fetchSettings, state.store, input, fetchers::UseRegistries::Limited).first;
|
||||
input = lookupInRegistries(state.fetchSettings, *state.store, input, fetchers::UseRegistries::Limited).first;
|
||||
|
||||
if (state.settings.pureEval && !input.isLocked(state.fetchSettings)) {
|
||||
if (input.getNarHash())
|
||||
@@ -220,7 +221,7 @@ static void fetchTree(
|
||||
}
|
||||
|
||||
auto cachedInput =
|
||||
state.inputCache->getAccessor(state.fetchSettings, state.store, input, fetchers::UseRegistries::No);
|
||||
state.inputCache->getAccessor(state.fetchSettings, *state.store, input, fetchers::UseRegistries::No);
|
||||
|
||||
auto storePath = state.mountInput(cachedInput.lockedInput, input, cachedInput.accessor);
|
||||
|
||||
@@ -235,229 +236,144 @@ static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, V
|
||||
static RegisterPrimOp primop_fetchTree({
|
||||
.name = "fetchTree",
|
||||
.args = {"input"},
|
||||
.doc = R"(
|
||||
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
|
||||
.doc = []() -> std::string {
|
||||
using namespace cmark;
|
||||
|
||||
- the resulting fixed-output [store path](@docroot@/store/store-path.md)
|
||||
- the corresponding [NAR](@docroot@/store/file-system-object/content-address.md#serial-nix-archive) hash
|
||||
- backend-specific metadata (currently not documented). <!-- TODO: document output attributes -->
|
||||
// Stores strings referenced by AST. Deallocate after rendering.
|
||||
std::vector<std::string> textArena;
|
||||
|
||||
*input* must be an attribute set with the following attributes:
|
||||
auto root = node_new(CMARK_NODE_DOCUMENT);
|
||||
|
||||
- `type` (String, required)
|
||||
auto & before = textArena.emplace_back(stripIndentation(R"(
|
||||
Fetch a file system tree or a plain file using one of the supported backends and return an attribute set with:
|
||||
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
- the resulting fixed-output [store path](@docroot@/store/store-path.md)
|
||||
- the corresponding [NAR](@docroot@/store/file-system-object/content-address.md#serial-nix-archive) hash
|
||||
- backend-specific metadata (currently not documented). <!-- TODO: document output attributes -->
|
||||
|
||||
- `narHash` (String, optional)
|
||||
*input* must be an attribute set with the following attributes:
|
||||
|
||||
The `narHash` parameter can be used to substitute the source of the tree.
|
||||
It also allows for verification of tree contents that may not be provided by the underlying transfer mechanism.
|
||||
If `narHash` is set, the source is first looked up is the Nix store and [substituters](@docroot@/command-ref/conf-file.md#conf-substituters), and only fetched if not available.
|
||||
- `type` (String, required)
|
||||
|
||||
A subset of the output attributes of `fetchTree` can be re-used for subsequent calls to `fetchTree` to produce the same result again.
|
||||
That is, `fetchTree` is idempotent.
|
||||
One of the [supported source types](#source-types).
|
||||
This determines other required and allowed input attributes.
|
||||
|
||||
Downloads are cached in `$XDG_CACHE_HOME/nix`.
|
||||
The remote source is fetched from the network if both are true:
|
||||
- A NAR hash is supplied and the corresponding store path is not [valid](@docroot@/glossary.md#gloss-validity), that is, not available in the store
|
||||
- `narHash` (String, optional)
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> [Substituters](@docroot@/command-ref/conf-file.md#conf-substituters) are not used in fetching.
|
||||
The `narHash` parameter can be used to substitute the source of the tree.
|
||||
It also allows for verification of tree contents that may not be provided by the underlying transfer mechanism.
|
||||
If `narHash` is set, the source is first looked up is the Nix store and [substituters](@docroot@/command-ref/conf-file.md#conf-substituters), and only fetched if not available.
|
||||
|
||||
- There is no cache entry or the cache entry is older than [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl)
|
||||
A subset of the output attributes of `fetchTree` can be re-used for subsequent calls to `fetchTree` to produce the same result again.
|
||||
That is, `fetchTree` is idempotent.
|
||||
|
||||
## Source types
|
||||
Downloads are cached in `$XDG_CACHE_HOME/nix`.
|
||||
The remote source is fetched from the network if both are true:
|
||||
- A NAR hash is supplied and the corresponding store path is not [valid](@docroot@/glossary.md#gloss-validity), that is, not available in the store
|
||||
|
||||
The following source types and associated input attributes are supported.
|
||||
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
|
||||
- `"file"`
|
||||
|
||||
Place a plain file into the Nix store.
|
||||
This is similar to [`builtins.fetchurl`](@docroot@/language/builtins.md#builtins-fetchurl)
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
Supported protocols:
|
||||
|
||||
- `https`
|
||||
|
||||
> **Example**
|
||||
> **Note**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "https://example.com/index.html";
|
||||
> }
|
||||
> ```
|
||||
> [Substituters](@docroot@/command-ref/conf-file.md#conf-substituters) are not used in fetching.
|
||||
|
||||
- `http`
|
||||
- There is no cache entry or the cache entry is older than [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl)
|
||||
|
||||
Insecure HTTP transfer for legacy sources.
|
||||
## Source types
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> HTTP performs no encryption or authentication.
|
||||
> Use a `narHash` known in advance to ensure the output has expected contents.
|
||||
The following source types and associated input attributes are supported.
|
||||
|
||||
- `file`
|
||||
<!-- TODO: It would be soooo much more predictable to work with (and
|
||||
document) if `fetchTree` was a curried call with the first parameter for
|
||||
`type` or an attribute like `builtins.fetchTree.git`! -->
|
||||
)"));
|
||||
parse_document(*root, before, CMARK_OPT_DEFAULT);
|
||||
|
||||
A file on the local file system.
|
||||
auto & schemes = node_append_child(*root, node_new(CMARK_NODE_LIST));
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "file:///home/eelco/nix/README.md";
|
||||
> }
|
||||
> ```
|
||||
for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) {
|
||||
auto & s = node_append_child(schemes, node_new(CMARK_NODE_ITEM));
|
||||
{
|
||||
auto & name_p = node_append_child(s, node_new(CMARK_NODE_PARAGRAPH));
|
||||
auto & name = node_append_child(name_p, node_new(CMARK_NODE_CODE));
|
||||
auto & name_t = textArena.emplace_back(quoteString(schemeName, '"'));
|
||||
node_set_literal(name, name_t.c_str());
|
||||
}
|
||||
parse_document(s, scheme->schemeDescription(), CMARK_OPT_DEFAULT);
|
||||
|
||||
- `"git"`
|
||||
auto & attrs = node_append_child(s, node_new(CMARK_NODE_LIST));
|
||||
for (const auto & [attrName, attribute] : scheme->allowedAttrs()) {
|
||||
auto & a = node_append_child(attrs, node_new(CMARK_NODE_ITEM));
|
||||
{
|
||||
auto & name_info = node_append_child(a, node_new(CMARK_NODE_PARAGRAPH));
|
||||
{
|
||||
auto & name = node_append_child(name_info, node_new(CMARK_NODE_CODE));
|
||||
auto & name_t = textArena.emplace_back(attrName);
|
||||
node_set_literal(name, name_t.c_str());
|
||||
}
|
||||
auto & info = node_append_child(name_info, node_new(CMARK_NODE_TEXT));
|
||||
auto & header = textArena.emplace_back(
|
||||
std::string{} + " (" + attribute.type + ", " + (attribute.required ? "required" : "optional")
|
||||
+ ")");
|
||||
node_set_literal(info, header.c_str());
|
||||
}
|
||||
{
|
||||
auto & doc = textArena.emplace_back(stripIndentation(attribute.doc));
|
||||
parse_document(a, doc, CMARK_OPT_DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
auto & after = textArena.emplace_back(stripIndentation(R"(
|
||||
The following input types are still subject to change:
|
||||
|
||||
- `allRefs` (Bool, optional)
|
||||
- `"path"`
|
||||
- `"github"`
|
||||
- `"gitlab"`
|
||||
- `"sourcehut"`
|
||||
- `"mercurial"`
|
||||
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
With this argument being true, it's possible to load a `rev` from *any* `ref`.
|
||||
(Without setting this option, only `rev`s from the specified `ref` are supported).
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `lastModified` (Integer, optional)
|
||||
|
||||
Unix timestamp of the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
|
||||
- `lfs` (Bool, optional)
|
||||
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `ref` (String, optional)
|
||||
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
A [Git reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References), such as a branch or tag name.
|
||||
|
||||
Default: `"HEAD"`
|
||||
|
||||
- `rev` (String, optional)
|
||||
|
||||
A Git revision; a commit hash.
|
||||
|
||||
Default: the tip of `ref`
|
||||
|
||||
- `revCount` (Integer, optional)
|
||||
|
||||
Number of revisions in the history of the Git repository before the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
|
||||
- `shallow` (Bool, optional)
|
||||
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
|
||||
Default: `true`
|
||||
|
||||
- `submodules` (Bool, optional)
|
||||
|
||||
Also fetch submodules if available.
|
||||
|
||||
Default: `false`
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
The URL formats supported are the same as for Git itself.
|
||||
*input* can also be a [URL-like reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references).
|
||||
The additional input types and the URL-like syntax requires the [`flakes` experimental feature](@docroot@/development/experimental-features.md#xp-feature-flakes) to be enabled.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch a GitHub repository using the attribute set representation:
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "git";
|
||||
> url = "git@github.com:NixOS/nixpkgs.git";
|
||||
> builtins.fetchTree {
|
||||
> type = "github";
|
||||
> owner = "NixOS";
|
||||
> repo = "nixpkgs";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> This evaluates to the following attribute set:
|
||||
>
|
||||
> ```nix
|
||||
> {
|
||||
> lastModified = 1686503798;
|
||||
> lastModifiedDate = "20230611171638";
|
||||
> narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=";
|
||||
> outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> shortRev = "ae2e6b3";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Note**
|
||||
> **Example**
|
||||
>
|
||||
> If the URL points to a local directory, and no `ref` or `rev` is given, Nix only considers files added to the Git index, as listed by `git ls-files` but use the *current file contents* of the Git working directory.
|
||||
> Fetch the same GitHub repository using the URL-like syntax:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||
> ```
|
||||
)"));
|
||||
parse_document(*root, after, CMARK_OPT_DEFAULT);
|
||||
|
||||
- `"tarball"`
|
||||
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underlying implementation as [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball)
|
||||
|
||||
- `url` (String, required)
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "tarball";
|
||||
> url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11";
|
||||
> }
|
||||
> ```
|
||||
|
||||
The following input types are still subject to change:
|
||||
|
||||
- `"path"`
|
||||
- `"github"`
|
||||
- `"gitlab"`
|
||||
- `"sourcehut"`
|
||||
- `"mercurial"`
|
||||
|
||||
*input* can also be a [URL-like reference](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references).
|
||||
The additional input types and the URL-like syntax requires the [`flakes` experimental feature](@docroot@/development/experimental-features.md#xp-feature-flakes) to be enabled.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch a GitHub repository using the attribute set representation:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree {
|
||||
> type = "github";
|
||||
> owner = "NixOS";
|
||||
> repo = "nixpkgs";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> }
|
||||
> ```
|
||||
>
|
||||
> This evaluates to the following attribute set:
|
||||
>
|
||||
> ```nix
|
||||
> {
|
||||
> lastModified = 1686503798;
|
||||
> lastModifiedDate = "20230611171638";
|
||||
> narHash = "sha256-rA9RqKP9OlBrgGCPvfd5HVAXDOy8k2SmPtB/ijShNXc=";
|
||||
> outPath = "/nix/store/l5m6qlvfs9sdw14ja3qbzpglcjlb6j1x-source";
|
||||
> rev = "ae2e6b3958682513d28f7d633734571fb18285dd";
|
||||
> shortRev = "ae2e6b3";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> Fetch the same GitHub repository using the URL-like syntax:
|
||||
>
|
||||
> ```nix
|
||||
> builtins.fetchTree "github:NixOS/nixpkgs/ae2e6b3958682513d28f7d633734571fb18285dd"
|
||||
> ```
|
||||
)",
|
||||
auto p = render_commonmark(*root, CMARK_OPT_DEFAULT, 0);
|
||||
assert(p);
|
||||
return {&*p};
|
||||
}(),
|
||||
.fun = prim_fetchTree,
|
||||
.experimentalFeature = Xp::FetchTree,
|
||||
});
|
||||
@@ -582,10 +498,10 @@ static void fetch(
|
||||
auto storePath = unpack ? fetchToStore(
|
||||
state.fetchSettings,
|
||||
*state.store,
|
||||
fetchers::downloadTarball(state.store, state.fetchSettings, *url),
|
||||
fetchers::downloadTarball(*state.store, state.fetchSettings, *url),
|
||||
FetchMode::Copy,
|
||||
name)
|
||||
: fetchers::downloadFile(state.store, state.fetchSettings, *url, name).storePath;
|
||||
: fetchers::downloadFile(*state.store, state.fetchSettings, *url, name).storePath;
|
||||
|
||||
if (expectedHash) {
|
||||
auto hash = unpack ? state.store->queryPathInfo(storePath)->narHash
|
||||
|
||||
@@ -196,7 +196,7 @@ TEST_F(GitTest, submodulePeriodSupport)
|
||||
{"ref", "main"},
|
||||
});
|
||||
|
||||
auto [accessor, i] = input.getAccessor(settings, store);
|
||||
auto [accessor, i] = input.getAccessor(settings, *store);
|
||||
|
||||
ASSERT_EQ(accessor->readFile(CanonPath("deps/sub/lib.txt")), "hello from submodule\n");
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "nix/fetchers/fetch-settings.hh"
|
||||
#include "nix/fetchers/fetch-to-store.hh"
|
||||
#include "nix/util/url.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
@@ -26,18 +27,9 @@ void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
|
||||
throw Error("Input scheme with name %s already registered", schemeName);
|
||||
}
|
||||
|
||||
nlohmann::json dumpRegisterInputSchemeInfo()
|
||||
const InputSchemeMap & getAllInputSchemes()
|
||||
{
|
||||
using nlohmann::json;
|
||||
|
||||
auto res = json::object();
|
||||
|
||||
for (auto & [name, scheme] : inputSchemes()) {
|
||||
auto & r = res[name] = json::object();
|
||||
r["allowedAttrs"] = scheme->allowedAttrs();
|
||||
}
|
||||
|
||||
return res;
|
||||
return inputSchemes();
|
||||
}
|
||||
|
||||
Input Input::fromURL(const Settings & settings, const std::string & url, bool requireTree)
|
||||
@@ -119,7 +111,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
|
||||
return std::move(*res);
|
||||
}
|
||||
|
||||
std::optional<std::string> Input::getFingerprint(ref<Store> store) const
|
||||
std::optional<std::string> Input::getFingerprint(Store & store) const
|
||||
{
|
||||
if (!scheme)
|
||||
return std::nullopt;
|
||||
@@ -198,7 +190,7 @@ bool Input::contains(const Input & other) const
|
||||
}
|
||||
|
||||
// FIXME: remove
|
||||
std::pair<StorePath, Input> Input::fetchToStore(const Settings & settings, ref<Store> store) const
|
||||
std::pair<StorePath, Input> Input::fetchToStore(const Settings & settings, Store & store) const
|
||||
{
|
||||
if (!scheme)
|
||||
throw Error("cannot fetch unsupported input '%s'", attrsToJSON(toAttrs()));
|
||||
@@ -208,9 +200,9 @@ std::pair<StorePath, Input> Input::fetchToStore(const Settings & settings, ref<S
|
||||
auto [accessor, result] = getAccessorUnchecked(settings, store);
|
||||
|
||||
auto storePath =
|
||||
nix::fetchToStore(settings, *store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
nix::fetchToStore(settings, store, SourcePath(accessor), FetchMode::Copy, result.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(storePath)->narHash;
|
||||
auto narHash = store.queryPathInfo(storePath)->narHash;
|
||||
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
result.attrs.insert_or_assign("__final", Explicit<bool>(true));
|
||||
@@ -297,7 +289,7 @@ void Input::checkLocks(Input specified, Input & result)
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settings, ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settings, Store & store) const
|
||||
{
|
||||
try {
|
||||
auto [accessor, result] = getAccessorUnchecked(settings, store);
|
||||
@@ -313,7 +305,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settin
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings & settings, ref<Store> store) const
|
||||
std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings & settings, Store & store) const
|
||||
{
|
||||
// FIXME: cache the accessor
|
||||
|
||||
@@ -333,13 +325,13 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings
|
||||
*/
|
||||
if (isFinal() && getNarHash()) {
|
||||
try {
|
||||
auto storePath = computeStorePath(*store);
|
||||
auto storePath = computeStorePath(store);
|
||||
|
||||
store->ensurePath(storePath);
|
||||
store.ensurePath(storePath);
|
||||
|
||||
debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
|
||||
debug("using substituted/cached input '%s' in '%s'", to_string(), store.printStorePath(storePath));
|
||||
|
||||
auto accessor = store->requireStoreObjectAccessor(storePath);
|
||||
auto accessor = store.requireStoreObjectAccessor(storePath);
|
||||
|
||||
accessor->fingerprint = getFingerprint(store);
|
||||
|
||||
@@ -349,7 +341,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(const Settings
|
||||
if (accessor->fingerprint) {
|
||||
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
|
||||
auto cacheKey = makeFetchToStoreCacheKey(getName(), *accessor->fingerprint, method, "/");
|
||||
settings.getCache()->upsert(cacheKey, *store, {}, storePath);
|
||||
settings.getCache()->upsert(cacheKey, store, {}, storePath);
|
||||
}
|
||||
|
||||
accessor->setPathDisplay("«" + to_string() + "»");
|
||||
@@ -377,10 +369,10 @@ Input Input::applyOverrides(std::optional<std::string> ref, std::optional<Hash>
|
||||
return scheme->applyOverrides(*this, ref, rev);
|
||||
}
|
||||
|
||||
void Input::clone(const Settings & settings, const Path & destDir) const
|
||||
void Input::clone(const Settings & settings, Store & store, const std::filesystem::path & destDir) const
|
||||
{
|
||||
assert(scheme);
|
||||
scheme->clone(settings, *this, destDir);
|
||||
scheme->clone(settings, store, *this, destDir);
|
||||
}
|
||||
|
||||
std::optional<std::filesystem::path> Input::getSourcePath() const
|
||||
@@ -493,9 +485,19 @@ void InputScheme::putFile(
|
||||
throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path);
|
||||
}
|
||||
|
||||
void InputScheme::clone(const Settings & settings, const Input & input, const Path & destDir) const
|
||||
void InputScheme::clone(
|
||||
const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir) const
|
||||
{
|
||||
throw Error("do not know how to clone input '%s'", input.to_string());
|
||||
if (std::filesystem::exists(destDir))
|
||||
throw Error("cannot clone into existing path %s", destDir);
|
||||
|
||||
auto [accessor, input2] = getAccessor(settings, store, input);
|
||||
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to %s...", input2.to_string(), destDir));
|
||||
|
||||
auto source = sinkToSource([&](Sink & sink) { accessor->dumpPath(CanonPath::root, sink); });
|
||||
|
||||
restorePath(destDir, *source);
|
||||
}
|
||||
|
||||
std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const
|
||||
|
||||
@@ -209,7 +209,7 @@ std::vector<nlohmann::json> Fetch::fetchUrls(const std::vector<Pointer> & pointe
|
||||
auto url = api.endpoint + "/objects/batch";
|
||||
const auto & authHeader = api.authHeader;
|
||||
FileTransferRequest request(parseURL(url));
|
||||
request.method = HttpMethod::POST;
|
||||
request.method = HttpMethod::Post;
|
||||
Headers headers;
|
||||
if (authHeader.has_value())
|
||||
headers.push_back({"Authorization", *authHeader});
|
||||
|
||||
@@ -194,28 +194,183 @@ struct GitInputScheme : InputScheme
|
||||
return "git";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"url",
|
||||
"ref",
|
||||
"rev",
|
||||
"shallow",
|
||||
"submodules",
|
||||
"lfs",
|
||||
"exportIgnore",
|
||||
"lastModified",
|
||||
"revCount",
|
||||
"narHash",
|
||||
"allRefs",
|
||||
"name",
|
||||
"dirtyRev",
|
||||
"dirtyShortRev",
|
||||
"verifyCommit",
|
||||
"keytype",
|
||||
"publicKey",
|
||||
"publicKeys",
|
||||
return stripIndentation(R"(
|
||||
Fetch a Git tree and copy it to the Nix store.
|
||||
This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit).
|
||||
)");
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{
|
||||
.type = "String",
|
||||
.required = true,
|
||||
.doc = R"(
|
||||
The URL formats supported are the same as for Git itself.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "git";
|
||||
> url = "git@github.com:NixOS/nixpkgs.git";
|
||||
> }
|
||||
> ```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If the URL points to a local directory, and no `ref` or `rev` is given, Nix only considers files added to the Git index, as listed by `git ls-files` but uses the *current file contents* of the Git working directory.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{
|
||||
.type = "String",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
A [Git reference](https://git-scm.com/book/en/v2/Git-Internals-Git-References), such as a branch or tag name.
|
||||
|
||||
Default: `"HEAD"`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{
|
||||
.type = "String",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
A Git revision; a commit hash.
|
||||
|
||||
Default: the tip of `ref`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"shallow",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Make a shallow clone when fetching the Git tree.
|
||||
When this is enabled, the options `ref` and `allRefs` have no effect anymore.
|
||||
|
||||
Default: `true`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"submodules",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Also fetch submodules if available.
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"lfs",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Fetch any [Git LFS](https://git-lfs.com/) files.
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"exportIgnore",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{
|
||||
.type = "Integer",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Unix timestamp of the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{
|
||||
.type = "Integer",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
Number of revisions in the history of the Git repository before the fetched commit.
|
||||
|
||||
If set, pass through the value to the output attribute set.
|
||||
Otherwise, generated from the fetched Git tree.
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"allRefs",
|
||||
{
|
||||
.type = "Bool",
|
||||
.required = false,
|
||||
.doc = R"(
|
||||
By default, this has no effect. This becomes relevant only once `shallow` cloning is disabled.
|
||||
|
||||
Whether to fetch all references (eg. branches and tags) of the repository.
|
||||
With this argument being true, it's possible to load a `rev` from *any* `ref`.
|
||||
(Without setting this option, only `rev`s from the specified `ref` are supported).
|
||||
|
||||
Default: `false`
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"dirtyRev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"dirtyShortRev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"verifyCommit",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"keytype",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"publicKey",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"publicKeys",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -278,7 +433,8 @@ struct GitInputScheme : InputScheme
|
||||
return res;
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto repoInfo = getRepoInfo(input);
|
||||
|
||||
@@ -623,7 +779,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessorFromCommit(const Settings & settings, ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
getAccessorFromCommit(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const
|
||||
{
|
||||
assert(!repoInfo.workdirInfo.isDirty);
|
||||
|
||||
@@ -797,7 +953,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessorFromWorkdir(const Settings & settings, ref<Store> store, RepoInfo & repoInfo, Input && input) const
|
||||
getAccessorFromWorkdir(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const
|
||||
{
|
||||
auto repoPath = repoInfo.getPath().value();
|
||||
|
||||
@@ -881,7 +1037,7 @@ struct GitInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
|
||||
@@ -903,7 +1059,7 @@ struct GitInputScheme : InputScheme
|
||||
return {accessor, std::move(final)};
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
auto makeFingerprint = [&](const Hash & rev) {
|
||||
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "")
|
||||
|
||||
@@ -110,18 +110,43 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
return {
|
||||
"owner",
|
||||
"repo",
|
||||
"ref",
|
||||
"rev",
|
||||
"narHash",
|
||||
"lastModified",
|
||||
"host",
|
||||
"treeHash",
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"owner",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"repo",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"host",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"treeHash",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const fetchers::Settings & settings, const Attrs & attrs) const override
|
||||
@@ -233,7 +258,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
std::optional<Hash> treeHash;
|
||||
};
|
||||
|
||||
virtual RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const = 0;
|
||||
virtual RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const = 0;
|
||||
|
||||
virtual DownloadUrl getDownloadUrl(const Settings & settings, const Input & input) const = 0;
|
||||
|
||||
@@ -243,7 +268,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
time_t lastModified;
|
||||
};
|
||||
|
||||
std::pair<Input, TarballInfo> downloadArchive(const Settings & settings, ref<Store> store, Input input) const
|
||||
std::pair<Input, TarballInfo> downloadArchive(const Settings & settings, Store & store, Input input) const
|
||||
{
|
||||
if (!maybeGetStrAttr(input.attrs, "ref"))
|
||||
input.attrs.insert_or_assign("ref", "HEAD");
|
||||
@@ -316,7 +341,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
auto [input, tarballInfo] = downloadArchive(settings, store, _input);
|
||||
|
||||
@@ -345,7 +370,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||
return Xp::Flakes;
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
if (auto rev = input.getRev())
|
||||
return rev->gitRev();
|
||||
@@ -361,6 +386,12 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return "github";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Github supports PAT/OAuth2 tokens and HTTP Basic
|
||||
@@ -387,7 +418,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return getStrAttr(input.attrs, "repo");
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
auto url = fmt(
|
||||
@@ -401,7 +432,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
|
||||
auto downloadResult = downloadFile(store, settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
store.requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
return RefInfo{
|
||||
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
|
||||
@@ -426,12 +457,13 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto host = getHost(input);
|
||||
Input::fromURL(settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(settings, destDir);
|
||||
.clone(settings, store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -442,6 +474,12 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return "gitlab";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// Gitlab supports 4 kinds of authorization, two of which are
|
||||
@@ -461,7 +499,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return std::make_pair(token.substr(0, fldsplit), token.substr(fldsplit + 1));
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// See rate limiting note below
|
||||
@@ -476,7 +514,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
|
||||
auto downloadResult = downloadFile(store, settings, url, "source", headers);
|
||||
auto json = nlohmann::json::parse(
|
||||
store->requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
store.requireStoreObjectAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||
|
||||
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
|
||||
return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
|
||||
@@ -507,7 +545,8 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
|
||||
// FIXME: get username somewhere
|
||||
@@ -515,7 +554,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||
settings,
|
||||
fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(settings, destDir);
|
||||
.clone(settings, store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -526,6 +565,12 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
return "sourcehut";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||
{
|
||||
// SourceHut supports both PAT and OAuth2. See
|
||||
@@ -536,7 +581,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
// Once it is implemented, however, should work as expected.
|
||||
}
|
||||
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::ref<Store> store, const Input & input) const override
|
||||
RefInfo getRevFromRef(const Settings & settings, nix::Store & store, const Input & input) const override
|
||||
{
|
||||
// TODO: In the future, when the sourcehut graphql API is implemented for mercurial
|
||||
// and with anonymous access, this method should use it instead.
|
||||
@@ -552,7 +597,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
std::string refUri;
|
||||
if (ref == "HEAD") {
|
||||
auto downloadFileResult = downloadFile(store, settings, fmt("%s/HEAD", base_url), "source", headers);
|
||||
auto contents = store->requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
auto contents = store.requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
|
||||
auto remoteLine = git::parseLsRemoteLine(getLine(contents).first);
|
||||
if (!remoteLine) {
|
||||
@@ -565,7 +610,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
std::regex refRegex(refUri);
|
||||
|
||||
auto downloadFileResult = downloadFile(store, settings, fmt("%s/info/refs", base_url), "source", headers);
|
||||
auto contents = store->requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
auto contents = store.requireStoreObjectAccessor(downloadFileResult.storePath)->readFile(CanonPath::root);
|
||||
std::istringstream is(contents);
|
||||
|
||||
std::string line;
|
||||
@@ -596,14 +641,15 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||
return DownloadUrl{parseURL(url), headers};
|
||||
}
|
||||
|
||||
void clone(const Settings & settings, const Input & input, const Path & destDir) const override
|
||||
void clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir)
|
||||
const override
|
||||
{
|
||||
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||
Input::fromURL(
|
||||
settings,
|
||||
fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||
.applyOverrides(input.getRef(), input.getRev())
|
||||
.clone(settings, destDir);
|
||||
.clone(settings, store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
* Fetch the entire input into the Nix store, returning the
|
||||
* location in the Nix store and the locked input.
|
||||
*/
|
||||
std::pair<StorePath, Input> fetchToStore(const Settings & settings, ref<Store> store) const;
|
||||
std::pair<StorePath, Input> fetchToStore(const Settings & settings, Store & store) const;
|
||||
|
||||
/**
|
||||
* Check the locking attributes in `result` against
|
||||
@@ -133,17 +133,17 @@ public:
|
||||
* input without copying it to the store. Also return a possibly
|
||||
* unlocked input.
|
||||
*/
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(const Settings & settings, ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessor(const Settings & settings, Store & store) const;
|
||||
|
||||
private:
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessorUnchecked(const Settings & settings, ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, Input> getAccessorUnchecked(const Settings & settings, Store & store) const;
|
||||
|
||||
public:
|
||||
|
||||
Input applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const;
|
||||
|
||||
void clone(const Settings & settings, const Path & destDir) const;
|
||||
void clone(const Settings & settings, Store & store, const std::filesystem::path & destDir) const;
|
||||
|
||||
std::optional<std::filesystem::path> getSourcePath() const;
|
||||
|
||||
@@ -173,7 +173,7 @@ public:
|
||||
*
|
||||
* This is not a stable identifier between Nix versions, but not guaranteed to change either.
|
||||
*/
|
||||
std::optional<std::string> getFingerprint(ref<Store> store) const;
|
||||
std::optional<std::string> getFingerprint(Store & store) const;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -203,20 +203,34 @@ struct InputScheme
|
||||
*/
|
||||
virtual std::string_view schemeName() const = 0;
|
||||
|
||||
/**
|
||||
* Longform description of this scheme, for documentation purposes.
|
||||
*/
|
||||
virtual std::string schemeDescription() const = 0;
|
||||
|
||||
// TODO remove these defaults
|
||||
struct AttributeInfo
|
||||
{
|
||||
const char * type = "String";
|
||||
bool required = true;
|
||||
const char * doc = "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Allowed attributes in an attribute set that is converted to an
|
||||
* input.
|
||||
* input, and documentation for each attribute.
|
||||
*
|
||||
* `type` is not included from this set, because the `type` field is
|
||||
* `type` is not included from this map, because the `type` field is
|
||||
parsed first to choose which scheme; `type` is always required.
|
||||
*/
|
||||
virtual StringSet allowedAttrs() const = 0;
|
||||
virtual const std::map<std::string, AttributeInfo> & allowedAttrs() const = 0;
|
||||
|
||||
virtual ParsedURL toURL(const Input & input) const;
|
||||
|
||||
virtual Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const;
|
||||
|
||||
virtual void clone(const Settings & settings, const Input & input, const Path & destDir) const;
|
||||
virtual void
|
||||
clone(const Settings & settings, Store & store, const Input & input, const std::filesystem::path & destDir) const;
|
||||
|
||||
virtual std::optional<std::filesystem::path> getSourcePath(const Input & input) const;
|
||||
|
||||
@@ -227,7 +241,7 @@ struct InputScheme
|
||||
std::optional<std::string> commitMsg) const;
|
||||
|
||||
virtual std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & input) const = 0;
|
||||
getAccessor(const Settings & settings, Store & store, const Input & input) const = 0;
|
||||
|
||||
/**
|
||||
* Is this `InputScheme` part of an experimental feature?
|
||||
@@ -239,7 +253,7 @@ struct InputScheme
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const
|
||||
virtual std::optional<std::string> getFingerprint(Store & store, const Input & input) const
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -263,7 +277,12 @@ struct InputScheme
|
||||
|
||||
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
|
||||
|
||||
nlohmann::json dumpRegisterInputSchemeInfo();
|
||||
using InputSchemeMap = std::map<std::string_view, std::shared_ptr<InputScheme>>;
|
||||
|
||||
/**
|
||||
* Use this for docs, not for finding a specific scheme
|
||||
*/
|
||||
const InputSchemeMap & getAllInputSchemes();
|
||||
|
||||
struct PublicKey
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@ struct InputCache
|
||||
};
|
||||
|
||||
CachedResult
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & originalInput, UseRegistries useRegistries);
|
||||
getAccessor(const Settings & settings, Store & store, const Input & originalInput, UseRegistries useRegistries);
|
||||
|
||||
struct CachedInput
|
||||
{
|
||||
|
||||
@@ -57,7 +57,7 @@ std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Pat
|
||||
|
||||
Path getUserRegistryPath();
|
||||
|
||||
Registries getRegistries(const Settings & settings, ref<Store> store);
|
||||
Registries getRegistries(const Settings & settings, Store & store);
|
||||
|
||||
void overrideRegistry(const Settings & settings, const Input & from, const Input & to, const Attrs & extraAttrs);
|
||||
|
||||
@@ -72,6 +72,6 @@ enum class UseRegistries : int {
|
||||
* use the registries for which the filter function returns true.
|
||||
*/
|
||||
std::pair<Input, Attrs>
|
||||
lookupInRegistries(const Settings & settings, ref<Store> store, const Input & input, UseRegistries useRegistries);
|
||||
lookupInRegistries(const Settings & settings, Store & store, const Input & input, UseRegistries useRegistries);
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -25,7 +25,7 @@ struct DownloadFileResult
|
||||
};
|
||||
|
||||
DownloadFileResult downloadFile(
|
||||
ref<Store> store,
|
||||
Store & store,
|
||||
const Settings & settings,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
@@ -43,6 +43,6 @@ struct DownloadTarballResult
|
||||
* Download and import a tarball into the Git cache. The result is the
|
||||
* Git tree hash of the root directory.
|
||||
*/
|
||||
ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings, const std::string & url);
|
||||
ref<SourceAccessor> downloadTarball(Store & store, const Settings & settings, const std::string & url);
|
||||
|
||||
} // namespace nix::fetchers
|
||||
|
||||
@@ -60,14 +60,33 @@ struct IndirectInputScheme : InputScheme
|
||||
return "indirect";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"id",
|
||||
"ref",
|
||||
"rev",
|
||||
"narHash",
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"id",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -107,7 +126,7 @@ struct IndirectInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & input) const override
|
||||
{
|
||||
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
namespace nix::fetchers {
|
||||
|
||||
InputCache::CachedResult InputCache::getAccessor(
|
||||
const Settings & settings, ref<Store> store, const Input & originalInput, UseRegistries useRegistries)
|
||||
const Settings & settings, Store & store, const Input & originalInput, UseRegistries useRegistries)
|
||||
{
|
||||
auto fetched = lookup(originalInput);
|
||||
Input resolvedInput = originalInput;
|
||||
|
||||
@@ -68,16 +68,41 @@ struct MercurialInputScheme : InputScheme
|
||||
return "hg";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"url",
|
||||
"ref",
|
||||
"rev",
|
||||
"revCount",
|
||||
"narHash",
|
||||
"name",
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"ref",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -154,7 +179,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return {isLocal, isLocal ? renderUrlPathEnsureLegal(url.path) : url.to_string()};
|
||||
}
|
||||
|
||||
StorePath fetchToStore(const Settings & settings, ref<Store> store, Input & input) const
|
||||
StorePath fetchToStore(const Settings & settings, Store & store, Input & input) const
|
||||
{
|
||||
auto origRev = input.getRev();
|
||||
|
||||
@@ -205,7 +230,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return files.count(file);
|
||||
};
|
||||
|
||||
auto storePath = store->addToStore(
|
||||
auto storePath = store.addToStore(
|
||||
input.getName(),
|
||||
{getFSSourceAccessor(), CanonPath(actualPath)},
|
||||
ContentAddressMethod::Raw::NixArchive,
|
||||
@@ -226,7 +251,7 @@ struct MercurialInputScheme : InputScheme
|
||||
"Hash '%s' is not supported by Mercurial. Only sha1 is supported.",
|
||||
rev.to_string(HashFormat::Base16, true));
|
||||
|
||||
return Cache::Key{"hgRev", {{"store", store->storeDir}, {"name", name}, {"rev", input.getRev()->gitRev()}}};
|
||||
return Cache::Key{"hgRev", {{"store", store.storeDir}, {"name", name}, {"rev", input.getRev()->gitRev()}}};
|
||||
};
|
||||
|
||||
auto makeResult = [&](const Attrs & infoAttrs, const StorePath & storePath) -> StorePath {
|
||||
@@ -246,7 +271,7 @@ struct MercurialInputScheme : InputScheme
|
||||
|
||||
/* If we have a rev, check if we have a cached store path. */
|
||||
if (auto rev = input.getRev()) {
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(*rev), *store))
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(*rev), store))
|
||||
return makeResult(res->value, res->storePath);
|
||||
}
|
||||
|
||||
@@ -300,7 +325,7 @@ struct MercurialInputScheme : InputScheme
|
||||
|
||||
/* Now that we have the rev, check the cache again for a
|
||||
cached store path. */
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(rev), *store))
|
||||
if (auto res = settings.getCache()->lookupStorePath(revInfoKey(rev), store))
|
||||
return makeResult(res->value, res->storePath);
|
||||
|
||||
Path tmpDir = createTempDir();
|
||||
@@ -310,7 +335,7 @@ struct MercurialInputScheme : InputScheme
|
||||
|
||||
deletePath(tmpDir + "/.hg_archival.txt");
|
||||
|
||||
auto storePath = store->addToStore(name, {getFSSourceAccessor(), CanonPath(tmpDir)});
|
||||
auto storePath = store.addToStore(name, {getFSSourceAccessor(), CanonPath(tmpDir)});
|
||||
|
||||
Attrs infoAttrs({
|
||||
{"revCount", (uint64_t) revCount},
|
||||
@@ -319,18 +344,18 @@ struct MercurialInputScheme : InputScheme
|
||||
if (!origRev)
|
||||
settings.getCache()->upsert(refToRevKey, {{"rev", rev.gitRev()}});
|
||||
|
||||
settings.getCache()->upsert(revInfoKey(rev), *store, infoAttrs, storePath);
|
||||
settings.getCache()->upsert(revInfoKey(rev), store, infoAttrs, storePath);
|
||||
|
||||
return makeResult(infoAttrs, std::move(storePath));
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
|
||||
auto storePath = fetchToStore(settings, store, input);
|
||||
auto accessor = store->requireStoreObjectAccessor(storePath);
|
||||
auto accessor = store.requireStoreObjectAccessor(storePath);
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
@@ -342,7 +367,7 @@ struct MercurialInputScheme : InputScheme
|
||||
return (bool) input.getRev();
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
if (auto rev = input.getRev())
|
||||
return rev->gitRev();
|
||||
|
||||
@@ -40,20 +40,42 @@ struct PathInputScheme : InputScheme
|
||||
return "path";
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return {
|
||||
"path",
|
||||
// TODO
|
||||
return "";
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"path",
|
||||
{},
|
||||
},
|
||||
/* Allow the user to pass in "fake" tree info
|
||||
attributes. This is useful for making a pinned tree work
|
||||
the same as the repository from which is exported (e.g.
|
||||
path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...).
|
||||
*/
|
||||
"rev",
|
||||
"revCount",
|
||||
"lastModified",
|
||||
"narHash",
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -117,7 +139,7 @@ struct PathInputScheme : InputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
Input input(_input);
|
||||
auto path = getStrAttr(input.attrs, "path");
|
||||
@@ -125,31 +147,31 @@ struct PathInputScheme : InputScheme
|
||||
auto absPath = getAbsPath(input);
|
||||
|
||||
// FIXME: check whether access to 'path' is allowed.
|
||||
auto storePath = store->maybeParseStorePath(absPath.string());
|
||||
auto storePath = store.maybeParseStorePath(absPath.string());
|
||||
|
||||
if (storePath)
|
||||
store->addTempRoot(*storePath);
|
||||
store.addTempRoot(*storePath);
|
||||
|
||||
time_t mtime = 0;
|
||||
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
|
||||
if (!storePath || storePath->name() != "source" || !store.isValidPath(*storePath)) {
|
||||
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying %s to the store", absPath));
|
||||
// FIXME: try to substitute storePath.
|
||||
auto src = sinkToSource(
|
||||
[&](Sink & sink) { mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter); });
|
||||
storePath = store->addToStoreFromDump(*src, "source");
|
||||
storePath = store.addToStoreFromDump(*src, "source");
|
||||
}
|
||||
|
||||
auto accessor = store->requireStoreObjectAccessor(*storePath);
|
||||
auto accessor = store.requireStoreObjectAccessor(*storePath);
|
||||
|
||||
// To prevent `fetchToStore()` copying the path again to Nix
|
||||
// store, pre-create an entry in the fetcher cache.
|
||||
auto info = store->queryPathInfo(*storePath);
|
||||
auto info = store.queryPathInfo(*storePath);
|
||||
accessor->fingerprint =
|
||||
fmt("path:%s", store->queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
|
||||
fmt("path:%s", store.queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
|
||||
settings.getCache()->upsert(
|
||||
makeFetchToStoreCacheKey(
|
||||
input.getName(), *accessor->fingerprint, ContentAddressMethod::Raw::NixArchive, "/"),
|
||||
*store,
|
||||
store,
|
||||
{},
|
||||
*storePath);
|
||||
|
||||
|
||||
@@ -136,7 +136,7 @@ void overrideRegistry(const Settings & settings, const Input & from, const Input
|
||||
getFlagRegistry(settings)->add(from, to, extraAttrs);
|
||||
}
|
||||
|
||||
static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, ref<Store> store)
|
||||
static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, Store & store)
|
||||
{
|
||||
static auto reg = [&]() {
|
||||
auto path = settings.flakeRegistry.get();
|
||||
@@ -149,9 +149,9 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, re
|
||||
[&] -> SourcePath {
|
||||
if (!isAbsolute(path)) {
|
||||
auto storePath = downloadFile(store, settings, path, "flake-registry.json").storePath;
|
||||
if (auto store2 = store.dynamic_pointer_cast<LocalFSStore>())
|
||||
if (auto store2 = dynamic_cast<LocalFSStore *>(&store))
|
||||
store2->addPermRoot(storePath, getCacheDir() + "/flake-registry.json");
|
||||
return {store->requireStoreObjectAccessor(storePath)};
|
||||
return {store.requireStoreObjectAccessor(storePath)};
|
||||
} else {
|
||||
return SourcePath{getFSSourceAccessor(), CanonPath{path}}.resolveSymlinks();
|
||||
}
|
||||
@@ -162,7 +162,7 @@ static std::shared_ptr<Registry> getGlobalRegistry(const Settings & settings, re
|
||||
return reg;
|
||||
}
|
||||
|
||||
Registries getRegistries(const Settings & settings, ref<Store> store)
|
||||
Registries getRegistries(const Settings & settings, Store & store)
|
||||
{
|
||||
Registries registries;
|
||||
registries.push_back(getFlagRegistry(settings));
|
||||
@@ -173,7 +173,7 @@ Registries getRegistries(const Settings & settings, ref<Store> store)
|
||||
}
|
||||
|
||||
std::pair<Input, Attrs>
|
||||
lookupInRegistries(const Settings & settings, ref<Store> store, const Input & _input, UseRegistries useRegistries)
|
||||
lookupInRegistries(const Settings & settings, Store & store, const Input & _input, UseRegistries useRegistries)
|
||||
{
|
||||
Attrs extraAttrs;
|
||||
int n = 0;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
namespace nix::fetchers {
|
||||
|
||||
DownloadFileResult downloadFile(
|
||||
ref<Store> store,
|
||||
Store & store,
|
||||
const Settings & settings,
|
||||
const std::string & url,
|
||||
const std::string & name,
|
||||
@@ -28,7 +28,7 @@ DownloadFileResult downloadFile(
|
||||
{"name", name},
|
||||
}}};
|
||||
|
||||
auto cached = settings.getCache()->lookupStorePath(key, *store);
|
||||
auto cached = settings.getCache()->lookupStorePath(key, store);
|
||||
|
||||
auto useCached = [&]() -> DownloadFileResult {
|
||||
return {
|
||||
@@ -74,7 +74,7 @@ DownloadFileResult downloadFile(
|
||||
dumpString(res.data, sink);
|
||||
auto hash = hashString(HashAlgorithm::SHA256, res.data);
|
||||
auto info = ValidPathInfo::makeFromCA(
|
||||
*store,
|
||||
store,
|
||||
name,
|
||||
FixedOutputInfo{
|
||||
.method = FileIngestionMethod::Flat,
|
||||
@@ -84,7 +84,7 @@ DownloadFileResult downloadFile(
|
||||
hashString(HashAlgorithm::SHA256, sink.s));
|
||||
info.narSize = sink.s.size();
|
||||
auto source = StringSource{sink.s};
|
||||
store->addToStore(info, source, NoRepair, NoCheckSigs);
|
||||
store.addToStore(info, source, NoRepair, NoCheckSigs);
|
||||
storePath = std::move(info.path);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ DownloadFileResult downloadFile(
|
||||
key.second.insert_or_assign("url", url);
|
||||
assert(!res.urls.empty());
|
||||
infoAttrs.insert_or_assign("url", *res.urls.rbegin());
|
||||
settings.getCache()->upsert(key, *store, infoAttrs, *storePath);
|
||||
settings.getCache()->upsert(key, store, infoAttrs, *storePath);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -214,7 +214,7 @@ static DownloadTarballResult downloadTarball_(
|
||||
return attrsToResult(infoAttrs);
|
||||
}
|
||||
|
||||
ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings, const std::string & url)
|
||||
ref<SourceAccessor> downloadTarball(Store & store, const Settings & settings, const std::string & url)
|
||||
{
|
||||
/* Go through Input::getAccessor() to ensure that the resulting
|
||||
accessor has a fingerprint. */
|
||||
@@ -278,7 +278,7 @@ struct CurlInputScheme : InputScheme
|
||||
HTTP request. Now that we've processed the Nix-specific
|
||||
attributes above, remove them so we don't also send them as
|
||||
part of the HTTP request. */
|
||||
for (auto & param : allowedAttrs())
|
||||
for (auto & [param, _] : allowedAttrs())
|
||||
url.query.erase(param);
|
||||
|
||||
input.attrs.insert_or_assign("type", std::string{schemeName()});
|
||||
@@ -286,18 +286,83 @@ struct CurlInputScheme : InputScheme
|
||||
return input;
|
||||
}
|
||||
|
||||
StringSet allowedAttrs() const override
|
||||
static const std::map<std::string, AttributeInfo> & allowedAttrsImpl()
|
||||
{
|
||||
return {
|
||||
"type",
|
||||
"url",
|
||||
"narHash",
|
||||
"name",
|
||||
"unpack",
|
||||
"rev",
|
||||
"revCount",
|
||||
"lastModified",
|
||||
static const std::map<std::string, AttributeInfo> attrs = {
|
||||
{
|
||||
"url",
|
||||
{
|
||||
.type = "String",
|
||||
.required = true,
|
||||
.doc = R"(
|
||||
Supported protocols:
|
||||
|
||||
- `https`
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "https://example.com/index.html";
|
||||
> }
|
||||
> ```
|
||||
|
||||
- `http`
|
||||
|
||||
Insecure HTTP transfer for legacy sources.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> HTTP performs no encryption or authentication.
|
||||
> Use a `narHash` known in advance to ensure the output has expected contents.
|
||||
|
||||
- `file`
|
||||
|
||||
A file on the local file system.
|
||||
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "file";
|
||||
> url = "file:///home/eelco/nix/README.md";
|
||||
> }
|
||||
> ```
|
||||
)",
|
||||
},
|
||||
},
|
||||
{
|
||||
"narHash",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"name",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"unpack",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"rev",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"revCount",
|
||||
{},
|
||||
},
|
||||
{
|
||||
"lastModified",
|
||||
{},
|
||||
},
|
||||
};
|
||||
return attrs;
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
return allowedAttrsImpl();
|
||||
}
|
||||
|
||||
std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
|
||||
@@ -332,6 +397,14 @@ struct FileInputScheme : CurlInputScheme
|
||||
return "file";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Place a plain file into the Nix store.
|
||||
This is similar to [`builtins.fetchurl`](@docroot@/language/builtins.md#builtins-fetchurl)
|
||||
)");
|
||||
}
|
||||
|
||||
bool isValidURL(const ParsedURL & url, bool requireTree) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
@@ -341,7 +414,7 @@ struct FileInputScheme : CurlInputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
auto input(_input);
|
||||
|
||||
@@ -351,10 +424,10 @@ struct FileInputScheme : CurlInputScheme
|
||||
tarballs. */
|
||||
auto file = downloadFile(store, settings, getStrAttr(input.attrs, "url"), input.getName());
|
||||
|
||||
auto narHash = store->queryPathInfo(file.storePath)->narHash;
|
||||
auto narHash = store.queryPathInfo(file.storePath)->narHash;
|
||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||
|
||||
auto accessor = ref{store->getFSAccessor(file.storePath)};
|
||||
auto accessor = ref{store.getFSAccessor(file.storePath)};
|
||||
|
||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||
|
||||
@@ -369,6 +442,34 @@ struct TarballInputScheme : CurlInputScheme
|
||||
return "tarball";
|
||||
}
|
||||
|
||||
std::string schemeDescription() const override
|
||||
{
|
||||
return stripIndentation(R"(
|
||||
Download a tar archive and extract it into the Nix store.
|
||||
This has the same underlying implementation as [`builtins.fetchTarball`](@docroot@/language/builtins.md#builtins-fetchTarball)
|
||||
)");
|
||||
}
|
||||
|
||||
const std::map<std::string, AttributeInfo> & allowedAttrs() const override
|
||||
{
|
||||
static const std::map<std::string, AttributeInfo> attrs = [] {
|
||||
auto attrs = CurlInputScheme::allowedAttrsImpl();
|
||||
// Override the "url" attribute to add tarball-specific example
|
||||
attrs["url"].doc = R"(
|
||||
> **Example**
|
||||
>
|
||||
> ```nix
|
||||
> fetchTree {
|
||||
> type = "tarball";
|
||||
> url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11";
|
||||
> }
|
||||
> ```
|
||||
)";
|
||||
return attrs;
|
||||
}();
|
||||
return attrs;
|
||||
}
|
||||
|
||||
bool isValidURL(const ParsedURL & url, bool requireTree) const override
|
||||
{
|
||||
auto parsedUrlScheme = parseUrlScheme(url.scheme);
|
||||
@@ -379,7 +480,7 @@ struct TarballInputScheme : CurlInputScheme
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, Input>
|
||||
getAccessor(const Settings & settings, ref<Store> store, const Input & _input) const override
|
||||
getAccessor(const Settings & settings, Store & store, const Input & _input) const override
|
||||
{
|
||||
auto input(_input);
|
||||
|
||||
@@ -404,7 +505,7 @@ struct TarballInputScheme : CurlInputScheme
|
||||
return {result.accessor, input};
|
||||
}
|
||||
|
||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||
std::optional<std::string> getFingerprint(Store & store, const Input & input) const override
|
||||
{
|
||||
if (auto narHash = input.getNarHash())
|
||||
return narHash->to_string(HashFormat::SRI, true);
|
||||
|
||||
@@ -373,7 +373,7 @@ static Flake getFlake(
|
||||
{
|
||||
// Fetch a lazy tree first.
|
||||
auto cachedInput =
|
||||
state.inputCache->getAccessor(state.fetchSettings, state.store, originalRef.input, useRegistries);
|
||||
state.inputCache->getAccessor(state.fetchSettings, *state.store, originalRef.input, useRegistries);
|
||||
|
||||
auto subdir = fetchers::maybeGetStrAttr(cachedInput.extraAttrs, "dir").value_or(originalRef.subdir);
|
||||
auto resolvedRef = FlakeRef(std::move(cachedInput.resolvedInput), subdir);
|
||||
@@ -390,7 +390,7 @@ static Flake getFlake(
|
||||
// FIXME: need to remove attrs that are invalidated by the changed input attrs, such as 'narHash'.
|
||||
newLockedRef.input.attrs.erase("narHash");
|
||||
auto cachedInput2 = state.inputCache->getAccessor(
|
||||
state.fetchSettings, state.store, newLockedRef.input, fetchers::UseRegistries::No);
|
||||
state.fetchSettings, *state.store, newLockedRef.input, fetchers::UseRegistries::No);
|
||||
cachedInput.accessor = cachedInput2.accessor;
|
||||
lockedRef = FlakeRef(std::move(cachedInput2.lockedInput), newLockedRef.subdir);
|
||||
}
|
||||
@@ -756,7 +756,7 @@ lockFlake(const Settings & settings, EvalState & state, const FlakeRef & topRef,
|
||||
return {*resolvedPath, *input.ref};
|
||||
} else {
|
||||
auto cachedInput = state.inputCache->getAccessor(
|
||||
state.fetchSettings, state.store, input.ref->input, useRegistriesInputs);
|
||||
state.fetchSettings, *state.store, input.ref->input, useRegistriesInputs);
|
||||
|
||||
auto lockedRef = FlakeRef(std::move(cachedInput.lockedInput), input.ref->subdir);
|
||||
|
||||
@@ -975,7 +975,7 @@ void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes)
|
||||
state.callFunction(*vCallFlake, args, vRes, noPos);
|
||||
}
|
||||
|
||||
std::optional<Fingerprint> LockedFlake::getFingerprint(ref<Store> store, const fetchers::Settings & fetchSettings) const
|
||||
std::optional<Fingerprint> LockedFlake::getFingerprint(Store & store, const fetchers::Settings & fetchSettings) const
|
||||
{
|
||||
if (lockFile.isUnlocked(fetchSettings))
|
||||
return std::nullopt;
|
||||
@@ -1005,7 +1005,7 @@ Flake::~Flake() {}
|
||||
ref<eval_cache::EvalCache> openEvalCache(EvalState & state, ref<const LockedFlake> lockedFlake)
|
||||
{
|
||||
auto fingerprint = state.settings.useEvalCache && state.settings.pureEval
|
||||
? lockedFlake->getFingerprint(state.store, state.fetchSettings)
|
||||
? lockedFlake->getFingerprint(*state.store, state.fetchSettings)
|
||||
: std::nullopt;
|
||||
auto rootLoader = [&state, lockedFlake]() {
|
||||
/* For testing whether the evaluation cache is
|
||||
|
||||
@@ -64,8 +64,8 @@ std::ostream & operator<<(std::ostream & str, const FlakeRef & flakeRef)
|
||||
return str;
|
||||
}
|
||||
|
||||
FlakeRef FlakeRef::resolve(
|
||||
const fetchers::Settings & fetchSettings, ref<Store> store, fetchers::UseRegistries useRegistries) const
|
||||
FlakeRef
|
||||
FlakeRef::resolve(const fetchers::Settings & fetchSettings, Store & store, fetchers::UseRegistries useRegistries) const
|
||||
{
|
||||
auto [input2, extraAttrs] = lookupInRegistries(fetchSettings, store, input, useRegistries);
|
||||
return FlakeRef(std::move(input2), fetchers::maybeGetStrAttr(extraAttrs, "dir").value_or(subdir));
|
||||
@@ -289,7 +289,7 @@ FlakeRef FlakeRef::fromAttrs(const fetchers::Settings & fetchSettings, const fet
|
||||
}
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef>
|
||||
FlakeRef::lazyFetch(const fetchers::Settings & fetchSettings, ref<Store> store) const
|
||||
FlakeRef::lazyFetch(const fetchers::Settings & fetchSettings, Store & store) const
|
||||
{
|
||||
auto [accessor, lockedInput] = input.getAccessor(fetchSettings, store);
|
||||
return {accessor, FlakeRef(std::move(lockedInput), subdir)};
|
||||
|
||||
@@ -135,7 +135,7 @@ struct LockedFlake
|
||||
*/
|
||||
std::map<ref<Node>, SourcePath> nodePaths;
|
||||
|
||||
std::optional<Fingerprint> getFingerprint(ref<Store> store, const fetchers::Settings & fetchSettings) const;
|
||||
std::optional<Fingerprint> getFingerprint(Store & store, const fetchers::Settings & fetchSettings) const;
|
||||
};
|
||||
|
||||
struct LockFlags
|
||||
|
||||
@@ -73,13 +73,12 @@ struct FlakeRef
|
||||
|
||||
FlakeRef resolve(
|
||||
const fetchers::Settings & fetchSettings,
|
||||
ref<Store> store,
|
||||
Store & store,
|
||||
fetchers::UseRegistries useRegistries = fetchers::UseRegistries::All) const;
|
||||
|
||||
static FlakeRef fromAttrs(const fetchers::Settings & fetchSettings, const fetchers::Attrs & attrs);
|
||||
|
||||
std::pair<ref<SourceAccessor>, FlakeRef>
|
||||
lazyFetch(const fetchers::Settings & fetchSettings, ref<Store> store) const;
|
||||
std::pair<ref<SourceAccessor>, FlakeRef> lazyFetch(const fetchers::Settings & fetchSettings, Store & store) const;
|
||||
|
||||
/**
|
||||
* Canonicalize a flakeref for the purpose of comparing "old" and
|
||||
|
||||
@@ -35,6 +35,8 @@ include_dirs = [ include_directories('.') ]
|
||||
|
||||
headers = files(
|
||||
'nix_api_store.h',
|
||||
'nix_api_store/derivation.h',
|
||||
'nix_api_store/store_path.h',
|
||||
)
|
||||
|
||||
# TODO don't install this once tests don't use it and/or move the header into `libstore`, non-`c`
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
#include "nix_api_store/store_path.h"
|
||||
#include "nix_api_store/derivation.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -21,10 +23,6 @@ extern "C" {
|
||||
|
||||
/** @brief Reference to a Nix store */
|
||||
typedef struct Store Store;
|
||||
/** @brief Nix store path */
|
||||
typedef struct StorePath StorePath;
|
||||
/** @brief Nix Derivation */
|
||||
typedef struct nix_derivation nix_derivation;
|
||||
|
||||
/**
|
||||
* @brief Initializes the Nix store library
|
||||
@@ -118,30 +116,6 @@ nix_store_get_storedir(nix_c_context * context, Store * store, nix_get_string_ca
|
||||
*/
|
||||
StorePath * nix_store_parse_path(nix_c_context * context, Store * store, const char * path);
|
||||
|
||||
/**
|
||||
* @brief Get the path name (e.g. "name" in /nix/store/...-name)
|
||||
*
|
||||
* @param[in] store_path the path to get the name from
|
||||
* @param[in] callback called with the name
|
||||
* @param[in] user_data arbitrary data, passed to the callback when it's called.
|
||||
*/
|
||||
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data);
|
||||
|
||||
/**
|
||||
* @brief Copy a StorePath
|
||||
*
|
||||
* @param[in] p the path to copy
|
||||
* @return a new StorePath
|
||||
*/
|
||||
StorePath * nix_store_path_clone(const StorePath * p);
|
||||
|
||||
/** @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)
|
||||
@@ -229,14 +203,6 @@ nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store
|
||||
*/
|
||||
StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation);
|
||||
|
||||
/**
|
||||
* @brief Deallocate a `nix_derivation`
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] drv the derivation to free
|
||||
*/
|
||||
void nix_derivation_free(nix_derivation * drv);
|
||||
|
||||
/**
|
||||
* @brief Copy the closure of `path` from `srcStore` to `dstStore`.
|
||||
*
|
||||
|
||||
38
src/libstore-c/nix_api_store/derivation.h
Normal file
38
src/libstore-c/nix_api_store/derivation.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef NIX_API_STORE_DERIVATION_H
|
||||
#define NIX_API_STORE_DERIVATION_H
|
||||
/**
|
||||
* @defgroup libstore_derivation Derivation
|
||||
* @ingroup libstore
|
||||
* @brief Derivation operations that don't require a Store
|
||||
* @{
|
||||
*/
|
||||
/** @file
|
||||
* @brief Derivation operations
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// cffi start
|
||||
|
||||
/** @brief Nix Derivation */
|
||||
typedef struct nix_derivation nix_derivation;
|
||||
|
||||
/**
|
||||
* @brief Deallocate a `nix_derivation`
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] drv the derivation to free
|
||||
*/
|
||||
void nix_derivation_free(nix_derivation * drv);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
#endif // NIX_API_STORE_DERIVATION_H
|
||||
54
src/libstore-c/nix_api_store/store_path.h
Normal file
54
src/libstore-c/nix_api_store/store_path.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef NIX_API_STORE_STORE_PATH_H
|
||||
#define NIX_API_STORE_STORE_PATH_H
|
||||
/**
|
||||
* @defgroup libstore_storepath StorePath
|
||||
* @ingroup libstore
|
||||
* @brief Store path operations that don't require a Store
|
||||
* @{
|
||||
*/
|
||||
/** @file
|
||||
* @brief Store path operations
|
||||
*/
|
||||
|
||||
#include "nix_api_util.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
// cffi start
|
||||
|
||||
/** @brief Nix store path */
|
||||
typedef struct StorePath StorePath;
|
||||
|
||||
/**
|
||||
* @brief Copy a StorePath
|
||||
*
|
||||
* @param[in] p the path to copy
|
||||
* @return a new StorePath
|
||||
*/
|
||||
StorePath * nix_store_path_clone(const StorePath * p);
|
||||
|
||||
/** @brief Deallocate a StorePath
|
||||
*
|
||||
* Does not fail.
|
||||
* @param[in] p the path to free
|
||||
*/
|
||||
void nix_store_path_free(StorePath * p);
|
||||
|
||||
/**
|
||||
* @brief Get the path name (e.g. "<name>" in /nix/store/<hash>-<name>)
|
||||
*
|
||||
* @param[in] store_path the path to get the name from
|
||||
* @param[in] callback called with the name
|
||||
* @param[in] user_data arbitrary data, passed to the callback when it's called.
|
||||
*/
|
||||
void nix_store_path_name(const StorePath * store_path, nix_get_string_callback callback, void * user_data);
|
||||
|
||||
// cffi end
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
#endif // NIX_API_STORE_STORE_PATH_H
|
||||
@@ -386,17 +386,17 @@ struct curlFileTransfer : public FileTransfer
|
||||
if (settings.downloadSpeed.get() > 0)
|
||||
curl_easy_setopt(req, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) (settings.downloadSpeed.get() * 1024));
|
||||
|
||||
if (request.method == HttpMethod::HEAD)
|
||||
if (request.method == HttpMethod::Head)
|
||||
curl_easy_setopt(req, CURLOPT_NOBODY, 1);
|
||||
|
||||
if (request.method == HttpMethod::DELETE)
|
||||
if (request.method == HttpMethod::Delete)
|
||||
curl_easy_setopt(req, CURLOPT_CUSTOMREQUEST, "DELETE");
|
||||
|
||||
if (request.data) {
|
||||
if (request.method == HttpMethod::POST) {
|
||||
if (request.method == HttpMethod::Post) {
|
||||
curl_easy_setopt(req, CURLOPT_POST, 1L);
|
||||
curl_easy_setopt(req, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) request.data->sizeHint);
|
||||
} else if (request.method == HttpMethod::PUT) {
|
||||
} else if (request.method == HttpMethod::Put) {
|
||||
curl_easy_setopt(req, CURLOPT_UPLOAD, 1L);
|
||||
curl_easy_setopt(req, CURLOPT_INFILESIZE_LARGE, (curl_off_t) request.data->sizeHint);
|
||||
} else {
|
||||
|
||||
@@ -120,7 +120,7 @@ bool HttpBinaryCacheStore::fileExists(const std::string & path)
|
||||
|
||||
try {
|
||||
FileTransferRequest request(makeRequest(path));
|
||||
request.method = HttpMethod::HEAD;
|
||||
request.method = HttpMethod::Head;
|
||||
getFileTransfer()->download(request);
|
||||
return true;
|
||||
} catch (FileTransferError & e) {
|
||||
@@ -141,7 +141,7 @@ void HttpBinaryCacheStore::upload(
|
||||
std::optional<Headers> headers)
|
||||
{
|
||||
auto req = makeRequest(path);
|
||||
req.method = HttpMethod::PUT;
|
||||
req.method = HttpMethod::Put;
|
||||
|
||||
if (headers) {
|
||||
req.headers.reserve(req.headers.size() + headers->size());
|
||||
|
||||
@@ -87,11 +87,11 @@ extern const unsigned int RETRY_TIME_MS_DEFAULT;
|
||||
* HTTP methods supported by FileTransfer.
|
||||
*/
|
||||
enum struct HttpMethod {
|
||||
GET,
|
||||
PUT,
|
||||
HEAD,
|
||||
POST,
|
||||
DELETE,
|
||||
Get,
|
||||
Put,
|
||||
Head,
|
||||
Post,
|
||||
Delete,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -110,7 +110,7 @@ struct FileTransferRequest
|
||||
VerbatimURL uri;
|
||||
Headers headers;
|
||||
std::string expectedETag;
|
||||
HttpMethod method = HttpMethod::GET;
|
||||
HttpMethod method = HttpMethod::Get;
|
||||
size_t tries = fileTransferSettings.tries;
|
||||
unsigned int baseRetryTimeMs = RETRY_TIME_MS_DEFAULT;
|
||||
ActivityId parentAct;
|
||||
@@ -164,14 +164,14 @@ struct FileTransferRequest
|
||||
std::string verb() const
|
||||
{
|
||||
switch (method) {
|
||||
case HttpMethod::HEAD:
|
||||
case HttpMethod::GET:
|
||||
case HttpMethod::Head:
|
||||
case HttpMethod::Get:
|
||||
return "download";
|
||||
case HttpMethod::PUT:
|
||||
case HttpMethod::POST:
|
||||
case HttpMethod::Put:
|
||||
case HttpMethod::Post:
|
||||
assert(data);
|
||||
return "upload";
|
||||
case HttpMethod::DELETE:
|
||||
case HttpMethod::Delete:
|
||||
return "delet";
|
||||
}
|
||||
unreachable();
|
||||
|
||||
@@ -1246,10 +1246,8 @@ StorePath LocalStore::addToStoreFromDump(
|
||||
|
||||
auto desc = ContentAddressWithReferences::fromParts(
|
||||
hashMethod,
|
||||
methodsMatch
|
||||
? dumpHash
|
||||
: hashPath(PosixSourceAccessor::createAtRoot(tempPath), hashMethod.getFileIngestionMethod(), hashAlgo)
|
||||
.first,
|
||||
methodsMatch ? dumpHash
|
||||
: hashPath(makeFSSourceAccessor(tempPath), hashMethod.getFileIngestionMethod(), hashAlgo).first,
|
||||
{
|
||||
.others = references,
|
||||
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||
@@ -1385,11 +1383,9 @@ bool LocalStore::verifyStore(bool checkContents, RepairFlag repair)
|
||||
checkInterrupt();
|
||||
auto name = link.path().filename();
|
||||
printMsg(lvlTalkative, "checking contents of %s", name);
|
||||
std::string hash = hashPath(
|
||||
PosixSourceAccessor::createAtRoot(link.path()),
|
||||
FileIngestionMethod::NixArchive,
|
||||
HashAlgorithm::SHA256)
|
||||
.first.to_string(HashFormat::Nix32, false);
|
||||
std::string hash =
|
||||
hashPath(makeFSSourceAccessor(link.path()), FileIngestionMethod::NixArchive, HashAlgorithm::SHA256)
|
||||
.first.to_string(HashFormat::Nix32, false);
|
||||
if (hash != name.string()) {
|
||||
printError("link %s was modified! expected hash %s, got '%s'", link.path(), name, hash);
|
||||
if (repair) {
|
||||
|
||||
@@ -172,7 +172,7 @@ void LocalStore::optimisePath_(
|
||||
auto stLink = lstat(linkPath.string());
|
||||
if (st.st_size != stLink.st_size || (repair && hash != ({
|
||||
hashPath(
|
||||
PosixSourceAccessor::createAtRoot(linkPath),
|
||||
makeFSSourceAccessor(linkPath),
|
||||
FileSerialisationMethod::NixArchive,
|
||||
HashAlgorithm::SHA256)
|
||||
.hash;
|
||||
|
||||
@@ -295,7 +295,7 @@ std::string S3BinaryCacheStore::createMultipartUpload(
|
||||
url.query["uploads"] = "";
|
||||
req.uri = VerbatimURL(url);
|
||||
|
||||
req.method = HttpMethod::POST;
|
||||
req.method = HttpMethod::Post;
|
||||
StringSource payload{std::string_view("")};
|
||||
req.data = {payload};
|
||||
req.mimeType = mimeType;
|
||||
@@ -325,7 +325,7 @@ S3BinaryCacheStore::uploadPart(std::string_view key, std::string_view uploadId,
|
||||
}
|
||||
|
||||
auto req = makeRequest(key);
|
||||
req.method = HttpMethod::PUT;
|
||||
req.method = HttpMethod::Put;
|
||||
req.setupForS3();
|
||||
|
||||
auto url = req.uri.parsed();
|
||||
@@ -355,7 +355,7 @@ void S3BinaryCacheStore::abortMultipartUpload(std::string_view key, std::string_
|
||||
auto url = req.uri.parsed();
|
||||
url.query["uploadId"] = uploadId;
|
||||
req.uri = VerbatimURL(url);
|
||||
req.method = HttpMethod::DELETE;
|
||||
req.method = HttpMethod::Delete;
|
||||
|
||||
getFileTransfer()->enqueueFileTransfer(req).get();
|
||||
} catch (...) {
|
||||
@@ -372,7 +372,7 @@ void S3BinaryCacheStore::completeMultipartUpload(
|
||||
auto url = req.uri.parsed();
|
||||
url.query["uploadId"] = uploadId;
|
||||
req.uri = VerbatimURL(url);
|
||||
req.method = HttpMethod::POST;
|
||||
req.method = HttpMethod::Post;
|
||||
|
||||
std::string xml = "<CompleteMultipartUpload>";
|
||||
for (const auto & [idx, etag] : enumerate(partEtags)) {
|
||||
|
||||
81
src/libutil/include/nix/util/cmark-cpp.hh
Normal file
81
src/libutil/include/nix/util/cmark-cpp.hh
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
///@file
|
||||
|
||||
#include "types.hh"
|
||||
#include "util.hh"
|
||||
|
||||
#include <cmark.h>
|
||||
|
||||
namespace nix::cmark {
|
||||
|
||||
using Node = struct cmark_node;
|
||||
using NodeType = cmark_node_type;
|
||||
using ListType = cmark_list_type;
|
||||
|
||||
using Iter = struct cmark_iter;
|
||||
|
||||
struct Deleter
|
||||
{
|
||||
void operator()(Node * ptr)
|
||||
{
|
||||
cmark_node_free(ptr);
|
||||
}
|
||||
|
||||
void operator()(Iter * ptr)
|
||||
{
|
||||
cmark_iter_free(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using UniquePtr = std::unique_ptr<Node, Deleter>;
|
||||
|
||||
static inline void parse_document(Node & root, std::string_view s, int options)
|
||||
{
|
||||
cmark_parser * parser = cmark_parser_new_with_mem_into_root(options, cmark_get_default_mem_allocator(), &root);
|
||||
cmark_parser_feed(parser, s.data(), s.size());
|
||||
(void) cmark_parser_finish(parser);
|
||||
cmark_parser_free(parser);
|
||||
}
|
||||
|
||||
static inline UniquePtr<Node> parse_document(std::string_view s, int options)
|
||||
{
|
||||
return UniquePtr<Node>{cmark_parse_document(s.data(), s.size(), options)};
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<char, FreeDeleter> render_commonmark(Node & root, int options, int width)
|
||||
{
|
||||
return std::unique_ptr<char, FreeDeleter>{cmark_render_commonmark(&root, options, width)};
|
||||
}
|
||||
|
||||
static inline std::unique_ptr<char, FreeDeleter> render_xml(Node & root, int options)
|
||||
{
|
||||
return std::unique_ptr<char, FreeDeleter>{cmark_render_xml(&root, options)};
|
||||
}
|
||||
|
||||
static inline UniquePtr<Node> node_new(NodeType type)
|
||||
{
|
||||
return UniquePtr<Node>{cmark_node_new(type)};
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent takes ownership
|
||||
*/
|
||||
static inline Node & node_append_child(Node & node, UniquePtr<Node> child)
|
||||
{
|
||||
auto status = (bool) cmark_node_append_child(&node, &*child);
|
||||
assert(status);
|
||||
return *child.release();
|
||||
}
|
||||
|
||||
static inline bool node_set_literal(Node & node, const char * content)
|
||||
{
|
||||
return (bool) cmark_node_set_literal(&node, content);
|
||||
}
|
||||
|
||||
static inline bool node_set_list_type(Node & node, ListType type)
|
||||
{
|
||||
return (bool) cmark_node_set_list_type(&node, type);
|
||||
}
|
||||
|
||||
} // namespace nix::cmark
|
||||
@@ -17,6 +17,7 @@ headers = files(
|
||||
'checked-arithmetic.hh',
|
||||
'chunked-vector.hh',
|
||||
'closure.hh',
|
||||
'cmark-cpp.hh',
|
||||
'comparator.hh',
|
||||
'compression.hh',
|
||||
'compute-levels.hh',
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
#include <limits>
|
||||
#include <string>
|
||||
|
||||
#include "nix/util/file-descriptor.hh"
|
||||
|
||||
namespace nix {
|
||||
|
||||
/**
|
||||
* Determine whether \param fd is a terminal.
|
||||
*/
|
||||
bool isTTY(Descriptor fd);
|
||||
|
||||
/**
|
||||
* Determine whether ANSI escape sequences are appropriate for the
|
||||
* present output.
|
||||
|
||||
@@ -110,6 +110,9 @@ deps_private += cpuid
|
||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||
deps_public += nlohmann_json
|
||||
|
||||
cmark = dependency('libcmark', required : true)
|
||||
deps_public += cmark
|
||||
|
||||
cxx = meson.get_compiler('cpp')
|
||||
|
||||
config_priv_h = configure_file(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
boost,
|
||||
brotli,
|
||||
cmark,
|
||||
libarchive,
|
||||
libblake3,
|
||||
libcpuid,
|
||||
@@ -57,6 +58,7 @@ mkMesonLibrary (finalAttrs: {
|
||||
|
||||
propagatedBuildInputs = [
|
||||
boost
|
||||
cmark
|
||||
libarchive
|
||||
nlohmann_json
|
||||
];
|
||||
|
||||
@@ -64,6 +64,16 @@ inline std::pair<int, size_t> charWidthUTF8Helper(std::string_view s)
|
||||
|
||||
namespace nix {
|
||||
|
||||
bool isTTY(Descriptor fd)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
return isatty(fd);
|
||||
#else
|
||||
DWORD mode;
|
||||
return GetConsoleMode(fd, &mode);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool isTTY()
|
||||
{
|
||||
static const bool tty = isatty(STDERR_FILENO) && getEnv("TERM").value_or("dumb") != "dumb"
|
||||
|
||||
@@ -75,7 +75,7 @@ struct CmdCatNar : StoreCommand, MixCat
|
||||
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
AutoCloseFD fd = open(narPath.c_str(), O_RDONLY);
|
||||
AutoCloseFD fd = toDescriptor(open(narPath.c_str(), O_RDONLY));
|
||||
if (!fd)
|
||||
throw SysError("opening NAR file '%s'", narPath);
|
||||
auto source = FdSource{fd.get()};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
#include "nix/cmd/command.hh"
|
||||
#include "nix/store/store-api.hh"
|
||||
#include "nix/util/archive.hh"
|
||||
#include "nix/util/terminal.hh"
|
||||
|
||||
using namespace nix;
|
||||
|
||||
static FdSink getNarSink()
|
||||
{
|
||||
auto fd = getStandardOutput();
|
||||
if (isatty(fd))
|
||||
if (isTTY(fd))
|
||||
throw UsageError("refusing to write NAR to a terminal");
|
||||
return FdSink(std::move(fd));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ struct CmdFlakePrefetchInputs : FlakeCommand
|
||||
if (auto lockedNode = dynamic_cast<const LockedNode *>(&node)) {
|
||||
try {
|
||||
Activity act(*logger, lvlInfo, actUnknown, fmt("fetching '%s'", lockedNode->lockedRef));
|
||||
auto accessor = lockedNode->lockedRef.input.getAccessor(fetchSettings, store).first;
|
||||
auto accessor = lockedNode->lockedRef.input.getAccessor(fetchSettings, *store).first;
|
||||
fetchToStore(
|
||||
fetchSettings, *store, accessor, FetchMode::Copy, lockedNode->lockedRef.input.getName());
|
||||
} catch (Error & e) {
|
||||
|
||||
@@ -240,7 +240,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
||||
j["lastModified"] = *lastModified;
|
||||
j["path"] = storePath;
|
||||
j["locks"] = lockedFlake.lockFile.toJSON().first;
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings))
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(*store, fetchSettings))
|
||||
j["fingerprint"] = fingerprint->to_string(HashFormat::Base16, false);
|
||||
printJSON(j);
|
||||
} else {
|
||||
@@ -260,7 +260,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
|
||||
logger->cout(
|
||||
ANSI_BOLD "Last modified:" ANSI_NORMAL " %s",
|
||||
std::put_time(std::localtime(&*lastModified), "%F %T"));
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings))
|
||||
if (auto fingerprint = lockedFlake.getFingerprint(*store, fetchSettings))
|
||||
logger->cout(
|
||||
ANSI_BOLD "Fingerprint:" ANSI_NORMAL " %s", fingerprint->to_string(HashFormat::Base16, false));
|
||||
|
||||
@@ -1019,7 +1019,7 @@ struct CmdFlakeNew : CmdFlakeInitCommon
|
||||
|
||||
struct CmdFlakeClone : FlakeCommand
|
||||
{
|
||||
Path destDir;
|
||||
std::filesystem::path destDir;
|
||||
|
||||
std::string description() override
|
||||
{
|
||||
@@ -1049,7 +1049,7 @@ struct CmdFlakeClone : FlakeCommand
|
||||
if (destDir.empty())
|
||||
throw Error("missing flag '--dest'");
|
||||
|
||||
getFlakeRef().resolve(fetchSettings, store).input.clone(fetchSettings, destDir);
|
||||
getFlakeRef().resolve(fetchSettings, *store).input.clone(fetchSettings, *store, destDir);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1100,7 +1100,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun, MixNoCheckSigs
|
||||
std::optional<StorePath> storePath;
|
||||
if (!(*inputNode)->lockedRef.input.isRelative()) {
|
||||
storePath = dryRun ? (*inputNode)->lockedRef.input.computeStorePath(*store)
|
||||
: (*inputNode)->lockedRef.input.fetchToStore(fetchSettings, store).first;
|
||||
: (*inputNode)->lockedRef.input.fetchToStore(fetchSettings, *store).first;
|
||||
sources.insert(*storePath);
|
||||
}
|
||||
if (json) {
|
||||
@@ -1498,8 +1498,8 @@ struct CmdFlakePrefetch : FlakeCommand, MixJSON
|
||||
void run(ref<Store> store) override
|
||||
{
|
||||
auto originalRef = getFlakeRef();
|
||||
auto resolvedRef = originalRef.resolve(fetchSettings, store);
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(getEvalState()->fetchSettings, store);
|
||||
auto resolvedRef = originalRef.resolve(fetchSettings, *store);
|
||||
auto [accessor, lockedRef] = resolvedRef.lazyFetch(getEvalState()->fetchSettings, *store);
|
||||
auto storePath =
|
||||
fetchToStore(getEvalState()->fetchSettings, *store, accessor, FetchMode::Copy, lockedRef.input.getName());
|
||||
auto hash = store->queryPathInfo(storePath)->narHash;
|
||||
|
||||
@@ -145,7 +145,7 @@ struct CmdLsNar : Command, MixLs
|
||||
|
||||
void run() override
|
||||
{
|
||||
AutoCloseFD fd = open(narPath.c_str(), O_RDONLY);
|
||||
AutoCloseFD fd = toDescriptor(open(narPath.c_str(), O_RDONLY));
|
||||
if (!fd)
|
||||
throw SysError("opening NAR file '%s'", narPath);
|
||||
auto source = FdSource{fd.get()};
|
||||
|
||||
@@ -193,20 +193,38 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs, virtual RootArgs
|
||||
|
||||
std::string dumpCli()
|
||||
{
|
||||
auto res = nlohmann::json::object();
|
||||
using nlohmann::json;
|
||||
|
||||
auto res = json::object();
|
||||
|
||||
res["args"] = toJSON();
|
||||
|
||||
auto stores = nlohmann::json::object();
|
||||
for (auto & [storeName, implem] : Implementations::registered()) {
|
||||
auto & j = stores[storeName];
|
||||
j["doc"] = implem.doc;
|
||||
j["uri-schemes"] = implem.uriSchemes;
|
||||
j["settings"] = implem.getConfig()->toJSON();
|
||||
j["experimentalFeature"] = implem.experimentalFeature;
|
||||
{
|
||||
auto & stores = res["stores"] = json::object();
|
||||
for (auto & [storeName, implem] : Implementations::registered()) {
|
||||
auto & j = stores[storeName];
|
||||
j["doc"] = implem.doc;
|
||||
j["uri-schemes"] = implem.uriSchemes;
|
||||
j["settings"] = implem.getConfig()->toJSON();
|
||||
j["experimentalFeature"] = implem.experimentalFeature;
|
||||
}
|
||||
}
|
||||
res["stores"] = std::move(stores);
|
||||
res["fetchers"] = fetchers::dumpRegisterInputSchemeInfo();
|
||||
|
||||
{
|
||||
auto & fetchers = res["fetchers"] = json::object();
|
||||
|
||||
for (const auto & [schemeName, scheme] : fetchers::getAllInputSchemes()) {
|
||||
auto & s = fetchers[schemeName] = json::object();
|
||||
s["description"] = scheme->schemeDescription();
|
||||
auto & attrs = s["allowedAttrs"] = json::object();
|
||||
for (auto & [fieldName, field] : scheme->allowedAttrs()) {
|
||||
auto & f = attrs[fieldName] = json::object();
|
||||
f["type"] = field.type;
|
||||
f["required"] = field.required;
|
||||
f["doc"] = stripIndentation(field.doc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return res.dump();
|
||||
}
|
||||
@@ -440,7 +458,7 @@ void mainWrapped(int argc, char ** argv)
|
||||
if (!primOp->doc)
|
||||
continue;
|
||||
b["args"] = primOp->args;
|
||||
b["doc"] = trim(stripIndentation(primOp->doc));
|
||||
b["doc"] = trim(stripIndentation(*primOp->doc));
|
||||
if (primOp->experimentalFeature)
|
||||
b["experimental-feature"] = primOp->experimentalFeature;
|
||||
builtinsJson.emplace(state.symbols[builtin.name], std::move(b));
|
||||
|
||||
@@ -121,7 +121,7 @@ static void update(const StringSet & channelNames)
|
||||
// We want to download the url to a file to see if it's a tarball while also checking if we
|
||||
// got redirected in the process, so that we can grab the various parts of a nix channel
|
||||
// definition from a consistent location if the redirect changes mid-download.
|
||||
auto result = fetchers::downloadFile(store, fetchSettings, url, std::string(baseNameOf(url)));
|
||||
auto result = fetchers::downloadFile(*store, fetchSettings, url, std::string(baseNameOf(url)));
|
||||
url = result.effectiveUrl;
|
||||
|
||||
bool unpacked = false;
|
||||
@@ -139,10 +139,10 @@ static void update(const StringSet & channelNames)
|
||||
if (!unpacked) {
|
||||
// Download the channel tarball.
|
||||
try {
|
||||
result = fetchers::downloadFile(store, fetchSettings, url + "/nixexprs.tar.xz", "nixexprs.tar.xz");
|
||||
result = fetchers::downloadFile(*store, fetchSettings, url + "/nixexprs.tar.xz", "nixexprs.tar.xz");
|
||||
} catch (FileTransferError & e) {
|
||||
result =
|
||||
fetchers::downloadFile(store, fetchSettings, url + "/nixexprs.tar.bz2", "nixexprs.tar.bz2");
|
||||
fetchers::downloadFile(*store, fetchSettings, url + "/nixexprs.tar.bz2", "nixexprs.tar.bz2");
|
||||
}
|
||||
}
|
||||
// Regardless of where it came from, add the expression representing this channel to accumulated expression
|
||||
|
||||
@@ -138,8 +138,7 @@ std::tuple<StorePath, Hash> prefetchFile(
|
||||
|
||||
Activity act(*logger, lvlChatty, actUnknown, fmt("adding '%s' to the store", url.to_string()));
|
||||
|
||||
auto info = store->addToStoreSlow(
|
||||
*name, PosixSourceAccessor::createAtRoot(tmpFile), method, hashAlgo, {}, expectedHash);
|
||||
auto info = store->addToStoreSlow(*name, makeFSSourceAccessor(tmpFile), method, hashAlgo, {}, expectedHash);
|
||||
storePath = info.path;
|
||||
assert(info.ca);
|
||||
hash = info.ca->hash;
|
||||
|
||||
@@ -68,7 +68,7 @@ struct CmdRegistryList : StoreCommand
|
||||
{
|
||||
using namespace fetchers;
|
||||
|
||||
auto registries = getRegistries(fetchSettings, store);
|
||||
auto registries = getRegistries(fetchSettings, *store);
|
||||
|
||||
for (auto & registry : registries) {
|
||||
for (auto & entry : registry->entries) {
|
||||
@@ -189,14 +189,14 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand
|
||||
auto registry = getRegistry();
|
||||
auto ref = parseFlakeRef(fetchSettings, url);
|
||||
auto lockedRef = parseFlakeRef(fetchSettings, locked);
|
||||
registry->remove(ref.input);
|
||||
auto resolvedInput = lockedRef.resolve(fetchSettings, store).input;
|
||||
auto resolved = resolvedInput.getAccessor(fetchSettings, store).second;
|
||||
auto resolvedInput = lockedRef.resolve(fetchSettings, *store).input;
|
||||
auto resolved = resolvedInput.getAccessor(fetchSettings, *store).second;
|
||||
if (!resolved.isLocked(fetchSettings))
|
||||
warn("flake '%s' is not locked", resolved.to_string());
|
||||
fetchers::Attrs extraAttrs;
|
||||
if (ref.subdir != "")
|
||||
extraAttrs["dir"] = ref.subdir;
|
||||
registry->remove(ref.input);
|
||||
registry->add(ref.input, resolved, extraAttrs);
|
||||
registry->write(getRegistryPath());
|
||||
}
|
||||
|
||||
@@ -369,6 +369,10 @@ tar cfz "$TEST_ROOT"/flake.tar.gz -C "$TEST_ROOT" flake5
|
||||
|
||||
nix build -o "$TEST_ROOT"/result file://"$TEST_ROOT"/flake.tar.gz
|
||||
|
||||
nix flake clone "file://$TEST_ROOT/flake.tar.gz" --dest "$TEST_ROOT/unpacked"
|
||||
[[ -e $TEST_ROOT/unpacked/flake.nix ]]
|
||||
expectStderr 1 nix flake clone "file://$TEST_ROOT/flake.tar.gz" --dest "$TEST_ROOT/unpacked" | grep 'existing path'
|
||||
|
||||
# Building with a tarball URL containing a SRI hash should also work.
|
||||
url=$(nix flake metadata --json file://"$TEST_ROOT"/flake.tar.gz | jq -r .url)
|
||||
[[ $url =~ sha256- ]]
|
||||
|
||||
@@ -120,7 +120,9 @@
|
||||
path_info_json = substituter.succeed(f"nix path-info --json {tarball_store_path}").strip()
|
||||
path_info_dict = json.loads(path_info_json)
|
||||
# nix path-info returns a dict with store paths as keys
|
||||
tarball_hash_sri = path_info_dict[tarball_store_path]["narHash"]
|
||||
narHash_obj = path_info_dict[tarball_store_path]["narHash"]
|
||||
# Convert from structured format {"algorithm": "sha256", "format": "base64", "hash": "..."} to SRI string
|
||||
tarball_hash_sri = f"{narHash_obj['algorithm']}-{narHash_obj['hash']}"
|
||||
print(f"Tarball NAR hash (SRI): {tarball_hash_sri}")
|
||||
|
||||
# Also get the old format hash for fetchTarball (which uses sha256 parameter)
|
||||
|
||||
Reference in New Issue
Block a user