diff options
| author | Eugen Wissner <belka@caraus.de> | 2026-10-07 02:25:34 +0200 |
|---|---|---|
| committer | Eugen Wissner <belka@caraus.de> | 2026-10-07 02:25:34 +0200 |
| commit | f9dfefc52fc792099b622ae47dafa32d2512e51e (patch) | |
| tree | 3be1fdc6d29bf04dc460684794ebfc566f7a2662 /rakelib/gcc.rake | |
| parent | e38f23a1bd6e0ee093655e1f241e524795f21fec (diff) | |
| download | elna-f9dfefc52fc792099b622ae47dafa32d2512e51e.tar.gz | |
Add some useful gems for ruby code
Diffstat (limited to 'rakelib/gcc.rake')
| -rw-r--r-- | rakelib/gcc.rake | 215 |
1 files changed, 104 insertions, 111 deletions
diff --git a/rakelib/gcc.rake b/rakelib/gcc.rake index 7ee9f5c..6dce877 100644 --- a/rakelib/gcc.rake +++ b/rakelib/gcc.rake @@ -5,8 +5,18 @@ require 'fileutils' require 'uri' -require 'net/http' -require 'open3' +require 'faraday' +require 'faraday/follow_redirects' +require 'English' +require_relative 'support/ui' + +# Dependencies. +GCC_VERSION = '16.2.0' +GCC_HOST = 'build/host/gcc' +GCC_HOST_INSTALL = Pathname.new 'build/host/install' +GCC_TREE = Pathname.new "build/tools/gcc-#{GCC_VERSION}" +GCC_PATCH = + "https://raw.githubusercontent.com/Homebrew/homebrew-core/refs/heads/main/Patches/gcc/gcc-#{GCC_VERSION}.diff".freeze # Use Homebrew GCC if available (it uses libstdc++, avoiding # macOS-specific libc++ incompatibilities in GCC's own sources). @@ -34,59 +44,50 @@ end def gcc_verbose(gcc_binary) read, write = IO.pipe - sh({'LC_ALL' => 'C'}, gcc_binary, '--verbose', err: write) + sh({ 'LC_ALL' => 'C' }, gcc_binary, '--verbose', err: write) write.close output = read.read read.close output end -def find_build_target(gcc_version) - if RUBY_PLATFORM =~ /darwin/ - suffix = '-' + gcc_version.split('.').first - mac_os_sdk = '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' - else - suffix = '' - end - build_target = gcc_verbose(ENV.fetch 'CC', "gcc#{suffix}") +def target_triple(cc_name) + gcc_verbose(cc_name) .lines .find { |line| line.start_with? 'Target: ' } - .split(' ') + .split .last .strip +end + +def find_build_target(gcc_version) + is_darwin = RUBY_PLATFORM =~ /darwin/ + suffix = is_darwin ? "-#{gcc_version.split('.').first}" : '' + mac_os_sdk = '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk' if is_darwin + build_target = target_triple(ENV.fetch('CC', "gcc#{suffix}")) GCCConfiguration.new suffix, build_target, mac_os_sdk end +def pipe_to_command(command, chdir:, &) + IO.popen(command, 'w', chdir:, &) + UI.fatal "Command #{command.join(' ')} failed" unless $CHILD_STATUS.success? +end + +def stream_http(url, &) + conn = Faraday.new do |f| + f.response :follow_redirects + f.response :raise_error + end + conn.get(url) do |req| + req.options.on_data = ->(chunk, _, env) { yield chunk if (200..299).cover?(env.status) && !chunk.empty? } + end +end + def download_and_pipe(url, target, command) target.mkpath - - Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https') do |http| - request = Net::HTTP::Get.new url.request_uri - - http.request request do |response| - case response - when Net::HTTPRedirection - download_and_pipe URI.parse(response['location']), target, command - when Net::HTTPSuccess - Dir.chdir target.to_path do - Open3.popen2(*command) do |stdin, stdout, wait_thread| - Thread.new do - stdout.each { |line| puts line } - end - - response.read_body do |chunk| - stdin.write chunk - end - stdin.close - - wait_thread.value - end - end - else - response.error! - end - end + pipe_to_command(command, chdir: target.to_path) do |stdin| + stream_http(url) { |chunk| stdin.write chunk } end end @@ -104,60 +105,69 @@ def convert_directive(line, directive, dg_name) end end +def test_trailer_lines(category, extra_lines = []) + lines = [] + lines << '(* { dg-do run } *)' if category == 'runnable' + lines.concat extra_lines + lines << '(* { dg-prune-output .* } *)' + case category + when 'compilable' then lines << '(* { dg-final { output-exists } } *)' + when 'fail_compilation' then lines << '(* { dg-final { output-exists-not } } *)' + end + lines +end + def convert_test(source, target, category, extra_lines = []) File.open(target, 'wb') do |output| File.foreach(source) do |line| line = convert_directive(line, '@Error', 'dg-error') - line = convert_directive(line, '@Flags', 'dg-additional-options') - - output.puts line + output.puts convert_directive(line, '@Flags', 'dg-additional-options') end - # Runnable tests compile, link and execute. - output.puts '(* { dg-do run } *)' if category == 'runnable' + test_trailer_lines(category, extra_lines).each { |line| output.puts line } + end +end + +def multi_module_extra_sources(test_target) + sources = test_target.glob('**/*.elna') + .map { |extra| %("#{extra.relative_path_from(test_target)}") } + "(* { dg-additional-sources #{sources * ' '} } *)" +end - extra_lines.each { |line| output.puts line } +def convert_multi_module_test(test_path, test_target, category) + main = test_path / 'sut.elna' + UI.fatal "Multi-module test directory \"#{test_path}\" must contain sut.elna." unless main.file? - # Suppress excess output so stray diagnostics do not fail the test. - output.puts '(* { dg-prune-output .* } *)' + test_target.mkdir + (test_path.children - [main]).each { |extra| cp_r extra, test_target } + extra_lines = ["(* { dg-additional-options \"-I#{test_target.expand_path}\" } *)"] + extra_lines << multi_module_extra_sources(test_target) if category == 'runnable' - case category - when 'compilable' - # Assert that an output file was produced. - output.puts '(* { dg-final { output-exists } } *)' - when 'fail_compilation' - # Assert that no output file was produced. - output.puts '(* { dg-final { output-exists-not } } *)' - end - end + convert_test main, test_target / 'sut.elna', category, extra_lines end namespace :gcc do - # Dependencies. - GCC_VERSION = "16.2.0" - HOST_GCC = 'build/host/gcc' - HOST_INSTALL = Pathname.new 'build/host/install' - GCC_TREE = Pathname.new "build/tools/gcc-#{GCC_VERSION}" - GCC_PATCH = - "https://raw.githubusercontent.com/Homebrew/homebrew-core/refs/heads/main/Patches/gcc/gcc-#{GCC_VERSION}.diff" - - directory HOST_GCC - directory HOST_INSTALL.to_path + directory GCC_HOST + directory GCC_HOST_INSTALL.to_path directory 'build/tools' desc 'Download the bootstrap compiler and its prerequisites' task download: 'build/tools' do + UI.step "Downloading bootstrap compiler and prerequisites (GCC #{GCC_VERSION})..." url = URI.parse "https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz" + UI.debug "Downloading #{url}..." download_and_pipe url, GCC_TREE.dirname, ['tar', '-Jxv'] + UI.debug "Downloading and applying patch #{GCC_PATCH}..." download_and_pipe URI.parse(GCC_PATCH), GCC_TREE, ['patch', '-p1'] # GCC 16.1.0 registers 17 languages but CL_PARAMS is at bit 16, # which collides with the 17th language class (CL_Rust). Shift # every CL_* constant from bit 16 upward by one position. - File.open(GCC_TREE + 'gcc/opts.h', 'r+') do |opts_h| - content = opts_h.read.gsub(/\(1U << (\d+)\)/) do |m| - "(1U << #{$1.to_i + 1})" + UI.debug 'Patching gcc/opts.h to shift CL_* flags...' + File.open(GCC_TREE / 'gcc/opts.h', 'r+') do |opts_h| + content = opts_h.read.gsub(/\(1U << (\d+)\)/) do + "(1U << #{Regexp.last_match(1).to_i + 1})" end opts_h.seek 0, IO::SEEK_SET opts_h.write content @@ -168,8 +178,9 @@ namespace :gcc do desc 'Link the frontend into the GCC source tree' task :link do - source_destination = GCC_TREE + 'gcc/elna' - test_destination = GCC_TREE + 'gcc/testsuite/elna.dg' + UI.step 'Linking Elna frontend into the GCC source tree...' + source_destination = GCC_TREE / 'gcc/elna' + test_destination = GCC_TREE / 'gcc/testsuite/elna.dg' rm_rf [source_destination, test_destination] mkdir_p [source_destination, test_destination] @@ -180,16 +191,17 @@ namespace :gcc do FileList['gcc/dg.exp', 'README.md'].each do |file| link_frontend file, test_destination end - destination = GCC_TREE + 'gcc/testsuite/lib' + destination = GCC_TREE / 'gcc/testsuite/lib' FileList['gcc/testlib/*'].each do |file| - rm_f (destination + File.basename(file)) + rm_f destination / File.basename(file) link_frontend file, destination end end desc 'Configure the bootstrap compiler' - task configure: [HOST_GCC, HOST_INSTALL.to_path] do |t| + task configure: [GCC_HOST, GCC_HOST_INSTALL.to_path] do |t| build_target = find_build_target GCC_VERSION + UI.step "Configuring bootstrap compiler for #{build_target.target}..." configure_options = [ "--prefix=#{File.realpath t.prerequisites.last}", '--enable-languages=c,c++,jit,elna', @@ -208,25 +220,28 @@ namespace :gcc do } env['CFLAGS'] = env['CXXFLAGS'] = '-O0 -g -fPIC -I/opt/homebrew/opt/flex/include' - configure = GCC_TREE.relative_path_from(HOST_GCC) + 'configure' - sh env, configure.to_path, *configure_options, chdir: HOST_GCC + configure = GCC_TREE.relative_path_from(GCC_HOST) / 'configure' + sh env, configure.to_path, *configure_options, chdir: GCC_HOST end desc 'Make and install the bootstrap compiler' task :make do - sh 'make', '-j', Etc.nprocessors.to_s, chdir: HOST_GCC - sh 'make', 'install', chdir: HOST_GCC + UI.step "Building bootstrap compiler (jobs: #{Etc.nprocessors})..." + sh 'make', '-j', Etc.nprocessors.to_s, chdir: GCC_HOST + sh 'make', 'install', chdir: GCC_HOST end desc 'Convert the testsuite into dejagnu format' task :convert do - destination = GCC_TREE + 'gcc/testsuite/elna.dg' + UI.step 'Converting testsuite to DejaGnu format...' + destination = GCC_TREE / 'gcc/testsuite/elna.dg' destination.mkpath source = Pathname.new 'testsuite' # Regenerate the category directories from scratch: this removes # converted tests whose sources are gone and adds new ones. %w[compilable fail_compilation runnable].each do |category| + UI.debug "Converting #{category} tests..." category_destination = destination + category category_source = source + category @@ -235,32 +250,8 @@ namespace :gcc do category_source.each_child do |test_path| test_target = category_destination + test_path.basename - - # Multi-module tests: directories containing a sut.elna (system under - # test) and any helper files, compiled and linked together. if test_path.directory? - main = test_path + 'sut.elna' - raise %(multi-module test directory \"#{test_path}\" must contain sut.elna) unless main - - test_target.mkdir - modules = test_path.children - [main] - - modules.each do |extra| - cp_r extra, test_target - end - extra_lines = ["(* { dg-additional-options \"-I#{test_target.expand_path}\" } *)"] - - # Only 'runnable' tests link the helper modules into an executable, - # so only they need the helper sources compiled as separate - # translation units via dg-additional-sources. Elna resolves imports - # on its own, so passing it as an additional source there would add a - # second positional. - if category == 'runnable' - extra_sources = test_target.glob("**/*.elna") - .map { |extra| %("#{extra.relative_path_from test_target}") } - extra_lines << "(* { dg-additional-sources #{extra_sources * ' '} } *)" - end - convert_test main, (test_target + 'sut.elna'), category, extra_lines + convert_multi_module_test test_path, test_target, category else convert_test test_path, test_target, category end @@ -270,25 +261,27 @@ namespace :gcc do desc 'Run tests' task check: 'gcc:convert' do - log_file = Pathname.new(HOST_GCC) + 'gcc/testsuite/elna/elna.log' - - sh 'make', 'check-elna', chdir: File.join(HOST_GCC, 'gcc') + UI.step 'Running DejaGnu testsuite...' + log_file = Pathname.new(GCC_HOST) / 'gcc/testsuite/elna/elna.log' - fail "\nSee #{log_file}." if log_file.file? and log_file.read =~ /^(FAIL|XPASS|UNRESOLVED|ERROR):/ + sh 'make', 'check-elna', chdir: File.join(GCC_HOST, 'gcc') + UI.fatal "See #{log_file}." if log_file.file? && log_file.read =~ /^(FAIL|XPASS|UNRESOLVED|ERROR):/ end desc 'Run clang-tidy' task :tidy do compile_db = Pathname.new 'compile_commands.json' - raise "#{compile_db} is missing. Run: bear -- rake gcc:make" unless compile_db.exist? + UI.fatal "#{compile_db} is missing.", hint: 'Run: bear -- rake gcc:make' unless compile_db.exist? + sources = FileList['boot/*.cc', 'gcc/gcc/*.cc', 'include/elna/**/*.h'] .reject do |file| ['/elna1.', '/elna-spec.'].any? { |pattern| file.include? pattern } end + UI.step "Running clang-tidy on #{sources.size} files..." build_target = find_build_target GCC_VERSION includes = ['.', build_target.target].collect do |inc| - HOST_INSTALL + 'include/c++' + GCC_VERSION + inc + GCC_HOST_INSTALL / 'include/c++' / GCC_VERSION / inc end sh 'clang-tidy', '-p', compile_db.to_path, '--extra-arg=-w', '--warnings-as-errors=*', @@ -298,7 +291,7 @@ namespace :gcc do desc 'Build documentation' task :doc do - sh 'make', 'html', chdir: File.join(HOST_GCC, 'gcc') + sh 'make', 'html', chdir: File.join(GCC_HOST, 'gcc') end desc 'Run GCC linters and tests' |
