diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6e18dbd5..1661b167 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,9 @@ jobs: platform: - "x64-mingw32" - "x64-mingw-ucrt" - name: cross-compile-windows + - "x86_64-linux-gnu" + + name: cross-compile runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 @@ -22,7 +24,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: "2.7" - + - name: "Install dependencies" run: bundle install @@ -34,10 +36,10 @@ jobs: uses: actions/cache@v4 with: path: ports - key: cross-compiled-v7-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + key: cross-compiled-v8-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} restore-keys: | - cross-compiled-v7-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} - cross-compiled-v7-${{ matrix.platform }}- + cross-compiled-v8-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + cross-compiled-v8-${{ matrix.platform }}- - name: Build gem shell: bash @@ -123,21 +125,9 @@ jobs: - name: Install native gem and restore cross-compiled code from it shell: pwsh - run: | - $rubyArchitecture = (ruby -e "puts RbConfig::CONFIG['arch']").Trim() - $gemVersion = (Get-Content VERSION).Trim() - $gemToUnpack = "./tiny_tds-$gemVersion-$rubyArchitecture.gem" - - Write-Host "Looking to unpack $gemToUnpack" - gem unpack --target ./tmp "$gemToUnpack" - - # Restore precompiled code - $source = (Resolve-Path ".\tmp\tiny_tds-$gemVersion-$rubyArchitecture\lib\tiny_tds").Path - $destination = (Resolve-Path ".\lib\tiny_tds").Path - Get-ChildItem $source -Recurse -Exclude "*.rb" | Copy-Item -Destination {Join-Path $destination $_.FullName.Substring($source.length)} - - # Restore ports - Copy-Item -Path ".\tmp\tiny_tds-$gemVersion-$rubyArchitecture\ports" -Destination "." -Recurse + run: "& ./test/bin/restore-from-native-gem.ps1" + env: + RUBY_ARCHITECTURE: "x64-mingw32" - name: Setup MSSQL uses: rails-sqlserver/setup-mssql@v1 @@ -250,21 +240,9 @@ jobs: - name: Install native gem and restore cross-compiled code from it shell: pwsh - run: | - $rubyArchitecture = (ruby -e "puts RbConfig::CONFIG['arch']").Trim() - $gemVersion = (Get-Content VERSION).Trim() - $gemToUnpack = "./tiny_tds-$gemVersion-$rubyArchitecture.gem" - - Write-Host "Looking to unpack $gemToUnpack" - gem unpack --target ./tmp "$gemToUnpack" - - # Restore precompiled code - $source = (Resolve-Path ".\tmp\tiny_tds-$gemVersion-$rubyArchitecture\lib\tiny_tds").Path - $destination = (Resolve-Path ".\lib\tiny_tds").Path - Get-ChildItem $source -Recurse -Exclude "*.rb" | Copy-Item -Destination {Join-Path $destination $_.FullName.Substring($source.length)} - - # Restore ports - Copy-Item -Path ".\tmp\tiny_tds-$gemVersion-$rubyArchitecture\ports" -Destination "." -Recurse + run: "& ./test/bin/restore-from-native-gem.ps1" + env: + RUBY_ARCHITECTURE: "x64-mingw-ucrt" - name: Setup MSSQL uses: rails-sqlserver/setup-mssql@v1 @@ -336,44 +314,59 @@ jobs: ruby -e "require 'tiny_tds'; puts TinyTds::Gem.root_path" exit $LASTEXITCODE - compile-native-ports: + install-linux: + needs: + - cross-compile + strategy: + fail-fast: false + matrix: + platform: + - "x86_64-linux-gnu" + + ruby-version: + - "2.7" + - "3.0" + - "3.1" + - "3.2" + - "3.3" + - "3.4" + + name: install-linux runs-on: ubuntu-22.04 - name: cross-compile-linux steps: - uses: actions/checkout@v4 - - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.4 - bundler-cache: true - - - name: Write used versions into file - run: bundle exec rake ports:version_file - - - name: Cache ports - uses: actions/cache@v4 + - name: Download precompiled gem + uses: actions/download-artifact@v4 with: - path: ports - key: native-v3-${{ hashFiles('**/.ports_versions') }} - restore-keys: | - native-v3-${{ hashFiles('* */.ports_versions') }} - native-v3- + name: gem-${{ matrix.platform }} + path: precompiled/gems - - name: Build required libraries - run: | - bundle exec rake ports + - run: | + docker run --rm -v $PWD/precompiled:/precompiled -w /precompiled \ + ${{ matrix.docker_platform }} ruby:${{ matrix.ruby-version }}${{ matrix.docker_tag }} \ + sh -c " + gem update --system 3.3.22 && + gem install --no-document ./gems/tiny_tds-$(cat VERSION)-${{ matrix.platform }}.gem && + ruby -e \"require 'tiny_tds'; puts TinyTds::Gem.root_path\" + " test-linux: needs: - - compile-native-ports + - cross-compile name: test-linux strategy: fail-fast: false matrix: + force-encryption: + - false + - true + mssql-version: - 2017 - 2019 - 2022 + ruby-version: - "2.7" - "3.0" @@ -391,20 +384,16 @@ jobs: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - - name: Write used versions into file - run: | - bundle exec rake ports:version_file - - - name: Cache ports - uses: actions/cache@v4 + - name: Download precompiled gem + uses: actions/download-artifact@v4 with: - path: ports - key: native-v3-${{ hashFiles('**/.ports_versions') }} - fail-on-cache-miss: true + name: gem-x86_64-linux-gnu - - name: Build gem - run: | - bundle exec rake build + - name: Install native gem and restore cross-compiled code from it + shell: pwsh + run: "& ./test/bin/restore-from-native-gem.ps1" + env: + RUBY_ARCHITECTURE: "x86_64-linux-gnu" - name: Setup MSSQL uses: rails-sqlserver/setup-mssql@v1 @@ -412,6 +401,7 @@ jobs: components: sqlcmd,sqlengine version: ${{ matrix.mssql-version }} sa-password: "c0MplicatedP@ssword" + force-encryption: ${{ matrix.force-encryption }} - name: Setup MSSQL database run: | diff --git a/Rakefile b/Rakefile index 403d6deb..98505576 100644 --- a/Rakefile +++ b/Rakefile @@ -10,6 +10,7 @@ CrossLibrary = Struct.new :platform, :openssl_config CrossLibraries = [ ['x64-mingw-ucrt', 'mingw64'], ['x64-mingw32', 'mingw64'], + ['x86_64-linux-gnu', 'linux-x86_64'], ].map do |platform, openssl_config| CrossLibrary.new platform, openssl_config end @@ -35,9 +36,11 @@ Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext| # The fat binary gem doesn't depend on the freetds package, since it bundles the library. spec.metadata.delete('msys2_mingw_dependencies') - spec.files += [ - "ports/#{spec.platform.to_s}/bin/libsybdb-5.dll" - ] + if spec.platform.to_s =~ /mingw/ + spec.files << "ports/#{spec.platform.to_s}/bin/libsybdb-5.dll" + elsif spec.platform.to_s =~ /linux/ + spec.files << "ports/#{spec.platform.to_s}/lib/libsybdb.so.5" + end end ext.cross_config_options += CrossLibraries.map do |xlib| diff --git a/ext/tiny_tds/extconf.rb b/ext/tiny_tds/extconf.rb index 2786a7bd..152ef052 100644 --- a/ext/tiny_tds/extconf.rb +++ b/ext/tiny_tds/extconf.rb @@ -52,8 +52,8 @@ class << recipe def configure envs = [] - envs << "CFLAGS=-DDSO_WIN32 -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /mingw|mswin/ - envs << "CFLAGS=-fPIC -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /linux/ + envs << "CFLAGS=-DDSO_WIN32 -DOPENSSL_THREADS" if MiniPortile.windows? + envs << "CFLAGS=-fPIC -DOPENSSL_THREADS" if MiniPortile.linux? execute('configure', ['env', *envs, "./Configure", openssl_platform, "threads", "-static", "CROSS_COMPILE=#{host}-", configure_prefix, "--libdir=lib"], altlog: "config.log") end @@ -72,7 +72,7 @@ def install end libiconv_recipe = BuildRecipe.new("libiconv", ICONV_VERSION, [ICONV_SOURCE_URI]).tap do |recipe| - recipe.configure_options << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/ + recipe.configure_options << "CFLAGS=-fPIC" if MiniPortile.linux? recipe.gem_platform = gem_platform recipe.cook_and_activate @@ -90,8 +90,7 @@ def configure_defaults "--host=#{@host}", "--enable-shared", "--disable-static", - "--disable-odbc", - "--enable-sspi", + "--disable-odbc" ] end end @@ -100,13 +99,18 @@ def configure_defaults # it seems that FreeTDS build system prefers OPENSSL_CFLAGS and OPENSSL_LIBS # but the linker still relies on LIBS and CPPFLAGS # removing one or the other leads to build failures in any case of FreeTDS - recipe.configure_options << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/ + if MiniPortile.linux? + recipe.configure_options << "CFLAGS=-fPIC" + elsif MiniPortile.windows? + recipe.configure_options << "--enable-sspi" + end + recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib" - recipe.configure_options << "LIBS=-liconv -lssl -lcrypto -lwsock32 -lgdi32 -lws2_32 -lcrypt32" + recipe.configure_options << "LIBS=-liconv -lssl -lcrypto #{"-lwsock32 -lgdi32 -lws2_32 -lcrypt32" if MiniPortile.windows?} #{"-ldl -lpthread" if MiniPortile.linux?}" recipe.configure_options << "CPPFLAGS=-I#{openssl_recipe.path}/include" recipe.configure_options << "OPENSSL_CFLAGS=-L#{openssl_recipe.path}/lib" - recipe.configure_options << "OPENSSL_LIBS=-lssl -lcrypto -lwsock32 -lgdi32 -lws2_32 -lcrypt32" + recipe.configure_options << "OPENSSL_LIBS=-lssl -lcrypto #{"-lwsock32 -lgdi32 -lws2_32 -lcrypt32" if MiniPortile.windows?} #{"-ldl -lpthread" if MiniPortile.linux?}" recipe.configure_options << "--with-openssl=#{openssl_recipe.path}" recipe.configure_options << "--with-libiconv-prefix=#{libiconv_recipe.path}" @@ -115,7 +119,9 @@ def configure_defaults recipe.cook_and_activate end - ENV["LDFLAGS"] = "-Wl,-rpath -Wl,#{freetds_recipe.path}/lib" + # enable relative path to later load the FreeTDS shared library + $LDFLAGS << " '-Wl,-rpath=$$ORIGIN/../../../ports/#{gem_platform}/lib'" + dir_config('freetds', "#{freetds_recipe.path}/include", "#{freetds_recipe.path}/lib") else # Make sure to check the ports path for the configured host @@ -172,14 +178,10 @@ def configure_defaults dir_config('freetds', idirs, ldirs) end -if /solaris/ =~ RUBY_PLATFORM - append_cppflags( '-D__EXTENSIONS__' ) -end - find_header('sybfront.h') or abort "Can't find the 'sybfront.h' header" find_header('sybdb.h') or abort "Can't find the 'sybdb.h' header" -unless find_library('sybdb', 'dbanydatecrack') +unless have_library('sybdb', 'dbanydatecrack') abort "Failed! Do you have FreeTDS 1.0.0 or higher installed?" end diff --git a/lib/tiny_tds.rb b/lib/tiny_tds.rb index 74da6646..ca08f9fe 100644 --- a/lib/tiny_tds.rb +++ b/lib/tiny_tds.rb @@ -9,53 +9,41 @@ require 'tiny_tds/result' require 'tiny_tds/gem' -# Support multiple ruby versions, fat binaries under Windows. -if RUBY_PLATFORM =~ /mingw|mswin/ && RUBY_VERSION =~ /(\d+.\d+)/ - ver = Regexp.last_match(1) +module TinyTds + + # Is this file part of a fat binary gem with bundled freetds? + # This path must be enabled by add_dll_directory on Windows. + gplat = ::Gem::Platform.local + POSTGRESQL_LIB_PATH = Dir[File.expand_path("../ports/#{gplat.cpu}-#{gplat.os}*/lib", __dir__)].first add_dll_path = proc do |path, &block| - begin - require 'ruby_installer/runtime' - RubyInstaller::Runtime.add_dll_directory(path, &block) - rescue LoadError - old_path = ENV['PATH'] - ENV['PATH'] = "#{path};#{old_path}" + if RUBY_PLATFORM =~/(mswin|mingw)/i && path begin + require 'ruby_installer/runtime' + RubyInstaller::Runtime.add_dll_directory(path, &block) + rescue LoadError + old_path = ENV['PATH'] + ENV['PATH'] = "#{path};#{old_path}" block.call - ensure ENV['PATH'] = old_path end - end - end - - add_dll_paths = proc do |paths, &block| - if path=paths.shift - add_dll_path.call(path) do - add_dll_paths.call(paths, &block) - end else + # libpq is found by a relative rpath in the cross compiled extension dll + # or by the system library loader block.call end end - # Temporary add bin directories for DLL search, so that freetds DLL can be found. - add_dll_paths.call( TinyTds::Gem.ports_bin_paths ) do + # Add a load path to the one retrieved from pg_config + add_dll_path.call(POSTGRESQL_LIB_PATH) do begin - require "tiny_tds/#{ver}/tiny_tds" - rescue LoadError + # 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 "tiny_tds/#{major_minor}/tiny_tds" + rescue LoadError => e + puts e.inspect require 'tiny_tds/tiny_tds' end end -else - # Load dependent shared libraries into the process, so that they are already present, - # when tiny_tds.so is loaded. This ensures, that shared libraries are loaded even when - # the path is different between build and run time (e.g. Heroku). - ports_libs = File.join(TinyTds::Gem.ports_root_path, - "#{RbConfig::CONFIG['host']}/lib/*.so") - Dir[ports_libs].each do |lib| - require 'fiddle' - Fiddle.dlopen(lib) - end - - require 'tiny_tds/tiny_tds' end diff --git a/tasks/native_gem.rake b/tasks/native_gem.rake index 90c702ee..18be796d 100644 --- a/tasks/native_gem.rake +++ b/tasks/native_gem.rake @@ -7,7 +7,7 @@ CrossLibraries.each do |xlib| RakeCompilerDock.sh <<-EOT, platform: platform bundle install && - rake native:#{platform} pkg/#{SPEC.full_name}-#{platform}.gem MAKEOPTS=-j`nproc` RUBY_CC_VERSION=3.4.1:3.3.5:3.2.0:3.1.0:3.0.0:2.7.0 + rake native:#{platform} pkg/#{SPEC.full_name}-#{platform}.gem MAKEOPTS=-j`nproc` RUBY_CC_VERSION=3.4.1:3.3.5:3.2.0:3.1.0:3.0.0:2.7.0 MAKEFLAGS="V=1" EOT end diff --git a/test/bin/restore-from-native-gem.ps1 b/test/bin/restore-from-native-gem.ps1 new file mode 100644 index 00000000..daeb0287 --- /dev/null +++ b/test/bin/restore-from-native-gem.ps1 @@ -0,0 +1,10 @@ +$gemVersion = (Get-Content VERSION).Trim() +$gemToUnpack = "./tiny_tds-$gemVersion-$env:RUBY_ARCHITECTURE.gem" + +Write-Host "Looking to unpack $gemToUnpack" +gem unpack --target ./tmp "$gemToUnpack" + +# Restore precompiled code +$source = (Resolve-Path ".\tmp\tiny_tds-$gemVersion-$env:RUBY_ARCHITECTURE\lib\tiny_tds").Path +$destination = (Resolve-Path ".\lib\tiny_tds").Path +Get-ChildItem $source -Recurse -Exclude "*.rb" | Copy-Item -Destination {Join-Path $destination $_.FullName.Substring($source.length)} diff --git a/tiny_tds.gemspec b/tiny_tds.gemspec index e50f3bbe..67f5e903 100644 --- a/tiny_tds.gemspec +++ b/tiny_tds.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.7.0' s.metadata['msys2_mingw_dependencies'] = 'freetds' s.add_dependency 'bigdecimal', '~> 3' - s.add_development_dependency 'mini_portile2', '~> 2.5.0' + s.add_development_dependency 'mini_portile2', '~> 2.8.0' s.add_development_dependency 'rake', '~> 13.0.0' s.add_development_dependency 'rake-compiler', '~> 1.2' s.add_development_dependency 'rake-compiler-dock', '~> 1.7.0'