Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: bitcoin-config.h includes cleanup #29404

Merged
merged 1 commit into from
Feb 20, 2024

Conversation

theuni
Copy link
Member

@theuni theuni commented Feb 7, 2024

As mentioned in #26924 (comment) and #29263 (comment), it is currently not safe to remove bitcoin-config.h includes from headers because some unrelated file might be depending on it.

See also #26972 for discussion.

Solve this by including the file directly everywhere it's required, regardless of whether or not it's already included by another header.

There should be no functional change here, but it will allow us to safely remove includes from headers in the future.

I'm afraid it's a bit tedious to reproduce these commits, but it's reasonably straightforward:
Edit: See note below

# All commands executed from the src/ subdir.

# Collect all tokens from bitcoin-config.h.in
# Isolate the tokens and remove blank lines
# Replace newlines with | and remove the last trailing one
# Collect all files which use these tokens
# Filter out subprojects (proper forwarding can be verified from Makefiles)
# Filter out .rc files
# Save to a text file
git grep -E -l `grep undef config/bitcoin-config.h.in | cut -d" " -f2 | grep -v '^$' | tr '\n' '|' | sed 's/|$//'` | grep -v -e "^leveldb/" -e "^secp256k1/" -e "^crc32c/" -e "^minisketch/" -e "^Makefile" -e "\.rc$" > files-with-config-include.txt

# Find all files from the above list which don't include bitcoin-config.h
git grep -L -E "config/bitcoin-config.h" -- `cat files-with-config-include.txt`

# Include them manually with the exception of some files in crypto:
# crypto/sha256_arm_shani.cpp crypto/sha256_avx2.cpp crypto/sha256_sse41.cpp crypto/sha256_x86_shani.cpp
# These are exceptions which don't use bitcoin-config.h, rather the Makefile.am adds these cppflags manually.

# Commit changes. This should match the first commit of this PR.

# Use the same search as above to find all files which DON'T use any config tokens
git grep -E -L `grep undef config/bitcoin-config.h.in | cut -d" " -f2 | grep -v '^$' | tr '\n' '|' | sed 's/|$//'` | grep -v -e "^leveldb/" -e "^secp256k1/" -e "^crc32c/" -e "^minisketch/" -e "^Makefile" -e "\.rc$" > files-without-config-include.txt

# Manually remove the includes and commit changes. This should match the second commit of this PR.

Edit: I'll keep this old description for posterity, but the manual approach has been replaced with a scripted diff from TheCharlatan

@DrahtBot
Copy link
Contributor

DrahtBot commented Feb 7, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK maflcko, TheCharlatan, hebasto, fanquake
Concept ACK jonatack, epiccurious

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

@theuni
Copy link
Member Author

theuni commented Feb 7, 2024

Ping @maflcko

This (or something like it) is a prerequisite for #29404.

There doesn't seem to be any sane way to make this a scripted diff or a clang-tidy transform, so it would be great if someone could reproduce my results.

@theuni
Copy link
Member Author

theuni commented Feb 7, 2024

Removed the include from interfaces/wallet.h which snuck in because of a comment:

//! function will be undefined in builds where ENABLE_WALLET is false.

That was the only example of a false-positive that I managed to find.

@TheCharlatan
Copy link
Contributor

I validated the patch by running:
> iwyu_tool -p . -j $(nproc) -- -Xiwyu --max_line_length=180 -I/usr/lib/llvm-14/lib/clang/14/include/ -Xiwyu --mapping_file=/home/drgrid/bitcoin/contrib/devtools/iwyu/bitcoin.core.imp > iwyu_output.txt
> cat iwyu_output.txt | awk '/should remove these lines:/ {file=$1} /- #include <config\/bitcoin-config.h>/ {split($NF, a, "-"); line=a[1]; if (!seen[file, line]++) print file " " a[1]}' | while read -r file line; do sed -i -e "$((line-1)),$((line+2))d" "src/$file"; done;
> cat iwyu_output.txt | awk '/should add these lines:/ {file=$1} /^#include "bitcoin-config.h"/ {if (!seen[file]++) print file ";" "#include <config/bitcoin-config.h>" " " substr($0, index($0, "//"))}' | while IFS=";" read -r file include; do sed -i "/\/\/ file COPYING or http:\/\/www.opensource.org\/licenses\/mit-license.php\./a \\\\n\#if defined(HAVE_CONFIG_H)\\n$include\\n\#endif" "src/$file" ; done

