Skip to content

[WIP] Qt 5.15 static build, help needed#136107

Closed
SCOTT-HAMILTON wants to merge 37 commits intoNixOS:masterfrom
SCOTT-HAMILTON:UnoconvUI-static
Closed

[WIP] Qt 5.15 static build, help needed#136107
SCOTT-HAMILTON wants to merge 37 commits intoNixOS:masterfrom
SCOTT-HAMILTON:UnoconvUI-static

Conversation

@SCOTT-HAMILTON
Copy link
Contributor

@SCOTT-HAMILTON SCOTT-HAMILTON commented Aug 29, 2021

Motivation for this change

I think it's useful to have static builds for a few reasons :

  • debugging
  • easily shipping standalone binaries to other linux distros
  • avoiding runtime dependencies
  • reducing startup time
Things done

I managed to get pkgsStatic.libsForQt515.qtbase build successfully.
But I couldn't get pkgsStatic.libsForQt515.qtsvg to build against it. Fixed !

The goal of this is to get at least one simple Qt Gui app to build statically.

The guidelines I tried to follow for this contribution are :

  • to use the least amount of patches (preferring sedding and other postPatch tricks to regular patches) because I consider them as technical debt
  • to interfer the least with the non static derivations using stdenv.hostPlatform.isStatic conditional extensively or only adding an override in pkgs/top-level/static.nix
Changes made
  • p11-kit: using meson instead of autotools (no maintainers in the meta ?)
  • mesonShlibsToStaticHook: a preconfigure setup hook that replaces all shared_library calls to static_library calls in meson.build files
  • dbus: use autoreconfHook instead of relying on provided configure script, it's much easier to patch an M4 script rather than patching a configure script @NixOS/freedesktop
  • libglvnd-meson: same as libglvnd but built with meson @primeos
  • NIX_MESON_DEPENDENCY_STATIC: a patch to meson that allows to force static linking of all dependencies if this environment variable is set. It seemed easier to do it like this rather than using a preconfigure setup hook like mesonShlibsToStaticHook which would try to force the static: true kwarg on all dependency() function calls (probably impossible to get write with a sed, it might be possible with some tools like beautifulsoup though)
  • libselinux: had to patch the Makefile to disable shared libs building @Phreedom
  • libudev-zero: systemd is too much work to get to build with musl-libc and qtbase really only depends on udev, so I've overriden pkgsStatic.udev to point to this "daemonless" implementation: https://github.com/illiliti/libudev-zero (used by Alpine OS) @andir @edolstra @flokli @kloenk
  • systemdMinimal: also made systemdMinimal point to libudev-zero, this is probably not good and should be fixed by replacing every use of systemdMinimal with the use of udev when really only the latter one is needed.
  • at-spi2-atk: not sure why but G_LOG_DOMAIN isn't defined and thus the static build crashes, fixed it with NIX_CFLAGS_COMPILE setting it to an empty string. I don't know if there is a specific NIX environment variable for such C preprocessor defines. @NixOS/gnome
  • postgresql: couldn't make it build statically, it's a C library that uses libicuuc which is a c++ library. c++ specific symbols are needed when linking against libicuuc which aren't easy to provide to gcc. I think linking with (libc++,libc++abi) or libstdc++ might fix it, help is needed there. Meanwhile, I've disabled it's static build but I think that we shouldn't merge this PR until postgresql builds.
  • gobject-introspection: had to disable the build of python bindings because they involve building shared libraries which is impossible to do with the "-static" flag enforced by the static adapter. Maybe this could be fixed by using super.stdenv or cleaning NIX_CFLAGS_COMPILE before building the bindings. But I think this would require to rebuild some stuff with -fPIC. @NixOS/gnome
  • gtk3: since the gobject-introspection's python bindings aren't built, I had to disable introspection, @NixOS/gnome
  • pyudev: replaced systemd dependency with udev.
  • qtbase: had to patch a few configure.json files to have it find the dependencies and link all the needed ones. I also had to make it build with clang, against libc++ and libc++abi as it seems like llvm toolchains are easier to use when building statically. This required that I also build libicuuc with the same compiler runtime libs. Finally, the postInstall and fixup phase aren't passing when building statically. This is unfortunate since debugging this requires to wait for the whole build to finish. I separated the build in two stages to temporairly work on those phases to make them work but help is needed to properly figure out what's going wrong. @NixOS/qt-kde

