aboutsummaryrefslogtreecommitdiff
AgeCommit message (Collapse)Author
2024-09-18util: fix brotli decompression of empty inputJade Lovelace
This caused an infinite loop before since it would just keep asking the underlying source for more data. In practice this happened because an HTTP server served a response to a HEAD request (for which curl will not retrieve any body or call our write callback function) with Content-Encoding: br, leading to decompressing nothing at all and going into an infinite loop. This adds a test to make sure none of our compression methods do that again, as well as just patching the HTTP client to never feed empty data into a compression algorithm (since they absolutely have the right to throw CompressionError on unexpectedly-short streams!). Reported on Matrix: https://matrix.to/#/!lymvtcwDJ7ZA9Npq:lix.systems/$8BWQR_zKxCQDJ40C5NnDo4bQPId3pZ_aoDj2ANP7Itc?via=lix.systems&via=matrix.org&via=tchncs.de Change-Id: I027566e280f0f569fdb8df40e5ecbf46c211dad1
2024-09-17tests/compression: rewriteJade Lovelace
This test suite was in desperate need of using the parameterization available with gtest, and was a bunch of useless duplicated code. At least now it's not duplicated code, though it still probably should be more full of property tests. Change-Id: Ia8ccee7ef4f02b2fa40417b79aa8c8f0626ea479
2024-09-17Merge "Remove readline support" into mainrebecca “wiggles” turner
2024-09-16Remove readline supportRebecca Turner
Lix cannot be built with GNU readline, and we would "rather not" be GPL. Change-Id: I0e86f0f10dab966ab1d1d467fb61fd2de50c00de
2024-09-15common-eval-args: raise warning if `--arg` isn't a valid Nix identifierMaximilian Bosch
See https://git.lix.systems/lix-project/lix/issues/496. The core idea is to be able to do e.g. nix-instantiate -A some-nonfree-thing --arg config.allowUnfree true which is currently not possible since `config.allowUnfree` is interpreted as attribute name with a dot in it. In order to change that (probably), Jade suggested to find out if there are any folks out there relying on this behavior. For such a use-case, it may still be possible to accept strings, i.e. `--arg '"config.allowUnfree"'. Change-Id: I986c73619fbd87a95b55e2f0ac03feaed3de2d2d
2024-09-14fix: docs issue template was bustedJade Lovelace
Apparently forgejo has a more creative interpretation of \(\) than I was hoping in their markdown parser and thought it was maths. I have no idea then how you put a link in parens next to another square-bracket link, but I am not going to worry about it. There were several more typos, which I also fixed. Fixes: https://git.lix.systems/lix-project/lix/issues/517 Change-Id: I6b144c6881f92ca60ba72a304ce7a0bcb9c6659a
2024-09-14Merge "store: add a hint on how to fix Lix installs broken by macOS Sequoia" ↵jade
into main
2024-09-14Merge changes Ia1481da4,Ifca1d74d into mainjade
* changes: archive: refactor bad mutable-state API in the NAR parse listener archive: rename ParseSink to NARParseVisitor
2024-09-14store: add a hint on how to fix Lix installs broken by macOS SequoiaJade Lovelace
This is not a detailed diagnosis, and it's not worth writing one, tbh. This error basically never happens in normal operation, so diagnosing it by changing the error on macOS is good enough. Relevant: https://git.lix.systems/lix-project/lix-installer/issues/24 Relevant: https://git.lix.systems/lix-project/lix-installer/issues/18 Relevant: https://git.lix.systems/lix-project/lix/issues/521 Change-Id: I03701f917d116575c72a97502b8e1617679447f2
2024-09-13archive: refactor bad mutable-state API in the NAR parse listenerJade Lovelace
Remove the mutable state stuff that assumes that one file is being written a time. It's true that we don't write multiple files interleaved, but that mutable state is evil. Change-Id: Ia1481da48255d901e4b09a9b783e7af44fae8cff
2024-09-12Merge "fish-completion: leave the shell prompt intact" into mainalois31
2024-09-11fish-completion: leave the shell prompt intactAlois Wohlschlager
When generating shell completions, no logging output should be visible because it would destroy the shell prompt. Originally this was attempted to be done by simply disabling the progress bar (ca946860ce6ce5d4800b0d93d3f83c30d3c953c0), since the situation is particularly bad there (the screen clearing required for the rendering ends up erasing the shell prompt). Due to overlooking the implementation of this hack, it was accidentally undone during a later change (0dd1d8ca1cdccfc620644a7f690ed35bcd2d1e74). Since even with the hack correctly in place, it is still possible to mess up the prompt by logging output (for example warnings for disabled experimental features, or messages generated by `builtins.trace`), simply send it to the bit bucket where it belongs. This was already done for bash and zsh (9d840758a8d195e52e8b7d08cd9c15f6b8259724), and it seems that fish was simply missed at that time. The last trace of the no-longer-working and obsolete hack is deleted too. Fixes: https://git.lix.systems/lix-project/lix/issues/513 Change-Id: I59f1ebf90903034e2059298fa8d76bf970bc3315
2024-09-11editline: Vendor cl/1883 patch to recognize `Alt+Left`/`Alt+Right`Rebecca Turner
This vendors the patch added in cl/1883 to avoid GitHub garbage-collecting the commits we're referring to. As @emilazy pointed out on GitHub: > GitHub can garbage‐collect unmerged PR commits if they are later > force‐pushed, which means that code review in upstreams can cause > Nixpkgs builds to fail to reproduce in future. See: https://github.com/NixOS/nixpkgs/pull/341131#discussion_r1753046220 See: https://github.com/troglobit/editline/pull/70 See: https://gerrit.lix.systems/c/lix/+/1883 Change-Id: Ifff522f7f23310d6dbe9efc72fd40be5500ae872
2024-09-11archive: rename ParseSink to NARParseVisitorJade Lovelace
- Rename the listener to not be called a "sink". If it were a "sink" it would be eating bytes and conform with any of the Nix sink stuff (maybe FileHandle should be a Sink itself! but that's a later CL's problem). This is a parser listener. - Move the RetrieveRegularNARSink thing into store-api.cc, which is its only usage, and fix it to actually do what it is stated to do: crash if its invariants are violated. It's, of course, used to erm, unpack single-file NAR files, generated via a horrible contraption of sources and sinks that looks like a plumbing blueprint. Refactoring that is a future task. - Add a description of the invariants of NARParseVisitor in preparation of refactoring it. Change-Id: Ifca1d74d2947204a1f66349772e54dad0743e944
2024-09-11Merge "repl: Patch editline to recognize Meta-Left & Meta-Right" into mainrebecca “wiggles” turner
2024-09-10Merge "repl-overlays: Provide an elaborate example" into mainrebecca “wiggles” turner
2024-09-10Merge "Add `getCwd`" into mainrebecca “wiggles” turner
2024-09-09repl: Patch editline to recognize Meta-Left & Meta-RightRebecca Turner
This applies https://github.com/troglobit/editline/pull/70 to our build of editline, which translates `meta-left` and `meta-right` into `fd_word` and `bk_word`. This makes `nix repl` soooo much nicer to use! Note: My terminal renders `meta-left` as `\e\e[C` and `meta-right` as `\e\e[D`. Closes https://git.lix.systems/lix-project/lix/issues/501 Change-Id: I048b10cf17231bbf4e6bf38e1d1d8572cedaa194
2024-09-09Merge changes If8ec210f,I6e2851b2 into mainalois31
* changes: libfetchers: serialise accept-flake-config properly libstore: declare SandboxMode JSON serialisation in the header
2024-09-09forbid gcc for compilation, only allow clangeldritch horrors
while gcc 12 and older miscompile our generators, gcc 13 and older outright crash on kj coroutines. (newer gcc versions may fix this) Change-Id: I19f12c8c147239680eb0fa5a84ef5c7de38c9263
2024-09-08Merge "libmain/progress-bar: erase all lines of the multi-line format" into mainalois31
2024-09-08libstore: turn Worker in a kj event loop usereldritch horrors
using a proper event loop basis we no longer have to worry about most of the intricacies of poll(), or platform-dependent replacements for it. we may even be able to use the event loop and its promise system for all of our scheduling in the future. we don't do any real async processing yet, this is just preparation to separate the first such change from the huge api design difference with the async framework we chose (kj from capnp): kj::Promise, unlike std::future, doesn't return exceptions unmangled. it instead wraps any non-kj exception into a kj exception, erasing all type information and preserving mostly the what() string in the process. this makes sense in the capnp rpc use case where unrestricted exception types can't be transferred, and since it moves error handling styles closer to a world we'd actually like there's no harm in doing it only here for now Change-Id: I20f888de74d525fb2db36ca30ebba4bcfe9cc838
2024-09-08libutil: add a result type using boost outcomeeldritch horrors
we're using boost::outcome rather than leaf or stl types because stl types are not available everywhere and leaf does not provide its own storage for error values, relying on thread-locals and the stack. if we want to use promises we won't have a stack and would have to wrap everything into leaf-specific allocating wrappers, so outcome it is. Change-Id: I35111a1f9ed517e7f12a839e2162b1ba6a993f8f
2024-09-07libmain/progress-bar: erase all lines of the multi-line formatAlois Wohlschlager
When the multi-line log format is enabled, the progress bar usually occupies multiple lines on the screen. When stopping the progress bar, only the last line was wiped, leaving all others visible on the screen. Erase all lines belonging to the progress bar to prevent these leftovers. Asking the user for input is theoretically affected by a similar issue, but this is not observed in practice since the only place where the user is asked (whether configuration options coming from flakes should be accepted) does not actually have multiple lines on the progress bar. However, there is no real reason to not fix this either, so let's do it anyway. Change-Id: Iaa5a701874fca32e6f06d85912835d86b8fa7a16
2024-09-06Merge "Stop the logger in legacy commands again" into mainalois31
2024-09-03Merge "Test including relative paths in configuration" into mainrebecca “wiggles” turner
2024-09-03Merge "Expand comment on `std::string operator+`" into mainrebecca “wiggles” turner
2024-09-02libfetchers: serialise accept-flake-config properlyAlois Wohlschlager
The AcceptFlakeConfig type used was missing its JSON serialisation definition, so it was incorrectly serialised as an integer, ending up that way for example in the nix.conf manual page. Declare a proper serialisation. Change-Id: If8ec210f9d4dd42fe480c4e97d0a4920eb66a01e
2024-09-02libstore: declare SandboxMode JSON serialisation in the headerAlois Wohlschlager
The JSON serialisation should be declared in the header so that all translation units can see it when needed, even though it seems that it has not been used anywhere else so far. Unfortunately, this means we cannot use the NLOHMANN_JSON_SERIALIZE_ENUM convenience macro, since it uses a slightly different signature, but the code is not too bad either. Change-Id: I6e2851b250e0b53114d2fecb8011ff1ea9379d0f
2024-09-01Test including relative paths in configurationRebecca Turner
Change-Id: If6c69a5e16d1ccd223fba392890f08f0032fb754
2024-09-01repl-overlays: Provide an elaborate exampleRebecca Turner
This is the repl overlay from my dotfiles, which I think provides a reasonable and ergonomic set of variables. We can iterate on this over time, or (perhaps?) provide a sentinel value like `repl-overlays = <DEFAULT>` to include a "suggested default" overlay like this one. Change-Id: I8eba3934c50fbac8367111103e66c7375b8d134e
2024-09-01Clarify that `diff-hook` no longer needs to be an absolute pathRebecca Turner
See: https://gerrit.lix.systems/c/lix/+/1864 Change-Id: Ic70bfe42b261a83f2cb68b8f102833b739b8e03a
2024-09-01Expand comment on `std::string operator+`Rebecca Turner
Nuts! Change-Id: Ib5bc0606d7c86e57ef76dd7bcc89dce91bd3d50a
2024-09-01Merge changes I5566a985,I88cf53d3 into mainrebecca “wiggles” turner
* changes: Support relative and `~/` paths in config settings Thread `ApplyConfigOptions` through config parsing
2024-08-30libstore: add build result to Goal::Finishedeldritch horrors
it just makes sense to have it too, rather than just the pass/fail information we keep so far. once we turn goals into something more promise-shaped it'll also help detangle the current data flow mess Change-Id: I915cf04d177cad849ea7a5833215d795326f1946
2024-08-30libstore: rename Goal::Finished::result to exitCodeeldritch horrors
the more useful type for `result` is BuildResult. Change-Id: If93d9384e8d686eb63b33320f1d565f9b9afbf3a
2024-08-30libstore: remove queryMissing call from Workereldritch horrors
it doesn't have a purpose except cache priming, which is largely irrelevant by default (since another code path already runs this exact query). our store implementations do not benefit that much from this either, and the more bursty load may indeed harm them. Change-Id: I1cc12f8c21cede42524317736d5987f1e43fc9c9
2024-08-30libstore: use notifications for stats counterseldritch horrors
updating statistics *immediately* when any counter changes declutters things somewhat and makes useful status reports less dependent on the current worker main loop. using callbacks will make it easier to move the worker loop into kj entirely, using only promises for scheduling. Change-Id: I695dfa83111b1ec09b1a54cff268f3c1d7743ed6
2024-08-30libstore: don't ContinueImmediately where we can tail calleldritch horrors
there's no reason to go through the event loop in these cases. returning ContinueImmediately here is just a very convoluted way of jumping to the state we've just set after unwinding one frame of the stack, which never matters in the cases changed here because there are no live RAII guards. Change-Id: I7c00948c22e3caf35e934c1a14ffd2d40efc5547
2024-08-30libstore: print dependency errors from DerivationGoaleldritch horrors
this is not ideal, but it's better than having this stuck in the worker loop itself. setting ex on all failing goals is not problematic because only toplevel goals can ever be observable, all the others are ignored. notably only derivation goals ever set `ex`, substitution goals do not. Change-Id: I02e2164487b2955df053fef3c8e774d557aa638a
2024-08-30libstore: hide Worker goal factory methodseldritch horrors
this doesn't serve a great purpose yet except to confine construction of goals to the stack frame of Worker::run() and its child frames. we don't need this yet (and the goal constructors remain fully visible), but in a future change that fully removes the current worker loop we'll need some way of knowing which goals are top-level goals without passing the goals themselves around. once that's possible we can remove visible goals as a concept and rely on build result futures and a scheduler built upon them Change-Id: Ia73cdeffcfb9ba1ce9d69b702dc0bc637a4c4ce6
2024-08-30libstore: add "is dependency" info to goaleldritch horrors
whether goal errors are reported via the `ex` member or just printed to the log depends on whether the goal is a toplevel goal or a dependency. if goals are aware of this themselves we can move error printing out of the worker loop, and since a running worker can only be used by running goals it's totally sufficient to keep a `Worker::running` flag for this Change-Id: I6b5cbe6eccee1afa5fde80653c4b968554ddd16f
2024-08-29manual: note that __sandboxProfile allows bypassing the darwin sandboxJade Lovelace
(but only if it is set to relaxed. no security hole here.) Thanks to lilyball for pointing out this omission in the docs. Change-Id: I2408a943bfe817fe660fe1c8fefef898aaf5f7e9
2024-08-28build-time: hide boost stacktrace in a .cc fileJade Lovelace
Saves about 16s of CPU time. Not a lot but not nothing. Feels more like the principle of the thing. Change-Id: I0992d4024317c20d6985a7977d5649edfb9f46bb
2024-08-28tree-wide: shuffle headers around for about 30s compile timeJade Lovelace
This didn't really feel so worth it afterwards, but I did untangle a bunch of stuff that should not have been tangled. The general gist of this change is that variant bullshit was causing a bunch of compile time, and it seems like the only way to deal with variant induced compile time is to keep variant types out of headers. Explicit template instantiation seems to do nothing for them. I also seem to have gotten some back-end time improvement from explicitly instantiating regex, but I don't know why. There is no corresponding front-end time improvement from it: regex is still at the top of the sinners list. **** Templates that took longest to instantiate: 15231 ms: std::basic_regex<char>::_M_compile (28 times, avg 543 ms) 15066 ms: std::__detail::_Compiler<std::regex_traits<char>>::_Compiler (28 times, avg 538 ms) 12571 ms: std::__detail::_Compiler<std::regex_traits<char>>::_M_disjunction (28 times, avg 448 ms) 12454 ms: std::__detail::_Compiler<std::regex_traits<char>>::_M_alternative (28 times, avg 444 ms) 12225 ms: std::__detail::_Compiler<std::regex_traits<char>>::_M_term (28 times, avg 436 ms) 11363 ms: nlohmann::basic_json<>::parse<const char *> (21 times, avg 541 ms) 10628 ms: nlohmann::basic_json<>::basic_json (109 times, avg 97 ms) 10134 ms: std::__detail::_Compiler<std::regex_traits<char>>::_M_atom (28 times, avg 361 ms) Back-end time before messing with the regex: **** Function sets that took longest to compile / optimize: 8076 ms: void boost::io::detail::put<$>(boost::io::detail::put_holder<$> cons... (177 times, avg 45 ms) 4382 ms: std::_Rb_tree<$>::_M_erase(std::_Rb_tree_node<$>*) (1247 times, avg 3 ms) 3137 ms: boost::stacktrace::detail::to_string_impl_base<boost::stacktrace::de... (137 times, avg 22 ms) 2896 ms: void boost::io::detail::mk_str<$>(std::__cxx11::basic_string<$>&, ch... (177 times, avg 16 ms) 2304 ms: std::_Rb_tree<$>::_M_get_insert_hint_unique_pos(std::_Rb_tree_const_... (210 times, avg 10 ms) 2116 ms: bool std::__detail::_Compiler<$>::_M_expression_term<$>(std::__detai... (112 times, avg 18 ms) 2051 ms: std::_Rb_tree_iterator<$> std::_Rb_tree<$>::_M_emplace_hint_unique<$... (244 times, avg 8 ms) 2037 ms: toml::result<$> toml::detail::sequence<$>::invoke<$>(toml::detail::l... (93 times, avg 21 ms) 1928 ms: std::__detail::_Compiler<$>::_M_quantifier() (28 times, avg 68 ms) 1859 ms: nlohmann::json_abi_v3_11_3::detail::serializer<$>::dump(nlohmann::js... (41 times, avg 45 ms) 1824 ms: std::_Function_handler<$>::_M_manager(std::_Any_data&, std::_Any_dat... (973 times, avg 1 ms) 1810 ms: std::__detail::_BracketMatcher<$>::_BracketMatcher(std::__detail::_B... (112 times, avg 16 ms) 1793 ms: nix::fetchers::GitInputScheme::fetch(nix::ref<$>, nix::fetchers::Inp... (1 times, avg 1793 ms) 1759 ms: std::_Rb_tree<$>::_M_get_insert_unique_pos(std::__cxx11::basic_strin... (281 times, avg 6 ms) 1722 ms: bool nlohmann::json_abi_v3_11_3::detail::parser<$>::sax_parse_intern... (19 times, avg 90 ms) 1677 ms: boost::io::basic_altstringbuf<$>::overflow(int) (194 times, avg 8 ms) 1674 ms: std::__cxx11::basic_string<$>::_M_mutate(unsigned long, unsigned lon... (249 times, avg 6 ms) 1660 ms: std::_Rb_tree_node<$>* std::_Rb_tree<$>::_M_copy<$>(std::_Rb_tree_no... (304 times, avg 5 ms) 1599 ms: bool nlohmann::json_abi_v3_11_3::detail::parser<$>::sax_parse_intern... (19 times, avg 84 ms) 1568 ms: void std::__detail::_Compiler<$>::_M_insert_bracket_matcher<$>(bool) (112 times, avg 14 ms) 1541 ms: std::__shared_ptr<$>::~__shared_ptr() (531 times, avg 2 ms) 1539 ms: nlohmann::json_abi_v3_11_3::detail::serializer<$>::dump_escaped(std:... (41 times, avg 37 ms) 1471 ms: void std::__detail::_Compiler<$>::_M_insert_character_class_matcher<... (112 times, avg 13 ms) After messing with the regex (notice std::__detail::_Compiler vanishes here, but I don't know why): **** Function sets that took longest to compile / optimize: 8054 ms: void boost::io::detail::put<$>(boost::io::detail::put_holder<$> cons... (177 times, avg 45 ms) 4313 ms: std::_Rb_tree<$>::_M_erase(std::_Rb_tree_node<$>*) (1217 times, avg 3 ms) 3259 ms: boost::stacktrace::detail::to_string_impl_base<boost::stacktrace::de... (137 times, avg 23 ms) 3045 ms: void boost::io::detail::mk_str<$>(std::__cxx11::basic_string<$>&, ch... (177 times, avg 17 ms) 2314 ms: std::_Rb_tree<$>::_M_get_insert_hint_unique_pos(std::_Rb_tree_const_... (207 times, avg 11 ms) 1923 ms: std::_Rb_tree_iterator<$> std::_Rb_tree<$>::_M_emplace_hint_unique<$... (216 times, avg 8 ms) 1817 ms: bool nlohmann::json_abi_v3_11_3::detail::parser<$>::sax_parse_intern... (18 times, avg 100 ms) 1816 ms: toml::result<$> toml::detail::sequence<$>::invoke<$>(toml::detail::l... (93 times, avg 19 ms) 1788 ms: nlohmann::json_abi_v3_11_3::detail::serializer<$>::dump(nlohmann::js... (40 times, avg 44 ms) 1749 ms: std::_Rb_tree<$>::_M_get_insert_unique_pos(std::__cxx11::basic_strin... (278 times, avg 6 ms) 1724 ms: std::__cxx11::basic_string<$>::_M_mutate(unsigned long, unsigned lon... (248 times, avg 6 ms) 1697 ms: boost::io::basic_altstringbuf<$>::overflow(int) (194 times, avg 8 ms) 1684 ms: nix::fetchers::GitInputScheme::fetch(nix::ref<$>, nix::fetchers::Inp... (1 times, avg 1684 ms) 1680 ms: std::_Rb_tree_node<$>* std::_Rb_tree<$>::_M_copy<$>(std::_Rb_tree_no... (303 times, avg 5 ms) 1589 ms: bool nlohmann::json_abi_v3_11_3::detail::parser<$>::sax_parse_intern... (18 times, avg 88 ms) 1483 ms: non-virtual thunk to boost::wrapexcept<$>::~wrapexcept() (181 times, avg 8 ms) 1447 ms: nlohmann::json_abi_v3_11_3::detail::serializer<$>::dump_escaped(std:... (40 times, avg 36 ms) 1441 ms: std::__shared_ptr<$>::~__shared_ptr() (496 times, avg 2 ms) 1420 ms: boost::stacktrace::basic_stacktrace<$>::init(unsigned long, unsigned... (137 times, avg 10 ms) 1396 ms: boost::basic_format<$>::~basic_format() (194 times, avg 7 ms) 1290 ms: std::__cxx11::basic_string<$>::_M_replace_cold(char*, unsigned long,... (231 times, avg 5 ms) 1258 ms: std::vector<$>::~vector() (354 times, avg 3 ms) 1222 ms: std::__cxx11::basic_string<$>::_M_replace(unsigned long, unsigned lo... (231 times, avg 5 ms) 1194 ms: std::_Rb_tree<$>::_M_get_insert_hint_unique_pos(std::_Rb_tree_const_... (49 times, avg 24 ms) 1186 ms: bool tao::pegtl::internal::sor<$>::match<$>(std::integer_sequence<$>... (1 times, avg 1186 ms) 1149 ms: std::__detail::_Executor<$>::_M_dfs(std::__detail::_Executor<$>::_Ma... (70 times, avg 16 ms) 1123 ms: toml::detail::sequence<$>::invoke(toml::detail::location&) (69 times, avg 16 ms) 1110 ms: nlohmann::json_abi_v3_11_3::basic_json<$>::json_value::destroy(nlohm... (55 times, avg 20 ms) 1079 ms: std::_Function_handler<$>::_M_manager(std::_Any_data&, std::_Any_dat... (541 times, avg 1 ms) 1033 ms: nlohmann::json_abi_v3_11_3::detail::lexer<$>::scan_number() (20 times, avg 51 ms) Change-Id: I10af282bcd4fc39c2d3caae3453e599e4639c70b
2024-08-28clang-tidy: fix the fact that we are not linting headers properlyJade Lovelace
This, however, took fixing a pile of lints that we predictably missed because of this bug. Change-Id: I92c36feb4a03f62bc594c2051c7bd7418d13fb08
2024-08-28build: remove about 30 cpu-sec of compile time by explicit instantiationJade Lovelace
Apparently the fmt contraption has some extremely popular overloads, and the boost stuff in there gets built approximately infinite times in every compilation unit. Change-Id: Ideba2db7d6bf8559e4d91974bab636f5ed106198
2024-08-28Merge "libstore: remove static initializers for Store registrations" into mainrebecca “wiggles” turner
2024-08-28Merge "treewide: fix a bunch of lints" into mainjade
2024-08-26libstore: remove static initializers for Store registrationsPierre Bourdon
Ref #359. Change-Id: Ia45530ddee25fa9fc399ff10738bb0d8bbc8b221