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

/bin/(ba|c)?sh -> /usr/bin/env $1sh #25380

Closed
wants to merge 1 commit into from

Conversation

haampie
Copy link
Member

@haampie haampie commented Aug 12, 2021

how else can you use a shell built by spack

@@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
Copy link
Member

Choose a reason for hiding this comment

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

This might be the only place I hesitate to use /usr/bin/env. How much overhead does this result in? Build scripts call cc thousands of times, so if we have to search for bash in the PATH every time that could be slow.

Copy link
Member Author

Choose a reason for hiding this comment

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

maybe we should bootstrap a static binary executable to replace the bash script ^^. i was meaning to check what the overhead of the shell wrapper actually is

Copy link
Member

Choose a reason for hiding this comment

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

This might actually help us on windows -- where there is no bash. If you get numbers on this I'd be interested. But I also worry b/c ultimately not every Spack build will actually require a compiler, so we may not have one handy (unlikely, I know). But sure we could use the bootstrap logic and also provide binaries for something this simple.

On a similar note: we have talked about (for a while) generating compiler wrappers for each build, to avoid the symlink farm currently in lib/spack/env and the lengthy switch statement in lib/spack/env/cc. Then the names of the compilers would be completely up to the classes in lib/spack/spack/compilers, or, ultimately, to the compiler packages (when compilers become normal dependencies).

Copy link
Member

@tgamblin tgamblin left a comment

Choose a reason for hiding this comment

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

So, I kind of like the spirit of this, but what problem does it solve?

This reminds me of how we originally had bin/spack running with #!/usr/bin/env python, then finding a python[23]? from your environment, and then we finally introduced SPACK_PYTHON to nail things down so that broken environments don't break Spack.

IMO this introduces a bunch of potential sources of error -- for little gain. What's the benefit? Why change this now?

@@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env sh
Copy link
Member

Choose a reason for hiding this comment

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

sbang is vendored and while I don't see a reason why this won't work in general -- why do this? Is it just so that we can use a shell built by Spack?

@haampie
Copy link
Member Author

haampie commented Aug 17, 2021

but what problem does it solve?

Spack can't bootstrap bash on Alpine.

Optimally I'd like to be able to just download spack on any platform, run spack install xyz, and it just works, without having to worry about the system package manager.

@tgamblin
Copy link
Member

Spack can't bootstrap bash on Alpine.

