From 49e89b957cffd1e00c5ec9de01a2fdaaf371c78d Mon Sep 17 00:00:00 2001 From: Lars Kanis Date: Mon, 30 Oct 2023 08:33:07 +0100 Subject: [PATCH] Add fat binary gem for x86_64-linux - Rename rake task 'gem:windows' to 'gem:native' - Add `rake gem:native:x86_64-linux` - Replace own PostgreSQL and OpenSSL build tasks by MiniPortile This is a more standard way and allows easier extensions. - Add krb5 library for Linux target to support GSSAPI/Kerberos - Change loading of pg_ext Try lib/pg_ext in addition to lib/3.2/pg_ext to support `rake spec` in the build directory - Fat binary linux gem: Try different UnixSocket paths of different distros. - CI: Adjust binary tests for new cross build target - Change patch directory to ports/patches///*.patch --- .github/workflows/binary-gems.yml | 32 ++-- Gemfile | 1 + README-Windows.rdoc | 2 +- Rakefile | 60 +++++- Rakefile.cross | 299 ------------------------------ ext/extconf.rb | 110 ++++++++++- lib/pg.rb | 19 +- lib/pg/connection.rb | 8 + 8 files changed, 201 insertions(+), 330 deletions(-) delete mode 100644 Rakefile.cross diff --git a/.github/workflows/binary-gems.yml b/.github/workflows/binary-gems.yml index 11d6c2724..f15943a72 100644 --- a/.github/workflows/binary-gems.yml +++ b/.github/workflows/binary-gems.yml @@ -18,6 +18,7 @@ jobs: - platform: "x64-mingw-ucrt" - platform: "x64-mingw32" - platform: "x86-mingw32" + - platform: "x86_64-linux" steps: - uses: actions/checkout@v3 - name: Set up Ruby @@ -34,7 +35,7 @@ jobs: cp gem-public_cert.pem ~/.gem/gem-public_cert.pem - name: Build binary gem - run: bundle exec rake gem:windows:${{ matrix.platform }} + run: bundle exec rake gem:native:${{ matrix.platform }} - name: Upload binary gem uses: actions/upload-artifact@v3 @@ -61,6 +62,9 @@ jobs: ruby: "2.5" platform: "x64-mingw32" PGVERSION: 10.20-1-windows + - os: ubuntu-latest + ruby: "3.2" + platform: "x86_64-linux" runs-on: ${{ matrix.os }} env: @@ -69,9 +73,12 @@ jobs: - uses: actions/checkout@v3 - name: Set up Ruby if: matrix.platform != 'x86-mingw32' - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby-pkgs@v1 with: ruby-version: ${{ matrix.ruby }} + apt-get: "postgresql" # Ubuntu + brew: "postgresql" # macOS + mingw: "postgresql" # Windows mingw / mswin /ucrt - name: Set up 32 bit x86 Ruby if: matrix.platform == 'x86-mingw32' @@ -81,7 +88,7 @@ jobs: echo "c:/ruby-${{ matrix.ruby }}/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append c:/ruby-${{ matrix.ruby }}/bin/ridk enable - c:/msys64/usr/bin/bash -lc "pacman -S --noconfirm --needed make `${MINGW_PACKAGE_PREFIX}-pkgconf `${MINGW_PACKAGE_PREFIX}-libyaml `${MINGW_PACKAGE_PREFIX}-gcc `${MINGW_PACKAGE_PREFIX}-make" + c:/msys64/usr/bin/bash -lc "pacman -S --noconfirm --needed make `${MINGW_PACKAGE_PREFIX}-pkgconf `${MINGW_PACKAGE_PREFIX}-libyaml `${MINGW_PACKAGE_PREFIX}-gcc `${MINGW_PACKAGE_PREFIX}-make `${MINGW_PACKAGE_PREFIX}-postgresql" echo "C:/msys64/$env:MSYSTEM_PREFIX/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Download gem from build job @@ -89,17 +96,9 @@ jobs: with: name: binary-gem - - name: Download PostgreSQL + - name: set PGUSER + if: ${{ matrix.os == 'windows-latest' }} run: | - Add-Type -AssemblyName System.IO.Compression.FileSystem - function Unzip { - param([string]$zipfile, [string]$outpath) - [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath) - } - - $(new-object net.webclient).DownloadFile("http://get.enterprisedb.com/postgresql/postgresql-$env:PGVERSION-binaries.zip", "postgresql-binaries.zip") - Unzip "postgresql-binaries.zip" "." - echo "$pwd/pgsql/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append echo "PGUSER=$env:USERNAME" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append echo "PGPASSWORD=" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append @@ -108,10 +107,17 @@ jobs: - run: bundle install - run: gem install --local pg-*${{ matrix.platform }}.gem --verbose - name: Run specs + if: ${{ matrix.os != 'windows-latest' }} run: ruby -rpg -S rspec -fd spec/**/*_spec.rb + - name: Run specs + if: ${{ matrix.os == 'windows-latest' }} + run: | + ridk enable + ruby -rpg -S rspec -fd spec/**/*_spec.rb - name: Print logs if job failed if: ${{ failure() && matrix.os == 'windows-latest' }} run: | ridk enable find "$(ruby -e"puts RbConfig::CONFIG[%q[libdir]]")" -name mkmf.log -print0 | xargs -0 cat + find -name setup.log -print0 | xargs -0 cat diff --git a/Gemfile b/Gemfile index ecc2f33df..132bdbc3a 100644 --- a/Gemfile +++ b/Gemfile @@ -11,4 +11,5 @@ group :development, :test do gem "rake-compiler-dock", "~> 1.0" gem "rdoc", "~> 6.4" gem "rspec", "~> 3.5" + gem "mini_portile2", "~> 2.1" end diff --git a/README-Windows.rdoc b/README-Windows.rdoc index 85d89594a..55f409f26 100644 --- a/README-Windows.rdoc +++ b/README-Windows.rdoc @@ -41,7 +41,7 @@ sure it is started. A native Docker installation is best on Linux. Then run: - rake gem:windows + rake gem:native This will download a docker image suited for building windows gems, and it will download and build OpenSSL and PostgreSQL. Finally the gem is built diff --git a/Rakefile b/Rakefile index be95343d7..1a876f1a0 100644 --- a/Rakefile +++ b/Rakefile @@ -31,8 +31,8 @@ CLEAN.include( PKGDIR.to_s, TMPDIR.to_s ) CLEAN.include "lib/*/libpq.dll" CLEAN.include "lib/pg_ext.*" CLEAN.include "lib/pg/postgresql_lib_path.rb" - -load 'Rakefile.cross' +CLEAN.include "ports/*.installed" +CLEAN.include "ports/*mingw*", "ports/*linux*" Bundler::GemHelper.install_tasks $gem_spec = Bundler.load_gemspec(GEMSPEC) @@ -42,6 +42,16 @@ task :maint do ENV['MAINTAINER_MODE'] = 'yes' end +CrossLibrary = Struct.new :platform, :openssl_config, :toolchain +CrossLibraries = [ + ['x64-mingw-ucrt', 'mingw64', 'x86_64-w64-mingw32'], + ['x86-mingw32', 'mingw', 'i686-w64-mingw32'], + ['x64-mingw32', 'mingw64', 'x86_64-w64-mingw32'], + ['x86_64-linux', 'linux-x86_64', 'x86_64-redhat-linux'], +].map do |platform, openssl_config, toolchain| + CrossLibrary.new platform, openssl_config, toolchain +end + # Rake-compiler task Rake::ExtensionTask.new do |ext| ext.name = 'pg_ext' @@ -50,24 +60,54 @@ Rake::ExtensionTask.new do |ext| ext.lib_dir = 'lib' ext.source_pattern = "*.{c,h}" ext.cross_compile = true - ext.cross_platform = CrossLibraries.map(&:for_platform) + ext.cross_platform = CrossLibraries.map(&:platform) - ext.cross_config_options += CrossLibraries.map do |lib| + ext.cross_config_options += CrossLibraries.map do |xlib| { - lib.for_platform => [ + xlib.platform => [ "--enable-windows-cross", - "--with-pg-include=#{lib.static_postgresql_incdir}", - "--with-pg-lib=#{lib.static_postgresql_libdir}", - # libpq-fe.h resides in src/interfaces/libpq/ before make install - "--with-opt-include=#{lib.static_postgresql_libdir}", + "--with-openssl-platform=#{xlib.openssl_config}", + "--with-toolchain=#{xlib.toolchain}", ] } end # Add libpq.dll to windows binary gemspec ext.cross_compiling do |spec| - spec.files << "lib/#{spec.platform}/libpq.dll" + spec.files << "ports/#{spec.platform.to_s}/lib/libpq.so.5" if spec.platform.to_s =~ /linux/ + spec.files << "ports/#{spec.platform.to_s}/lib/libpq.dll" if spec.platform.to_s =~ /mingw|mswin/ + end +end + +task 'gem:native:prepare' do + require 'io/console' + require 'rake_compiler_dock' + + # Copy gem signing key and certs to be accessible from the docker container + mkdir_p 'build/gem' + sh "cp ~/.gem/gem-*.pem build/gem/ || true" + sh "bundle package" + begin + OpenSSL::PKey.read(File.read(File.expand_path("~/.gem/gem-private_key.pem")), ENV["GEM_PRIVATE_KEY_PASSPHRASE"] || "") + rescue OpenSSL::PKey::PKeyError + ENV["GEM_PRIVATE_KEY_PASSPHRASE"] = STDIN.getpass("Enter passphrase of gem signature key: ") + retry + end +end + +CrossLibraries.each do |xlib| + platform = xlib.platform + desc "Build fat binary gem for platform #{platform}" + task "gem:native:#{platform}" => ['gem:native:prepare'] do + RakeCompilerDock.sh <<-EOT, platform: platform + #{ "sudo yum install -y perl-IPC-Cmd &&" if platform =~ /linux/ } + (cp build/gem/gem-*.pem ~/.gem/ || true) && + bundle install --local && + rake native:#{platform} pkg/#{$gem_spec.full_name}-#{platform}.gem MAKE="make -j`nproc`" RUBY_CC_VERSION=3.2.0:3.1.0:3.0.0:2.7.0:2.6.0:2.5.0 + EOT end + desc "Build the native binary gems" + multitask 'gem:native' => "gem:native:#{platform}" end RSpec::Core::RakeTask.new(:spec).rspec_opts = "--profile -cfdoc" diff --git a/Rakefile.cross b/Rakefile.cross deleted file mode 100644 index 35b701e8d..000000000 --- a/Rakefile.cross +++ /dev/null @@ -1,299 +0,0 @@ -# -*- rake -*- - -require 'uri' -require 'tempfile' -require 'rbconfig' -require 'rake/clean' -require 'rake/extensiontask' -require 'rake/extensioncompiler' -require 'ostruct' -require_relative 'rakelib/task_extension' - -MISCDIR = BASEDIR + 'misc' - -NUM_CPUS = if File.exist?('/proc/cpuinfo') - File.read('/proc/cpuinfo').scan('processor').length -elsif RUBY_PLATFORM.include?( 'darwin' ) - `system_profiler SPHardwareDataType | grep 'Cores' | awk '{print $5}'`.chomp -else - 1 -end - -class CrossLibrary < OpenStruct - include Rake::DSL - prepend TaskExtension - - def initialize(for_platform, openssl_config, toolchain) - super() - - self.for_platform = for_platform - self.openssl_config = openssl_config - self.host_platform = toolchain - - # Cross-compilation constants - self.openssl_version = ENV['OPENSSL_VERSION'] || '3.1.2' - self.postgresql_version = ENV['POSTGRESQL_VERSION'] || '16.0' - - # Check if symlinks work in the current working directory. - # This fails, if rake-compiler-dock is running on a Windows box. - begin - FileUtils.rm_f '.test_symlink' - FileUtils.ln_s '/', '.test_symlink' - rescue NotImplementedError, SystemCallError - # Symlinks don't work -> use home directory instead - self.compile_home = Pathname( "~/.ruby-pg-build" ).expand_path - else - self.compile_home = Pathname( "./build" ).expand_path - end - self.static_sourcesdir = compile_home + 'sources' - self.static_builddir = compile_home + 'builds' + for_platform - CLOBBER.include( static_sourcesdir ) - CLEAN.include( static_builddir ) - - # Static OpenSSL build vars - self.static_openssl_builddir = static_builddir + "openssl-#{openssl_version}" - - self.openssl_source_uri = - URI( "http://www.openssl.org/source/openssl-#{openssl_version}.tar.gz" ) - self.openssl_tarball = static_sourcesdir + File.basename( openssl_source_uri.path ) - self.openssl_makefile = static_openssl_builddir + 'Makefile' - - self.libssl = static_openssl_builddir + 'libssl.a' - self.libcrypto = static_openssl_builddir + 'libcrypto.a' - - self.openssl_patches = Rake::FileList[ (MISCDIR + "openssl-#{openssl_version}.*.patch").to_s ] - - # Static PostgreSQL build vars - self.static_postgresql_builddir = static_builddir + "postgresql-#{postgresql_version}" - self.postgresql_source_uri = begin - uristring = "http://ftp.postgresql.org/pub/source/v%s/postgresql-%s.tar.bz2" % - [ postgresql_version, postgresql_version ] - URI( uristring ) - end - self.postgresql_tarball = static_sourcesdir + File.basename( postgresql_source_uri.path ) - - self.static_postgresql_srcdir = static_postgresql_builddir + 'src' - self.static_postgresql_libdir = static_postgresql_srcdir + 'interfaces/libpq' - self.static_postgresql_incdir = static_postgresql_srcdir + 'include' - - self.postgresql_global_makefile = static_postgresql_srcdir + 'Makefile.global' - self.postgresql_shlib_makefile = static_postgresql_srcdir + 'Makefile.shlib' - self.postgresql_shlib_mf_orig = static_postgresql_srcdir + 'Makefile.shlib.orig' - self.postgresql_lib = static_postgresql_libdir + 'libpq.dll' - self.postgresql_patches = Rake::FileList[ (MISCDIR + "postgresql-#{postgresql_version}.*.patch").to_s ] - - # clean intermediate files and folders - CLEAN.include( static_builddir.to_s ) - - ##################################################################### - ### C R O S S - C O M P I L A T I O N - T A S K S - ##################################################################### - - - directory static_sourcesdir.to_s - - # - # Static OpenSSL build tasks - # - directory static_openssl_builddir.to_s - - # openssl source file should be stored there - file openssl_tarball => static_sourcesdir do |t| - download( openssl_source_uri, t.name ) - end - - # Extract the openssl builds - file static_openssl_builddir => openssl_tarball do |t| - puts "extracting %s to %s" % [ openssl_tarball, static_openssl_builddir.parent ] - static_openssl_builddir.mkpath - run 'tar', '-xzf', openssl_tarball.to_s, '-C', static_openssl_builddir.parent.to_s - openssl_makefile.unlink if openssl_makefile.exist? - - openssl_patches.each do |patchfile| - puts " applying patch #{patchfile}..." - run 'patch', '-Np1', '-d', static_openssl_builddir.to_s, - '-i', File.expand_path( patchfile, BASEDIR ) - end - end - - self.cmd_prelude = [ - "env", - "CROSS_COMPILE=#{host_platform}-", - "CFLAGS=-DDSO_WIN32", - ] - - - # generate the makefile in a clean build location - file openssl_makefile => static_openssl_builddir do |t| - chdir( static_openssl_builddir ) do - cmd = cmd_prelude.dup - cmd << "./Configure" << "-static" << openssl_config - - run( *cmd ) - end - end - - desc "compile static openssl libraries" - task "openssl_libs:#{for_platform}" => [ libssl, libcrypto ] - - task "compile_static_openssl:#{for_platform}" => openssl_makefile do |t| - chdir( static_openssl_builddir ) do - cmd = cmd_prelude.dup - cmd << 'make' << "-j#{NUM_CPUS}" << 'build_libs' - - run( *cmd ) - end - end - - desc "compile static #{libssl}" - file libssl => "compile_static_openssl:#{for_platform}" - - desc "compile static #{libcrypto}" - file libcrypto => "compile_static_openssl:#{for_platform}" - - - - # - # Static PostgreSQL build tasks - # - directory static_postgresql_builddir.to_s - - - # postgresql source file should be stored there - file postgresql_tarball => static_sourcesdir do |t| - download( postgresql_source_uri, t.name ) - end - - # Extract the postgresql sources - file static_postgresql_builddir => postgresql_tarball do |t| - puts "extracting %s to %s" % [ postgresql_tarball, static_postgresql_builddir.parent ] - static_postgresql_builddir.mkpath - run 'tar', '-xjf', postgresql_tarball.to_s, '-C', static_postgresql_builddir.parent.to_s - - postgresql_patches.each do |patchfile| - puts " applying patch #{patchfile}..." - run 'patch', '-Np1', '-d', static_postgresql_builddir.to_s, - '-i', File.expand_path( patchfile, BASEDIR ) - end - end - - # generate the makefile in a clean build location - file postgresql_global_makefile => [ static_postgresql_builddir, "openssl_libs:#{for_platform}" ] do |t| - options = [ - "--target=#{host_platform}", - "--host=#{host_platform}", - '--with-openssl', - '--without-zlib', - '--without-icu', - ] - - chdir( static_postgresql_builddir ) do - configure_path = static_postgresql_builddir + 'configure' - cmd = [ configure_path.to_s, *options ] - cmd << "CFLAGS=-L#{static_openssl_builddir}" - cmd << "LDFLAGS=-L#{static_openssl_builddir}" - cmd << "LDFLAGS_SL=-L#{static_openssl_builddir}" - cmd << "LIBS=-lwsock32 -lgdi32 -lws2_32 -lcrypt32" - cmd << "CPPFLAGS=-I#{static_openssl_builddir}/include" - - run( *cmd ) - end - end - - - # make libpq.dll - task postgresql_lib => [ postgresql_global_makefile ] do |t| - # Work around missing dependency to libcommon in PostgreSQL-9.4.0 - chdir( static_postgresql_srcdir + "common" ) do - sh 'make', "-j#{NUM_CPUS}" - end - chdir( static_postgresql_srcdir + "port" ) do - sh 'make', "-j#{NUM_CPUS}" - end - - chdir( postgresql_lib.dirname ) do - sh 'make', - "-j#{NUM_CPUS}", - postgresql_lib.basename.to_s, - 'SHLIB_LINK=-lssl -lcrypto -lcrypt32 -lgdi32 -lsecur32 -lwsock32 -lws2_32' - end - end - - - #desc 'compile libpg.a' - task "native:#{for_platform}" => postgresql_lib - - # copy libpq.dll to lib dir - dest_libpq = "lib/#{for_platform}/#{postgresql_lib.basename}" - directory File.dirname(dest_libpq) - file dest_libpq => [postgresql_lib, File.dirname(dest_libpq)] do - cp postgresql_lib, dest_libpq - end - - stage_libpq = "tmp/#{for_platform}/stage/#{dest_libpq}" - directory File.dirname(stage_libpq) - file stage_libpq => [postgresql_lib, File.dirname(stage_libpq)] do |t| - cp postgresql_lib, stage_libpq - end - end - - def download(url, save_to) - part = save_to+".part" - sh "wget #{url.to_s.inspect} -O #{part.inspect} || curl #{url.to_s.inspect} -o #{part.inspect}" - FileUtils.mv part, save_to - end - - def run(*args) - sh(*args) - end -end - -CrossLibraries = [ - ['x64-mingw-ucrt', 'mingw64', 'x86_64-w64-mingw32'], - ['x86-mingw32', 'mingw', 'i686-w64-mingw32'], - ['x64-mingw32', 'mingw64', 'x86_64-w64-mingw32'], -].map do |platform, openssl_config, toolchain| - CrossLibrary.new platform, openssl_config, toolchain -end - -desc 'cross compile pg for win32' -task :cross => [ :mingw32 ] - -task :mingw32 do - # Use Rake::ExtensionCompiler helpers to find the proper host - unless Rake::ExtensionCompiler.mingw_host then - warn "You need to install mingw32 cross compile functionality to be able to continue." - warn "Please refer to your distribution/package manager documentation about installation." - fail - end -end - -task 'gem:windows:prepare' do - require 'io/console' - require 'rake_compiler_dock' - - # Copy gem signing key and certs to be accessible from the docker container - mkdir_p 'build/gem' - sh "cp ~/.gem/gem-*.pem build/gem/ || true" - sh "bundle package" - begin - OpenSSL::PKey.read(File.read(File.expand_path("~/.gem/gem-private_key.pem")), ENV["GEM_PRIVATE_KEY_PASSPHRASE"] || "") - rescue OpenSSL::PKey::PKeyError - ENV["GEM_PRIVATE_KEY_PASSPHRASE"] = STDIN.getpass("Enter passphrase of gem signature key: ") - retry - end -end - -CrossLibraries.each do |xlib| - platform = xlib.for_platform - desc "Build fat binary gem for platform #{platform}" - task "gem:windows:#{platform}" => ['gem:windows:prepare', xlib.openssl_tarball, xlib.postgresql_tarball] do - RakeCompilerDock.sh <<-EOT, platform: platform - (cp build/gem/gem-*.pem ~/.gem/ || true) && - bundle install --local && - rake native:#{platform} pkg/#{$gem_spec.full_name}-#{platform}.gem MAKE="make -j`nproc`" RUBY_CC_VERSION=3.2.0:3.1.0:3.0.0:2.7.0:2.6.0:2.5.0 - EOT - end - desc "Build the windows binary gems" - multitask 'gem:windows' => "gem:windows:#{platform}" -end diff --git a/ext/extconf.rb b/ext/extconf.rb index d9b7b38bb..9e6ef9142 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -1,7 +1,6 @@ require 'pp' require 'mkmf' - if ENV['MAINTAINER_MODE'] $stderr.puts "Maintainer mode enabled." $CFLAGS << @@ -23,11 +22,116 @@ end if enable_config("windows-cross") + gem 'mini_portile2', '~>2.1' + require 'mini_portile2' + + OPENSSL_VERSION = ENV['OPENSSL_VERSION'] || '3.1.2' + OPENSSL_SOURCE_URI = "http://www.openssl.org/source/openssl-#{OPENSSL_VERSION}.tar.gz" + + KRB5_VERSION = ENV['KRB5_VERSION'] || '1.21.2' + KRB5_SOURCE_URI = "http://kerberos.org/dist/krb5/#{KRB5_VERSION[/^(\d+\.\d+)/]}/krb5-#{KRB5_VERSION}.tar.gz" + + POSTGRESQL_VERSION = ENV['POSTGRESQL_VERSION'] || '16.0' + POSTGRESQL_SOURCE_URI = "http://ftp.postgresql.org/pub/source/v#{POSTGRESQL_VERSION}/postgresql-#{POSTGRESQL_VERSION}.tar.bz2" + + class BuildRecipe < MiniPortile + def initialize(name, version, files) + super(name, version) + self.files = files + rootdir = File.expand_path('../..', __FILE__) + self.target = File.join(rootdir, "ports") + self.patch_files = Dir[File.join(target, "patches", self.name, self.version, "*.patch")].sort + end + + def port_path + "#{target}/#{RUBY_PLATFORM}" + end + + def cook_and_activate + checkpoint = File.join(self.target, "#{self.name}-#{self.version}-#{RUBY_PLATFORM}.installed") + unless File.exist?(checkpoint) + self.cook + FileUtils.touch checkpoint + end + self.activate + self + end + end + + openssl_platform = with_config("openssl-platform") + toolchain = with_config("toolchain") + + openssl_recipe = BuildRecipe.new("openssl", OPENSSL_VERSION, [OPENSSL_SOURCE_URI]).tap do |recipe| + class << recipe + attr_accessor :openssl_platform + def configure + envs = [] + envs << "CFLAGS=-DDSO_WIN32" if RUBY_PLATFORM =~ /mingw|mswin/ + envs << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/ + execute('configure', ['env', *envs, "./Configure", openssl_platform, "-static", "CROSS_COMPILE=#{host}-", configure_prefix], altlog: "config.log") + end + def compile + execute('compile', "#{make_cmd} build_libs") + end + def install + execute('install', "#{make_cmd} install_dev") + end + end + + recipe.openssl_platform = openssl_platform + recipe.host = toolchain + recipe.cook_and_activate + end + + if RUBY_PLATFORM =~ /linux/ + krb5_recipe = BuildRecipe.new("krb5", KRB5_VERSION, [KRB5_SOURCE_URI]).tap do |recipe| + class << recipe + def work_path + File.join(super, "src") + end + end + # We specify -fcommon to get around duplicate definition errors in recent gcc. + # See https://github.com/cockroachdb/cockroach/issues/49734 + recipe.configure_options << "CFLAGS=-fcommon#{" -fPIC" if RUBY_PLATFORM =~ /linux/}" + recipe.configure_options << "--without-keyutils" + recipe.host = toolchain + recipe.cook_and_activate + end + end + + postgresql_recipe = BuildRecipe.new("postgresql", POSTGRESQL_VERSION, [POSTGRESQL_SOURCE_URI]).tap do |recipe| + class << recipe + def configure_defaults + [ + "--target=#{host}", + "--host=#{host}", + '--with-openssl', + *(RUBY_PLATFORM=~/linux/ ? ['--with-gssapi'] : []), + '--without-zlib', + '--without-icu', + ] + end + def compile + execute 'compile include', "#{make_cmd} -C src/include install" + execute 'compile interfaces', "#{make_cmd} -C src/interfaces install" + end + def install + end + end + + recipe.configure_options << "CFLAGS=#{" -fPIC" if RUBY_PLATFORM =~ /linux/}" + recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib -L#{openssl_recipe.path}/lib64 #{"-lgssapi_krb5 -lkrb5 -lk5crypto -lkrb5support" if RUBY_PLATFORM =~ /linux/}" + recipe.configure_options << "LIBS=-lkrb5 -lcom_err -lk5crypto -lkrb5support -lresolv" if RUBY_PLATFORM =~ /linux/ + recipe.configure_options << "LIBS=-lwsock32 -lgdi32 -lws2_32 -lcrypt32" if RUBY_PLATFORM =~ /mingw|mswin/ + recipe.configure_options << "CPPFLAGS=-I#{openssl_recipe.path}/include" + recipe.host = toolchain + recipe.cook_and_activate + end + # Avoid dependency to external libgcc.dll on x86-mingw32 $LDFLAGS << " -static-libgcc" # Don't use pg_config for cross build, but --with-pg-* path options - dir_config 'pg' - + dir_config('pg', "#{postgresql_recipe.path}/include", "#{postgresql_recipe.path}/lib") else # Native build diff --git a/lib/pg.rb b/lib/pg.rb index 159ff20b6..370819627 100644 --- a/lib/pg.rb +++ b/lib/pg.rb @@ -6,7 +6,7 @@ module PG # Is this file part of a fat binary gem with bundled libpq? - bundled_libpq_path = File.join(__dir__, RUBY_PLATFORM.gsub(/^i386-/, "x86-")) + bundled_libpq_path = File.expand_path("../ports/#{RUBY_PLATFORM.gsub(/^i386-/, "x86-")}/lib", __dir__) if File.exist?(bundled_libpq_path) POSTGRESQL_LIB_PATH = bundled_libpq_path else @@ -23,6 +23,7 @@ module PG add_dll_path = proc do |path, &block| if RUBY_PLATFORM =~/(mswin|mingw)/i && path && File.exist?(path) + BUNDLED_LIBPQ_WITH_UNIXSOCKET = false begin require 'ruby_installer/runtime' RubyInstaller::Runtime.add_dll_directory(path, &block) @@ -32,7 +33,17 @@ module PG block.call ENV['PATH'] = old_path end + elsif RUBY_PLATFORM =~/(linux)/i && bundled_libpq_path && File.exist?(bundled_libpq_path) + BUNDLED_LIBPQ_WITH_UNIXSOCKET = true + + # Load dependent libpq.so into the process, so that it is already present, + # when pg_ext.so is loaded. + # This ensures, that the shared library is loaded when the path is different between build and run time (e.g. fat binary gems). + require 'fiddle' + Fiddle.dlopen(File.join(bundled_libpq_path, "libpq.so.5")) + block.call else + BUNDLED_LIBPQ_WITH_UNIXSOCKET = false # No need to set a load path manually - it's set as library rpath. block.call end @@ -40,12 +51,12 @@ module PG # Add a load path to the one retrieved from pg_config add_dll_path.call(POSTGRESQL_LIB_PATH) do - if bundled_libpq_path - # It's a Windows binary gem, try the . subdirectory + begin + # Try the . subdirectory for fat binary gems major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}" require "#{major_minor}/pg_ext" - else + rescue LoadError require 'pg_ext' end end diff --git a/lib/pg/connection.rb b/lib/pg/connection.rb index a8d07bb37..cd37c4ea5 100644 --- a/lib/pg/connection.rb +++ b/lib/pg/connection.rb @@ -778,6 +778,14 @@ def new(*args) iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] } iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts) + if PG::BUNDLED_LIBPQ_WITH_UNIXSOCKET && iopts[:host].to_s.empty? + # Many distors patch the hardcoded default UnixSocket path in libpq to /var/run/postgresql instead of /tmp . + # We simply try them all. + iopts[:host] = "/var/run/postgresql" # Ubuntu, Debian, Fedora, Opensuse + ",/run/postgresql" # Alpine, Archlinux, Gentoo + ",/tmp" # Stock PostgreSQL + end + if iopts[:hostaddr] # hostaddr is provided -> no need to resolve hostnames