diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 7251cbbbe59b..7fedf2f6e632 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -235,131 +235,6 @@ static RegisterPrimOp primop_fetchTree({ document) if `fetchTree` was a curried call with the first paramter 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** - > - > ```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"; - > } - > ``` - - - `"tarball"` - - Download a tar archive and extract it into the Nix store. - This has the same underyling 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"; - > } - > ``` - - - `"git"` - - Fetch a Git tree and copy it to the Nix store. - This is similar to [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit). - - - `url` (String, required) - - 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 will only consider files added to the Git index, as listed by `git ls-files` but use the *current file contents* of the Git working directory. - - - `ref` (String, optional) - - 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` - - - `shallow` (Bool, optional) - - Make a shallow clone when fetching the Git tree. - - Default: `false` - - - `submodules` (Bool, optional) - - Also fetch submodules if available. - - Default: `false` - - - `allRefs` (Bool, optional) - - If set to `true`, always fetch the entire repository, even if the latest commit is still in the cache. - Otherwise, only the latest commit is fetched if it is not already cached. - - 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. - - - `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. - The following input types are still subject to change: - `"path"` diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 7f282c97268a..57dd383fdbc3 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -8,8 +8,6 @@ namespace nix::fetchers { -using InputSchemeMap = std::map>; - std::unique_ptr inputSchemes = nullptr; void registerInputScheme(std::shared_ptr && inputScheme) @@ -22,17 +20,9 @@ void registerInputScheme(std::shared_ptr && inputScheme) inputSchemes->insert_or_assign(schemeName, std::move(inputScheme)); } -nlohmann::json dumpRegisterInputSchemeInfo() { - using nlohmann::json; - - auto res = json::object(); - - for (auto & [name, scheme] : *inputSchemes) { - auto & r = res[name] = json::object(); - r["allowedAttrs"] = scheme->allowedAttrs(); - } - - return res; +const InputSchemeMap & getAllInputSchemes() +{ + return *inputSchemes; } Input Input::fromURL(const std::string & url, bool requireTree) diff --git a/src/libfetchers/fetchers.hh b/src/libfetchers/fetchers.hh index 036647830d30..5e98f6375b81 100644 --- a/src/libfetchers/fetchers.hh +++ b/src/libfetchers/fetchers.hh @@ -149,14 +149,26 @@ 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 std::map allowedAttrs() const = 0; virtual ParsedURL toURL(const Input & input) const; @@ -200,7 +212,12 @@ struct InputScheme void registerInputScheme(std::shared_ptr && fetcher); -nlohmann::json dumpRegisterInputSchemeInfo(); +using InputSchemeMap = std::map>; + +/** + * Use this for docs, not for finding a specific scheme + */ +const InputSchemeMap & getAllInputSchemes(); struct PublicKey { diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 6ecb7a4ea1f6..2d516869a538 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -191,26 +191,163 @@ struct GitInputScheme : InputScheme return "git"; } - StringSet allowedAttrs() const override + std::string schemeDescription() const override + { + 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). + )"); + } + + std::map allowedAttrs() const override { return { - "url", - "ref", - "rev", - "shallow", - "submodules", - "exportIgnore", - "lastModified", - "revCount", - "narHash", - "allRefs", - "name", - "dirtyRev", - "dirtyShortRev", - "verifyCommit", - "keytype", - "publicKey", - "publicKeys", + { + "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 will only consider files added to the Git index, as listed by `git ls-files` but use the *current file contents* of the Git working directory. + )", + }, + }, + { + "ref", + { + .type = "String", + .required = false, + .doc = R"( + 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. + + Default: `false` + )", + }, + }, + { + "submodules", + { + .type = "Bool", + .required = false, + .doc = R"( + Also fetch submodules if available. + + Default: `false` + )", + }, + }, + { + "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"( + If set to `true`, always fetch the entire repository, even if the latest commit is still in the cache. + Otherwise, only the latest commit is fetched if it is not already cached. + + Default: `false` + )", + }, + }, + { + "name", + {}, + }, + { + "dirtyRev", + {}, + }, + { + "dirtyShortRev", + {}, + }, + { + "exportIgnore", + {}, + }, + { + "verifyCommit", + {}, + }, + { + "keytype", + {}, + }, + { + "publicKey", + {}, + }, + { + "publicKeys", + {}, + }, }; } diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 498e4135793f..2283c13a1489 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -99,16 +99,37 @@ struct GitArchiveInputScheme : InputScheme return input; } - StringSet allowedAttrs() const override + std::map allowedAttrs() const override { return { - "owner", - "repo", - "ref", - "rev", - "narHash", - "lastModified", - "host", + { + "owner", + {}, + }, + { + "repo", + {}, + }, + { + "ref", + {}, + }, + { + "rev", + {}, + }, + { + "narHash", + {}, + }, + { + "lastModified", + {}, + }, + { + "host", + {}, + }, }; } @@ -243,6 +264,12 @@ struct GitHubInputScheme : GitArchiveInputScheme { std::string_view schemeName() const override { return "github"; } + std::string schemeDescription() const override + { + // TODO + return ""; + } + std::optional> accessHeaderFromToken(const std::string & token) const override { // Github supports PAT/OAuth2 tokens and HTTP Basic @@ -324,6 +351,12 @@ struct GitLabInputScheme : GitArchiveInputScheme { std::string_view schemeName() const override { return "gitlab"; } + std::string schemeDescription() const override + { + // TODO + return ""; + } + std::optional> accessHeaderFromToken(const std::string & token) const override { // Gitlab supports 4 kinds of authorization, two of which are @@ -392,6 +425,12 @@ struct SourceHutInputScheme : GitArchiveInputScheme { std::string_view schemeName() const override { return "sourcehut"; } + std::string schemeDescription() const override + { + // TODO + return ""; + } + std::optional> accessHeaderFromToken(const std::string & token) const override { // SourceHut supports both PAT and OAuth2. See diff --git a/src/libfetchers/indirect.cc b/src/libfetchers/indirect.cc index 002c0c292f43..18bff8ed5194 100644 --- a/src/libfetchers/indirect.cc +++ b/src/libfetchers/indirect.cc @@ -55,13 +55,31 @@ struct IndirectInputScheme : InputScheme return "indirect"; } - StringSet allowedAttrs() const override + std::string schemeDescription() const override + { + // TODO + return ""; + } + + std::map allowedAttrs() const override { return { - "id", - "ref", - "rev", - "narHash", + { + "id", + {}, + }, + { + "ref", + {}, + }, + { + "rev", + {}, + }, + { + "narHash", + {}, + }, }; } diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 9982389ab20d..201d8959067e 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -77,15 +77,39 @@ struct MercurialInputScheme : InputScheme return "hg"; } - StringSet allowedAttrs() const override + std::string schemeDescription() const override + { + // TODO + return ""; + } + + std::map allowedAttrs() const override { return { - "url", - "ref", - "rev", - "revCount", - "narHash", - "name", + { + "url", + {}, + }, + { + "ref", + {}, + }, + { + "rev", + {}, + }, + { + "revCount", + {}, + }, + { + "narHash", + {}, + }, + { + "name", + {}, + }, }; } diff --git a/src/libfetchers/path.cc b/src/libfetchers/path.cc index f9b973320b08..b96db27214eb 100644 --- a/src/libfetchers/path.cc +++ b/src/libfetchers/path.cc @@ -37,19 +37,40 @@ struct PathInputScheme : InputScheme return "path"; } - StringSet allowedAttrs() const override + std::string schemeDescription() const override + { + // TODO + return ""; + } + + std::map allowedAttrs() const override { return { - "path", + { + "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", + {}, + }, }; } std::optional inputFromAttrs(const Attrs & attrs) const override diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc index 3b7709440b23..8a20c470ed0a 100644 --- a/src/libfetchers/tarball.cc +++ b/src/libfetchers/tarball.cc @@ -228,17 +228,85 @@ struct CurlInputScheme : InputScheme return input; } - StringSet allowedAttrs() const override + std::map allowedAttrs() const override { return { - "type", - "url", - "narHash", - "name", - "unpack", - "rev", - "revCount", - "lastModified", + { + "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"; + > } + > ``` + + > **Example** + > + > ```nix + > fetchTree { + > type = "tarball"; + > url = "https://github.com/NixOS/nixpkgs/tarball/nixpkgs-23.11"; + > } + > ``` + )", + }, + }, + { + "narHash", + {}, + }, + { + "name", + {}, + }, + { + "unpack", + {}, + }, + { + "rev", + {}, + }, + { + "revCount", + {}, + }, + { + "lastModified", + {}, + }, }; } @@ -266,6 +334,14 @@ struct FileInputScheme : CurlInputScheme { std::string_view schemeName() const override { 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); @@ -286,6 +362,15 @@ struct TarballInputScheme : CurlInputScheme { std::string_view schemeName() const override { 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 underyling implementation as [`builtins.fetchTarball`](@doc + root@/language/builtins.md#builtins-fetchTarball) + )"); + } + bool isValidURL(const ParsedURL & url, bool requireTree) const override { auto parsedUrlScheme = parseUrlScheme(url.scheme); diff --git a/src/nix/main.cc b/src/nix/main.cc index 39c04069be64..51c3dd56f88a 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -179,21 +179,40 @@ 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 & implem : *Implementations::registered) { - auto storeConfig = implem.getConfig(); - auto storeName = storeConfig->name(); - auto & j = stores[storeName]; - j["doc"] = storeConfig->doc(); - j["settings"] = storeConfig->toJSON(); - j["experimentalFeature"] = storeConfig->experimentalFeature(); + { + auto & stores = res["stores"] = json::object(); + for (const auto & implem : *Implementations::registered) { + auto storeConfig = implem.getConfig(); + auto storeName = storeConfig->name(); + auto & j = stores[storeName]; + j["doc"] = storeConfig->doc(); + j["settings"] = storeConfig->toJSON(); + j["experimentalFeature"] = storeConfig->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(); }