<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrea Ricchi</title>
    <description>The latest articles on DEV Community by Andrea Ricchi (@andrearicchi).</description>
    <link>https://dev.to/andrearicchi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F239265%2F427ab642-55e0-42aa-9fa0-c9d91be056ca.JPG</url>
      <title>DEV Community: Andrea Ricchi</title>
      <link>https://dev.to/andrearicchi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrearicchi"/>
    <language>en</language>
    <item>
      <title>cxvm: per-project GCC and Clang versions on Linux</title>
      <dc:creator>Andrea Ricchi</dc:creator>
      <pubDate>Mon, 12 Oct 2026 14:02:24 +0000</pubDate>
      <link>https://dev.to/andrearicchi/cxvm-per-project-gcc-and-clang-versions-on-linux-3feg</link>
      <guid>https://dev.to/andrearicchi/cxvm-per-project-gcc-and-clang-versions-on-linux-3feg</guid>
      <description>&lt;p&gt;I work on embedded Linux. A lot of that work means building against a vendor BSP, and the cross toolchain in those BSPs is usually several GCC majors behind whatever my distro ships. Some builds want a host GCC with the same major version as that cross compiler. My laptop has GCC 15.&lt;/p&gt;

&lt;p&gt;The usual answers are to build GCC manually into &lt;code&gt;/opt&lt;/code&gt; and note somewhere which project needs it, or use &lt;code&gt;update-alternatives&lt;/code&gt;. The second one needs root and changes &lt;code&gt;/usr/bin/gcc&lt;/code&gt; for every project on the machine at once.&lt;/p&gt;

&lt;p&gt;Flutter developers have &lt;a href="https://fvm.app" rel="noopener noreferrer"&gt;FVM&lt;/a&gt; for this problem, Node has nvm, Python has pyenv. I wanted the same thing for native compilers, so I wrote &lt;strong&gt;cxvm&lt;/strong&gt;. Version 0.1.0 is out.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;cxvm keeps any number of GCC and Clang versions side by side under &lt;code&gt;~/.local/share/cxvm&lt;/code&gt;, and each project says which one it uses in a small file you commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cxvm &lt;span class="nb"&gt;install &lt;/span&gt;gcc@13      &lt;span class="c"&gt;# GCC 13.4.0, into ~/.local/share/cxvm&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;my-project
cxvm use gcc@13          &lt;span class="c"&gt;# pins gcc@13.4.0 in .cxvmrc, generates .cxvm/&lt;/span&gt;
cmake &lt;span class="nt"&gt;-S&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt; build/gcc-13.4.0 &lt;span class="nt"&gt;-DCMAKE_TOOLCHAIN_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.cxvm/toolchain.cmake
cmake &lt;span class="nt"&gt;--build&lt;/span&gt; build/gcc-13.4.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cxvm use&lt;/code&gt; writes &lt;code&gt;.cxvmrc&lt;/code&gt; and generates a &lt;code&gt;.cxvm/&lt;/code&gt; folder, which it adds to &lt;code&gt;.gitignore&lt;/code&gt;. A colleague who clones the project runs &lt;code&gt;cxvm install&lt;/code&gt; with no arguments and gets the same compiler.&lt;/p&gt;

&lt;p&gt;If your project doesn't use CMake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cxvm &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; make        &lt;span class="c"&gt;# PATH, CC and CXX set for one command&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;cxvm &lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;       &lt;span class="c"&gt;# the same for the current shell; works with direnv too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cxvm current&lt;/code&gt; tells you which compiler is active and why: an environment variable, the nearest &lt;code&gt;.cxvmrc&lt;/code&gt; walking up from the current directory, a global default, or the system &lt;code&gt;cc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design choices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Pins are exact
&lt;/h3&gt;

&lt;p&gt;Even when you type &lt;code&gt;gcc@13&lt;/code&gt;, &lt;code&gt;.cxvmrc&lt;/code&gt; stores &lt;code&gt;gcc@13.4.0&lt;/code&gt;. A pin that floats to whatever 13.x is newest isn't a pin.&lt;/p&gt;

&lt;h3&gt;
  
  
  Point, don't wrap
&lt;/h3&gt;

&lt;p&gt;Many version managers put shims on your &lt;code&gt;PATH&lt;/code&gt; that decide at runtime which binary to call. cxvm doesn't. The generated &lt;code&gt;.cxvm/toolchain.cmake&lt;/code&gt; and &lt;code&gt;.cxvm/env&lt;/code&gt; name the compilers by their real paths in the store, and &lt;code&gt;.cxvm/current&lt;/code&gt; is a symlink there for editors and for people.&lt;/p&gt;

&lt;p&gt;That makes the tool easy to stop using. If cxvm isn't on &lt;code&gt;PATH&lt;/code&gt;, or you uninstall it, configured builds keep working, because nothing in them refers to cxvm.&lt;/p&gt;

&lt;h3&gt;
  
  
  libstdc++ through rpath, not LD_LIBRARY_PATH
&lt;/h3&gt;

