Top Level Namespace
Defined Under Namespace
Modules: PQCrypto
Instance Method Summary collapse
- #argument_value(name) ⇒ Object
-
#command_output(*command) ⇒ Object
Ruby 2.7 installations on macOS are often built against Homebrew OpenSSL 1.1.
- #configure_selected_openssl!(installation) ⇒ Object
- #discover_openssl3_installation ⇒ Object
- #explicit_openssl_installation ⇒ Object
- #host_os ⇒ Object
- #include_paths_from_flags(flags) ⇒ Object
- #normalize_existing_path(path) ⇒ Object
- #openssl_header_major(include_directory) ⇒ Object
- #openssl_installation(include_directory, library_directory) ⇒ Object
- #openssl_library_dirs(prefix) ⇒ Object
- #openssl_library_file(directory) ⇒ Object
- #openssl_prefix_installation(prefix) ⇒ Object
- #pkg_config_openssl_installation ⇒ Object
- #remove_competing_openssl_headers!(selected_include_directory) ⇒ Object
- #strip_include_path(flags, path) ⇒ Object
- #windows_host? ⇒ Boolean
Instance Method Details
#argument_value(name) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'ext/pq_crypto_seal/extconf.rb', line 100 def argument_value(name) prefix = "--#{name}" ARGV.each_with_index do |argument, index| return argument.split("=", 2)[1] if argument.start_with?("#{prefix}=") return ARGV[index + 1] if argument == prefix && ARGV[index + 1] end nil end |
#command_output(*command) ⇒ Object
Ruby 2.7 installations on macOS are often built against Homebrew OpenSSL 1.1. Their RbConfig can inject that old prefix into every extension build, even when OpenSSL 3 is installed separately. Select one complete OpenSSL 3 installation, remove competing OpenSSL header paths, and link the selected libcrypto by its absolute path so headers and library cannot silently come from different roots.
16 17 18 19 20 |
# File 'ext/pq_crypto_seal/extconf.rb', line 16 def command_output(*command) IO.popen(command, err: File::NULL, &:read).to_s.strip rescue StandardError "" end |
#configure_selected_openssl!(installation) ⇒ Object
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 |
# File 'ext/pq_crypto_seal/extconf.rb', line 229 def configure_selected_openssl!(installation) include_directory = installation.fetch(:include_directory) library_directory = installation.fetch(:library_directory) crypto_library = installation.fetch(:crypto_library) remove_competing_openssl_headers!(include_directory) # Do not call dir_config("openssl") here. On Ruby 2.7 it can reuse the # --with-openssl-dir value from Ruby's own build and reintroduce OpenSSL 1.1. $INCFLAGS = "-I#{Shellwords.escape(include_directory)} #{$INCFLAGS}".strip $CPPFLAGS = "-I#{Shellwords.escape(include_directory)} #{$CPPFLAGS}".strip $LIBPATH.unshift(library_directory) unless $LIBPATH.include?(library_directory) $LOCAL_LIBS = "#{Shellwords.escape(crypto_library)} #{$LOCAL_LIBS}".strip unless windows_host? || library_directory.start_with?("/usr/lib") $LDFLAGS = "-Wl,-rpath,#{Shellwords.escape(library_directory)} #{$LDFLAGS}".strip end puts "OpenSSL source: #{installation.fetch(:source)}" puts "OpenSSL prefix: #{installation[:prefix]}" if installation[:prefix] puts "OpenSSL include dir: #{include_directory}" puts "OpenSSL library dir: #{library_directory}" puts "OpenSSL libcrypto: #{crypto_library}" end |
#discover_openssl3_installation ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
# File 'ext/pq_crypto_seal/extconf.rb', line 145 def discover_openssl3_installation explicit = explicit_openssl_installation return explicit if explicit candidates = [] if host_os =~ /darwin/i && find_executable("brew") candidates << command_output("brew", "--prefix", "openssl@3") candidates << command_output("brew", "--prefix", "openssl") end candidates.concat( %w[ /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3 /opt/homebrew/opt/openssl /usr/local/opt/openssl ] ) if host_os =~ /darwin/i candidates.compact.map(&:strip).reject(&:empty?).uniq.each do |prefix| installation = openssl_prefix_installation(prefix) return installation.merge(source: "prefix", prefix: File.(prefix)) if installation end pkg_config_openssl_installation || openssl_prefix_installation("/usr/local")&.merge(source: "system prefix", prefix: "/usr/local") || openssl_prefix_installation("/usr")&.merge(source: "system prefix", prefix: "/usr") end |
#explicit_openssl_installation ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
# File 'ext/pq_crypto_seal/extconf.rb', line 109 def explicit_openssl_installation explicit_root = argument_value("with-openssl-dir") || ENV["OPENSSL_ROOT_DIR"] || ENV["OPENSSL_DIR"] if explicit_root && !explicit_root.to_s.strip.empty? installation = openssl_prefix_installation(explicit_root) abort <<~MSG unless installation #{explicit_root.inspect} does not point to a complete OpenSSL 3 installation. Expected OpenSSL 3 headers below <prefix>/include/openssl and libcrypto below <prefix>/lib, <prefix>/lib64, or a Linux multiarch lib directory. MSG return installation.merge(source: "explicit prefix") end explicit_include = argument_value("with-openssl-include") explicit_library = argument_value("with-openssl-lib") return nil unless explicit_include || explicit_library abort "Both --with-openssl-include and --with-openssl-lib are required together" unless explicit_include && explicit_library installation = openssl_installation(explicit_include, explicit_library) abort "Explicit OpenSSL include/lib paths do not contain a matching OpenSSL 3 headers + libcrypto pair" unless installation installation.merge(source: "explicit include/lib") end |
#host_os ⇒ Object
22 23 24 |
# File 'ext/pq_crypto_seal/extconf.rb', line 22 def host_os RbConfig::CONFIG.fetch("host_os", "") end |
#include_paths_from_flags(flags) ⇒ Object
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'ext/pq_crypto_seal/extconf.rb', line 179 def include_paths_from_flags(flags) tokens = Shellwords.split(flags.to_s) paths = [] index = 0 while index < tokens.length token = tokens[index] case token when "-I", "-isystem" paths << tokens[index + 1] if tokens[index + 1] index += 2 next when /\A-I(.+)\z/ paths << Regexp.last_match(1) when /\A-isystem(.+)\z/ paths << Regexp.last_match(1) end index += 1 end paths rescue ArgumentError [] end |
#normalize_existing_path(path) ⇒ Object
173 174 175 176 177 |
# File 'ext/pq_crypto_seal/extconf.rb', line 173 def normalize_existing_path(path) File.realpath(path) rescue Errno::ENOENT File.(path) end |
#openssl_header_major(include_directory) ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'ext/pq_crypto_seal/extconf.rb', line 55 def openssl_header_major(include_directory) header = File.join(include_directory, "openssl", "opensslv.h") return nil unless File.file?(header) contents = File.read(header) major = contents[/^\s*#\s*define\s+OPENSSL_VERSION_MAJOR\s+(\d+)/, 1] return major.to_i if major # OpenSSL 1.x has no OPENSSL_VERSION_MAJOR. Parse the high hexadecimal nibble # only to reject it cleanly; OpenSSL 3 installations always define the major. number = contents[/^\s*#\s*define\s+OPENSSL_VERSION_NUMBER\s+0x([0-9A-Fa-f]+)L?/, 1] number ? (number.to_i(16) >> 28) & 0xF : nil rescue StandardError nil end |
#openssl_installation(include_directory, library_directory) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'ext/pq_crypto_seal/extconf.rb', line 71 def openssl_installation(include_directory, library_directory) return nil unless include_directory && library_directory include_directory = File.(include_directory) library_directory = File.(library_directory) return nil unless openssl_header_major(include_directory).to_i >= 3 crypto_library = openssl_library_file(library_directory) return nil unless crypto_library { include_directory: include_directory, library_directory: library_directory, crypto_library: crypto_library } end |
#openssl_library_dirs(prefix) ⇒ Object
30 31 32 33 34 |
# File 'ext/pq_crypto_seal/extconf.rb', line 30 def openssl_library_dirs(prefix) dirs = Dir.glob(File.join(prefix, "lib", "*-linux-gnu")) dirs.concat([File.join(prefix, "lib64"), File.join(prefix, "lib")]) dirs.select { |directory| File.directory?(directory) }.uniq end |
#openssl_library_file(directory) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'ext/pq_crypto_seal/extconf.rb', line 36 def openssl_library_file(directory) candidates = [ File.join(directory, "libcrypto.so.3"), *Dir.glob(File.join(directory, "libcrypto.so.3.*")).sort, File.join(directory, "libcrypto.so"), File.join(directory, "libcrypto.3.dylib"), *Dir.glob(File.join(directory, "libcrypto.3.*.dylib")).sort, File.join(directory, "libcrypto.dylib"), File.join(directory, "libcrypto.a"), File.join(directory, "libcrypto.dll.a"), File.join(directory, "crypto.lib") ] path = candidates.find { |candidate| File.file?(candidate) } path && File.realpath(path) rescue Errno::ENOENT nil end |
#openssl_prefix_installation(prefix) ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 |
# File 'ext/pq_crypto_seal/extconf.rb', line 88 def openssl_prefix_installation(prefix) return nil if prefix.nil? || prefix.to_s.strip.empty? prefix = File.(prefix.to_s.strip) include_directory = File.join(prefix, "include") openssl_library_dirs(prefix).each do |library_directory| installation = openssl_installation(include_directory, library_directory) return installation.merge(prefix: prefix) if installation end nil end |
#pkg_config_openssl_installation ⇒ Object
133 134 135 136 137 138 139 140 141 142 143 |
# File 'ext/pq_crypto_seal/extconf.rb', line 133 def pkg_config_openssl_installation return nil unless find_executable("pkg-config") version = command_output("pkg-config", "--modversion", "openssl") return nil if version.empty? || version.split(".").first.to_i < 3 include_directory = command_output("pkg-config", "--variable=includedir", "openssl") library_directory = command_output("pkg-config", "--variable=libdir", "openssl") installation = openssl_installation(include_directory, library_directory) installation && installation.merge(source: "pkg-config #{version}") end |
#remove_competing_openssl_headers!(selected_include_directory) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'ext/pq_crypto_seal/extconf.rb', line 212 def remove_competing_openssl_headers!(selected_include_directory) selected = normalize_existing_path(selected_include_directory) competing = [$INCFLAGS, $CPPFLAGS, $CFLAGS] .flat_map { |flags| include_paths_from_flags(flags) } .uniq .select do |path| File.file?(File.join(path, "openssl", "opensslv.h")) && normalize_existing_path(path) != selected end competing.each do |path| $INCFLAGS = strip_include_path($INCFLAGS, path) $CPPFLAGS = strip_include_path($CPPFLAGS, path) $CFLAGS = strip_include_path($CFLAGS, path) puts "Ignoring competing OpenSSL headers: #{path}" end end |
#strip_include_path(flags, path) ⇒ Object
204 205 206 207 208 209 210 |
# File 'ext/pq_crypto_seal/extconf.rb', line 204 def strip_include_path(flags, path) escaped = Regexp.escape(path) flags.to_s .gsub(/(?:\A|\s)-I\s*#{escaped}(?=\s|\z)/, " ") .gsub(/(?:\A|\s)-isystem\s*#{escaped}(?=\s|\z)/, " ") .strip end |
#windows_host? ⇒ Boolean
26 27 28 |
# File 'ext/pq_crypto_seal/extconf.rb', line 26 def windows_host? host_os =~ /mswin|mingw|cygwin/i end |