diff --git a/Gemfile b/Gemfile index 8ddc405b3..b6595ceb7 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ source "https://rubygems.org/" gem "hoe-mercurial", "~>1.4", :group => [:development, :test] gem "hoe-deveiate", "~>0.9", :group => [:development, :test] gem "hoe-highline", "~>0.2", :group => [:development, :test] -gem "rake-compiler", "~>1.0", :group => [:development, :test] +gem "rake-compiler", "1.1.1", :group => [:development, :test] gem "rake-compiler-dock", "~>1.0", :group => [:development, :test] gem "hoe-bundler", "~>1.0", :group => [:development, :test] gem "rspec", "~>3.5", :group => [:development, :test] diff --git a/Rakefile b/Rakefile index d5d65ede5..890752439 100644 --- a/Rakefile +++ b/Rakefile @@ -6,6 +6,7 @@ require 'tmpdir' begin require 'rake/extensiontask' + require_relative 'misc/rake-compiler-make-install-patch' rescue LoadError abort "This Rakefile requires rake-compiler (gem install rake-compiler)" end @@ -37,6 +38,7 @@ CLOBBER.include( TESTDIR.to_s ) 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" # Set up Hoe plugins Hoe.plugin :mercurial @@ -62,7 +64,7 @@ $hoespec = Hoe.spec 'pg' do self.developer 'Michael Granger', 'ged@FaerieMUD.org' self.developer 'Lars Kanis', 'lars@greiz-reinsdorf.de' - self.dependency 'rake-compiler', '~> 1.0', :developer + self.dependency 'rake-compiler', '1.1.1', :developer self.dependency 'rake-compiler-dock', ['~> 1.0'], :developer self.dependency 'hoe-deveiate', '~> 0.9', :developer self.dependency 'hoe-bundler', '~> 1.0', :developer diff --git a/appveyor.yml b/appveyor.yml index 345236e4f..3cd9de761 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,7 +19,6 @@ install: $(new-object net.webclient).DownloadFile('http://get.enterprisedb.com/postgresql/postgresql-' + $env:PGVERSION + '.exe', 'C:/postgresql-setup.exe') cmd /c "C:/postgresql-setup.exe" --mode unattended --extract-only 1 } - $env:RUBY_DLL_PATH = 'C:/Program Files/PostgreSQL/' + $env:PGVER + '/bin;C:/Program Files (x86)/PostgreSQL/' + $env:PGVER + '/bin' $env:PATH = 'C:/Program Files/PostgreSQL/' + $env:PGVER + '/bin;' + $env:PATH $env:PATH = 'C:/Program Files (x86)/PostgreSQL/' + $env:PGVER + '/bin;' + $env:PATH build_script: diff --git a/ext/extconf.rb b/ext/extconf.rb index beced6e79..9ea7469b9 100755 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -34,19 +34,33 @@ libdir = `"#{pgconfig}" --libdir`.chomp dir_config 'pg', incdir, libdir - # Try to use runtime path linker option, even if RbConfig doesn't know about it. - # The rpath option is usually set implicit by dir_config(), but so far not - # on MacOS-X. - if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', " -Wl,-rpath,#{libdir}") - $LDFLAGS << " -Wl,-rpath,#{libdir}" - end + # Windows traditionally stores DLLs beside executables, not in libdir + dlldir = RUBY_PLATFORM=~/mingw|mswin/ ? `"#{pgconfig}" --bindir`.chomp : libdir + else $stderr.puts "No pg_config... trying anyway. If building fails, please try again with", " --with-pg-config=/path/to/pg_config" - dir_config 'pg' + incdir, libdir = dir_config 'pg' + dlldir = libdir + end + + # Try to use runtime path linker option, even if RbConfig doesn't know about it. + # The rpath option is usually set implicit by dir_config(), but so far not + # on MacOS-X. + if dlldir && RbConfig::CONFIG["RPATHFLAG"].to_s.empty? + append_ldflags "-Wl,-rpath,#{dlldir.quote}" end end +File.write("postgresql_lib_path.rb", <<-EOT) +module PG + POSTGRESQL_LIB_PATH = #{dlldir.inspect} +end +EOT +$INSTALLFILES = { + "./postgresql_lib_path.rb" => "$(RUBYLIBDIR)/pg/" +} + if RUBY_VERSION >= '2.3.0' && /solaris/ =~ RUBY_PLATFORM append_cppflags( '-D__EXTENSIONS__' ) end diff --git a/lib/pg.rb b/lib/pg.rb index a53554670..9414d8719 100644 --- a/lib/pg.rb +++ b/lib/pg.rb @@ -1,15 +1,28 @@ + # -*- ruby -*- # frozen_string_literal: true -begin - require 'pg_ext' -rescue LoadError - # If it's a Windows binary gem, try the . subdirectory - if RUBY_PLATFORM =~/(mswin|mingw)/i - major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or - raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}" +# The top-level PG namespace. +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-")) + if File.exist?(bundled_libpq_path) + POSTGRESQL_LIB_PATH = bundled_libpq_path + else + bundled_libpq_path = nil + # Try to load libpq path as found by extconf.rb + begin + require "pg/postgresql_lib_path" + rescue LoadError + # rake-compiler doesn't use regular "make install", but uses it's own install tasks. + # It therefore doesn't copy pg/postgresql_lib_path.rb in case of "rake compile". + POSTGRESQL_LIB_PATH = false + end + end - add_dll_path = proc do |path, &block| + add_dll_path = proc do |path, &block| + if RUBY_PLATFORM =~/(mswin|mingw)/i && path && File.exist?(path) begin require 'ruby_installer/runtime' RubyInstaller::Runtime.add_dll_directory(path, &block) @@ -19,22 +32,24 @@ block.call ENV['PATH'] = old_path end + else + # No need to set a load path manually - it's set as library rpath. + block.call end + end - # Temporary add this directory for DLL search, so that libpq.dll can be found. - # mingw32-platform strings differ (RUBY_PLATFORM=i386-mingw32 vs. x86-mingw32 for rubygems) - add_dll_path.call(File.join(__dir__, RUBY_PLATFORM.gsub(/^i386-/, "x86-"))) do + # 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 + 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 + require 'pg_ext' end - else - raise end -end - - -# The top-level PG namespace. -module PG # Library version VERSION = '1.2.3' diff --git a/misc/rake-compiler-make-install-patch.rb b/misc/rake-compiler-make-install-patch.rb new file mode 100644 index 000000000..20a4cfb1b --- /dev/null +++ b/misc/rake-compiler-make-install-patch.rb @@ -0,0 +1,153 @@ +require 'rake/baseextensiontask' + +module Rake + class ExtensionTask < BaseExtensionTask + + # Replace method + undef define_compile_tasks + + def define_compile_tasks(for_platform = nil, ruby_ver = RUBY_VERSION) + # platform usage + platf = for_platform || platform + + binary_path = binary(platf) + + # lib_path + lib_path = lib_dir + + lib_binary_path = "#{lib_path}/#{binary_path}" + lib_binary_dir_path = File.dirname(lib_binary_path) + + # tmp_path + tmp_path = "#{@tmp_dir}/#{platf}/#{@name}/#{ruby_ver}" + stage_path = "#{@tmp_dir}/#{platf}/stage" + + siteconf_path = "#{tmp_path}/.rake-compiler-siteconf.rb" + tmp_binary_path = "#{tmp_path}/#{binary_path}" + tmp_binary_dir_path = File.dirname(tmp_binary_path) + stage_binary_path = "#{stage_path}/#{lib_path}/#{binary_path}" + stage_binary_dir_path = File.dirname(stage_binary_path) + + # cleanup and clobbering + CLEAN.include(tmp_path) + CLEAN.include(stage_path) + CLOBBER.include("#{lib_path}/#{binary(platf)}") + CLOBBER.include("#{@tmp_dir}") + + # directories we need + directory tmp_path + directory tmp_binary_dir_path + directory lib_binary_dir_path + directory stage_binary_dir_path + + directory File.dirname(siteconf_path) + # Set paths for "make install" destinations + file siteconf_path => File.dirname(siteconf_path) do + File.open(siteconf_path, "w") do |siteconf| + siteconf.puts "require 'rbconfig'" + siteconf.puts "require 'mkmf'" + siteconf.puts "dest_path = mkintpath(#{File.expand_path(lib_path).dump})" + %w[sitearchdir sitelibdir].each do |dir| + siteconf.puts "RbConfig::MAKEFILE_CONFIG['#{dir}'] = dest_path" + siteconf.puts "RbConfig::CONFIG['#{dir}'] = dest_path" + end + end + end + + # copy binary from temporary location to final lib + # tmp/extension_name/extension_name.{so,bundle} => lib/ + task "copy:#{@name}:#{platf}:#{ruby_ver}" => [lib_binary_dir_path, tmp_binary_path, "#{tmp_path}/Makefile"] do + # install in lib for native platform only + unless for_platform + sh "#{make} install", chdir: tmp_path + end + end + # copy binary from temporary location to staging directory + task "copy:#{@name}:#{platf}:#{ruby_ver}" => [stage_binary_dir_path, tmp_binary_path] do + cp tmp_binary_path, stage_binary_path + end + + # copy other gem files to staging directory + define_staging_file_tasks(@gem_spec.files, lib_path, stage_path, platf, ruby_ver) if @gem_spec + + # binary in temporary folder depends on makefile and source files + # tmp/extension_name/extension_name.{so,bundle} + file tmp_binary_path => [tmp_binary_dir_path, "#{tmp_path}/Makefile"] + source_files do + jruby_compile_msg = <<-EOF +Compiling a native C extension on JRuby. This is discouraged and a +Java extension should be preferred. + EOF + warn_once(jruby_compile_msg) if defined?(JRUBY_VERSION) + + chdir tmp_path do + sh make + if binary_path != File.basename(binary_path) + cp File.basename(binary_path), binary_path + end + end + end + + # makefile depends of tmp_dir and config_script + # tmp/extension_name/Makefile + file "#{tmp_path}/Makefile" => [tmp_path, extconf, siteconf_path] do |t| + options = @config_options.dup + + # include current directory + include_dirs = ['.'].concat(@config_includes).uniq.join(File::PATH_SEPARATOR) + cmd = [Gem.ruby, "-I#{include_dirs}", "-r#{File.basename(siteconf_path)}"] + + # build a relative path to extconf script + abs_tmp_path = (Pathname.new(Dir.pwd) + tmp_path).realpath + abs_extconf = (Pathname.new(Dir.pwd) + extconf).realpath + + # now add the extconf script + cmd << abs_extconf.relative_path_from(abs_tmp_path) + + # fake.rb will be present if we are cross compiling + if t.prerequisites.include?("#{tmp_path}/fake.rb") then + options.push(*cross_config_options(platf)) + end + + # add options to command + cmd.push(*options) + + # add any extra command line options + unless extra_options.empty? + cmd.push(*extra_options) + end + + chdir tmp_path do + # FIXME: Rake is broken for multiple arguments system() calls. + # Add current directory to the search path of Ruby + sh cmd.join(' ') + end + end + + # compile tasks + unless Rake::Task.task_defined?('compile') then + desc "Compile all the extensions" + task "compile" + end + + # compile:name + unless Rake::Task.task_defined?("compile:#{@name}") then + desc "Compile #{@name}" + task "compile:#{@name}" + end + + # Allow segmented compilation by platform (open door for 'cross compile') + task "compile:#{@name}:#{platf}" => ["copy:#{@name}:#{platf}:#{ruby_ver}"] + task "compile:#{platf}" => ["compile:#{@name}:#{platf}"] + + # Only add this extension to the compile chain if current + # platform matches the indicated one. + if platf == RUBY_PLATFORM then + # ensure file is always copied + file "#{lib_path}/#{binary_path}" => ["copy:#{name}:#{platf}:#{ruby_ver}"] + + task "compile:#{@name}" => ["compile:#{@name}:#{platf}"] + task "compile" => ["compile:#{platf}"] + end + end + end +end diff --git a/spec/pg_spec.rb b/spec/pg_spec.rb index 9f9f484cd..6b291b0ed 100644 --- a/spec/pg_spec.rb +++ b/spec/pg_spec.rb @@ -46,5 +46,8 @@ ]) end -end + it "tells about the libpq library path" do + expect( PG::POSTGRESQL_LIB_PATH ).to include("/") + end +end