And while pkgsStatic.libsForQt515.qtbase builds successfully, it doesn't seem to work, pkgsStatic.libsForQt515.qtsvg doesn't build against it. This PR shouldn't be merge until both pkgsStatic.postgresql and pkgsStatic.libsForQt515.qtsvg build successfully. Fixed !

Status

I can build a simple Qt app of mine statically !

  • Built on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin
  • For non-Linux: Is sandbox = true set in nix.conf? (See Nix manual)
  • Tested via one or more NixOS test(s) if existing and applicable for the change (look inside nixos/tests)
  • Tested compilation of all packages that depend on this change using nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
  • Tested execution of all binary files (usually in ./result/bin/)
  • 21.11 Release Notes (or backporting 21.05 Release notes)
    • (Package updates) Added a release notes entry if the change is major or breaking
    • (Module updates) Added a release notes entry if the change is significant
    • (Module addition) Added a release notes entry if adding a new NixOS module
  • Fits CONTRIBUTING.md.

@github-actions github-actions bot added 6.topic: python Python is a high-level, general-purpose programming language. 6.topic: qt/kde Object-oriented framework for GUI creation labels Aug 29, 2021
@SCOTT-HAMILTON SCOTT-HAMILTON marked this pull request as draft August 29, 2021 18:04
@jtojnar
Copy link
Member

jtojnar commented Aug 29, 2021

mesonShlibsToStaticHook: a preconfigure setup hook that replaces all shared_library calls to static_library calls in meson.build files

  • How does e.g. Alpine handle this?
  • Would it be possible to use environment variable too (or rather a build flag so it is more amenable to upstreaming)?

dbus: use autoreconfHook instead of relying on provided configure script, it's much easier to patch an M4 script rather than patching a configure script NixOS/freedesktop

Sounds good. The next version will hopefully use Meson.

libglvnd-meson: same as libglvnd but built with meson primeos

Why not do it in the main package?

at-spi2-atk: not sure why but G_LOG_DOMAIN isn't defined

Sounds like a build system bug.

systemdMinimal: also made systemdMinimal point to libudev-zero, this is probably not good and should be fixed by replacing every use of systemdMinimal with the use of udev when really only the latter one is needed.

Maybe we should bring back the use of udev attribute.

gobject-introspection: had to disable the build of python bindings

Afraid giscanner, and therefore also bindings generation, will not work without that. But do not think bindings would work with static libraries anyway (they are loaded at runtime).

qtbase

After #136071, it might be possible to stop linking Qt against GTK so perhaps most of this will be unnecessary for static Qt.

@SCOTT-HAMILTON
Copy link
Contributor Author

SCOTT-HAMILTON commented Aug 29, 2021

How does e.g. Alpine handle this?

Alpine Linux handles it the same way we separate dev and bin outputs except that they don't call it outputs but subpackages. So they build the library with both shared and static libs and then the "fixup" phase automatically splits the static libraries to the static subpackage ($pkgname-static). cf https://wiki.alpinelinux.org/wiki/APKBUILD_Reference#static.28.29

Why not do it in the main package? (regarding libglvnd-meson)

« To interfer the least with the non static derivations ».

@jtojnar
Copy link
Member

jtojnar commented Aug 29, 2021

Alpine Linux handles it the same way we separate dev and bin outputs except that they don't call it outputs but subpackages. So they build the library with both shared and static libs and then the "fixup" phase automatically splits the static libraries to the static subpackage ($pkgname-static). cf wiki.alpinelinux.org/wiki/APKBUILD_Reference#static.28.29

