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

Probe and dlopen() the correct libstdc++ #46976

Merged
merged 28 commits into from
Nov 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8a2ad6b
Probe if system libstdc++ is newer than ours
giordano Sep 27, 2022
314df3e
Addressed review comments.
apaz-cli Oct 14, 2022
b395b0b
Change error handling in wrapper functions
apaz-cli Oct 14, 2022
128e8be
Call write_wrapper three times instead of snprintf
apaz-cli Oct 14, 2022
b4f876f
Apply suggestions from code review
apaz-cli Oct 14, 2022
f423adb
Update cli/loader_lib.c
apaz-cli Oct 14, 2022
6d84325
Reordered reading and waiting to avoid a deadlock.
apaz-cli Oct 14, 2022
2961b0f
Fixed obvious issues.
apaz-cli Oct 17, 2022
6ff2bf5
Only load libstdc++ preemptively on linux.
apaz-cli Oct 17, 2022
799c642
Update cli/loader_lib.c
apaz-cli Oct 18, 2022
c652040
Update cli/loader_lib.c
apaz-cli Oct 18, 2022
1574d22
Specified path to bundled libstdc++ on the command line.
apaz-cli Oct 19, 2022
213fdce
Removed whitespace.
apaz-cli Oct 19, 2022
ea99107
Update cli/Makefile
apaz-cli Oct 19, 2022
ece7a5a
Handled make install stringreplace.
apaz-cli Oct 20, 2022
2788bc0
Correctly quoted stringreplace.
apaz-cli Oct 20, 2022
c4a95e7
Added -Wl,--enable-new-dtags to prevent DT_RPATH for transitive depen…
apaz-cli Oct 24, 2022
ca967aa
Updated news entry.
apaz-cli Oct 24, 2022
4bf5699
Added comment about environment variable.
apaz-cli Oct 24, 2022
3314c16
patched rpath for libgfortran and libLLVM.
apaz-cli Oct 26, 2022
794e7a7
Added explaination to Make.inc
apaz-cli Oct 28, 2022
483a081
Removed trailing space
apaz-cli Oct 29, 2022
f570772
Merge branch 'master' into mg/dlopen-libstdc++
staticfloat Oct 29, 2022
2c9a17e
Removed patchelf for libgfortran, now that BB has been fixed.
apaz-cli Oct 31, 2022
cc88e0e
Merge branch 'mg/dlopen-libstdc++' of https://github.com/apaz-cli/jul…
apaz-cli Oct 31, 2022
a8262e4
Merge remote-tracking branch 'origin/master' into mg/dlopen-libstdc++
apaz-cli Nov 1, 2022
f675f17
Fixed typos and comments
apaz-cli Nov 2, 2022
7685f15
Merge branch 'master' into mg/dlopen-libstdc++
apaz-cli Nov 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions Make.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,29 @@ BB_TRIPLET := $(subst $(SPACE),-,$(filter-out cxx%,$(filter-out libgfortran%,$(s

LIBGFORTRAN_VERSION := $(subst libgfortran,,$(filter libgfortran%,$(subst -,$(SPACE),$(BB_TRIPLET_LIBGFORTRAN))))

# CSL_NEXT_GLIBCXX_VERSION is a triple of the symbols representing support for whatever
# the next libstdc++ version would be. This is used for two things.
# 1. Whether the system libraries are new enough, if we need to use the libs bundled with CSL
# 2. To know which libstdc++ to load at runtime
# We want whichever libstdc++ library is newer, because if we don't it can cause problems.
# While what CSL bundles is quite bleeding-edge compared to what most distros ship, if someone
# tries to build an older branch of Julia, the version of CSL that ships with it may be
# relatively old. This is not a problem for code that is built in BB, but when we build Julia
# with the system compiler, that compiler uses the version of `libstdc++` that it is bundled
# with, and we can get linker errors when trying to run that `julia` executable with the
# `libstdc++` that comes from the (now old) BB-built CSL.
# To fix this, we take note when the system `libstdc++.so` is newer than whatever we
# would get from CSL (by searching for a `GLIBCXX_X.Y.Z` symbol that does not exist
# in our CSL, but would in a newer one), and default to `USE_BINARYBUILDER_CSL=0` in
# this case. This ensures that we link against a version with the symbols required.
# We also check the system libstdc++ at runtime in the cli loader library, and
# load it if it contains the version symbol that indicates that it is newer than the one
# shipped with CSL. Although we do not depend on any of the symbols, it is entirely
# possible that a user might choose to install a library which depends on symbols provided
# by a newer libstdc++. Without runtime detection, those libraries would break.
CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.31|GLIBCXX_3\.5\.|GLIBCXX_4\.


# This is the set of projects that BinaryBuilder dependencies are hooked up for.
# Note: we explicitly _do not_ define `CSL` here, since it requires some more
# advanced techniques to decide whether it should be installed from a BB source
Expand Down Expand Up @@ -1205,18 +1228,16 @@ ifneq (,$(filter $(OS),WINNT emscripten))
RPATH :=
RPATH_ORIGIN :=
RPATH_ESCAPED_ORIGIN :=
RPATH_LIB :=
else ifeq ($(OS), Darwin)
RPATH := -Wl,-rpath,'@executable_path/$(build_libdir_rel)'
RPATH_ORIGIN := -Wl,-rpath,'@loader_path/'
RPATH_ESCAPED_ORIGIN := $(RPATH_ORIGIN)
RPATH_LIB := -Wl,-rpath,'@loader_path/'
else
RPATH := -Wl,-rpath,'$$ORIGIN/$(build_libdir_rel)' -Wl,-rpath,'$$ORIGIN/$(build_private_libdir_rel)' -Wl,-rpath-link,$(build_shlibdir) -Wl,-z,origin
RPATH_ORIGIN := -Wl,-rpath,'$$ORIGIN' -Wl,-z,origin
RPATH_ESCAPED_ORIGIN := -Wl,-rpath,'\$$\$$ORIGIN' -Wl,-z,origin -Wl,-rpath-link,$(build_shlibdir)
RPATH_LIB := -Wl,-rpath,'$$ORIGIN/' -Wl,-z,origin
RPATH := -Wl,-rpath,'$$ORIGIN/$(build_libdir_rel)' -Wl,-rpath,'$$ORIGIN/$(build_private_libdir_rel)' -Wl,-rpath-link,$(build_shlibdir) -Wl,-z,origin -Wl,--enable-new-dtags
RPATH_ORIGIN := -Wl,-rpath,'$$ORIGIN' -Wl,-z,origin -Wl,--enable-new-dtags
RPATH_ESCAPED_ORIGIN := -Wl,-rpath,'\$$\$$ORIGIN' -Wl,-z,origin -Wl,-rpath-link,$(build_shlibdir) -Wl,--enable-new-dtags
endif
RPATH_LIB := $(RPATH_ORIGIN)

# --whole-archive
ifeq ($(OS), Darwin)
Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ endif
# Note that we disable MSYS2's path munging here, as otherwise
# it replaces our `:`-separated list as a `;`-separated one.
define stringreplace
MSYS2_ARG_CONV_EXCL='*' $(build_depsbindir)/stringreplace $$(strings -t x - $1 | grep $2 | awk '{print $$1;}') $3 255 "$(call cygpath_w,$1)"
MSYS2_ARG_CONV_EXCL='*' $(build_depsbindir)/stringreplace $$(strings -t x - '$1' | grep "$2" | awk '{print $$1;}') "$3" 255 "$(call cygpath_w,$1)"
endef


Expand Down Expand Up @@ -382,6 +382,16 @@ else ifeq ($(JULIA_BUILD_MODE),debug)
endif
endif

# Fix rpaths for dependencies. This should be fixed in BinaryBuilder later.
ifeq ($(OS), Linux)
-$(PATCHELF) --set-rpath '$$ORIGIN' $(DESTDIR)$(private_shlibdir)/libLLVM.$(SHLIB_EXT)
endif
Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Note for other reviewers: we also identified that libgit2, lib*san, and all suitesparse libraries do not have RUNPATH: $ORIGIN in them as expected. Of those, however, the lib*san libraries are not installed, so we don't care about them currently. Secondly, we appear to load all of the dependencies of libgit2 at startup (excluding libgit2 itself), according to Libdl.dllist(), so it shouldn't see a change there, even with attempted LD_LIBRARY_PATH overloading:

  SONAME               libgit2.so.1.4
  NEEDED               librt.so.1
  NEEDED               libpthread.so.0
  NEEDED               libmbedtls.so.14
  NEEDED               libmbedx509.so.1
  NEEDED               libmbedcrypto.so.7
  NEEDED               libssh2.so.1
  NEEDED               libc.so.6
  RPATH                $ORIGIN

And while we do not load all of SuiteSparse at startup, the _jll pkg will attempt to sort them correctly, so the presence of RPATH $ORIGIN should be of little to no consequence, compared to RUNPATH $ORIGIN that we would prefer to see there.

So these seem to be the only 2 libraries that we need to care about for this PR to progress at present.


# Replace libstdc++ path, which is also moving from `lib` to `../lib/julia`.
ifeq ($(OS),Linux)
$(call stringreplace,$(DESTDIR)$(shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT),\*libstdc++\.so\.6$$,*$(call dep_lib_path,$(shlibdir),$(private_shlibdir)/libstdc++.so.6))
endif


ifneq ($(LOADER_BUILD_DEP_LIBS),$(LOADER_INSTALL_DEP_LIBS))
# Next, overwrite relative path to libjulia-internal in our loader if $$(LOADER_BUILD_DEP_LIBS) != $$(LOADER_INSTALL_DEP_LIBS)
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ Deprecated or removed

External dependencies
---------------------

* On Linux, now autodetects the system libstdc++ version, and automatically loads the system library if it is newer. The old behavior of loading the bundled libstdc++ regardless of the system version obtained by setting the environment variable `JULIA_PROBE_LIBSTDCXX=0`.
* Removed `RPATH` from the julia binary. On Linux this may break libraries that have failed to set `RUNPATH`.

Tooling Improvements
---------------------
Expand Down
6 changes: 4 additions & 2 deletions cli/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ LOADER_LDFLAGS = $(JLDFLAGS) -ffreestanding -L$(build_shlibdir) -L$(build_libdir

ifeq ($(OS),WINNT)
LOADER_CFLAGS += -municode -mconsole -nostdlib -fno-stack-check -fno-stack-protector -mno-stack-arg-probe
else ifeq ($(OS),Linux)
LOADER_CFLAGS += -DGLIBCXX_LEAST_VERSION_SYMBOL=\"$(shell echo "$(CSL_NEXT_GLIBCXX_VERSION)" | cut -d'|' -f1 | sed 's/\\//g')\"
endif

ifeq ($(OS),WINNT)
Expand Down Expand Up @@ -110,7 +112,7 @@ endif

$(build_shlibdir)/libjulia.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_OBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir)
@$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(SHIPFLAGS) $(LIB_OBJS) -o $@ \
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT)))
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia.$(JL_MAJOR_SHLIB_EXT)))
@$(INSTALL_NAME_CMD)libjulia.$(SHLIB_EXT) $@
ifeq ($(OS), WINNT)
@# Note that if the objcopy command starts getting too long, we can use `@file` to read
Expand All @@ -120,7 +122,7 @@ endif

