Skip to content

Commit

Permalink
wip linux gem
Browse files Browse the repository at this point in the history
  • Loading branch information
andyundso committed Jan 8, 2025
1 parent e768151 commit 0be7840
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 121 deletions.
126 changes: 58 additions & 68 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,7 +24,7 @@ jobs:
uses: ruby/setup-ruby@v1
with:
ruby-version: "2.7"

- name: "Install dependencies"
run: bundle install

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand All @@ -391,27 +384,24 @@ 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
with:
components: sqlcmd,sqlengine
version: ${{ matrix.mssql-version }}
sa-password: "c0MplicatedP@ssword"
force-encryption: ${{ matrix.force-encryption }}

- name: Setup MSSQL database
run: |
Expand Down
9 changes: 6 additions & 3 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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|
Expand Down
30 changes: 16 additions & 14 deletions ext/tiny_tds/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -90,8 +90,7 @@ def configure_defaults
"--host=#{@host}",
"--enable-shared",
"--disable-static",
"--disable-odbc",
"--enable-sspi",
"--disable-odbc"
]
end
end
Expand All @@ -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}"
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
56 changes: 22 additions & 34 deletions lib/tiny_tds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 <major>.<minor> 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
2 changes: 1 addition & 1 deletion tasks/native_gem.rake
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 10 additions & 0 deletions test/bin/restore-from-native-gem.ps1
Original file line number Diff line number Diff line change
@@ -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)}
Loading

0 comments on commit 0be7840

Please sign in to comment.