Commit 5c4273f9 authored by Chris Reuter's avatar Chris Reuter

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.
parent b6b4ac82
# 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
#!/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} <command-line>"
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()
......@@ -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
......
......@@ -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 }
......
......@@ -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
......
......@@ -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
......
......@@ -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)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment