Skip to content

Commit

Permalink
Add cross build task for Linux native gems.
Browse files Browse the repository at this point in the history
The current GLIBC requirement of the nokogiri.so is version 2.17.
This means that distros before 2013 will be incompatible.

No need for libiconv on linux - it's part of glibc.
  • Loading branch information
larskanis committed Aug 17, 2018
1 parent c232226 commit df987d0
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 59 deletions.
8 changes: 8 additions & 0 deletions .cross_rubies
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
2.5.0:i686-w64-mingw32
2.5.0:x86_64-w64-mingw32
2.5.0:i686-linux-gnu
2.5.0:x86_64-linux-gnu
2.4.0:i686-w64-mingw32
2.4.0:x86_64-w64-mingw32
2.4.0:i686-linux-gnu
2.4.0:x86_64-linux-gnu
2.3.0:i686-w64-mingw32
2.3.0:x86_64-w64-mingw32
2.3.0:i686-linux-gnu
2.3.0:x86_64-linux-gnu
2.2.2:i686-w64-mingw32
2.2.2:x86_64-w64-mingw32
2.2.2:i686-linux-gnu
2.2.2:x86_64-linux-gnu
93 changes: 71 additions & 22 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,34 @@ CrossRuby = Struct.new(:version, :host) {
def platform
@platform ||=
case host
when /\Ax86_64-/
when /\Ax86_64.*mingw32/
'x64-mingw32'
when /\Ai[3-6]86-/
when /\Ai[3-6]86.*mingw32/
'x86-mingw32'
when /\Ax86_64.*linux/
'x86_64-linux'
when /\Ai[3-6]86.*linux/
'x86-linux'
else
raise "unsupported host: #{host}"
end
end

def windows?
!!(platform =~ /mingw|mswin/)
end

def tool(name)
(@binutils_prefix ||=
case platform
when 'x64-mingw32'
'x86_64-w64-mingw32-'
when 'x86-mingw32'
'i686-w64-mingw32-'
when 'x86_64-linux'
'x86_64-linux-gnu-'
when 'x86-linux'
'i686-linux-gnu-'
end) + name
end

Expand All @@ -77,16 +89,32 @@ CrossRuby = Struct.new(:version, :host) {
end

def dlls
[
'kernel32.dll',
'msvcrt.dll',
'ws2_32.dll',
*(case
when ver >= '2.0.0'
'user32.dll'
end),
libruby_dll
]
case platform
when /mingw32/
[
'kernel32.dll',
'msvcrt.dll',
'ws2_32.dll',
*(case
when ver >= '2.0.0'
'user32.dll'
end),
libruby_dll
]
when /linux/
[
'libm.so.6',
'libpthread.so.0',
'libc.so.6',
]
end
end

def dll_ref_versions
case platform
when /linux/
{"GLIBC"=>"2.17"}
end
end
}

Expand Down Expand Up @@ -312,14 +340,35 @@ Concourse.new("nokogiri").create_tasks!
def verify_dll(dll, cross_ruby)
dll_imports = cross_ruby.dlls
dump = `#{['env', 'LANG=C', cross_ruby.tool('objdump'), '-p', dll].shelljoin}`
raise "unexpected file format for generated dll #{dll}" unless /file format #{Regexp.quote(cross_ruby.target)}\s/ === dump
raise "export function Init_nokogiri not in dll #{dll}" unless /Table.*\sInit_nokogiri\s/mi === dump

# Verify that the expected DLL dependencies match the actual dependencies
# and that no further dependencies exist.
dll_imports_is = dump.scan(/DLL Name: (.*)$/).map(&:first).map(&:downcase).uniq
if dll_imports_is.sort != dll_imports.sort
raise "unexpected dll imports #{dll_imports_is.inspect} in #{dll}"
if cross_ruby.windows?
raise "unexpected file format for generated dll #{dll}" unless /file format #{Regexp.quote(cross_ruby.target)}\s/ === dump
raise "export function Init_nokogiri not in dll #{dll}" unless /Table.*\sInit_nokogiri\s/mi === dump

# Verify that the expected DLL dependencies match the actual dependencies
# and that no further dependencies exist.
dll_imports_is = dump.scan(/DLL Name: (.*)$/).map(&:first).map(&:downcase).uniq
if dll_imports_is.sort != dll_imports.sort
raise "unexpected dll imports #{dll_imports_is.inspect} in #{dll}"
end
else
# Verify that the expected so dependencies match the actual dependencies
# and that no further dependencies exist.
dll_imports_is = dump.scan(/NEEDED\s+(.*)/).map(&:first).uniq
if dll_imports_is.sort != dll_imports.sort
raise "unexpected so imports #{dll_imports_is.inspect} in #{dll}"
end

# Verify that the expected so version requirements match the actual dependencies.
dll_ref_versions_list = dump.scan(/0x[\da-f]+ 0x[\da-f]+ \d+ (\w+)_([\d\.]+)$/i)
# Build a hash of library versions like {"LIBUDEV"=>"183", "GLIBC"=>"2.17"}
dll_ref_versions_is = dll_ref_versions_list.each.with_object({}) do |(lib, ver), h|
if !h[lib] || ver.split(".").map(&:to_i).pack("C*") > h[lib].split(".").map(&:to_i).pack("C*")
h[lib] = ver
end
end
if dll_ref_versions_is != cross_ruby.dll_ref_versions
raise "unexpected so version requirements #{dll_ref_versions_is.inspect} in #{dll}"
end
end
puts "#{dll}: Looks good!"
end
Expand All @@ -339,8 +388,8 @@ task :cross do
end
end

desc "build a windows gem without all the ceremony."
task "gem:windows" do
desc "build native fat binary gems for windows and linux"
task "gem:native" do
require "rake_compiler_dock"
RakeCompilerDock.sh "bundle && rake cross native gem MAKE='nice make -j`nproc`' RUBY_CC_VERSION=#{ENV['RUBY_CC_VERSION']}"
end
Expand Down
6 changes: 3 additions & 3 deletions build_all
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ bundle exec rake clean clobber
rm -rf gems
mkdir -p gems

# windows
bundle exec rake gem:windows
cp -v pkg/nokogiri*{x86,x64}-mingw32*.gem gems
# windows and linux fat binary gems
bundle exec rake gem:native
cp -v pkg/nokogiri-*{x86,x64}*.gem gems

# MRI
bundle exec rake clean
Expand Down
78 changes: 44 additions & 34 deletions ext/nokogiri/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -480,50 +480,60 @@ def using_system_libraries?
url: "http://zlib.net/fossils/#{recipe.name}-#{recipe.version}.tar.gz",
sha256: dependencies["zlib"]["sha256"]
}]
class << recipe
attr_accessor :cross_build_p

