mirror of
https://github.com/urbit/shrub.git
synced 2024-12-23 02:41:35 +03:00
edd57d380d
- Fixes the IPC bug - Fixes the terminfo bug - Moves the OSX SDK out of our nixcrpkgs fork. - Vendor nixcrpkgs instead of having it be a submodule.
500 lines
14 KiB
Ruby
500 lines
14 KiB
Ruby
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
|