metacc 0.1.0 → 0.2.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 +4 -4
- data/bin/metacc +7 -1
- data/lib/metacc/cli.rb +55 -56
- data/lib/metacc/driver.rb +69 -31
- data/lib/metacc/toolchain.rb +143 -115
- data/lib/metacc/version.rb +5 -0
- data/lib/metacc.rb +1 -6
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a14fc8ca9c502275a4cc8105906722c167fb58cf6a269559748da22cf88f83d3
|
|
4
|
+
data.tar.gz: f56a289ba4240caf683f8614f5317252753032ad8902cce314a0ab43380d05fd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b5c6055a1445b3e0341fbde3da0ca9b76e2cfe021c0c7694be91233a2eb2c68de7f063cc5c2b9b953b697fdfa41f3a949b168eb33790d8544fedeeef90f222a
|
|
7
|
+
data.tar.gz: 30b826250c85ce5d7b57c83f81714d7ac51b6e911abed4ea8371e3bc36540f0b29779aeced94b398b7d590ffbbbf608c7ab2929f0d9743a8967824edad8bea59
|
data/bin/metacc
CHANGED
|
@@ -5,4 +5,10 @@ $LOAD_PATH.unshift(File.join(__dir__, "..", "lib"))
|
|
|
5
5
|
|
|
6
6
|
require "metacc/cli"
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
begin
|
|
9
|
+
MetaCC::CLI.new.run(ARGV)
|
|
10
|
+
rescue MetaCC::CLI::InvalidOption, MetaCC::ToolchainNotFoundError => e
|
|
11
|
+
warn "#{$0} error: #{e.message}"
|
|
12
|
+
rescue MetaCC::CompileError => e
|
|
13
|
+
warn e.message
|
|
14
|
+
end
|
data/lib/metacc/cli.rb
CHANGED
|
@@ -16,7 +16,7 @@ module MetaCC
|
|
|
16
16
|
# --std=c++11 --std=c++14 --std=c++17 --std=c++20 --std=c++23 --std=c++26
|
|
17
17
|
#
|
|
18
18
|
# Linking:
|
|
19
|
-
#
|
|
19
|
+
# -c – compile only; don't link
|
|
20
20
|
# -l, -L - specify linker input
|
|
21
21
|
# --shared – produce a shared library
|
|
22
22
|
# --static – produce a static library
|
|
@@ -69,9 +69,9 @@ module MetaCC
|
|
|
69
69
|
}.freeze
|
|
70
70
|
|
|
71
71
|
STANDARDS = {
|
|
72
|
-
"c11"
|
|
73
|
-
"c17"
|
|
74
|
-
"c23"
|
|
72
|
+
"c11" => :c11,
|
|
73
|
+
"c17" => :c17,
|
|
74
|
+
"c23" => :c23,
|
|
75
75
|
"c++11" => :cxx11,
|
|
76
76
|
"c++14" => :cxx14,
|
|
77
77
|
"c++17" => :cxx17,
|
|
@@ -82,42 +82,46 @@ module MetaCC
|
|
|
82
82
|
|
|
83
83
|
# Maps --x<name> CLI option names to xflags toolchain-class keys.
|
|
84
84
|
XFLAGS = {
|
|
85
|
-
"xmsvc" =>
|
|
86
|
-
"xgnu" =>
|
|
87
|
-
"xclang" =>
|
|
85
|
+
"xmsvc" => MSVC,
|
|
86
|
+
"xgnu" => GNU,
|
|
87
|
+
"xclang" => Clang,
|
|
88
88
|
"xclangcl" => ClangCL
|
|
89
89
|
}.freeze
|
|
90
90
|
|
|
91
|
-
def
|
|
92
|
-
|
|
91
|
+
def initialize(driver: Driver.new)
|
|
92
|
+
@driver = driver
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def run(argv)
|
|
96
|
+
input_paths, options = parse_compile_args(argv)
|
|
93
97
|
output_path = options.delete(:output_path)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
invoke(driver, input_paths, output_path, options, run: run_flag)
|
|
98
|
+
validate_options!(options[:flags], output_path, link: options[:link], run: options[:run])
|
|
99
|
+
invoke(input_paths, output_path, **options)
|
|
97
100
|
end
|
|
98
101
|
|
|
99
102
|
# Parses compile arguments.
|
|
100
|
-
# Returns [
|
|
101
|
-
def parse_compile_args(argv
|
|
103
|
+
# Returns [positional_args, options_hash].
|
|
104
|
+
def parse_compile_args(argv)
|
|
102
105
|
options = {
|
|
103
106
|
include_paths: [],
|
|
104
|
-
defs:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
107
|
+
defs: [],
|
|
108
|
+
link: true,
|
|
109
|
+
link_paths: [],
|
|
110
|
+
libs: [],
|
|
111
|
+
output_path: nil,
|
|
112
|
+
run: false,
|
|
113
|
+
flags: [],
|
|
114
|
+
xflags: {}
|
|
111
115
|
}
|
|
112
116
|
parser = OptionParser.new
|
|
113
|
-
setup_compile_options(parser, options
|
|
114
|
-
|
|
115
|
-
[
|
|
117
|
+
setup_compile_options(parser, options)
|
|
118
|
+
input_paths = parser.permute(argv)
|
|
119
|
+
[input_paths, options]
|
|
116
120
|
end
|
|
117
121
|
|
|
118
122
|
private
|
|
119
123
|
|
|
120
|
-
def setup_compile_options(parser, options
|
|
124
|
+
def setup_compile_options(parser, options)
|
|
121
125
|
parser.on("-o FILEPATH", "Output file path") do |value|
|
|
122
126
|
options[:output_path] = value
|
|
123
127
|
end
|
|
@@ -127,26 +131,23 @@ module MetaCC
|
|
|
127
131
|
parser.on("-D DEF", "Add a preprocessor definition") do |value|
|
|
128
132
|
options[:defs] << value
|
|
129
133
|
end
|
|
130
|
-
parser.on("-O LEVEL", /\A[0-3]\z/, "Optimization level (0–3)") do |level|
|
|
131
|
-
options[:flags] << :"o#{
|
|
132
|
-
end
|
|
133
|
-
parser.on("-Os", "Optimize for size") do
|
|
134
|
-
options[:flags] << :os
|
|
134
|
+
parser.on("-O LEVEL", /\A[0-3]|s\z/, "Optimization level (0–3)") do |level|
|
|
135
|
+
options[:flags] << :"o#{level}"
|
|
135
136
|
end
|
|
136
137
|
parser.on("-m", "--arch ARCH", "Target architecture") do |value|
|
|
137
|
-
options[:flags] << TARGETS[
|
|
138
|
+
options[:flags] << TARGETS[value]
|
|
138
139
|
end
|
|
139
140
|
parser.on("-g", "--debug", "Emit debugging symbols") do
|
|
140
141
|
options[:flags] << :debug
|
|
141
142
|
end
|
|
142
143
|
parser.on("--std STANDARD", "Specify the language standard") do |value|
|
|
143
|
-
options[:flags] << STANDARDS[
|
|
144
|
+
options[:flags] << STANDARDS[value]
|
|
144
145
|
end
|
|
145
146
|
parser.on("-W OPTION", "Configure warnings") do |value|
|
|
146
|
-
options[:flags] << WARNING_CONFIGS[
|
|
147
|
+
options[:flags] << WARNING_CONFIGS[value]
|
|
147
148
|
end
|
|
148
|
-
parser.on("-c", "
|
|
149
|
-
options[:
|
|
149
|
+
parser.on("-c", "Produce object files") do
|
|
150
|
+
options[:link] = false
|
|
150
151
|
end
|
|
151
152
|
parser.on("-r", "--run", "Run the compiled executable after a successful build") do
|
|
152
153
|
options[:run] = true
|
|
@@ -155,7 +156,7 @@ module MetaCC
|
|
|
155
156
|
options[:libs] << value
|
|
156
157
|
end
|
|
157
158
|
parser.on("-L DIR", "Add linker library search path") do |value|
|
|
158
|
-
options[:
|
|
159
|
+
options[:link_paths] << value
|
|
159
160
|
end
|
|
160
161
|
parser.on("--shared", "Produce a shared library") do
|
|
161
162
|
options[:flags] << :shared
|
|
@@ -178,39 +179,37 @@ module MetaCC
|
|
|
178
179
|
end
|
|
179
180
|
end
|
|
180
181
|
parser.on_tail("--version", "Print the toolchain version and exit") do
|
|
181
|
-
puts driver
|
|
182
|
+
puts @driver.toolchain.version_banner
|
|
182
183
|
exit
|
|
183
184
|
end
|
|
184
185
|
end
|
|
185
186
|
|
|
186
|
-
def validate_options!(flags, output_path,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
if objects && output_path
|
|
190
|
-
warn "error: -o cannot be used with --objects"
|
|
191
|
-
exit 1
|
|
187
|
+
def validate_options!(flags, output_path, link:, run:)
|
|
188
|
+
if !link && output_path
|
|
189
|
+
raise InvalidOption, "cannot specify output path (-o) in compile only mode (-c)"
|
|
192
190
|
end
|
|
193
191
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
exit 1
|
|
192
|
+
if link && !output_path
|
|
193
|
+
raise InvalidOption, "must specify an output path (-o)"
|
|
197
194
|
end
|
|
198
195
|
|
|
199
|
-
if
|
|
200
|
-
|
|
201
|
-
exit 1
|
|
196
|
+
if run && (!link || flags.include?(:shared) || flags.include?(:static))
|
|
197
|
+
raise InvalidOption, "--run may not be used with -c, --shared, or --static"
|
|
202
198
|
end
|
|
203
199
|
end
|
|
204
200
|
|
|
205
|
-
def
|
|
206
|
-
|
|
201
|
+
def invoke(input_paths, desired_output_path = nil, link: true, run: false, **options)
|
|
202
|
+
if link
|
|
203
|
+
actual_output_path = @driver.compile_and_link(input_paths, desired_output_path, **options)
|
|
204
|
+
system(actual_output_path) if run
|
|
205
|
+
else
|
|
206
|
+
options.delete(:link_paths)
|
|
207
|
+
options.delete(:libs)
|
|
208
|
+
@driver.compile(input_paths, **options)
|
|
209
|
+
end
|
|
207
210
|
end
|
|
208
211
|
|
|
209
|
-
|
|
210
|
-
result = driver.invoke(input_paths, output_path, **options)
|
|
211
|
-
exit 1 unless result
|
|
212
|
-
run_executable(result) if run
|
|
213
|
-
end
|
|
212
|
+
class InvalidOption < StandardError; end
|
|
214
213
|
|
|
215
214
|
end
|
|
216
215
|
|
data/lib/metacc/driver.rb
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "open3"
|
|
3
4
|
require_relative "toolchain"
|
|
4
5
|
|
|
5
6
|
module MetaCC
|
|
6
7
|
|
|
7
8
|
# Raised when no supported C/C++ compiler can be found on the system.
|
|
8
|
-
class
|
|
9
|
+
class CompileError < StandardError; end
|
|
10
|
+
|
|
11
|
+
# Raised when no supported C/C++ compiler can be found on the system.
|
|
12
|
+
class ToolchainNotFoundError < StandardError; end
|
|
9
13
|
|
|
10
14
|
# Driver wraps C and C++ compile and link operations using the first
|
|
11
15
|
# available compiler found on the system (Clang, GCC, or MSVC).
|
|
@@ -22,7 +26,7 @@ module MetaCC
|
|
|
22
26
|
asan ubsan msan
|
|
23
27
|
no_rtti no_exceptions pic
|
|
24
28
|
no_semantic_interposition no_omit_frame_pointer no_strict_aliasing
|
|
25
|
-
|
|
29
|
+
shared static strip
|
|
26
30
|
]
|
|
27
31
|
).freeze
|
|
28
32
|
|
|
@@ -36,16 +40,48 @@ module MetaCC
|
|
|
36
40
|
# Defaults to [Clang, GNU, MSVC].
|
|
37
41
|
# @param search_paths [Array<String>] directories to search for toolchain executables
|
|
38
42
|
# before falling back to PATH. Defaults to [].
|
|
39
|
-
# @raise [
|
|
40
|
-
def initialize(prefer: [
|
|
41
|
-
search_paths: [])
|
|
43
|
+
# @raise [ToolchainNotFoundError] if no supported compiler is found.
|
|
44
|
+
def initialize(prefer: [MSVC], search_paths: [])
|
|
42
45
|
@toolchain = select_toolchain!(prefer, search_paths)
|
|
43
46
|
end
|
|
44
47
|
|
|
48
|
+
# Invokes the compiler driver for the given input files and output path,
|
|
49
|
+
# compiling and producting object files without linking.
|
|
50
|
+
#
|
|
51
|
+
# @param input_files [String, Array<String>] paths to the input files
|
|
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 env [Hash] environment variables to set for the subprocess
|
|
57
|
+
# @param working_dir [String] working directory for the subprocess (default: ".")
|
|
58
|
+
# @raise [CompileError] if the underlying toolchain executable returns a non-zero exit status
|
|
59
|
+
def compile(
|
|
60
|
+
input_files,
|
|
61
|
+
flags: [],
|
|
62
|
+
xflags: {},
|
|
63
|
+
include_paths: [],
|
|
64
|
+
defs: [],
|
|
65
|
+
env: {},
|
|
66
|
+
working_dir: "."
|
|
67
|
+
)
|
|
68
|
+
flags = translate_flags(flags)
|
|
69
|
+
flags.concat(xflags[@toolchain.class] || [])
|
|
70
|
+
|
|
71
|
+
cmd = @toolchain.compile_command(
|
|
72
|
+
input_files,
|
|
73
|
+
flags:,
|
|
74
|
+
include_paths:,
|
|
75
|
+
defs:
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
!!run_command(cmd, env:, working_dir:)
|
|
79
|
+
end
|
|
80
|
+
|
|
45
81
|
# Invokes the compiler driver for the given input files and output path.
|
|
46
82
|
# The kind of output (object files, executable, shared library, or static
|
|
47
|
-
# library) is determined by the flags: +:
|
|
48
|
-
#
|
|
83
|
+
# library) is determined by the flags: +:shared+ or +:static+. When none of
|
|
84
|
+
# these mode flags is present, an executable is produced.
|
|
49
85
|
#
|
|
50
86
|
# @param input_files [String, Array<String>] paths to the input files
|
|
51
87
|
# @param output_path [String] path for the resulting output file
|
|
@@ -53,37 +89,45 @@ module MetaCC
|
|
|
53
89
|
# @param xflags [Hash{Class => String}] extra (native) compiler flags keyed by toolchain Class
|
|
54
90
|
# @param include_paths [Array<String>] directories to add with -I
|
|
55
91
|
# @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
92
|
# @param linker_paths [Array<String>] linker library search paths (-L / /LIBPATH:)
|
|
93
|
+
# @param libs [Array<String>] library names to link (e.g. "m", "pthread")
|
|
58
94
|
# @param env [Hash] environment variables to set for the subprocess
|
|
59
95
|
# @param working_dir [String] working directory for the subprocess (default: ".")
|
|
60
|
-
# @return [String
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
# @raise [ArgumentError] if output_path is nil and the :objects flag is not present
|
|
64
|
-
def invoke(
|
|
96
|
+
# @return [String] the (possibly extension-augmented) output path on success
|
|
97
|
+
# @raise [CompileError] if the underlying toolchain executable returns a non-zero exit status
|
|
98
|
+
def compile_and_link(
|
|
65
99
|
input_files,
|
|
66
100
|
output_path,
|
|
67
101
|
flags: [],
|
|
68
102
|
xflags: {},
|
|
69
103
|
include_paths: [],
|
|
70
104
|
defs: [],
|
|
105
|
+
link_paths: [],
|
|
71
106
|
libs: [],
|
|
72
|
-
linker_paths: [],
|
|
73
107
|
env: {},
|
|
74
108
|
working_dir: "."
|
|
75
109
|
)
|
|
76
|
-
output_type =
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
110
|
+
output_type = if flags.include?(:shared) then :shared
|
|
111
|
+
elsif flags.include?(:static) then :static
|
|
112
|
+
else :executable
|
|
113
|
+
end
|
|
114
|
+
output_path = apply_default_extension(output_path, output_type)
|
|
80
115
|
|
|
81
|
-
input_files = Array(input_files)
|
|
82
116
|
flags = translate_flags(flags)
|
|
83
117
|
flags.concat(xflags[@toolchain.class] || [])
|
|
84
118
|
|
|
85
|
-
|
|
86
|
-
|
|
119
|
+
cmds = @toolchain.compile_and_link_commands(
|
|
120
|
+
input_files,
|
|
121
|
+
output_path,
|
|
122
|
+
flags:,
|
|
123
|
+
include_paths:,
|
|
124
|
+
defs:,
|
|
125
|
+
libs:,
|
|
126
|
+
link_paths:
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
cmds.each { |cmd| run_command(cmd, env:, working_dir:) }
|
|
130
|
+
output_path
|
|
87
131
|
end
|
|
88
132
|
|
|
89
133
|
private
|
|
@@ -93,15 +137,8 @@ module MetaCC
|
|
|
93
137
|
toolchain = toolchain_class.new(search_paths:)
|
|
94
138
|
return toolchain if toolchain.available?
|
|
95
139
|
end
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
140
|
+
candidate_names = candidates.map { |candidate| candidate.name.split("::").last }
|
|
141
|
+
raise ToolchainNotFoundError, "no supported C/C++ toolchain found (tried #{candidate_names.join(", ")})"
|
|
105
142
|
end
|
|
106
143
|
|
|
107
144
|
def apply_default_extension(path, output_type)
|
|
@@ -121,7 +158,8 @@ module MetaCC
|
|
|
121
158
|
end
|
|
122
159
|
|
|
123
160
|
def run_command(cmd, env: {}, working_dir: ".")
|
|
124
|
-
|
|
161
|
+
_out, err, status = Open3.capture3(env, *cmd, chdir: working_dir)
|
|
162
|
+
raise CompileError, err unless status.success?
|
|
125
163
|
end
|
|
126
164
|
|
|
127
165
|
end
|
data/lib/metacc/toolchain.rb
CHANGED
|
@@ -17,7 +17,7 @@ module MetaCC
|
|
|
17
17
|
@search_paths = search_paths
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# Returns true if this toolchain's primary compiler
|
|
20
|
+
# Returns true if this toolchain's primary compiler can be found
|
|
21
21
|
def available?
|
|
22
22
|
command_available?(c)
|
|
23
23
|
end
|
|
@@ -26,14 +26,12 @@ module MetaCC
|
|
|
26
26
|
# The default implementation returns [:c, :cxx]. Subclasses that only
|
|
27
27
|
# support a subset of languages should override this method.
|
|
28
28
|
def languages
|
|
29
|
-
[
|
|
29
|
+
%i[c cxx]
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Returns true if +command+ is present in PATH, false otherwise.
|
|
33
33
|
# Intentionally ignores the exit status – only ENOENT (not found) matters.
|
|
34
34
|
def command_available?(command)
|
|
35
|
-
return false if command.nil?
|
|
36
|
-
|
|
37
35
|
!system(command, "--version", out: File::NULL, err: File::NULL).nil?
|
|
38
36
|
end
|
|
39
37
|
|
|
@@ -44,14 +42,31 @@ module MetaCC
|
|
|
44
42
|
|
|
45
43
|
# Returns a Hash mapping universal flags to native flags for this toolchain.
|
|
46
44
|
def flags
|
|
47
|
-
raise
|
|
45
|
+
raise "#{self.class}#flags not implemented"
|
|
48
46
|
end
|
|
49
47
|
|
|
50
48
|
# Returns the full command array for the given inputs, output, and flags.
|
|
51
49
|
# The output mode (object files, shared library, static library, or
|
|
52
50
|
# executable) is determined by the translated flags.
|
|
53
|
-
def
|
|
54
|
-
|
|
51
|
+
def compile_command(
|
|
52
|
+
input_files,
|
|
53
|
+
flags:,
|
|
54
|
+
include_paths:,
|
|
55
|
+
defs:
|
|
56
|
+
)
|
|
57
|
+
raise "#{self.class}#command not implemented"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def compile_and_link_commands(
|
|
61
|
+
input_files,
|
|
62
|
+
output_file,
|
|
63
|
+
flags:,
|
|
64
|
+
include_paths:,
|
|
65
|
+
defs:,
|
|
66
|
+
link_paths:,
|
|
67
|
+
libs:
|
|
68
|
+
)
|
|
69
|
+
raise "#{self.class}#command not implemented"
|
|
55
70
|
end
|
|
56
71
|
|
|
57
72
|
# Returns the default file extension (with leading dot, e.g. ".o") for the
|
|
@@ -102,57 +117,73 @@ module MetaCC
|
|
|
102
117
|
# GNU-compatible toolchain (gcc).
|
|
103
118
|
class GNU < Toolchain
|
|
104
119
|
|
|
105
|
-
def initialize(search_paths: [])
|
|
106
|
-
super
|
|
107
|
-
@c
|
|
120
|
+
def initialize(cc_command = "gcc", search_paths: [])
|
|
121
|
+
super(search_paths:)
|
|
122
|
+
@c = resolve_command(cc_command)
|
|
108
123
|
end
|
|
109
124
|
|
|
110
|
-
def
|
|
125
|
+
def compile_command(
|
|
126
|
+
input_files,
|
|
127
|
+
flags:,
|
|
128
|
+
include_paths:,
|
|
129
|
+
defs:
|
|
130
|
+
)
|
|
111
131
|
inc_flags = include_paths.map { |p| "-I#{p}" }
|
|
112
|
-
def_flags =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
def_flags = defs.map { |d| "-D#{d}" }
|
|
133
|
+
[c, "-c", *flags, *inc_flags, *def_flags, *input_files]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def compile_and_link_commands(
|
|
137
|
+
input_files,
|
|
138
|
+
output_file,
|
|
139
|
+
flags:,
|
|
140
|
+
include_paths:,
|
|
141
|
+
defs:,
|
|
142
|
+
link_paths:,
|
|
143
|
+
libs:
|
|
144
|
+
)
|
|
145
|
+
inc_flags = include_paths.map { |p| "-I#{p}" }
|
|
146
|
+
def_flags = defs.map { |d| "-D#{d}" }
|
|
147
|
+
lib_path_flags = link_paths.map { |p| "-L#{p}" }
|
|
148
|
+
lib_flags = libs.map { |l| "-l#{l}" }
|
|
149
|
+
[[c, *flags, *inc_flags, *def_flags, *input_files, *lib_path_flags, *lib_flags, "-o", output_file]]
|
|
118
150
|
end
|
|
119
151
|
|
|
120
152
|
GNU_FLAGS = {
|
|
121
|
-
o0:
|
|
122
|
-
o1:
|
|
123
|
-
o2:
|
|
124
|
-
o3:
|
|
125
|
-
os:
|
|
126
|
-
sse4_2:
|
|
127
|
-
avx:
|
|
128
|
-
avx2:
|
|
129
|
-
avx512:
|
|
130
|
-
native:
|
|
131
|
-
debug:
|
|
132
|
-
lto:
|
|
133
|
-
warn_all:
|
|
134
|
-
warn_error:
|
|
135
|
-
c11:
|
|
136
|
-
c17:
|
|
137
|
-
c23:
|
|
138
|
-
cxx11:
|
|
139
|
-
cxx14:
|
|
140
|
-
cxx17:
|
|
141
|
-
cxx20:
|
|
142
|
-
cxx23:
|
|
143
|
-
cxx26:
|
|
144
|
-
asan:
|
|
145
|
-
ubsan:
|
|
146
|
-
msan:
|
|
153
|
+
o0: ["-O0"],
|
|
154
|
+
o1: ["-O1"],
|
|
155
|
+
o2: ["-O2"],
|
|
156
|
+
o3: ["-O3"],
|
|
157
|
+
os: ["-Os"],
|
|
158
|
+
sse4_2: ["-march=x86-64-v2"], # This is a better match for /arch:SSE4.2 than -msse4_2 is
|
|
159
|
+
avx: ["-march=x86-64-v2", "-mavx"],
|
|
160
|
+
avx2: ["-march=x86-64-v3"], # This is a better match for /arch:AVX2 than -mavx2 is
|
|
161
|
+
avx512: ["-march=x86-64-v4"],
|
|
162
|
+
native: ["-march=native", "-mtune=native"],
|
|
163
|
+
debug: ["-g3"],
|
|
164
|
+
lto: ["-flto"],
|
|
165
|
+
warn_all: ["-Wall", "-Wextra", "-pedantic"],
|
|
166
|
+
warn_error: ["-Werror"],
|
|
167
|
+
c11: ["-std=c11"],
|
|
168
|
+
c17: ["-std=c17"],
|
|
169
|
+
c23: ["-std=c23"],
|
|
170
|
+
cxx11: ["-std=c++11"],
|
|
171
|
+
cxx14: ["-std=c++14"],
|
|
172
|
+
cxx17: ["-std=c++17"],
|
|
173
|
+
cxx20: ["-std=c++20"],
|
|
174
|
+
cxx23: ["-std=c++23"],
|
|
175
|
+
cxx26: ["-std=c++2c"],
|
|
176
|
+
asan: ["-fsanitize=address"],
|
|
177
|
+
ubsan: ["-fsanitize=undefined"],
|
|
178
|
+
msan: ["-fsanitize=memory"],
|
|
147
179
|
no_rtti: ["-fno-rtti"],
|
|
148
180
|
no_exceptions: ["-fno-exceptions", "-fno-unwind-tables"],
|
|
149
181
|
pic: ["-fPIC"],
|
|
150
182
|
no_semantic_interposition: ["-fno-semantic-interposition"],
|
|
151
183
|
no_omit_frame_pointer: ["-fno-omit-frame-pointer"],
|
|
152
184
|
no_strict_aliasing: ["-fno-strict-aliasing"],
|
|
153
|
-
objects: ["-c"],
|
|
154
185
|
shared: ["-shared"],
|
|
155
|
-
static: ["-
|
|
186
|
+
static: ["-static"],
|
|
156
187
|
strip: ["-Wl,--strip-unneeded"]
|
|
157
188
|
}.freeze
|
|
158
189
|
|
|
@@ -166,8 +197,7 @@ module MetaCC
|
|
|
166
197
|
class Clang < GNU
|
|
167
198
|
|
|
168
199
|
def initialize(search_paths: [])
|
|
169
|
-
super
|
|
170
|
-
@c = resolve_command("clang")
|
|
200
|
+
super("clang", search_paths:)
|
|
171
201
|
end
|
|
172
202
|
|
|
173
203
|
CLANG_FLAGS = GNU_FLAGS.merge(lto: ["-flto=thin"]).freeze
|
|
@@ -190,24 +220,37 @@ module MetaCC
|
|
|
190
220
|
def initialize(cl_command = "cl", search_paths: [])
|
|
191
221
|
super(search_paths:)
|
|
192
222
|
resolved_cmd = resolve_command(cl_command)
|
|
193
|
-
@c
|
|
223
|
+
@c = resolved_cmd
|
|
194
224
|
setup_msvc_environment(resolved_cmd)
|
|
195
225
|
end
|
|
196
226
|
|
|
197
|
-
def
|
|
227
|
+
def compile_command(
|
|
228
|
+
input_files,
|
|
229
|
+
flags:,
|
|
230
|
+
include_paths:,
|
|
231
|
+
defs:
|
|
232
|
+
)
|
|
198
233
|
inc_flags = include_paths.map { |p| "/I#{p}" }
|
|
199
|
-
def_flags =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
234
|
+
def_flags = defs.map { |d| "/D#{d}" }
|
|
235
|
+
[c, "/c", *flags, *inc_flags, *def_flags, *input_files]
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def compile_and_link_commands(
|
|
239
|
+
input_files,
|
|
240
|
+
output_file,
|
|
241
|
+
flags:,
|
|
242
|
+
include_paths:,
|
|
243
|
+
defs:,
|
|
244
|
+
link_paths:,
|
|
245
|
+
libs:
|
|
246
|
+
)
|
|
247
|
+
inc_flags = include_paths.map { |p| "/I#{p}" }
|
|
248
|
+
def_flags = defs.map { |d| "/D#{d}" }
|
|
249
|
+
lib_flags = libs.map { |l| "#{l}.lib" }
|
|
250
|
+
lib_path_flags = link_paths.map { |p| "/LIBPATH:#{p}" }
|
|
251
|
+
cmd = [c, *flags, *inc_flags, *def_flags, *input_files, *lib_flags, "/Fe#{output_file}"]
|
|
252
|
+
cmd += ["/link", *lib_path_flags] unless lib_path_flags.empty?
|
|
253
|
+
[cmd]
|
|
211
254
|
end
|
|
212
255
|
|
|
213
256
|
MSVC_FLAGS = {
|
|
@@ -243,7 +286,6 @@ module MetaCC
|
|
|
243
286
|
no_semantic_interposition: [],
|
|
244
287
|
no_omit_frame_pointer: ["/Oy-"],
|
|
245
288
|
no_strict_aliasing: [],
|
|
246
|
-
objects: ["/c"],
|
|
247
289
|
shared: ["/LD"],
|
|
248
290
|
static: ["/c"],
|
|
249
291
|
strip: []
|
|
@@ -285,70 +327,55 @@ module MetaCC
|
|
|
285
327
|
def setup_msvc_environment(cl_command)
|
|
286
328
|
return if command_available?(cl_command)
|
|
287
329
|
|
|
288
|
-
devenv_path =
|
|
289
|
-
|
|
330
|
+
devenv_path = MSVC.vswhere("-path", "-property", "productPath") ||
|
|
331
|
+
MSVC.vswhere("-latest", "-prerelease", "-property", "productPath")
|
|
290
332
|
return unless devenv_path
|
|
291
333
|
|
|
292
|
-
vcvarsall
|
|
293
|
-
return unless vcvarsall
|
|
294
|
-
|
|
295
|
-
run_vcvarsall(vcvarsall)
|
|
334
|
+
MSVC.vcvarsall(devenv_path)
|
|
296
335
|
end
|
|
297
336
|
|
|
298
337
|
# Runs vswhere.exe with the given arguments and returns the trimmed stdout,
|
|
299
338
|
# or nil if vswhere.exe is absent, the command fails, or produces no output.
|
|
300
|
-
def
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
stdout = IO.popen([VSWHERE_PATH, *args], &:read)
|
|
339
|
+
def self.vswhere(*args)
|
|
340
|
+
path = IO.popen([VSWHERE_PATH, *args], &:read).strip
|
|
304
341
|
status = $?
|
|
305
|
-
return nil unless status.success?
|
|
306
342
|
|
|
307
|
-
path
|
|
308
|
-
path.empty? ? nil : path
|
|
343
|
+
status.success? && !path.empty? ? path : nil
|
|
309
344
|
rescue Errno::ENOENT
|
|
310
345
|
nil
|
|
311
346
|
end
|
|
312
347
|
|
|
313
|
-
#
|
|
348
|
+
# Runs vcvarsall.bat for the x64 architecture and merges the resulting
|
|
349
|
+
# environment variables into the current process's ENV so that cl.exe
|
|
350
|
+
# and related tools become available on PATH.
|
|
351
|
+
#
|
|
352
|
+
# Finds the path to vcvarsall.bat for the given devenv.exe path, or nil
|
|
314
353
|
# if it cannot be located. devenv.exe lives at:
|
|
315
354
|
# <root>\Common7\IDE\devenv.exe
|
|
316
355
|
# vcvarsall.bat lives at:
|
|
317
356
|
# <root>\VC\Auxiliary\Build\vcvarsall.bat
|
|
318
|
-
|
|
357
|
+
#
|
|
358
|
+
# Parses the output of `vcvarsall.bat … && set` and merges the resulting
|
|
359
|
+
# environment variables into the current process's ENV.
|
|
360
|
+
def MSVC.vcvarsall(devenv_path)
|
|
361
|
+
# See https://stackoverflow.com/a/19929778
|
|
362
|
+
return if ENV.has_key?("DevEnvDir")
|
|
363
|
+
|
|
364
|
+
# Calculate the location of vcvarsall.bat
|
|
319
365
|
install_root = File.expand_path("../../..", devenv_path)
|
|
366
|
+
|
|
367
|
+
# Check if a file is actually present there
|
|
320
368
|
vcvarsall = File.join(install_root, "VC", "Auxiliary", "Build", "vcvarsall.bat")
|
|
321
|
-
File.exist?(vcvarsall)
|
|
322
|
-
end
|
|
369
|
+
return unless File.exist?(vcvarsall)
|
|
323
370
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
# and related tools become available on PATH.
|
|
327
|
-
def run_vcvarsall(vcvarsall)
|
|
328
|
-
stdout = IO.popen(["cmd.exe", "/c", vcvarsall_command(vcvarsall)], &:read)
|
|
371
|
+
# Run vcvarsall.bat and dump the environment to the shell
|
|
372
|
+
output = `"#{vcvarsall}" x64 && set`
|
|
329
373
|
status = $?
|
|
330
374
|
return unless status.success?
|
|
331
375
|
|
|
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
376
|
output.each_line do |line|
|
|
350
|
-
key,
|
|
351
|
-
next if
|
|
377
|
+
key, value = line.chomp.split("=", 2)
|
|
378
|
+
next if value.to_s.empty?
|
|
352
379
|
|
|
353
380
|
ENV[key] = value
|
|
354
381
|
end
|
|
@@ -376,11 +403,19 @@ module MetaCC
|
|
|
376
403
|
end
|
|
377
404
|
|
|
378
405
|
# TinyCC toolchain (tcc). TinyCC only supports C, not C++.
|
|
379
|
-
class TinyCC <
|
|
406
|
+
class TinyCC < GNU
|
|
380
407
|
|
|
381
408
|
def initialize(search_paths: [])
|
|
382
|
-
super
|
|
383
|
-
@
|
|
409
|
+
super("tcc", search_paths:)
|
|
410
|
+
@ar = resolve_command("ar")
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def compile_and_link_commands(input_files, output_file, **options)
|
|
414
|
+
commands = super(input_files, output_file, **options)
|
|
415
|
+
if options[:flags].include?(:static)
|
|
416
|
+
object_files = input_files.map { |f| f.sub(/\.c\z/, ".o") }
|
|
417
|
+
commands << [@ar, "rcs", output_file, *object_files]
|
|
418
|
+
end
|
|
384
419
|
end
|
|
385
420
|
|
|
386
421
|
# TinyCC does not support C++.
|
|
@@ -388,14 +423,8 @@ module MetaCC
|
|
|
388
423
|
[:c]
|
|
389
424
|
end
|
|
390
425
|
|
|
391
|
-
def
|
|
392
|
-
|
|
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]
|
|
426
|
+
def version_banner
|
|
427
|
+
IO.popen([c, "-v", { err: :out }], &:read)
|
|
399
428
|
end
|
|
400
429
|
|
|
401
430
|
TINYCC_FLAGS = {
|
|
@@ -431,7 +460,6 @@ module MetaCC
|
|
|
431
460
|
no_semantic_interposition: [],
|
|
432
461
|
no_omit_frame_pointer: [],
|
|
433
462
|
no_strict_aliasing: [],
|
|
434
|
-
objects: ["-c"],
|
|
435
463
|
shared: ["-shared"],
|
|
436
464
|
static: ["-c"],
|
|
437
465
|
strip: []
|
data/lib/metacc.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: metacc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Praneeth Sadda
|
|
@@ -23,9 +23,13 @@ files:
|
|
|
23
23
|
- lib/metacc/cli.rb
|
|
24
24
|
- lib/metacc/driver.rb
|
|
25
25
|
- lib/metacc/toolchain.rb
|
|
26
|
+
- lib/metacc/version.rb
|
|
27
|
+
homepage: https://github.com/psadda/metacc
|
|
26
28
|
licenses:
|
|
27
29
|
- BSD-3-Clause
|
|
28
30
|
metadata:
|
|
31
|
+
homepage_uri: https://github.com/psadda/metacc
|
|
32
|
+
source_code_uri: https://github.com/psadda/metacc
|
|
29
33
|
rubygems_mfa_required: 'true'
|
|
30
34
|
rdoc_options: []
|
|
31
35
|
require_paths:
|
|
@@ -34,7 +38,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
34
38
|
requirements:
|
|
35
39
|
- - ">="
|
|
36
40
|
- !ruby/object:Gem::Version
|
|
37
|
-
version:
|
|
41
|
+
version: 3.2.0
|
|
38
42
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
39
43
|
requirements:
|
|
40
44
|
- - ">="
|