From 5c4273f944b538bc24ed98c52991ea8bf9044654 Mon Sep 17 00:00:00 2001 From: Chris Reuter Date: Thu, 21 Oct 2021 21:33:17 -0400 Subject: Added testing support for cross-MinGW builds. This adds a build_config that will cross-build a Windows executable using the MinGW cross-compiler and will also run the unit (i.e. 'rake test') using Wine. For this to work, I made some modifications to the underlying test scripts as well as some minor changes to a couple of the tests themselves. --- build_config/cross-mingw-winetest.rb | 91 ++++++++++++++++++++++++++++++++ build_config/helpers/wine_runner.rb | 71 +++++++++++++++++++++++++ lib/mruby/build.rb | 17 ++++++ lib/mruby/build/command.rb | 5 ++ mrbgems/mruby-bin-mrbc/bintest/mrbc.rb | 4 +- mrbgems/mruby-bin-mruby/bintest/mruby.rb | 4 +- test/bintest.rb | 18 ++++++- 7 files changed, 204 insertions(+), 6 deletions(-) create mode 100644 build_config/cross-mingw-winetest.rb create mode 100755 build_config/helpers/wine_runner.rb diff --git a/build_config/cross-mingw-winetest.rb b/build_config/cross-mingw-winetest.rb new file mode 100644 index 000000000..fad06b265 --- /dev/null +++ b/build_config/cross-mingw-winetest.rb @@ -0,0 +1,91 @@ + +# Cross-compile using MinGW and test using Wine. +# +# Steps: +# +# 1. Install MinGW; 64-bit target seems to work best. +# +# 2. Install Wine. +# +# 3. Run command: +# +# wine cmd /c echo "Hello world"' +# +# This will confirm that Wine works and will trigger standard +# Wine setup, which is slow. +# +# 4. Confirm that drive 'z:' is mapped to your root filesystem. +# (This is supposed to be a default but it helps to +# double-check.) To confirm, run: +# +# wine cmd /c dir 'z:\\' +# +# This should give you a DOS-style equivalent of 'ls /'. If not, +# you'll need to fix that with winecfg or by adding a symlink to +# '~/.wine/dosdevices'. +# +# 5. You will likely need to tweak the settings below to work with +# your configuration unless it is exactly like one of the platforms +# I've tested on (Ubuntu 20.04 or macOS using brew.) +# +# 6. Run the build command: +# +# MRUBY_CONFIG=build_config/cross-mingw-winetest.rb rake test +# +# If all goes well, you should now have Windows executables and a +# set of passing tests. +# +# +# Caveats: +# +# 1. This works by using a helper script that rewrites test output +# to make it look *nix-like and then handing it back to the test +# cases. Some of the existing tests were (slightly) modified to +# make this easier but only for the 'full-core' gembox. Other +# gems' bintests may or may not work with the helper script and +# may or may not be fixable by extending the script. +# +# 2. MinGW and Wine are both complex and not very consistent so you +# will likely need to do some fiddling to get things to work. +# +# 3. This script assumes you are running it on a *nix-style OS. +# +# 4. I recommend building 64-bit targets only. Building a 32-bit +# Windows binary with i686-w64-mingw32 seems to work (at least, +# it did for me) but the resulting executable failed a number of +# unit tests due to small errors in some floating point +# operations. It's unclear if this indicates more serious problems. +# + + +MRuby::CrossBuild.new("cross-mingw-winetest") do |conf| + conf.toolchain :gcc + + conf.host_target = "x86_64-w64-mingw32" + + # Ubuntu 20 + conf.cc.command = "#{conf.host_target}-gcc-posix" + + # macOS+Wine from brew + #conf.cc.command = "#{conf.host_target}-gcc" + + conf.linker.command = conf.cc.command + conf.archiver.command = "#{conf.host_target}-gcc-ar" + conf.exts.executable = ".exe" + + # By default, we compile as static as possible to remove runtime + # MinGW dependencies; they are probably fixable but it gets + # complicated. + conf.cc.flags = ['-static'] + conf.linker.flags += ['-static'] + + conf.test_runner do |t| + thisdir = File.absolute_path( File.dirname(__FILE__) ) + t.command = File.join(thisdir, * %w{ helpers wine_runner.rb}) + end + + conf.gembox "full-core" + + conf.enable_bintest + conf.enable_test +end diff --git a/build_config/helpers/wine_runner.rb b/build_config/helpers/wine_runner.rb new file mode 100755 index 000000000..9a7eb46b0 --- /dev/null +++ b/build_config/helpers/wine_runner.rb @@ -0,0 +1,71 @@ +#!/usr/bin/env ruby + +# Wrapper for running tests for cross-compiled Windows builds in Wine. + +require 'open3' + +DOSROOT = 'z:' + +# Rewrite test output to replace DOS-isms with Unix-isms. +def clean(output, stderr = false) + ends_with_newline = !!(output =~ /\n$/) + executable = ARGV[0].gsub(/\.exe\z/i, '') + + # Fix line-ends + output = output.gsub(/\r\n/, "\n") + + # Strip out Wine messages + + + results = output.split(/\n/).map do |line| + # Fix file paths + if line =~ /#{DOSROOT}\\/i + line.gsub!(/#{DOSROOT}([^:]*)/i) { |path| + path.gsub!(/^#{DOSROOT}/i, '') + path.gsub!(%r{\\}, '/') + path + } + end + + # strip '.exe' off the end of the executable's name if needed + line.gsub!(/(#{Regexp.escape executable})\.exe/i, '\1') + + line + end + + result_text = results.join("\n") + result_text += "\n" if ends_with_newline + return result_text +end + + +def main + if ARGV.empty? || ARGV[0] =~ /^- (-?) (\?|help|h) $/x + puts "#{$0} " + exit 0 + end + + # For simplicity, just read all of stdin into memory and pass that + # as an argument when invoking wine. (Skipped if STDIN was not + # redirected.) + if !STDIN.tty? + input = STDIN.read + else + input = "" + end + + # Disable all Wine messages so they don't interfere with the output + ENV['WINEDEBUG'] = 'err-all,warn-all,fixme-all,trace-all' + + # Run the program in wine and capture the output + output, errormsg, status = Open3.capture3('wine', *ARGV, :stdin_data => input) + + # Clean and print the results. + STDOUT.write clean(output) + STDERR.write clean(errormsg) + + exit(status.exitstatus) +end + + +main() diff --git a/lib/mruby/build.rb b/lib/mruby/build.rb index 67267ab46..66005a6df 100644 --- a/lib/mruby/build.rb +++ b/lib/mruby/build.rb @@ -541,6 +541,23 @@ EOS end end + def run_bintest + puts ">>> Bintest #{name} <<<" + targets = @gems.select { |v| File.directory? "#{v.dir}/bintest" }.map { |v| filename v.dir } + targets << filename(".") if File.directory? "./bintest" + mrbc = @gems["mruby-bin-mrbc"] ? exefile("#{@build_dir}/bin/mrbc") : mrbcfile + + emulator = @test_runner.command + emulator = @test_runner.shellquote(emulator) if emulator + + env = { + "BUILD_DIR" => @build_dir, + "MRBCFILE" => mrbc, + "EMULATOR" => @test_runner.emulator, + } + sh env, "ruby test/bintest.rb#{verbose_flag} #{targets.join ' '}" + end + protected def create_mrbc_build; end diff --git a/lib/mruby/build/command.rb b/lib/mruby/build/command.rb index c93d08ea7..31a595ef0 100644 --- a/lib/mruby/build/command.rb +++ b/lib/mruby/build/command.rb @@ -363,6 +363,11 @@ module MRuby @flags = [] end + def emulator + return "" unless @command + return [@command, *@flags].map{|c| shellquote(c)}.join(' ') + end + def run(testbinfile) puts "TEST for " + @build.name _run runner_options, { :flags => [flags, verbose_flag].flatten.join(' '), :infile => testbinfile } diff --git a/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb b/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb index f4d9208b3..90bbd123f 100644 --- a/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb +++ b/mrbgems/mruby-bin-mrbc/bintest/mrbc.rb @@ -7,7 +7,7 @@ assert('Compiling multiple files without new line in last line. #2361') do b.write('module B; end') b.flush result = `#{cmd('mrbc')} -c -o #{out.path} #{a.path} #{b.path} 2>&1` - assert_equal "#{cmd('mrbc')}:#{a.path}:Syntax OK", result.chomp + assert_equal "#{cmd_bin('mrbc')}:#{a.path}:Syntax OK", result.chomp assert_equal 0, $?.exitstatus end @@ -16,7 +16,7 @@ assert('parsing function with void argument') do a.write('f ()') a.flush result = `#{cmd('mrbc')} -c -o #{out.path} #{a.path} 2>&1` - assert_equal "#{cmd('mrbc')}:#{a.path}:Syntax OK", result.chomp + assert_equal "#{cmd_bin('mrbc')}:#{a.path}:Syntax OK", result.chomp assert_equal 0, $?.exitstatus end diff --git a/mrbgems/mruby-bin-mruby/bintest/mruby.rb b/mrbgems/mruby-bin-mruby/bintest/mruby.rb index bc25f18c1..a626a13cd 100644 --- a/mrbgems/mruby-bin-mruby/bintest/mruby.rb +++ b/mrbgems/mruby-bin-mruby/bintest/mruby.rb @@ -2,7 +2,7 @@ require 'tempfile' require 'open3' def assert_mruby(exp_out, exp_err, exp_success, args) - out, err, stat = Open3.capture3(cmd("mruby"), *args) + out, err, stat = Open3.capture3( *(cmd_list("mruby") + args)) assert "assert_mruby" do assert_operator(exp_out, :===, out, "standard output") assert_operator(exp_err, :===, err, "standard error") @@ -87,7 +87,7 @@ assert('mruby -e option (no code specified)') do end assert('mruby -h option') do - assert_mruby(/\AUsage: #{Regexp.escape cmd("mruby")} .*/m, "", true, %w[-h]) + assert_mruby(/\AUsage: #{Regexp.escape cmd_bin("mruby")} .*/m, "", true, %w[-h]) end assert('mruby -r option') do diff --git a/test/bintest.rb b/test/bintest.rb index a6888c9fb..773d61edc 100644 --- a/test/bintest.rb +++ b/test/bintest.rb @@ -3,13 +3,27 @@ require 'test/assert.rb' GEMNAME = "" -def cmd(s) +def cmd_list(s) path = s == "mrbc" ? ENV['MRBCFILE'] : "#{ENV['BUILD_DIR']}/bin/#{s}" path = path.sub(/\.exe\z/, "") if /mswin(?!ce)|mingw|bccwin/ =~ RbConfig::CONFIG['host_os'] path = "#{path}.exe".tr("/", "\\") end - path + + path_list = [path] + + emu = ENV['EMULATOR'] + path_list.unshift emu if emu && !emu.empty? + + path_list +end + +def cmd(s) + return cmd_list(s).join(' ') +end + +def cmd_bin(s) + return cmd_list(s).pop end def shellquote(s) -- cgit v1.2.3