From 2a86a6bc51ca10ebd35aa3a53aaa02b06a9ea577 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 11:23:11 +0100 Subject: [PATCH 01/11] Compile OpenSSL and libiconv statically into FreeTDS --- .github/workflows/ci.yml | 20 ++-- CHANGELOG.md | 5 + Rakefile | 46 ++++---- VERSION | 2 +- ext/tiny_tds/extconf.rb | 237 +++++++++++++++++++++++++++------------ lib/tiny_tds.rb | 2 +- tasks/native_gem.rake | 33 +++--- tasks/ports.rake | 92 +-------------- tasks/ports/freetds.rb | 32 ------ tasks/ports/libiconv.rb | 26 ----- tasks/ports/openssl.rb | 62 ---------- tasks/ports/recipe.rb | 64 ----------- 12 files changed, 222 insertions(+), 399 deletions(-) delete mode 100644 tasks/ports/freetds.rb delete mode 100644 tasks/ports/libiconv.rb delete mode 100644 tasks/ports/openssl.rb delete mode 100644 tasks/ports/recipe.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a603b679..6e18dbd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,15 +15,15 @@ jobs: - "x64-mingw-ucrt" name: cross-compile-windows runs-on: ubuntu-22.04 - container: - image: "ghcr.io/rake-compiler/rake-compiler-dock-image:1.7.0-mri-${{ matrix.platform }}" steps: - uses: actions/checkout@v4 - - run: git config --global --add safe.directory /__w/tiny_tds/tiny_tds # shrug - - - name: Install gems - shell: bash + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: "2.7" + + - name: "Install dependencies" run: bundle install - name: Write used versions into file @@ -34,14 +34,14 @@ jobs: uses: actions/cache@v4 with: path: ports - key: cross-compiled-v3-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + key: cross-compiled-v7-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} restore-keys: | - cross-compiled-v3-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} - cross-compiled-v3-${{ matrix.platform }}- + cross-compiled-v7-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + cross-compiled-v7-${{ matrix.platform }}- - name: Build gem shell: bash - run: bundle exec rake gem:for_platform[${{ matrix.platform }}] + run: bundle exec rake gem:native:${{ matrix.platform }} - uses: actions/upload-artifact@v4 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3128e1ec..3a9a4a8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.2.0 + +* Reduce number of files shipped with precompiled Windows gem + ## 3.1.0 * Add Ruby 3.4 to the cross compile list @@ -13,6 +17,7 @@ * Add `bigdecimal` to dependencies ## 2.1.7 + * Add Ruby 3.3 to the cross compile list ## 2.1.6 diff --git a/Rakefile b/Rakefile index 9999bff6..403d6deb 100644 --- a/Rakefile +++ b/Rakefile @@ -3,23 +3,16 @@ require 'rbconfig' require 'rake' require 'rake/clean' require 'rake/extensiontask' -require_relative './ext/tiny_tds/extconsts' SPEC = Gem::Specification.load(File.expand_path('../tiny_tds.gemspec', __FILE__)) -ruby_cc_ucrt_versions = "3.4.0:3.3.5:3.2.0:3.1.0".freeze -ruby_cc_mingw32_versions = "3.0.0:2.7.0".freeze - -GEM_PLATFORM_HOSTS = { - 'x64-mingw32' => { - host: 'x86_64-w64-mingw32', - ruby_versions: ruby_cc_mingw32_versions - }, - 'x64-mingw-ucrt' => { - host: 'x86_64-w64-mingw32', - ruby_versions: ruby_cc_ucrt_versions - }, -} +CrossLibrary = Struct.new :platform, :openssl_config +CrossLibraries = [ + ['x64-mingw-ucrt', 'mingw64'], + ['x64-mingw32', 'mingw64'], +].map do |platform, openssl_config| + CrossLibrary.new platform, openssl_config +end # Add our project specific files to clean for a rebuild CLEAN.include FileList["{ext,lib}/**/*.{so,#{RbConfig::CONFIG['DLEXT']},o}"], @@ -35,23 +28,26 @@ Dir['tasks/*.rake'].sort.each { |f| load f } Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext| ext.lib_dir = 'lib/tiny_tds' ext.cross_compile = true - ext.cross_platform = GEM_PLATFORM_HOSTS.keys + ext.cross_platform = CrossLibraries.map(&:platform) # Add dependent DLLs to the cross gems ext.cross_compiling do |spec| # The fat binary gem doesn't depend on the freetds package, since it bundles the library. spec.metadata.delete('msys2_mingw_dependencies') - - # We don't need the sources in a fat binary gem - spec.files = spec.files.reject { |f| f =~ %r{^ports\/archives/} } - - # Make sure to include the ports binaries and libraries - spec.files += FileList["ports/#{spec.platform.to_s}/**/**/{bin,lib}/*"].exclude do |f| - File.directory? f - end - - spec.files += Dir.glob('exe/*') + + spec.files += [ + "ports/#{spec.platform.to_s}/bin/libsybdb-5.dll" + ] end + + ext.cross_config_options += CrossLibraries.map do |xlib| + { + xlib.platform => [ + "--with-cross-build=#{xlib.platform}", + "--with-openssl-platform=#{xlib.openssl_config}" + ] + } + end end task build: [:clean, :compile] diff --git a/VERSION b/VERSION index fd2a0186..944880fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.0 +3.2.0 diff --git a/ext/tiny_tds/extconf.rb b/ext/tiny_tds/extconf.rb index d0691792..2786a7bd 100644 --- a/ext/tiny_tds/extconf.rb +++ b/ext/tiny_tds/extconf.rb @@ -1,91 +1,186 @@ -ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/ - -# :stopdoc: - require 'mkmf' -require 'rbconfig' -require_relative './extconsts' - -# Shamelessly copied from nokogiri -# - -def do_help - print < ['ports:cross'] do - require 'rake_compiler_dock' + 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 + EOT + end - # make sure to install our bundle - sh "bundle package --all" # Avoid repeated downloads of gems by using gem files from the host. - - GEM_PLATFORM_HOSTS.each do |plat, meta| - RakeCompilerDock.sh "bundle --local && RUBY_CC_VERSION=#{meta[:ruby_versions]} rake native:#{plat} gem", platform: plat - end -end - -# assumes you are in a container provided by Rake compiler -# if not, use the task above -task 'gem:for_platform', [:gem_platform] do |_task, args| - args.with_defaults(gem_platform: RbConfig::CONFIG["arch"]) - - sh "bundle install" - Rake::Task["ports:compile"].invoke(GEM_PLATFORM_HOSTS[args.gem_platform][:host], args.gem_platform) - sh "RUBY_CC_VERSION=#{GEM_PLATFORM_HOSTS[args.gem_platform][:ruby_versions]} rake native:#{args.gem_platform} gem" + desc "Build the native binary gems" + multitask 'gem:native' => "gem:native:#{platform}" end diff --git a/tasks/ports.rake b/tasks/ports.rake index 1ab6bf96..4fdc9498 100644 --- a/tasks/ports.rake +++ b/tasks/ports.rake @@ -1,99 +1,20 @@ -# encoding: UTF-8 -require 'mini_portile2' -require 'fileutils' -require_relative 'ports/libiconv' -require_relative 'ports/openssl' -require_relative 'ports/freetds' require_relative '../ext/tiny_tds/extconsts' namespace :ports do libraries_to_compile = { - openssl: Ports::Openssl.new(OPENSSL_VERSION), - libiconv: Ports::Libiconv.new(ICONV_VERSION), - freetds: Ports::Freetds.new(FREETDS_VERSION) + openssl: OPENSSL_VERSION, + libiconv: ICONV_VERSION, + freetds: FREETDS_VERSION } - directory "ports" - CLEAN.include "ports/*mingw*" - CLEAN.include "ports/*.installed" - - task :openssl, [:host, :gem_platform] do |_task, args| - args.with_defaults(host: RbConfig::CONFIG['host'], gem_platform: RbConfig::CONFIG["arch"]) - - libraries_to_compile[:openssl].files = [OPENSSL_SOURCE_URI] - libraries_to_compile[:openssl].host = args.host - libraries_to_compile[:openssl].gem_platform = args.gem_platform - - libraries_to_compile[:openssl].cook - libraries_to_compile[:openssl].activate - end - - task :libiconv, [:host, :gem_platform] do |_task, args| - args.with_defaults(host: RbConfig::CONFIG['host'], gem_platform: RbConfig::CONFIG["arch"]) - - libraries_to_compile[:libiconv].files = [ICONV_SOURCE_URI] - libraries_to_compile[:libiconv].host = args.host - libraries_to_compile[:libiconv].gem_platform = args.gem_platform - libraries_to_compile[:libiconv].cook - libraries_to_compile[:libiconv].activate - end - - task :freetds, [:host, :gem_platform] do |_task, args| - args.with_defaults(host: RbConfig::CONFIG['host'], gem_platform: RbConfig::CONFIG["arch"]) - - libraries_to_compile[:freetds].files = [FREETDS_SOURCE_URI] - libraries_to_compile[:freetds].host = args.host - libraries_to_compile[:freetds].gem_platform = args.gem_platform - - if libraries_to_compile[:openssl] - # freetds doesn't have an option that will provide an rpath - # so we do it manually - ENV['OPENSSL_CFLAGS'] = "-Wl,-rpath -Wl,#{libraries_to_compile[:openssl].path}/lib64" - # Add the pkgconfig file with MSYS2'ish path, to prefer our ports build - # over MSYS2 system OpenSSL. - ENV['PKG_CONFIG_PATH'] = "#{libraries_to_compile[:openssl].path.gsub(/^(\w):/i) { "/" + $1.downcase }}/lib64/pkgconfig:#{ENV['PKG_CONFIG_PATH']}" - libraries_to_compile[:freetds].configure_options << "--with-openssl=#{libraries_to_compile[:openssl].path}" - end - - if libraries_to_compile[:libiconv] - libraries_to_compile[:freetds].configure_options << "--with-libiconv-prefix=#{libraries_to_compile[:libiconv].path}" - end - - libraries_to_compile[:freetds].cook - libraries_to_compile[:freetds].activate - end - - task :compile, [:host, :gem_platform] do |_task, args| - args.with_defaults(host: RbConfig::CONFIG['host'], gem_platform: RbConfig::CONFIG["arch"]) - - puts "Compiling ports for #{args.host} (Ruby platform #{args.gem_platform}) ..." - - libraries_to_compile.keys.each do |lib| - Rake::Task["ports:#{lib}"].invoke(args.host, args.gem_platform) - end - end - - desc 'Build the ports windows binaries via rake-compiler-dock' - task 'cross' do - require 'rake_compiler_dock' - - # build the ports for all our cross compile hosts - GEM_PLATFORM_HOSTS.each do |gem_platform, meta| - # make sure to install our bundle - build = ['bundle'] - build << "RUBY_CC_VERSION=#{meta[:ruby_versions]} rake ports:compile[#{meta[:host]},#{gem_platform}] MAKE='make -j`nproc`'" - RakeCompilerDock.sh build.join(' && '), platform: gem_platform - end - end - desc "Notes the actual versions for the compiled ports into a file" task "version_file", [:gem_platform] do |_task, args| args.with_defaults(gem_platform: RbConfig::CONFIG["arch"]) ports_version = {} - libraries_to_compile.each do |library, library_recipe| - ports_version[library] = library_recipe.version + libraries_to_compile.each do |library, version| + ports_version[library] = version end ports_version[:platform] = args.gem_platform @@ -103,6 +24,3 @@ namespace :ports do end end end - -desc 'Build ports and activate libraries for the current architecture.' -task :ports => ['ports:compile'] diff --git a/tasks/ports/freetds.rb b/tasks/ports/freetds.rb deleted file mode 100644 index d0f4166c..00000000 --- a/tasks/ports/freetds.rb +++ /dev/null @@ -1,32 +0,0 @@ -require_relative './recipe' - -module Ports - class Freetds < Recipe - def initialize(version) - super('freetds', version) - - set_patches - end - - private - - def configure_defaults - opts = super - - opts << '--with-pic' - opts << '--disable-odbc' - opts << '--with-tdsver=7.3' - - if windows? - opts << '--sysconfdir=C:/Sites' - opts << '--enable-sspi' - end - - opts - end - - def set_patches - self.patch_files.concat get_patches(name, version) - end - end -end diff --git a/tasks/ports/libiconv.rb b/tasks/ports/libiconv.rb deleted file mode 100644 index 52e0f184..00000000 --- a/tasks/ports/libiconv.rb +++ /dev/null @@ -1,26 +0,0 @@ -require_relative './recipe' - -module Ports - class Libiconv < Recipe - def initialize(version) - super('libiconv', version) - - set_patches - end - - private - - def configure_defaults - [ - "--host=#{@host}", - '--disable-static', - '--enable-shared', - 'CFLAGS=-fPIC -O2' - ] - end - - def set_patches - self.patch_files.concat get_patches(name, version) - end - end -end diff --git a/tasks/ports/openssl.rb b/tasks/ports/openssl.rb deleted file mode 100644 index 17da4e0a..00000000 --- a/tasks/ports/openssl.rb +++ /dev/null @@ -1,62 +0,0 @@ -require_relative './recipe' - -module Ports - class Openssl < Recipe - def initialize(version) - super('openssl', version) - - set_patches - end - - def configure - return if configured? - - md5_file = File.join(tmp_path, 'configure.md5') - digest = Digest::MD5.hexdigest(computed_options.to_s) - File.open(md5_file, "w") { |f| f.write digest } - - # Windows doesn't recognize the shebang so always explicitly use sh - execute('configure', "sh -c \"./Configure #{computed_options.join(' ')}\"") - end - - def install - unless installed? - execute('install', %Q(#{make_cmd} install_sw install_ssldirs)) - end - end - - private - - def configure_defaults - opts = [ - 'shared', - target_arch, - "--openssldir=#{path}", - ] - - if cross_build? - opts << "--cross-compile-prefix=#{host}-" - end - - opts - end - - def target_arch - if windows? - arch = '' - arch = '64' if host=~ /x86_64/ - - "mingw#{arch}" - else - arch = 'x32' - arch = 'x86_64' if host=~ /x86_64/ - - "linux-#{arch}" - end - end - - def set_patches - self.patch_files.concat get_patches(name, version) - end - end -end diff --git a/tasks/ports/recipe.rb b/tasks/ports/recipe.rb deleted file mode 100644 index b3d4dd04..00000000 --- a/tasks/ports/recipe.rb +++ /dev/null @@ -1,64 +0,0 @@ -# encoding: UTF-8 -require 'mini_portile2' -require 'fileutils' -require 'rbconfig' - -module Ports - class Recipe < MiniPortile - attr_writer :gem_platform - - def cook - checkpoint = "ports/checkpoints/#{name}-#{version}-#{gem_platform}.installed" - - unless File.exist? checkpoint - super - FileUtils.mkdir_p("ports/checkpoints") - FileUtils.touch checkpoint - end - end - - private - - attr_reader :gem_platform - - def port_path - "#{@target}/#{gem_platform}/#{@name}/#{@version}" - end - - def tmp_path - "tmp/#{gem_platform}/ports/#{@name}/#{@version}" - end - - def configure_defaults - [ - "--host=#{@host}", - '--disable-static', - '--enable-shared' - ] - end - - def windows? - host =~ /mswin|mingw32/ - end - - def system_host - RbConfig::CONFIG['host'] - end - - def cross_build? - host != system_host - end - - def get_patches(libname, version) - patches = [] - - patch_path = File.expand_path( - File.join('..','..','..','patches',libname,version), - __FILE__ - ) - - patches.concat(Dir[File.join(patch_path, '*.patch')].sort) - patches.concat(Dir[File.join(patch_path, '*.diff')].sort) - end - end -end From e8bd4215b4bfc628487fb94b8bd52c9c86344c92 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 16:50:08 +0100 Subject: [PATCH 02/11] Provide precompiled gem for `x86_64-linux-gnu` --- .github/workflows/ci.yml | 126 ++++++++++++--------------- CHANGELOG.md | 1 + Rakefile | 9 +- ext/tiny_tds/extconf.rb | 30 ++++--- lib/tiny_tds.rb | 53 +++++------ tasks/native_gem.rake | 2 +- test/bin/restore-from-native-gem.ps1 | 10 +++ tiny_tds.gemspec | 2 +- 8 files changed, 113 insertions(+), 120 deletions(-) create mode 100644 test/bin/restore-from-native-gem.ps1 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/CHANGELOG.md b/CHANGELOG.md index 3a9a4a8d..94c4d847 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 3.2.0 * Reduce number of files shipped with precompiled Windows gem +* Provide precompiled gem for Linux (GNU / 64-bit x86) ## 3.1.0 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..057e298e 100644 --- a/lib/tiny_tds.rb +++ b/lib/tiny_tds.rb @@ -9,53 +9,40 @@ 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 + FREETDS_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(FREETDS_LIB_PATH) do begin - require "tiny_tds/#{ver}/tiny_tds" + # 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 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' From 66e1ea599232b0ea3543ce0e3c66000a479cfed3 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 16:52:26 +0100 Subject: [PATCH 03/11] Provide precompiled gem for `x86_64-linux-musl` --- .github/workflows/ci.yml | 8 ++++++++ CHANGELOG.md | 2 +- Rakefile | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1661b167..d818cca6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,7 @@ jobs: - "x64-mingw32" - "x64-mingw-ucrt" - "x86_64-linux-gnu" + - "x86_64-linux-musl" name: cross-compile runs-on: ubuntu-22.04 @@ -322,6 +323,7 @@ jobs: matrix: platform: - "x86_64-linux-gnu" + - "x86_64-linux-musl" ruby-version: - "2.7" @@ -331,6 +333,11 @@ jobs: - "3.3" - "3.4" + include: + - platform: x86_64-linux-musl + docker_tag: "-alpine" + bootstrap: "apk add -U build-base &&" # required to compile bigdecimal on Ruby 2.7 + name: install-linux runs-on: ubuntu-22.04 steps: @@ -347,6 +354,7 @@ jobs: ${{ matrix.docker_platform }} ruby:${{ matrix.ruby-version }}${{ matrix.docker_tag }} \ sh -c " gem update --system 3.3.22 && + ${{ matrix.bootstrap }} gem install --no-document ./gems/tiny_tds-$(cat VERSION)-${{ matrix.platform }}.gem && ruby -e \"require 'tiny_tds'; puts TinyTds::Gem.root_path\" " diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c4d847..53ceb0cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 3.2.0 * Reduce number of files shipped with precompiled Windows gem -* Provide precompiled gem for Linux (GNU / 64-bit x86) +* Provide precompiled gem for Linux (GNU + MUSL / 64-bit x86) ## 3.1.0 diff --git a/Rakefile b/Rakefile index 98505576..7fc29ba7 100644 --- a/Rakefile +++ b/Rakefile @@ -11,6 +11,7 @@ CrossLibraries = [ ['x64-mingw-ucrt', 'mingw64'], ['x64-mingw32', 'mingw64'], ['x86_64-linux-gnu', 'linux-x86_64'], + ['x86_64-linux-musl', 'linux-x86_64'], ].map do |platform, openssl_config| CrossLibrary.new platform, openssl_config end From f10db7f23cb9428901094e88686acbdc37a9f410 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 17:01:07 +0100 Subject: [PATCH 04/11] Provide precompiled gem for `aarch64-linux-gnu` --- .github/workflows/ci.yml | 9 +++++++++ Rakefile | 1 + 2 files changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d818cca6..172d4623 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,7 @@ jobs: - "x64-mingw-ucrt" - "x86_64-linux-gnu" - "x86_64-linux-musl" + - "aarch64-linux-gnu" name: cross-compile runs-on: ubuntu-22.04 @@ -324,6 +325,7 @@ jobs: platform: - "x86_64-linux-gnu" - "x86_64-linux-musl" + - "aarch64-linux-gnu" ruby-version: - "2.7" @@ -338,6 +340,9 @@ jobs: docker_tag: "-alpine" bootstrap: "apk add -U build-base &&" # required to compile bigdecimal on Ruby 2.7 + - platform: aarch64-linux-gnu + docker_platform: "--platform=linux/arm64" + name: install-linux runs-on: ubuntu-22.04 steps: @@ -349,6 +354,10 @@ jobs: name: gem-${{ matrix.platform }} path: precompiled/gems + - name: Setup QEMU for docker + uses: docker/setup-qemu-action@v3 + if: ${{ matrix.docker_platform }} != '' + - run: | docker run --rm -v $PWD/precompiled:/precompiled -w /precompiled \ ${{ matrix.docker_platform }} ruby:${{ matrix.ruby-version }}${{ matrix.docker_tag }} \ diff --git a/Rakefile b/Rakefile index 7fc29ba7..74461e8c 100644 --- a/Rakefile +++ b/Rakefile @@ -12,6 +12,7 @@ CrossLibraries = [ ['x64-mingw32', 'mingw64'], ['x86_64-linux-gnu', 'linux-x86_64'], ['x86_64-linux-musl', 'linux-x86_64'], + ['aarch64-linux-gnu', 'linux-aarch64'], ].map do |platform, openssl_config| CrossLibrary.new platform, openssl_config end From f2c444a3f768ad727884a9c3ab36c13c9568a749 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 17:04:06 +0100 Subject: [PATCH 05/11] Provide precompiled gem for `aarch64-linux-musl` --- .github/workflows/ci.yml | 7 +++++++ CHANGELOG.md | 2 +- Rakefile | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 172d4623..cb29f398 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: - "x86_64-linux-gnu" - "x86_64-linux-musl" - "aarch64-linux-gnu" + - "aarch64-linux-musl" name: cross-compile runs-on: ubuntu-22.04 @@ -326,6 +327,7 @@ jobs: - "x86_64-linux-gnu" - "x86_64-linux-musl" - "aarch64-linux-gnu" + - "aarch64-linux-musl" ruby-version: - "2.7" @@ -343,6 +345,11 @@ jobs: - platform: aarch64-linux-gnu docker_platform: "--platform=linux/arm64" + - platform: aarch64-linux-musl + docker_platform: "--platform=linux/arm64" + docker_tag: "-alpine" + bootstrap: "apk add -U build-base &&" + name: install-linux runs-on: ubuntu-22.04 steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ceb0cd..c79b64f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## 3.2.0 * Reduce number of files shipped with precompiled Windows gem -* Provide precompiled gem for Linux (GNU + MUSL / 64-bit x86) +* Provide precompiled gem for Linux (GNU + MUSL / 64-bit x86 + ARM) ## 3.1.0 diff --git a/Rakefile b/Rakefile index 74461e8c..e4bef2f7 100644 --- a/Rakefile +++ b/Rakefile @@ -13,6 +13,7 @@ CrossLibraries = [ ['x86_64-linux-gnu', 'linux-x86_64'], ['x86_64-linux-musl', 'linux-x86_64'], ['aarch64-linux-gnu', 'linux-aarch64'], + ['aarch64-linux-musl', 'linux-aarch64'], ].map do |platform, openssl_config| CrossLibrary.new platform, openssl_config end From dc890d0ab29a35b62bf92f1f0301855321f608b2 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 17:07:44 +0100 Subject: [PATCH 06/11] Update README --- README.md | 82 ++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index f0af29f6..05d1f88e 100644 --- a/README.md +++ b/README.md @@ -18,16 +18,41 @@ The API is simple and consists of these classes: ## Install -Installing with rubygems should just work. TinyTDS is currently tested on Ruby version 2.7.0 and upward. +tiny_tds is tested with Ruby v2.7 and upwards. +### Windows and Linux (64-bit) + +We precompile tiny_tds with static versions of FreeTDS and supporting libraries. Just run: + +```shell +gem install tiny_tds +``` + +It should find the platform-specific gem. + +You can also avoid getting the platform-specific gem if you want to compile FreeTDS yourself: + +```shell +gem install tiny_tds --platform ruby ``` -$ gem install tiny_tds + +### Mac + +Install FreeTDS via Homebrew: + +```shell +brew install freetds +``` + +Then you can install tiny_tds: + +```shell +gem install tiny_tds ``` -If you use Windows, we pre-compile TinyTDS with static versions of FreeTDS and supporting libraries. -If you're using RubyInstaller, the binary gem will require that devkit is installed and in your path to operate properly. +### Everybody else -On all other platforms, we will find these dependencies. It is recommended that you install the latest FreeTDS via your method of choice. For example, here is how to install FreeTDS on Ubuntu. You might also need the `build-essential` and possibly the `libc6-dev` packages. +`tiny_tds` will find FreeTDS based on your compiler paths. Below you can see an example on how to install FreeTDS on a Debian system. ```shell $ apt-get install wget @@ -37,41 +62,21 @@ $ apt-get install libc6-dev $ wget http://www.freetds.org/files/stable/freetds-1.4.23.tar.gz $ tar -xzf freetds-1.4.23.tar.gz $ cd freetds-1.4.23 -$ ./configure --prefix=/usr/local --with-tdsver=7.4 +$ ./configure --prefix=/usr/local --with-tdsver=7.4 --disable-odbc $ make $ make install ``` -Please read the MiniPortile and/or Windows sections at the end of this file for advanced configuration options past the following: +You can also tell `tiny_tds` where to find your FreeTDS installation. -``` ---with-freetds-dir=DIR - Use the freetds library placed under DIR. +```shell +gem install tiny_tds -- --with-freetds-dir=/opt/freetds ``` - ## Getting Started Optionally, Microsoft has done a great job writing [an article](https://learn.microsoft.com/en-us/sql/connect/ruby/ruby-driver-for-sql-server?view=sql-server-ver16) on how to get started with SQL Server and Ruby using TinyTDS, however, the articles are using outdated versions. - -## FreeTDS Compatibility & Configuration - -TinyTDS is developed against FreeTDs 1.1+. We also test with SQL Server 2017, 2019, 2022 and Azure. Older version of SQL Server or FreeTDS could work, but are not supported. - -> [!IMPORTANT] -> -> Windows users of our pre-compiled native gems need not worry about installing FreeTDS and its dependencies. - -* **Do I need to install FreeTDS?** Yes! Somehow, someway, you are going to need FreeTDS for TinyTDS to compile against. - -* **OK, I am installing FreeTDS, how do I configure it?** Contrary to what most people think, you do not need to specially configure FreeTDS in any way for client libraries like TinyTDS to use it. About the only requirement is that you compile it with libiconv for proper encoding support. FreeTDS must also be compiled with OpenSSL (or the like) to use it with Azure. See the "Using TinyTDS with Azure" section below for more info. - -* **Do I need to configure `--with-tdsver` equal to anything?** Most likely! Technically you should not have to. This is only a default for clients/configs that do not specify what TDS version they want to use. - -* **I want to configure FreeTDS using `--enable-msdblib` and/or `--enable-sybase-compat` so it works for my database. Cool?** It's a waste of time and totally moot! Client libraries like TinyTDS define their own C structure names where they diverge from Sybase to SQL Server. Technically we use the MSDBLIB structures which does not mean we only work with that database vs Sybase. These configs are just a low level default for C libraries that do not define what they want. So I repeat, you do not NEED to use any of these, nor will they hurt anything since we control what C structure names we use internally! - - ## Data Types Our goal is to support every SQL Server data type and covert it to a logical Ruby object. When dates or times are returned, they are instantiated to either `:utc` or `:local` time depending on the query options. Only [datetimeoffset] types are excluded. All strings are associated the to the connection's encoding and all binary data types are associated to Ruby's `ASCII-8BIT/BINARY` encoding. @@ -387,11 +392,11 @@ Please read our [thread_test.rb](https://github.com/rails-sqlserver/tiny_tds/blo This is possible. Since FreeTDS v1.0, utf-16 is enabled by default and supported by tiny_tds. You can toggle it by using `use_utf16` when establishing the connection. -## Compiling Gems for Windows +## Compiling Gems for Windows and Linux -For the convenience of Windows users, TinyTDS ships pre-compiled gems for supported versions of Ruby on Windows. In order to generate these gems, [rake-compiler-dock](https://github.com/rake-compiler/rake-compiler-dock) is used. This project provides several [Docker images](https://registry.hub.docker.com/u/larskanis/) with rvm, cross-compilers and a number of different target versions of Ruby. +For the convenience of Windows and Linux users, TinyTDS ships pre-compiled gems for supported versions of Ruby on Windows. In order to generate these gems, [rake-compiler-dock](https://github.com/rake-compiler/rake-compiler-dock) is used. This project provides several [Docker images](https://registry.hub.docker.com/u/larskanis/) with rvm, cross-compilers and a number of different target versions of Ruby. -Run the following rake task to compile the gems for Windows. This will check the availability of [Docker](https://www.docker.com/) (and boot2docker on Windows or OS-X) and will give some advice for download and installation. When docker is running, it will download the docker image (once-only) and start the build: +Run the following rake task to compile the gems for Windows. This will check the availability of [Docker](https://www.docker.com/) and will give some advice for download and installation. When docker is running, it will download the docker image (once-only) and start the build: ``` $ rake gem:native @@ -446,17 +451,6 @@ $ rake TINYTDS_UNIT_DATASERVER=mydbserver TINYTDS_SCHEMA=sqlserver_2017 $ rake TINYTDS_UNIT_HOST=mydb.host.net TINYTDS_SCHEMA=sqlserver_azure ``` -## Docker Builds - -If you use a [multi stage](https://docs.docker.com/develop/develop-images/multistage-build/) Docker build to assemble your gems in one phase and then copy your app and gems -into another, lighter, container without build tools you will need to make sure you tell the OS how to find dependencies for TinyTDS. - -After you have built and installed FreeTDS it will normally place library files in `/usr/local/lib`. When TinyTDS builds native extensions, -it [already knows to look here](https://github.com/rails-sqlserver/tiny_tds/blob/master/ext/tiny_tds/extconf.rb#L31) but if you copy your app to a new container that link will be broken. - -Set the LD_LIBRARY_PATH environment variable `export LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH}` and run `ldconfig`. If you run `ldd tiny_tds.so` you should not see any broken links. Make -sure you also copied in the library dependencies from your build container with a command like `COPY --from=builder /usr/local/lib /usr/local/lib`. - ## Help & Support * Github Source: http://github.com/rails-sqlserver/tiny_tds @@ -481,4 +475,4 @@ My name is Ken Collins and I currently maintain the SQL Server adapter for Activ ## License -TinyTDS is Copyright (c) 2010-2015 Ken Collins, and Will Bond (Veracross LLC) . It is distributed under the MIT license. Windows binaries contain pre-compiled versions of FreeTDS which is licensed under the GNU LGPL license at +TinyTDS is Copyright (c) 2010-2015 Ken Collins, and Will Bond (Veracross LLC) . It is distributed under the MIT license. Windows and Linux binaries contain pre-compiled versions of FreeTDS and `libconv` which is licensed under the GNU LGPL license at . They also contain OpenSSL, which is licensed under the OpenSSL license at . From 3dd127c40c6331a793b2f822a640f1136531760f Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Wed, 8 Jan 2025 17:14:38 +0100 Subject: [PATCH 07/11] Fix FreeTDS utility wrappers --- .github/workflows/ci.yml | 92 +++++++++++++++++++++++++++++++++++-- CHANGELOG.md | 1 + Rakefile | 12 ++++- ext/tiny_tds/extconf.rb | 5 +- lib/tiny_tds/bin.rb | 15 +----- lib/tiny_tds/gem.rb | 8 +--- test/bin/install-freetds.sh | 6 +-- test/gem_test.rb | 73 ----------------------------- 8 files changed, 107 insertions(+), 105 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb29f398..ec714572 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,10 +39,10 @@ jobs: uses: actions/cache@v4 with: path: ports - key: cross-compiled-v8-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + key: cross-compiled-v9-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} restore-keys: | - cross-compiled-v8-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} - cross-compiled-v8-${{ matrix.platform }}- + cross-compiled-v9-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + cross-compiled-v9-${{ matrix.platform }}- - name: Build gem shell: bash @@ -94,6 +94,18 @@ jobs: ruby -e "require 'tiny_tds'; puts TinyTds::Gem.root_path" exit $LASTEXITCODE + - name: Test if tsql wrapper works + shell: pwsh + run: | + tsql-ttds -C + exit $LASTEXITCODE + + - name: Test if defncopy wrapper works + shell: pwsh + run: | + defncopy-ttds -v + exit $LASTEXITCODE + test-windows-mingw: needs: - cross-compile @@ -207,6 +219,18 @@ jobs: ruby -e "require 'tiny_tds'; puts TinyTds::Gem.root_path" exit $LASTEXITCODE + - name: Test if tsql wrapper works + shell: pwsh + run: | + tsql-ttds -C + exit $LASTEXITCODE + + - name: Test if defncopy wrapper works + shell: pwsh + run: | + defncopy-ttds -v + exit $LASTEXITCODE + test-windows-ucrt: needs: - cross-compile @@ -317,6 +341,18 @@ jobs: ruby -e "require 'tiny_tds'; puts TinyTds::Gem.root_path" exit $LASTEXITCODE + - name: Test if tsql wrapper works + shell: pwsh + run: | + tsql-ttds -C + exit $LASTEXITCODE + + - name: Test if defncopy wrapper works + shell: pwsh + run: | + defncopy-ttds -v + exit $LASTEXITCODE + install-linux: needs: - cross-compile @@ -372,7 +408,9 @@ jobs: gem update --system 3.3.22 && ${{ matrix.bootstrap }} gem install --no-document ./gems/tiny_tds-$(cat VERSION)-${{ matrix.platform }}.gem && - ruby -e \"require 'tiny_tds'; puts TinyTds::Gem.root_path\" + ruby -e \"require 'tiny_tds'; puts TinyTds::Gem.root_path\" && + tsql-ttds -C && + defncopy-ttds -v " test-linux: @@ -449,6 +487,52 @@ jobs: paths: "test/reports/TEST-*.xml" if: always() + install-linux-native: + strategy: + fail-fast: false + matrix: + ruby-version: + - "2.7" + - "3.0" + - "3.1" + - "3.2" + - "3.3" + - "3.4" + + name: install-linux-native + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Install FreeTDS + shell: bash + run: ./test/bin/install-freetds.sh + + - name: Build gem + shell: bash + run: gem build tiny_tds.gemspec + + - name: Install gem + shell: bash + run: gem install "tiny_tds-$(cat VERSION).gem" + + - name: Test if TinyTDS loads + shell: bash + run: ruby -e "require 'tiny_tds'; puts TinyTds::Gem.root_path" + + - name: Test if tsql wrapper works + shell: bash + run: tsql-ttds -C + + - name: Test if defncopy wrapper works + shell: bash + run: defncopy-ttds -v + install_macos: strategy: fail-fast: false diff --git a/CHANGELOG.md b/CHANGELOG.md index c79b64f8..ffe8e6b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * Reduce number of files shipped with precompiled Windows gem * Provide precompiled gem for Linux (GNU + MUSL / 64-bit x86 + ARM) +* Fix wrappers for `tsql` and `defncopy` utility. ## 3.1.0 diff --git a/Rakefile b/Rakefile index e4bef2f7..ec6da503 100644 --- a/Rakefile +++ b/Rakefile @@ -40,9 +40,17 @@ Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext| spec.metadata.delete('msys2_mingw_dependencies') if spec.platform.to_s =~ /mingw/ - spec.files << "ports/#{spec.platform.to_s}/bin/libsybdb-5.dll" + spec.files += [ + "ports/#{spec.platform.to_s}/bin/libsybdb-5.dll", + "ports/#{spec.platform.to_s}/bin/defncopy.exe", + "ports/#{spec.platform.to_s}/bin/tsql.exe" + ] elsif spec.platform.to_s =~ /linux/ - spec.files << "ports/#{spec.platform.to_s}/lib/libsybdb.so.5" + spec.files += [ + "ports/#{spec.platform.to_s}/lib/libsybdb.so.5", + "ports/#{spec.platform.to_s}/bin/defncopy", + "ports/#{spec.platform.to_s}/bin/tsql" + ] end end diff --git a/ext/tiny_tds/extconf.rb b/ext/tiny_tds/extconf.rb index 152ef052..47f6630c 100644 --- a/ext/tiny_tds/extconf.rb +++ b/ext/tiny_tds/extconf.rb @@ -105,8 +105,9 @@ def configure_defaults 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" if MiniPortile.windows?} #{"-ldl -lpthread" if MiniPortile.linux?}" + # pass an additional runtime path to the linker so defncopy and tsql can find our shared sybdb + recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib #{"-Wl,-rpath='$$ORIGIN/../lib'" if MiniPortile.linux?}" + 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" diff --git a/lib/tiny_tds/bin.rb b/lib/tiny_tds/bin.rb index 832692a3..87cee11a 100644 --- a/lib/tiny_tds/bin.rb +++ b/lib/tiny_tds/bin.rb @@ -81,24 +81,11 @@ def which exe = File.expand_path File.join(path, "#{name}#{ext}"), @root next if exe == @binstub next unless File.executable?(exe) - next unless binary?(exe) + return exe end end nil end - - # Implementation directly copied from ptools. - # https://github.com/djberg96/ptools - # https://opensource.org/licenses/Artistic-2.0 - # - def binary?(file) - bytes = File.stat(file).blksize - return false unless bytes - bytes = 4096 if bytes > 4096 - s = (File.read(file, bytes) || '') - s = s.encode('US-ASCII', undef: :replace).split(//) - ((s.size - s.grep(' '..'~').size) / s.size.to_f) > 0.30 - end end end diff --git a/lib/tiny_tds/gem.rb b/lib/tiny_tds/gem.rb index a52f047d..55be78b4 100644 --- a/lib/tiny_tds/gem.rb +++ b/lib/tiny_tds/gem.rb @@ -12,15 +12,11 @@ def ports_root_path end def ports_bin_paths - Dir.glob(File.join(ports_root_path,ports_host,'**','bin')) + Dir.glob(File.join(ports_root_path, '**', 'bin')) end def ports_lib_paths - Dir.glob(File.join(ports_root_path,ports_host,'**','lib')) - end - - def ports_host - RbConfig::CONFIG["arch"] + Dir.glob(File.join(ports_root_path, '**', 'lib')) end end end diff --git a/test/bin/install-freetds.sh b/test/bin/install-freetds.sh index d4ba3955..8c1f0eac 100755 --- a/test/bin/install-freetds.sh +++ b/test/bin/install-freetds.sh @@ -10,11 +10,9 @@ fi wget http://www.freetds.org/files/stable/freetds-$FREETDS_VERSION.tar.gz tar -xzf freetds-$FREETDS_VERSION.tar.gz cd freetds-$FREETDS_VERSION -./configure --prefix=/opt/local \ - --with-openssl=/opt/local \ - --with-tdsver=7.3 +./configure make -make install +sudo make install cd .. rm -rf freetds-$FREETDS_VERSION rm freetds-$FREETDS_VERSION.tar.gz diff --git a/test/gem_test.rb b/test/gem_test.rb index 0412a88a..eea45623 100644 --- a/test/gem_test.rb +++ b/test/gem_test.rb @@ -98,79 +98,6 @@ class GemTest < Minitest::Spec end end end - - describe '#ports_lib_paths' do - let(:ports_lib_paths) { TinyTds::Gem.ports_lib_paths } - - describe 'when the ports directories exist' do - let(:fake_lib_paths) do - ports_host_root = File.join(gem_root, 'ports', 'fake-host-with-dirs') - [ - File.join('a','lib'), - File.join('a','inner','lib'), - File.join('b','lib') - ].map do |p| - File.join(ports_host_root, p) - end - end - - before do - RbConfig::CONFIG['arch'] = 'fake-host-with-dirs' - fake_lib_paths.each do |path| - FileUtils.mkdir_p(path) - end - end - - after do - FileUtils.remove_entry_secure( - File.join(gem_root, 'ports', 'fake-host-with-dirs'), true - ) - end - - it 'should return all the lib directories' do - _(ports_lib_paths.sort).must_equal fake_lib_paths.sort - end - - it 'should return all the lib directories regardless of cwd' do - Dir.chdir '/' - _(ports_lib_paths.sort).must_equal fake_lib_paths.sort - end - end - - describe 'when the ports directories are missing' do - before do - RbConfig::CONFIG['arch'] = 'fake-host-without-dirs' - end - - - it 'should return no directories' do - _(ports_lib_paths).must_be_empty - end - - it 'should return no directories regardless of cwd' do - Dir.chdir '/' - _(ports_lib_paths).must_be_empty - end - end - end - - describe '#ports_host' do - { - 'x64-mingw-ucrt' => 'x64-mingw-ucrt', - 'x64-mingw32' => 'x64-mingw32', - 'x86_64-linux' => 'x86_64-linux', - }.each do |host,expected| - describe "on a #{host} architecture" do - before do - RbConfig::CONFIG['arch'] = host - end - - it "should return a #{expected} ports host" do - _(TinyTds::Gem.ports_host).must_equal expected - end - end - end - end end end From 3dff397386a847cd7f4d254779b67c987a2097ea Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Thu, 9 Jan 2025 13:54:34 +0100 Subject: [PATCH 08/11] Provide `sysconfdir` to FreeTDS You can use this parameter to specify a `freetds.conf` there. per default, it will use the build path, which will be some obscure thing on GitHub Actions. Although most people will customize this location at runtime using environment variables, it still makes sense to provide sensible defaults. --- .github/workflows/ci.yml | 6 +++--- ext/tiny_tds/extconf.rb | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ec714572..dc45f0f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,10 +39,10 @@ jobs: uses: actions/cache@v4 with: path: ports - key: cross-compiled-v9-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + key: cross-compiled-v1-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} restore-keys: | - cross-compiled-v9-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} - cross-compiled-v9-${{ matrix.platform }}- + cross-compiled-v1-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }} + cross-compiled-v1-${{ matrix.platform }}- - name: Build gem shell: bash diff --git a/ext/tiny_tds/extconf.rb b/ext/tiny_tds/extconf.rb index 47f6630c..5ba460cb 100644 --- a/ext/tiny_tds/extconf.rb +++ b/ext/tiny_tds/extconf.rb @@ -115,6 +115,7 @@ def configure_defaults recipe.configure_options << "--with-openssl=#{openssl_recipe.path}" recipe.configure_options << "--with-libiconv-prefix=#{libiconv_recipe.path}" + recipe.configure_options << "--sysconfdir=#{MiniPortile.windows? ? "C:/Sites" : "/usr/local/etc"}" recipe.gem_platform = gem_platform recipe.cook_and_activate From 21ede3845e3a4537dc0479afbda705875ee9ee0c Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Fri, 10 Jan 2025 11:28:36 +0100 Subject: [PATCH 09/11] Replace tabs with spaces --- Rakefile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Rakefile b/Rakefile index ec6da503..887b9ff2 100644 --- a/Rakefile +++ b/Rakefile @@ -8,14 +8,14 @@ SPEC = Gem::Specification.load(File.expand_path('../tiny_tds.gemspec', __FILE__) CrossLibrary = Struct.new :platform, :openssl_config CrossLibraries = [ - ['x64-mingw-ucrt', 'mingw64'], - ['x64-mingw32', 'mingw64'], + ['x64-mingw-ucrt', 'mingw64'], + ['x64-mingw32', 'mingw64'], ['x86_64-linux-gnu', 'linux-x86_64'], ['x86_64-linux-musl', 'linux-x86_64'], ['aarch64-linux-gnu', 'linux-aarch64'], ['aarch64-linux-musl', 'linux-aarch64'], ].map do |platform, openssl_config| - CrossLibrary.new platform, openssl_config + CrossLibrary.new platform, openssl_config end # Add our project specific files to clean for a rebuild @@ -55,13 +55,13 @@ Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext| end ext.cross_config_options += CrossLibraries.map do |xlib| - { - xlib.platform => [ - "--with-cross-build=#{xlib.platform}", - "--with-openssl-platform=#{xlib.openssl_config}" - ] - } - end + { + xlib.platform => [ + "--with-cross-build=#{xlib.platform}", + "--with-openssl-platform=#{xlib.openssl_config}" + ] + } + end end task build: [:clean, :compile] From 4425ab607b80e59c6de0142af0c720f184e3665e Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Fri, 10 Jan 2025 11:29:46 +0100 Subject: [PATCH 10/11] Only set `sysconfdir` on Windows --- ext/tiny_tds/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/tiny_tds/extconf.rb b/ext/tiny_tds/extconf.rb index 5ba460cb..55a0d65e 100644 --- a/ext/tiny_tds/extconf.rb +++ b/ext/tiny_tds/extconf.rb @@ -115,7 +115,7 @@ def configure_defaults recipe.configure_options << "--with-openssl=#{openssl_recipe.path}" recipe.configure_options << "--with-libiconv-prefix=#{libiconv_recipe.path}" - recipe.configure_options << "--sysconfdir=#{MiniPortile.windows? ? "C:/Sites" : "/usr/local/etc"}" + recipe.configure_options << "--sysconfdir=C:/Sites" if MiniPortile.windows? recipe.gem_platform = gem_platform recipe.cook_and_activate From c579def5e2a5a721c70956db12f76159e3911327 Mon Sep 17 00:00:00 2001 From: Andy Pfister Date: Fri, 10 Jan 2025 11:31:16 +0100 Subject: [PATCH 11/11] Rewrite comments about Postgres --- lib/tiny_tds.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/tiny_tds.rb b/lib/tiny_tds.rb index 057e298e..11125a2c 100644 --- a/lib/tiny_tds.rb +++ b/lib/tiny_tds.rb @@ -28,13 +28,12 @@ module TinyTds ENV['PATH'] = old_path end else - # libpq is found by a relative rpath in the cross compiled extension dll + # libsybdb is found by a relative rpath in the cross compiled extension dll # or by the system library loader block.call end end - # Add a load path to the one retrieved from pg_config add_dll_path.call(FREETDS_LIB_PATH) do begin # Try the . subdirectory for fat binary gems