Skip to content

Commit 2a86a6b

Browse files
committed
Compile OpenSSL and libiconv statically into FreeTDS
1 parent f5cd106 commit 2a86a6b

File tree

12 files changed

+222
-399
lines changed

12 files changed

+222
-399
lines changed

.github/workflows/ci.yml

+10-10
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ jobs:
1515
- "x64-mingw-ucrt"
1616
name: cross-compile-windows
1717
runs-on: ubuntu-22.04
18-
container:
19-
image: "ghcr.io/rake-compiler/rake-compiler-dock-image:1.7.0-mri-${{ matrix.platform }}"
2018
steps:
2119
- uses: actions/checkout@v4
2220

23-
- run: git config --global --add safe.directory /__w/tiny_tds/tiny_tds # shrug
24-
25-
- name: Install gems
26-
shell: bash
21+
- name: Set up Ruby
22+
uses: ruby/setup-ruby@v1
23+
with:
24+
ruby-version: "2.7"
25+
26+
- name: "Install dependencies"
2727
run: bundle install
2828

2929
- name: Write used versions into file
@@ -34,14 +34,14 @@ jobs:
3434
uses: actions/cache@v4
3535
with:
3636
path: ports
37-
key: cross-compiled-v3-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
37+
key: cross-compiled-v7-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
3838
restore-keys: |
39-
cross-compiled-v3-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
40-
cross-compiled-v3-${{ matrix.platform }}-
39+
cross-compiled-v7-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
40+
cross-compiled-v7-${{ matrix.platform }}-
4141
4242
- name: Build gem
4343
shell: bash
44-
run: bundle exec rake gem:for_platform[${{ matrix.platform }}]
44+
run: bundle exec rake gem:native:${{ matrix.platform }}
4545

4646
- uses: actions/upload-artifact@v4
4747
with:

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 3.2.0
2+
3+
* Reduce number of files shipped with precompiled Windows gem
4+
15
## 3.1.0
26

37
* Add Ruby 3.4 to the cross compile list
@@ -13,6 +17,7 @@
1317
* Add `bigdecimal` to dependencies
1418

1519
## 2.1.7
20+
1621
* Add Ruby 3.3 to the cross compile list
1722

1823
## 2.1.6

Rakefile

+21-25
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,16 @@ require 'rbconfig'
33
require 'rake'
44
require 'rake/clean'
55
require 'rake/extensiontask'
6-
require_relative './ext/tiny_tds/extconsts'
76

87
SPEC = Gem::Specification.load(File.expand_path('../tiny_tds.gemspec', __FILE__))
98

10-
ruby_cc_ucrt_versions = "3.4.0:3.3.5:3.2.0:3.1.0".freeze
11-
ruby_cc_mingw32_versions = "3.0.0:2.7.0".freeze
12-
13-
GEM_PLATFORM_HOSTS = {
14-
'x64-mingw32' => {
15-
host: 'x86_64-w64-mingw32',
16-
ruby_versions: ruby_cc_mingw32_versions
17-
},
18-
'x64-mingw-ucrt' => {
19-
host: 'x86_64-w64-mingw32',
20-
ruby_versions: ruby_cc_ucrt_versions
21-
},
22-
}
9+
CrossLibrary = Struct.new :platform, :openssl_config
10+
CrossLibraries = [
11+
['x64-mingw-ucrt', 'mingw64'],
12+
['x64-mingw32', 'mingw64'],
13+
].map do |platform, openssl_config|
14+
CrossLibrary.new platform, openssl_config
15+
end
2316

2417
# Add our project specific files to clean for a rebuild
2518
CLEAN.include FileList["{ext,lib}/**/*.{so,#{RbConfig::CONFIG['DLEXT']},o}"],
@@ -35,23 +28,26 @@ Dir['tasks/*.rake'].sort.each { |f| load f }
3528
Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext|
3629
ext.lib_dir = 'lib/tiny_tds'
3730
ext.cross_compile = true
38-
ext.cross_platform = GEM_PLATFORM_HOSTS.keys
31+
ext.cross_platform = CrossLibraries.map(&:platform)
3932