Do you guys need Alpine? (not saying that's bad just curious - are you using it for lots of containers?)

Optimally I'd like to be able to just download spack on any platform, run spack install xyz, and it just works, without having to worry about the system package manager.

My inclination for this one would be to port cc to posix sh instead of doing this (mainly b/c I think this one is going to cause problems elsewhere). Are you up for that? Or did you try measuring the overhead of a C compiler wrapper?

Alternately if we do generate a cc per build we could give it the path to a spack bash right in the shebang (or sbang)

@haampie
Copy link
Member Author

haampie commented Aug 17, 2021

Do you guys need Alpine? (not saying that's bad just curious - are you using it for lots of containers?)

It's not super popular, but I've been using it with some success to build small static executables (so, building in a container, but the static executables can be used outside of it too).

My inclination for this one would be to port cc to posix sh instead of doing this (mainly b/c I think this one is going to cause problems elsewhere). Are you up for that? Or did you try measuring the overhead of a C compiler wrapper?

Yeah, I think this is sensible, we have to improve cc a bit anways to better reorder the -L/-I flags. Haven't measured the overhead of cc just yet, but that's on my todo list.

@haampie
Copy link
Member Author

haampie commented Aug 18, 2021

With the attached diff the "only" things to fix are arrays, read, heredocs, and some trivial stuff we can work around:

https://github.com/koalaman/shellcheck/wiki/SC3011
https://github.com/koalaman/shellcheck/wiki/SC3024
https://github.com/koalaman/shellcheck/wiki/SC3030
https://github.com/koalaman/shellcheck/wiki/SC3045
https://github.com/koalaman/shellcheck/wiki/SC3053
https://github.com/koalaman/shellcheck/wiki/SC3054

Instead of relying on arrays & environment variables & heredocs, we could generate the shell script from a template once ahead of time per package installation? A just-in-time compiler wrapper 😄 that generates explicit case statements to detect spack and system -I and -L flags to reorder them. (Or, crazy idea, generate a C file instead of a shell script and compile it with the user's compiler with all paths hard-coded in there)

diff --git a/lib/spack/env/cc b/lib/spack/env/cc
index 5157a5f381..160c301cc7 100755
--- a/lib/spack/env/cc
+++ b/lib/spack/env/cc
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 #
 # Copyright 2013-2021 Lawrence Livermore National Security, LLC and other
 # Spack Project Developers. See the top-level COPYRIGHT file for details.
@@ -60,7 +60,7 @@ parameters=(
 
 # die()
 # Prints a message and exits with error 1.
-function die {
+die() {
     echo "$@"
     exit 1
 }
@@ -78,10 +78,10 @@ IFS=' ' read -ra SPACK_LDFLAGS  <<< "$SPACK_LDFLAGS"
 IFS=' ' read -ra SPACK_LDLIBS   <<< "$SPACK_LDLIBS"
 
 # test whether a path is a system directory
-function system_dir {
+system_dir() {
     path="$1"
     for sd in "${SPACK_SYSTEM_DIRS[@]}"; do
-        if [ "${path}" == "${sd}" ] || [ "${path}" == "${sd}/" ]; then
+        if [ "${path}" = "${sd}" ] || [ "${path}" = "${sd}/" ]; then
             # success if path starts with a system prefix
             return 0
         fi
@@ -90,27 +90,27 @@ function system_dir {
 }
 
 for param in "${parameters[@]}"; do
-    if [[ -z ${!param+x} ]]; then
+    if [ -z ${!param+x} ]; then
         die "Spack compiler must be run from Spack! Input '$param' is missing."
     fi
 done
 
 # Check if optional parameters are defined
 # If we aren't asking for debug flags, don't add them
-if [[ -z ${SPACK_ADD_DEBUG_FLAGS+x} ]]; then
+if [ -z ${SPACK_ADD_DEBUG_FLAGS+x} ]; then
     SPACK_ADD_DEBUG_FLAGS="false"
 fi
 
 # SPACK_ADD_DEBUG_FLAGS must be true/false/custom
 is_valid="false"
 for param in "true" "false" "custom"; do
-  if [ "$param" == "$SPACK_ADD_DEBUG_FLAGS" ];  then
+  if [ "$param" = "$SPACK_ADD_DEBUG_FLAGS" ];  then
       is_valid="true"
   fi
 done
 
 # Exit with error if we are given an incorrect value
-if [ "$is_valid" == "false" ]; then
+if [ "$is_valid" = "false" ]; then
     die "SPACK_ADD_DEBUG_FLAGS, if defined, must be one of 'true' 'false' or 'custom'"
 fi
 
@@ -174,9 +174,9 @@ esac
 # If any of the arguments below are present, then the mode is vcheck.
 # In vcheck mode, nothing is added in terms of extra search paths or
 # libraries.
-if [[ -z $mode ]] || [[ $mode == ld ]]; then
+if [ -z "$mode" ] || [ "$mode" = ld ]; then
     for arg in "$@"; do
-        case $arg in
+        case "$arg" in
             -v|-V|--version|-dumpversion)
                 mode=vcheck
                 break
@@ -186,16 +186,16 @@ if [[ -z $mode ]] || [[ $mode == ld ]]; then
 fi
 
 # Finish setting up the mode.
-if [[ -z $mode ]]; then
+if [ -z "$mode" ]; then
     mode=ccld
     for arg in "$@"; do
-        if [[ $arg == -E ]]; then
+        if [ "$arg" = -E ]; then
             mode=cpp
             break
-        elif [[ $arg == -S ]]; then
+        elif [ "$arg" = -S ]; then
             mode=as
             break
-        elif [[ $arg == -c ]]; then
+        elif [ "$arg" = -c ]; then
             mode=cc
             break
         fi
@@ -225,14 +225,14 @@ linker_arg="${SPACK_LINKER_ARG}"
 eval rpath=\$SPACK_${comp}_RPATH_ARG
 
 # Dump the mode and exit if the command is dump-mode.
-if [[ $SPACK_TEST_COMMAND == dump-mode ]]; then
+if [ "$SPACK_TEST_COMMAND" = dump-mode ]; then
     echo "$mode"
     exit
 fi
 
 # Check that at least one of the real commands was actually selected,
 # otherwise we don't know what to execute.
-if [[ -z $command ]]; then
+if [ -z "$command" ]; then
     die "ERROR: Compiler '$SPACK_COMPILER_SPEC' does not support compiling $language programs."
 fi
 
@@ -247,7 +247,7 @@ export PATH=""
 for dir in "${env_path[@]}"; do
     addpath=true
     for env_dir in "${spack_env_dirs[@]}"; do
-        if [[ "$dir" == "$env_dir" ]]; then
+        if [ "$dir" = "$env_dir" ]; then
             addpath=false
             break
         fi
@@ -257,7 +257,7 @@ for dir in "${env_path[@]}"; do
     fi
 done
 
-if [[ $mode == vcheck ]]; then
+if [ "$mode" = vcheck ]; then
     exec "${command}" "$@"
 fi
 
@@ -265,16 +265,20 @@ fi
 # It doesn't work with -rpath.
 # This variable controls whether they are added.
 add_rpaths=true
-if [[ ($mode == ld || $mode == ccld) && "$SPACK_SHORT_SPEC" =~ "darwin" ]];
+if [ "$mode" = ld ] || [ "$mode" = ccld ];
 then
-    for arg in "$@"; do
-        if [[ ($arg == -r && $mode == ld) ||
-              ($arg == -r && $mode == ccld) ||
-              ($arg == -Wl,-r && $mode == ccld) ]]; then
-            add_rpaths=false
-            break
-        fi
-    done
+    case "$SPACK_SHORT_SPEC" in
+        *darwin*)
+            for arg in "$@"; do
+                if { [ "$arg" = -r ] && [ "$mode" = ld ];   } ||
+                { [ "$arg" = -r ] && [ "$mode" = ccld ]; } ||
+                { [ "$arg" = -Wl,-r ] && [ "$mode" = ccld ]; }; then
+                    add_rpaths=false
+                    break
+                fi
+            done
+            ;;
+    esac
 fi
 
 # Save original command for debug logging
@@ -368,58 +372,71 @@ while [ $# -ne 0 ]; do
         -Wl,*)
             arg="${1#-Wl,}"
             if [ -z "$arg" ]; then shift; arg="$1"; fi
-            if [[ "$arg" = -rpath=* ]]; then
-                rp="${arg#-rpath=}"
-            elif [[ "$arg" = --rpath=* ]]; then
-                rp="${arg#--rpath=}"
-            elif [[ "$arg" = -rpath,* ]]; then
-                rp="${arg#-rpath,}"
-            elif [[ "$arg" = --rpath,* ]]; then
-                rp="${arg#--rpath,}"
-            elif [[ "$arg" =~ ^-?-rpath$ ]]; then
-                shift; arg="$1"
-                if [[ "$arg" != -Wl,* ]]; then
-                    die "-Wl,-rpath was not followed by -Wl,*"
-                fi
-                rp="${arg#-Wl,}"
-            elif [[ "$arg" = "$dtags_to_strip" ]] ; then
-                :  # We want to remove explicitly this flag
-            else
-                other_args+=("-Wl,$arg")
-            fi
+            case "$arg" in
+                -rpath=*)
+                    rp="${arg#-rpath=}"
+                    ;;
+                --rpath=*)
+                    rp="${arg#--rpath=}"
+                    ;;
+                -rpath,*)
+                    rp="${arg#-rpath,}"
+                    ;;
+                --rpath,*)
+                    rp="${arg#--rpath,}"
+                    ;;
+                ^-?-rpath$)
+                    shift; arg="$1"
+                    case "$arg" in
+                        -Wl,*) die "-Wl,-rpath was not followed by -Wl,*" ;;
+                    esac
+                    rp="${arg#-Wl,}"
+                    ;;
+                "$dtags_to_strip")
+                    :  # We want to remove explicitly this flag
+                    ;;
+                *)
+                    other_args+=("-Wl,$arg")
+                    ;;
+            esac
             ;;
         -Xlinker,*)
             arg="${1#-Xlinker,}"
             if [ -z "$arg" ]; then shift; arg="$1"; fi