The script misses a few of the cases, because some subset of the bitcoin-config.h symbols are left commented or undefined, meaning IWYU is not capable of picking up on them. It does add the commented required symbol names though, which should be part of this patch in my opinion. I'll try to come back with a scripted-diff solution for this.

Maybe a tidy plugin doing a full text search during the pre-processor hook could work, which would be kind of similar to the process you already posted here. So I guess we could add a little rust lint job for this instead?

Copy link
Member

@maflcko maflcko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include them manually with the exception of some files in crypto:

unrelated question: Is there a reason they need to use the flag at all? The files are never compiled and linked when the flag isn't set, so making them appear "empty" when the flag isn't set is not needed?

@@ -3,6 +3,10 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#if defined(HAVE_CONFIG_H)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated question: Is there any reason for this check? I understand that it is possible to leave the symbol unset and then set any symbols over the command line, but is anyone doing this, or is it realistic that anyone will ever do it?

If not, it could make sense to unconditionally include the config header?

Otherwise, I could see a bug silently sneak in when there is a typo in this line, such as #if defined(_HAVE_CONFIG_H) (or any other intentional or accidental typo)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several headers (serialization comes to mind) that are very handy to use just by copying them out of our tree and using them as-is without a buildsystem. I do this quite often myself. I'd hate to give it up, but it's also become a goal of mine to remove the need for any autoconf defines for low-level files like that. Not a hard goal, just a nice-to-have. And with c++20 we're nearly there. See #29263 for a PR that does a good bit in this regard.

It would also be a nice property for libbitcoinkernel though :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking was that removing the guard doesn't preclude to copy the header and use it elsewhere, instead it makes it obvious that some features are disabled, or may be misdetected (which could lead to bugs). The overhead of removing a single line of code after creating the copy seems worth it to notify the user of this fact? Also, the overall overhead of all users copying a few headers and having to remove a single line in some of them should be much less than the overhead on Bitcoin Core having to add at least two lines of code for each such include manually. (For example, IWYU doesn't do it itself).

So it seems like a win-win to me, but no strong opinion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless there's an objection, I'd follow up with this in a follow-up?

@jonatack
Copy link
Contributor

jonatack commented Feb 8, 2024

Concept ACK

@theuni
Copy link
Member Author

theuni commented Feb 8, 2024

Include them manually with the exception of some files in crypto:

unrelated question: Is there a reason they need to use the flag at all? The files are never compiled and linked when the flag isn't set, so making them appear "empty" when the flag isn't set is not needed?

Yeah, I agree. If there's some historical reason for this, I've forgotten it. It makes sense to me to make it either optionally compiled or optionally opted in (or out) via a define, but I don't see the need for both.

I do seem to remember some tools (ar or ld) that didn't like dealing with an empty file. So maybe it started as being unconditionally compiled with an ifdef to control, and became additionally guarded at the buildsystem level on top to account for those tools?

@TheCharlatan
Copy link
Contributor

Made this into a scripted-diff here: TheCharlatan@e4186f7

There are no differences with the patch in this PR, besides a single whitespace issue. Feel free to pick the commit and make this into a scripted-diff as well, though I'm not sure if it is worth it, since the script is a bit dense.

@epiccurious
Copy link

Concept ACK 70171e0.

@theuni
Copy link
Member Author

theuni commented Feb 12, 2024

There doesn't seem to be any sane way to make this a scripted diff

