Skip to content

Commit

Permalink
wip: restructure the entire thing
Browse files Browse the repository at this point in the history
  • Loading branch information
andyundso committed Jan 6, 2025
1 parent f5cd106 commit 2dcaa5c
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 388 deletions.
19 changes: 8 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,13 @@ 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
run: bundle install
- uses: ruby/setup-ruby@v1
with:
ruby-version: "2.7"
bundler-cache: true

- name: Write used versions into file
shell: bash
Expand All @@ -34,14 +31,14 @@ jobs:
uses: actions/cache@v4
with:
path: ports
key: cross-compiled-v3-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
key: cross-compiled-v4-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
restore-keys: |
cross-compiled-v3-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
cross-compiled-v3-${{ matrix.platform }}-
cross-compiled-v4-${{ matrix.platform }}-${{ hashFiles('**/.ports_versions') }}
cross-compiled-v4-${{ 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:
Expand Down
33 changes: 18 additions & 15 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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, :toolchain
CrossLibraries = [
['x64-mingw-ucrt', 'mingw64', 'x86_64-w64-mingw32'],
['x64-mingw32', 'mingw64', 'x86_64-w64-mingw32'],
].map do |platform, openssl_config, toolchain|
CrossLibrary.new platform, openssl_config, toolchain
end

# Add our project specific files to clean for a rebuild
CLEAN.include FileList["{ext,lib}/**/*.{so,#{RbConfig::CONFIG['DLEXT']},o}"],
Expand All @@ -35,7 +28,7 @@ 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|
Expand All @@ -52,6 +45,16 @@ Rake::ExtensionTask.new('tiny_tds', SPEC) do |ext|

spec.files += Dir.glob('exe/*')
end

ext.cross_config_options += CrossLibraries.map do |xlib|
{
xlib.platform => [
"--with-cross-build=#{xlib.platform}",
"--with-openssl-platform=#{xlib.openssl_config}",
"--with-toolchain=#{xlib.toolchain}",
]
}
end
end

task build: [:clean, :compile]
Expand Down
219 changes: 148 additions & 71 deletions ext/tiny_tds/extconf.rb
Original file line number Diff line number Diff line change
@@ -1,91 +1,168 @@
ENV['RC_ARCHS'] = '' if RUBY_PLATFORM =~ /darwin/

# :stopdoc:

require 'mkmf'
require 'rbconfig'
require_relative './extconsts'

# Shamelessly copied from nokogiri
#

def do_help
print <<HELP
usage: ruby #{$0} [options]
--with-freetds-dir=DIR
Use the freetds library placed under DIR.
HELP
exit! 0
require_relative 'extconsts'

if ENV['MAINTAINER_MODE']
$stderr.puts "Maintainer mode enabled."
$CFLAGS <<
' -Wall' <<
' -ggdb' <<
' -DDEBUG' <<
' -pedantic'
$LDFLAGS <<
' -ggdb'
end

do_help if arg_config('--help')
puts with_config("cross-build")

if gem_platform=with_config("cross-build")
require 'mini_portile2'

openssl_platform = with_config("openssl-platform")
toolchain = with_config("toolchain")

class BuildRecipe < MiniPortile
def initialize(name, version, files)
super(name, version)
self.files = files
rootdir = File.expand_path('../..', __FILE__)
self.target = File.join(rootdir, "ports")
self.patch_files = Dir[File.join(target, "patches", self.name, self.version, "*.patch")].sort
end

def port_path
"../../../../ports/#{RUBY_PLATFORM}/#{@name}/#{@version}"
end

def tmp_path
"../../../../tmp/#{RUBY_PLATFORM}/ports/#{@name}/#{@version}"
end

def cook_and_activate
checkpoint = "../../../../ports/checkpoints/#{name}-#{version}-#{RUBY_PLATFORM}.installed"

unless File.exist?(checkpoint)
self.cook
FileUtils.mkdir_p("../../../../ports/checkpoints")
FileUtils.touch checkpoint
end

self.activate
self
end
end

# Make sure to check the ports path for the configured host
architecture = RbConfig::CONFIG['arch']
openssl_recipe = BuildRecipe.new("openssl", OPENSSL_VERSION, [OPENSSL_SOURCE_URI]).tap do |recipe|
class << recipe
attr_accessor :openssl_platform

def configure
envs = []
envs << "CFLAGS=-DDSO_WIN32 -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /mingw|mswin/
envs << "CFLAGS=-fPIC -DOPENSSL_THREADS" if RUBY_PLATFORM =~ /linux/
execute('configure', ['env', *envs, "./Configure", openssl_platform, "threads", "-static", "CROSS_COMPILE=#{host}-", configure_prefix], altlog: "config.log")
end

def compile
execute('compile', "#{make_cmd} build_libs")
end

def install
execute('install', "#{make_cmd} install_dev")
end
end

recipe.openssl_platform = openssl_platform
recipe.host = toolchain
recipe.cook_and_activate
end

project_dir = File.expand_path("../../..", __FILE__)
freetds_ports_dir = File.join(project_dir, 'ports', architecture, 'freetds', FREETDS_VERSION)
freetds_ports_dir = File.expand_path(freetds_ports_dir)
libiconv_recipe = BuildRecipe.new("libiconv", ICONV_VERSION, [ICONV_SOURCE_URI]).tap do |recipe|
recipe.configure_options << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/
recipe.host = toolchain

# Add all the special path searching from the original tiny_tds build
# order is important here! First in, first searched.
DIRS = %w(
/opt/local
/usr/local
)
recipe.cook_and_activate
end

if RbConfig::CONFIG['host_os'] =~ /darwin/i
# Ruby below 2.7 seems to label the host CPU on Apple Silicon as aarch64
# 2.7 and above print is as ARM64
target_host_cpu = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') ? 'aarch64' : 'arm64'
freetds_recipe = BuildRecipe.new("freetds", FREETDS_VERSION, [FREETDS_SOURCE_URI]).tap do |recipe|
recipe.configure_options << "CFLAGS=-fPIC" if RUBY_PLATFORM =~ /linux/
recipe.configure_options << "LDFLAGS=-L#{openssl_recipe.path}/lib -L#{openssl_recipe.path}/lib64 -L#{libiconv_recipe.path}/lib"
recipe.configure_options << "LIBS=-lssl -lcrypto -liconv"
recipe.configure_options << "CPPFLAGS=-I#{openssl_recipe.path}/include -I#{libiconv_recipe.path}/include"

if RbConfig::CONFIG['host_cpu'] == target_host_cpu
# Homebrew on Apple Silicon installs into /opt/hombrew
# https://docs.brew.sh/Installation
# On Intel Macs, it is /usr/local, so no changes necessary to DIRS
DIRS.unshift("/opt/homebrew")
recipe.host = toolchain
recipe.cook_and_activate
end
end

if ENV["RI_DEVKIT"] && ENV["MINGW_PREFIX"] # RubyInstaller Support
DIRS.unshift(File.join(ENV["RI_DEVKIT"], ENV["MINGW_PREFIX"]))
end
# Avoid dependency to external libgcc.dll on x86-mingw32
$LDFLAGS << " -static-libgcc"

dir_config('freetds', "#{freetds_recipe.path}/include", "#{freetds_recipe.path}/lib")
else
# Make sure to check the ports path for the configured host
architecture = RbConfig::CONFIG['arch']

project_dir = File.expand_path("../../..", __FILE__)
freetds_ports_dir = File.join(project_dir, 'ports', architecture, 'freetds', FREETDS_VERSION)
freetds_ports_dir = File.expand_path(freetds_ports_dir)

# Add all the special path searching from the original tiny_tds build
# order is important here! First in, first searched.
DIRS = %w(
/opt/local
/usr/local
)

if RbConfig::CONFIG['host_os'] =~ /darwin/i
# Ruby below 2.7 seems to label the host CPU on Apple Silicon as aarch64
# 2.7 and above print is as ARM64
target_host_cpu = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.7') ? 'aarch64' : 'arm64'

if RbConfig::CONFIG['host_cpu'] == target_host_cpu
# Homebrew on Apple Silicon installs into /opt/hombrew
# https://docs.brew.sh/Installation
# On Intel Macs, it is /usr/local, so no changes necessary to DIRS
DIRS.unshift("/opt/homebrew")
end
end

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

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

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

idirs = DIRS.flat_map do |path|
idir = "#{path}/include"
[idir, "#{idir}/freetds"]
end
# Add the search paths for freetds configured above
ldirs = DIRS.flat_map do |path|
ldir = "#{path}/lib"
[ldir, "#{ldir}/freetds"]
end

idirs = DIRS.flat_map do |path|
idir = "#{path}/include"
[idir, "#{idir}/freetds"]
end

puts "looking for freetds headers in the following directories:\n#{idirs.map{|a| " - #{a}\n"}.join}"
puts "looking for freetds library in the following directories:\n#{ldirs.map{|a| " - #{a}\n"}.join}"
dir_config('freetds', idirs, ldirs)

have_dependencies = [
find_header('sybfront.h'),
find_header('sybdb.h'),
find_library('sybdb', 'tdsdbopen'),
find_library('sybdb', 'dbanydatecrack')
].inject(true) do |memo, current|
memo && current
puts "looking for freetds headers in the following directories:\n#{idirs.map{|a| " - #{a}\n"}.join}"
puts "looking for freetds library in the following directories:\n#{ldirs.map{|a| " - #{a}\n"}.join}"
dir_config('freetds', idirs, ldirs)
end

unless have_dependencies
abort 'Failed! Do you have FreeTDS 1.0.0 or higher installed?'
# $stderr.puts "Using freetds from #{dlldir}"

if /solaris/ =~ RUBY_PLATFORM
append_cppflags( '-D__EXTENSIONS__' )
end

create_makefile('tiny_tds/tiny_tds')
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 have_library('sybdb', 'dbanydatecrack')
abort "Failed! Do you have FreeTDS 1.0.0 or higher installed?"
end

# :startdoc:
create_makefile("tiny_tds/tiny_tds")
33 changes: 13 additions & 20 deletions tasks/native_gem.rake
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
# encoding: UTF-8
CrossLibraries.each do |xlib|
platform = xlib.platform

desc "Build fat binary gem for platform #{platform}"
task "gem:native:#{platform}" do
require "rake_compiler_dock"

desc 'Build the native binary gems using rake-compiler-dock'
task 'gem:native' => ['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.0: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
Loading

0 comments on commit 2dcaa5c

Please sign in to comment.