-            if [[ "$arg" = -rpath=* ]]; then
-                rp="${arg#-rpath=}"
-            elif [[ "$arg" = --rpath=* ]]; then
-                rp="${arg#--rpath=}"
-            elif [[ "$arg" = -rpath  ]] || [[ "$arg" = --rpath ]]; then
-                shift; arg="$1"
-                if [[ "$arg" != -Xlinker,* ]]; then
-                    die "-Xlinker,-rpath was not followed by -Xlinker,*"
-                fi
-                rp="${arg#-Xlinker,}"
-            else
-                other_args+=("-Xlinker,$arg")
-            fi
+            case "$arg" in
+                -rpath=*)
+                    rp="${arg#-rpath=}"
+                    ;;
+                --rpath=*)
+                    rp="${arg#--rpath=}"
+                    ;;
+                -rpath|--rpath)
+                    shift; arg="$1"
+                    case "$arg" in
+                        -Xlinker,*) die "-Xlinker,-rpath was not followed by -Xlinker,*" ;;
+                    esac
+                    rp="${arg#-Xlinker,}"
+                    ;;
+                *)
+                    other_args+=("-Xlinker,$arg")
+                    ;;            
+            esac
             ;;
         -Xlinker)
-            if [[ "$2" == "-rpath" ]]; then
-                if [[ "$3" != "-Xlinker" ]]; then
+            if [ "$2" = "-rpath" ]; then
+                if [ "$3" != "-Xlinker" ]; then
                     die "-Xlinker,-rpath was not followed by -Xlinker,*"
                 fi
                 shift 3;
                 rp="$1"