@TheCharlatan saw this and said "hold my beer". Amazing!

@theuni theuni force-pushed the cleanup-config-h-headers2 branch 2 times, most recently from 3e0ddb0 to a12fb57 Compare February 12, 2024 23:29
@theuni
Copy link
Member Author

theuni commented Feb 12, 2024

Replaced my commit with @TheCharlatan's artwork above.

It doesn't work in the c-i environment because bitcoin-config.h.in is missing. I added an ./autogen.sh to the script but that doesn't help because that env doesn't have autoconf.

Next I tried prepending with a bare autoheader, which should be all that's actually needed, but that's missing too.

Sadly, I'm not sure it's worth continuing with this. Even if it does work, the verify script would have to modify the user's tree which isn't very nice :(

@TheCharlatan
Copy link
Contributor

TheCharlatan commented Feb 13, 2024

Re #29404 (comment)

It doesn't work in the c-i environment because bitcoin-config.h.in is missing. I added an ./autogen.sh to the script but that doesn't help because that env doesn't have autoconf.

Could just replace the first command then. Not as nice, but for the purpose of this PR, reviewers can run the mentioned command manually: TheCharlatan@b78e3ce (also applies some fixes, because the verify script uses sh and not bash :P).

@theuni
Copy link
Member Author

theuni commented Feb 13, 2024

@TheCharlatan Sure, agreed it's better than nothing. Not quite self-contained, but given that a few of us have independently arrived at what the correct diff should be, it's easy to tell if it's correct.

Will take that commit.

-BEGIN VERIFY SCRIPT-

regex_string='^(?!//).*(AC_APPLE_UNIVERSAL_BUILD|BOOST_PROCESS_USE_STD_FS|CHAR_EQUALS_INT8|CLIENT_VERSION_BUILD|CLIENT_VERSION_IS_RELEASE|CLIENT_VERSION_MAJOR|CLIENT_VERSION_MINOR|COPYRIGHT_HOLDERS|COPYRIGHT_HOLDERS_FINAL|COPYRIGHT_HOLDERS_SUBSTITUTION|COPYRIGHT_YEAR|ENABLE_ARM_SHANI|ENABLE_AVX2|ENABLE_EXTERNAL_SIGNER|ENABLE_SSE41|ENABLE_TRACING|ENABLE_WALLET|ENABLE_X86_SHANI|ENABLE_ZMQ|HAVE_BOOST|HAVE_BUILTIN_CLZL|HAVE_BUILTIN_CLZLL|HAVE_BYTESWAP_H|HAVE_CLMUL|HAVE_CONSENSUS_LIB|HAVE_CXX20|HAVE_DECL_BE16TOH|HAVE_DECL_BE32TOH|HAVE_DECL_BE64TOH|HAVE_DECL_BSWAP_16|HAVE_DECL_BSWAP_32|HAVE_DECL_BSWAP_64|HAVE_DECL_FORK|HAVE_DECL_FREEIFADDRS|HAVE_DECL_GETIFADDRS|HAVE_DECL_HTOBE16|HAVE_DECL_HTOBE32|HAVE_DECL_HTOBE64|HAVE_DECL_HTOLE16|HAVE_DECL_HTOLE32|HAVE_DECL_HTOLE64|HAVE_DECL_LE16TOH|HAVE_DECL_LE32TOH|HAVE_DECL_LE64TOH|HAVE_DECL_PIPE2|HAVE_DECL_SETSID|HAVE_DECL_STRERROR_R|HAVE_DEFAULT_VISIBILITY_ATTRIBUTE|HAVE_DLFCN_H|HAVE_DLLEXPORT_ATTRIBUTE|HAVE_ENDIAN_H|HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR|HAVE_FDATASYNC|HAVE_GETENTROPY_RAND|HAVE_GETRANDOM|HAVE_GMTIME_R|HAVE_INTTYPES_H|HAVE_LIBADVAPI32|HAVE_LIBCOMCTL32|HAVE_LIBCOMDLG32|HAVE_LIBGDI32|HAVE_LIBIPHLPAPI|HAVE_LIBKERNEL32|HAVE_LIBOLE32|HAVE_LIBOLEAUT32|HAVE_LIBSHELL32|HAVE_LIBSHLWAPI|HAVE_LIBUSER32|HAVE_LIBUUID|HAVE_LIBWINMM|HAVE_LIBWS2_32|HAVE_MALLOC_INFO|HAVE_MALLOPT_ARENA_MAX|HAVE_MINIUPNPC_MINIUPNPC_H|HAVE_MINIUPNPC_UPNPCOMMANDS_H|HAVE_MINIUPNPC_UPNPERRORS_H|HAVE_NATPMP_H|HAVE_O_CLOEXEC|HAVE_POSIX_FALLOCATE|HAVE_PTHREAD|HAVE_PTHREAD_PRIO_INHERIT|HAVE_STDINT_H|HAVE_STDIO_H|HAVE_STDLIB_H|HAVE_STRERROR_R|HAVE_STRINGS_H|HAVE_STRING_H|HAVE_STRONG_GETAUXVAL|HAVE_SYSCTL|HAVE_SYSCTL_ARND|HAVE_SYSTEM|HAVE_SYS_ENDIAN_H|HAVE_SYS_PRCTL_H|HAVE_SYS_RESOURCES_H|HAVE_SYS_SELECT_H|HAVE_SYS_STAT_H|HAVE_SYS_SYSCTL_H|HAVE_SYS_TYPES_H|HAVE_SYS_VMMETER_H|HAVE_THREAD_LOCAL|HAVE_TIMINGSAFE_BCMP|HAVE_UNISTD_H|HAVE_VM_VM_PARAM_H|LT_OBJDIR|PACKAGE_BUGREPORT|PACKAGE_NAME|PACKAGE_STRING|PACKAGE_TARNAME|PACKAGE_URL|PACKAGE_VERSION|PTHREAD_CREATE_JOINABLE|QT_QPA_PLATFORM_ANDROID|QT_QPA_PLATFORM_COCOA|QT_QPA_PLATFORM_MINIMAL|QT_QPA_PLATFORM_WINDOWS|QT_QPA_PLATFORM_XCB|QT_STATICPLUGIN|STDC_HEADERS|STRERROR_R_CHAR_P|USE_ASM|USE_BDB|USE_DBUS|USE_NATPMP|USE_QRCODE|USE_SQLITE|USE_UPNP|_FILE_OFFSET_BITS|_LARGE_FILES)'

exclusion_files=":(exclude)src/minisketch :(exclude)src/crc32c :(exclude)src/secp256k1 :(exclude)src/crypto/sha256_arm_shani.cpp :(exclude)src/crypto/sha256_avx2.cpp :(exclude)src/crypto/sha256_sse41.cpp :(exclude)src/crypto/sha256_x86_shani.cpp"

git grep --perl-regexp --files-with-matches "$regex_string" -- '*.cpp' $exclusion_files | xargs git grep -L "bitcoin-config.h" | while read -r file; do line_number=$(awk -v my_file="$file" '/\/\/ file COPYING or https?:\/\/www.opensource.org\/licenses\/mit-license.php\./ {line = NR} /^\/\// && NR == line + 1 {while(getline && /^\/\//) line = NR} END {print line+1}' "$file"); sed -i "${line_number}i\\\\n\#if defined(HAVE_CONFIG_H)\\n#include <config/bitcoin-config.h>\\n\#endif" "$file"; done;

git grep --perl-regexp --files-with-matches "$regex_string" -- '*.h' $exclusion_files | xargs git grep -L "bitcoin-config.h" | while read -r file; do sed -i "/#define.*_H/a \\\\n\#if defined(HAVE_CONFIG_H)\\n#include <config/bitcoin-config.h>\\n\#endif" "$file"; done;

for file in $(git grep --files-with-matches 'bitcoin-config.h' -- '*.cpp' '*.h' $exclusion_files); do if ! grep -q --perl-regexp "$regex_string" $file; then sed -i '/HAVE_CONFIG_H/{N;N;N;d;}' $file; fi; done;

-END VERIFY SCRIPT-

The first command creates a regular expression for matching all bitcoin-config.h symbols in the following form: ^(?!//).*(AC_APPLE_UNIVERSAL_BUILD|BOOST_PROCESS_USE_STD_FS|...|_LARGE_FILES). It was generated with:
./autogen.sh && printf '^(?!//).*(%s)' $(awk '/^#undef/ {print $2}' src/config/bitcoin-config.h.in | paste -sd "|" -)

The second command holds a list of files and directories that should not be processed. These include subtree directories as well as some crypto files that already get their symbols through the makefile.

The third command checks for missing bitcoin-config headers in .cpp files and adds the header if it is missing.

The fourth command checks for missing bitcoin-config headers in .h files and adds the header if it is missing.

The fifth command checks for unneeded bitcoin-config headers in sources files and removes the header if it is unneeded.
Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK.

#29404 (comment):

So I guess we could add a little ... lint job for this instead?

Agree. Without a linter, the code might be drifted back in its current state.

@maflcko
Copy link
Member

maflcko commented Feb 15, 2024

It would be interesting where this changes the code. I presume the places are:

  • The timingsafe_bcmp impl in the code was previously always picked. Now it may or may not be picked.
  • LogQtInfo will always print "dynamic" for plugin. Now it will print the correct thing.

Also, there is an iwyu false-positive in the CI output:

httpserver.cpp should remove these lines:
- #include <config/bitcoin-config.h>  // lines 6-6

@maflcko
Copy link
Member

maflcko commented Feb 15, 2024

ACK 9d1dbbd 🚪

Show signature

Signature:

untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: ACK 9d1dbbd4ceb8c04340927f5127195dc306adf3f 🚪
HvZ6TDJIggqv10zJMB4fjX1k9ctbrU9io7T5b+6A1S7hmM31kL4OefrOTWQjQYmPiOyhpWQSXrt38WoH8hVgBw==

@theuni
Copy link
Member Author

theuni commented Feb 15, 2024

* The `timingsafe_bcmp` impl in the code was previously always picked. Now it may or may not be picked.

At least for now, this one should be coming from crypto/common.h. That wouldn't have been the case after #29263 though.

@maflcko
Copy link
Member

maflcko commented Feb 15, 2024

Ah, good point. So it looks like right now this is a refactor, and shouldn't change the binary on any platform? If you agree, you can add the refactor: prefix to the pull request title.

@theuni theuni changed the title bitcoin-config.h includes cleanup refactor: bitcoin-config.h includes cleanup Feb 16, 2024
@theuni
Copy link
Member Author

theuni commented Feb 16, 2024

PR title an description updated.

Copy link
Contributor

@TheCharlatan TheCharlatan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 9d1dbbd

Copy link
Member

@hebasto hebasto left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 9d1dbbd, I have reviewed the code and it looks OK.

@DrahtBot
Copy link
Contributor

Guix builds (on x86_64)

File commit 3cbc8cb
(master)
commit 82f19db
(master and this pull)
SHA256SUMS.part 09096ba05ec1a6ce... 7284c7b9f9a4b068...
*-aarch64-linux-gnu-debug.tar.gz eefa1f348909dab6... f531cd0946ea0f03...
*-aarch64-linux-gnu.tar.gz 80431debc5f713f7... 82cd8e0853c524c0...
*-arm-linux-gnueabihf-debug.tar.gz b64d310d6443013f... 39ce34d8ab85ac06...
*-arm-linux-gnueabihf.tar.gz 1dec607fce40d7e0... 77eb89bbe0e3b5c5...
*-arm64-apple-darwin-unsigned.tar.gz 6f73fe8dd6032796... a144ae3c6e139d5a...
*-arm64-apple-darwin-unsigned.zip fc0415d43de4272f... 4b0ecae3e5d09d83...
*-arm64-apple-darwin.tar.gz 898fe2cffff08af3... a58329b6796a43e5...
*-powerpc64-linux-gnu-debug.tar.gz 61ed4d97bc1cdab0... aab697e4a0866714...
*-powerpc64-linux-gnu.tar.gz 12088845cd03ff54... 3ce6353da3475758...
*-powerpc64le-linux-gnu-debug.tar.gz fe7761ddb8daa8f3... 8541e4f5221d015e...
*-powerpc64le-linux-gnu.tar.gz 31f3ac1830488de5... d30035e906c99a5d...
*-riscv64-linux-gnu-debug.tar.gz 87208aa0655bc41a... 4db9363189ca6480...
*-riscv64-linux-gnu.tar.gz d64326ec00c6da8e... 0d0202fbfcbd36a0...
*-x86_64-apple-darwin-unsigned.tar.gz 455bcb971dd9c82f... 0eccae1bf1946fe1...
*-x86_64-apple-darwin-unsigned.zip 21d3464f68f9e37d... 96de578eb7de8a2b...
*-x86_64-apple-darwin.tar.gz 94eb1efdaf765b77... 3eaa9f86f279e7b0...
*-x86_64-linux-gnu-debug.tar.gz 0e8067199808b910... d70b2547c0dba4ce...
*-x86_64-linux-gnu.tar.gz 4db39dd75d018295... d64e0496fcc5c1e3...
*.tar.gz 84a94e3093081baf... dc3912e76f3a627d...
guix_build.log f1d3772a0147f2b8... 495f89b1b7b4db63...
guix_build.log.diff a68ca74e42e7a50f...

@fanquake
Copy link
Member

fanquake commented Feb 20, 2024

Note there are at least two defines missing from the scriptied diff (_TIME_BITS & __MINGW_USE_VC2005_COMPAT), if using Autoconf 2.72 , however that should not make a difference here. Looks like WORDS_BIGENDIAN is also missing, but that should also be ok.

@maflcko
Copy link
Member

maflcko commented Feb 20, 2024

Looks like WORDS_BIGENDIAN is also missing, but that should also be ok.

It is covered in #29408, in any case.

Copy link
Member

@fanquake fanquake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 9d1dbbd

@fanquake fanquake merged commit 45b2a91 into bitcoin:master Feb 20, 2024
16 checks passed
achow101 added a commit that referenced this pull request May 7, 2024
…tcoin-config.h includes

fa09451 Add lint check for bitcoin-config.h include IWYU pragma (MarcoFalke)
dddd40b scripted-diff: Add IWYU pragma keep to bitcoin-config.h includes (MarcoFalke)

Pull request description:

  The `bitcoin-config.h` includes have issues:

  * The header is incompatible with iwyu, because symbols may be defined or not defined. So the `IWYU pragma: keep` is needed to keep the include when a symbol is not defined on a platform. Compare the previous discussion in #29408 (comment)
  * Guarding the includes by `HAVE_CONFIG_H` is verbose and brittle. Now that all build config dependencies have been removed from low level headers, the benefits are questionable, and the guard can be removed. The linter could also be tricked by guarding the include by `#if defined(HAVE_C0NFIG_H)` (`O` replaced by `0`). Compare the previous discussion in #29404 (comment) .

ACKs for top commit:
  achow101:
    ACK fa09451
  TheCharlatan:
    ACK fa09451
  hebasto:
    re-ACK fa09451, only rebased since my recent [review](#29494 (review)) (`timedata.cpp` removed in #29623).

Tree-SHA512: 47cb973f7f24bc625acc4e78683371863675d186780236d55d886cf4130e05a78bb04f1d731aae7088313b8e963a9677cc77cf518187dbd99d776f6421ca9b52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants