Import existing code

dblsaiko
Nov 26, 2023, 2:58 AM
3KEFKH5FYGFUIWMIW6NY3K4FYSLE7LEXR4ZSF6WN52PCC3TVDZBAC

Dependencies

Change contents

  • file addition: src (d--r------)
    [4.1]
  • file addition: fetcher.cpp (----------)
    [0.15]
    #include <chrono>
    #include <cache.hh>
    #include <fetch-settings.hh>
    #include <fetchers.hh>
    #include <store-api.hh>
    #include <date/date.h>
    #include <nlohmann/json.hpp>
    using namespace std::string_literals;
    using namespace std::string_view_literals;
    namespace nix::fetchers
    {
    std::string runPijul(Strings args, std::optional<Path> chdir = {}, std::optional<std::string> input = {}, bool isInteractive = false)
    {
    auto program = "pijul"sv;
    auto res = runProgram(RunOptions{
    .program = std::string(program),
    .searchPath = true,
    .args = std::move(args),
    .chdir = std::move(chdir),
    .input = std::move(input),
    .isInteractive = isInteractive,
    });
    if (!statusOk(res.first)) {
    throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
    }
    return res.second;
    }
    struct PijulInputScheme : InputScheme {
    [[nodiscard]] std::optional<Input> inputFromURL(const ParsedURL &url, bool requireTree) const override
    {
    if (url.scheme != "pijul+http" && url.scheme != "pijul+https" && url.scheme != "pijul+ssh" && url.scheme != "pijul+file") {
    return {};
    }
    auto url2(url);
    url2.scheme = std::string(url2.scheme, 6);
    url2.query.clear();
    Attrs attrs;
    attrs.emplace("type"s, "pijul"s);
    for (const auto &[name, value] : url.query) {
    if (name == "channel" || name == "state") {
    attrs.emplace(name, value);
    } else {
    url2.query.emplace(name, value);
    }
    }
    attrs.emplace("url"s, url2.to_string());
    return inputFromAttrs(attrs);
    }
    [[nodiscard]] std::optional<Input> inputFromAttrs(const Attrs &attrs) const override
    {
    if (maybeGetStrAttr(attrs, "type") != "pijul") {
    return {};
    }
    for (const auto &[name, _] : attrs) {
    if (name != "type"sv && name != "url"sv && name != "channel"sv && name != "state"sv && name != "narHash"sv && name != "lastModified"sv) {
    throw Error("unsupported Pijul input attribute '%s'"s, name);
    }
    }
    parseURL(getStrAttr(attrs, "url"));
    Input input;
    input.attrs = attrs;
    if (maybeGetStrAttr(input.attrs, "channel") && maybeGetStrAttr(input.attrs, "state")) {
    input.locked = true;
    }
    return input;
    }
    [[nodiscard]] bool hasAllInfo(const Input &input) const override
    {
    return maybeGetIntAttr(input.attrs, "lastModified").has_value();
    }
    [[nodiscard]] ParsedURL toURL(const Input &input) const override
    {
    auto url = parseURL(getStrAttr(input.attrs, "url"));
    if (url.scheme != "pijul") {
    url.scheme = "pijul+"s + url.scheme;
    }
    if (auto channel = maybeGetStrAttr(input.attrs, "channel"s)) {
    url.query.insert_or_assign("channel"s, std::move(*channel));
    }
    if (auto state = maybeGetStrAttr(input.attrs, "state"s)) {
    url.query.insert_or_assign("state"s, std::move(*state));
    }
    return url;
    }
    std::pair<StorePath, Input> fetch(ref<Store> store, const Input &_input) override
    {
    if (auto localPath = getSourcePath(_input)) {
    return fetchLocal(store, _input, *localPath);
    } else {
    auto [storePath, infoAttrs] = doFetch(store, _input);
    Input input(_input);
    mergeAttrs(input.attrs, std::move(infoAttrs));
    return {std::move(storePath), input};
    }
    }
    std::optional<Path> getSourcePath(const Input &input) override
    {
    auto url = parseURL(getStrAttr(input.attrs, "url"));
    if (url.scheme == "file" && !input.getRef() && !input.getRev()) {
    return url.path;
    }
    return {};
    }
    void markChangedFile(const Input &input, std::string_view file, std::optional<std::string> commitMsg) override
    {
    auto sourcePath = getSourcePath(input);
    assert(sourcePath);
    runPijul({"add", "--", std::string(file)}, sourcePath);
    if (commitMsg)
    runPijul({"record", std::string(file), "-m", *commitMsg}, sourcePath, {}, true);
    }
    private:
    struct RepoStatus {
    std::string channel;
    std::string state;
    uint64_t lastModified;
    };
    static std::pair<StorePath, Attrs> doFetch(const ref<Store> &store, const Input &input)
    {
    const auto &name = input.getName();
    const auto url = parseURL(getStrAttr(input.attrs, "url"));
    const auto &repoUrl = url.base;
    const auto channel = maybeGetStrAttr(input.attrs, "channel");
    const auto state = maybeGetStrAttr(input.attrs, "state");
    const Attrs impureKey{
    {"type", "pijul"},
    {"name", name},
    {"url", repoUrl},
    };
    std::optional<Attrs> key;
    bool isLocked = false;
    if (channel && state) {
    isLocked = true;
    key = {
    {"type", "pijul"},
    {"name", name},
    {"channel", *channel},
    {"state", *state},
    };
    if (auto res = getCache()->lookup(store, *key)) {
    auto &[infoAttrs, storePath] = *res;
    return {std::move(storePath), std::move(infoAttrs)};
    }
    }
    if (auto res = getCache()->lookup(store, impureKey)) {
    auto &[infoAttrs, storePath] = *res;
    if ((!channel || *channel == getStrAttr(infoAttrs, "channel")) && (!state || *state == getStrAttr(infoAttrs, "state"))) {
    return {std::move(storePath), std::move(infoAttrs)};
    }
    }
    auto [storePath, rs] = doFetch(store, name, repoUrl, channel, state);
    if (!key) {
    key = {
    {"type", "pijul"},
    {"name", name},
    };
    }
    mergeAttrs(*key,
    {
    {"channel", rs.channel},
    {"state", rs.state},
    });
    Attrs infoAttrs = {
    {"channel", std::move(rs.channel)},
    {"state", std::move(rs.state)},
    {"lastModified", rs.lastModified},
    };
    if (!isLocked) {
    getCache()->add(store, impureKey, infoAttrs, storePath, false);
    }
    getCache()->add(store, *key, infoAttrs, storePath, true);
    return {std::move(storePath), std::move(infoAttrs)};
    }
    static std::pair<StorePath, RepoStatus> doFetch(const ref<Store> &store,
    const std::string_view &inputName,
    const std::string_view &repoUrl,
    const std::optional<std::string_view> &channel,
    const std::optional<std::string_view> &state)
    {
    const Path tmpDir = createTempDir();
    const AutoDelete delTmpDir(tmpDir, true);
    const auto repoDir = tmpDir + "/source"sv;
    Strings args{"clone"s};
    if (channel) {
    args.push_back("--channel"s);
    args.emplace_back(*channel);
    }
    if (state) {
    args.push_back("--state"s);
    args.emplace_back(*state);
    }
    args.emplace_back(repoUrl);
    args.push_back(repoDir);
    runPijul(args, {}, {}, true);
    RepoStatus rs = getRepoStatus(repoDir);
    if (channel && *channel != rs.channel) {
    throw Error("channel mismatch: requested %s, got %s"s, *channel, rs.channel);
    }
    if (state && *state != rs.state) {
    throw Error("state mismatch: requested %s, got %s"s, *state, rs.state);
    }
    deletePath(repoDir + "/.pijul"sv);
    auto storePath = store->addToStore(inputName, repoDir);
    return {std::move(storePath), std::move(rs)};
    }
    static std::pair<StorePath, Input> fetchLocal(const ref<Store> &store, const Input &_input, const std::string_view &path)
    {
    if (_input.attrs.contains("channel"s) || _input.attrs.contains("state"s)) {
    throw Error("no channel/state support for local Pijul repository yet"s);
    }
    Input input(_input);
    const std::string &diffOutput = runPijul({"diff", "--json"}, std::string(path));
    bool dirty = false;
    if (!diffOutput.empty()) {
    const auto &json = nlohmann::json::parse(diffOutput);
    if (!json.empty()) {
    dirty = true;
    }
    }
    if (dirty) {
    if (!fetchSettings.allowDirty) {
    throw Error("Pijul tree '%s' is dirty", path);
    }
    if (fetchSettings.warnDirty) {
    warn("Pijul tree '%s' is dirty", path);
    }
    }
    auto files = tokenizeString<std::set<std::string>>(runPijul({"list"}, std::string(path)), "\n\r");
    Path actualPath(absPath(Path(path)));
    PathFilter filter = [&](const Path &p) -> bool {
    assert(hasPrefix(p, actualPath));
    std::string file(p, actualPath.size() + 1);
    auto st = lstat(p);
    // TODO is this necessary? pijul tracks directories
    if (S_ISDIR(st.st_mode)) {
    auto prefix = file + "/";
    auto i = files.lower_bound(prefix);
    return i != files.end() && hasPrefix(*i, prefix);
    }
    return files.count(file);
    };
    auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
    try {
    const auto [state, timestamp] = getState(path);
    input.attrs.insert_or_assign("lastModified", timestamp);
    } catch (...) {
    input.attrs.insert_or_assign("lastModified", uint64_t(0));
    }
    return {std::move(storePath), input};
    }
    static void mergeAttrs(Attrs &dest, Attrs &&source)
    {
    while (true) {
    auto next = source.begin();
    if (next == source.end()) {
    break;
    }
    auto handle = source.extract(next);
    mergeOne(dest, std::move(handle.key()), std::move(handle.mapped()));
    }
    }
    static void mergeOne(Attrs &dest, std::string key, Attr attr)
    {
    const auto &d = dest.find(key);
    if (d != dest.end()) {
    if (d->second != attr) {
    throw Error("while merging attrs: value mismatch for %s", d->first);
    }
    } else {
    dest.emplace(std::move(key), std::move(attr));
    }
    }
    static RepoStatus getRepoStatus(const PathView &repoPath)
    {
    auto [state, lastModified] = getState(repoPath);
    auto channel = getRepoChannel(repoPath);
    return RepoStatus{
    .channel = std::move(channel),
    .state = std::move(state),
    .lastModified = lastModified,
    };
    }
    static std::pair<std::string, uint64_t> getState(const PathView &repoPath)
    {
    const auto &output = runPijul({"log", "--output-format", "json", "--state", "--limit", "1"}, Path(repoPath));
    const auto &json = nlohmann::json::parse(output);
    const auto &commitInfo = json.at(0);
    const auto &timestampSpec = commitInfo.at("timestamp").get<std::string>();
    const uint64_t timestamp = std::chrono::duration_cast<std::chrono::seconds>(parseRFC3339(timestampSpec).time_since_epoch()).count();
    const std::string &state = commitInfo.at("state");
    return {state, timestamp};
    }
    static date::sys_seconds parseRFC3339(const std::string &spec)
    {
    std::istringstream in;
    date::sys_seconds pt;
    in >> date::parse("%FT%TZ", pt);
    if (in.fail()) {
    in.clear();
    in.exceptions(std::ios::failbit);
    in >> date::parse("%FT%T%Ez", pt);
    }
    return pt;
    }
    static std::string getRepoChannel(const PathView &repoPath)
    {
    const auto &output = runPijul({"channel"}, Path(repoPath));
    std::string::size_type pos = 0;
    do {
    const auto nl = output.find('\n', pos);
    const auto line = std::string_view(output).substr(pos, nl - pos);
    if (line.empty()) {
    continue;
    }
    if (line.at(0) == '*') {
    return std::string(line.substr(2));
    }
    pos = nl;
    } while (pos != std::string::npos);
    throw Error("could not parse current channel"s);
    }
    };
    [[maybe_unused]] static auto rPijulInputScheme = OnStartup([] {
    registerInputScheme(std::make_unique<PijulInputScheme>());
    });
    } // namespace nix::fetchers
  • file addition: CMakeLists.txt (----------)
    [0.15]
    add_library(pijul MODULE)
    target_sources(pijul PRIVATE
    fetcher.cpp)
    target_link_libraries(pijul PUBLIC
    PkgConfig::Nix
    Boost::headers)
    target_compile_definitions(pijul PRIVATE SYSTEM="")
    install(TARGETS pijul DESTINATION ${CMAKE_INSTALL_LIBDIR}/nix/plugins)
  • file addition: package.nix (----------)
    [4.1]
    {
    stdenv,
    cmake,
    pkg-config,
    boost,
    howard-hinnant-date,
    nix,
    }:
    stdenv.mkDerivation {
    pname = "nix-plugin-pijul";
    version = "0.1.0";
    src = ./.;
    nativeBuildInputs = [
    cmake
    pkg-config
    ];
    buildInputs = [
    boost
    howard-hinnant-date
    nix
    ];
    }
  • edit in flake.nix at line 14
    [2.382]
    [2.382]
    packages = eachSystem (pkgs: rec {
    default = nix-plugin-pijul;
    nix-plugin-pijul = pkgs.callPackage ./package.nix {};
    });
  • file addition: CMakeLists.txt (----------)
    [4.1]
    cmake_minimum_required(VERSION 3.23)
    set(CMAKE_CXX_STANDARD 20)
    set(CMAKE_C_STANDARD 17)
    project(nix-plugin-pijul VERSION 0.1.0)
    set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
    set(CMAKE_SHARED_MODULE_PREFIX)
    include(GNUInstallDirs)
    find_package(PkgConfig REQUIRED)
    find_package(Boost REQUIRED)
    find_package(date REQUIRED)
    pkg_check_modules(Nix REQUIRED IMPORTED_TARGET nix-main)
    add_subdirectory(src)
  • edit in .editorconfig at line 6
    [2.1112]
    [2.1112]
    insert_final_newline = true
  • file addition: .clang-format (----------)
    [4.1]
    ---
    # SPDX-FileCopyrightText: 2019 Christoph Cullmann <cullmann@kde.org>
    # SPDX-FileCopyrightText: 2019 Gernot Gebhard <gebhard@absint.com>
    #
    # SPDX-License-Identifier: MIT
    # This file got automatically created by ECM, do not edit
    # See https://clang.llvm.org/docs/ClangFormatStyleOptions.html for the config options
    # and https://community.kde.org/Policies/Frameworks_Coding_Style#Clang-format_automatic_code_formatting
    # for clang-format tips & tricks
    ---
    Language: JavaScript
    DisableFormat: true
    ---
    # Style for C++
    Language: Cpp
    # base is WebKit coding style: https://webkit.org/code-style-guidelines/
    # below are only things set that diverge from this style!
    BasedOnStyle: WebKit
    # enforce C++11 (e.g. for std::vector<std::vector<lala>>
    Standard: c++20
    # 4 spaces indent
    TabWidth: 4
    # 2 * 80 wide lines
    ColumnLimit: 160
    # sort includes inside line separated groups
    SortIncludes: true
    # break before braces on function, namespace and class definitions.
    BreakBeforeBraces: Linux
    # CrlInstruction *a;
    PointerAlignment: Right
    # horizontally aligns arguments after an open bracket.
    AlignAfterOpenBracket: Align
    # don't move all parameters to new line
    AllowAllParametersOfDeclarationOnNextLine: false
    # no single line functions
    AllowShortFunctionsOnASingleLine: None
    # always break before you encounter multi line strings
    AlwaysBreakBeforeMultilineStrings: true
    # don't move arguments to own lines if they are not all on the same
    BinPackArguments: false
    # don't move parameters to own lines if they are not all on the same
    BinPackParameters: false
    # In case we have an if statement with multiple lines the operator should be at the beginning of the line
    # but we do not want to break assignments
    BreakBeforeBinaryOperators: NonAssignment
    # format C++11 braced lists like function calls
    Cpp11BracedListStyle: true
    # do not put a space before C++11 braced lists
    SpaceBeforeCpp11BracedList: false
    # remove empty lines
    KeepEmptyLinesAtTheStartOfBlocks: false
    # no namespace indentation to keep indent level low
    NamespaceIndentation: None
    # we use template< without space.
    SpaceAfterTemplateKeyword: false
    # Always break after template declaration
    AlwaysBreakTemplateDeclarations: true
    # macros for which the opening brace stays attached.
    ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE , wl_resource_for_each, wl_resource_for_each_safe ]
    # keep lambda formatting multi-line if not empty
    AllowShortLambdasOnASingleLine: Empty
    # We do not want clang-format to put all arguments on a new line
    AllowAllArgumentsOnNextLine: false