4033
# Add dependent DLLs to the cross gems
4134
ext.cross_compiling do |spec|
4235
# The fat binary gem doesn't depend on the freetds package, since it bundles the library.
4336
spec.metadata.delete('msys2_mingw_dependencies')
44-
45-
# We don't need the sources in a fat binary gem
46-
spec.files = spec.files.reject { |f| f =~ %r{^ports\/archives/} }
47-
48-
# Make sure to include the ports binaries and libraries
49-
spec.files += FileList["ports/#{spec.platform.to_s}/**/**/{bin,lib}/*"].exclude do |f|
50-
File.directory? f
51-
end
52-
53-
spec.files += Dir.glob('exe/*')
37+
38+
spec.files += [
39+
"ports/#{spec.platform.to_s}/bin/libsybdb-5.dll"
40+
]
5441
end
42+
43+
ext.cross_config_options += CrossLibraries.map do |xlib|
44+
{
45+
xlib.platform => [
46+
"--with-cross-build=#{xlib.platform}",
47+
"--with-openssl-platform=#{xlib.openssl_config}"
48+
]
49+
}
50+
end
5551
end
5652

5753
task build: [:clean, :compile]

VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.1.0
1+
3.2.0

ext/tiny_tds/extconf.rb

+166-71
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,186 @@
1-
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/
2-
3-
# :stopdoc:
4-
51
require 'mkmf'
6-
require 'rbconfig'
7-
require_relative './extconsts'
8-
9-
# Shamelessly copied from nokogiri
10-
#
11-
12-
def do_help
13-
print <<HELP
14-
usage: ruby #{$0} [options]
15-
--with-freetds-dir=DIR
16-
Use the freetds library placed under DIR.
17-
HELP
18-
exit! 0
2+
require_relative 'extconsts'
3+
4+
if ENV['MAINTAINER_MODE']
5+
$stderr.puts "Maintainer mode enabled."
6+
$CFLAGS <<
7+
' -Wall' <<
8+
' -ggdb' <<
9+
' -DDEBUG' <<
10+
' -pedantic'
11+
$LDFLAGS <<
12+
' -ggdb'
1913
end
2014

21-
do_help if arg_config('--help')
15+
if gem_platform=with_config("cross-build")
16+
require 'mini_portile2'
17+
18+
openssl_platform = with_config("openssl-platform")
19+
20+
class BuildRecipe < MiniPortile
21+
attr_accessor :gem_platform
22+
23+
def initialize(name, version, files)
24+
super(name, version)
25+
self.files = files
26+
rootdir = File.expand_path('../../..', __FILE__)
27+
self.target = File.join(rootdir, "ports")
28+
self.patch_files = Dir[File.join("patches", self.name, self.version, "*.patch")].sort
29+
end
30+
31+
# this will yield all ports into the same directory, making our path configuration for the linker easier
32+
def port_path
33+
"#{@target}/#{gem_platform}"
34+
end
35+
36+
def cook_and_activate
37+
checkpoint = File.join(self.target, "#{self.name}-#{self.version}-#{gem_platform}.installed")
38+
39+
unless File.exist?(checkpoint)
40+
self.cook
41+
FileUtils.touch checkpoint
42+
end
43+
44+
self.activate
45+
self
46+
end
47+
end
2248

23-
# Make sure to check the ports path for the configured host
24-
architecture = RbConfig::CONFIG['arch']
49+
openssl_recipe = BuildRecipe.new("openssl", OPENSSL_VERSION, [OPENSSL_SOURCE_URI]).tap do |recipe|
50+
class << recipe
51+
attr_accessor :openssl_platform
52+
53+
def configure
54+
envs = []
55+
envs << "CFLAGS=-DDSO_WIN32 -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /mingw|mswin/
56+
envs << "CFLAGS=-fPIC -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /linux/
57+
execute('configure', ['env', *envs, "./Configure", openssl_platform, "threads", "-static", "CROSS_COMPILE=#{host}-", configure_prefix, "--libdir=lib"], altlog: "config.log")
58+
end
59+
60+
def compile
61+
execute('compile', "#{make_cmd} build_libs")
62+
end
63+
64+
def install
65+
execute('install', "#{make_cmd} install_dev")
66+
end
67+
end
68+
69+
recipe.gem_platform = gem_platform
70+
recipe.openssl_platform = openssl_platform
71+
recipe.cook_and_activate
72+
end
2573

