plasma-umass/cwhy

Integrating into build systems / CI -- using a compiler wrapper

Closed this issue · 4 comments

Another way to integrate this nicely into CI / build systems is with a small wrapper around the compiler.

#!/usr/bin/env python3

import sys
import subprocess

CXX = "c++"

if __name__ == "__main__":
    process = subprocess.run(
        [CXX, *sys.argv[1:]],
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        text=True,
    )
    status = process.returncode
    compiler_output = process.stdout
    print(compiler_output)
    if status != 0:
        print("==================================================")
        print("CWhy")
        print("==================================================")
        process = subprocess.Popen(
            "cwhy",
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
        )
        cwhy_output, _ = process.communicate(compiler_output)
        print(cwhy_output)
        print("==================================================")
    sys.exit(status)

Trying this locally with the example added in 4741f89:

[~] CXX=~/Programming/plasma-umass/cwhy/c++why.py make
mkdir -p build
/home/nvankempen/Programming/plasma-umass/cwhy/c++why.py --std=c++20 -O3 -march=native -mtune=native -DNDEBUG -Wall -Wextra -Wpedantic -Werror  -o build/rapl rapl-read.cpp -lm
rapl-read.cpp: In function ‘std::string {anonymous}::trim(std::string)’:
rapl-read.cpp:67:10: error: no matching function for call to ‘std::__cxx11::basic_string<char>::erase(std::reverse_iterator<__gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> > >, std::__cxx11::basic_string<char>::iterator)’
   67 |   s.erase(std::find_if(s.rbegin(), s.rend(), isNotSpace), s.end());
      |   ~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /usr/include/c++/11/string:55,
                 from /usr/include/c++/11/bits/locale_classes.h:40,
                 from /usr/include/c++/11/bits/ios_base.h:41,
                 from /usr/include/c++/11/ios:42,
                 from /usr/include/c++/11/istream:38,
                 from /usr/include/c++/11/fstream:38,
                 from rapl-read.cpp:14:
/usr/include/c++/11/bits/basic_string.h:1827:7: note: candidate: ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>& std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::erase(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::size_type = long unsigned int]’
 1827 |       erase(size_type __pos = 0, size_type __n = npos)
      |       ^~~~~
/usr/include/c++/11/bits/basic_string.h:1827:23: note:   no known conversion for argument 1 from ‘std::reverse_iterator<__gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> > >’ to ‘std::__cxx11::basic_string<char>::size_type’ {aka ‘long unsigned int’}
 1827 |       erase(size_type __pos = 0, size_type __n = npos)
      |             ~~~~~~~~~~^~~~~~~~~
/usr/include/c++/11/bits/basic_string.h:1846:7: note: candidate: ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::iterator std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::erase(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__const_iterator) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::iterator = std::__cxx11::basic_string<char>::iterator; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__const_iterator = std::__cxx11::basic_string<char>::const_iterator]’
 1846 |       erase(__const_iterator __position)
      |       ^~~~~
/usr/include/c++/11/bits/basic_string.h:1846:7: note:   candidate expects 1 argument, 2 provided
/usr/include/c++/11/bits/basic_string.h:1865:7: note: candidate: ‘std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::iterator std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::erase(std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__const_iterator, std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__const_iterator) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::iterator = std::__cxx11::basic_string<char>::iterator; std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::__const_iterator = std::__cxx11::basic_string<char>::const_iterator]’
 1865 |       erase(__const_iterator __first, __const_iterator __last)
      |       ^~~~~
/usr/include/c++/11/bits/basic_string.h:1865:30: note:   no known conversion for argument 1 from ‘std::reverse_iterator<__gnu_cxx::__normal_iterator<char*, std::__cxx11::basic_string<char> > >’ to ‘std::__cxx11::basic_string<char>::__const_iterator’ {aka ‘std::__cxx11::basic_string<char>::const_iterator’}
 1865 |       erase(__const_iterator __first, __const_iterator __last)
      |             ~~~~~~~~~~~~~~~~~^~~~~~~

==================================================
CWhy
==================================================
The problem is that the `erase()` function called in `trim()` takes
iterators as arguments, but `std::find_if()` returns a reverse
iterator. Reverse iterators have to be converted to regular iterators
before being used with `erase()`. To do this, you can call the
`base()` function on the reverse iterator, which returns the
corresponding regular iterator. The `erase()` function can then be
called with these regular iterators. Here's how you can modify
`trim()` to fix the error:  ``` std::string trim(std::string s) {
static const auto isNotSpace = [](auto c) { return !std::isspace(c);
};   s.erase(s.begin(), std::find_if(s.begin(), s.end(), isNotSpace));
s.erase(std::find_if(s.rbegin(), s.rend(), isNotSpace).base(),
s.end());   return s; } ```

==================================================
make: *** [Makefile:11: build/rapl] Error 1

Added benefit: this would have the proper working directory for path resolution. Right now, running a test from the project root (with Clang 16, works with g++ 10), CWhy is unable to get the source code because the diagnostic doesn't include test/c++ in the paths.

This would also happen with a Makefile for example, where the working directory is changed before invoking the compiler.

vadi2 commented

Will this work with other existing wrappers like ccache?

I am not very familiar with ccache, but this should work just fine, wrapping either the short script above in ccache or the other way around. I think the easiest is to have ccache be the top-level wrapper, since the script essentially behaves exactly like the compiler would, with some extra output. I just gave this a try for the cmake project.

# Only for C++ files. If we also wanted C support, we should have another
# wrapper calling the C compiler and set the corresponding CMake variables.
[~] git clone https://gitlab.kitware.com/cmake/cmake.git
[cmake] cmake . -B build -G Ninja -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=$HOME/plasma-umass/cwhy/c++why
[cmake/build] ninja

I introduced a static_assert(false) in one of the C++ files, and the error was properly reported and put through CWhy.

Marking this as closed.
#11