require 'pathname' require 'fileutils' include FileUtils STDOUT.sync = true ENV['PATH'] = ENV.fetch('_PATH') Os = ENV.fetch('os') QtVersionString = ENV.fetch('version') QtVersionMajor = QtVersionString.split('.').first.to_i QtBaseDir = Pathname(ENV.fetch('qtbase')) OutDir = Pathname(ENV.fetch('out')) OutPcDir = OutDir + 'lib' + 'pkgconfig' CMakeDir = OutDir + 'lib' + 'cmake' OutIncDir = OutDir + 'include' MocExe = OutDir + 'bin' + 'moc' RccExe = OutDir + 'bin' + 'rcc' DepGraph = {} DepGraphBack = {} DepInfo = {} DepInfo.default_proc = proc do |hash, name| hash[name] = find_dep_info(name) end case Os when "windows" PrlPrefix = '' else PrlPrefix = 'lib' end # Note: These dependencies just came from me fixing errors for specific # programs. There are likely misisng dependencies in this graph, and there # might be a few dependencies that could be safely removed because they are # purely transitive. def make_dep_graph # High-level dependencies. add_dep 'Qt5Widgets.x', 'libQt5Widgets.a' add_dep 'Qt5Widgets.x', 'Qt5Gui.x' add_dep 'Qt5Gui.x', 'Qt5GuiNoPlugins.x' add_dep 'Qt5GuiNoPlugins.x', 'libQt5Gui.a' add_dep 'Qt5GuiNoPlugins.x', 'Qt5Core.x' add_dep 'Qt5Core.x', 'libQt5Core.a' # Include directories. add_dep 'Qt5Core.x', '-I' + OutIncDir.to_s add_dep 'Qt5Core.x', '-I' + (OutIncDir + 'QtCore').to_s add_dep 'Qt5Gui.x', '-I' + (OutIncDir + 'QtGui').to_s add_dep 'Qt5Widgets.x', '-I' + (OutIncDir + 'QtWidgets').to_s # Libraries that Qt depends on. add_dep 'libQt5Widgets.a', 'libQt5Gui.a' add_dep 'libQt5FontDatabaseSupport.a', 'libqtfreetype.a' add_dep 'libQt5Gui.a', 'libQt5Core.a' add_dep 'libQt5Gui.a', 'libqtlibpng.a' add_dep 'libQt5Gui.a', 'libqtharfbuzz.a' add_dep 'libQt5Core.a', 'libqtpcre2.a' if Os == 'windows' add_dep 'Qt5Gui.x', 'qwindows.x' add_dep 'qwindows.x', 'libqwindows.a' add_dep 'libqwindows.a', '-ldwmapi' add_dep 'libqwindows.a', '-limm32' add_dep 'libqwindows.a', '-loleaut32' add_dep 'libqwindows.a', 'libQt5Gui.a' add_dep 'libqwindows.a', 'libQt5EventDispatcherSupport.a' add_dep 'libqwindows.a', 'libQt5FontDatabaseSupport.a' add_dep 'libqwindows.a', 'libQt5ThemeSupport.a' add_dep 'libQt5Core.a', '-lole32' add_dep 'libQt5Core.a', '-luuid' add_dep 'libQt5Core.a', '-lversion' add_dep 'libQt5Core.a', '-lwinmm' add_dep 'libQt5Core.a', '-lws2_32' add_dep 'libQt5Gui.a', '-lopengl32' add_dep 'libQt5Widgets.a', '-luxtheme' end if Os == 'linux' add_dep 'Qt5Gui.x', 'qlinuxfb.x' add_dep 'Qt5Gui.x', 'qxcb.x' add_dep 'qlinuxfb.x', 'libqlinuxfb.a' add_dep 'qxcb.x', 'libqxcb.a' add_dep 'libqlinuxfb.a', 'libQt5FbSupport.a' add_dep 'libqlinuxfb.a', 'libQt5InputSupport.a' add_dep 'libqxcb.a', 'libQt5XcbQpa.a' add_dep 'libQt5DBus.a', 'libQt5Core.a' add_dep 'libQt5DBus.a', 'libQt5Gui.a' add_dep 'libQt5DeviceDiscoverySupport.a', 'libudev.pc' add_dep 'libQt5InputSupport.a', 'libQt5DeviceDiscoverySupport.a' add_dep 'libQt5LinuxAccessibilitySupport.a', 'libQt5AccessibilitySupport.a' add_dep 'libQt5LinuxAccessibilitySupport.a', 'libQt5DBus.a' add_dep 'libQt5LinuxAccessibilitySupport.a', 'xcb-aux.pc' add_dep 'libQt5ThemeSupport.a', 'libQt5DBus.a' add_dep 'libQt5XcbQpa.a', 'libQt5EventDispatcherSupport.a' add_dep 'libQt5XcbQpa.a', 'libQt5FontDatabaseSupport.a' add_dep 'libQt5XcbQpa.a', 'libQt5Gui.a' add_dep 'libQt5XcbQpa.a', 'libQt5LinuxAccessibilitySupport.a' add_dep 'libQt5XcbQpa.a', 'libQt5ServiceSupport.a' add_dep 'libQt5XcbQpa.a', 'libQt5ThemeSupport.a' add_dep 'libQt5XcbQpa.a', 'x11.pc' add_dep 'libQt5XcbQpa.a', 'x11-xcb.pc' add_dep 'libQt5XcbQpa.a', 'xcb.pc' add_dep 'libQt5XcbQpa.a', 'xcb-icccm.pc' add_dep 'libQt5XcbQpa.a', 'xcb-image.pc' add_dep 'libQt5XcbQpa.a', 'xcb-keysyms.pc' add_dep 'libQt5XcbQpa.a', 'xcb-randr.pc' add_dep 'libQt5XcbQpa.a', 'xcb-renderutil.pc' add_dep 'libQt5XcbQpa.a', 'xcb-shape.pc' add_dep 'libQt5XcbQpa.a', 'xcb-shm.pc' add_dep 'libQt5XcbQpa.a', 'xcb-sync.pc' add_dep 'libQt5XcbQpa.a', 'xcb-xfixes.pc' add_dep 'libQt5XcbQpa.a', 'xcb-xinerama.pc' add_dep 'libQt5XcbQpa.a', 'xcb-xkb.pc' add_dep 'libQt5XcbQpa.a', 'xi.pc' end if Os == 'macos' add_dep 'Qt5Gui.x', 'qcocoa.x' add_dep 'qcocoa.x', 'libqcocoa.a' add_dep 'libqcocoa.a', 'libcocoaprintersupport.a' add_dep 'libqcocoa.a', '-lcups' # Also available: -lcups.2 add_dep 'libqcocoa.a', 'libQt5AccessibilitySupport.a' add_dep 'libqcocoa.a', 'libQt5ClipboardSupport.a' add_dep 'libqcocoa.a', 'libQt5CglSupport.a' add_dep 'libqcocoa.a', 'libQt5GraphicsSupport.a' add_dep 'libqcocoa.a', 'libQt5FontDatabaseSupport.a' add_dep 'libqcocoa.a', 'libQt5ThemeSupport.a' add_dep 'libqcocoa.a', 'libQt5PrintSupport.a' add_dep 'libqtlibpng.a', '-lz' add_dep 'libQt5Core.a', '-lobjc' add_dep 'libQt5Core.a', '-framework CoreServices' add_dep 'libQt5Core.a', '-framework CoreText' add_dep 'libQt5Gui.a', '-framework CoreGraphics' add_dep 'libQt5Gui.a', '-framework OpenGL' add_dep 'libQt5Widgets.a', '-framework Carbon' add_dep 'libQt5Widgets.a', '-framework AppKit' end add_deps_of_pc_files end # Qt depends on some system libraries with .pc files. It tends to only depend # on these things at link time, not compile time. So use pkg-config with --libs # to get those dependencies, for use in .cmake files. def add_deps_of_pc_files DepGraph.keys.each do |dep| next if determine_dep_type(dep) != :pc name = dep.chomp('.pc') new_deps = `pkg-config-cross --libs #{name}`.split(' ') raise "Failed to #{dep} libs" if $?.exitstatus != 0 new_deps.each do |new_dep| add_dep dep, new_dep end end end def add_dep(library, *deps) a = DepGraph[library] ||= [] DepGraphBack[library] ||= [] deps.each do |dep| DepGraph[dep] ||= [] a << dep unless a.include? dep (DepGraphBack[dep] ||= []) << library end end # Given a name of a dep in the graph, figure out what kind of dep # it use. def determine_dep_type(name) extension = Pathname(name).extname case when extension == '.a' then :a when extension == '.pc' then :pc when extension == '.x' then :x when name.start_with?('-I') then :incdirflag when name.start_with?('-L') then :libdirflag when name.start_with?('-l') then :ldflag when name.start_with?('-framework') then :ldflag end end def find_pkg_config_file(name) ENV.fetch('PKG_CONFIG_CROSS_PATH').split(':').each do |dir| path = Pathname(dir) + name return path if path.exist? end nil end def find_qt_library(name) debug_name = Pathname(name).sub_ext("d.a").to_s search_dirs = [ OutDir + 'lib' ] + (OutDir + 'plugins').children search_dirs.each do |dir| lib = dir + name return lib if lib.exist? end search_dirs.each do |dir| lib = dir + debug_name return lib if lib.exist? end nil end def find_dep_info(name) case determine_dep_type(name) when :a then find_qt_library(name) when :pc then find_pkg_config_file(name) end end # Given an array of dependencies and a block for retrieving dependencies of an # dependency, returns an array of dependencies with three guarantees: # # 1) Contains all the listed dependencies. # 2) Has no duplicates. # 3) For any dependency in the list, all of its dependencies are before it. # # Guarantee 3 only holds if the underlying graph has no circul dependencies. If # there is a circular dependency, it will not be detected, but it will not cause # an infinite loop either. def flatten_deps(deps) work = [].concat(deps) expanded = {} output = {} while !work.empty? dep = work.last if expanded[dep] output[dep] = true work.pop else expanded[dep] = true deps = yield dep work.concat(deps) end end output.keys # relies on Ruby's ordered hashes end def canonical_x_file(dep) return nil if determine_dep_type(dep) != :a x_files = DepGraphBack.fetch(dep).select do |name| determine_dep_type(name) == :x end if x_files.size > 2 raise "There is more than one .x file #{dep}." end x_files.first end # Note: It would be nice to find some solution so that Qt5Widgets.pc does not # require Qt5GuiNoPlugins, since it already requires Qt5Gui. def flatten_deps_for_pc_file(pc_file) flatten_deps(DepGraph[pc_file]) do |dep| deps = case determine_dep_type(dep) when :x, :pc then # Don't expand dependencies for a .pc file because we can just # refer to them with the Requires line in our .pc file. [] else DepGraph.fetch(dep) end # Replace .a files with a canonical .x file if there is one. deps.map do |name| substitute = canonical_x_file(name) substitute = nil if substitute == pc_file substitute || name end end end def flatten_deps_for_cmake_file(cmake_file) flatten_deps(DepGraph[cmake_file]) do |dep| DepGraph.fetch(dep) end end def create_pc_file(name) requires = [] libdirs = [] ldflags = [] cflags = [] deps = flatten_deps_for_pc_file(name) deps.each do |dep| dep = dep.dup case determine_dep_type(dep) when :a then full_path = DepInfo[dep] raise "Could not find library: #{dep}" if !full_path libdir = full_path.dirname.to_s libdir.sub!((OutDir + 'lib').to_s, '${libdir}') libdir.sub!(OutDir.to_s, '${prefix}') libname = full_path.basename.to_s libname.sub!(/\Alib/, '') libname.sub!(/.a\Z/, '') libdirs << "-L#{libdir}" ldflags << "-l#{libname}" when :x then dep.chomp!('.x') requires << dep when :pc then dep.chomp!('.pc') requires << dep when :ldflag then ldflags << dep when :libdirflag then libdirs << dep when :incdirflag then dep.sub!(OutIncDir.to_s, '${includedir}') cflags << dep end end r = "" r << "prefix=#{OutDir}\n" r << "libdir=${prefix}/lib\n" r << "includedir=${prefix}/include\n" r << "Version: #{QtVersionString}\n" if !libdirs.empty? || !ldflags.empty? r << "Libs: #{libdirs.reverse.uniq.join(' ')} #{ldflags.reverse.join(' ')}\n" end if !cflags.empty? r << "Cflags: #{cflags.join(' ')}\n" end if !requires.empty? r << "Requires: #{requires.sort.join(' ')}\n" end path = OutPcDir + Pathname(name).sub_ext(".pc") File.open(path.to_s, 'w') do |f| f.write r end end # For .pc files we depend on, add symlinks to the .pc file and any other .pc # files in the same directory which might be transitive dependencies. def symlink_pc_file_closure(name) dep_pc_dir = DepInfo[name].dirname dep_pc_dir.each_child do |target| link = OutPcDir + target.basename # Skip it if we already made this link. next if link.symlink? # Link directly to the real PC file. target = target.realpath ln_s target, link end end def create_pc_files mkdir OutPcDir DepGraph.each_key do |name| case determine_dep_type(name) when :x then create_pc_file(name) when :pc then symlink_pc_file_closure(name) end end end def set_property(f, target_name, property_name, value) if value.is_a?(Array) value = value.map do |entry| if entry.to_s.include?(' ') "\"#{entry}\"" else entry end end.join(' ') end f.puts "set_property(TARGET #{target_name} " \ "PROPERTY #{property_name} #{value})" end def set_properties(f, target_name, properties) properties.each do |property_name, value| set_property(f, target_name, property_name, value) end end def import_static_lib(f, target_name, properties) f.puts "add_library(#{target_name} STATIC IMPORTED)" set_properties(f, target_name, properties) end def create_cmake_core_files File.open(CMakeDir + 'core.cmake', 'w') do |f| f.puts "set(QT_VERSION_MAJOR #{QtVersionMajor})" f.puts f.puts "set(QT_MOC_EXECUTABLE #{MocExe})" f.puts "add_executable(Qt5::moc IMPORTED)" f.puts "set_target_properties(Qt5::moc PROPERTIES " \ "IMPORTED_LOCATION ${QT_MOC_EXECUTABLE})" f.puts f.puts "add_executable(Qt5::rcc IMPORTED)" f.puts "set_target_properties(Qt5::rcc PROPERTIES " \ "IMPORTED_LOCATION #{RccExe})" f.puts "set(Qt5Core_RCC_EXECUTABLE Qt5::rcc)" f.puts f.write File.read(ENV.fetch('core_macros')) end end def create_cmake_qt5widgets mkdir CMakeDir + 'Qt5Widgets' widgets_a = find_qt_library('libQt5Widgets.a') || raise deps = flatten_deps_for_cmake_file('Qt5Widgets.x') incdirs = [] libdirflags = [] ldflags = [] deps.each do |dep| dep = dep.dup case determine_dep_type(dep) when :a then full_path = DepInfo[dep] raise "Could not find library: #{dep}" if !full_path libdir = full_path.dirname.to_s libname = full_path.basename.to_s libname.sub!(/\Alib/, '') libname.sub!(/.a\Z/, '') libdirflags << "-L#{libdir}" ldflags << "-l#{libname}" when :ldflag then ldflags << dep when :libdirflag then libdirflags << dep when :incdirflag then incdir = dep.sub(/\A-I/, '') incdirs << incdir end end File.open(CMakeDir + 'Qt5Widgets' + 'Qt5WidgetsConfig.cmake', 'w') do |f| import_static_lib f, 'Qt5::Widgets', IMPORTED_LOCATION: widgets_a, IMPORTED_LINK_INTERFACE_LANGUAGES: 'CXX', INTERFACE_LINK_LIBRARIES: libdirflags.reverse.uniq + ldflags.reverse, INTERFACE_INCLUDE_DIRECTORIES: incdirs, INTERFACE_COMPILE_DEFINITIONS: 'QT_STATIC' f.puts "include(#{CMakeDir + 'core.cmake'})" end end def main # Symlink the include, bin, and plugins directories into $out. mkdir OutDir ln_s QtBaseDir + 'include', OutDir + 'include' ln_s QtBaseDir + 'bin', OutDir + 'bin' ln_s QtBaseDir + 'plugins', OutDir + 'plugins' ln_s QtBaseDir + 'src', OutDir + 'src' # Symlink the .a files and copy the .prl files into $out/lib. mkdir OutDir + 'lib' (QtBaseDir + 'lib').each_child do |c| ln_s c, OutDir + 'lib' if c.extname == '.a' cp c, OutDir + 'lib' if c.extname == '.prl' end make_dep_graph create_pc_files mkdir CMakeDir create_cmake_core_files create_cmake_qt5widgets end main