<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Gcc on kistu</title>
  <link rel="alternate" href="/tags/gcc/" />
  <link rel="self" href="/tags/gcc/index.xml" />
  <subtitle>Recent content in Gcc on kistu</subtitle>
  <id>/tags/gcc/</id>
  <generator uri="http://gohugo.io" version="0.124.0">Hugo</generator>
  <language>en</language>
  <updated>2025-11-28T00:15:11+02:00</updated>
  <author>
    
    
  </author>
  
      <entry>
        <title>Packaging Qt Applications for Linux</title>
        <link rel="alternate" href="/posts/002-packaging-qt-applications/" />
        <id>/posts/002-packaging-qt-applications/</id>
        <published>2025-11-28T00:15:11+02:00</published>
        <updated>2026-03-26T20:51:04+02:00</updated>
        <summary type="html">Rhythmbox-Reloaded is a C++ application that uses Qt for the user interface. An executable that anyone can use to run the application needs to be created. For Windows and Apple systems, there is little variation in operating system structure so simply building the application on that platform is sufficient. This is not the case for Linux with its many distributions and variations.
Single Distribution The first step is to get an executable for the developer machine.</summary>
          <content type="html"><![CDATA[<p><a href="https://git.kistu.dev/kistu/rhythmbox-reloaded-desktop">Rhythmbox-Reloaded</a> is a C++ application that uses Qt for the user interface. An executable that anyone can use to run the application needs to be created. For Windows and Apple systems, there is little variation in operating system structure so simply building the application on that platform is sufficient. This is not the case for Linux with its many distributions and variations.</p>
<h1 id="single-distribution">Single Distribution</h1>
<p>The first step is to get an executable for the developer machine. This is quite simple - it can be done in the Qt Creator IDE or using the terminal with <code>qmake</code> and <code>make</code>. <code>qmake</code> generates a Makefile based on the contents of a project file.</p>
<p>The specifics of the project file aren&rsquo;t important; this file is created automatically by QtCreator and defines a few configurations (compiler versions and required Qt modules) as well as files to include and compile. Here is a minimal example of a project file <code>Application.pro</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">QT       += core gui
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">CONFIG += c++17
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">SOURCES += \
</span></span><span class="line"><span class="cl">    application-src/main.cpp \
</span></span><span class="line"><span class="cl">    application-src/mainwindow.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">HEADERS += \
</span></span><span class="line"><span class="cl">    application-headers/mainwindow.h
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">FORMS += \
</span></span><span class="line"><span class="cl">    application-forms/mainwindow.ui
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">INCLUDEPATH += application-headers/
</span></span><span class="line"><span class="cl">INCLUDEPATH += application-src/
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">RESOURCES += \
</span></span><span class="line"><span class="cl">    application-styling/application-styling.qrc
</span></span></code></pre></div><p><code>qmake</code> reads this file and generates a Makefile:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="c">#############################################################################
</span></span></span><span class="line"><span class="cl"><span class="c"># Generated by qmake (3.1) (Qt 6.2.4)
</span></span></span><span class="line"><span class="cl"><span class="c">#############################################################################
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="c">####### Compiler, tools and options
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nv">CC</span>            <span class="o">=</span> gcc
</span></span><span class="line"><span class="cl"><span class="nv">CXX</span>           <span class="o">=</span> g++
</span></span><span class="line"><span class="cl"><span class="nv">MKDIR</span>         <span class="o">=</span> mkdir -p
</span></span><span class="line"><span class="cl"><span class="nv">COPY</span>          <span class="o">=</span> cp -f
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Output directory
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nv">OBJECTS_DIR</span>   <span class="o">=</span> ./
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Files
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nv">SOURCES</span>       <span class="o">=</span> ../application-src/main.cpp <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		../application-src/mainwindow.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">OBJECTS</span>       <span class="o">=</span> main.o <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		mainwindow.o 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">DIST</span>          <span class="o">=</span> /usr/lib/x86_64-linux-gnu/qt6/mkspecs/features/spec_pre.prf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		/usr/lib/x86_64-linux-gnu/qt6/mkspecs/common/unix.conf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		/usr/lib/x86_64-linux-gnu/qt6/mkspecs/common/linux.conf <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		../application-src/main.cpp <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>		../application-src/mainwindow.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Build rules
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">Application</span><span class="o">:</span> <span class="n">ui_mainwindow</span>.<span class="n">h</span> <span class="k">$(</span><span class="nv">OBJECTS</span><span class="k">)</span>  
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>LINK<span class="k">)</span> <span class="k">$(</span>LFLAGS<span class="k">)</span> -o <span class="k">$(</span>TARGET<span class="k">)</span> <span class="k">$(</span>OBJECTS<span class="k">)</span> <span class="k">$(</span>OBJCOMP<span class="k">)</span> <span class="k">$(</span>LIBS<span class="k">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Sub-libraries
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">mocclean</span><span class="o">:</span> <span class="n">compiler_moc_header_clean</span> <span class="n">compiler_moc_objc_header_clean</span> <span class="n">compiler_moc_source_clean</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Compile
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">main.o</span><span class="o">:</span> ../<span class="n">application</span>-<span class="n">src</span>/<span class="n">main</span>.<span class="n">cpp</span> ../<span class="n">application</span>-<span class="n">headers</span>/<span class="n">mainwindow</span>.<span class="n">h</span>
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>CXX<span class="k">)</span> -c <span class="k">$(</span>CXXFLAGS<span class="k">)</span> <span class="k">$(</span>INCPATH<span class="k">)</span> -o main.o ../application-src/main.cpp
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c">####### Install
</span></span></span><span class="line"><span class="cl"><span class="c"></span>
</span></span><span class="line"><span class="cl"><span class="nf">install_target</span><span class="o">:</span> <span class="n">first</span> <span class="n">FORCE</span>
</span></span><span class="line"><span class="cl">	@test -d <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin <span class="o">||</span> mkdir -p <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>QINSTALL_PROGRAM<span class="k">)</span> <span class="k">$(</span>QMAKE_TARGET<span class="k">)</span> <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin/<span class="k">$(</span>QMAKE_TARGET<span class="k">)</span>
</span></span><span class="line"><span class="cl">	-<span class="k">$(</span>STRIP<span class="k">)</span> <span class="k">$(</span>INSTALL_ROOT<span class="k">)</span>/opt/Application/bin/<span class="k">$(</span>QMAKE_TARGET<span class="k">)</span>
</span></span></code></pre></div><p>Running <code>make</code> uses the GNU Compiler Collection to generate an <code>elf</code> binary that can be executed.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-0aea2" hidden>
    <label for="zoomCheck-0aea2">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="compilation-chain.svg" alt="compilation-chain" 
             />
    </label>