26-
project_dir = File.expand_path("../../..", __FILE__)
27-
freetds_ports_dir = File.join(project_dir, 'ports', architecture, 'freetds', FREETDS_VERSION)
28-
freetds_ports_dir = File.expand_path(freetds_ports_dir)
74+
libiconv_recipe = BuildRecipe.new("libiconv", ICONV_VERSION, [ICONV_SOURCE_URI]).tap do |recipe|
75+
recipe.configure_options << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/
76+
recipe.gem_platform = gem_platform
2977

30-
# Add all the special path searching from the original tiny_tds build
31-
# order is important here! First in, first searched.
32-
DIRS = %w(
33-
/opt/local
34-
/usr/local
35-
)
78+
recipe.cook_and_activate
79+
end
3680

37-
if RbConfig::CONFIG['host_os'] =~ /darwin/i
38-
# Ruby below 2.7 seems to label the host CPU on Apple Silicon as aarch64
39-
# 2.7 and above print is as ARM64
40-
target_host_cpu = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') ? 'aarch64' : 'arm64'
81+
# remove the ".la" files, otherwise libtool starts to complain when linking into FreeTDS
82+
Dir.glob(File.join(libiconv_recipe.path, "lib", "**", "*.la")).each do |la_file|
83+
File.delete(la_file)
84+
end
4185

42-
if RbConfig::CONFIG['host_cpu'] == target_host_cpu
43-
# Homebrew on Apple Silicon installs into /opt/hombrew
44-
# https://docs.brew.sh/Installation
45-
# On Intel Macs, it is /usr/local, so no changes necessary to DIRS
46-
DIRS.unshift("/opt/homebrew")
86+
freetds_recipe = BuildRecipe.new("freetds", FREETDS_VERSION, [FREETDS_SOURCE_URI]).tap do |recipe|
87+
class << recipe
88+
def configure_defaults
89+
[
90+
"--host=#{@host}",
91+
"--enable-shared",
92+
"--disable-static",
93+
"--disable-odbc",
94+
"--enable-sspi",
95+
]
96+
end
97+
end
98+
99+
# i am not 100% what is going on behind the scenes
100+
# it seems that FreeTDS build system prefers OPENSSL_CFLAGS and OPENSSL_LIBS
101+
# but the linker still relies on LIBS and CPPFLAGS
102+
# removing one or the other leads to build failures in any case of FreeTDS
103+
recipe.configure_options << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/
104+
recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib"
105+
recipe.configure_options << "LIBS=-liconv -lssl -lcrypto -lwsock32 -lgdi32 -lws2_32 -lcrypt32"
106+
recipe.configure_options << "CPPFLAGS=-I#{openssl_recipe.path}/include"
107+
108+
recipe.configure_options << "OPENSSL_CFLAGS=-L#{openssl_recipe.path}/lib"
109+
recipe.configure_options << "OPENSSL_LIBS=-lssl -lcrypto -lwsock32 -lgdi32 -lws2_32 -lcrypt32"
110+
111+
recipe.configure_options << "--with-openssl=#{openssl_recipe.path}"
112+
recipe.configure_options << "--with-libiconv-prefix=#{libiconv_recipe.path}"
113+
114+
recipe.gem_platform = gem_platform
115+
recipe.cook_and_activate
47116
end
48-
end
49117

50-
if ENV["RI_DEVKIT"] && ENV["MINGW_PREFIX"] # RubyInstaller Support
51-
DIRS.unshift(File.join(ENV["RI_DEVKIT"], ENV["MINGW_PREFIX"]))
52-
end
118+
ENV["LDFLAGS"] = "-Wl,-rpath -Wl,#{freetds_recipe.path}/lib"
119+
dir_config('freetds', "#{freetds_recipe.path}/include", "#{freetds_recipe.path}/lib")
120+
else
121+
# Make sure to check the ports path for the configured host
122+
architecture = RbConfig::CONFIG['arch']
123+
124+
project_dir = File.expand_path("../../..", __FILE__)
125+
freetds_ports_dir = File.join(project_dir, 'ports', architecture, 'freetds', FREETDS_VERSION)
126+
freetds_ports_dir = File.expand_path(freetds_ports_dir)
127+
128+
# Add all the special path searching from the original tiny_tds build
129+
# order is important here! First in, first searched.
130+
DIRS = %w(
131+
/opt/local
132+
/usr/local
133+
)
134+
135+
if RbConfig::CONFIG['host_os'] =~ /darwin/i
136+
# Ruby below 2.7 seems to label the host CPU on Apple Silicon as aarch64
137+
# 2.7 and above print is as ARM64
138+
target_host_cpu = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') ? 'aarch64' : 'arm64'
139+
140+
if RbConfig::CONFIG['host_cpu'] == target_host_cpu
141+
# Homebrew on Apple Silicon installs into /opt/hombrew
142+
# https://docs.brew.sh/Installation
143+
# On Intel Macs, it is /usr/local, so no changes necessary to DIRS
144+
DIRS.unshift("/opt/homebrew")
145+
end
146+
end
53147