Oh, I thought Alpine was building primarily static libs (must have confused it with some other distro). I wondered how they convince the projects to build statically but if they just use the static libs built by default like we do then that will not help us.

Why not do it in the main package? (regarding libglvnd-meson)

« To interfer the least with the non static derivations ».

I assume non-static pkgs could use the Meson-built version too (and it is usually preferred these days) and then it would result in smaller static.nix.

@primeos
Copy link
Member

primeos commented Aug 29, 2021

libglvnd-meson: same as libglvnd but built with meson @primeos

Why not do it in the main package?

Also +1 for that. Switching libglvnd over to Meson was already on my TODO list but I didn't get around to it yet.

@symphorien
Copy link
Member

to interfer the least with the non static derivations using stdenv.hostPlatform.isStatic conditional extensively or only adding an override in pkgs/top-level/static.nix

pkgs/top-level/static.nix has been removed in staging, so you should modify the main derivations

@SCOTT-HAMILTON SCOTT-HAMILTON marked this pull request as ready for review September 3, 2021 21:54
@SCOTT-HAMILTON
Copy link
Contributor Author

This PR is ready for review, I can build a Qt app with it, I fixed the qtsvg, qtdeclarative and qttools modules.

clangCxxFlags = "-nostdinc++ -isystem ${libcxxIncDir}";
clangCxxLdflags = "-nostdlib++ -L ${libcxxIncDir} -Wl,-rpath,${libcxxLibDir} -lc++ -lc++abi";

# Building with libc++ implies that all statically linked c++ deps are also
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe you want to build pkgs.pkgsStatic.pkgsLLVM.qtbase then?
can you add a comment as to why you are using clang instead of the default compiler in stdenv? Doesn't that force all dependent apps to also compile with clang?

};