def configure
Dir.chdir work_path do
mk = File.read 'win32/Makefile.gcc'
File.open 'win32/Makefile.gcc', 'wb' do |f|
f.puts "BINARY_PATH = #{path}/bin"
f.puts "LIBRARY_PATH = #{path}/lib"
f.puts "INCLUDE_PATH = #{path}/include"
mk.sub!(/^PREFIX\s*=\s*$/, "PREFIX = #{host}-") if cross_build_p
f.puts mk
if windows?
class << recipe
attr_accessor :cross_build_p

def configure
Dir.chdir work_path do
mk = File.read 'win32/Makefile.gcc'
File.open 'win32/Makefile.gcc', 'wb' do |f|
f.puts "BINARY_PATH = #{path}/bin"
f.puts "LIBRARY_PATH = #{path}/lib"
f.puts "INCLUDE_PATH = #{path}/include"
mk.sub!(/^PREFIX\s*=\s*$/, "PREFIX = #{host}-") if cross_build_p
f.puts mk
end
end
end
end

def configured?
Dir.chdir work_path do
!! (File.read('win32/Makefile.gcc') =~ /^BINARY_PATH/)
def configured?
Dir.chdir work_path do
!! (File.read('win32/Makefile.gcc') =~ /^BINARY_PATH/)
end
end
end

def compile
execute "compile", "make -f win32/Makefile.gcc"
end
def compile
execute "compile", "make -f win32/Makefile.gcc"
end

def install
execute "install", "make -f win32/Makefile.gcc install"
def install
execute "install", "make -f win32/Makefile.gcc install"
end
end
recipe.cross_build_p = cross_build_p
else
class << recipe
def configure
execute "configure", ["env", "CHOST=#{host}", "CFLAGS=-fPIC #{ENV['CFLAGS']}", "./configure", "--static", configure_prefix]
end
end
end
recipe.cross_build_p = cross_build_p
end

libiconv_recipe = process_recipe("libiconv", dependencies["libiconv"]["version"], static_p, cross_build_p) do |recipe|
recipe.files = [{
url: "http://ftp.gnu.org/pub/gnu/libiconv/#{recipe.name}-#{recipe.version}.tar.gz",
sha256: dependencies["libiconv"]["sha256"]
}]
recipe.configure_options += [
"CPPFLAGS=-Wall",
"CFLAGS=-O2 -g",
"CXXFLAGS=-O2 -g",
"LDFLAGS="
]
unless nix?
libiconv_recipe = process_recipe("libiconv", dependencies["libiconv"]["version"], static_p, cross_build_p) do |recipe|
recipe.files = [{
url: "http://ftp.gnu.org/pub/gnu/libiconv/#{recipe.name}-#{recipe.version}.tar.gz",
sha256: dependencies["libiconv"]["sha256"]
}]
recipe.configure_options += [
"CPPFLAGS=-Wall",
"CFLAGS=-O2 -g",
"CXXFLAGS=-O2 -g",
"LDFLAGS="
]
end
end
else
if darwin? && !have_header('iconv.h')
Expand Down

0 comments on commit df987d0

Please sign in to comment.