metacc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/metacc +8 -0
- data/lib/metacc/cli.rb +217 -0
- data/lib/metacc/driver.rb +129 -0
- data/lib/metacc/toolchain.rb +446 -0
- data/lib/metacc.rb +9 -0
- metadata +47 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3f277277e6c19c84a200cf69d21452da422980b3955c895bc148666b2e170d7c
|
|
4
|
+
data.tar.gz: e17a0627af2e670202be743c4e745a6aedaf10284ead45cd7c6740dd64dcf4b0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d889eff550e1f704c4e4264d750c1d03ddd7e040c1303eb6147501de5b0b2eec970c595cf183afe6fe7da91ee9465c0aa70f7596887018d3b9f4e979801e9f27
|
|
7
|
+
data.tar.gz: 9a0ae1d9c54d117d809dc236396ee0d4a69b321161a535287195474cdd6845a78dc0352bfe0b51f80487dbe25d9194a2afb98320ed2c90ff458f857d2be92f53
|
data/bin/metacc
ADDED
data/lib/metacc/cli.rb
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "driver"
|
|
5
|
+
|
|
6
|
+
module MetaCC
|
|
7
|
+
|
|
8
|
+
# Command-line interface for the MetaCC Driver.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# metacc <sources...> -o <output> [options] – compile source file(s)
|
|
12
|
+
#
|
|
13
|
+
# General:
|
|
14
|
+
# -Wall -Werror
|
|
15
|
+
# --std=c11 --std=c17 --std=c23
|
|
16
|
+
# --std=c++11 --std=c++14 --std=c++17 --std=c++20 --std=c++23 --std=c++26
|
|
17
|
+
#
|
|
18
|
+
# Linking:
|
|
19
|
+
# --objects / -c – compile only; don't link
|
|
20
|
+
# -l, -L - specify linker input
|
|
21
|
+
# --shared – produce a shared library
|
|
22
|
+
# --static – produce a static library
|
|
23
|
+
# --lto - enable link time optimization
|
|
24
|
+
# --strip / -s – strip unneeded symbols
|
|
25
|
+
#
|
|
26
|
+
# Code generation:
|
|
27
|
+
# -O0, -O1, -O2, -O3 - Set the optimization level
|
|
28
|
+
# -msse4.2 -mavx -mavx2 -mavx512 --arch=native - Compile for the given target
|
|
29
|
+
# --no-rtti --no-exceptions
|
|
30
|
+
# --pic
|
|
31
|
+
#
|
|
32
|
+
# Debugging:
|
|
33
|
+
# --debug / -g
|
|
34
|
+
# --asan --ubsan --msan
|
|
35
|
+
#
|
|
36
|
+
# Toolchain-specific flags (passed to Driver#compile via xflags:):
|
|
37
|
+
# --xmsvc VALUE – appended to xflags[MSVC]
|
|
38
|
+
# --xgnu VALUE – appended to xflags[GNU]
|
|
39
|
+
# --xclang VALUE – appended to xflags[Clang]
|
|
40
|
+
# --xclangcl VALUE – appended to xflags[ClangCL]
|
|
41
|
+
class CLI
|
|
42
|
+
|
|
43
|
+
# Maps long-form CLI flag names to Driver::RECOGNIZED_FLAGS symbols.
|
|
44
|
+
# Optimization-level flags are handled separately via -O LEVEL.
|
|
45
|
+
LONG_FLAGS = {
|
|
46
|
+
"lto" => :lto,
|
|
47
|
+
"asan" => :asan,
|
|
48
|
+
"ubsan" => :ubsan,
|
|
49
|
+
"msan" => :msan,
|
|
50
|
+
"no-rtti" => :no_rtti,
|
|
51
|
+
"no-exceptions" => :no_exceptions,
|
|
52
|
+
"pic" => :pic,
|
|
53
|
+
"no-semantic-interposition" => :no_semantic_interposition,
|
|
54
|
+
"no-omit-frame-pointer" => :no_omit_frame_pointer,
|
|
55
|
+
"no-strict-aliasing" => :no_strict_aliasing
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
WARNING_CONFIGS = {
|
|
59
|
+
"all" => :warn_all,
|
|
60
|
+
"error" => :warn_error
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
TARGETS = {
|
|
64
|
+
"sse4.2" => :sse4_2,
|
|
65
|
+
"avx" => :avx,
|
|
66
|
+
"avx2" => :avx2,
|
|
67
|
+
"avx512" => :avx512,
|
|
68
|
+
"native" => :native
|
|
69
|
+
}.freeze
|
|
70
|
+
|
|
71
|
+
STANDARDS = {
|
|
72
|
+
"c11" => :c11,
|
|
73
|
+
"c17" => :c17,
|
|
74
|
+
"c23" => :c23,
|
|
75
|
+
"c++11" => :cxx11,
|
|
76
|
+
"c++14" => :cxx14,
|
|
77
|
+
"c++17" => :cxx17,
|
|
78
|
+
"c++20" => :cxx20,
|
|
79
|
+
"c++23" => :cxx23,
|
|
80
|
+
"c++26" => :cxx26
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
83
|
+
# Maps --x<name> CLI option names to xflags toolchain-class keys.
|
|
84
|
+
XFLAGS = {
|
|
85
|
+
"xmsvc" => MSVC,
|
|
86
|
+
"xgnu" => GNU,
|
|
87
|
+
"xclang" => Clang,
|
|
88
|
+
"xclangcl" => ClangCL
|
|
89
|
+
}.freeze
|
|
90
|
+
|
|
91
|
+
def run(argv, driver: Driver.new)
|
|
92
|
+
options, input_paths = parse_compile_args(argv, driver:)
|
|
93
|
+
output_path = options.delete(:output_path)
|
|
94
|
+
run_flag = options.delete(:run)
|
|
95
|
+
validate_options!(options[:flags], output_path, run_flag)
|
|
96
|
+
invoke(driver, input_paths, output_path, options, run: run_flag)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Parses compile arguments.
|
|
100
|
+
# Returns [options_hash, remaining_positional_args].
|
|
101
|
+
def parse_compile_args(argv, driver: nil)
|
|
102
|
+
options = {
|
|
103
|
+
include_paths: [],
|
|
104
|
+
defs: [],
|
|
105
|
+
linker_paths: [],
|
|
106
|
+
libs: [],
|
|
107
|
+
output_path: nil,
|
|
108
|
+
run: false,
|
|
109
|
+
flags: [],
|
|
110
|
+
xflags: {},
|
|
111
|
+
}
|
|
112
|
+
parser = OptionParser.new
|
|
113
|
+
setup_compile_options(parser, options, driver:)
|
|
114
|
+
sources = parser.permute(argv)
|
|
115
|
+
[options, sources]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def setup_compile_options(parser, options, driver: nil)
|
|
121
|
+
parser.on("-o FILEPATH", "Output file path") do |value|
|
|
122
|
+
options[:output_path] = value
|
|
123
|
+
end
|
|
124
|
+
parser.on("-I DIRPATH", "Add an include search directory") do |value|
|
|
125
|
+
options[:include_paths] << value
|
|
126
|
+
end
|
|
127
|
+
parser.on("-D DEF", "Add a preprocessor definition") do |value|
|
|
128
|
+
options[:defs] << value
|
|
129
|
+
end
|
|
130
|
+
parser.on("-O LEVEL", /\A[0-3]\z/, "Optimization level (0–3)") do |level|
|
|
131
|
+
options[:flags] << :"o#{l}"
|
|
132
|
+
end
|
|
133
|
+
parser.on("-Os", "Optimize for size") do
|
|
134
|
+
options[:flags] << :os
|
|
135
|
+
end
|
|
136
|
+
parser.on("-m", "--arch ARCH", "Target architecture") do |value|
|
|
137
|
+
options[:flags] << TARGETS[v]
|
|
138
|
+
end
|
|
139
|
+
parser.on("-g", "--debug", "Emit debugging symbols") do
|
|
140
|
+
options[:flags] << :debug
|
|
141
|
+
end
|
|
142
|
+
parser.on("--std STANDARD", "Specify the language standard") do |value|
|
|
143
|
+
options[:flags] << STANDARDS[v]
|
|
144
|
+
end
|
|
145
|
+
parser.on("-W OPTION", "Configure warnings") do |value|
|
|
146
|
+
options[:flags] << WARNING_CONFIGS[v]
|
|
147
|
+
end
|
|
148
|
+
parser.on("-c", "--objects", "Produce object files") do
|
|
149
|
+
options[:flags] << :objects
|
|
150
|
+
end
|
|
151
|
+
parser.on("-r", "--run", "Run the compiled executable after a successful build") do
|
|
152
|
+
options[:run] = true
|
|
153
|
+
end
|
|
154
|
+
parser.on("-l LIB", "Link against library LIB") do |value|
|
|
155
|
+
options[:libs] << value
|
|
156
|
+
end
|
|
157
|
+
parser.on("-L DIR", "Add linker library search path") do |value|
|
|
158
|
+
options[:linker_paths] << value
|
|
159
|
+
end
|
|
160
|
+
parser.on("--shared", "Produce a shared library") do
|
|
161
|
+
options[:flags] << :shared
|
|
162
|
+
end
|
|
163
|
+
parser.on("--static", "Produce a static library") do
|
|
164
|
+
options[:flags] << :static
|
|
165
|
+
end
|
|
166
|
+
parser.on("-s", "--strip", "Strip unneeded symbols") do
|
|
167
|
+
options[:flags] << :strip
|
|
168
|
+
end
|
|
169
|
+
LONG_FLAGS.each do |name, sym|
|
|
170
|
+
parser.on("--#{name}") do
|
|
171
|
+
options[:flags] << sym
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
XFLAGS.each do |name, tc_class|
|
|
175
|
+
parser.on("--#{name} VALUE", "Pass VALUE to the #{tc_class} toolchain") do |value|
|
|
176
|
+
options[:xflags][tc_class] ||= []
|
|
177
|
+
options[:xflags][tc_class] << value
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
parser.on_tail("--version", "Print the toolchain version and exit") do
|
|
181
|
+
puts driver&.toolchain&.version_banner
|
|
182
|
+
exit
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def validate_options!(flags, output_path, run_flag)
|
|
187
|
+
objects = flags.include?(:objects)
|
|
188
|
+
|
|
189
|
+
if objects && output_path
|
|
190
|
+
warn "error: -o cannot be used with --objects"
|
|
191
|
+
exit 1
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
unless objects || output_path
|
|
195
|
+
warn "error: -o is required"
|
|
196
|
+
exit 1
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
if run_flag && (objects || flags.include?(:shared) || flags.include?(:static))
|
|
200
|
+
warn "error: --run cannot be used with --objects, --shared, or --static"
|
|
201
|
+
exit 1
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def run_executable(path)
|
|
206
|
+
system(path)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def invoke(driver, input_paths, output_path, options, run: false)
|
|
210
|
+
result = driver.invoke(input_paths, output_path, **options)
|
|
211
|
+
exit 1 unless result
|
|
212
|
+
run_executable(result) if run
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "toolchain"
|
|
4
|
+
|
|
5
|
+
module MetaCC
|
|
6
|
+
|
|
7
|
+
# Raised when no supported C/C++ compiler can be found on the system.
|
|
8
|
+
class CompilerNotFoundError < StandardError; end
|
|
9
|
+
|
|
10
|
+
# Driver wraps C and C++ compile and link operations using the first
|
|
11
|
+
# available compiler found on the system (Clang, GCC, or MSVC).
|
|
12
|
+
class Driver
|
|
13
|
+
|
|
14
|
+
RECOGNIZED_FLAGS = Set.new(
|
|
15
|
+
%i[
|
|
16
|
+
o0 o1 o2 o3 os
|
|
17
|
+
sse4_2 avx avx2 avx512 native
|
|
18
|
+
debug lto
|
|
19
|
+
warn_all warn_error
|
|
20
|
+
c11 c17 c23
|
|
21
|
+
cxx11 cxx14 cxx17 cxx20 cxx23 cxx26
|
|
22
|
+
asan ubsan msan
|
|
23
|
+
no_rtti no_exceptions pic
|
|
24
|
+
no_semantic_interposition no_omit_frame_pointer no_strict_aliasing
|
|
25
|
+
objects shared static strip
|
|
26
|
+
]
|
|
27
|
+
).freeze
|
|
28
|
+
|
|
29
|
+
# The detected toolchain (a Toolchain subclass instance).
|
|
30
|
+
attr_reader :toolchain
|
|
31
|
+
|
|
32
|
+
# Detects the first available C/C++ compiler toolchain.
|
|
33
|
+
#
|
|
34
|
+
# @param prefer [Array<Class>] toolchain classes to probe, in priority order.
|
|
35
|
+
# Each element must be a Class derived from Toolchain.
|
|
36
|
+
# Defaults to [Clang, GNU, MSVC].
|
|
37
|
+
# @param search_paths [Array<String>] directories to search for toolchain executables
|
|
38
|
+
# before falling back to PATH. Defaults to [].
|
|
39
|
+
# @raise [CompilerNotFoundError] if no supported compiler is found.
|
|
40
|
+
def initialize(prefer: [Clang, GNU, MSVC],
|
|
41
|
+
search_paths: [])
|
|
42
|
+
@toolchain = select_toolchain!(prefer, search_paths)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Invokes the compiler driver for the given input files and output path.
|
|
46
|
+
# The kind of output (object files, executable, shared library, or static
|
|
47
|
+
# library) is determined by the flags: +:objects+, +:shared+, or +:static+.
|
|
48
|
+
# When none of these mode flags is present, an executable is produced.
|
|
49
|
+
#
|
|
50
|
+
# @param input_files [String, Array<String>] paths to the input files
|
|
51
|
+
# @param output_path [String] path for the resulting output file
|
|
52
|
+
# @param flags [Array<Symbol>] compiler/linker flags
|
|
53
|
+
# @param xflags [Hash{Class => String}] extra (native) compiler flags keyed by toolchain Class
|
|
54
|
+
# @param include_paths [Array<String>] directories to add with -I
|
|
55
|
+
# @param defs [Array<String>] preprocessor macros (e.g. "FOO" or "FOO=1")
|
|
56
|
+
# @param libs [Array<String>] library names to link (e.g. "m", "pthread")
|
|
57
|
+
# @param linker_paths [Array<String>] linker library search paths (-L / /LIBPATH:)
|
|
58
|
+
# @param env [Hash] environment variables to set for the subprocess
|
|
59
|
+
# @param working_dir [String] working directory for the subprocess (default: ".")
|
|
60
|
+
# @return [String, true, nil] the (possibly extension-augmented) output path on success,
|
|
61
|
+
# true if output_path was nil and the command succeeded,
|
|
62
|
+
# nil if the underlying toolchain executable returned a non-zero exit status
|
|
63
|
+
# @raise [ArgumentError] if output_path is nil and the :objects flag is not present
|
|
64
|
+
def invoke(
|
|
65
|
+
input_files,
|
|
66
|
+
output_path,
|
|
67
|
+
flags: [],
|
|
68
|
+
xflags: {},
|
|
69
|
+
include_paths: [],
|
|
70
|
+
defs: [],
|
|
71
|
+
libs: [],
|
|
72
|
+
linker_paths: [],
|
|
73
|
+
env: {},
|
|
74
|
+
working_dir: "."
|
|
75
|
+
)
|
|
76
|
+
output_type = output_type_from_flags(flags)
|
|
77
|
+
raise ArgumentError, "output_path must not be nil" if output_path.nil? && output_type != :objects
|
|
78
|
+
|
|
79
|
+
output_path = apply_default_extension(output_path, output_type) unless output_path.nil?
|
|
80
|
+
|
|
81
|
+
input_files = Array(input_files)
|
|
82
|
+
flags = translate_flags(flags)
|
|
83
|
+
flags.concat(xflags[@toolchain.class] || [])
|
|
84
|
+
|
|
85
|
+
cmd = @toolchain.command(input_files, output_path, flags, include_paths, defs, libs, linker_paths)
|
|
86
|
+
run_command(cmd, env:, working_dir:) ? (output_path || true) : nil
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private
|
|
90
|
+
|
|
91
|
+
def select_toolchain!(candidates, search_paths)
|
|
92
|
+
candidates.each do |toolchain_class|
|
|
93
|
+
toolchain = toolchain_class.new(search_paths:)
|
|
94
|
+
return toolchain if toolchain.available?
|
|
95
|
+
end
|
|
96
|
+
raise CompilerNotFoundError, "No supported C/C++ compiler found (tried clang, gcc, cl)"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def output_type_from_flags(flags)
|
|
100
|
+
if flags.include?(:objects) then :objects
|
|
101
|
+
elsif flags.include?(:shared) then :shared
|
|
102
|
+
elsif flags.include?(:static) then :static
|
|
103
|
+
else :executable
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def apply_default_extension(path, output_type)
|
|
108
|
+
return path unless File.extname(path).empty?
|
|
109
|
+
|
|
110
|
+
ext = @toolchain.default_extension(output_type)
|
|
111
|
+
ext.empty? ? path : "#{path}#{ext}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def translate_flags(flags)
|
|
115
|
+
unrecognized_flag = flags.find { |flag| !RECOGNIZED_FLAGS.include?(flag) }
|
|
116
|
+
if unrecognized_flag
|
|
117
|
+
raise "#{unrecognized_flag.inspect} is not a known flag"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
flags.flat_map { |flag| @toolchain.flags[flag] }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def run_command(cmd, env: {}, working_dir: ".")
|
|
124
|
+
!!system(env, *cmd, chdir: working_dir, out: File::NULL, err: File::NULL)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
|
|
5
|
+
module MetaCC
|
|
6
|
+
|
|
7
|
+
# Base class for compiler toolchains.
|
|
8
|
+
# Subclasses set their own command attributes in +initialize+ by calling
|
|
9
|
+
# +command_available?+ to probe the system, then implement the
|
|
10
|
+
# toolchain-specific flag and command building methods.
|
|
11
|
+
# c – command used to compile source files
|
|
12
|
+
class Toolchain
|
|
13
|
+
|
|
14
|
+
attr_reader :c
|
|
15
|
+
|
|
16
|
+
def initialize(search_paths: [])
|
|
17
|
+
@search_paths = search_paths
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns true if this toolchain's primary compiler is present in PATH.
|
|
21
|
+
def available?
|
|
22
|
+
command_available?(c)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Returns the languages supported by this toolchain as an array of symbols.
|
|
26
|
+
# The default implementation returns [:c, :cxx]. Subclasses that only
|
|
27
|
+
# support a subset of languages should override this method.
|
|
28
|
+
def languages
|
|
29
|
+
[:c, :cxx]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns true if +command+ is present in PATH, false otherwise.
|
|
33
|
+
# Intentionally ignores the exit status – only ENOENT (not found) matters.
|
|
34
|
+
def command_available?(command)
|
|
35
|
+
return false if command.nil?
|
|
36
|
+
|
|
37
|
+
!system(command, "--version", out: File::NULL, err: File::NULL).nil?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the output of running the compiler with --version.
|
|
41
|
+
def version_banner
|
|
42
|
+
IO.popen([c, "--version", { err: :out }], &:read)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns a Hash mapping universal flags to native flags for this toolchain.
|
|
46
|
+
def flags
|
|
47
|
+
raise RuntimeError, "#{self.class}#flags not implemented"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns the full command array for the given inputs, output, and flags.
|
|
51
|
+
# The output mode (object files, shared library, static library, or
|
|
52
|
+
# executable) is determined by the translated flags.
|
|
53
|
+
def command(input_files, output, flags, include_paths, definitions, libs, linker_include_dirs)
|
|
54
|
+
raise RuntimeError, "#{self.class}#command not implemented"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the default file extension (with leading dot, e.g. ".o") for the
|
|
58
|
+
# given output type on this toolchain/OS combination. Returns an empty
|
|
59
|
+
# string when no extension is conventional (e.g. executables on Unix).
|
|
60
|
+
#
|
|
61
|
+
# @param output_type [:objects, :shared, :static, :executable]
|
|
62
|
+
# @return [String]
|
|
63
|
+
def default_extension(output_type)
|
|
64
|
+
host_os = RbConfig::CONFIG["host_os"]
|
|
65
|
+
case output_type
|
|
66
|
+
when :objects then ".o"
|
|
67
|
+
when :static then ".a"
|
|
68
|
+
when :shared
|
|
69
|
+
if host_os.match?(/mswin|mingw|cygwin/)
|
|
70
|
+
".dll"
|
|
71
|
+
elsif host_os.match?(/darwin/)
|
|
72
|
+
".dylib"
|
|
73
|
+
else
|
|
74
|
+
".so"
|
|
75
|
+
end
|
|
76
|
+
when :executable
|
|
77
|
+
host_os.match?(/mswin|mingw|cygwin/) ? ".exe" : ""
|
|
78
|
+
else
|
|
79
|
+
raise ArgumentError, "unknown output_type: #{output_type.inspect}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def c_file?(path)
|
|
86
|
+
File.extname(path).downcase == ".c"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns the full path to +name+ if it is found (and executable) in one of
|
|
90
|
+
# the configured search paths, otherwise returns +name+ unchanged so that
|
|
91
|
+
# the shell's PATH is used at execution time.
|
|
92
|
+
def resolve_command(name)
|
|
93
|
+
@search_paths.each do |dir|
|
|
94
|
+
full_path = File.join(dir, name)
|
|
95
|
+
return full_path if File.executable?(full_path)
|
|
96
|
+
end
|
|
97
|
+
name
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# GNU-compatible toolchain (gcc).
|
|
103
|
+
class GNU < Toolchain
|
|
104
|
+
|
|
105
|
+
def initialize(search_paths: [])
|
|
106
|
+
super
|
|
107
|
+
@c = resolve_command("gcc")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def command(input_files, output, flags, include_paths, definitions, libs, linker_include_dirs)
|
|
111
|
+
inc_flags = include_paths.map { |p| "-I#{p}" }
|
|
112
|
+
def_flags = definitions.map { |d| "-D#{d}" }
|
|
113
|
+
link_mode = !flags.include?("-c")
|
|
114
|
+
lib_path_flags = link_mode ? linker_include_dirs.map { |p| "-L#{p}" } : []
|
|
115
|
+
lib_flags = link_mode ? libs.map { |l| "-l#{l}" } : []
|
|
116
|
+
cmd = [c, *flags, *inc_flags, *def_flags, *input_files, *lib_path_flags, *lib_flags]
|
|
117
|
+
output.nil? ? cmd : [*cmd, "-o", output]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
GNU_FLAGS = {
|
|
121
|
+
o0: ["-O0"],
|
|
122
|
+
o1: ["-O1"],
|
|
123
|
+
o2: ["-O2"],
|
|
124
|
+
o3: ["-O3"],
|
|
125
|
+
os: ["-Os"],
|
|
126
|
+
sse4_2: ["-march=x86-64-v2"], # This is a better match for /arch:SSE4.2 than -msse4_2 is
|
|
127
|
+
avx: ["-march=x86-64-v2", "-mavx"],
|
|
128
|
+
avx2: ["-march=x86-64-v3"], # This is a better match for /arch:AVX2 than -mavx2 is
|
|
129
|
+
avx512: ["-march=x86-64-v4"],
|
|
130
|
+
native: ["-march=native", "-mtune=native"],
|
|
131
|
+
debug: ["-g3"],
|
|
132
|
+
lto: ["-flto"],
|
|
133
|
+
warn_all: ["-Wall", "-Wextra", "-pedantic"],
|
|
134
|
+
warn_error: ["-Werror"],
|
|
135
|
+
c11: ["-std=c11"],
|
|
136
|
+
c17: ["-std=c17"],
|
|
137
|
+
c23: ["-std=c23"],
|
|
138
|
+
cxx11: ["-std=c++11"],
|
|
139
|
+
cxx14: ["-std=c++14"],
|
|
140
|
+
cxx17: ["-std=c++17"],
|
|
141
|
+
cxx20: ["-std=c++20"],
|
|
142
|
+
cxx23: ["-std=c++23"],
|
|
143
|
+
cxx26: ["-std=c++2c"],
|
|
144
|
+
asan: ["-fsanitize=address"],
|
|
145
|
+
ubsan: ["-fsanitize=undefined"],
|
|
146
|
+
msan: ["-fsanitize=memory"],
|
|
147
|
+
no_rtti: ["-fno-rtti"],
|
|
148
|
+
no_exceptions: ["-fno-exceptions", "-fno-unwind-tables"],
|
|
149
|
+
pic: ["-fPIC"],
|
|
150
|
+
no_semantic_interposition: ["-fno-semantic-interposition"],
|
|
151
|
+
no_omit_frame_pointer: ["-fno-omit-frame-pointer"],
|
|
152
|
+
no_strict_aliasing: ["-fno-strict-aliasing"],
|
|
153
|
+
objects: ["-c"],
|
|
154
|
+
shared: ["-shared"],
|
|
155
|
+
static: ["-r", "-nostdlib"],
|
|
156
|
+
strip: ["-Wl,--strip-unneeded"]
|
|
157
|
+
}.freeze
|
|
158
|
+
|
|
159
|
+
def flags
|
|
160
|
+
GNU_FLAGS
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Clang toolchain – identical command structure to GNU.
|
|
166
|
+
class Clang < GNU
|
|
167
|
+
|
|
168
|
+
def initialize(search_paths: [])
|
|
169
|
+
super
|
|
170
|
+
@c = resolve_command("clang")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
CLANG_FLAGS = GNU_FLAGS.merge(lto: ["-flto=thin"]).freeze
|
|
174
|
+
|
|
175
|
+
def flags
|
|
176
|
+
CLANG_FLAGS
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Microsoft Visual C++ toolchain.
|
|
182
|
+
class MSVC < Toolchain
|
|
183
|
+
|
|
184
|
+
# Default location of the Visual Studio Installer's vswhere utility.
|
|
185
|
+
VSWHERE_PATH = File.join(
|
|
186
|
+
ENV.fetch("ProgramFiles(x86)", "C:\\Program Files (x86)"),
|
|
187
|
+
"Microsoft Visual Studio", "Installer", "vswhere.exe"
|
|
188
|
+
).freeze
|
|
189
|
+
|
|
190
|
+
def initialize(cl_command = "cl", search_paths: [])
|
|
191
|
+
super(search_paths:)
|
|
192
|
+
resolved_cmd = resolve_command(cl_command)
|
|
193
|
+
@c = resolved_cmd
|
|
194
|
+
setup_msvc_environment(resolved_cmd)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def command(input_files, output, flags, include_paths, definitions, libs, linker_include_dirs)
|
|
198
|
+
inc_flags = include_paths.map { |p| "/I#{p}" }
|
|
199
|
+
def_flags = definitions.map { |d| "/D#{d}" }
|
|
200
|
+
|
|
201
|
+
if flags.include?("/c")
|
|
202
|
+
cmd = [c, *flags, *inc_flags, *def_flags, *input_files]
|
|
203
|
+
output.nil? ? cmd : [*cmd, "/Fo#{output}"]
|
|
204
|
+
else
|
|
205
|
+
lib_flags = libs.map { |l| "#{l}.lib" }
|
|
206
|
+
lib_path_flags = linker_include_dirs.map { |p| "/LIBPATH:#{p}" }
|
|
207
|
+
cmd = [c, *flags, *inc_flags, *def_flags, *input_files, *lib_flags, "/Fe#{output}"]
|
|
208
|
+
cmd += ["/link", *lib_path_flags] unless lib_path_flags.empty?
|
|
209
|
+
cmd
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
MSVC_FLAGS = {
|
|
214
|
+
o0: ["/Od"],
|
|
215
|
+
o1: ["/O1"],
|
|
216
|
+
o2: ["/O2"],
|
|
217
|
+
o3: ["/O2", "/Ob3"],
|
|
218
|
+
os: ["/O1"],
|
|
219
|
+
sse4_2: ["/arch:SSE4.2"],
|
|
220
|
+
avx: ["/arch:AVX"],
|
|
221
|
+
avx2: ["/arch:AVX2"],
|
|
222
|
+
avx512: ["/arch:AVX512"],
|
|
223
|
+
native: [],
|
|
224
|
+
debug: ["/Zi"],
|
|
225
|
+
lto: ["/GL"],
|
|
226
|
+
warn_all: ["/W4"],
|
|
227
|
+
warn_error: ["/WX"],
|
|
228
|
+
c11: ["/std:c11"],
|
|
229
|
+
c17: ["/std:c17"],
|
|
230
|
+
c23: ["/std:clatest"],
|
|
231
|
+
cxx11: [],
|
|
232
|
+
cxx14: ["/std:c++14"],
|
|
233
|
+
cxx17: ["/std:c++17"],
|
|
234
|
+
cxx20: ["/std:c++20"],
|
|
235
|
+
cxx23: ["/std:c++23preview"],
|
|
236
|
+
cxx26: ["/std:c++latest"],
|
|
237
|
+
asan: ["/fsanitize=address"],
|
|
238
|
+
ubsan: [],
|
|
239
|
+
msan: [],
|
|
240
|
+
no_rtti: ["/GR-"],
|
|
241
|
+
no_exceptions: ["/EHs-", "/EHc-"],
|
|
242
|
+
pic: [],
|
|
243
|
+
no_semantic_interposition: [],
|
|
244
|
+
no_omit_frame_pointer: ["/Oy-"],
|
|
245
|
+
no_strict_aliasing: [],
|
|
246
|
+
objects: ["/c"],
|
|
247
|
+
shared: ["/LD"],
|
|
248
|
+
static: ["/c"],
|
|
249
|
+
strip: []
|
|
250
|
+
}.freeze
|
|
251
|
+
|
|
252
|
+
def flags
|
|
253
|
+
MSVC_FLAGS
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# MSVC and clang-cl always target Windows, so extensions are Windows-specific
|
|
257
|
+
# regardless of the host OS.
|
|
258
|
+
def default_extension(output_type)
|
|
259
|
+
case output_type
|
|
260
|
+
when :objects then ".obj"
|
|
261
|
+
when :static then ".lib"
|
|
262
|
+
when :shared then ".dll"
|
|
263
|
+
when :executable then ".exe"
|
|
264
|
+
else
|
|
265
|
+
raise ArgumentError, "unknown output_type: #{output_type.inspect}"
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# MSVC prints its version banner to stderr when invoked with no arguments.
|
|
270
|
+
def show_version
|
|
271
|
+
IO.popen([c, { err: :out }], &:read)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
private
|
|
275
|
+
|
|
276
|
+
# Attempts to configure the MSVC environment using vswhere.exe when cl.exe
|
|
277
|
+
# is not already available on PATH. Tries two vswhere strategies in order:
|
|
278
|
+
#
|
|
279
|
+
# 1. Query vswhere for VS instances whose tools are already on PATH (-path).
|
|
280
|
+
# 2. Query vswhere for the latest VS instance, including prereleases.
|
|
281
|
+
#
|
|
282
|
+
# When a VS instance is found, locates vcvarsall.bat relative to the
|
|
283
|
+
# returned devenv.exe path and runs it so that cl.exe and related tools
|
|
284
|
+
# become available on PATH.
|
|
285
|
+
def setup_msvc_environment(cl_command)
|
|
286
|
+
return if command_available?(cl_command)
|
|
287
|
+
|
|
288
|
+
devenv_path = run_vswhere("-path", "-property", "productPath") ||
|
|
289
|
+
run_vswhere("-latest", "-prerelease", "-property", "productPath")
|
|
290
|
+
return unless devenv_path
|
|
291
|
+
|
|
292
|
+
vcvarsall = find_vcvarsall(devenv_path)
|
|
293
|
+
return unless vcvarsall
|
|
294
|
+
|
|
295
|
+
run_vcvarsall(vcvarsall)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Runs vswhere.exe with the given arguments and returns the trimmed stdout,
|
|
299
|
+
# or nil if vswhere.exe is absent, the command fails, or produces no output.
|
|
300
|
+
def run_vswhere(*args)
|
|
301
|
+
return nil unless File.exist?(VSWHERE_PATH)
|
|
302
|
+
|
|
303
|
+
stdout = IO.popen([VSWHERE_PATH, *args], &:read)
|
|
304
|
+
status = $?
|
|
305
|
+
return nil unless status.success?
|
|
306
|
+
|
|
307
|
+
path = stdout.strip
|
|
308
|
+
path.empty? ? nil : path
|
|
309
|
+
rescue Errno::ENOENT
|
|
310
|
+
nil
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Returns the path to vcvarsall.bat for the given devenv.exe path, or nil
|
|
314
|
+
# if it cannot be located. devenv.exe lives at:
|
|
315
|
+
# <root>\Common7\IDE\devenv.exe
|
|
316
|
+
# vcvarsall.bat lives at:
|
|
317
|
+
# <root>\VC\Auxiliary\Build\vcvarsall.bat
|
|
318
|
+
def find_vcvarsall(devenv_path)
|
|
319
|
+
install_root = File.expand_path("../../..", devenv_path)
|
|
320
|
+
vcvarsall = File.join(install_root, "VC", "Auxiliary", "Build", "vcvarsall.bat")
|
|
321
|
+
File.exist?(vcvarsall) ? vcvarsall : nil
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Runs vcvarsall.bat for the x64 architecture and merges the resulting
|
|
325
|
+
# environment variables into the current process's ENV so that cl.exe
|
|
326
|
+
# and related tools become available on PATH.
|
|
327
|
+
def run_vcvarsall(vcvarsall)
|
|
328
|
+
stdout = IO.popen(["cmd.exe", "/c", vcvarsall_command(vcvarsall)], &:read)
|
|
329
|
+
status = $?
|
|
330
|
+
return unless status.success?
|
|
331
|
+
|
|
332
|
+
load_vcvarsall(stdout)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Builds the cmd.exe command string for calling vcvarsall.bat and capturing
|
|
336
|
+
# the resulting environment variables. The path is double-quoted to handle
|
|
337
|
+
# spaces; any embedded double quotes are escaped by doubling them, which is
|
|
338
|
+
# the cmd.exe convention inside a double-quoted string. Shellwords is not
|
|
339
|
+
# used here because it produces POSIX sh escaping, which is incompatible
|
|
340
|
+
# with cmd.exe syntax.
|
|
341
|
+
def vcvarsall_command(vcvarsall)
|
|
342
|
+
quoted = '"' + vcvarsall.gsub('"', '""') + '"'
|
|
343
|
+
"#{quoted} x64 && set"
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Parses the output of `vcvarsall.bat … && set` and merges the resulting
|
|
347
|
+
# environment variables into the current process's ENV.
|
|
348
|
+
def load_vcvarsall(output)
|
|
349
|
+
output.each_line do |line|
|
|
350
|
+
key, sep, value = line.chomp.partition("=")
|
|
351
|
+
next if sep.empty?
|
|
352
|
+
|
|
353
|
+
ENV[key] = value
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# clang-cl toolchain – uses clang-cl compiler with MSVC-compatible flags and
|
|
360
|
+
# environment setup.
|
|
361
|
+
class ClangCL < MSVC
|
|
362
|
+
|
|
363
|
+
def initialize(search_paths: [])
|
|
364
|
+
super("clang-cl", search_paths:)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
CLANG_CL_FLAGS = MSVC_FLAGS.merge(
|
|
368
|
+
o3: ["/Ot"], # Clang-CL treats /Ot as -O3
|
|
369
|
+
lto: ["-flto=thin"]
|
|
370
|
+
).freeze
|
|
371
|
+
|
|
372
|
+
def flags
|
|
373
|
+
CLANG_CL_FLAGS
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
# TinyCC toolchain (tcc). TinyCC only supports C, not C++.
|
|
379
|
+
class TinyCC < Toolchain
|
|
380
|
+
|
|
381
|
+
def initialize(search_paths: [])
|
|
382
|
+
super
|
|
383
|
+
@c = resolve_command("tcc")
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# TinyCC does not support C++.
|
|
387
|
+
def languages
|
|
388
|
+
[:c]
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def command(input_files, output, flags, include_paths, definitions, libs, linker_include_dirs)
|
|
392
|
+
inc_flags = include_paths.map { |p| "-I#{p}" }
|
|
393
|
+
def_flags = definitions.map { |d| "-D#{d}" }
|
|
394
|
+
link_mode = !flags.include?("-c")
|
|
395
|
+
lib_path_flags = link_mode ? linker_include_dirs.map { |p| "-L#{p}" } : []
|
|
396
|
+
lib_flags = link_mode ? libs.map { |l| "-l#{l}" } : []
|
|
397
|
+
cmd = [c, *flags, *inc_flags, *def_flags, *input_files, *lib_path_flags, *lib_flags]
|
|
398
|
+
output.nil? ? cmd : [*cmd, "-o", output]
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
TINYCC_FLAGS = {
|
|
402
|
+
o0: [],
|
|
403
|
+
o1: ["-O1"],
|
|
404
|
+
o2: ["-O2"],
|
|
405
|
+
o3: ["-O2"],
|
|
406
|
+
os: [],
|
|
407
|
+
sse4_2: [],
|
|
408
|
+
avx: [],
|
|
409
|
+
avx2: [],
|
|
410
|
+
avx512: [],
|
|
411
|
+
native: [],
|
|
412
|
+
debug: ["-g"],
|
|
413
|
+
lto: [],
|
|
414
|
+
warn_all: ["-Wall"],
|
|
415
|
+
warn_error: ["-Werror"],
|
|
416
|
+
c11: [],
|
|
417
|
+
c17: [],
|
|
418
|
+
c23: [],
|
|
419
|
+
cxx11: [],
|
|
420
|
+
cxx14: [],
|
|
421
|
+
cxx17: [],
|
|
422
|
+
cxx20: [],
|
|
423
|
+
cxx23: [],
|
|
424
|
+
cxx26: [],
|
|
425
|
+
asan: [],
|
|
426
|
+
ubsan: [],
|
|
427
|
+
msan: [],
|
|
428
|
+
no_rtti: [],
|
|
429
|
+
no_exceptions: [],
|
|
430
|
+
pic: [],
|
|
431
|
+
no_semantic_interposition: [],
|
|
432
|
+
no_omit_frame_pointer: [],
|
|
433
|
+
no_strict_aliasing: [],
|
|
434
|
+
objects: ["-c"],
|
|
435
|
+
shared: ["-shared"],
|
|
436
|
+
static: ["-c"],
|
|
437
|
+
strip: []
|
|
438
|
+
}.freeze
|
|
439
|
+
|
|
440
|
+
def flags
|
|
441
|
+
TINYCC_FLAGS
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
end
|
data/lib/metacc.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: metacc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Praneeth Sadda
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |
|
|
13
|
+
metacc provides a small set of classes for invoking C/C++ build tools, abstracting
|
|
14
|
+
away differences between compilers.
|
|
15
|
+
email: psadda@gmail.com
|
|
16
|
+
executables:
|
|
17
|
+
- metacc
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- bin/metacc
|
|
22
|
+
- lib/metacc.rb
|
|
23
|
+
- lib/metacc/cli.rb
|
|
24
|
+
- lib/metacc/driver.rb
|
|
25
|
+
- lib/metacc/toolchain.rb
|
|
26
|
+
licenses:
|
|
27
|
+
- BSD-3-Clause
|
|
28
|
+
metadata:
|
|
29
|
+
rubygems_mfa_required: 'true'
|
|
30
|
+
rdoc_options: []
|
|
31
|
+
require_paths:
|
|
32
|
+
- lib
|
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
34
|
+
requirements:
|
|
35
|
+
- - ">="
|
|
36
|
+
- !ruby/object:Gem::Version
|
|
37
|
+
version: '3.2'
|
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
43
|
+
requirements: []
|
|
44
|
+
rubygems_version: 4.0.7
|
|
45
|
+
specification_version: 4
|
|
46
|
+
summary: A small Ruby scripting system for building C and C++ applications
|
|
47
|
+
test_files: []
|