|
| 1 | +require "rake" |
| 2 | +require_relative "./product" |
| 3 | + |
| 4 | +module RubyWasm |
| 5 | + class CrossRubyExtProduct < BuildProduct |
| 6 | + attr_reader :name, :toolchain |
| 7 | + def initialize(name, toolchain) |
| 8 | + @name, @toolchain = name, toolchain |
| 9 | + end |
| 10 | + |
| 11 | + def define_task(crossruby) |
| 12 | + task "#{crossruby.name}-ext-#{@name}" => [crossruby.configure] do |
| 13 | + make_args = [] |
| 14 | + make_args << "CC=#{toolchain.cc}" |
| 15 | + make_args << "RANLIB=#{toolchain.ranlib}" |
| 16 | + make_args << "LD=#{toolchain.ld}" |
| 17 | + make_args << "AR=#{toolchain.ar}" |
| 18 | + |
| 19 | + lib = @name |
| 20 | + source = crossruby.source |
| 21 | + objdir = "#{crossruby.ext_build_dir}/#{lib}" |
| 22 | + FileUtils.mkdir_p objdir |
| 23 | + srcdir = "#{crossruby.base_dir}/ext/#{lib}" |
| 24 | + extconf_args = [ |
| 25 | + "--disable=gems", |
| 26 | + # HACK: top_srcdir is required to find ruby headers |
| 27 | + "-e", |
| 28 | + %Q('$top_srcdir="#{source.src_dir}"'), |
| 29 | + # HACK: extout is required to find config.h |
| 30 | + "-e", |
| 31 | + %Q('$extout="#{crossruby.build_dir}/.ext"'), |
| 32 | + # HACK: force static ext build by imitating extmk |
| 33 | + "-e", |
| 34 | + "'$static = true; trace_var(:$static) {|v| $static = true }'", |
| 35 | + # HACK: $0 should be extconf.rb path due to mkmf source file detection |
| 36 | + # and we want to insert some hacks before it. But -e and $0 cannot be |
| 37 | + # used together, so we rewrite $0 in -e. |
| 38 | + "-e", |
| 39 | + %Q('$0="#{srcdir}/extconf.rb"'), |
| 40 | + "-e", |
| 41 | + %Q('require_relative "#{srcdir}/extconf.rb"'), |
| 42 | + "-I#{crossruby.build_dir}" |
| 43 | + ] |
| 44 | + sh "#{crossruby.baseruby_path} #{extconf_args.join(" ")}", chdir: objdir |
| 45 | + make_cmd = %Q(make -C "#{objdir}" #{make_args.join(" ")} static) |
| 46 | + sh make_cmd |
| 47 | + # A ext can provide link args by link.filelist. It contains only built archive file by default. |
| 48 | + unless File.exist?("#{objdir}/link.filelist") |
| 49 | + File.write( |
| 50 | + "#{objdir}/link.filelist", |
| 51 | + Dir.glob("#{objdir}/*.a").join("\n") |
| 52 | + ) |
| 53 | + end |
| 54 | + end |
| 55 | + end |
| 56 | + end |
| 57 | + |
| 58 | + class CrossRubyProduct < BuildProduct |
| 59 | + attr_reader :base_dir, :source, :toolchain, :build, :configure |
| 60 | + |
| 61 | + def initialize(params, base_dir, baseruby, source, toolchain) |
| 62 | + @params = params |
| 63 | + @base_dir = base_dir |
| 64 | + @baseruby = baseruby |
| 65 | + @source = source |
| 66 | + @toolchain = toolchain |
| 67 | + @dep_tasks = [] |
| 68 | + end |
| 69 | + |
| 70 | + def define_task |
| 71 | + directory dest_dir |
| 72 | + directory build_dir |
| 73 | + |
| 74 | + @configure = |
| 75 | + task "#{name}-configure", |
| 76 | + [:reconfigure] => |
| 77 | + [build_dir, source.src_dir, source.configure_file] + |
| 78 | + dep_tasks do |t, args| |
| 79 | + args.with_defaults(reconfigure: false) |
| 80 | + |
| 81 | + if !File.exist?("#{build_dir}/Makefile") || args[:reconfigure] |
| 82 | + args = configure_args(RbConfig::CONFIG["host"], toolchain) |
| 83 | + sh "#{source.configure_file} #{args.join(" ")}", chdir: build_dir |
| 84 | + end |
| 85 | + # NOTE: we need rbconfig.rb at configuration time to build user given extensions with mkmf |
| 86 | + sh "make rbconfig.rb", chdir: build_dir |
| 87 | + end |
| 88 | + |
| 89 | + user_ext_products = @params.user_exts |
| 90 | + user_ext_tasks = user_ext_products.map { |prod| prod.define_task(self) } |
| 91 | + user_ext_names = user_ext_products.map(&:name) |
| 92 | + user_exts = |
| 93 | + task "#{name}-libs" => [@configure] + user_ext_tasks do |
| 94 | + mkdir_p File.dirname(extinit_obj) |
| 95 | + sh %Q(ruby #{base_dir}/ext/extinit.c.erb #{user_ext_names.join(" ")} | #{toolchain.cc} -c -x c - -o #{extinit_obj}) |
| 96 | + end |
| 97 | + |
| 98 | + install = |
| 99 | + task "#{name}-install" => [@configure, user_exts, dest_dir] do |
| 100 | + next if File.exist?("#{dest_dir}-install") |
| 101 | + sh "make install DESTDIR=#{dest_dir}-install", chdir: build_dir |
| 102 | + end |
| 103 | + |
| 104 | + desc "Build #{name}" |
| 105 | + task name => [@configure, install, dest_dir] do |
| 106 | + artifact = "rubies/ruby-#{name}.tar.gz" |
| 107 | + next if File.exist?(artifact) |
| 108 | + rm_rf dest_dir |
| 109 | + cp_r "#{dest_dir}-install", dest_dir |
| 110 | + ruby_api_version = |
| 111 | + `#{baseruby_path} -e 'print RbConfig::CONFIG["ruby_version"]'` |
| 112 | + # TODO: move copying logic to ext product |
| 113 | + user_ext_names.each do |lib| |
| 114 | + next unless File.exist?("ext/#{lib}/lib") |
| 115 | + cp_r( |
| 116 | + File.join(base_dir, "ext/#{lib}/lib/."), |
| 117 | + File.join(dest_dir, "usr/local/lib/ruby/#{ruby_api_version}") |
| 118 | + ) |
| 119 | + end |
| 120 | + sh "tar cfz #{artifact} -C rubies #{name}" |
| 121 | + end |
| 122 | + end |
| 123 | + |
| 124 | + def name |
| 125 | + @params.name |
| 126 | + end |
| 127 | + |
| 128 | + def build_dir |
| 129 | + "#{@base_dir}/build/build/#{name}" |
| 130 | + end |
| 131 | + |
| 132 | + def ext_build_dir |
| 133 | + "#{@base_dir}/build/ext-build/#{name}" |
| 134 | + end |
| 135 | + |
| 136 | + def with_libyaml(libyaml) |
| 137 | + @libyaml = libyaml |
| 138 | + @dep_tasks << libyaml.install_task |
| 139 | + end |
| 140 | + |
| 141 | + def with_zlib(zlib) |
| 142 | + @zlib = zlib |
| 143 | + @dep_tasks << zlib.install_task |
| 144 | + end |
| 145 | + |
| 146 | + def dest_dir |
| 147 | + "#{@base_dir}/rubies/#{name}" |
| 148 | + end |
| 149 | + |
| 150 | + def extinit_obj |
| 151 | + "#{ext_build_dir}/extinit.o" |
| 152 | + end |
| 153 | + |
| 154 | + def baseruby_path |
| 155 | + File.join(@baseruby.install_dir, "bin/ruby") |
| 156 | + end |
| 157 | + |
| 158 | + def dep_tasks |
| 159 | + [@baseruby.install_task] + @dep_tasks |
| 160 | + end |
| 161 | + |
| 162 | + def configure_args(build_triple, toolchain) |
| 163 | + target = @params.target |
| 164 | + default_exts = @params.default_exts |
| 165 | + user_exts = @params.user_exts |
| 166 | + |
| 167 | + ldflags = |
| 168 | + if @params.debug |
| 169 | + # use --stack-first to detect stack overflow easily |
| 170 | + %w[-Xlinker --stack-first -Xlinker -z -Xlinker stack-size=16777216] |
| 171 | + else |
| 172 | + %w[-Xlinker -zstack-size=16777216] |
| 173 | + end |
| 174 | + |
| 175 | + xldflags = [] |
| 176 | + |
| 177 | + args = ["--host", target, "--build", build_triple] |
| 178 | + args << "--with-static-linked-ext" |
| 179 | + args << %Q(--with-ext="#{default_exts}") |
| 180 | + args << %Q(--with-libyaml-dir="#{@libyaml.install_root}") |
| 181 | + args << %Q(--with-zlib-dir="#{@zlib.install_root}") |
| 182 | + args << %Q(--with-baseruby="#{baseruby_path}") |
| 183 | + |
| 184 | + case target |
| 185 | + when "wasm32-unknown-wasi" |
| 186 | + unless toolchain.lib_wasi_vfs_a.nil? |
| 187 | + xldflags << toolchain.lib_wasi_vfs_a |
| 188 | + end |
| 189 | + when "wasm32-unknown-emscripten" |
| 190 | + ldflags.concat(%w[-s MODULARIZE=1]) |
| 191 | + args.concat(%w[CC=emcc LD=emcc AR=emar RANLIB=emranlib]) |
| 192 | + else |
| 193 | + raise "unknown target: #{target}" |
| 194 | + end |
| 195 | + |
| 196 | + (user_exts || []).each do |lib| |
| 197 | + xldflags << "@#{ext_build_dir}/#{lib.name}/link.filelist" |
| 198 | + end |
| 199 | + xldflags << extinit_obj |
| 200 | + |
| 201 | + xcflags = [] |
| 202 | + xcflags << "-DWASM_SETJMP_STACK_BUFFER_SIZE=24576" |
| 203 | + xcflags << "-DWASM_FIBER_STACK_BUFFER_SIZE=24576" |
| 204 | + xcflags << "-DWASM_SCAN_STACK_BUFFER_SIZE=24576" |
| 205 | + |
| 206 | + args << %Q(LDFLAGS="#{ldflags.join(" ")}") |
| 207 | + args << %Q(XLDFLAGS="#{xldflags.join(" ")}") |
| 208 | + args << %Q(XCFLAGS="#{xcflags.join(" ")}") |
| 209 | + if @params.debug |
| 210 | + args << %Q(debugflags="-g") |
| 211 | + args << %Q(wasmoptflags="-O3 -g") |
| 212 | + else |
| 213 | + args << %Q(debugflags="-g0") |
| 214 | + end |
| 215 | + args << "--disable-install-doc" |
| 216 | + args |
| 217 | + end |
| 218 | + end |
| 219 | +end |
0 commit comments