$(build_shlibdir)/libjulia-debug.$(JL_MAJOR_MINOR_SHLIB_EXT): $(LIB_DOBJS) $(SRCDIR)/list_strip_symbols.h | $(build_shlibdir) $(build_libdir)
@$(call PRINT_LINK, $(CC) $(call IMPLIB_FLAGS,$@.tmp) $(LOADER_CFLAGS) -DLIBRARY_EXPORTS -shared $(DEBUGFLAGS) $(LIB_DOBJS) -o $@ \
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(RPATH_LIB) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT)))
$(JLIBLDFLAGS) $(LOADER_LDFLAGS) $(call SONAME_FLAGS,libjulia-debug.$(JL_MAJOR_SHLIB_EXT)))
@$(INSTALL_NAME_CMD)libjulia-debug.$(SHLIB_EXT) $@
ifeq ($(OS), WINNT)
@$(call PRINT_ANALYZE, $(OBJCOPY) $(build_libdir)/$(notdir $@).tmp.a $(STRIP_EXPORTED_FUNCS) $(build_libdir)/$(notdir $@).a && rm $(build_libdir)/$(notdir $@).tmp.a)
Expand Down
214 changes: 207 additions & 7 deletions cli/loader_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ static int win_file_exists(wchar_t* wpath) {
#endif

// Save DEP_LIBS to a variable that is explicitly sized for expansion
static char dep_libs[1024] = DEP_LIBS;
static char dep_libs[1024] = "\0" DEP_LIBS;

JL_DLLEXPORT void jl_loader_print_stderr(const char * msg)
{
Expand All @@ -45,7 +45,6 @@ void jl_loader_print_stderr3(const char * msg1, const char * msg2, const char *
* and abort the process. */
static void * load_library(const char * rel_path, const char * src_dir, int err) {
void * handle = NULL;

// See if a handle is already open to the basename
const char *basename = rel_path + strlen(rel_path);
while (basename-- > rel_path)
Expand Down Expand Up @@ -167,6 +166,174 @@ JL_DLLEXPORT const char * jl_get_libdir()
return lib_dir;
}

// On Linux, it can happen that the system has a newer libstdc++ than the one we ship,
// which can break loading of some system libraries: <https://github.com/JuliaLang/julia/issues/34276>.
// As a fix, on linux we probe the system libstdc++ to see if it is newer, and then load it if it is.
apaz-cli marked this conversation as resolved.
Show resolved Hide resolved
// Otherwise, we load the bundled one. This improves compatibility with third party dynamic libs that
// may depend on symbols exported by the system libstdxc++.
#ifdef _OS_LINUX_
#ifndef GLIBCXX_LEAST_VERSION_SYMBOL
#warning GLIBCXX_LEAST_VERSION_SYMBOL should always be defined in the makefile.
apaz-cli marked this conversation as resolved.
Show resolved Hide resolved
#define GLIBCXX_LEAST_VERSION_SYMBOL "GLIBCXX_a.b.c" /* Appease the linter */
#endif

#include <link.h>
#include <sys/wait.h>

// write(), but handle errors and avoid EINTR
static void write_wrapper(int fd, const char *str, size_t len)
apaz-cli marked this conversation as resolved.
Show resolved Hide resolved
{
size_t written_sofar = 0;
while (len) {
ssize_t bytes_written = write(fd, str + written_sofar, len);
if (bytes_written == -1 && errno == EINTR) continue;
if (bytes_written == -1 && errno != EINTR) {
perror("(julia) child libstdcxxprobe write");
_exit(1);
}
len -= bytes_written;
written_sofar += bytes_written;
}
}

// read(), but handle errors and avoid EINTR
static void read_wrapper(int fd, char **ret, size_t *ret_len)
{
// Allocate an initial buffer
size_t len = JL_PATH_MAX;
char *buf = (char *)malloc(len + 1);
if (!buf) {
perror("(julia) malloc");
exit(1);
}

// Read into it, reallocating as necessary
size_t have_read = 0;
while (1) {
ssize_t n = read(fd, buf + have_read, len - have_read);
have_read += n;
if (n == 0) break;
if (n == -1 && errno != EINTR) {
perror("(julia) libstdcxxprobe read");
exit(1);
}
if (n == -1 && errno == EINTR) continue;
if (have_read == len) {
buf = (char *)realloc(buf, 1 + (len *= 2));
if (!buf) {
perror("(julia) realloc");
exit(1);
}
}
}

*ret = buf;
*ret_len = have_read;
}

// Return the path to the libstdcxx to load.
// If the path is found, return it.
// Otherwise, print the error and exit.
// The path returned must be freed.
static char *libstdcxxprobe(void)
{
// Create the pipe and child process.
int fork_pipe[2];
int ret = pipe(fork_pipe);
if (ret == -1) {
perror("(julia) Error during libstdcxxprobe: pipe");
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror("Error during libstdcxxprobe:\nfork");
exit(1);
}
if (pid == (pid_t) 0) { // Child process.
close(fork_pipe[0]);

// Open the first available libstdc++.so.
// If it can't be found, report so by exiting zero.
// The star is there to prevent the compiler from merging constants
// with "\0*libstdc++.so.6", which we string replace inside the .so during
// make install.
void *handle = dlopen("libstdc++.so.6\0*", RTLD_LAZY);
staticfloat marked this conversation as resolved.
Show resolved Hide resolved
if (!handle) {
_exit(0);
}

// See if the version is compatible
char *dlerr = dlerror(); // clear out dlerror
void *sym = dlsym(handle, GLIBCXX_LEAST_VERSION_SYMBOL);
dlerr = dlerror();
if (dlerr) {
// We can't use the library that was found, so don't write anything.
// The main process will see that nothing was written,
// then exit the function and return null.
_exit(0);
}

// No error means the symbol was found, we can use this library.
// Get the path to it, and write it to the parent process.
struct link_map *lm;
ret = dlinfo(handle, RTLD_DI_LINKMAP, &lm);
if (ret == -1) {
char *errbuf = dlerror();
char *errdesc = (char*)"Error during libstdcxxprobe in child process:\ndlinfo: ";
write_wrapper(STDERR_FILENO, errdesc, strlen(errdesc));
write_wrapper(STDERR_FILENO, errbuf, strlen(errbuf));
write_wrapper(STDERR_FILENO, "\n", 1);
_exit(1);
}
char *libpath = lm->l_name;
write_wrapper(fork_pipe[1], libpath, strlen(libpath));
_exit(0);
}
else { // Parent process.
close(fork_pipe[1]);

// Read the absolute path to the lib from the child process.
char *path;
size_t pathlen;
read_wrapper(fork_pipe[0], &path, &pathlen);

// Close the read end of the pipe
close(fork_pipe[0]);

// Wait for the child to complete.
staticfloat marked this conversation as resolved.
Show resolved Hide resolved
while (1) {
int wstatus;
pid_t npid = waitpid(pid, &wstatus, 0);
if (npid == -1) {
if (errno == EINTR) continue;
if (errno != EINTR) {
perror("Error during libstdcxxprobe in parent process:\nwaitpid");
exit(1);
}
}
else if (!WIFEXITED(wstatus)) {
const char *err_str = "Error during libstdcxxprobe in parent process:\n"
"The child process did not exit normally.\n";
size_t err_strlen = strlen(err_str);
write_wrapper(STDERR_FILENO, err_str, err_strlen);
exit(1);
}
else if (WEXITSTATUS(wstatus)) {
// The child has printed an error and exited, so the parent should exit too.
exit(1);
}
break;
staticfloat marked this conversation as resolved.
Show resolved Hide resolved
}

if (!pathlen) {
free(path);
return NULL;
}
return path;
}
}
#endif

void * libjulia_internal = NULL;
__attribute__((constructor)) void jl_load_libjulia_internal(void) {
// Only initialize this once
Expand All @@ -175,11 +342,43 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
}

// Introspect to find our own path
const char * lib_dir = jl_get_libdir();
const char *lib_dir = jl_get_libdir();

// Pre-load libraries that libjulia-internal needs.
int deps_len = strlen(dep_libs);
char * curr_dep = &dep_libs[0];
int deps_len = strlen(&dep_libs[1]);
char *curr_dep = &dep_libs[1];

void *cxx_handle;

#if defined(_OS_LINUX_)
int do_probe = 1;
int done_probe = 0;
char *probevar = getenv("JULIA_PROBE_LIBSTDCXX");
if (probevar) {
if (strcmp(probevar, "1") == 0 || strcmp(probevar, "yes") == 0)
do_probe = 1;
else if (strcmp(probevar, "0") == 0 || strcmp(probevar, "no") == 0)
do_probe = 0;
}
if (do_probe) {
char *cxxpath = libstdcxxprobe();
if (cxxpath) {
cxx_handle = dlopen(cxxpath, RTLD_LAZY);
char *dlr = dlerror();
if (dlr) {
jl_loader_print_stderr("ERROR: Unable to dlopen(cxxpath) in parent!\n");
jl_loader_print_stderr3("Message: ", dlr, "\n");
exit(1);
}
free(cxxpath);
done_probe = 1;
}
}
if (!done_probe) {
const static char bundled_path[256] = "\0*libstdc++.so.6";
load_library(&bundled_path[2], lib_dir, 1);
}
#endif

// We keep track of "special" libraries names (ones whose name is prefixed with `@`)
// which are libraries that we want to load in some special, custom way, such as
Expand All @@ -203,7 +402,8 @@ __attribute__((constructor)) void jl_load_libjulia_internal(void) {
}
special_library_names[special_idx] = curr_dep + 1;
special_idx += 1;
} else {
}
else {
load_library(curr_dep, lib_dir, 1);
}

Expand Down Expand Up @@ -292,7 +492,7 @@ JL_DLLEXPORT int jl_load_repl(int argc, char * argv[]) {
}

#ifdef _OS_WINDOWS_
int __stdcall DllMainCRTStartup(void* instance, unsigned reason, void* reserved) {
int __stdcall DllMainCRTStartup(void *instance, unsigned reason, void *reserved) {
setup_stdio();

// Because we override DllMainCRTStartup, we have to manually call our constructor methods
Expand Down
7 changes: 3 additions & 4 deletions deps/csl.mk
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ endef

# CSL bundles lots of system compiler libraries, and while it is quite bleeding-edge
# as compared to what most distros ship, if someone tries to build an older branch,
# the version of CSL that ships with that branch may become relatively old. This is
# not a problem for code that is built in BB, but when we build Julia with the system
# the version of CSL that ships with that branch may be relatively old. This is not
# a problem for code that is built in BB, but when we build Julia with the system
# compiler, that compiler uses the version of `libstdc++` that it is bundled with,
# and we can get linker errors when trying to run that `julia` executable with the
# and we can get linker errors when trying to run that `julia` executable with the
# `libstdc++` that comes from the (now old) BB-built CSL.
#
# To fix this, we take note when the system `libstdc++.so` is newer than whatever we
# would get from CSL (by searching for a `GLIBCXX_3.4.X` symbol that does not exist
# in our CSL, but would in a newer one), and default to `USE_BINARYBUILDER_CSL=0` in
# this case.
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a leftover comment which should probably be moved to Make.inc

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

@apaz-cli can you move this comment please?

Copy link
Sponsor Member

Choose a reason for hiding this comment

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

Actually, just copy it, since it makes sense in both places.

Copy link
Contributor

Choose a reason for hiding this comment

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

Huh, it seems the comment I edited above is derived from the comment here, but has unique typos?! What's going on there?

CSL_NEXT_GLIBCXX_VERSION=GLIBCXX_3\.4\.31|GLIBCXX_3\.5\.|GLIBCXX_4\.

# First, check to see if BB is disabled on a global setting
ifeq ($(USE_BINARYBUILDER),0)
Expand Down