54-
# Add the ports directory if it exists for local developer builds
55-
DIRS.unshift(freetds_ports_dir) if File.directory?(freetds_ports_dir)
148+
if ENV["RI_DEVKIT"] && ENV["MINGW_PREFIX"] # RubyInstaller Support
149+
DIRS.unshift(File.join(ENV["RI_DEVKIT"], ENV["MINGW_PREFIX"]))
150+
end
56151

57-
# Grab freetds environment variable for use by people on services like
58-
# Heroku who they can't easily use bundler config to set directories
59-
DIRS.unshift(ENV['FREETDS_DIR']) if ENV.has_key?('FREETDS_DIR')
152+
# Add the ports directory if it exists for local developer builds
153+
DIRS.unshift(freetds_ports_dir) if File.directory?(freetds_ports_dir)
60154

61-
# Add the search paths for freetds configured above
62-
ldirs = DIRS.flat_map do |path|
63-
ldir = "#{path}/lib"
64-
[ldir, "#{ldir}/freetds"]
65-
end
155+
# Grab freetds environment variable for use by people on services like
156+
# Heroku who they can't easily use bundler config to set directories
157+
DIRS.unshift(ENV['FREETDS_DIR']) if ENV.has_key?('FREETDS_DIR')
66158

67-
idirs = DIRS.flat_map do |path|
68-
idir = "#{path}/include"
69-
[idir, "#{idir}/freetds"]
70-
end
159+
# Add the search paths for freetds configured above
160+
ldirs = DIRS.flat_map do |path|
161+
ldir = "#{path}/lib"
162+
[ldir, "#{ldir}/freetds"]
163+
end
71164

72-
puts "looking for freetds headers in the following directories:\n#{idirs.map{|a| " - #{a}\n"}.join}"
73-
puts "looking for freetds library in the following directories:\n#{ldirs.map{|a| " - #{a}\n"}.join}"
74-
dir_config('freetds', idirs, ldirs)
75-
76-
have_dependencies = [
77-
find_header('sybfront.h'),
78-
find_header('sybdb.h'),
79-
find_library('sybdb', 'tdsdbopen'),
80-
find_library('sybdb', 'dbanydatecrack')
81-
].inject(true) do |memo, current|
82-
memo && current
165+
idirs = DIRS.flat_map do |path|
166+
idir = "#{path}/include"
167+
[idir, "#{idir}/freetds"]
168+
end
169+
170+
puts "looking for freetds headers in the following directories:\n#{idirs.map{|a| " - #{a}\n"}.join}"
171+
puts "looking for freetds library in the following directories:\n#{ldirs.map{|a| " - #{a}\n"}.join}"
172+
dir_config('freetds', idirs, ldirs)
83173
end
84174

85-
unless have_dependencies
86-
abort 'Failed! Do you have FreeTDS 1.0.0 or higher installed?'
175+
if /solaris/ =~ RUBY_PLATFORM
176+
append_cppflags( '-D__EXTENSIONS__' )
87177
end
88178

89-
create_makefile('tiny_tds/tiny_tds')
179+
find_header('sybfront.h') or abort "Can't find the 'sybfront.h' header"
180+
find_header('sybdb.h') or abort "Can't find the 'sybdb.h' header"
181+
182+
unless find_library('sybdb', 'dbanydatecrack')
183+
abort "Failed! Do you have FreeTDS 1.0.0 or higher installed?"
184+
end
90185

91-
# :startdoc:
186+
create_makefile("tiny_tds/tiny_tds")

lib/tiny_tds.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
end
3939
end
4040

41-
# Temporary add bin directories for DLL search, so that freetds DLLs can be found.
41+
# Temporary add bin directories for DLL search, so that freetds DLL can be found.
4242
add_dll_paths.call( TinyTds::Gem.ports_bin_paths ) do
4343
begin
4444
require "tiny_tds/#{ver}/tiny_tds"

0 commit comments

Comments
 (0)