</p>
<h1 id="multiple-distributions">Multiple Distributions</h1>
<p>A critical limitation of the executable generated above is that it depends on certain libraries in a way that makes it distribution dependent.</p>
<h2 id="discovering-the-issue">Discovering the Issue</h2>
<p>The most likely way this issue would be discovered is by running the application on different distributions - typically a given with even with a small user base. However, for this particular project, the issue was intrestingly identified in the CI/CD pipeline.</p>
<p>You may wish to automate the building and publishing of the application when the source code is pushed to remote git repository. Git hosting services often include an &ldquo;Actions&rdquo; feature that can use docker to achieve this. A minimal image that is often chosen is Alpine Linux. Required packages are installed with the Alpine package manager <code>apk</code> and the process for building the application outlined above is followed.</p>
<p>To test the generated binary, run it on the development machine. In this case, it happens to be Arch Linux - an operating system that is organised differently to Alpine Linux. Running the binary results in an error: <code>Error: file not found</code>.</p>
<p>To understand the cause of this error, a better understanding is required of how Make and the GNU compiler works.</p>
<h2 id="compilation-and-linking-process">Compilation and Linking Process</h2>
<p>The compiler&rsquo;s actions can be divided into 4 stages handled by different components.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-179d2" hidden>
    <label for="zoomCheck-179d2">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="compiler-components.svg" alt="compiler components" 
             />
    </label>