&lt;p&gt;This is the problem that bites everyone who installs a newer GCC by hand. You build with GCC 14 on a host whose system libstdc++ comes from GCC 13, run the program, and get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version `GLIBCXX_3.4.32' not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CMake toolchain file adds an rpath to the pinned GCC's &lt;code&gt;lib64&lt;/code&gt;, so the program finds the right &lt;code&gt;libstdc++.so.6&lt;/code&gt; on its own. cxvm deliberately never sets &lt;code&gt;LD_LIBRARY_PATH&lt;/code&gt;. It leaks into every child process and hides the problem until you run the binary somewhere else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clang with the libstdc++ you choose
&lt;/h3&gt;

&lt;p&gt;Clang doesn't ship a C++ standard library on Linux; it picks the newest GCC installation it finds on the host. An old Clang paired with a much newer libstdc++ often fails inside the standard headers. cxvm lets you pick the GCC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cxvm &lt;span class="nb"&gt;install &lt;/span&gt;clang@19
cxvm use clang@19 &lt;span class="nt"&gt;--gcc&lt;/span&gt; gcc@13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, that's &lt;code&gt;--gcc-install-dir&lt;/code&gt;, or &lt;code&gt;--gcc-toolchain&lt;/code&gt; for Clang older than 16.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verified, atomic installs
&lt;/h3&gt;

&lt;p&gt;The sha256 of every archive cxvm can download is compiled into the binary, together with a record of where each checksum came from. It checks each download against the stored checksum, unpacks it into a staging directory, smoke-tests it with a hello-world, and only then moves it into the store. An interrupted download resumes, and a half-unpacked toolchain never shows up as installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the compilers come from
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Clang&lt;/strong&gt;: LLVM's official release builds. Every release since 19.1.0, and the newest patch release of each major from 11 to 18.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCC&lt;/strong&gt;: the &lt;a href="https://xpack-dev-tools.github.io" rel="noopener noreferrer"&gt;xPack&lt;/a&gt; builds, 8.5.0 and 11.2.0 to 15.2.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCC from source&lt;/strong&gt;: &lt;code&gt;cxvm install gcc@16 --from-source&lt;/code&gt; downloads the GNU tarball and its GMP, MPFR, MPC and ISL, and builds it into the store. The catalogue covers every release since 7.1.0 plus the last of the 4.8, 4.9, 5 and 6 series. I've built 10.5.0 and 16.2.0 so far; the older ones are untested.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own&lt;/strong&gt;: &lt;code&gt;cxvm link /opt/gcc-11&lt;/code&gt; registers a compiler you already have, as &lt;code&gt;gcc@11.4.0-local&lt;/code&gt; for example.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it compares
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;update-alternatives&lt;/strong&gt; switches the system compiler between versions your distro packages. It needs root and affects everyone. On the other hand, distro packages get security updates; cxvm's toolchains don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;xPack with xpm&lt;/strong&gt; installs per-project toolchains from a &lt;code&gt;package.json&lt;/code&gt; and needs Node.js. cxvm uses the same GCC builds, adds LLVM's Clang and source builds, and generates the CMake and editor files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pixi&lt;/strong&gt; manages a whole conda-forge environment, libraries included, on Linux, macOS and Windows. If you need the libraries too, use pixi. cxvm builds against your host's C library and leaves everything else alone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nix&lt;/strong&gt; pins every tool and library exactly, at the cost of a &lt;code&gt;/nix&lt;/code&gt; store and its own way of building. cxvm is a much smaller step.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;It's 0.1.0, and the limits are real. It runs on x86_64 Linux only and manages native compilers, not cross toolchains. The release list is compiled into the binary, so a new GCC release needs a new cxvm release (or &lt;code&gt;CXVM_CATALOG&lt;/code&gt; pointing to a catalogue you generate). Some upstream builds have host quirks: LLVM 13.0.1 and 18.1.8 need &lt;code&gt;libtinfo.so.5&lt;/code&gt;, which Ubuntu 24.04 dropped, and GCC 10 from source needs &lt;code&gt;--configure-arg=--disable-libsanitizer&lt;/code&gt; on glibc 2.42 and later. The README FAQ lists the workarounds.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;cxvm is written in Rust and ships as one static musl binary. I wrote it with a lot of help from Claude Code. The design decisions were mine, and I tested it on real hosts myself: Ubuntu 26.04 and an Ubuntu 24.04 container for the older-glibc cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://github.com/AndreaRicchi/cxvm/releases/latest/download/install.sh | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script checks the sha256, puts &lt;code&gt;cxvm&lt;/code&gt; in &lt;code&gt;~/.local/bin&lt;/code&gt;, and doesn't touch your shell config. &lt;code&gt;cargo install cxvm --locked&lt;/code&gt; works too.&lt;/p&gt;

&lt;p&gt;The code is at &lt;a href="https://github.com/AndreaRicchi/cxvm" rel="noopener noreferrer"&gt;https://github.com/AndreaRicchi/cxvm&lt;/a&gt;, under MIT or Apache-2.0. I'd like to hear where it breaks: other distros, old GCC releases from source, workflows it doesn't cover. Issues are open.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>c</category>
      <category>linux</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
