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.
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.