From e8e88ff315c6984ebfe94301d2c543d84c0beb36 Mon Sep 17 00:00:00 2001
From: Lars Kanis <kanis@comcard.de>
Date: Mon, 3 Aug 2015 17:50:59 +0200
Subject: [PATCH] Ship two versions of x86 DLLs with Windows fat binary gems.

C++ DLLs built with i586-mingw32msvc and i686-w64-mingw32 are not compatible.

So this changes the build, so that we ship the ports directorys of both
compiler versions.

This fixes https://github.com/larskanis/fxruby/issues/22
---
 Rakefile               |  36 +++++---
 ext/fox16_c/extconf.rb | 205 ++++++++++++++++-------------------------
 lib/fox16.rb           |  14 ++-
 3 files changed, 114 insertions(+), 141 deletions(-)

diff --git a/Rakefile b/Rakefile
index a15c1ae..c72da39 100755
--- a/Rakefile
+++ b/Rakefile
@@ -98,19 +98,33 @@ Rake::ExtensionTask.new("fox16_c", hoe.spec) do |ext|
 
   # Add dependent DLLs to the cross gems
   ext.cross_compiling do |spec|
-    plat = spec.platform
-    dlls = Dir["tmp/#{plat}/#{ext.name}/*/*.dll"].map{|dll| File.basename(dll) }.uniq
-    spec.files += dlls.map{|dll| "lib/#{plat}/#{dll}" }
-
-    directory "tmp/#{plat}/stage/lib/#{plat}/"
-    dlls.each do |dll|
-      ENV['RUBY_CC_VERSION'].to_s.split(':').last.tap do |ruby_version|
-        file "tmp/#{plat}/stage/lib/#{plat}/#{dll}" => ["tmp/#{plat}/stage/lib/#{plat}/", "tmp/#{plat}/#{ext.name}/#{ruby_version}/#{dll}"] do
-          cp "tmp/#{plat}/#{ext.name}/#{ruby_version}/#{dll}", "tmp/#{plat}/stage/lib/#{plat}"
-          sh "x86_64-w64-mingw32-strip", "tmp/#{plat}/stage/lib/#{plat}/#{dll}"
+    platform_host_map =  {
+      'x86-mingw32' => ['i586-mingw32msvc', 'i686-w64-mingw32'],
+      'x64-mingw32' => ['x86_64-w64-mingw32'],
+    }
+
+    gemplat = spec.platform.to_s
+    platform_host_map[gemplat].each do |host|
+
+      gcc_shared_dlls = %w[libwinpthread-1.dll libgcc_s_dw2-1.dll libgcc_s_sjlj-1.dll libgcc_s_seh-1.dll libstdc++-6.dll]
+
+      dlls = gcc_shared_dlls.select{|dll| File.exist?("ports/#{host}/bin/#{dll}") }
+      dlls += [
+          "libfxscintilla-20.dll",
+          "libFOX-1.6-0.dll",
+          "libjpeg-8.dll",
+          "libpng15-15.dll",
+          "libtiff-5.dll",
+          "zlib1.dll",
+      ]
+
+      spec.files += dlls.map{|dll| "ports/#{host}/bin/#{dll}" }
+
+      dlls.each do |dll|
+        task "ports/#{host}/bin/#{dll}" do |t|
+          sh "x86_64-w64-mingw32-strip", t.name
         end
       end
-      file "lib/#{plat}/#{dll}" => "tmp/#{plat}/stage/lib/#{plat}/#{dll}"
     end
   end
 end
diff --git a/ext/fox16_c/extconf.rb b/ext/fox16_c/extconf.rb
index 5d6913c..149ddf1 100755
--- a/ext/fox16_c/extconf.rb
+++ b/ext/fox16_c/extconf.rb
@@ -2,6 +2,7 @@
 
 require 'fileutils'
 require 'mkmf'