-            elif [[ "$2" = "$dtags_to_strip" ]] ; then
+            elif [ "$2" = "$dtags_to_strip" ] ; then
                 shift  # We want to remove explicitly this flag
             else
                 other_args+=("$1")
             fi
             ;;
         *)
-            if [[ "$1" = "$dtags_to_strip" ]] ; then
+            if [ "$1" = "$dtags_to_strip" ] ; then
                 :  # We want to remove explicitly this flag
             else
                 other_args+=("$1")
@@ -448,11 +465,11 @@ done
 flags=()
 
 # Add debug flags
-if [ "${SPACK_ADD_DEBUG_FLAGS}" == "true" ]; then
+if [ "${SPACK_ADD_DEBUG_FLAGS}" = "true" ]; then
     flags=("${flags[@]}" "${debug_flags}")
 
 # If a custom flag is requested, derive from environment
-elif [ "$SPACK_ADD_DEBUG_FLAGS" == "custom" ]; then
+elif [ "$SPACK_ADD_DEBUG_FLAGS" = "custom" ]; then
     IFS=' ' read -ra SPACK_DEBUG_FLAGS <<< "$SPACK_DEBUG_FLAGS"
     flags=("${flags[@]}" "${SPACK_DEBUG_FLAGS[@]}")
 fi
@@ -494,20 +511,24 @@ case "$mode" in
 esac
 
 # On macOS insert headerpad_max_install_names linker flag
-if [[ ($mode == ld || $mode == ccld) && "$SPACK_SHORT_SPEC" =~ "darwin" ]];
+if [ "$mode" = ld ] || [ "$mode" = ccld ];
 then
-    case "$mode" in
-        ld)
-            flags=("${flags[@]}" -headerpad_max_install_names) ;;
-        ccld)
-            flags=("${flags[@]}" "-Wl,-headerpad_max_install_names") ;;
+    case "$SPACK_SHORT_SPEC" in
+        *darwin*)
+            case "$mode" in
+                ld)
+                    flags=("${flags[@]}" -headerpad_max_install_names) ;;
+                ccld)
+                    flags=("${flags[@]}" "-Wl,-headerpad_max_install_names") ;;
+            esac
+        ;;
     esac
 fi
 
 IFS=':' read -ra rpath_dirs <<< "$SPACK_RPATH_DIRS"
-if [[ $mode == ccld || $mode == ld ]]; then
+if [ "$mode" = ccld ] || [ "$mode" = ld ]; then
 
