My previous article about Steam Deck has exploded in size, so I decided to move the part about installing packages and building stuff into a separate article.

Steam Deck, Konsole in Desktop mode and docked with mouse and keyboard

But it’s not just already published parts, there are some new things too: in particular, I’ve managed to build Qt (both shared and static configurations) and used it to build my own applications.

But first, continuing the trend, here’s a photo with Deadpool:

Steam Deck, Konsole in Desktop mode and Deadpool

And now to the actual stuff.

Y tho

Aside from being a “video game machine”, Steam Deck is also a quite powerful PC with a common x86_64 / AMD64 architecture - the same that you (most likely) have on your desktop PC. So you can use it for software development the same way as your main machine.

In some situations it might even be the only computer that you have available, so isn’t it handy that you can perform all your regular software development tasks on it.

Finally, certain software might not be available in a form of pre-built binaries in neither of the repositories/storages/stores, and to get it running on Steam Deck you’d need to build it from sources right on the device (of course, you can also build it on another GNU/Linux host and copy the binaries to Steam Deck, but that won’t be as exciting, would it, plus you might stumble upon a mismatch in some dependencies versions, such as ICU).

Is it convenient/viable/feasible

The device screen is certainly not big enough, so I’d recommend docking to an external display (shown on the left):

Steam Deck, Steam Dock, external display, keyboard and mouse

I mean, Steam Deck screen size is rather okay, you can read from it and work with code, but probably not for a long periods of time, if you value your eyesight.

Instead of docking to an external display you can also connect to Steam Deck via SSH from your main machine (that’s what is happenning on the right display on the photo above), where you have both normal display and proper mouse/keyboard.

If even SSH isn’t an option, then at least do get a decent keyboard, because I most definitely can recommend neither the one from the photo above (Microsoft Wireless Universal Foldable) nor the one from the very first photo in the article (Microsoft Arc 1392).

Software-wise, SteamOS (Arch Linux) environment is good enough for software development, despite its limitations of having a read-only wipeable filesystem. You’ll see how that works in the next section.

As for computing power, it is more than decent. Hell, it is far more powerful than my desktop PC. Later in the article there will be a section about Qt build times, which can be used as a good real-life compilation benchmark.

So, while (undocked) Steam Deck is not the most convenient workstation in the world, it is powerful enough for software development tasks. As a bonus, it is a portable device that is rather easy to carry around, although compiling big projects without being connected to a power supply isn’t a particularly bright idea.

Homebrew

As I already described in the previous article, it might be wise not to install system-wide packages via pacman and use a different package manager instead, specifically one that would keep its packages “isolated” somewhere and not getting in the way or “contaminating” the system environment.

Nix package manager will likely do the job, but I don’t have any experience with it. Also, its installation involves certain trickery with symlinks and system services for persisting those, so I decided not to try that one.

Homebrew package manager, on the other hand, I know very well, as I use it every day on my Mac. Conveniently enough it also has GNU/Linux support, so that is what I’ll be using on Steam Deck too. It does not require sudo elevation and installs stuff into /home/linuxbrew/, where it is nicely contained.

Some say that Homebrew is a bad choice, because there is already a bazillion of package managers for GNU/Linux distributions. But how many of those are easy enough to install and, most importantly, how many of them allow to install to /home/ or other specified folder without affecting system environment? Yeah, I’ll stick to Homebrew for now.

There is a guide for installing Homebrew on Steam Deck from some guy over here, and he says not to put stuff into ~/.bash_profile, otherwise it “it will break Steam”, but he doesn’t say how exactly it will break Steam. He probably means that Homebrew environment variables/paths will be prepended to they system ones, and while that’s what is desired for the whole purpose of using Homebrew, the default SteamOS stuff (Steam itself, Gamescope, etc) might get “unexpected” paths/libraries/etc, which will affect the way system works. That I can only agree with, that is exactly why I am trying to avoid using pacman for installing additional software.

There is also this thread on Steam Community forums, where people also mention that the system environment should not be affected if Homebrew environment is activated manually (not added to the ~/.bash_profile).

Finally, here’s Homebrew own instructions where they tell to add stuff to ~/.bash_profile, but most likely these instructions do not account for SteamOS environment.

So, in the end, it probably really is a good idea not to add Homebrew environment stuff to ~/.bash_profile and instead explicitly activate it every time you’d like to use it.

Let’s proceed with the installation then. First create a user for it:

$ sudo useradd --system --user-group --create-home linuxbrew

$ grep linuxbrew /etc/passwd
linuxbrew:x:966:966::/home/linuxbrew:/usr/sbin/nologin

$ ls -l /home
drwx------ 17 deck      deck            4096 Jan 27 12:29 deck
drwx------  3 linuxbrew linuxbrew       4096 Jan 27 15:18 linuxbrew
drwx------  2 root      root           16384 Aug 26 09:39 lost+found
-rw-------  1 root      root      1073741824 Jan 22 21:55 swapfile

If linuxbrew user didn’t get nologin, set it explicitly to prevent logging-in as that user:

$ sudo usermod -s /usr/sbin/nologin linuxbrew
$ sudo --login --user linuxbrew
This account is currently not available.

Then execute the Homebrew installation script as linuxbrew user. Yes, it is a horrible idea to execute shell scripts downloaded from the internet, but I couldn’t comprehend individual steps in the script, so I decided to take the risk:

$ sudo -u linuxbrew /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
==> Checking for `sudo` access (which may request your password)...
==> This script will install:
/home/linuxbrew/.linuxbrew/bin/brew
/home/linuxbrew/.linuxbrew/share/doc/homebrew
/home/linuxbrew/.linuxbrew/share/man/man1/brew.1
/home/linuxbrew/.linuxbrew/share/zsh/site-functions/_brew
/home/linuxbrew/.linuxbrew/etc/bash_completion.d/brew
/home/linuxbrew/.linuxbrew/Homebrew

Press RETURN/ENTER to continue or any other key to abort:
Sorry, user linuxbrew may not run sudo on HOSTNAME.
==> /bin/chown -R linuxbrew:linuxbrew /home/linuxbrew/.linuxbrew/Homebrew
==> Downloading and installing Homebrew...
remote: Enumerating objects: 224720, done.
remote: Counting objects: 100% (232/232), done.
remote: Compressing objects: 100% (163/163), done.
remote: Total 224720 (delta 85), reused 175 (delta 67), pack-reused 224488
Receiving objects: 100% (224720/224720), 63.74 MiB | 2.73 MiB/s, done.
Resolving deltas: 100% (165343/165343), done.
From https://github.com/Homebrew/brew
 * [new branch]          less-agressive-core-tap -> origin/less-agressive-core-tap
 * [new branch]          master                  -> origin/master
 * [new tag]             0.1                     -> 0.1
 * [new tag]             0.2                     -> 0.2
 * [new tag]             0.3                     -> 0.3
 ...
 * [new tag]             3.6.6                   -> 3.6.6
 * [new tag]             3.6.7                   -> 3.6.7
 * [new tag]             3.6.8                   -> 3.6.8
 * [new tag]             3.6.9                   -> 3.6.9
HEAD is now at 10845a112 Merge pull request #14444 from dduugg/resolve-rubocop-todo
==> Tapping homebrew/core
remote: Enumerating objects: 1404036, done.
remote: Counting objects: 100% (194/194), done.
remote: Compressing objects: 100% (99/99), done.
remote: Total 1404036 (delta 112), reused 169 (delta 95), pack-reused 1403842
Receiving objects: 100% (1404036/1404036), 536.53 MiB | 1.42 MiB/s, done.
Resolving deltas: 100% (978676/978676), done.
From https://github.com/Homebrew/homebrew-core
 * [new branch]              master     -> origin/master
HEAD is now at b14bb200024 eksctl: update 0.127.0 bottle.
==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:fc45ee6eddf4c7a17f4373dde7b1bc8a58255ea61e6847d3bf895225b28d072a
######################################################################## 100.0%
==> Pouring portable-ruby-2.6.8_1.x86_64_linux.bottle.tar.gz
Warning: /home/linuxbrew/.linuxbrew/bin is not in your PATH.
  Instructions on how to configure your shell for Homebrew
  can be found in the 'Next steps' section below.
==> Installation successful!
==> Homebrew has enabled anonymous aggregate formulae and cask analytics.
Read the analytics documentation (and how to opt-out) here:
  https://docs.brew.sh/Analytics
No analytics data has been sent yet (nor will any be during this install run).
==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
  https://github.com/Homebrew/brew#donations
==> Next steps:
- Run these three commands in your terminal to add Homebrew to your PATH:
    echo '# Set PATH, MANPATH, etc., for Homebrew.' >> /home/linuxbrew/.bash_profile
    echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> /home/linuxbrew/.bash_profile
    eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
- Install Homebrew's dependencies if you have sudo access:
    sudo pacman -S base-devel
  For more information, see:
    https://docs.brew.sh/Homebrew-on-Linux
- We recommend that you install GCC:
    brew install gcc
- Run brew help to get started
- Further documentation:
    https://docs.brew.sh

Do not follow these instructions in the end of the output (except for the one about installing base-devel, but we’ll get to that later). Do not add anything to ~/.bash_profile. Like I said, Homebrew environment will be activated manually.

If you get permission errors, check that you’ve run the commands as linuxbrew user. Also just in case check who is the owner of /home/linuxbrew, and if it’s not linuxbrew, then change to it:

$ sudo chown -R linuxbrew:linuxbrew /home/linuxbrew

Now the Homebrew is installed, but you might still get permission problems, this time for your deck user to access linuxbrew stuff. So maybe the step with creating linuxbrew user was actually redundant, but they do say not to use a sudo-capable user for that. Anyway, I unfortunately don’t exactly remember what I did to resolve this, but I found the following in my history of commands:

$ sudo usermod -a -G linuxbrew deck
$ sudo chmod -R g+rx /home/linuxbrew

$ grep linuxbrew /etc/group
linuxbrew:x:966:deck

$ groups
wheel deck

So I added brew user to linuxbrew group and changed the group permissions for /home/linuxbrew folder. Reboot (or restart the session) to refresh the deck user groups and check what you got:

$ whoami
deck

$ id
uid=1000(deck) gid=1000(deck) groups=1000(deck),966(linuxbrew),998(wheel)

$ groups
linuxbrew wheel deck

Now you should be able to activate Homebrew environment (as deck user):

$ eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"
$ brew --version

But when I tried to verify the installation with brew doctor, I got lots of problems reported, such as no Git origin, non-writable folders and so on. Again, this is still likely because of the linuxbrew access rights, and so probably one should execute everything as deck from the begining, but since I am already where I am, the fix was to grant the following rights:

$ sudo chown -R deck \
    /home/linuxbrew/.linuxbrew/Homebrew \
    /home/linuxbrew/.linuxbrew/etc/bash_completion.d \
    /home/linuxbrew/.linuxbrew/share/doc \
    /home/linuxbrew/.linuxbrew/share/man \
    /home/linuxbrew/.linuxbrew/share/man/man1 \
    /home/linuxbrew/.linuxbrew/share/zsh \
    /home/linuxbrew/.linuxbrew/share/zsh/site-functions \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks

$ chmod u+w \
    /home/linuxbrew/.linuxbrew/Homebrew \
    /home/linuxbrew/.linuxbrew/etc/bash_completion.d \
    /home/linuxbrew/.linuxbrew/share/doc \
    /home/linuxbrew/.linuxbrew/share/man \
    /home/linuxbrew/.linuxbrew/share/man/man1 \
    /home/linuxbrew/.linuxbrew/share/zsh \
    /home/linuxbrew/.linuxbrew/share/zsh/site-functions \
    /home/linuxbrew/.linuxbrew/var/homebrew/locks

Now everything should be in order:

$ brew doctor

Warning: No developer tools installed.
Install Clang or run `brew install gcc`.

The warning about no developer tools installed is okay, we’ll get to it.

Disable analytics, if you wish:

$ brew analytics off
$ brew analytics
Analytics are disabled.

Out of the box with no packages installed yet, Homebrew takes almost 750 MB of disk space:

$ du -hs /home/linuxbrew/
746M    /home/linuxbrew/

Run an update just in case and check the version:

$ brew update
==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
  https://github.com/Homebrew/brew#donations

Updated 1 tap (homebrew/core).

$ brew --version
Homebrew 3.6.20
Homebrew/homebrew-core (git revision 7c064c30a3d; last commit 2023-01-27)

The final step: make an alias in ~/.bashrc for activating Homebrew environment:

$ nano ~/.bashrc

alias brewsome="eval \"$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)\""

If you’ll add it to ~/.bash_profile instead, then this alias will be available only when you connect over SSH, but Konsole sessions won’t have it.

To test the alias, restart the Konsole (or reconnect via SSH) and run brew --version. It should fail, then you activate Homebrew environment and try again:

$ brew --version
-bash: brew: command not found

$ brewsome

$ brew --version
Homebrew 3.6.20
Homebrew/homebrew-core (git revision 7c064c30a3d; last commit 2023-01-27)

Installing GCC

The first thing you need to install with Homebrew is developer tools. To be specific - GCC:

$ brew install gcc

==> Pouring gcc--12.2.0.x86_64_linux.bottle.2.tar.gz
Warning: The post-install step did not complete successfully
You can try again using:
  brew postinstall gcc
==> Summary
�  /home/linuxbrew/.linuxbrew/Cellar/gcc/12.2.0: 1,623 files, 306.6MB
==> Running `brew cleanup gcc`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).

Aw, something failed. At the same time, it did install something, because the folder size has doubled:

$ du -hs /home/linuxbrew/
1.6G    /home/linuxbrew/

Trying to do what it says to fails as well:

$ brew postinstall gcc
==> Postinstalling gcc
Warning: The post-install step did not complete successfully
You can try again using:
  brew postinstall gcc

Adding --debug does output more details:

$ brew postinstall --debug gcc

==> Postinstalling gcc
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/brew.rb (Formulary::FromPathLoader): loading /home/linuxbrew/.linuxbrew/opt/gcc/.brew/gcc.rb
Warning: The post-install step did not complete successfully
You can try again using:
  brew postinstall gcc
==> An exception occurred within a child process:
  ErrorDuringExecution: Failure while executing; `/usr/bin/cc -print-file-name=crti.o` exited with 127. Here's the output:


