aboutsummaryrefslogtreecommitdiff
path: root/rakelib/gcc.rake
blob: ce02bb6b5790aefb5d9ec3b504d307f15ac3cffe (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
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/. -}
# frozen_string_literal: true

require 'uri'
require 'net/http'
require 'open3'
require 'pathname'

def gcc_verbose(gcc_binary)
  read, write = IO.pipe
  sh({'LC_ALL' => 'C'}, gcc_binary, '--verbose', err: write)
  write.close
  output = read.read
  read.close
  output
end

def find_build_target
  gcc_verbose(ENV.fetch 'CC', 'gcc')
    .lines
    .find { |line| line.start_with? 'Target: ' }
    .split(' ')
    .last
    .strip
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
  end
end

def link_frontend(source, destination)
  File.symlink Pathname.new(source).relative_path_from(destination), (destination + File.basename(source))
end

namespace :gcc do
  # Dependencies.
  GCC_VERSION = "16.1.0"
  HOST_GCC = 'build/host/gcc'
  GCC_TREE = Pathname.new "build/tools/gcc-#{GCC_VERSION}"
  GCC_PATCH = 'https://raw.githubusercontent.com/Homebrew/homebrew-core/refs/heads/main/Patches/gcc/gcc-16.1.0.diff'

  directory HOST_GCC
  directory 'build/host/install'
  directory 'build/tools'

  desc 'Download the bootstrap compiler and its prerequisites'
  task download: 'build/tools' do
    url = URI.parse "https://gcc.gnu.org/pub/gcc/releases/gcc-#{GCC_VERSION}/gcc-#{GCC_VERSION}.tar.xz"

    download_and_pipe url, GCC_TREE.dirname, ['tar', '-Jxv']
    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})"
      end
      opts_h.seek 0, IO::SEEK_SET
      opts_h.write content
    end

    sh 'contrib/download_prerequisites', chdir: GCC_TREE.to_path
  end

  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'

    rm_rf [source_destination, test_destination]
    mkdir_p [source_destination, test_destination]

    FileList['boot', 'include', 'COPYING3', 'README.md', 'gcc/gcc', 'gcc/*.in', 'gcc/lang*'].each do |file|
      link_frontend file, source_destination
    end
    FileList['testsuite/*', 'gcc/dg.exp', 'README.md'].each do |file|
      link_frontend file, test_destination
    end
    destination = GCC_TREE + 'gcc/testsuite/lib'
    FileList['gcc/testlib/*'].each do |file|
      rm_f (destination + File.basename(file))
      link_frontend file, destination
    end
  end

  desc 'Configure the bootstrap compiler'
  task configure: [HOST_GCC, 'build/host/install'] do |t|
    build_target = find_build_target
    configure_options = [
      "--prefix=#{File.realpath t.prerequisites.last}",
      '--enable-languages=c,c++,jit,elna',
      '--disable-bootstrap',
      '--disable-multilib',
      '--enable-host-shared',
      '--with-system-zlib',
      "--target=#{build_target}",
      "--build=#{build_target}",
      "--host=#{build_target}"
    ]
    mac_os_sdk = '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk'
    configure_options << "--with-sysroot=#{mac_os_sdk}" if File.symlink? mac_os_sdk

    env = ENV.slice 'CC', 'CXX'
    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
  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
  end

  desc 'Run tests'
  task :check do
    sh 'make', 'check-elna', chdir: File.join(HOST_GCC, 'gcc')
  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?
    sources = FileList['boot/*.cc', 'include/elna/boot/*.h'].exclude do |f|
      f.include?('generated/')
    end
    sh 'clang-tidy', '-p', compile_db.to_path, '--extra-arg=-w', '--warnings-as-errors=*', *sources
  end

  desc 'Build documentation'
  task :doc do
    sh 'make', 'html', chdir: File.join(HOST_GCC, 'gcc')
  end
end

desc 'Build the bootstrap compiler'
task gcc: %w[gcc:download gcc:link gcc:configure gcc:make]