forked from oracle/truffleruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjt.rb
executable file
·2437 lines (2094 loc) · 79.8 KB
/
jt.rb
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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env ruby
# encoding: utf-8
# Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
# This code is released under a tri EPL/GPL/LGPL license. You can use it,
# redistribute it and/or modify it under the terms of the:
#
# Eclipse Public License version 1.0, or
# GNU General Public License version 2, or
# GNU Lesser General Public License version 2.1.
# A workflow tool for TruffleRuby development
# Recommended: function jt { ruby tool/jt.rb "$@"; }
require 'fileutils'
require 'json'
require 'timeout'
require 'yaml'
require 'rbconfig'
require 'pathname'
TRUFFLERUBY_DIR = File.expand_path('../..', File.realpath(__FILE__))
MRI_TEST_CEXT_DIR = "#{TRUFFLERUBY_DIR}/test/mri/tests/cext-c"
MRI_TEST_CEXT_LIB_DIR = "#{TRUFFLERUBY_DIR}/.ext/c"
PROFILES_DIR = "#{TRUFFLERUBY_DIR}/profiles"
TRUFFLERUBY_GEM_TEST_PACK_VERSION = "8b57f6022f0fa17ace7c8d2a3af730357715e0a2"
JDEBUG_PORT = 51819
JDEBUG = "--jvm.agentlib:jdwp=transport=dt_socket,server=y,address=#{JDEBUG_PORT},suspend=y"
JEXCEPTION = "--exceptions.print_uncaught_java=true"
METRICS_REPS = Integer(ENV["TRUFFLERUBY_METRICS_REPS"] || 10)
DEFAULT_PROFILE_OPTIONS = %w[--cpusampler --cpusampler.SampleInternal=true --cpusampler.Mode=roots --cpusampler.Output=json]
RUBOCOP_INCLUDE_LIST = %w[
lib/cext
lib/truffle
src/main/ruby
src/test/ruby
test/truffleruby-tool
tool/generate-sulongmock.rb
]
MAC = RbConfig::CONFIG['host_os'].include?('darwin')
LINUX = RbConfig::CONFIG['host_os'].include?('linux')
SO = MAC ? 'dylib' : 'so'
# Expand GEM_HOME relative to cwd so it cannot be misinterpreted later.
ENV['GEM_HOME'] = File.expand_path(ENV['GEM_HOME']) if ENV['GEM_HOME']
require "#{TRUFFLERUBY_DIR}/lib/truffle/truffle/openssl-prefix.rb"
MRI_TEST_MODULES = {
'--no-sulong' => {
help: 'exclude all tests requiring Sulong',
exclude: "#{TRUFFLERUBY_DIR}/test/mri/sulong.exclude"
},
'--openssl' => {
help: 'include only openssl tests',
include: openssl = ["#{TRUFFLERUBY_DIR}/test/mri/tests/openssl/test_*.rb"],
},
'--syslog' => {
help: 'include only syslog tests',
include: syslog = [
"#{TRUFFLERUBY_DIR}/test/mri/tests/test_syslog.rb",
"#{TRUFFLERUBY_DIR}/test/mri/tests/syslog/test_syslog_logger.rb"
]
},
'--cext' => {
help: 'include only tests requiring Sulong',
include: openssl + syslog + [
"#{TRUFFLERUBY_DIR}/test/mri/tests/cext-ruby/**/test_*.rb",
"#{TRUFFLERUBY_DIR}/test/mri/tests/etc/test_etc.rb",
]
}
}
SUBPROCESSES = []
# Forward signals to sub-processes, so they get notified when sending a signal to jt
[:SIGINT, :SIGTERM].each do |signal|
trap(signal) do
SUBPROCESSES.each do |pid|
puts "\nSending #{signal} to process #{pid}"
begin
Process.kill(signal, pid)
rescue Errno::ESRCH
# Already killed
end
end
# Keep running jt which will wait for the subprocesses termination
end
end
module Utilities
private
def truffle_version
suite = File.read("#{TRUFFLERUBY_DIR}/mx.truffleruby/suite.py")
raise unless /"name": "tools",.+?"version": "(\h{40})"/m =~ suite
$1
end
def jvmci_update_and_version
if env = ENV["JVMCI_VERSION"]
unless /8u(\d+)-(jvmci-0.\d+)/ =~ env
raise "Could not parse JDK update and JVMCI version from $JVMCI_VERSION"
end
else
ci = File.read("#{TRUFFLERUBY_DIR}/ci.jsonnet")
unless /JAVA_HOME: \{\n\s*name: "labsjdk",\n\s*version: "8u(\d+)-(jvmci-0.\d+)",/ =~ ci
raise "JVMCI version not found in ci.jsonnet: #{ci[0, 1000]}"
end
end
update, jvmci = $1, $2
[update, jvmci]
end
def find_graal_javacmd_and_options
graalvm = ENV['GRAALVM_BIN']
jvmci = ENV['JVMCI_BIN']
graal_home = ENV['GRAAL_HOME']
raise "More than one of GRAALVM_BIN, JVMCI_BIN or GRAAL_HOME defined!" if [graalvm, jvmci, graal_home].compact.count > 1
if graalvm
javacmd = File.expand_path(graalvm, TRUFFLERUBY_DIR)
vm_args = []
options = []
elsif jvmci
javacmd = File.expand_path(jvmci, TRUFFLERUBY_DIR)
jvmci_graal_home = ENV['JVMCI_GRAAL_HOME']
raise "Also set JVMCI_GRAAL_HOME if you set JVMCI_BIN" unless jvmci_graal_home
jvmci_graal_home = File.expand_path(jvmci_graal_home, TRUFFLERUBY_DIR)
vm_args = [
'-d64',
'-XX:+UnlockExperimentalVMOptions',
'-XX:+EnableJVMCI',
'--add-exports=java.base/jdk.internal.module=com.oracle.graal.graal_core',
"--module-path=#{jvmci_graal_home}/../truffle/mxbuild/modules/com.oracle.truffle.truffle_api.jar:#{jvmci_graal_home}/mxbuild/modules/com.oracle.graal.graal_core.jar"
]
options = ['--no-bootclasspath']
elsif graal_home
graal_home = File.expand_path(graal_home, TRUFFLERUBY_DIR)
output = mx('-v', '-p', graal_home, 'vm', '-version', capture: true, :err => :out)
command_line = output.lines.select { |line| line.include? '-version' }
if command_line.size == 1
command_line = command_line[0]
else
$stderr.puts "Error in mx for setting up Graal:"
$stderr.puts output
abort
end
vm_args = command_line.split
vm_args.pop # Drop "-version"
javacmd = vm_args.shift
options = []
elsif graal_home = find_auto_graal_home
javacmd = "#{find_graal_java_home(graal_home)}/bin/java"
graal_jars = [
"#{graal_home}/mxbuild/dists/jdk1.8/graal.jar",
"#{graal_home}/mxbuild/dists/jdk1.8/graal-management.jar"
]
vm_args = [
'-XX:+UnlockExperimentalVMOptions',
'-XX:+EnableJVMCI',
"-Djvmci.class.path.append=#{graal_jars.join(':')}",
# No -Xbootclasspath for sdk & Truffle, it's already added by the normal launcher
]
options = []
else
raise 'set one of GRAALVM_BIN or GRAAL_HOME in order to use Graal'
end
[javacmd, vm_args.map { |arg| "--jvm.#{arg[1..-1]}" } + options]
end
def find_auto_graal_home
sibling_compiler = File.expand_path('../graal/compiler', TRUFFLERUBY_DIR)
return nil unless Dir.exist?(sibling_compiler)
return nil unless File.exist?("#{sibling_compiler}/mxbuild/dists/jdk1.8/graal-compiler.jar")
sibling_compiler
end
def find_graal_java_home(graal_home)
env_file = "#{graal_home}/mx.compiler/env"
graal_env = File.exist?(env_file) ? File.read(env_file) : ""
if java_home = graal_env[/^JAVA_HOME=(.+)$/, 1]
java_home.gsub(/\$(\w+)/) { ENV.fetch($1) }
else
ENV.fetch("JAVA_HOME") { raise "Could not find JAVA_HOME of graal in #{env_file} or in ENV" }
end
end
def which(binary)
ENV["PATH"].split(File::PATH_SEPARATOR).each do |dir|
path = "#{dir}/#{binary}"
return path if File.executable? path
end
nil
end
def find_mx
if which('mx')
'mx'
else
mx_repo = find_or_clone_repo("https://github.com/graalvm/mx.git")
"#{mx_repo}/mx"
end
end
def find_launcher(use_native)
if use_native
ENV['AOT_BIN'] || "#{TRUFFLERUBY_DIR}/bin/native-ruby"
else
ENV['RUBY_BIN'] || "#{TRUFFLERUBY_DIR}/bin/truffleruby"
end
end
def find_repo(name)
[TRUFFLERUBY_DIR, "#{TRUFFLERUBY_DIR}/.."].each do |dir|
found = Dir.glob("#{dir}/#{name}*").sort.first
return File.expand_path(found) if found
end
raise "Can't find the #{name} repo - clone it into the repository directory or its parent"
end
def find_or_clone_repo(url, commit=nil)
name = File.basename url, '.git'
path = File.expand_path("../#{name}", TRUFFLERUBY_DIR)
unless Dir.exist? path
target = "../#{name}"
sh "git", "clone", url, target
sh "git", "checkout", commit, chdir: target if commit
end
path
end
def find_benchmark(benchmark)
if File.exist?(benchmark)
benchmark
else
File.join(TRUFFLERUBY_DIR, 'bench', benchmark)
end
end
def find_gem(name)
["#{TRUFFLERUBY_DIR}/lib/ruby/gems/shared/gems"].each do |dir|
found = Dir.glob("#{dir}/#{name}*").sort.first
return File.expand_path(found) if found
end
[TRUFFLERUBY_DIR, "#{TRUFFLERUBY_DIR}/.."].each do |dir|
found = Dir.glob("#{dir}/#{name}").sort.first
return File.expand_path(found) if found
end
raise "Can't find the #{name} gem - gem install it in this repository, or put it in the repository directory or its parent"
end
def git_branch
@git_branch ||= `GIT_DIR="#{TRUFFLERUBY_DIR}/.git" git rev-parse --abbrev-ref HEAD`.strip
end
def igv_running?
`ps ax`.include?('idealgraphvisualizer')
end
def no_gem_vars_env
{
'TRUFFLERUBY_RESILIENT_GEM_HOME' => nil,
'GEM_HOME' => nil,
'GEM_PATH' => nil,
'GEM_ROOT' => nil,
}
end
def human_size(bytes)
if bytes < 1024
"#{bytes} B"
elsif bytes < 1000**2
"#{(bytes/1024.0).round(2)} KB"
elsif bytes < 1000**3
"#{(bytes/1024.0**2).round(2)} MB"
elsif bytes < 1000**4
"#{(bytes/1024.0**3).round(2)} GB"
else
"#{(bytes/1024.0**4).round(2)} TB"
end
end
def log(tty_message, full_message)
if STDERR.tty?
STDERR.print tty_message unless tty_message.nil?
else
STDERR.print full_message unless full_message.nil?
end
end
def diff(expected, actual)
`diff -u #{expected} #{actual}`
end
def raw_sh_failed_status
`false`
$?
end
def raw_sh_with_timeout(timeout, pid)
if !timeout
yield
else
begin
Timeout.timeout(timeout) do
yield
end
rescue Timeout::Error
Process.kill('TERM', pid)
yield # Wait and read the pipe if capture: true
:timeout
end
end
end
def raw_sh_track_subprocess(pid)
SUBPROCESSES << pid
yield
ensure
SUBPROCESSES.delete(pid)
end
def raw_sh(*args)
options = args.last.is_a?(Hash) ? args.last : {}
continue_on_failure = options.delete :continue_on_failure
use_exec = options.delete :use_exec
timeout = options.delete :timeout
capture = options.delete :capture
unless options.delete :no_print_cmd
STDERR.puts "$ #{printable_cmd(args)}"
end
exec(*args) if use_exec
if capture
raise ":capture can only be combined with :err => :out" if options.include?(:out)
pipe_r, pipe_w = IO.pipe
options[:out] = pipe_w
options[:err] = pipe_w if options[:err] == :out
end
status = nil
out = nil
begin
pid = Process.spawn(*args)
rescue Errno::ENOENT # No such executable
status = raw_sh_failed_status
else
raw_sh_track_subprocess(pid) do
pipe_w.close if capture
result = raw_sh_with_timeout(timeout, pid) do
out = pipe_r.read if capture
_, status = Process.waitpid2(pid)
end
if result == :timeout
status = raw_sh_failed_status
end
end
end
result = status.success?
if capture
pipe_r.close
end
if status.success? || continue_on_failure
if capture
out
else
status.success?
end
else
$stderr.puts "FAILED (#{status}): #{printable_cmd(args)}"
$stderr.puts out if capture
exit(status.exitstatus || status.termsig || status.stopsig || 1)
end
end
def printable_cmd(args)
env = {}
if Hash === args.first
env, *args = args
end
if Hash === args.last && args.last.empty?
*args, options = args
end
env = env.map { |k,v| "#{k}=#{shellescape(v)}" }.join(' ')
args = args.map { |a| shellescape(a) }.join(' ')
env.empty? ? args : "#{env} #{args}"
end
def shellescape(str)
return str unless str.is_a?(String)
if str.include?(' ')
if str.include?("'")
require 'shellwords'
Shellwords.escape(str)
else
"'#{str}'"
end
else
str
end
end
def replace_env_vars(string, env = ENV)
string.gsub(/\$([A-Z_]+)/) {
var = $1
abort "You need to set $#{var}" unless env[var]
env[var]
}
end
def sh(*args)
chdir(TRUFFLERUBY_DIR) do
raw_sh(*args)
end
end
def app_open(file)
cmd = MAC ? 'open' : 'xdg-open'
sh cmd, file
end
def chdir(dir, &block)
raise LocalJumpError, "no block given" unless block_given?
if dir == Dir.pwd
yield
else
STDERR.puts "$ cd #{dir}"
ret = Dir.chdir(dir, &block)
STDERR.puts "$ cd #{Dir.pwd}"
ret
end
end
def mx(*args, java_home: nil, **kwargs)
mx_args = args
mx_args.unshift '--java-home', java_home if java_home
raw_sh find_mx, *mx_args, **kwargs
end
def mx_os
if MAC
'darwin'
elsif LINUX
'linux'
else
abort "Unknown OS"
end
end
def mspec(command, *args)
env_vars = {}
if command.is_a?(Hash)
env_vars = command
command, *args = args
end
mspec_args = ['spec/mspec/bin/mspec', command, '--config', 'spec/truffle.mspec', *args]
if i = args.index('-t')
launcher = args[i+1]
flags = args.select { |arg| arg.start_with?('-T') }.map { |arg| arg[2..-1] }
sh env_vars, launcher, *flags, *mspec_args, use_exec: true
else
ruby env_vars, *mspec_args
end
end
def newer?(input, output)
return true unless File.exist? output
File.mtime(input) > File.mtime(output)
end
end
module Commands
include Utilities
def help
puts <<-TXT.gsub(/^#{' '*6}/, '')
jt build [options] build
parser build the parser
options build the options
cexts build only the C extensions (part of "jt build")
native [--no-sulong] [--no-jvmci] [--no-sforceimports] [--no-tools] [extra mx image options]
build a native image of TruffleRuby (--no-jvmci to use the system Java)
(--no-tools to exclude chromeinspector and profiler)
jt build_stats [--json] <attribute> prints attribute's value from build process (e.g., binary size)
jt clean clean
jt env prints the current environment
jt rebuild clean, sforceimports, and build
jt dis <file> finds the bc file in the project, disassembles, and returns new filename
jt ruby [jt options] [--] [ruby options] args...
run TruffleRuby with args
--graal use Graal (set either GRAALVM_BIN, JVMCI_BIN or GRAAL_HOME, or have graal built as a sibling)
--stress stress the compiler (compile immediately, foreground compilation, compilation exceptions are fatal)
--asm show assembly (implies --graal)
--server run an instrumentation server on port 8080
--igv make sure IGV is running and dump Graal graphs after partial escape (implies --graal)
--igv-full show all phases, not just up to the Truffle partial escape
--infopoints show source location for each node in IGV
--fg disable background compilation
--trace show compilation information on stdout
--jdebug run a JDWP debug server on #{JDEBUG_PORT}
--jexception[s] print java exceptions
--exec use exec rather than system
--no-print-cmd don\'t print the command
jt gem shortcut for `jt ruby -S gem`, to install Ruby gems, etc
jt e 14 + 2 evaluate an expression
jt puts 14 + 2 evaluate and print an expression
jt cextc directory clang-args compile the C extension in directory, with optional extra clang arguments
jt test run all mri tests, specs and integration tests
jt test mri run mri tests
#{MRI_TEST_MODULES.map { |k, h| format ' '*10+'%-16s%s', k, h[:help] }.join("\n")}
--native use native TruffleRuby image (set AOT_BIN)
--graal use Graal (set either GRAALVM_BIN, JVMCI_BIN or GRAAL_HOME, or have graal built as a sibling)
jt test mri test/mri/tests/test_find.rb [-- <MRI runner options>]
run tests in given file, -n option of the runner can be used to further
limit executed test methods
jt test specs run all specs
jt test specs fast run all specs except sub-processes, GC, sleep, ...
jt test spec/ruby/language run specs in this directory
jt test spec/ruby/language/while_spec.rb run specs in this file
jt test compiler run compiler tests (uses the same logic as --graal to find Graal)
jt test integration runs all integration tests
jt test integration [TESTS] runs the given integration tests
jt test bundle [--jdebug] tests using bundler
jt test gems tests using gems
jt test ecosystem [TESTS] tests using the wider ecosystem such as bundler, Rails, etc
jt test cexts [--no-openssl] [--no-gems] [test_names...]
run C extension tests (set GEM_HOME)
jt test report :language build a report on language specs
:core (results go into test/target/mspec-html-report)
:library
jt gem-test-pack check that the gem test pack is downloaded, or download it for you, and print the path
jt rubocop [rubocop options] run rubocop rules (using ruby available in the environment)
jt tag spec/ruby/language tag failing specs in this directory
jt tag spec/ruby/language/while_spec.rb tag failing specs in this file
jt tag all spec/ruby/language tag all specs in this file, without running them
jt untag spec/ruby/language untag passing specs in this directory
jt untag spec/ruby/language/while_spec.rb untag passing specs in this file
jt mspec ... run MSpec with the TruffleRuby configuration and custom arguments
jt metrics alloc [--json] ... how much memory is allocated running a program
jt metrics instructions ... how many CPU instructions are used to run a program
jt metrics minheap ... what is the smallest heap you can use to run an application
jt metrics time ... how long does it take to run a command, broken down into different phases
jt benchmark [options] args... run benchmark-interface (implies --graal)
--no-graal don't imply --graal
JT_BENCHMARK_RUBY=ruby benchmark some other Ruby, like MRI
note that to run most MRI benchmarks, you should translate them first with normal Ruby and cache the result, such as
benchmark bench/mri/bm_vm1_not.rb --cache
jt benchmark bench/mri/bm_vm1_not.rb --use-cache
jt profile profiles an application, including the TruffleRuby runtime, and generates a flamegraph
jt where repos ... find these repositories
jt next tell you what to work on next (give you a random core library spec)
jt pr [pr_number] pushes GitHub's PR to bitbucket to let CI run under github/pr/<number> name
if the pr_number is not supplied current HEAD is used to find a PR which contains it
jt pr clean [--dry-run] delete all github/pr/<number> branches from BB whose GitHub PRs are closed
jt install jvmci install a JVMCI JDK in the parent directory
jt install graal [--no-jvmci] install Graal in the parent directory (--no-jvmci to use the system Java)
jt docker build a Docker image - see doc/contributor/docker.md
you can also put --build or --rebuild in front of any command to build or rebuild first
recognised environment variables:
RUBY_BIN The TruffleRuby executable to use (normally just bin/truffleruby)
GRAALVM_BIN GraalVM executable (java command)
GRAAL_HOME Directory where there is a built checkout of the Graal compiler (make sure mx is on your path)
JVMCI_BIN JVMCI-enabled java command (also set JVMCI_GRAAL_HOME)
JVMCI_GRAAL_HOME Like GRAAL_HOME, but only used for the JARs to run with JVMCI_BIN
OPENSSL_PREFIX Where to find OpenSSL headers and libraries
AOT_BIN TruffleRuby/SVM executable
TXT
end
def mx(*args)
super(*args)
end
def build(*options)
project = options.shift
case project
when 'parser'
jay = "#{TRUFFLERUBY_DIR}/tool/jay"
raw_sh 'make', chdir: jay
ENV['PATH'] = "#{jay}:#{ENV['PATH']}"
sh 'bash', 'tool/generate_parser'
yytables = 'src/main/java/org/truffleruby/parser/parser/YyTables.java'
File.write(yytables, File.read(yytables).gsub('package org.jruby.parser;', 'package org.truffleruby.parser.parser;'))
when 'options'
sh 'tool/generate-options.rb'
when "cexts" # Included in 'mx build' but useful to recompile just that part
require 'etc'
cores = Etc.respond_to?(:nprocessors) ? Etc.nprocessors : 4
raw_sh "make", "-j#{cores}", chdir: "#{TRUFFLERUBY_DIR}/src/main/c"
when 'native'
build_native_image *options
when nil
build_truffleruby
else
raise ArgumentError, project
end
end
def build_truffleruby(*options, sforceimports: true)
mx 'sforceimports' if sforceimports
mx 'build_truffleruby', *options
end
def clean(*options)
project = options.shift
case project
when 'cexts'
raw_sh "make", "clean", chdir: "#{TRUFFLERUBY_DIR}/src/main/c"
when nil
mx 'clean'
sh 'rm', '-rf', 'mxbuild'
sh 'rm', '-rf', 'spec/ruby/ext'
else
raise ArgumentError, project
end
end
def dis(file)
dis = `which llvm-dis-3.8 llvm-dis 2>/dev/null`.lines.first.chomp
file = `find #{TRUFFLERUBY_DIR} -name "#{file}"`.lines.first.chomp
raise ArgumentError, "file not found:`#{file}`" if file.empty?
sh dis, file
puts Pathname(file).sub_ext('.ll')
end
def env
puts "Environment"
env_vars = %w[JAVA_HOME PATH RUBY_BIN GRAALVM_BIN
GRAAL_HOME TRUFFLERUBY_RESILIENT_GEM_HOME
JVMCI_BIN JVMCI_GRAAL_HOME OPENSSL_PREFIX
AOT_BIN TRUFFLERUBYOPT RUBYOPT]
column_size = env_vars.map(&:size).max
env_vars.each do |e|
puts format "%#{column_size}s: %s", e, ENV[e].inspect
end
shell = -> command { raw_sh(*command.split, continue_on_failure: true) }
shell['ruby -v']
shell['uname -a']
shell['cc -v']
shell['gcc -v']
shell['clang -v']
shell['opt -version']
shell['/usr/local/opt/llvm@4/bin/clang -v']
shell['/usr/local/opt/llvm@4/bin/opt -version']
shell['mx version']
sh('mx', 'sversions', continue_on_failure: true)
shell['git --no-pager show -s --format=%H']
if ENV['OPENSSL_PREFIX']
shell["#{ENV['OPENSSL_PREFIX']}/bin/openssl version"]
else
shell["openssl version"]
end
shell['java -version']
end
def rebuild(*options)
clean(*options)
build(*options)
end
def run_ruby(*args)
env_vars = args.first.is_a?(Hash) ? args.shift : {}
options = args.last.is_a?(Hash) ? args.pop : {}
raise ArgumentError, args.inspect + ' has non-String values' if args.any? { |v| not v.is_a? String }
native = false
graal = false
ruby_args = []
vm_args = [core_load_path = "--core.load_path=#{TRUFFLERUBY_DIR}/src/main/ruby"]
while (arg = args.shift)
case arg
when '--native'
native = true
when '--no-core-load-path'
vm_args.delete core_load_path
when '--graal'
graal = true
when '--stress'
graal = true
vm_args << '--jvm.Dgraal.TruffleCompileImmediately=true'
vm_args << '--jvm.Dgraal.TruffleBackgroundCompilation=false'
vm_args << '--jvm.Dgraal.TruffleCompilationExceptionsAreFatal=true'
when '--asm'
graal = true
vm_args += %w[--jvm.XX:+UnlockDiagnosticVMOptions --jvm.XX:CompileCommand=print,*::callRoot]
when '--jdebug'
vm_args << JDEBUG
when '--jexception', '--jexceptions'
vm_args << JEXCEPTION
when '--server'
vm_args += %w[--instrumentation_server_port=8080]
when '--infopoints'
vm_args << "--jvm.XX:+UnlockDiagnosticVMOptions" << "--jvm.XX:+DebugNonSafepoints"
vm_args << "--jvm.Dgraal.TruffleEnableInfopoints=true"
when '--fg'
vm_args << "--jvm.Dgraal.TruffleBackgroundCompilation=false"
when '--trace'
graal = true
vm_args << "--jvm.Dgraal.TraceTruffleCompilation=true"
when '--igv', '--igv-full'
graal = true
vm_args << (arg == '--igv-full' ? "--jvm.Dgraal.Dump=:2" : "--jvm.Dgraal.Dump=TruffleTree,PartialEscape:2")
vm_args << "--jvm.Dgraal.PrintGraphFile=true" unless igv_running?
vm_args << "--jvm.Dgraal.PrintBackendCFG=false"
when '--no-print-cmd'
options[:no_print_cmd] = true
when '--exec'
options[:use_exec] = true
when '--'
# marks rest of the options as Ruby arguments, stop parsing jt options
break
else
ruby_args.push arg
end
end
ruby_args += args
if graal
if ENV["RUBY_BIN"] || native
# Assume that Graal is automatically set up if RUBY_BIN is set or using a native image.
else
javacmd, javacmd_options = find_graal_javacmd_and_options
env_vars["JAVACMD"] = javacmd
vm_args.push(*javacmd_options)
end
end
ruby_bin = find_launcher(native)
raw_sh env_vars, ruby_bin, *vm_args, *ruby_args, options
end
private :run_ruby
def ruby(*args)
env = args.first.is_a?(Hash) ? args.shift : {}
run_ruby(env, '--exec', *args)
end
# Legacy alias
alias_method :run, :ruby
def e(*args)
ruby '-e', args.join(' ')
end
def command_puts(*args)
e 'puts begin', *args, 'end'
end
def command_p(*args)
e 'p begin', *args, 'end'
end
# Just convenience
def gem(*args)
ruby '-S', 'gem', *args
end
def cextc(cext_dir, *clang_opts)
cext_dir = File.expand_path(cext_dir)
name = File.basename(cext_dir)
ext_dir = "#{cext_dir}/ext/#{name}"
target = "#{cext_dir}/lib/#{name}/#{name}.su"
compile_cext(name, ext_dir, target, *clang_opts)
end
def compile_cext(name, ext_dir, target, *clang_opts)
extconf = "#{ext_dir}/extconf.rb"
raise "#{extconf} does not exist" unless File.exist?(extconf)
# Make sure ruby.su is built
build("cexts")
chdir(ext_dir) do
run_ruby('-rmkmf', "#{ext_dir}/extconf.rb") # -rmkmf is required for C ext tests
if File.exists?('Makefile')
raw_sh("make")
FileUtils::Verbose.cp("#{name}.su", target) if target
else
STDERR.puts "Makefile not found in #{ext_dir}, skipping make."
end
end
end
module PR
include Utilities
extend self
def pr_clean(*args)
require 'net/http'
dry_run = args.delete '--dry-run'
uri = URI('https://api.github.com/repos/oracle/truffleruby/pulls')
puts "Contacting GitHub: #{uri}"
data = Net::HTTP.get(uri)
prs_data = JSON.parse data
open_prs = prs_data.map { |prd| Integer(prd.fetch('number')) }
puts "Open PRs: #{open_prs}"
sh 'git', 'fetch', Remotes.bitbucket, '--prune' # ensure we have locally only existing remote branches
branches = sh 'git', 'branch', '--remote', '--list', capture: true
branches_to_delete = branches.
scan(/^ *#{Remotes.bitbucket}\/(github\/pr\/(\d+))$/).
reject { |_, number| open_prs.include? Integer(number) }
puts "Deleting #{branches_to_delete.size} remote branches on #{Remotes.bitbucket}:"
puts branches_to_delete.map(&:last).map(&:to_i).to_s
return if dry_run
branches_to_delete.each do |remote_branch, _|
sh 'git', 'push', '--no-verify', Remotes.bitbucket, ":#{remote_branch}"
end
# update remote branches
sh 'git', 'fetch', Remotes.bitbucket, '--prune'
end
def pr_push(*args)
# Fetch PRs on GitHub
fetch = "+refs/pull/*/head:refs/remotes/#{Remotes.github}/pr/*"
out = sh 'git', 'config', '--get-all', "remote.#{Remotes.github}.fetch", capture: true
sh 'git', 'config', '--add', "remote.#{Remotes.github}.fetch", fetch unless out.include? fetch
sh 'git', 'fetch', Remotes.github
pr_number = args.first
if pr_number
github_pr_branch = "#{Remotes.github}/pr/#{pr_number}"
else
github_pr_branch = begin
out = sh 'git', 'branch', '-r', '--contains', 'HEAD', capture: true
candidate = out.lines.find { |l| l.strip.start_with? "#{Remotes.github}/pr/" }
candidate && candidate.strip.chomp
end
unless github_pr_branch
puts 'Could not find HEAD in any of the GitHub pull-requests.'
exit 1
end
pr_number = github_pr_branch.split('/').last
end
target_branch = if git_branch.start_with?('release')
git_branch
else
"github/pr/#{pr_number}"
end
sh 'git', 'push', '--force', '--no-verify', Remotes.bitbucket, "#{github_pr_branch}:refs/heads/#{target_branch}"
end
def pr_update_master(skip_upstream_fetch: false)
sh 'git', 'fetch', Remotes.github unless skip_upstream_fetch
sh 'git', 'push', '--no-verify', Remotes.bitbucket, "#{Remotes.github}/master:master"
end
end
module Remotes
include Utilities
extend self
def bitbucket(dir = TRUFFLERUBY_DIR)
candidate = remote_urls(dir).find { |r, u| u.include? 'ol-bitbucket' }
candidate.first if candidate
end
def github(dir = TRUFFLERUBY_DIR)
candidate = remote_urls(dir).find { |r, u| u.match %r(github.com[:/]oracle) }
candidate.first if candidate
end
def remote_urls(dir = TRUFFLERUBY_DIR)
@remote_urls ||= Hash.new
@remote_urls[dir] ||= begin
out = raw_sh 'git', '-C', dir, 'remote', capture: true, no_print_cmd: true
out.split.map do |remote|
url = raw_sh 'git', '-C', dir, 'config', '--get', "remote.#{remote}.url", capture: true, no_print_cmd: true
[remote, url.chomp]
end
end
end
def url(remote_name, dir = TRUFFLERUBY_DIR)
remote_urls(dir).find { |r, u| r == remote_name }.last
end
def try_fetch(repo)
remote = github(repo) || bitbucket(repo) || 'origin'
raw_sh "git", "-C", repo, "fetch", remote, continue_on_failure: true
end
end
def pr(*args)
command, *options = args
case command
when 'clean'
PR.pr_clean *options
when 'up'
PR.pr_update_master *options
else
PR.pr_push *args
# To regularly update bb/master
PR.pr_update_master skip_upstream_fetch: true
end
end
def test(*args)
path, *rest = args
case path
when nil
ENV['HAS_REDIS'] = 'true'
%w[specs mri bundle cexts integration gems ecosystem compiler].each do |kind|
jt('test', kind)
end
when 'bundle' then test_bundle(*rest)
when 'compiler' then test_compiler(*rest)
when 'cexts' then test_cexts(*rest)
when 'report' then test_report(*rest)
when 'integration' then test_integration(*rest)
when 'gems' then test_gems(*rest)
when 'ecosystem' then test_ecosystem(*rest)
when 'specs' then test_specs('run', *rest)
when 'mri' then test_mri(*rest)
else
if File.expand_path(path, TRUFFLERUBY_DIR).start_with?("#{TRUFFLERUBY_DIR}/test")
test_mri(*args)
else
test_specs('run', *args)
end
end
end
def jt(*args)
sh RbConfig.ruby, 'tool/jt.rb', *args
end
private :jt
def test_mri(*args)
double_dash_index = args.index '--'
if double_dash_index
args, runner_args = args[0...double_dash_index], args[(double_dash_index+1)..-1]
else
runner_args = []
end
mri_args = []
prefix = "#{TRUFFLERUBY_DIR}/test/mri/tests/"
exclusions = ["#{TRUFFLERUBY_DIR}/test/mri/failing.exclude"]
patterns = []
args.each do |arg|
test_module = MRI_TEST_MODULES[arg]
if test_module
patterns.push(*test_module[:include])
exclusions.push(*test_module[:exclude])
elsif arg.start_with?('-')
mri_args.push arg
else
patterns.push arg
end
end
patterns.push "#{TRUFFLERUBY_DIR}/test/mri/tests/**/test_*.rb" if patterns.empty?
excluded_files = exclusions.
flat_map { |f| File.readlines(f) }.
map { |l| l.gsub(/#.*/, '').strip }.
reject(&:empty?)
files_to_run = patterns.flat_map do |pattern|
Dir.glob(pattern).map do |path|
expanded_path = File.expand_path path
raise 'pattern does not match test files' unless expanded_path.start_with?(prefix)
expanded_path[prefix.size..-1]
end.reject do |path|