Skip to content

Commit

Permalink
Improve options for linking with OpenSSL especially on MacOS (#1303)
Browse files Browse the repository at this point in the history
Starting a few MacOS majors ago, OpenSSL was no longer included in a way that
applications could link against. Even the system Ruby at /usr/bin/ruby was
modified to use a MacOS internal SSL implementation.

The most common workaround is to use Homebrew to install OpenSSL. Using GitHub
Actions as the project's CI tool, we found that both [email protected] and openssl@3
were installed in the default image, and that openssl@3 was returned by default
but this mismatched the version the MySQL client libraries were compiled against.

While the quick workaround might be to look for [email protected] instead of openssl,
a more general improvement is to provide an option for users to specify where
OpenSSL is installed. Indeed this issue has been the cause of many postings on
GH issues and Stack Overflow over the years. Hopefully this PR improves the
situation for a broad swath of users!

Unlike the existing option `--with-opt-dir`, the new option `--with-openssl-dir`
will fail if the argument is not a valid path rather than producing unexpected
results at runtime.

This is the default behavior on MacOS:

    --with-openssl-dir=$(brew --prefix openssl)

If you have both [email protected] and openssl@3 installed, be explicit:

    --with-openssl-dir=$(brew --prefix [email protected])

The option is available on all platforms and may be helpful for non-default
OpenSSL installations on Linux or FreeBSD as well.

Co-authored-by: Jun Aruga <[email protected]>
  • Loading branch information
sodabrew and junaruga authored Jan 19, 2023
1 parent c0410ab commit a89fd5b
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 17 deletions.
21 changes: 10 additions & 11 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,42 +26,41 @@ jobs:
- '2.3'
- '2.2'
- '2.1'
db: ['']
include:
# Comment out due to ci/setup.sh stucking.
# - {os: ubuntu-18.04, ruby: 2.4, db: mariadb10.1}
- {os: ubuntu-20.04, ruby: '2.4', db: mariadb10.3}
- {os: ubuntu-18.04, ruby: '2.4', db: mysql57}
- {os: ubuntu-20.04, ruby: '2.4', db: mysql80}
- {os: ubuntu-18.04, ruby: 'head', db: ''}
- {os: ubuntu-18.04, ruby: 'head'}
# db: A DB's brew package name in macOS case.
# Set a name "db: '[email protected]'" when using an old version.
# MariaDB lastet version
# Allow failure due to the following test failures that rarely happens.
# https://github.com/brianmario/mysql2/issues/1194
- {os: macos-latest, ruby: '2.6', db: mariadb, allow-failure: true}
- {os: macos-latest, ruby: '2.6', db: mariadb, ssl: [email protected], allow-failure: true}
# MySQL latest version
# Allow failure due to the issue #1194.
- {os: macos-latest, ruby: '2.6', db: mysql, allow-failure: true}
- {os: macos-latest, ruby: '2.6', db: mysql, ssl: [email protected], allow-failure: true}
# On the fail-fast: true, it cancels all in-progress jobs
# if any matrix job fails unlike Travis fast_finish.
fail-fast: false
env:
BUNDLE_WITHOUT: development
# reduce MacOS CI time, don't need to clean a runtime that isn't saved
HOMEBREW_NO_INSTALL_CLEANUP: 1
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
steps:
- uses: actions/checkout@v3
- name: Install openssl
if: matrix.os == 'macos-latest'
run: |
brew update
brew install openssl
# https://github.com/ruby/setup-ruby
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- if: matrix.db != ''
- if: matrix.db
run: echo 'DB=${{ matrix.db }}' >> $GITHUB_ENV
- run: sudo echo "127.0.0.1 mysql2gem.example.com" | sudo tee -a /etc/hosts
- run: bash ci/setup.sh
- run: bundle exec rake spec
- if: matrix.ssl
run: echo "rake_spec_opts=--with-openssl-dir=$(brew --prefix ${{ matrix.ssl }})" >> $GITHUB_ENV
- run: bundle exec rake spec -- $rake_spec_opts
43 changes: 37 additions & 6 deletions ext/mysql2/extconf.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
require 'mkmf'
require 'English'

### Some helper functions

def asplode(lib)
if RUBY_PLATFORM =~ /mingw|mswin/
abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----"
Expand All @@ -26,11 +28,7 @@ def add_ssl_defines(header)
end
end

# Homebrew openssl
if RUBY_PLATFORM =~ /darwin/ && system("command -v brew")
openssl_location = `brew --prefix openssl`.strip
$LDFLAGS << " -L#{openssl_location}/lib" if openssl_location
end
### Check for Ruby C extention interfaces

# 2.1+
have_func('rb_absint_size')
Expand All @@ -42,7 +40,33 @@ def add_ssl_defines(header)
# Missing in RBX (https://github.com/rubinius/rubinius/issues/3771)
have_func('rb_wait_for_single_fd')

have_func("rb_enc_interned_str", "ruby.h")
# 3.0+
have_func('rb_enc_interned_str', 'ruby.h')

### Find OpenSSL library

# User-specified OpenSSL if explicitly specified
if with_config('openssl-dir')
_, lib = dir_config('openssl')
if lib
# Ruby versions below 2.0 on Unix and below 2.1 on Windows
# do not properly search for lib directories, and must be corrected:
# https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717
unless lib && lib[-3, 3] == 'lib'
@libdir_basename = 'lib'
_, lib = dir_config('openssl')
end
abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
warn "-----\nUsing --with-openssl-dir=#{File.dirname lib}\n-----"
$LDFLAGS << " -L#{lib}"
end
# Homebrew OpenSSL on MacOS
elsif RUBY_PLATFORM =~ /darwin/ && system('command -v brew')
openssl_location = `brew --prefix openssl`.strip
$LDFLAGS << " -L#{openssl_location}/lib" if openssl_location
end

### Find MySQL client library

# borrowed from mysqlplus
# http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
Expand Down Expand Up @@ -140,10 +164,13 @@ def add_ssl_defines(header)
# to retain compatibility with the typedef in earlier MySQLs.
have_type('my_bool', mysql_h)

### Compiler flags to help catch errors

# This is our wishlist. We use whichever flags work on the host.
# -Wall and -Wextra are included by default.
wishlist = [
'-Weverything',
'-Wno-compound-token-split-by-macro', # Fixed in Ruby 2.7+ at https://bugs.ruby-lang.org/issues/17865
'-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE
'-Wno-conditional-uninitialized', # false positive in client.c
'-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5)
Expand All @@ -168,6 +195,8 @@ def add_ssl_defines(header)

$CFLAGS << ' ' << usable_flags.join(' ')

### Sanitizers to help with debugging -- many are available on both Clang/LLVM and GCC

enabled_sanitizers = disabled_sanitizers = []
# Specify a comma-separated list of sanitizers, or try them all by default
sanitizers = with_config('sanitize')
Expand Down Expand Up @@ -202,6 +231,8 @@ def add_ssl_defines(header)
$CFLAGS << ' -g -fno-omit-frame-pointer'
end

### Find MySQL Client on Windows, set RPATH to find the library at runtime

if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller)
# Build libmysql.a interface link library
require 'rake'
Expand Down

0 comments on commit a89fd5b

Please sign in to comment.