+require 'mini_portile'
 
 def find_installed_fox_version
   stddirs = ["/usr/include/fox-1.6",
@@ -103,19 +104,64 @@ LIBFOX_SOURCE_URI         = "http://ftp.fox-toolkit.org/pub/fox-#{LIBFOX_VERSION
 LIBFXSCINTILLA_VERSION            = ENV['LIBFXSCINTILLA_VERSION'] || '2.28.0'
 LIBFXSCINTILLA_SOURCE_URI         = "http://download.savannah.gnu.org/releases/fxscintilla/fxscintilla-#{LIBFXSCINTILLA_VERSION}.tar.gz"
 
+
+class BuildRecipe < MiniPortile
+  def initialize(name, version, files)
+    super(name, version)
+    self.files = files
+    self.target = File.expand_path('../../../ports', __FILE__)
+    # Prefer host_alias over host in order to use i586-mingw32msvc as
+    # correct compiler prefix for cross build, but use host if not set.
+    self.host = consolidated_host(RbConfig::CONFIG["host_alias"].empty? ? RbConfig::CONFIG["host"] : RbConfig::CONFIG["host_alias"])
+    self.patch_files = Dir[File.join(self.target, "patches", self.name, self.version, "*.diff")].sort
+  end
+
+  def consolidated_host(name)
+    name.gsub('i686-pc-mingw32', 'i586-mingw32msvc')
+  end
+
+  def configure_defaults
+    [
+      "--host=#{host}",    # build for specific target (host)
+      "--disable-static",
+      "--enable-shared",
+    ]
+  end
+
+  def port_path
+    "#{target}/#{host}"
+  end
+
+  # When using rake-compiler-dock on Windows, the underlying Virtualbox shared
+  # folders don't support symlinks, but libiconv expects it for a build on
+  # Linux. We work around this limitation by using the temp dir for cooking.
+  def chdir_for_build
+    build_dir = ENV['RCD_HOST_RUBY_PLATFORM'].to_s =~ /mingw|mswin|cygwin/ ? '/tmp' : '.'
+    Dir.chdir(build_dir) do
+      yield
+    end
+  end
+
+  def cook_and_activate
+    checkpoint = File.join(self.target, "#{self.name}-#{self.version}-#{self.host}.installed")
+    unless File.exist?(checkpoint)
+      chdir_for_build do
+        self.cook
+      end
+      FileUtils.touch checkpoint
+    end
+    self.activate
+    self
+  end
+end
+
 def do_rake_compiler_setup
   if enable_config("win32-cross")
     require 'mini_portile'
 
     dir_config("installed")
 
-    host = RbConfig::CONFIG["host_alias"].empty? ? RbConfig::CONFIG["host"] : RbConfig::CONFIG["host_alias"]
-    libz_recipe = MiniPortile.new("libz", LIBZ_VERSION).tap do |recipe|
-      recipe.files = [LIBZ_SOURCE_URI]
-      recipe.target = portsdir = File.expand_path('../../../ports', __FILE__)
-      # Prefer host_alias over host in order to use i586-mingw32msvc as
-      # correct compiler prefix for cross build, but use host if not set.
-      recipe.host = host
+    libz_recipe = BuildRecipe.new("libz", LIBZ_VERSION, [LIBZ_SOURCE_URI]).tap do |recipe|
       class << recipe
         def configure
           Dir.chdir work_path do
@@ -145,91 +191,30 @@ def do_rake_compiler_setup
         end
       end
 
-      checkpoint = File.join(portsdir, "#{recipe.name}-#{recipe.version}-#{recipe.host}.installed")
-      unless File.exist?(checkpoint)
-        recipe.cook
-        FileUtils.touch checkpoint
-      end
-      recipe.activate
+      recipe.cook_and_activate
     end
 
-    libpng_recipe = MiniPortile.new("libpng", LIBPNG_VERSION).tap do |recipe|
-      recipe.files = [LIBPNG_SOURCE_URI]
-      recipe.target = portsdir = File.expand_path('../../../ports', __FILE__)
-      # Prefer host_alias over host in order to use i586-mingw32msvc as
-      # correct compiler prefix for cross build, but use host if not set.
-      recipe.host = host
-      recipe.configure_options = [
-        "--host=#{recipe.host}",
-        "--enable-shared",
-        "--disable-static",
-        ]
-
-      checkpoint = File.join(portsdir, "#{recipe.name}-#{recipe.version}-#{recipe.host}.installed")
-      unless File.exist?(checkpoint)
-        with_env(
-          'CPPFLAGS' => "-I#{libz_recipe.path}/include",
-          'LDFLAGS' => "-L#{libz_recipe.path}/lib"
-        ) do
-          recipe.cook
-          FileUtils.touch checkpoint
-        end
+    libpng_recipe = BuildRecipe.new("libpng", LIBPNG_VERSION, [LIBPNG_SOURCE_URI]).tap do |recipe|
+      with_env(
+        'CPPFLAGS' => "-I#{libz_recipe.path}/include",
+        'LDFLAGS' => "-L#{libz_recipe.path}/lib"
+      ) do
+        recipe.cook_and_activate
       end
-      recipe.activate
     end
 
-    libjpeg_recipe = MiniPortile.new("libjpeg", LIBJPEG_VERSION).tap do |recipe|
-      recipe.files = [LIBJPEG_SOURCE_URI]
-      recipe.target = portsdir = File.expand_path('../../../ports', __FILE__)
-      # Prefer host_alias over host in order to use i586-mingw32msvc as
-      # correct compiler prefix for cross build, but use host if not set.
-      recipe.host = host
-      recipe.configure_options = [
-        "--host=#{recipe.host}",
-        "--enable-shared",
-        "--disable-static",
-        ]
-
-      checkpoint = File.join(portsdir, "#{recipe.name}-#{recipe.version}-#{recipe.host}.installed")
-      unless File.exist?(checkpoint)
-        recipe.cook
-        FileUtils.touch checkpoint
-      end
-      recipe.activate
+    libjpeg_recipe = BuildRecipe.new("libjpeg", LIBJPEG_VERSION, [LIBJPEG_SOURCE_URI]).tap do |recipe|
+      recipe.cook_and_activate
     end
 
-    libtiff_recipe = MiniPortile.new("libtiff", LIBTIFF_VERSION).tap do |recipe|
-      recipe.files = [LIBTIFF_SOURCE_URI]
-      recipe.target = portsdir = File.expand_path('../../../ports', __FILE__)
-      # Prefer host_alias over host in order to use i586-mingw32msvc as
-      # correct compiler prefix for cross build, but use host if not set.
-      recipe.host = host
-      recipe.configure_options = [
-        "--host=#{recipe.host}",
-        "--enable-shared",
-        "--disable-static",
-        ]
-
-      checkpoint = File.join(portsdir, "#{recipe.name}-#{recipe.version}-#{recipe.host}.installed")
-      unless File.exist?(checkpoint)
-        recipe.cook
-        FileUtils.touch checkpoint
-      end
-      recipe.activate
+    libtiff_recipe = BuildRecipe.new("libtiff", LIBTIFF_VERSION, [LIBTIFF_SOURCE_URI]).tap do |recipe|
+      recipe.cook_and_activate
     end
 
-    libfox_recipe = MiniPortile.new("libfox", LIBFOX_VERSION).tap do |recipe|
-      recipe.files = [LIBFOX_SOURCE_URI]
-      recipe.target = portsdir = File.expand_path('../../../ports', __FILE__)
-      # Prefer host_alias over host in order to use i586-mingw32msvc as
-      # correct compiler prefix for cross build, but use host if not set.
-      recipe.host = host
-      recipe.configure_options = [
-        "--host=#{recipe.host}",
+    libfox_recipe = BuildRecipe.new("libfox", LIBFOX_VERSION, [LIBFOX_SOURCE_URI]).tap do |recipe|
+      recipe.configure_options += [
         "--without-xft",
         "--without-x",
-        "--enable-shared",
-        "--disable-static",
         enable_config("debug") ? "--enable-debug" : "--enable-release",
         ]
       class << recipe
@@ -239,30 +224,15 @@ def do_rake_compiler_setup
         end
       end
 
-      checkpoint = File.join(portsdir, "#{recipe.name}-#{recipe.version}-#{recipe.host}.installed")
-      unless File.exist?(checkpoint)
-        with_env(
-          "CPPFLAGS" => "-I#{libjpeg_recipe.path}/include -I#{libpng_recipe.path}/include -I#{libtiff_recipe.path}/include -I#{libz_recipe.path}/include",
-          "LDFLAGS" => "-L#{libjpeg_recipe.path}/lib -L#{libpng_recipe.path}/lib -L#{libtiff_recipe.path}/lib -L#{libz_recipe.path}/lib"
-        ) do
-          recipe.cook
-          FileUtils.touch checkpoint
-        end
+      with_env(
+        "CPPFLAGS" => "-I#{libjpeg_recipe.path}/include -I#{libpng_recipe.path}/include -I#{libtiff_recipe.path}/include -I#{libz_recipe.path}/include",
+        "LDFLAGS" => "-L#{libjpeg_recipe.path}/lib -L#{libpng_recipe.path}/lib -L#{libtiff_recipe.path}/lib -L#{libz_recipe.path}/lib"
+      ) do
+        recipe.cook_and_activate
       end
-      recipe.activate
     end
 
-    libfxscintills_recipe = MiniPortile.new("libfxscintilla", LIBFXSCINTILLA_VERSION).tap do |recipe|
-      recipe.files = [LIBFXSCINTILLA_SOURCE_URI]
-      recipe.target = portsdir = File.expand_path('../../../ports', __FILE__)
-      # Prefer host_alias over host in order to use i586-mingw32msvc as
-      # correct compiler prefix for cross build, but use host if not set.
-      recipe.host = host
-      recipe.configure_options = [
-        "--host=#{recipe.host}",
-        "--enable-shared",
-        "--disable-static",
-        ]
+    libfxscintills_recipe = BuildRecipe.new("libfxscintilla", LIBFXSCINTILLA_VERSION, [LIBFXSCINTILLA_SOURCE_URI]).tap do |recipe|
       class << recipe
         attr_accessor :libfox_path
         def mk
@@ -282,44 +252,27 @@ def do_rake_compiler_setup
       end
       recipe.libfox_path = libfox_recipe.path
 
-      checkpoint = File.join(portsdir, "#{recipe.name}-#{recipe.version}-#{recipe.host}.installed")
-      unless File.exist?(checkpoint)
-        with_env(
-          'PKG_CONFIG_PATH' => "#{libfox_recipe.path}/lib/pkgconfig"
-        ) do
-          recipe.cook
-          FileUtils.touch checkpoint
-        end
+      with_env(
+        'PKG_CONFIG_PATH' => "#{libfox_recipe.path}/lib/pkgconfig"
+      ) do
+        recipe.cook_and_activate
       end
-      recipe.activate
     end
     $autodetected_fxscintilla = true
 
     dir_config('libfox', "#{libfox_recipe.path}/include/fox-1.6", "#{libfox_recipe.path}/lib")
     dir_config('libfxscintilla', "#{libfxscintills_recipe.path}/include/fxscintilla", "#{libfxscintills_recipe.path}/lib")
 
-    shared_dlls = [
-        "#{libfxscintills_recipe.path}/bin/libfxscintilla-20.dll",
-        "#{libfox_recipe.path}/bin/libFOX-1.6-0.dll",
-        "#{libjpeg_recipe.path}/bin/libjpeg-8.dll",
-        "#{libpng_recipe.path}/bin/libpng15-15.dll",
-        "#{libtiff_recipe.path}/bin/libtiff-5.dll",
-        "#{libz_recipe.path}/bin/zlib1.dll",
-      ]
-    shared_dlls.each do |dll|
-      FileUtils.cp dll, '.', verbose: true
-    end
-
     gcc_shared_dlls = %w[libwinpthread-1.dll libgcc_s_dw2-1.dll libgcc_s_sjlj-1.dll libgcc_s_seh-1.dll libstdc++-6.dll]
     gcc_shared_dlls.each do |dll|
-      cmd = "#{CONFIG['CC']} -print-file-name=#{dll}"
+      cmd = "#{CONFIG['CC']} -print-file-name=\"#{dll}\""
       res = `#{cmd}`.chomp
       next if dll == res
       puts "#{cmd} => #{res}"
-      FileUtils.cp `#{cmd}`.chomp, '.', verbose: true
+      FileUtils.cp `#{cmd}`.chomp, "#{libfox_recipe.path}/bin/", verbose: true
     end
 
-    CONFIG['CXX'] = "#{host}-g++" # CXX setting must be prefixed for cross build
+    CONFIG['CXX'] = "#{libfox_recipe.host}-g++" # CXX setting must be prefixed for cross build
     CONFIG['CC'] += "\nCXX=#{CONFIG['CXX']}" # Hack CXX into Makefile for cross compilation
     CONFIG['LDSHARED'].gsub!('gcc', 'g++') # ensure C++ linker is used, so that libstdc++ is linked static
     $LDFLAGS += " -s" # remove symbol table informations from shared lib
diff --git a/lib/fox16.rb b/lib/fox16.rb
index bfe60ea..1674bff 100644
--- a/lib/fox16.rb
+++ b/lib/fox16.rb
@@ -6,11 +6,17 @@ rescue LoadError
     major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or
       raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}"
 
-    # Set the PATH environment variable, so that libpq.dll can be found.
+    # Set the PATH environment variable, so that the DLLs can be found.
     old_path = ENV['PATH']
-    ENV['PATH'] = "#{File.expand_path("../#{RUBY_PLATFORM.gsub("i386", "x86")}", __FILE__)};#{old_path}"
-    require "#{major_minor}/fox16_c"
-    ENV['PATH'] = old_path
+    begin
+      ports_dir = RbConfig::CONFIG["host"].gsub('i686-pc-mingw32') do
+        major_minor < '2.0' ? 'i586-mingw32msvc' : 'i686-w64-mingw32'
+      end
+      ENV['PATH'] = "#{File.expand_path("../../ports/#{ports_dir}/bin", __FILE__)};#{old_path}"
+      require "#{major_minor}/fox16_c"
+    ensure
+      ENV['PATH'] = old_path
+    end
   else
     raise
   end
-- 
GitLab