/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/utils/popen.rb:12:in `popen_read'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/utils/popen.rb:16:in `safe_popen_read'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/gcc.rb:173:in `post_install'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/formula.rb:1161:in `block (2 levels) in run_post_install'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/formula.rb:968:in `with_logging'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/formula.rb:1160:in `block in run_post_install'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/utils.rb:605:in `with_env'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/formula.rb:1149:in `run_post_install'
/home/linuxbrew/.linuxbrew/Homebrew/Library/Homebrew/postinstall.rb:24:in `<main>'

I don’t remember how I got to the actual reason, but I discovered that installing GCC with Homebrew requires… GCC being already available in the system! What is the actual fuck.

That is one of those exceptions to the rule of not installing system-wide packages with pacman, which I mentioned earlier. There is simply no other way to proceed without installing GCC with pacman. At least I didn’t find any other way.

Using these instructions, GCC can be installed like this:

$ sudo steamos-readonly disable

$ sudo pacman-key --init

$ sudo pacman-key --populate archlinux
==> Appending keys from archlinux.gpg...
==> Updating trust database...
gpg: next trustdb check due at 2023-07-12

$ sudo pacman -Syu
:: Synchronizing package databases...
 jupiter-rel is up to date
 holo-rel is up to date
 core-rel is up to date
 extra-rel is up to date
 community-rel is up to date
 multilib-rel is up to date
:: Starting full system upgrade...
 there is nothing to do

$ sudo pacman -S base-devel
:: There are 26 members in group base-devel:
:: Repository holo-rel
   1) archlinux-keyring
:: Repository core-rel
   2) autoconf  3) automake  4) binutils  5) bison  6) debugedit  7) fakeroot  8) file  9) findutils  10) flex  11) gawk  12) gcc  13) gettext  14) grep  15) groff
   16) gzip  17) libtool  18) m4  19) make  20) pacman  21) patch  22) pkgconf  23) sed  24) sudo  25) texinfo  26) which

Enter a selection (default=all):
resolving dependencies...
looking for conflicting packages...

Packages (28) libisl-0.25-1  libmpc-1.2.1-2  archlinux-keyring-20221123-1.1  autoconf-2.71-1  automake-1.16.5-1  binutils-2.39-3  bison-3.8.2-4  debugedit-5.0-4
              fakeroot-1.29-1  file-5.43-1  findutils-4.9.0-1  flex-2.6.4-3  gawk-5.2.0-3  gcc-12.2.0-1  gettext-0.21.1-1  grep-3.8-2  groff-1.22.4-7  gzip-1.12-1
              libtool-2.4.7-5  m4-1.4.19-1  make-4.3-3  pacman-6.0.1-8  patch-2.7.6-8  pkgconf-1.8.0-1  sed-4.8-1  sudo-1.9.11.p3-1  texinfo-6.8-2  which-2.21-5

Total Installed Size:  275.34 MiB
Net Upgrade Size:      185.29 MiB

:: Proceed with installation? [Y/n] Y
(28/28) checking keys in keyring                                                                     [###########################################################] 100%
(28/28) checking package integrity                                                                   [###########################################################] 100%
(28/28) loading package files                                                                        [###########################################################] 100%
(28/28) checking for file conflicts                                                                  [###########################################################] 100%
error: failed to commit transaction (conflicting files)
fakeroot: /etc/ld.so.conf.d/fakeroot.conf exists in filesystem
Errors occurred, no packages were upgraded.

When I installed it for the first time, it just worked. But when it all seemingly got wiped after one of the system updates and I tried to install it again, then, as you can see, I got this error about fakeroot, which I couldn’t figure out how to resolve at first.

And one other time after another system update I got errors about signing keys. Tried changing key servers, re-initializing and re-populating keyrings, but nothing helped, any attempt to update anything was failing like this:

downloading required keys...
:: Import PGP key AF1D2199EF0A3CCF, "GitLab CI Package Builder <ci-package-builder-1@steamos.cloud>"? [Y/n] y
error: key "AF1D2199EF0A3CCF" could not be looked up remotely
error: required key missing from keyring
error: failed to commit transaction (unexpected error)
Errors occurred, no packages were upgraded.

In both cases, I think, those problems were probably caused by some leftovers from the previous installations, which were somehow still there (but the tools themselves were not?). The solution for both problems turned out to be simply adding --overwrite '*':

$ sudo pacman -S base-devel --overwrite '*'

:: There are 26 members in group base-devel:
:: Repository holo-rel
   1) archlinux-keyring
:: Repository core-rel
   2) autoconf  3) automake  4) binutils  5) bison  6) debugedit  7) fakeroot  8) file  9) findutils  10) flex  11) gawk  12) gcc  13) gettext  14) grep  15) groff
   16) gzip  17) libtool  18) m4  19) make  20) pacman  21) patch  22) pkgconf  23) sed  24) sudo  25) texinfo  26) which

Enter a selection (default=all):
resolving dependencies...
looking for conflicting packages...

Packages (28) libisl-0.25-1  libmpc-1.2.1-2  archlinux-keyring-20221123-1.1  autoconf-2.71-1  automake-1.16.5-1  binutils-2.39-3  bison-3.8.2-4  debugedit-5.0-4
              fakeroot-1.29-1  file-5.43-1  findutils-4.9.0-1  flex-2.6.4-3  gawk-5.2.0-3  gcc-12.2.0-1  gettext-0.21.1-1  grep-3.8-2  groff-1.22.4-7  gzip-1.12-1
              libtool-2.4.7-5  m4-1.4.19-1  make-4.3-3  pacman-6.0.1-8  patch-2.7.6-8  pkgconf-1.8.0-1  sed-4.8-1  sudo-1.9.11.p3-1  texinfo-6.8-2  which-2.21-5

Total Installed Size:  275.34 MiB
Net Upgrade Size:      185.29 MiB

:: Proceed with installation? [Y/n] Y
(28/28) checking keys in keyring                                                                     [###########################################################] 100%
(28/28) checking package integrity                                                                   [###########################################################] 100%
(28/28) loading package files                                                                        [###########################################################] 100%
(28/28) checking for file conflicts                                                                  [###########################################################] 100%
(28/28) checking available disk space                                                                [###########################################################] 100%
warning: could not get file information for usr/include/
warning: could not get file information for usr/include/autosprintf.h
warning: could not get file information for usr/include/gettext-po.h
...
warning: could not get file information for usr/share/man/
warning: could not get file information for usr/share/man/man1/
warning: could not get file information for usr/share/man/man1/which.1.gz
:: Processing package changes...
( 1/28) reinstalling gettext                                                                         [###########################################################] 100%
( 2/28) reinstalling gawk                                                                            [###########################################################] 100%
( 3/28) reinstalling grep                                                                            [###########################################################] 100%
( 4/28) reinstalling findutils                                                                       [###########################################################] 100%
( 5/28) reinstalling pacman                                                                          [###########################################################] 100%
( 6/28) reinstalling archlinux-keyring                                                               [###########################################################] 100%
==> Appending keys from archlinux.gpg...
==> Updating trust database...
gpg: next trustdb check due at 2023-07-12
==> Updating trust database...
gpg: next trustdb check due at 2023-07-12
( 7/28) installing m4                                                                                [###########################################################] 100%
( 8/28) installing autoconf                                                                          [###########################################################] 100%
( 9/28) installing automake                                                                          [###########################################################] 100%
(10/28) reinstalling binutils                                                                        [###########################################################] 100%
(11/28) installing bison                                                                             [###########################################################] 100%
(12/28) installing debugedit                                                                         [###########################################################] 100%
(13/28) reinstalling sed                                                                             [###########################################################] 100%
(14/28) reinstalling file                                                                            [###########################################################] 100%
(15/28) installing fakeroot                                                                          [###########################################################] 100%
(16/28) installing flex                                                                              [###########################################################] 100%
(17/28) installing libmpc                                                                            [###########################################################] 100%
(18/28) installing libisl                                                                            [###########################################################] 100%
(19/28) installing gcc                                                                               [###########################################################] 100%
Optional dependencies for gcc
    lib32-gcc-libs: for generating code for 32-bit ABI [installed]
(20/28) reinstalling groff                                                                           [###########################################################] 100%
(21/28) reinstalling gzip                                                                            [###########################################################] 100%
(22/28) reinstalling libtool                                                                         [###########################################################] 100%
(23/28) reinstalling texinfo                                                                         [###########################################################] 100%
(24/28) installing make                                                                              [###########################################################] 100%
(25/28) installing patch                                                                             [###########################################################] 100%
Optional dependencies for patch
    ed: for patch -e functionality
(26/28) reinstalling pkgconf                                                                         [###########################################################] 100%
(27/28) reinstalling sudo                                                                            [###########################################################] 100%
warning: directory permissions differ on /etc/sudoers.d/
filesystem: 755  package: 750
warning: directory permissions differ on /var/db/
filesystem: 711  package: 755
warning: directory permissions differ on /var/db/sudo/lectured/
filesystem: 711  package: 700
(28/28) reinstalling which                                                                           [###########################################################] 100%
:: Running post-transaction hooks...
(1/4) Reloading system manager configuration...
(2/4) Creating temporary files...
/usr/lib/tmpfiles.d/steamos.conf:24: Duplicate line for path "/var/empty", ignoring.
/usr/lib/tmpfiles.d/tmp.conf:12: Duplicate line for path "/var/tmp", ignoring.
/usr/lib/tmpfiles.d/var.conf:19: Duplicate line for path "/var/cache", ignoring.
(3/4) Arming ConditionNeedsUpdate...
(4/4) Updating the info directory file...

$ echo $?
0

Then everything got installed successfully:

$ which gcc
/usr/bin/gcc

$ gcc --version
gcc (GCC) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Don’t forget to enable read-only mode back:

$ sudo steamos-readonly enable
$ sudo steamos-readonly status
enabled

Now we can proceed with installing GCC for Homebrew:

$ brew postinstall gcc
==> Postinstalling gcc
==> Creating the GCC specs file: /home/linuxbrew/.linuxbrew/Cellar/gcc/12.2.0/bin/../lib/gcc/current/gcc/x86_64-pc-linux-gnu/12/specs

$ ls -l /home/linuxbrew/.linuxbrew/bin/{gcc*,g++*}
lrwxrwxrwx 1 deck deck 31 Jan 27 16:50 /home/linuxbrew/.linuxbrew/bin/g++-12 -> ../Cellar/gcc/12.2.0/bin/g++-12
lrwxrwxrwx 1 deck deck 31 Jan 27 16:50 /home/linuxbrew/.linuxbrew/bin/gcc-12 -> ../Cellar/gcc/12.2.0/bin/gcc-12
lrwxrwxrwx 1 deck deck 34 Jan 27 16:50 /home/linuxbrew/.linuxbrew/bin/gcc-ar-12 -> ../Cellar/gcc/12.2.0/bin/gcc-ar-12
lrwxrwxrwx 1 deck deck 34 Jan 27 16:50 /home/linuxbrew/.linuxbrew/bin/gcc-nm-12 -> ../Cellar/gcc/12.2.0/bin/gcc-nm-12
lrwxrwxrwx 1 deck deck 38 Jan 27 16:50 /home/linuxbrew/.linuxbrew/bin/gcc-ranlib-12 -> ../Cellar/gcc/12.2.0/bin/gcc-ranlib-12

$ which gcc-12
/home/linuxbrew/.linuxbrew/bin/gcc-12

$ gcc-12 --version
gcc-12 (Homebrew GCC 12.2.0) 12.2.0
Copyright (C) 2022 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

So these are the tools that Homebrew will (apparently) be using for building its stuff.

But let’s try to build a simple C++ program with either of compilers (the system one and the Homebrew’s one):

$ cd /tmp
$ nano ./some.cpp
#include <iostream>

int main(int argc, char *argv[])
{
    std::cout << "ololo" << std::endl;

    return EXIT_SUCCESS;
}

It will likely fail to compile like this:

$ g++ ./some.cpp -o some

In file included from /usr/include/c++/12.2.0/x86_64-pc-linux-gnu/bits/c++config.h:655,
                 from /usr/include/c++/12.2.0/iostream:38,
                 from ./some.cpp:1:
/usr/include/c++/12.2.0/x86_64-pc-linux-gnu/bits/os_defines.h:39:10: fatal error: features.h: No such file or directory
   39 | #include <features.h>
      |          ^~~~~~~~~~~~
compilation terminated.

That is because glibc is missing in the system (why isn’t it included in base-devel?). Do not install it with Homebrew, that should be done with pacman:

$ sudo steamos-readonly disable
$ sudo pacman -S glibc
$ sudo steamos-readonly enable

Try to compile again, and if it fails like this:

$ g++ ./some.cpp -o some

In file included from /usr/include/errno.h:28,
                 from /usr/include/c++/12.2.0/cerrno:42,
                 from /usr/include/c++/12.2.0/ext/string_conversions.h:44,
                 from /usr/include/c++/12.2.0/bits/basic_string.h:3960,
                 from /usr/include/c++/12.2.0/string:53,
                 from /usr/include/c++/12.2.0/bits/locale_classes.h:40,
                 from /usr/include/c++/12.2.0/bits/ios_base.h:41,
                 from /usr/include/c++/12.2.0/ios:42,
                 from /usr/include/c++/12.2.0/ostream:38,
                 from /usr/include/c++/12.2.0/iostream:39,
                 from ./some.cpp:1:
/usr/include/bits/errno.h:26:11: fatal error: linux/errno.h: No such file or directory
   26 | # include <linux/errno.h>
      |           ^~~~~~~~~~~~~~~
compilation terminated.

then this time it is missing linux-api-headers, which you also need to install with pacman (again, why isn’t it included in base-devel?):

$ sudo steamos-readonly disable
$ sudo pacman -S linux-api-headers
$ sudo steamos-readonly enable

Now it should compile without problems with both compilers:

$ which g++-12
/home/linuxbrew/.linuxbrew/bin/g++-12
$ g++-12 --version
g++-12 (Homebrew GCC 12.2.0) 12.2.0
$ g++-12 ./some.cpp -o some-gcc-homebrew
$ ./some-gcc-homebrew
ololo

$ which g++
/usr/bin/g++
$ g++ --version
g++ (GCC) 12.2.0
$ g++ ./some.cpp -o some-gcc-system
$ ./some-gcc-system
ololo

$ du -b ./some-gcc-*
21232    ./some-gcc-homebrew
21144    ./some-gcc-system

It’s interesting that even though the compilers versions are the same, resulting binaries differ in size.

Anyway, now it’s all finally sorted, but do remember that system GCC (and related packages) will likely get wiped once again on the next system update, so you will need to be install them with pacman again.

Building from sources

Now that you have Homebrew and GCC, you can install additional build tools (without contaminating the system environment), such as:

$ brew install cmake ninja

A simple application

For example, I can try to build my GLFW Dear ImGui sample application:

$ mkdir ~/code && cd $_
$ git clone git@github.com:retifrav/glfw-imgui-example.git
$ cd ./glfw-imgui-example/

This project has several dependencies, which I could install via Homebrew as well, but for resolving libraries dependencies it might be a better idea to use vcpkg instead, especially that vcpkg also is contained within the user home folder and does not “spam” to system environment.

But when I tried to configure the project, it will fail to find OpenGL:

...
CMake Error at /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.1/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find OpenGL (missing: OPENGL_INCLUDE_DIR)
Call Stack (most recent call first):
  /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.1/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.1/share/cmake/Modules/FindOpenGL.cmake:443 (FIND_PACKAGE_HANDLE_STANDARD_ARGS)
  CMakeLists.txt:71 (find_package)

So it says that a GNU/Linux based gaming OS does not have developer libraries for OpenGL. Well, I guess it wasn’t expected that someone will actually build something directly on Steam Deck. Okay, we can install it with Homebrew then:

$ brew install mesa

That will (should) also install libx11 dependency, which we’ll need in a moment.

Let’s now try to build the project:

$ cmake --preset vcpkg-default-triplet

But it will likely fail on resolving GLFW dependency:

...
-- Using X11 for window creation
CMake Error at /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.1/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find X11 (missing: X11_X11_INCLUDE_PATH)
Call Stack (most recent call first):
  /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.1/share/cmake/Modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.1/share/cmake/Modules/FindX11.cmake:481 (find_package_handle_standard_args)
  /home/deck/programs/vcpkg/scripts/buildsystems/vcpkg.cmake:852 (_find_package)
  CMakeLists.txt:208 (find_package)

Here it says that it couldn’t find exactly the X11 library, which has been just installed together with Mesa.

It would be correct to assume that one needs to provide the Homebrew prefix path, so CMake could find the dependencies there, but in case of vcpkg it won’t be enough to just give it -DCMAKE_PREFIX_PATH="/home/linuxbrew/.linuxbrew", because it’s vcpkg who can’t find it, not the project. And I didn’t find a better way to resolve this but to edit ~/programs/vcpkg/scripts/buildsystems/vcpkg.cmake and add this line to the end of the file:

list(APPEND CMAKE_PREFIX_PATH "/home/linuxbrew/.linuxbrew")

Now the project configuration succeeds:

$ cmake --preset vcpkg-default-triplet
Preset CMake variables:

  CMAKE_BUILD_TYPE:STRING="Release"
  CMAKE_INSTALL_PREFIX:PATH="/home/deck/code/glfw-imgui-example/install/vcpkg-default-triplet"
  CMAKE_TOOLCHAIN_FILE:FILEPATH="/home/deck/programs/vcpkg/scripts/buildsystems/vcpkg.cmake"
  USING_PACKAGE_MANAGER_VCPKG:BOOL="TRUE"

-- Running vcpkg install
Fetching registry information from git@github.com:retifrav/vcpkg-registry.git (HEAD)...
Detecting compiler hash for triplet x64-linux...
The following packages will be built and installed:
    dear-imgui[backend-glfw,core]:x64-linux -> 1.88.0 -- /home/deck/.cache/vcpkg/registries/git-trees/fda742f0dd720fcd2af3cb8e946173069d70435f
  * decovar-vcpkg-cmake[core]:x64-linux -> 2022-10-15 -- /home/deck/.cache/vcpkg/registries/git-trees/71a584c17648c10b4c515cfb980d644ce8486bbe
    glad[core]:x64-linux -> 0.1.36 -- /home/deck/.cache/vcpkg/registries/git-trees/2341f5144ce8e76a256289517d61abb4ab9fb72c
    glfw[core]:x64-linux -> 3.3.8 -- /home/deck/.cache/vcpkg/registries/git-trees/ff428db2871ef4e409c2ea9f29a866b63ba5b90b
  * vcpkg-cmake[core]:x64-linux -> 2022-08-18 -- /home/deck/.cache/vcpkg/registries/git-trees/84c200e8e625d4d99b1649525fcdf81a73197078
  * vcpkg-cmake-config[core]:x64-linux -> 2022-02-06 -- /home/deck/.cache/vcpkg/registries/git-trees/e23b39e21f0dd42ecc615262640d211c39696aa1
Additional packages (*) will be modified to complete this operation.
Restored 0 package(s) from /home/deck/.cache/vcpkg/archives in 50.7 us. Use --debug to see more details.
Installing 1/6 vcpkg-cmake-config:x64-linux...
Building vcpkg-cmake-config[core]:x64-linux...
-- Installing port from location: /home/deck/.cache/vcpkg/registries/git-trees/e23b39e21f0dd42ecc615262640d211c39696aa1
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake-config_x64-linux/share/vcpkg-cmake-config/vcpkg_cmake_config_fixup.cmake
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake-config_x64-linux/share/vcpkg-cmake-config/vcpkg-port-config.cmake
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake-config_x64-linux/share/vcpkg-cmake-config/copyright
-- Performing post-build validation
Stored binary cache: "/home/deck/.cache/vcpkg/archives/ca/ca9825c42848d6276fd7e99fcdba804bee900f94a2b30e25e8f7bf3b2112aeeb.zip"
Elapsed time to handle vcpkg-cmake-config:x64-linux: 44.1 ms
Installing 2/6 vcpkg-cmake:x64-linux...
Building vcpkg-cmake[core]:x64-linux...
-- Installing port from location: /home/deck/.cache/vcpkg/registries/git-trees/84c200e8e625d4d99b1649525fcdf81a73197078
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake_x64-linux/share/vcpkg-cmake/vcpkg_cmake_configure.cmake
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake_x64-linux/share/vcpkg-cmake/vcpkg_cmake_build.cmake
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake_x64-linux/share/vcpkg-cmake/vcpkg_cmake_install.cmake
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake_x64-linux/share/vcpkg-cmake/vcpkg-port-config.cmake
-- Installing: /home/deck/programs/vcpkg/packages/vcpkg-cmake_x64-linux/share/vcpkg-cmake/copyright
-- Performing post-build validation
Stored binary cache: "/home/deck/.cache/vcpkg/archives/f1/f1cf145f5b84d9cc4327ee8e27c01cd269058869ec23bf3040cb3db9d31cae88.zip"
Elapsed time to handle vcpkg-cmake:x64-linux: 48.8 ms
Installing 3/6 decovar-vcpkg-cmake:x64-linux...
Building decovar-vcpkg-cmake[core]:x64-linux...
-- Installing port from location: /home/deck/.cache/vcpkg/registries/git-trees/71a584c17648c10b4c515cfb980d644ce8486bbe
-- Installing: /home/deck/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-linux/share/decovar-vcpkg-cmake/Installing.cmake
-- Installing: /home/deck/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-linux/share/decovar-vcpkg-cmake/Config.cmake.in
-- Installing: /home/deck/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-linux/share/decovar-vcpkg-cmake/decovar_vcpkg_cmake_ololo.cmake
-- Installing: /home/deck/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-linux/share/decovar-vcpkg-cmake/vcpkg-port-config.cmake
-- Installing: /home/deck/programs/vcpkg/packages/decovar-vcpkg-cmake_x64-linux/share/decovar-vcpkg-cmake/copyright
-- Performing post-build validation
Stored binary cache: "/home/deck/.cache/vcpkg/archives/5b/5bc820e411604043dfd9f73645b718b9860cfac802616fee91c12360a7174690.zip"
Elapsed time to handle decovar-vcpkg-cmake:x64-linux: 48 ms
Installing 4/6 glfw:x64-linux...
Building glfw[core]:x64-linux...
-- Installing port from location: /home/deck/.cache/vcpkg/registries/git-trees/ff428db2871ef4e409c2ea9f29a866b63ba5b90b
-- Using cached /home/deck/programs/vcpkg/downloads/glfw-7482de6071d21db77a7236155da44c172a7f6c9e.tar.gz
-- Cleaning sources at /home/deck/programs/vcpkg/buildtrees/glfw/src/172a7f6c9e-ac9aaa482e.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source /home/deck/programs/vcpkg/downloads/glfw-7482de6071d21db77a7236155da44c172a7f6c9e.tar.gz
-- Applying patch disable-pkgconfig.patch
-- Using source at /home/deck/programs/vcpkg/buildtrees/glfw/src/172a7f6c9e-ac9aaa482e.clean
-- Found external ninja('1.11.1').
-- Configuring x64-linux
-- Building x64-linux-dbg
-- Building x64-linux-rel
-- Installing: /home/deck/programs/vcpkg/packages/glfw_x64-linux/share/glfw/copyright
-- Performing post-build validation
Stored binary cache: "/home/deck/.cache/vcpkg/archives/6a/6a6b5672bee1fb07fe9454ba0dd6d2cf991d154131a14e7cdad4ad0fe3e5217f.zip"
Elapsed time to handle glfw:x64-linux: 3 s
Installing 5/6 dear-imgui:x64-linux...
Building dear-imgui[backend-glfw,core]:x64-linux...
-- Installing port from location: /home/deck/.cache/vcpkg/registries/git-trees/fda742f0dd720fcd2af3cb8e946173069d70435f
-- Using cached /home/deck/programs/vcpkg/downloads/dear-imgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Cleaning sources at /home/deck/programs/vcpkg/buildtrees/dear-imgui/src/3a12ceb889-c011915ee6.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source /home/deck/programs/vcpkg/downloads/dear-imgui-9aae45eb4a05a5a1f96be1ef37eb503a12ceb889.tar.gz
-- Using source at /home/deck/programs/vcpkg/buildtrees/dear-imgui/src/3a12ceb889-c011915ee6.clean
-- Found external ninja('1.11.1').
-- Configuring x64-linux
-- Building x64-linux-dbg
-- Building x64-linux-rel
-- Installing: /home/deck/programs/vcpkg/packages/dear-imgui_x64-linux/share/dear-imgui/copyright
-- ololo
-- Performing post-build validation
Stored binary cache: "/home/deck/.cache/vcpkg/archives/91/919da7bde86d29184e2fad379ef3b8064542bf1390ff6113090a11f7d8e32502.zip"
Elapsed time to handle dear-imgui:x64-linux: 11 s
Installing 6/6 glad:x64-linux...
Building glad[core]:x64-linux...
-- Installing port from location: /home/deck/.cache/vcpkg/registries/git-trees/2341f5144ce8e76a256289517d61abb4ab9fb72c
-- Using cached /home/deck/programs/vcpkg/downloads/glad-1ecd45775d96f35170458e6b148eb0708967e402.tar.gz
-- Cleaning sources at /home/deck/programs/vcpkg/buildtrees/glad/src/708967e402-a791cb9077.clean. Use --editable to skip cleaning for the packages you specify.
-- Extracting source /home/deck/programs/vcpkg/downloads/glad-1ecd45775d96f35170458e6b148eb0708967e402.tar.gz
-- Using source at /home/deck/programs/vcpkg/buildtrees/glad/src/708967e402-a791cb9077.clean
-- Found external ninja('1.11.1').
-- Configuring x64-linux
-- Building x64-linux-dbg
-- Building x64-linux-rel
-- Installing: /home/deck/programs/vcpkg/packages/glad_x64-linux/share/glad/copyright
-- Performing post-build validation
Stored binary cache: "/home/deck/.cache/vcpkg/archives/8b/8ba6bfc9ece2fd756e5818f78d2bd968ec282e85b520deddca37beaa17b3cc3b.zip"
Elapsed time to handle glad:x64-linux: 3.2 s
Total install time: 17 s
glfw provides CMake targets:

    # this is heuristically generated, and may not be correct
    find_package(glfw3 CONFIG REQUIRED)
    target_link_libraries(main PRIVATE glfw)

dear-imgui provides CMake targets:

    # this is heuristically generated, and may not be correct
    find_package(DearImGui CONFIG REQUIRED)
    target_link_libraries(main PRIVATE DearImGui)

glad provides CMake targets:

    # this is heuristically generated, and may not be correct
    find_package(glad CONFIG REQUIRED)
    target_link_libraries(main PRIVATE glad::glad)

-- Running vcpkg install - done
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenGL: /usr/lib/libOpenGL.so
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD
-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success
-- Found Threads: TRUE
-- Found X11: /home/linuxbrew/.linuxbrew/include
-- Looking for XOpenDisplay in /home/linuxbrew/.linuxbrew/lib/libX11.so;/home/linuxbrew/.linuxbrew/lib/libXext.so
-- Looking for XOpenDisplay in /home/linuxbrew/.linuxbrew/lib/libX11.so;/home/linuxbrew/.linuxbrew/lib/libXext.so - found
-- Looking for gethostbyname
-- Looking for gethostbyname - found
-- Looking for connect
-- Looking for connect - found
-- Looking for remove
-- Looking for remove - found
-- Looking for shmat
-- Looking for shmat - found
-- Looking for IceConnectionNumber in ICE
-- Looking for IceConnectionNumber in ICE - found
-- Configuring done (21.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/deck/code/glfw-imgui-example/build/vcpkg-default-triplet

Interesting that according to this output it has found system OpenGL (/usr/lib/libOpenGL.so), while not that long ago it was complaining that it can’t find one, and I needed to install Mesa with Homebrew. So which one is used/needed then?

Anyway, the project can finally build and run:

$ cmake --build --preset vcpkg-default-triplet
$ ./install/vcpkg-default-triplet/bin/glfw-imgui/glfw-imgui

And here it goes:

Steam Deck, GLFW Dear ImGui example built from sources

The 717 window height apparently reports the canvas height without the height of window decoration bar on top (which leaves it 83 pixels then).

The Qt Framework

Of course, let’s build Qt! First of all, because we can, but also to see how good Steam Deck hardware is for compiling really big projects.

Some information about the build environment:

  • Qt 6.5.1 sources;
  • Homebrew environment is activated (to find CMake and Ninja executables, although CMAKE_PREFIX_PATH still needs to be set, as you’ll see in a second);
  • VCPKG_ROOT environment variable is unset (just in case, as Qt seems to be using it for some purposes when it is set).

And here we go:

$ pwd
/home/deck/programs/qt/src/6.5.1/build

$ ../configure -static -release -no-pch \
    -prefix "/home/deck/programs/qt/6.5.1-static" \
    -skip qtwebengine -nomake tests -nomake examples \
    -- \
    -DCMAKE_PREFIX_PATH="/home/linuxbrew/.linuxbrew"

Without pointing it to Homebrew prefix in CMAKE_PREFIX_PATH the configuration will fail to even find OpenGL. With the prefix set, however, I almost immediately got another error:

CMake Error at /home/linuxbrew/.linuxbrew/lib/cmake/zstd/zstdTargets.cmake:42 (message):
  Some (but not all) targets in this export set were already defined.

  Targets Defined: zstd::libzstd_static

  Targets not yet defined: zstd::libzstd_shared

From what I understood, what’s wrong is that in /home/linuxbrew/.linuxbrew/lib/cmake/zstd/zstdTargets.cmake there is this line:

foreach(_cmake_expected_target IN ITEMS zstd::libzstd_shared zstd::libzstd_static)

And that is somehow problematic for static Qt (because dynamic Qt configured and built fine without complaining about zstd). So I just removed zstd::libzstd_shared from that foreach(), leaving only zstd::libzstd_static.

That did help once, but later at some point, probably after one of the system or/and Homebrew or/and Qt updates, the configuration started to fail like this:

Qt is now configured for building. Just run 'cmake --build . --parallel'

Once everything is built, you must run 'cmake --install .'
Qt will be installed into '/home/deck/programs/qt/6.6.2-static'

To configure and build other Qt modules, you can use the following convenience script:
        /home/deck/programs/qt/6.6.2-static/bin/qt-configure-module

If reconfiguration fails for some reason, try removing 'CMakeCache.txt' from the build directory
Alternatively, you can add the --fresh flag to your CMake flags.

-- Configuring incomplete, errors occurred!
CMake Error at /home/deck/programs/qt/src/6.6.2/qtbase/cmake/QtProcessConfigureArgs.cmake:1040 (message):
  CMake exited with code 1.

As you can see, even though it says “Qt is now configured for building” and prints the usual build commands, in the very end it says that there was some unspecified CMake error. And the line 1040 in QtProcessConfigureArgs.cmake isn’t particularly helpful either:

1039 if(NOT exit_code EQUAL 0)
1040     message(FATAL_ERROR "CMake exited with code ${exit_code}.")
1041 endif()

Only having scrolled up the configuration output I saw the actual errors:

-- Configuring submodule 'qtimageformats'
-- Found the following ICU libraries:
--   i18n (required): /home/linuxbrew/.linuxbrew/lib/libicui18n.so
--   uc (required): /home/linuxbrew/.linuxbrew/lib/libicuuc.so
--   data (required): /home/linuxbrew/.linuxbrew/lib/libicudata.so
CMake Error at /home/linuxbrew/.linuxbrew/lib/cmake/zstd/zstdTargets.cmake:60 (add_library):
  add_library cannot create imported target "zstd::libzstd_shared" because
  another target with the same name already exists.
Call Stack (most recent call first):
  /home/linuxbrew/.linuxbrew/lib/cmake/zstd/zstdConfig.cmake:1 (include)
  qtbase/cmake/FindWrapZSTD.cmake:24 (find_package)
  /home/linuxbrew/.linuxbrew/Cellar/cmake/3.28.3/share/cmake/Modules/CMakeFindDependencyMacro.cmake:76 (find_package)
  qtbase/cmake/QtPublicDependencyHelpers.cmake:36 (find_dependency)
  build/qtbase/lib/cmake/Qt6Core/Qt6CoreDependencies.cmake:30 (_qt_internal_find_third_party_dependencies)
  build/qtbase/lib/cmake/Qt6Core/Qt6CoreConfig.cmake:41 (include)
  build/qtbase/lib/cmake/Qt6/Qt6Config.cmake:164 (find_package)
  qtimageformats/CMakeLists.txt:14 (find_package)

So the trick with removing zstd::libzstd_shared target from foreach() apparently wasn’t enough anymore(?), and I also had to comment that target out in /home/linuxbrew/.linuxbrew/lib/cmake/zstd/zstdTargets.cmake:

# Create imported target zstd::libzstd_shared
#add_library(zstd::libzstd_shared SHARED IMPORTED)
#
#set_target_properties(zstd::libzstd_shared PROPERTIES
#  INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include"
#)

and in /home/linuxbrew/.linuxbrew/lib/cmake/zstd/zstdTargets-release.cmake:

# Import target "zstd::libzstd_shared" for configuration "Release"
#set_property(TARGET zstd::libzstd_shared APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
#set_target_properties(zstd::libzstd_shared PROPERTIES
#  IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libzstd.so.1.5.5"
#  IMPORTED_SONAME_RELEASE "libzstd.so.1"
#  )
#
#list(APPEND _cmake_import_check_targets zstd::libzstd_shared )
#list(APPEND _cmake_import_check_files_for_zstd::libzstd_shared "${_IMPORT_PREFIX}/lib/libzstd.so.1.5.5" )

Which is most certainly not a great idea, as these files aren’t meant to be modified. And I really can’t tell where this problem actually needs to be “fixed”: on Qt side (in FindWrapZSTD.cmake?) or in the Homebrew’s formula/recipe. By the way, the current package in AUR has this the other way around - with zstd::libzstd_shared and without zstd::libzstd_static.

Either way, after I left only the zstd::libzstd_static target, Qt was able to configure without complaining about zstd. If anything, after the build is done, you should probably revert these changes in zstd CMake configs.

Related to that, there is this bugreport and this commit, which apparently isn’t part of Qt 6.5.1 as of now, but eventually it probably will appear in one of the next versions. It won’t resolve the problem still, but it will make you delete zstd::libzstd_static instead of zstd::libzstd_shared, which means that you’ll end up with zstd being a shared/dynamic library dependency of your Qt build (or you can modify Qt’s FindWrapZSTD.cmake to make static zstd to be the preferred option again).

But okay, zstd is sorted, Qt has successfully configured, so it can be built now:

$ time cmake --build . --parallel

But very soon building failed with this error:

/home/deck/programs/qt/src/6.5.1/qtgrpc/src/tools/qtprotoccommon/generatorbase.h:8:10: fatal error: google/protobuf/compiler/code_generator.h: No such file or directory
    8 | #include <google/protobuf/compiler/code_generator.h>
      |          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

Fortunately, this is just a missing dependency, which can be installed with Homebrew:

$ brew install protobuf

After that it built some more, but then failed with another problem:

/home/deck/programs/qt/src/6.5.1/qtbase/src/corelib/text/qdoublescanprint_p.h:115:14: fatal error: double-conversion/double-conversion.h: No such file or directory
  115 | #    include <double-conversion/double-conversion.h>
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

This one is strange, because Qt sources do contain this library in /home/deck/programs/qt/src/6.5.1/qtbase/src/3rdparty/double-conversion/. But okay, this one also can be installed with Homebrew:

$ brew install double-conversion

That helped for a while, but then the build failed with another one:

/home/deck/programs/qt/src/6.5.1/qtbase/src/gui/text/qtextmarkdownimporter.cpp:16:10: fatal error: md4c.h: No such file or directory
   16 | #include <md4c.h>
      |          ^~~~~~~~
compilation terminated.

And again, Qt sources do have this library in /home/deck/programs/qt/src/6.5.1/qtbase/src/3rdparty/md4c/, so what’s going on then? But alright, Homebrew has this one too:

$ brew install md4c

After that the build was going fine for quite some time, but then still failed with another problem:

FAILED: qtbase/plugins/multimedia/libgstreamermediaplugin.so
/usr/bin/ld: cannot find -lgstphotography-1.0: No such file or directory
collect2: error: ld returned 1 exit status

So it says that it’s missing a GStreamer plugin called photography, which I indeed didn’t have:

$ gst-inspect-1.0 photography
No such element or plugin 'photography'

It is a part of a package called gst-plugins-bad, so I installed this one:

$ brew install gst-plugins-bad

But surprisingly the build still failed (even after removing everything from the build folder and re-configuring Qt). Then I realized that Qt actually doesn’t need GStreamer anymore - starting from Qt 6.5.1 the default multimedia backend is FFmpeg. So I just uninstalled GStreamer:

$ brew uninstall gstreamer

and re-configured Qt. This time it didn’t discover GStreamer and didn’t enable related features. And then the build finally succeeded, and I could install Qt:

$ cmake --install .

To verify that it’s a proper working build, I tried to use it to compile an application (my world-famous Color Corners):

$ cd ~/code
$ git clone git@github.com:retifrav/color-corners.git
$ cd ./color-corners
$ mkdir build && cd $_
$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_PREFIX_PATH="/home/deck/programs/qt/6.5.1-static" \
    ..

…but it failed, although with a harmless error first:

CMake Warning at /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6/Qt6Config.cmake:157 (find_package):
  Found package configuration file:

    /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6Core/Qt6CoreConfig.cmake

  but it set Qt6Core_FOUND to FALSE so package "Qt6Core" is considered to be
  NOT FOUND.  Reason given by package:

  Qt6Core could not be found because dependency WrapZLIB could not be found.

or, in case of a shared Qt build:

CMake Warning at /home/linuxbrew/.linuxbrew/Cellar/cmake/3.26.2/share/cmake/Modules/CMakeFindDependencyMacro.cmake:76 (find_package):
  Found package configuration file:

    /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6Gui/Qt6GuiConfig.cmake

  but it set Qt6Gui_FOUND to FALSE so package "Qt6Gui" is considered to be
  NOT FOUND.  Reason given by package:

  Qt6Gui could not be found because dependency WrapOpenGL could not be found.

That is because the path to Homebrew prefix needs to be provided too:

$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_PREFIX_PATH="/home/linuxbrew/.linuxbrew;/home/deck/programs/qt/6.5.1-static" \
    ..

Then the project configures further, but shortly after either fails like this (if it is a static Qt):

/home/deck/programs/qt/6.5.1-static/./libexec/qmlimportscanner: error while loading shared libraries: libb2.so.1: cannot open shared object file: No such file or directory
CMake Error at /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake:2837 (message):
  Failed to scan target color-corners for QML imports: 127
Call Stack (most recent call first):
  /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake:2872 (_qt_internal_scan_qml_imports)
  /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6Qml/Qt6QmlMacros.cmake:2944 (qt6_import_qml_plugins)
  CMakeLists.txt:159 (qt_import_qml_plugins)

or configures fine but fails to build (if it is a shared Qt):

$ cmake --build .
[1/7] Running qmlimportscanner for color-corners
FAILED: .qt_plugins/Qt6_QmlPlugins_Imports_color-corners.cmake /home/deck/code/color-corners/build/.qt_plugins/Qt6_QmlPlugins_Imports_color-corners.cmake 
cd /home/deck/code/color-corners && /home/deck/programs/qt/6.5.1/./libexec/qmlimportscanner @/home/deck/code/color-corners/build/.qt_plugins/Qt6_QmlPlugins_Imports_color-corners.rsp
/home/deck/programs/qt/6.5.1/./libexec/qmlimportscanner: error while loading shared libraries: libicui18n.so.72: cannot open shared object file: No such file or directory
ninja: build stopped: subcommand failed.

This is because one also needs to provide path (in LD_LIBRARY_PATH) to shared libraries that are required for Qt tools (such as qmlimportscanner) on project configuration:

$ LD_LIBRARY_PATH="/home/linuxbrew/.linuxbrew/lib" cmake -G Ninja -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_PREFIX_PATH="/home/linuxbrew/.linuxbrew;/home/deck/programs/qt/6.5.1-static" \
    ..

or for building:

$ LD_LIBRARY_PATH="/home/linuxbrew/.linuxbrew/lib" cmake --build .

Then I was able to build my application, but it failed to run:

$ ./color-corners
qt.qpa.plugin: Could not find the Qt platform plugin "xcb" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: wayland, wayland-egl, eglfs, minimal, vnc, offscreen, linuxfb, minimalegl.

If I paid attention to the Qt configuration output, I would have noticed that XCB backend feature wasn’t enabled. Fortunately, this is again just a matter of installing missing dependencies, which in case of Homebrew are these:

$ brew install xcb-util xcb-util-image xcb-util-renderutil xcb-proto xcb-util-cursor xcb-util-keysyms xcb-util-wm \
    libxinerama libxi libxkbcommon libxrandr libxrender libxt libxv \
    mesa mesa-glu \
    xinput xorgproto

…even though all of these (and some more) were already available in system’s /usr/lib/, but for some reason aren’t picked up, so one needs to install them via Homebrew. I am actually not sure if all of these are needed, plus there might be more that are required, so just in case here’s a full list of Homebrew packages that I had installed back then.

Anyway, after that Qt configuration successfully discovered XCB:

Qt Gui:
  Accessibility .......................... yes
  FreeType ............................... yes
    Using system FreeType ................ yes
  HarfBuzz ............................... yes
    Using system HarfBuzz ................ yes
  Fontconfig ............................. yes
  Image formats:
    GIF .................................. yes
    ICO .................................. yes
    JPEG ................................. yes
      Using system libjpeg ............... yes
    PNG .................................. yes
      Using system libpng ................ yes
  Text formats:
    HtmlParser ........................... yes
    CssParser ............................ yes
    OdfWriter ............................ yes
    MarkdownReader ....................... yes
      Using system libmd4c ............... yes
    MarkdownWriter ....................... yes
  EGL .................................... yes
  OpenVG ................................. no
  OpenGL:
    Desktop OpenGL ....................... yes
    OpenGL ES 2.0 ........................ no
    OpenGL ES 3.0 ........................ no
    OpenGL ES 3.1 ........................ no
    OpenGL ES 3.2 ........................ no
  Vulkan ................................. yes
  Session Management ..................... yes
Features used by QPA backends:
  evdev .................................. yes
  libinput ............................... no
  HiRes wheel support in libinput ........ no
  INTEGRITY HID .......................... no
  mtdev .................................. no
  tslib .................................. no
  xkbcommon .............................. yes
  X11 specific:
    XLib ................................. yes
    XCB Xlib ............................. yes
    EGL on X11 ........................... yes
    xkbcommon-x11 ........................ yes
    xcb-sm ............................... yes
QPA backends:
  DirectFB ............................... no
  EGLFS .................................. yes
  EGLFS details:
    EGLFS OpenWFD ........................ no
    EGLFS i.Mx6 .......................... no
    EGLFS i.Mx6 Wayland .................. no
    EGLFS RCAR ........................... no
    EGLFS EGLDevice ...................... yes
    EGLFS GBM ............................ yes
    EGLFS VSP2 ........................... no
    EGLFS Mali ........................... no
    EGLFS Raspberry Pi ................... no
    EGLFS X11 ............................ yes
  LinuxFB ................................ yes
  VNC .................................... yes
  VK_KHR_display ......................... yes
  QNX:
    lgmon ................................ no
    IMF .................................. no
  XCB:
    Using system-provided xcb-xinput ..... no
    GL integrations:
      GLX Plugin ......................... yes
        XCB GLX .......................... yes
      EGL-X11 Plugin ..................... yes

Before that the xcb-sm feature was set to no, and XCB section in QPA backends also had no on all items.

Having re-built Qt again, I finally got the XCB plugin built too:

$ ls -L1 /home/deck/programs/qt/6.5.1-static/plugins/platforms/*.a
libqeglfs.a
libqlinuxfb.a
libqminimal.a
libqminimalegl.a
libqoffscreen.a
libqvkkhrdisplay.a
libqvnc.a
libqwayland-egl.a
libqwayland-generic.a
libqxcb.a

And then the application built and ran just fine:

Steam Deck, Color Corners application built with Qt

What’s a bit weird is the amount of dependencies reported by ldd:

$ ldd ./color-corners
        linux-vdso.so.1 (0x00007ffc5fb30000)
        libSM.so.6 => /home/linuxbrew/.linuxbrew/lib/libSM.so.6 (0x00007f2ab197d000)
        libICE.so.6 => /home/linuxbrew/.linuxbrew/lib/libICE.so.6 (0x00007f2ab195b000)
        libX11.so.6 => /home/linuxbrew/.linuxbrew/lib/libX11.so.6 (0x00007f2ab1838000)
        libXext.so.6 => /home/linuxbrew/.linuxbrew/lib/libXext.so.6 (0x00007f2ab1821000)
        libdrm.so.2 => /home/linuxbrew/.linuxbrew/lib/libdrm.so.2 (0x00007f2ab1805000)
        libgbm.so.1 => /home/linuxbrew/.linuxbrew/lib/libgbm.so.1 (0x00007f2ab17f2000)
        libjpeg.so.8 => /home/linuxbrew/.linuxbrew/lib/libjpeg.so.8 (0x00007f2ab1747000)
        libtiff.so.6 => /home/linuxbrew/.linuxbrew/lib/libtiff.so.6 (0x00007f2ab16be000)
        libwebpdemux.so.2 => /home/linuxbrew/.linuxbrew/lib/libwebpdemux.so.2 (0x00007f2ab16b6000)
        libwebpmux.so.3 => /home/linuxbrew/.linuxbrew/lib/libwebpmux.so.3 (0x00007f2ab16a8000)
        libwebp.so.7 => /home/linuxbrew/.linuxbrew/lib/libwebp.so.7 (0x00007f2ab15ff000)
        libsharpyuv.so.0 => /home/linuxbrew/.linuxbrew/lib/libsharpyuv.so.0 (0x00007f2ab15f4000)
        libxcb-glx.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-glx.so.0 (0x00007f2ab15d1000)
        libX11-xcb.so.1 => /home/linuxbrew/.linuxbrew/lib/libX11-xcb.so.1 (0x00007f2ab15cb000)
        libxkbcommon-x11.so.0 => /home/linuxbrew/.linuxbrew/lib/libxkbcommon-x11.so.0 (0x00007f2ab15be000)
        libxkbcommon.so.0 => /home/linuxbrew/.linuxbrew/lib/libxkbcommon.so.0 (0x00007f2ab1575000)
        libxcb-cursor.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-cursor.so.0 (0x00007f2ab156b000)
        libxcb-icccm.so.4 => /home/linuxbrew/.linuxbrew/lib/libxcb-icccm.so.4 (0x00007f2ab1561000)
        libxcb-image.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-image.so.0 (0x00007f2ab1559000)
        libxcb-keysyms.so.1 => /home/linuxbrew/.linuxbrew/lib/libxcb-keysyms.so.1 (0x00007f2ab1553000)
        libxcb-randr.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-randr.so.0 (0x00007f2ab153c000)
        libxcb-render-util.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-render-util.so.0 (0x00007f2ab1534000)
        libxcb-shm.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-shm.so.0 (0x00007f2ab152c000)
        libxcb-sync.so.1 => /home/linuxbrew/.linuxbrew/lib/libxcb-sync.so.1 (0x00007f2ab1521000)
        libxcb-xfixes.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-xfixes.so.0 (0x00007f2ab1515000)
        libxcb-render.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-render.so.0 (0x00007f2ab1503000)
        libxcb-shape.so.0 => /home/linuxbrew/.linuxbrew/lib/libxcb-shape.so.0 (0x00007f2ab14fc000)
        libxcb-xkb.so.1 => /home/linuxbrew/.linuxbrew/lib/libxcb-xkb.so.1 (0x00007f2ab14d8000)
        libxcb.so.1 => /home/linuxbrew/.linuxbrew/lib/libxcb.so.1 (0x00007f2ab14a5000)
        libEGL.so.1 => /home/linuxbrew/.linuxbrew/lib/libEGL.so.1 (0x00007f2ab145a000)
        libGLX.so.0 => /usr/lib/libGLX.so.0 (0x00007f2ab140a000)
        libOpenGL.so.0 => /usr/lib/libOpenGL.so.0 (0x00007f2ab13df000)
        libpng16.so.16 => /home/linuxbrew/.linuxbrew/lib/libpng16.so.16 (0x00007f2ab13a6000)
        libharfbuzz.so.0 => /home/linuxbrew/.linuxbrew/lib/libharfbuzz.so.0 (0x00007f2ab127e000)
        libmd4c.so.0 => /home/linuxbrew/.linuxbrew/lib/libmd4c.so.0 (0x00007f2ab1000000)
        libfreetype.so.6 => /home/linuxbrew/.linuxbrew/lib/libfreetype.so.6 (0x00007f2ab0f46000)
        libfontconfig.so.1 => /home/linuxbrew/.linuxbrew/lib/libfontconfig.so.1 (0x00007f2ab122d000)
        libgobject-2.0.so.0 => /home/linuxbrew/.linuxbrew/lib/libgobject-2.0.so.0 (0x00007f2ab0edb000)
        libgio-2.0.so.0 => /home/linuxbrew/.linuxbrew/lib/libgio-2.0.so.0 (0x00007f2ab0cc8000)
        libdbus-1.so.3 => /home/linuxbrew/.linuxbrew/lib/libdbus-1.so.3 (0x00007f2ab0c69000)
        libz.so.1 => /home/linuxbrew/.linuxbrew/lib/libz.so.1 (0x00007f2ab0c4e000)
        libdouble-conversion.so.3 => /home/linuxbrew/.linuxbrew/lib/libdouble-conversion.so.3 (0x00007f2ab1217000)
        libb2.so.1 => /home/linuxbrew/.linuxbrew/lib/libb2.so.1 (0x00007f2ab0a00000)
        libicui18n.so.72 => /home/linuxbrew/.linuxbrew/lib/libicui18n.so.72 (0x00007f2ab0639000)
        libicuuc.so.72 => /home/linuxbrew/.linuxbrew/lib/libicuuc.so.72 (0x00007f2ab0409000)
        libicudata.so.72 => /home/linuxbrew/.linuxbrew/lib/libicudata.so.72 (0x00007f2aae638000)
        libpcre2-16.so.0 => /home/linuxbrew/.linuxbrew/lib/libpcre2-16.so.0 (0x00007f2aae5a7000)
        libglib-2.0.so.0 => /home/linuxbrew/.linuxbrew/lib/libglib-2.0.so.0 (0x00007f2aae44d000)
        libgthread-2.0.so.0 => /home/linuxbrew/.linuxbrew/lib/libgthread-2.0.so.0 (0x00007f2ab0c48000)
        libbrotlidec.so.1 => /home/linuxbrew/.linuxbrew/lib/libbrotlidec.so.1 (0x00007f2aae200000)
        libgssapi_krb5.so.2 => /home/linuxbrew/.linuxbrew/lib/libgssapi_krb5.so.2 (0x00007f2aae1a9000)
        libstdc++.so.6 => /home/linuxbrew/.linuxbrew/lib/libstdc++.so.6 (0x00007f2aadef3000)
        libm.so.6 => /usr/lib/libm.so.6 (0x00007f2aade0b000)
        libgcc_s.so.1 => /home/linuxbrew/.linuxbrew/lib/libgcc_s.so.1 (0x00007f2ab0c21000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f2aadc24000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f2ab3399000)
        libwayland-server.so.0 => /home/linuxbrew/.linuxbrew/opt/wayland/lib/libwayland-server.so.0 (0x00007f2aae433000)
        libexpat.so.1 => /home/linuxbrew/.linuxbrew/opt/expat/lib/libexpat.so.1 (0x00007f2aadbf2000)
        libzstd.so.1 => /home/linuxbrew/.linuxbrew/opt/zstd/lib/libzstd.so.1 (0x00007f2aadadf000)
        libxcb-util.so.1 => /home/linuxbrew/.linuxbrew/opt/xcb-util/lib/libxcb-util.so.1 (0x00007f2aae429000)
        libXau.so.6 => /home/linuxbrew/.linuxbrew/opt/libxau/lib/libXau.so.6 (0x00007f2aae420000)
        libXdmcp.so.6 => /home/linuxbrew/.linuxbrew/opt/libxdmcp/lib/libXdmcp.so.6 (0x00007f2aae417000)
        libglapi.so.0 => /home/linuxbrew/.linuxbrew/Cellar/mesa/22.3.6_1/lib/libglapi.so.0 (0x00007f2aadaa3000)
        libxcb-dri2.so.0 => /home/linuxbrew/.linuxbrew/opt/libxcb/lib/libxcb-dri2.so.0 (0x00007f2aae40f000)
        libwayland-client.so.0 => /home/linuxbrew/.linuxbrew/opt/wayland/lib/libwayland-client.so.0 (0x00007f2aada8d000)
        libxcb-dri3.so.0 => /home/linuxbrew/.linuxbrew/opt/libxcb/lib/libxcb-dri3.so.0 (0x00007f2aada85000)
        libxcb-present.so.0 => /home/linuxbrew/.linuxbrew/opt/libxcb/lib/libxcb-present.so.0 (0x00007f2aada7f000)
        libxshmfence.so.1 => /home/linuxbrew/.linuxbrew/opt/libxshmfence/lib/libxshmfence.so.1 (0x00007f2aada79000)
        libGLdispatch.so.0 => /usr/lib/libGLdispatch.so.0 (0x00007f2aad9c1000)
        libgraphite2.so.3 => /home/linuxbrew/.linuxbrew/opt/graphite2/lib/libgraphite2.so.3 (0x00007f2aad600000)
        libbz2.so.1.0 => /home/linuxbrew/.linuxbrew/opt/bzip2/lib/libbz2.so.1.0 (0x00007f2aad9a9000)
        libffi.so.8 => /home/linuxbrew/.linuxbrew/opt/libffi/lib/libffi.so.8 (0x00007f2aad998000)
        libgmodule-2.0.so.0 => /home/linuxbrew/.linuxbrew/Cellar/glib/2.74.6/lib/libgmodule-2.0.so.0 (0x00007f2aad990000)
        libmount.so.1 => /home/linuxbrew/.linuxbrew/opt/util-linux/lib/libmount.so.1 (0x00007f2aad926000)
        libgomp.so.1 => /home/linuxbrew/.linuxbrew/lib/libgomp.so.1 (0x00007f2aad8d1000)
        libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007f2aad8cc000)
        libpcre2-8.so.0 => /home/linuxbrew/.linuxbrew/opt/pcre2/lib/libpcre2-8.so.0 (0x00007f2aad82d000)
        libbrotlicommon.so.1 => /home/linuxbrew/.linuxbrew/Cellar/brotli/1.0.9/lib/libbrotlicommon.so.1 (0x00007f2aad200000)
        libkrb5.so.3 => /home/linuxbrew/.linuxbrew/Cellar/krb5/1.20.1/lib/libkrb5.so.3 (0x00007f2aad51f000)
        libk5crypto.so.3 => /home/linuxbrew/.linuxbrew/Cellar/krb5/1.20.1/lib/libk5crypto.so.3 (0x00007f2aad4ee000)
        libcom_err.so.3 => /home/linuxbrew/.linuxbrew/Cellar/krb5/1.20.1/lib/libcom_err.so.3 (0x00007f2aad4e7000)
        libkrb5support.so.0 => /home/linuxbrew/.linuxbrew/Cellar/krb5/1.20.1/lib/libkrb5support.so.0 (0x00007f2aad4d6000)
        libblkid.so.1 => /home/linuxbrew/.linuxbrew/Cellar/util-linux/2.38.1/lib/libblkid.so.1 (0x00007f2aad47a000)
        libresolv.so.2 => /usr/lib/libresolv.so.2 (0x00007f2aad468000)

But I guess that’s okay, especially that there is no Qt in the list, as I linked against static build.

Build times

Out of curiosity I’ve built the same static Qt 6.5.1 configuration on several other hosts that I have, and here are the build times for each (from fastest to slowest):

Device OS CPU Cores Time (minutes)
1 MacBook Pro Mac OS 13.6 Apple M2 Pro 12 12
2 Office desktop PC Windows 10 Intel Core i9-9900K 8/16 30
3 MacBook Pro Mac OS 13.4 Intel Core i7-8850H 6/12 40
4 Steam Deck SteamOS 3.4.6 AMD Zen 2 4/8 75
5 Laptop Ubuntu 22.04 Intel Core i7-4710MQ 4/8 87
6 Home desktop PC Windows 11 Intel Core i5-4670k 4/4 108

Quite not bad, is it. Especially considering the price of each device (Steam Deck is the cheapest).

The temperature reported by sensors during all 75 minutes of the build time was on a level of 84°C with rare spikes to 88°C.

It would also be interesting to measure how fast the battery goes from 100% to 0%, but I guess it will be the same as with demanding games, so about 1.5 hours at best. I tried it once when I had 71% of charge, and that lasted only to build a bit more than a half, so I think 100% might not be enough either. Still, I’ll try it with a fully charged battery at some point later.

Problem with VAAPI in Qt Multimedia static build

I have an application that uses Qt Multimedia, and while building it with shared Qt goes fine, it fails to build with static Qt:

CMake Error at /home/deck/programs/qt/6.5.1-static/lib/cmake/Qt6Multimedia/Qt6QFFmpegMediaPluginTargets.cmake:61 (set_target_properties):
  The link interface of target "Qt6::QFFmpegMediaPlugin" contains:

    VAAPI::VAAPI

  but the target was not found.  Possible reasons include:

    * There is a typo in the target name.
    * A find_package call is missing for an IMPORTED target.
    * An ALIAS target is missing.

Since it builds fine with shared Qt, this probably means that static Qt expects a static build of that VAAPI thing? But where should it come from, is it a FFmpeg/VAAPI thing or is it something else?

I tried building FFmpeg from sources using Qt’s Coin script in /path/to/qt/src/6.5.1/coin/provisioning/qtci-linux-Ubuntu-22.04-x86_64/90-install-ffmpeg.sh and then configuring Qt with -- -DFFMPEG_DIR="/path/to/ffmpeg/install" (that build looked static to me, because it had .a libraries), but still my application failed to configure even with that Qt build.

I couldn’t figure out this problem yet.

Inspecting applications with RenderDoc

If you work with something that puts pixels on a screen, you’ll probably want to inspect your application/game with RenderDoc. It does support GNU/Linux hosts, and in fact renderdoccmd is already available on Steam Deck out of the box (unless it was me who installed it at some point):

$ renderdoccmd --version
renderdoccmd x64 v1.22 built from NO_GIT_COMMIT_HASH_DEFINED
Packaged for SteamOS (1.22) - joshua@froggi.es
APIs supported at compile-time: Vulkan, GL, GLES.
Windowing systems supported at compile-time: xlib, XCB, Vulkan KHR_display.

$ which renderdoccmd
/usr/bin/renderdoccmd

Either way, you can also download it from its website; and you’ll have to do that if you need the GUI application (qrenderdoc), as, unlike renderdoccmd, it isn’t available in the system by default. Once you download it, keep in mind that you might need to register that one for Vulkan capture instead of the default one. I kept the default one, just in case (I only needed the GUI anyway).

Let’s try to inspect an OpenGL application, such as this one:

$ renderdoccmd capture "/path/to/glfw-imgui"

The application will launch and you’ll see RenderDoc’s info in the top left corner:

RenderDoc, capturing an OpenGL application

If you press F12, it will make a capture and save it to /tmp/RenderDoc/glfw-imgui_2023.07.30_15.20_frame6983.rdc file. That file can now be openned in RenderDoc GUI (File → Open Capture):

RenderDoc, viewing an OpenGL capture

You can inspect Vulkan applications as well, here’s an example with a default vkcube sample:

$ renderdoccmd capture "vkcube"

Alternatively, in case of Vulkan applications, you can just set the ENABLE_VULKAN_RENDERDOC_CAPTURE environment variable:

$ ENABLE_VULKAN_RENDERDOC_CAPTURE=1 vkcube

It will again launch the application with RenderDoc info in the top-left corner:

RenderDoc, capturing a Vulkan application

If you’d like to inspect captures in RenderDoc GUI application right on Steam Deck, then you better do that with an external display, because the GUI is rather packed with controls and widgets to be viewed on Steam Deck’s own screen.

You can also move the capture file over to your main machine with a big screen and open it there, but chances are you have a different GPU on it, and so the capture will fail to load:

RenderDoc, error opening capture

To be able to view it on that host, you’ll need to run renderdoccmd on Steam Deck in server mode:

$ renderdoccmd remoteserver -p 4321

then discover it on your main host:

RenderDoc, remote host manager

and then you’ll be able to choose Remote option:

RenderDoc, openning capture on remote host

and that will work:

RenderDoc, viewing a Vulkan capture

You should also be able to launch applications remotely, which should eliminate the step of manually transferring capture files from Steam Deck to you main host. I could indeed launch an application remotely, but then I didn’t find a way to remotely trigger a capture and view captured results.

Capturing a Windows application running with Proton

In general, it should be just a matter of running that application/game with Proton and wrapping that call into renderdoccmd capture:

$ STEAM_COMPAT_CLIENT_INSTALL_PATH=/home/deck/.steam/steam \
    STEAM_COMPAT_DATA_PATH=/home/deck/.local/share/Steam/steamapps/compatdata/2718988064 \
    renderdoccmd capture /home/deck/.local/share/Steam/compatibilitytools.d/GE-Proton8-4/proton waitforexitandrun \
    /run/media/mmcblk0p1/games/diablo-2-resurrected/D2R.exe -launch

But for me that just ran the game without RenderDoc capturing it. And I didn’t find a way to make it work with renderdoccmd capture, but fortunately this game is using Vulkan (or rather DirectX via Vulkan?), and so the variant with setting environment variable works fine:

$ ENABLE_VULKAN_RENDERDOC_CAPTURE=1 \
    STEAM_COMPAT_CLIENT_INSTALL_PATH=/home/deck/.steam/steam \
    STEAM_COMPAT_DATA_PATH=/home/deck/.local/share/Steam/steamapps/compatdata/2718988064 \
    /home/deck/.local/share/Steam/compatibilitytools.d/GE-Proton8-4/proton waitforexitandrun \
    /run/media/mmcblk0p1/games/diablo-2-resurrected/D2R.exe -launch

RenderDoc, capturing Diablo II Resurrected

RenderDoc, viewing Diablo II Resurrected

Obviously, if that game/application is already added to your Steam library, then you can just set this variable in its launch options instead of running it from bare CLI.

What about IDEs

I reckon, it’s the same as with any other GNU/Linux hosts - whatever you can install there, most likely can be installed on Steam Deck too. And if you are connecting to it via SSH, then you probably will use IDEs that you have on your main machine anyway.

Personally, I do not care much about IDEs, because, as you might have noticed, I prefer to build stuff from CLI. The code editing is a different story, of course, but even then I do not like full-blown IDEs, as they are too “slow” and “clumsy” for me.

My absolute favorite text/code editor is Sublime Text. It can be installed on Steam Deck either with pacman or via direct download of a portable archive (64 bit .tar.xz option). There is also a version published on Flathub, but it is quite old and isn’t official, so it is not recommended to install that one. Speaking about C++ development in particular, LSP and clangd make it almost as good as a “proper” IDE without getting as bloated:

If video doesn’t play in your browser, you can download it here.

If you prefer text editors based on web-browsers, then you can download and run Visual Studio Code. This one provides a portable archive option too, and LSP support goes without saying.

For Qt development one might want to use Qt Creator, and that one can be installed with an official installer. There is one issue with it, though - out of the box the text and background colors of application menu items are set to something unreadable (probably can be fixed somewhere in Plasma theme settings):

If video doesn’t play in your browser, you can download it here.

Lastly, there is a default(?) Kate editor, which is quite good too and seems to support LSP as well.

So, is it worth it

In my opinion, Steam Deck makes a rather decent development host. As a matter of fact, I plan to publish one of my applications on Flathub with Steam Deck being one of the target platforms, and I am currently building and testing that application right on the device.

It gets even better if you are don’t have enough money to purchase a proper computer and just want something affordable that is capable of building stuff with acceptable performance.

When I was a student, I’d be more than happy to get it, especially remembering how I had ASUS Eee 1025C back in my days. It would be like a dream come true: an affordable portable computer for making notes on lectures/seminars at the university, which at the same time is powerful enough for working on assignments, and you can also play games on it.