</p>
<table>
<thead>
<tr>
<th>Component</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td>Preprocessor</td>
<td>expand or resolve lines starting with the directive (&rsquo;#')</td>
</tr>
<tr>
<td>Compiler</td>
<td>translates source code to assembly code</td>
</tr>
<tr>
<td>Assembler</td>
<td>translates assembly code to object code</td>
</tr>
<tr>
<td>Linker</td>
<td>combines multiple object files and links external libraries</td>
</tr>
</tbody>
</table>
<p>Examining the output of each stage gives a better insight into the process.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-ec13b" hidden>
    <label for="zoomCheck-ec13b">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="compiler-stages-with-output.svg" alt="compiler stages with output" 
             />
    </label>

</p>
<p>The component of importance here is the linker.</p>
<h3 id="dynamic-linking">Dynamic Linking</h3>
<p>Dynamic link object files are identified by the <code>.so</code> for &ldquo;shared object&rdquo; extension on Linux and <code>.dll</code> for &ldquo;dynamically linked library&rdquo; on Windows.</p>
<p>When linking, the symbols of the object file is added instead of the entire file itself.</p>
<p>The following command lists all the symbols in the libcurl library:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">objdump -T /usr/lib/libcurl.so
</span></span></code></pre></div><p>During runtime, missing symbols are resolved by the system. This makes the application more resource efficient and means changes to shared objects can be easily propagated. The comes at the cost of comptatibility since more assumptions are made about the system - such as having the shared object in an accessible path.</p>
<h3 id="static-linking">Static Linking</h3>
<p>All object files are stored directly in the resulting executable. Statically linked libraries are identified by an <code>.a</code> for &ldquo;archive&rdquo; extension.</p>
<p>Static linking results in better compatibility since it minimizes external dependencies. However, is increases resource consumption and makes it more difficult to update.</p>
<p>Static linking Qt is not trivial. The official installation only includes dynamic libraries and static linking may potentially face licensing restrictions. Static linking requires building Qt from source and additional configuration.</p>
<h2 id="packaging-with-appimage">Packaging with AppImage</h2>
<p>AppImages takes a different approach. The executable binary is generated using the same process outlined before, but this is then packaged with most required files to run with minimal external dependencies.</p>
<p>It should be noted that AppImages do still have some dependencies, but these are likely fulfilled by default by your distribution. There are some exceptions; Filesystem in Userspace (FUSE), which allows non-root users to mount filesystems and is required for the AppImage to work, is not bundled with older AppImages and may need to be installed explicitly.</p>
<p>If this is your first time packaging an application with AppImage, getting it to work may take time. After much trial and error, it seems that packaging with AppImage has two requirements: a compliant <code>AppDir</code> and a specific version of <code>glibc</code>.</p>
<h3 id="appdir-structure">AppDir Structure</h3>
<p>Creating the <code>AppDir</code> is quite simple and can be done manually; this is how it is organized:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">.
</span></span><span class="line"><span class="cl">└── AppDir
</span></span><span class="line"><span class="cl">    └── usr
</span></span><span class="line"><span class="cl">        ├── bin
</span></span><span class="line"><span class="cl">        │   └── app-name.elf
</span></span><span class="line"><span class="cl">        └── share
</span></span><span class="line"><span class="cl">            ├── applications
</span></span><span class="line"><span class="cl">            │   └── app-name.desktop
</span></span><span class="line"><span class="cl">            └── icons
</span></span><span class="line"><span class="cl">                └── hicolor
</span></span><span class="line"><span class="cl">                    └── 256x256
</span></span><span class="line"><span class="cl">                        └── apps
</span></span><span class="line"><span class="cl">                            └── app-name.svg 
</span></span></code></pre></div><p><code>app-name.svg</code> is an image file that will be displayed as the application&rsquo;s icon. <code>app-name.elf</code> is the binary that was generated.</p>
<p><code>app-name.desktop</code> is a desktop entry file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Type=Application
</span></span><span class="line"><span class="cl">Exec=app-name
</span></span><span class="line"><span class="cl">Name=app-name
</span></span><span class="line"><span class="cl">Icon=app-name
</span></span><span class="line"><span class="cl">Categories=Utility;
</span></span></code></pre></div><blockquote>
<p>Note that the file extension is excluded in the entries for <code>Exec</code> and <code>Icon</code>.</p>
</blockquote>
<h3 id="glibc">glibc</h3>
<p><code>glibc</code> is a standard C library that provides many common and important functions. The <code>glibc</code> contraint is a little more nuanced.</p>
<blockquote>
<p>To produce binaries that are compatible with many target systems, build on the oldest still-supported build system.</p>
</blockquote>
<p><a href="https://github.com/probonopd/linuxdeployqt">linuxdeployqt</a> - the chosen packaging tool - recommends Ubuntu and enforces this system constraint quite strictly to encourage developers to support the most platforms.</p>
<p>The initial attempt on Alpine Linux failed because the glibc version was too new. From the documentation, it wasn&rsquo;t entirely clear if &ldquo;still-supported&rdquo; referred to the &ldquo;end of standard support&rdquo; or &ldquo;end of life&rdquo;. Ubuntu 14.04 was the oldest system that had not reached end of life. Not only did the system have network issues (there was a bug with libcurl3 that made TLS connections unusable), but the application didn&rsquo;t even compile. Sections of the source code needed to be modified because the <code>glibc</code> version was too old and didn&rsquo;t recognise some of the newer syntax.</p>
<p>At this stage, the packaging tool was abandoned and alternatives were tested - this time on Arch Linux, a rolling distribution with the latest versions of all packages. After much frustration, the issue was narrowed down to the <code>glibc</code> version again (this was not as obvious as it is in hindsight). Arch Linux does not support rolling back to a specific version, so the correct version was manually downloaded from <code>https://archive.archlinux.org/packages/g/glibc/</code> and forcefully installed. This promptly broke the system, causing a blue screen of death on shutdown (a first time on Linux). Luckily this was inside a virtual machine. Before shutdown, new terminals could not be opened, but some commands in an already open terminal worked and was used to copy some important data to a thumbdrive. The thumbdrive later failed due to an unrelated issue.</p>
<p>



    
    <input type="checkbox" id="zoomCheck-c17e7" hidden>
    <label for="zoomCheck-c17e7">
        <img class="zoomCheck" loading="lazy" decoding="async" 
            src="linux-bsod.png" alt="Linux&amp;rsquo;s blue screen of death" 
             />
    </label>

</p>
<p>Ubuntu 22.04 - the oldest system that still had standard support - was used next. Everything went smoothly once the format for the AppDir was established and application was successfully packaged and run on different distributions.</p>
<p>A example of a command that might be used to create an AppImage on Ubuntu 22.04:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">./linuxdeploy.AppImage -appimage <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    AppDir/usr/share/applications/app-name.desktop <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -qmake<span class="o">=</span>/usr/bin/qmake6 <span class="nv">qmldir</span><span class="o">=</span>application-styling/ <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>    -qmldir-importscanner<span class="o">=</span>/usr/lib/qt6/libexec/qmlimportscanner
</span></span></code></pre></div><blockquote>
<p>The <code>qmldir</code> and <code>importscanner</code> are required to include styling and image assets defined as Qt Resources.</p>
</blockquote>
<p>It is intresting to see the lifespan of the software we build, and the chain of dependencies it requires.</p>
]]></content>
      </entry>

</feed>