in {
in rec {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file does was removed on staging next, please move this to the main derivations and rebase.

at-spi2-core = super.at-spi2-core.overrideAttrs (old: {
NIX_MESON_DEPENDENCY_STATIC = true;
nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
super.mesonShlibsToStaticHook
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this hook should be propagated by meson when targetPlatform.isStatic ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that work ?

Details
diff --git a/pkgs/development/tools/build-managers/meson/default.nix b/pkgs/development/tools/build-managers/meson/default.nix
index 60ab7605944..219160c66a5 100644
--- a/pkgs/development/tools/build-managers/meson/default.nix
+++ b/pkgs/development/tools/build-managers/meson/default.nix
@@ -1,13 +1,14 @@
-{ lib
+{ stdenv
+, lib
 , python3
-
+, mesonShlibsToStaticHook
 , writeTextDir
 , substituteAll
 , fetchpatch
 , installShellFiles
 }:
 
-python3.pkgs.buildPythonApplication rec {
+python3.pkgs.buildPythonApplication (rec {
   pname = "meson";
   version = "0.57.1";
 
@@ -98,4 +99,6 @@ python3.pkgs.buildPythonApplication rec {
     maintainers = with maintainers; [ jtojnar mbe ];
     platforms = platforms.all;
   };
-}
+} // lib.optionalAttrs stdenv.hostPlatform.isStatic {
+  propagatedBuildInputs = [ mesonShlibsToStaticHook ];
+})

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually putting this in propagatedBuildInputs is wrong because it also affects transitive dependencies. What about incorporating this to pkgs/development/tools/build-managers/meson/setup-hook.sh gated behind if [ -n $mesonForceReplaceSharedLibsByStaticLibs ] and $mesonForceReplaceSharedLibsByStaticLibs defaults to targetPlatform.isStatic (you can pass the value from nix to setup-hook.sh by using the fact that according to the doc of setupHook https://nixos.org/manual/nixpkgs/stable/#ssec-fixup-phase @envvar@ is replaced by the value of envvar during the build of meson)

};

shared-mime-info = super.shared-mime-info.overrideAttrs (old: {
NIX_MESON_DEPENDENCY_STATIC = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this should be the default and one can disable this if this was wrong, like dontAddStaticConfigureFlag, what do you think ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add this logic in the makeStaticLibraries adapter. BTW I don't know why it's called like that because it's applied to both libraries and executables.

@symphorien
Copy link
Member

I think it would be preferable to focus on making pkgsStatic.pkgsLLVM.qtbase work and giveup on pkgsStatic.qtbase rather than hacking icu and postgresql to use llvm when compiling statically.

@SCOTT-HAMILTON
Copy link
Contributor Author

SCOTT-HAMILTON commented Sep 4, 2021

I chose to use clang mainly because "${pkgsStatic.stdenv.cc.cc.out}/include" is empty whereas "${stdenv.cc.cc.out}/include" isn't. So the easiest way to solve this was to include libc++ instead of relying on the missing include dirs of the static gcc.

I don't know if this has something to do with https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/compilers/gcc/no-sys-dirs.patch

@r-burns
Copy link
Contributor

r-burns commented Sep 16, 2021

I chose to use clang mainly because "${pkgsStatic.stdenv.cc.cc.out}/include" is empty whereas "${stdenv.cc.cc.out}/include" isn't. So the easiest way to solve this was to include libc++ instead of relying on the missing include dirs of the static gcc.

gcc's c++ include is fine afaict, it just isn't in $out/include but in a different internal dir when cross-compiling. It still shows up in e.g. $CC -E -v -

Not sure why icu has this linking issue, but setting postgresql's NIX_LDFLAGS to -lstdc++ -lz when using pkgsStatic and libstdc++ seems to fix the build, so I don't think any llvm-specific overrides are needed there.

@Ma27
Copy link
Member

Ma27 commented Oct 25, 2021

This contains quite a bunch of helpful fixes, any chance to get at least a subset into master before 21.11? :)

@alyssais
Copy link
Member

alyssais commented Nov 3, 2021

Could you please split this change up into smaller PRs? It's too big to review all at once and make a decision on, especially because different people need to make a call about different bits.

@SuperSandro2000 SuperSandro2000 added the 2.status: merge conflict This PR has merge conflicts with the target branch label Nov 3, 2021
@SCOTT-HAMILTON
Copy link
Contributor Author

Should I make 37 PRs ? One per commit ?

@SuperSandro2000
Copy link
Member

Should I make 37 PRs ? One per commit ?

qt can probably be combined and for the rest we can maybe also do batches of 3 or 5.

Comment on lines +4 to +5
runHook preMesonShlibsToStatic
replaceMesonFunction() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are mixing tabs and spaces. I think nixpkgs is always formatted with spaces.

# hence, libtasn1 is required in both native and build inputs.
nativeBuildInputs = [ autoreconfHook pkg-config which libtasn1 ];
nativeBuildInputs = [ meson ninja pkg-config which libtasn1 ] ++
lib.optional (stdenv.hostPlatform.isStatic) [ mesonShlibsToStaticHook ];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
lib.optional (stdenv.hostPlatform.isStatic) [ mesonShlibsToStaticHook ];
lib.optional (stdenv.hostPlatform.isStatic) mesonShlibsToStaticHook;

or use lib.optionals.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SuperSandro2000 as said above, this PR isn't meant to be merged any more, I'll make it a draft again and only use it as a reference for smaller PRs like this one #147600.

Once Qt5 statically builds on staging, I'll close it.

nativeBuildInputs = [ meson ninja pkg-config which libtasn1 ] ++
lib.optional (stdenv.hostPlatform.isStatic) [ mesonShlibsToStaticHook ];
buildInputs = [ gettext libffi libiconv libtasn1 ];
propagatedBuildInputs = [ bash-completion ];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to propogate this when using the path in line 33?

sha256 = "1cb2hwi859hds0fa2cbap014qaa7mah9p0rcxcm2cvj2ybl33qfc";
})
];
] ++ lib.optional (stdenv.hostPlatform.isStatic)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI The () here are optional.

Comment on lines +147 to +150
sed -iE 's!^QMAKE_CXXFLAGS.*!QMAKE_CXXFLAGS += ${clangCxxFlags}!g'\
mkspecs/linux-clang-libc++/qmake.conf
sed -iE 's!^QMAKE_LFLAGS.*!QMAKE_LFLAGS += ${clangCxxLdflags}!g'\
mkspecs/linux-clang-libc++/qmake.conf
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sed -iE 's!^QMAKE_CXXFLAGS.*!QMAKE_CXXFLAGS += ${clangCxxFlags}!g'\
mkspecs/linux-clang-libc++/qmake.conf
sed -iE 's!^QMAKE_LFLAGS.*!QMAKE_LFLAGS += ${clangCxxLdflags}!g'\
mkspecs/linux-clang-libc++/qmake.conf
sed -iE -e 's!^QMAKE_CXXFLAGS.*!QMAKE_CXXFLAGS += ${clangCxxFlags}!g' \
-e 's!^QMAKE_LFLAGS.*!QMAKE_LFLAGS += ${clangCxxLdflags}!g' \
mkspecs/linux-clang-libc++/qmake.conf

FYI this could be done like this but does not matter.

outputs = [ "bin" "dev" "out" ];
dontUnpack = true;

buildPhase = "";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
buildPhase = "";
dontBuild = true;

# it's just easier to separate the build from the fixup
# and do/tweak it in a second/final stage.
in if (!stdenv.hostPlatform.isStatic) then stage1 else (stdenv.mkDerivation {
pname = "qtbase-final";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pname = "qtbase-final";
pname = "qtbase";

It would be nicer to name the stages differently as this might break other things.

Comment on lines +42 to +45
} // lib.optionalAttrs stdenv.hostPlatform.isStatic {
preConfigure = ''
NIX_LDFLAGS+=" -lXdmcp -lXau -lexpat -lbz2"
'';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are rebuilding everything anyway we can also inline this.


LIBA=libselinux.a
-TARGET=libselinux.so
+# TARGET=libselinux.so
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
+# TARGET=libselinux.so

When rebasing this patch in the future it will be easier when the line is just deleted.

nativeBuildInputs = (old.nativeBuildInputs or []) ++ [
super.mesonShlibsToStaticHook
];
NIX_CFLAGS_COMPILE = "-DG_LOG_DOMAIN=\"\"\"\"";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what is being done here.

@SCOTT-HAMILTON SCOTT-HAMILTON marked this pull request as draft December 5, 2021 10:08
@SCOTT-HAMILTON
Copy link
Contributor Author

Closing to fix confusion.

@ShamrockLee
Copy link
Contributor

ShamrockLee commented Feb 19, 2022

There seems to be a pkgsStatic.libglvnd fix (i.e. 292a04e) needed to solve #157665. Would you please open a PR for it?

@ShamrockLee
Copy link
Contributor

ShamrockLee commented Mar 22, 2022

@SCOTT-HAMILTON Are you available to create a PR for 292a04e? This should fix the static build of OpenGL.

If not, may I create a PR with your changes and add your name as a package maintainer?

@SCOTT-HAMILTON
Copy link
Contributor Author

I'm on it, will have the time this evening.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2.status: merge conflict This PR has merge conflicts with the target branch 6.topic: python Python is a high-level, general-purpose programming language. 6.topic: qt/kde Object-oriented framework for GUI creation 8.has: package (new) This PR adds a new package 10.rebuild-darwin: 501+ This PR causes many rebuilds on Darwin and should normally target the staging branches. 10.rebuild-darwin: 5001+ This PR causes many rebuilds on Darwin and must target the staging branches. 10.rebuild-linux: 501+ This PR causes many rebuilds on Linux and should normally target the staging branches. 10.rebuild-linux: 5001+ This PR causes many rebuilds on Linux and must target the staging branches.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants