From a89fd5b5948e03f38d2a15678cc2615b3840777d Mon Sep 17 00:00:00 2001 From: Aaron Stone Date: Thu, 19 Jan 2023 13:31:35 -0800 Subject: [PATCH] Improve options for linking with OpenSSL especially on MacOS (#1303) 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 openssl@1.1 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 openssl@1.1 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 openssl@1.1 and openssl@3 installed, be explicit: --with-openssl-dir=$(brew --prefix openssl@1.1) 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 --- .github/workflows/build.yml | 21 +++++++++--------- ext/mysql2/extconf.rb | 43 +++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77701d7e..fe694d84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: 'name@X.Y'" 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: openssl@1.1, 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: openssl@1.1, 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 diff --git a/ext/mysql2/extconf.rb b/ext/mysql2/extconf.rb index a6417acf..7a07639c 100644 --- a/ext/mysql2/extconf.rb +++ b/ext/mysql2/extconf.rb @@ -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-----" @@ -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') @@ -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 @@ -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) @@ -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') @@ -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'