summaryrefslogtreecommitdiff
path: root/tasks/bin/cross-ruby.rake
blob: 8317a2a3b15fff0cfed03503651a9396f7587569 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#--
# Cross-compile ruby, using Rake
#
# This source code is released under the MIT License.
# See LICENSE file for details
#++

#
# This code is inspired and based on notes from the following sites:
#
# http://tenderlovemaking.com/2008/11/21/cross-compiling-ruby-gems-for-win32/
# http://github.com/jbarnette/johnson/tree/master/cross-compile.txt
# http://eigenclass.org/hiki/cross+compiling+rcovrt
#
# This recipe only cleanup the dependency chain and automate it.
# Also opens the door to usage different ruby versions
# for cross-compilation.
#

require 'rake'
require 'rake/clean'

begin
  require 'psych'
rescue LoadError
end

require 'yaml'
require "rbconfig"

# load compiler helpers
# add lib directory to the search path
libdir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)

if RUBY_PLATFORM =~ /mingw|mswin/ then
  puts "This command is meant to be executed under Linux or OSX, not Windows (is for cross-compilation)"
  exit(1)
end

require 'rake/extensioncompiler'

MAKE = ENV['MAKE'] || %w[gmake make].find { |c| system("#{c} -v > /dev/null 2>&1") }
USER_HOME = File.realpath(File.expand_path("~/.rake-compiler"))
RUBY_SOURCE = ENV['SOURCE']
RUBY_BUILD = RbConfig::CONFIG["host"]

# Unset any possible variable that might affect compilation
["RUBYOPT"].each do |var|
  ENV.delete(var)
end

RUBY_CC_VERSIONS = ENV.fetch("VERSION", "1.8.7-p371")
RUBY_CC_VERSIONS.split(":").each do |ruby_cc_version|
  ruby_cc_version = "ruby-" + ruby_cc_version
  # grab the major "1.8" or "1.9" part of the version number
  major = ruby_cc_version.match(/.*-(\d.\d).\d/)[1]

  # define a location where sources will be stored
  source_dir = "#{USER_HOME}/sources/#{ruby_cc_version}"
  directory source_dir
  # clean intermediate files and folders
  CLEAN.include(source_dir)

  # remove the final products and sources
  CLOBBER.include("#{USER_HOME}/sources")
  CLOBBER.include("#{USER_HOME}/builds")
  CLOBBER.include("#{USER_HOME}/config.yml")

  # Extract the sources
  source_file = RUBY_SOURCE ? RUBY_SOURCE.split('/').last : "#{ruby_cc_version}.tar.gz"
  file source_dir => ["#{USER_HOME}/sources/#{source_file}"] do |t|
    t.prerequisites.each { |f| sh "tar xf #{File.basename(f)}", chdir: File.dirname(t.name) }
  end

  # ruby source file should be stored there
  file "#{USER_HOME}/sources/#{ruby_cc_version}.tar.gz" => ["#{USER_HOME}/sources"] do |t|
    # download the source file using wget or curl
    if RUBY_SOURCE
      url = RUBY_SOURCE
    else
      url = "http://cache.ruby-lang.org/pub/ruby/#{major}/#{File.basename(t.name)}"
    end
    sh "wget #{url} || curl -O #{url}", chdir: File.dirname(t.name)
  end

  # Create tasks for each host out of the ":" separated hosts list in the HOST variable.
  # These tasks are processed in parallel as dependencies to the "install" task.
  mingw_hosts = ENV['HOST'] || Rake::ExtensionCompiler.mingw_host
  mingw_hosts.split(":").each do |mingw_host|

    # Use Rake::ExtensionCompiler helpers to find the proper host
    mingw_target = mingw_host.gsub('msvc', '')

    # define a location where built files for each host will be stored
    build_dir = "#{USER_HOME}/builds/#{mingw_host}/#{ruby_cc_version}"
    directory build_dir
    install_dir = "#{USER_HOME}/ruby/#{mingw_host}/#{ruby_cc_version}"

    # clean intermediate files and folders
    CLEAN.include(build_dir)
    CLOBBER.include(install_dir)

    task :mingw32 do
      unless mingw_host then
        warn "You need to install mingw32 cross compile functionality to be able to continue."
        warn "Please refer to your distribution/package manager documentation about installation."
        fail
      end
    end

    # generate the makefile in a clean build location
    file "#{build_dir}/Makefile" => [build_dir, source_dir] do |t|

      options = [
        "--host=#{mingw_host}",
        "--target=#{mingw_target}",
        "--build=#{RUBY_BUILD}",
        '--enable-shared',
        '--disable-install-doc',
        '--with-ext=',
      ]

      # Force Winsock2 for Ruby 1.8, 1.9 defaults to it
      options << "--with-winsock2" if major == "1.8"
      options << "--prefix=#{install_dir}"
      sh File.expand_path("#{USER_HOME}/sources/#{ruby_cc_version}/configure"), *options, chdir: File.dirname(t.name)
    end

    # make
    file "#{build_dir}/ruby.exe" => ["#{build_dir}/Makefile"] do |t|
      sh MAKE, chdir: File.dirname(t.prerequisites.first)
    end

    # make install
    file "#{USER_HOME}/ruby/#{mingw_host}/#{ruby_cc_version}/bin/ruby.exe" => ["#{build_dir}/ruby.exe"] do |t|
      sh "#{MAKE} install", chdir: File.dirname(t.prerequisites.first)
    end
    multitask :install => ["#{USER_HOME}/ruby/#{mingw_host}/#{ruby_cc_version}/bin/ruby.exe"]
  end
end

desc "Update rake-compiler list of installed Ruby versions"
task 'update-config' do
  config_file = "#{USER_HOME}/config.yml"
  if File.exist?(config_file) then
    puts "Updating #{config_file}"
    config = YAML.load_file(config_file)
  else
    puts "Generating #{config_file}"
    config = {}
  end

  files = Dir.glob("#{USER_HOME}/ruby/*/*/**/rbconfig.rb").sort

  files.each do |rbconfig|
    version, platform = rbconfig.match(/.*-(\d.\d.\d).*\/([-\w]+)\/rbconfig/)[1,2]
    platforms = [platform]

    # fake alternate (binary compatible) i386-mswin32-60 platform
    platform == "i386-mingw32" and
      platforms.push "i386-mswin32-60"

    platforms.each do |plat|
      config["rbconfig-#{plat}-#{version}"] = rbconfig

      # also store RubyGems-compatible version
      gem_platform = Gem::Platform.new(plat)
      config["rbconfig-#{gem_platform}-#{version}"] = rbconfig
    end

    puts "Found Ruby version #{version} for platform #{platform} (#{rbconfig})"
  end

  when_writing("Saving changes into #{config_file}") {
    File.open(config_file, 'w') do |f|
      f.puts config.to_yaml
    end
  }
end

task :default do
  # Force the display of the available tasks when no option is given
  Rake.application.options.show_task_pattern = //
  Rake.application.display_tasks_and_comments
end

desc "Build rubies suitable for cross-platform development."
task 'cross-ruby' => [:mingw32, :install, 'update-config']