-    if [[ "$add_rpaths" != "false" ]] ; then
+    if [ "$add_rpaths" != "false" ] ; then
         # Append RPATH directories. Note that in the case of the
         # top-level package these directories may not exist yet. For dependencies
         # it is assumed that paths have already been confirmed.
@@ -517,7 +538,7 @@ if [[ $mode == ccld || $mode == ld ]]; then
 fi
 
 IFS=':' read -ra link_dirs <<< "$SPACK_LINK_DIRS"
-if [[ $mode == ccld || $mode == ld ]]; then
+if [ "$mode" = ccld ] || [ "$mode" = ld ]; then
     libdirs=("${libdirs[@]}" "${link_dirs[@]}")
 fi
 
@@ -527,13 +548,13 @@ case "$mode" in
         # Set extra RPATHs
         IFS=':' read -ra extra_rpaths <<< "$SPACK_COMPILER_EXTRA_RPATHS"
         libdirs+=("${extra_rpaths[@]}")
-        if [[ "$add_rpaths" != "false" ]] ; then
+        if [ "$add_rpaths" != "false" ] ; then
             rpaths+=("${extra_rpaths[@]}")
         fi
 
         # Set implicit RPATHs
         IFS=':' read -ra implicit_rpaths <<< "$SPACK_COMPILER_IMPLICIT_RPATHS"
-        if [[ "$add_rpaths" != "false" ]] ; then
+        if [ "$add_rpaths" != "false" ] ; then
             rpaths+=("${implicit_rpaths[@]}")
         fi
 
@@ -560,8 +581,8 @@ for dir in "${includes[@]}";         do args+=("-I$dir"); done
 for dir in "${isystem_includes[@]}";         do args+=("-isystem" "$dir"); done
 
 IFS=':' read -ra spack_include_dirs <<< "$SPACK_INCLUDE_DIRS"
-if [[ $mode == cpp || $mode == cc || $mode == as || $mode == ccld ]]; then
-    if [[ "$isystem_was_used" == "true" ]] ; then
+if [ "$mode" = cpp ] || [ "$mode" = cc ] || [ "$mode" = as ] || [ "$mode" = ccld ]; then
+    if [ "$isystem_was_used" = "true" ] ; then
         for dir in "${spack_include_dirs[@]}";  do args+=("-isystem" "$dir"); done
     else
         for dir in "${spack_include_dirs[@]}";  do args+=("-I$dir"); done
@@ -612,18 +633,18 @@ if [ -n "$SPACK_CCACHE_BINARY" ]; then
 fi
 
 # dump the full command if the caller supplies SPACK_TEST_COMMAND=dump-args
-if [[ $SPACK_TEST_COMMAND == dump-args ]]; then
+if [ "$SPACK_TEST_COMMAND" = dump-args ]; then
     IFS="
 " && echo "${full_command[*]}"
     exit
-elif [[ -n $SPACK_TEST_COMMAND ]]; then
+elif [ -n "$SPACK_TEST_COMMAND" ]; then
     die "ERROR: Unknown test command"
 fi
 
 #
 # Write the input and output commands to debug logs if it's asked for.
 #
-if [[ $SPACK_DEBUG == TRUE ]]; then
+if [ "$SPACK_DEBUG" = TRUE ]; then
     input_log="$SPACK_DEBUG_LOG_DIR/spack-cc-$SPACK_DEBUG_LOG_ID.in.log"
     output_log="$SPACK_DEBUG_LOG_DIR/spack-cc-$SPACK_DEBUG_LOG_ID.out.log"
     echo "[$mode] $command $input_command" >> "$input_log"

@haampie
Copy link
Member Author

haampie commented Aug 18, 2021

About the overhead of the compiler wrapper, I think an interesting benchmark is the configure script of gettext, since gettext spends more than a minute on it.

Using the following diff

diff --git a/lib/spack/spack/build_systems/autotools.py b/lib/spack/spack/build_systems/autotools.py
index 03923e3e51..209a58295c 100644
--- a/lib/spack/spack/build_systems/autotools.py
+++ b/lib/spack/spack/build_systems/autotools.py
@@ -339,7 +339,11 @@ def configure(self, spec, prefix):
         options += self.configure_args()
 
         with working_dir(self.build_directory, create=True):
+            import time
+            start = time.time()
             inspect.getmodule(self).configure(*options)
+            end = time.time()
+            print("Time = ", end - start)
 
     def build(self, spec, prefix):
         """Makes the build targets specified by

and the following environment:

spack:
  config:
    ccache: false
    install_tree:
      root: ./here
  specs:
  - gettext
  view: false

When I do spack -e . install -vu configure the time is:

Time =  75.01701498031616

Then when I remove the compiler wrapper:

$ cd ~/spack/lib/spack/env/
$ rm -rf *
$ mkdir gcc
$ ln -s /usr/bin/gcc-10 gcc/gcc
$ ln -s /usr/bin/g++-10 gcc/g++

And rerun the spack -e . install -vu configure benchmark, I get:

Time =  64.67220401763916

So, the overhead of the compiler wrapper is not particularly negligible; 16% more time spent on the gettext configure phase. I got similar results for spack install mpc, somewhere between 15-20% increase in compilation / configure time.

@tgamblin
Copy link
Member

@haampie can you put up a PR for the initial diff you've got there? We can start with that and work on the rest of the posix conversion from there.

We've been talking about how to do the compiler wrappers for the windows port -- so we've given it some thought and settled on still doing shell instead of compiling... the rationale was, basically:

  1. git requires bash to work on windows anyway. So we will almost certainly have it, and we’ll want to make that self-hosted eventually, anyway.
  2. Easier to maintain and debug scripts
  3. Binaries will require either binary bootstrapping (maintenance) or a C compiler (might not have it for a Fortran or other language-only build)
  4. We want to generate wrappers for each build and persist them with the build directory so that you can run the build outside spack. This is easier with scripts, especially when you consider that we’d need the binaries to take a build-specific config file vs. copying and patching an sh script.

So, it just seems simpler esp. if we need bash for git and have to bootstrap that anyway. And bootstrapping bash or posix sh (via, say, busybox or some spack build) seems simpler in any scenario than bootstrapping the wrappers themselves for every build.

The fact that git comes with bash, and azure VMs ship with git, git bash, and WSL sort of makes me think that the Unix dev environment is pretty much the only real CLI dev environment these days, so we should work on making spack self-host that instead of working really hard to be pure windows, since windows itself seems to be going the other way (towards Linuxness).

Thoughts?

@tgamblin
Copy link
Member

Based on some numbers from @adamjstewart, I think we could reclaim 40% or so of that overhead with dash, though I wonder how much is startup overhead and how much is just the string processing.

@adamjstewart
Copy link
Member

See #8703 for those numbers.

@tgamblin
Copy link
Member

I put up an initial draft PR here: #25557.

@alalazo
Copy link
Member

alalazo commented Aug 23, 2021

We want to generate wrappers for each build and persist them with the build directory so that you can run the build outside spack. This is easier with scripts, especially when you consider that we’d need the binaries to take a build-specific config file vs. copying and patching an sh script.

This one is also good for compiler as dependencies, since in this way each package providing a language will be responsible to create the wrappers for that language in the build directory of the dependent - and all compiled languages will be treated equally.

@christoph-conrads
Copy link
Contributor

Do you guys need Alpine? (not saying that's bad just curious - are you using it for lots of containers?)

My team has Alpine Linux containers for testing Melissa. The three main reasons for Alpine Linux (containers) are

  • musl libc instead of GNU C library to make sure the code relies only on POSIX features,
  • very recent packages in comparison to, say, Debian/Devuan or CentOS,
  • available on every instruction set architecture,
  • smaller and faster than other distributions (bonus but not a reason to switch to Alpine Linux images).

Disadvantages:

  • No GNU C library; some software won't build, the address sanitizer is not available.
  • The GNU C library can also be forced to be POSIX-compliant by setting the environment flag POSIXLY_CORRECT=1.

@tgamblin
Copy link
Member

Closing this as we decided to convert the compiler wrappers to posix sh. See #26048.

@tgamblin tgamblin closed this Sep 19, 2021
@haampie
Copy link
Member Author

haampie commented Sep 19, 2021

it's touching more than the compiler wrapper, but sure :p

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

5 participants