rubycc 1.0.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/CHANGELOG.md +56 -0
- data/LICENSE.txt +21 -0
- data/NOTICE +52 -0
- data/README.md +208 -0
- data/data/README.md +117 -0
- data/data/r10_corpus_scan.json +4082 -0
- data/data/r10_manual_classification.json +5342 -0
- data/data/r10_verification_m4a.json +1531 -0
- data/data/r10_verification_m4b.json +1818 -0
- data/data/r10_verification_m4c.json +1489 -0
- data/data/r10_verification_m4d.json +318 -0
- data/data/r10_verification_rbs.json +192 -0
- data/data/verified_gems.json +397 -0
- data/exe/rmake +16 -0
- data/exe/rubycc +9 -0
- data/exe/rubycc-ar +114 -0
- data/exe/rubycc-doctor +14 -0
- data/exe/rubycc-pkgconf +8 -0
- data/include/float.h +87 -0
- data/include/iso646.h +18 -0
- data/include/libc/alloca.h +18 -0
- data/include/libc/arpa/inet.h +61 -0
- data/include/libc/assert.h +43 -0
- data/include/libc/dirent.h +72 -0
- data/include/libc/dlfcn.h +58 -0
- data/include/libc/features.h +205 -0
- data/include/libc/glibc/aarch64/ctype.h +108 -0
- data/include/libc/glibc/aarch64/endian.h +54 -0
- data/include/libc/glibc/aarch64/errno.h +154 -0
- data/include/libc/glibc/aarch64/fcntl.h +144 -0
- data/include/libc/glibc/aarch64/inttypes.h +182 -0
- data/include/libc/glibc/aarch64/limits.h +63 -0
- data/include/libc/glibc/aarch64/pthread.h +131 -0
- data/include/libc/glibc/aarch64/setjmp.h +84 -0
- data/include/libc/glibc/aarch64/stdint.h +174 -0
- data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
- data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
- data/include/libc/glibc/aarch64/sys/select.h +75 -0
- data/include/libc/glibc/aarch64/sys/stat.h +136 -0
- data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
- data/include/libc/glibc/aarch64/sys/time.h +74 -0
- data/include/libc/glibc/aarch64/sys/types.h +127 -0
- data/include/libc/glibc/aarch64/time.h +113 -0
- data/include/libc/glibc/x86_64/ctype.h +108 -0
- data/include/libc/glibc/x86_64/endian.h +54 -0
- data/include/libc/glibc/x86_64/errno.h +154 -0
- data/include/libc/glibc/x86_64/fcntl.h +134 -0
- data/include/libc/glibc/x86_64/inttypes.h +182 -0
- data/include/libc/glibc/x86_64/limits.h +61 -0
- data/include/libc/glibc/x86_64/pthread.h +111 -0
- data/include/libc/glibc/x86_64/setjmp.h +82 -0
- data/include/libc/glibc/x86_64/stdint.h +163 -0
- data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
- data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
- data/include/libc/glibc/x86_64/sys/select.h +75 -0
- data/include/libc/glibc/x86_64/sys/stat.h +130 -0
- data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
- data/include/libc/glibc/x86_64/sys/time.h +74 -0
- data/include/libc/glibc/x86_64/sys/types.h +123 -0
- data/include/libc/glibc/x86_64/time.h +113 -0
- data/include/libc/grp.h +50 -0
- data/include/libc/langinfo.h +126 -0
- data/include/libc/link.h +18 -0
- data/include/libc/locale.h +83 -0
- data/include/libc/math.h +191 -0
- data/include/libc/netinet/in.h +126 -0
- data/include/libc/netinet/tcp.h +44 -0
- data/include/libc/poll.h +46 -0
- data/include/libc/pwd.h +58 -0
- data/include/libc/regex.h +53 -0
- data/include/libc/sched.h +35 -0
- data/include/libc/signal.h +204 -0
- data/include/libc/stdio.h +157 -0
- data/include/libc/stdlib.h +92 -0
- data/include/libc/string.h +74 -0
- data/include/libc/strings.h +25 -0
- data/include/libc/sys/cdefs.h +126 -0
- data/include/libc/sys/inotify.h +111 -0
- data/include/libc/sys/ioctl.h +36 -0
- data/include/libc/sys/mman.h +65 -0
- data/include/libc/sys/param.h +41 -0
- data/include/libc/sys/resource.h +109 -0
- data/include/libc/sys/socket.h +167 -0
- data/include/libc/sys/statfs.h +82 -0
- data/include/libc/sys/timerfd.h +55 -0
- data/include/libc/sys/uio.h +40 -0
- data/include/libc/sys/un.h +25 -0
- data/include/libc/sys/utsname.h +35 -0
- data/include/libc/sys/wait.h +135 -0
- data/include/libc/termios.h +179 -0
- data/include/libc/unistd.h +194 -0
- data/include/stdalign.h +16 -0
- data/include/stdarg.h +31 -0
- data/include/stdatomic.h +158 -0
- data/include/stdbool.h +15 -0
- data/include/stdckdint.h +28 -0
- data/include/stddef.h +60 -0
- data/include/stdnoreturn.h +18 -0
- data/include/x86intrin.h +16 -0
- data/lib/rubycc/backend/aarch64.rb +1724 -0
- data/lib/rubycc/backend/x86_64.rb +1369 -0
- data/lib/rubycc/compile_error.rb +36 -0
- data/lib/rubycc/compiler.rb +305 -0
- data/lib/rubycc/doctor/builder.rb +151 -0
- data/lib/rubycc/doctor/cli.rb +190 -0
- data/lib/rubycc/doctor/fetcher.rb +84 -0
- data/lib/rubycc/doctor/gemfile.rb +115 -0
- data/lib/rubycc/doctor/verified_gems.rb +114 -0
- data/lib/rubycc/doctor.rb +16 -0
- data/lib/rubycc/driver.rb +463 -0
- data/lib/rubycc/front/ast.rb +528 -0
- data/lib/rubycc/front/constant_evaluator.rb +631 -0
- data/lib/rubycc/front/initializer_resolver.rb +592 -0
- data/lib/rubycc/front/lexeme_reader.rb +460 -0
- data/lib/rubycc/front/lexer.rb +232 -0
- data/lib/rubycc/front/parser.rb +4122 -0
- data/lib/rubycc/front/token.rb +49 -0
- data/lib/rubycc/ir/call_convention.rb +486 -0
- data/lib/rubycc/ir/generator.rb +6036 -0
- data/lib/rubycc/ir/ir.rb +417 -0
- data/lib/rubycc/link/compat_runtime.rb +89 -0
- data/lib/rubycc/link/errors.rb +13 -0
- data/lib/rubycc/link/executable_linker.rb +405 -0
- data/lib/rubycc/link/library_resolver.rb +437 -0
- data/lib/rubycc/link/partial_linker.rb +546 -0
- data/lib/rubycc/link/shared_linker.rb +1732 -0
- data/lib/rubycc/mkmf_shim.rb +83 -0
- data/lib/rubycc/objfile/ar_archive.rb +437 -0
- data/lib/rubycc/objfile/elf_reader.rb +646 -0
- data/lib/rubycc/objfile/elf_writer.rb +891 -0
- data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
- data/lib/rubycc/pkgconf/cli.rb +139 -0
- data/lib/rubycc/pkgconf/errors.rb +39 -0
- data/lib/rubycc/pkgconf/model.rb +28 -0
- data/lib/rubycc/pkgconf/parser.rb +109 -0
- data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
- data/lib/rubycc/pkgconf/resolver.rb +70 -0
- data/lib/rubycc/pkgconf/search_path.rb +38 -0
- data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
- data/lib/rubycc/preprocess/constant_expression.rb +219 -0
- data/lib/rubycc/preprocess/glibc_version.rb +124 -0
- data/lib/rubycc/preprocess/pp_token.rb +62 -0
- data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
- data/lib/rubycc/preprocess/scanner.rb +290 -0
- data/lib/rubycc/preprocess/token_converter.rb +157 -0
- data/lib/rubycc/rmake/cli.rb +143 -0
- data/lib/rubycc/rmake/errors.rb +71 -0
- data/lib/rubycc/rmake/executor.rb +818 -0
- data/lib/rubycc/rmake/expander.rb +251 -0
- data/lib/rubycc/rmake/makefile.rb +352 -0
- data/lib/rubycc/rmake/model.rb +137 -0
- data/lib/rubycc/rmake/parser.rb +226 -0
- data/lib/rubycc/rmake/rmake.rb +15 -0
- data/lib/rubycc/type.rb +1236 -0
- data/lib/rubycc/version.rb +5 -0
- data/lib/rubycc.rb +36 -0
- data/lib/rubygems_plugin.rb +102 -0
- metadata +219 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module ObjFile
|
|
5
|
+
# Writes a *general* ELF64 relocatable object (ET_REL) for Linux x86_64:
|
|
6
|
+
# arbitrary named sections, an arbitrary symbol table, and full SHT_RELA
|
|
7
|
+
# relocation tables carrying any relocation type and addend.
|
|
8
|
+
#
|
|
9
|
+
# It is the counterpart the linker's `ld -r` core needs and deliberately
|
|
10
|
+
# NOT the compiler's ELFWriter. That writer serves a single translation
|
|
11
|
+
# unit through a fixed menu — .text/.rodata/.data/.bss and three baked-in
|
|
12
|
+
# relocation kinds — because a compiler always emits the same shape. A
|
|
13
|
+
# merged object is open-ended: it may hold any section the inputs carried
|
|
14
|
+
# (.comment, .note.*, .eh_frame, ...), section symbols pointing at merged
|
|
15
|
+
# sections, and relocations of any numeric type against any symbol with any
|
|
16
|
+
# addend. Rather than contort the compiler's writer, this one accepts those
|
|
17
|
+
# pieces directly and lays them out.
|
|
18
|
+
#
|
|
19
|
+
# The API is build-then-emit: #add_section, #add_symbol and #add_relocation
|
|
20
|
+
# return opaque handles (the value structs below) that later calls reference
|
|
21
|
+
# by identity, so the caller never computes on-disk indices itself. #to_binary
|
|
22
|
+
# fixes the section order (NULL, the content sections in insertion order, one
|
|
23
|
+
# .rela.<name> per relocated target, then .symtab/.strtab/.shstrtab), resolves
|
|
24
|
+
# every cross-reference (symbol st_shndx, rela sh_link/sh_info, r_info's symbol
|
|
25
|
+
# index) against that order, and concatenates the image.
|
|
26
|
+
#
|
|
27
|
+
# Two invariants the caller relies on: symbols are emitted with every
|
|
28
|
+
# STB_LOCAL before the first non-local (a stable partition of insertion
|
|
29
|
+
# order, so relocation handles stay valid) and sh_info of .symtab lands on
|
|
30
|
+
# the first global; output is fully deterministic (N4) — identical build
|
|
31
|
+
# calls yield byte-identical bytes, with no timestamps embedded.
|
|
32
|
+
class RelocatableWriter
|
|
33
|
+
ELFCLASS64 = 2
|
|
34
|
+
ELFDATA2LSB = 1
|
|
35
|
+
EV_CURRENT = 1
|
|
36
|
+
ET_REL = 1
|
|
37
|
+
EM_X86_64 = 62
|
|
38
|
+
EM_AARCH64 = 183
|
|
39
|
+
|
|
40
|
+
SHN_UNDEF = 0
|
|
41
|
+
SHN_ABS = 0xFFF1
|
|
42
|
+
|
|
43
|
+
SHT_SYMTAB = 2
|
|
44
|
+
SHT_STRTAB = 3
|
|
45
|
+
SHT_RELA = 4
|
|
46
|
+
SHT_NOBITS = 8
|
|
47
|
+
|
|
48
|
+
SHF_INFO_LINK = 0x40
|
|
49
|
+
|
|
50
|
+
# Symbolic bind/type/visibility accepted from the caller, mapped to their
|
|
51
|
+
# st_info / st_other encodings. A caller may also pass the raw integer,
|
|
52
|
+
# which passes through unchanged.
|
|
53
|
+
BINDINGS = { local: 0, global: 1, weak: 2 }.freeze
|
|
54
|
+
TYPES = { notype: 0, object: 1, func: 2, section: 3, file: 4, tls: 6, ifunc: 10 }.freeze
|
|
55
|
+
VISIBILITIES = { default: 0, internal: 1, hidden: 2, protected: 3 }.freeze
|
|
56
|
+
|
|
57
|
+
SYM_ENTSIZE = 24
|
|
58
|
+
RELA_ENTSIZE = 24
|
|
59
|
+
SHDR_ENTSIZE = 64
|
|
60
|
+
EHDR_SIZE = 64
|
|
61
|
+
|
|
62
|
+
# A section to emit. `data` holds the file bytes for a non-NOBITS section;
|
|
63
|
+
# a NOBITS section (.bss) carries `size` instead and occupies no file
|
|
64
|
+
# space. `index` is filled in during layout.
|
|
65
|
+
Section = Struct.new(
|
|
66
|
+
:name, :type, :flags, :addralign, :entsize, :data, :size, :index,
|
|
67
|
+
keyword_init: true
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# A symbol to emit. `section` (a Section handle) names the section that
|
|
71
|
+
# defines it, in which case st_shndx is that section's index; otherwise
|
|
72
|
+
# `shndx` carries a reserved value (SHN_UNDEF / SHN_ABS). `index` is filled
|
|
73
|
+
# in during layout.
|
|
74
|
+
Symbol = Struct.new(
|
|
75
|
+
:name, :bind, :type, :visibility, :section, :shndx, :value, :size, :index,
|
|
76
|
+
keyword_init: true
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# A relocation to emit into the .rela table of `target`. `symbol` is a
|
|
80
|
+
# Symbol handle; `type` is the numeric x86_64 relocation type.
|
|
81
|
+
Relocation = Struct.new(:target, :offset, :symbol, :type, :addend, keyword_init: true)
|
|
82
|
+
|
|
83
|
+
# `machine` is the ELF e_machine value stamped into the output header. It
|
|
84
|
+
# defaults to x86_64 so an existing caller is unaffected; the partial-link
|
|
85
|
+
# merge passes the inputs' own machine through so a merged aarch64 object
|
|
86
|
+
# keeps its EM_AARCH64 identity (the final linker reads it back to pick the
|
|
87
|
+
# target's relocation and crt logic).
|
|
88
|
+
def initialize(machine: EM_X86_64)
|
|
89
|
+
@machine = machine
|
|
90
|
+
@sections = []
|
|
91
|
+
# The reserved null symbol is index 0 by the ELF ABI; expose it so a
|
|
92
|
+
# caller can re-point a "no symbol" relocation at it.
|
|
93
|
+
@null_symbol = Symbol.new(name: nil, bind: 0, type: 0, visibility: 0,
|
|
94
|
+
section: nil, shndx: SHN_UNDEF, value: 0, size: 0)
|
|
95
|
+
@symbols = [@null_symbol]
|
|
96
|
+
@relocations = []
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
attr_reader :null_symbol
|
|
100
|
+
|
|
101
|
+
# Registers a content section. `type`/`flags`/`entsize` are raw ELF values;
|
|
102
|
+
# `data` (bytes) or, for a NOBITS section, `size` gives its extent. Returns
|
|
103
|
+
# the Section handle to reference from symbols and relocations.
|
|
104
|
+
def add_section(name:, type:, flags:, addralign:, entsize: 0, data: nil, size: nil)
|
|
105
|
+
section = Section.new(name: name, type: type, flags: flags,
|
|
106
|
+
addralign: [addralign, 1].max, entsize: entsize,
|
|
107
|
+
data: data&.b, size: size)
|
|
108
|
+
@sections << section
|
|
109
|
+
section
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Registers a symbol. `bind`/`type`/`visibility` may be the symbolic forms
|
|
113
|
+
# (:local, :func, :hidden, ...) or raw integers. Returns the Symbol handle.
|
|
114
|
+
def add_symbol(name:, bind:, type:, visibility: :default, section: nil, shndx: SHN_UNDEF, value: 0, size: 0)
|
|
115
|
+
symbol = Symbol.new(
|
|
116
|
+
name: name, bind: BINDINGS.fetch(bind, bind), type: TYPES.fetch(type, type),
|
|
117
|
+
visibility: VISIBILITIES.fetch(visibility, visibility),
|
|
118
|
+
section: section, shndx: shndx, value: value, size: size
|
|
119
|
+
)
|
|
120
|
+
@symbols << symbol
|
|
121
|
+
symbol
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Records a relocation to emit into `target`'s .rela table.
|
|
125
|
+
def add_relocation(target:, offset:, symbol:, type:, addend:)
|
|
126
|
+
@relocations << Relocation.new(target: target, offset: offset, symbol: symbol,
|
|
127
|
+
type: type, addend: addend)
|
|
128
|
+
self
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Assembles and returns the object as an ASCII-8BIT String.
|
|
132
|
+
def to_binary
|
|
133
|
+
order_symbols
|
|
134
|
+
layout = build_layout
|
|
135
|
+
assemble(layout)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def write(path)
|
|
139
|
+
File.binwrite(path, to_binary)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
# Stable-partitions the symbol table so every STB_LOCAL precedes the first
|
|
145
|
+
# non-local, as the ABI requires, without disturbing relative order within
|
|
146
|
+
# each class. The null symbol (bind 0 = local) naturally stays first.
|
|
147
|
+
def order_symbols
|
|
148
|
+
locals, globals = @symbols.partition { |sym| sym.bind == BINDINGS[:local] }
|
|
149
|
+
@symbols = locals + globals
|
|
150
|
+
@first_global = locals.size
|
|
151
|
+
@symbols.each_with_index { |sym, i| sym.index = i }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# The relocations aimed at `section`, in insertion order — matched by
|
|
155
|
+
# object identity, since a Section struct hashes by value and its :index
|
|
156
|
+
# field is mutated during layout (a value-keyed lookup would then miss).
|
|
157
|
+
def relocations_for(section)
|
|
158
|
+
@relocations.select { |reloc| reloc.target.equal?(section) }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# A section descriptor used only during layout, carrying the payload bytes
|
|
162
|
+
# and the resolved sh_link/sh_info as concrete indices.
|
|
163
|
+
LaidOut = Struct.new(:name, :type, :flags, :addr, :size, :link, :info,
|
|
164
|
+
:addralign, :entsize, :data, :offset, keyword_init: true)
|
|
165
|
+
|
|
166
|
+
# Fixes the on-disk section order and assigns every section its index, then
|
|
167
|
+
# builds the symbol/relocation/string payloads against those indices.
|
|
168
|
+
def build_layout
|
|
169
|
+
# Section order: NULL, the content sections, one .rela per relocated
|
|
170
|
+
# target, then the symbol and string tables. Indices are assigned as this
|
|
171
|
+
# list is built so the cross-references below resolve to concrete values.
|
|
172
|
+
sections = [null_layout]
|
|
173
|
+
index = 1
|
|
174
|
+
@sections.each do |sec|
|
|
175
|
+
sec.index = index
|
|
176
|
+
index += 1
|
|
177
|
+
end
|
|
178
|
+
rela_targets = @sections.select { |sec| relocations_for(sec).any? }
|
|
179
|
+
symtab_index = index + rela_targets.size
|
|
180
|
+
strtab_index = symtab_index + 1
|
|
181
|
+
shstrtab_index = strtab_index + 1
|
|
182
|
+
|
|
183
|
+
strtab, sym_name_offsets = build_strtab
|
|
184
|
+
symtab = build_symtab(sym_name_offsets)
|
|
185
|
+
|
|
186
|
+
@sections.each { |sec| sections << content_layout(sec) }
|
|
187
|
+
rela_targets.each do |target|
|
|
188
|
+
sections << rela_layout(target, relocations_for(target), symtab_index)
|
|
189
|
+
end
|
|
190
|
+
sections << symtab_layout(symtab, strtab_index)
|
|
191
|
+
sections << strtab_layout(strtab)
|
|
192
|
+
sections << shstrtab_layout
|
|
193
|
+
|
|
194
|
+
{ sections: sections, shstrtab_index: shstrtab_index }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def null_layout
|
|
198
|
+
LaidOut.new(name: nil, type: 0, flags: 0, addr: 0, size: 0, link: 0, info: 0,
|
|
199
|
+
addralign: 0, entsize: 0, data: "".b)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# A NOBITS section reports its in-memory size but carries no file bytes; any
|
|
203
|
+
# other section carries its data (nil data means the layout pass skips it).
|
|
204
|
+
def content_layout(sec)
|
|
205
|
+
nobits = sec.type == SHT_NOBITS
|
|
206
|
+
LaidOut.new(
|
|
207
|
+
name: sec.name, type: sec.type, flags: sec.flags, addr: 0,
|
|
208
|
+
size: nobits ? (sec.size || 0) : (sec.data ? sec.data.bytesize : 0),
|
|
209
|
+
link: 0, info: 0, addralign: sec.addralign, entsize: sec.entsize,
|
|
210
|
+
data: nobits ? nil : (sec.data || "".b)
|
|
211
|
+
)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def rela_layout(target, relocs, symtab_index)
|
|
215
|
+
LaidOut.new(
|
|
216
|
+
name: ".rela#{target.name}", type: SHT_RELA, flags: SHF_INFO_LINK, addr: 0,
|
|
217
|
+
size: relocs.size * RELA_ENTSIZE, link: symtab_index, info: target.index,
|
|
218
|
+
addralign: 8, entsize: RELA_ENTSIZE, data: build_rela(relocs)
|
|
219
|
+
)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def symtab_layout(symtab, strtab_index)
|
|
223
|
+
LaidOut.new(
|
|
224
|
+
name: ".symtab", type: SHT_SYMTAB, flags: 0, addr: 0, size: symtab.bytesize,
|
|
225
|
+
link: strtab_index, info: @first_global, addralign: 8, entsize: SYM_ENTSIZE,
|
|
226
|
+
data: symtab
|
|
227
|
+
)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def strtab_layout(strtab)
|
|
231
|
+
LaidOut.new(name: ".strtab", type: SHT_STRTAB, flags: 0, addr: 0,
|
|
232
|
+
size: strtab.bytesize, link: 0, info: 0, addralign: 1, entsize: 0,
|
|
233
|
+
data: strtab)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def shstrtab_layout
|
|
237
|
+
# Filled with real bytes in #assemble, once every section name is known.
|
|
238
|
+
LaidOut.new(name: ".shstrtab", type: SHT_STRTAB, flags: 0, addr: 0, size: 0,
|
|
239
|
+
link: 0, info: 0, addralign: 1, entsize: 0, data: nil)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Builds .strtab (symbol names). Returns [bytes, name -> offset]. Names are
|
|
243
|
+
# de-duplicated so two symbols with the same string share one entry.
|
|
244
|
+
def build_strtab
|
|
245
|
+
buf = +"\0".b
|
|
246
|
+
offsets = {}
|
|
247
|
+
@symbols.each do |sym|
|
|
248
|
+
name = sym.name
|
|
249
|
+
next if name.nil? || name.empty? || offsets.key?(name)
|
|
250
|
+
|
|
251
|
+
offsets[name] = buf.bytesize
|
|
252
|
+
buf << name.b << "\0".b
|
|
253
|
+
end
|
|
254
|
+
[buf, offsets]
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def build_symtab(sym_name_offsets)
|
|
258
|
+
buf = +"".b
|
|
259
|
+
@symbols.each do |sym|
|
|
260
|
+
shndx = sym.section ? sym.section.index : sym.shndx
|
|
261
|
+
name_off = sym.name && !sym.name.empty? ? sym_name_offsets.fetch(sym.name) : 0
|
|
262
|
+
buf << sym_entry(name: name_off, info: (sym.bind << 4) | sym.type,
|
|
263
|
+
other: sym.visibility, shndx: shndx, value: sym.value, size: sym.size)
|
|
264
|
+
end
|
|
265
|
+
buf
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Builds one .rela payload: 24 bytes per entry (r_offset, r_info, r_addend).
|
|
269
|
+
# r_info packs the symbol's resolved table index with the numeric type.
|
|
270
|
+
def build_rela(relocs)
|
|
271
|
+
buf = +"".b
|
|
272
|
+
relocs.each do |reloc|
|
|
273
|
+
r_info = (reloc.symbol.index << 32) | (reloc.type & 0xFFFFFFFF)
|
|
274
|
+
buf << [reloc.offset].pack("Q<") << [r_info].pack("Q<") << [reloc.addend].pack("q<")
|
|
275
|
+
end
|
|
276
|
+
buf
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Computes file offsets, fills in .shstrtab, and concatenates the header,
|
|
280
|
+
# section payloads and section header table.
|
|
281
|
+
def assemble(layout)
|
|
282
|
+
sections = layout[:sections]
|
|
283
|
+
shstrtab, name_offsets = build_shstrtab(sections)
|
|
284
|
+
sections[layout[:shstrtab_index]].data = shstrtab
|
|
285
|
+
sections[layout[:shstrtab_index]].size = shstrtab.bytesize
|
|
286
|
+
|
|
287
|
+
offset = EHDR_SIZE
|
|
288
|
+
sections.each do |sec|
|
|
289
|
+
# A NOBITS section keeps an aligned sh_offset for tooling but does not
|
|
290
|
+
# advance the file cursor; a NULL section (nil-ish) is skipped likewise.
|
|
291
|
+
if sec.type == SHT_NOBITS
|
|
292
|
+
offset = align(offset, [sec.addralign, 1].max)
|
|
293
|
+
sec.offset = offset
|
|
294
|
+
next
|
|
295
|
+
end
|
|
296
|
+
next if sec.data.nil?
|
|
297
|
+
|
|
298
|
+
offset = align(offset, [sec.addralign, 1].max)
|
|
299
|
+
sec.offset = offset
|
|
300
|
+
offset += sec.data.bytesize
|
|
301
|
+
end
|
|
302
|
+
shoff = align(offset, 8)
|
|
303
|
+
|
|
304
|
+
out = +"".b
|
|
305
|
+
out << build_ehdr(shoff, sections.size, layout[:shstrtab_index])
|
|
306
|
+
sections.each do |sec|
|
|
307
|
+
next if sec.data.nil? || sec.type == SHT_NOBITS
|
|
308
|
+
|
|
309
|
+
pad_to(out, sec.offset)
|
|
310
|
+
out << sec.data
|
|
311
|
+
end
|
|
312
|
+
pad_to(out, shoff)
|
|
313
|
+
sections.each { |sec| out << build_shdr(sec, name_offsets) }
|
|
314
|
+
out
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def build_shstrtab(sections)
|
|
318
|
+
buf = +"\0".b
|
|
319
|
+
offsets = {}
|
|
320
|
+
sections.each do |sec|
|
|
321
|
+
name = sec.name
|
|
322
|
+
next if name.nil? || offsets.key?(name)
|
|
323
|
+
|
|
324
|
+
offsets[name] = buf.bytesize
|
|
325
|
+
buf << name.b << "\0".b
|
|
326
|
+
end
|
|
327
|
+
[buf, offsets]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def build_ehdr(shoff, shnum, shstrndx)
|
|
331
|
+
e_ident = [0x7F, 0x45, 0x4C, 0x46, ELFCLASS64, ELFDATA2LSB, EV_CURRENT,
|
|
332
|
+
0, 0, 0, 0, 0, 0, 0, 0, 0].pack("C16")
|
|
333
|
+
e_ident +
|
|
334
|
+
[ET_REL].pack("S<") +
|
|
335
|
+
[@machine].pack("S<") +
|
|
336
|
+
[EV_CURRENT].pack("L<") +
|
|
337
|
+
[0].pack("Q<") + # e_entry
|
|
338
|
+
[0].pack("Q<") + # e_phoff
|
|
339
|
+
[shoff].pack("Q<") + # e_shoff
|
|
340
|
+
[0].pack("L<") + # e_flags
|
|
341
|
+
[EHDR_SIZE].pack("S<") + # e_ehsize
|
|
342
|
+
[0].pack("S<") + # e_phentsize
|
|
343
|
+
[0].pack("S<") + # e_phnum
|
|
344
|
+
[SHDR_ENTSIZE].pack("S<") + # e_shentsize
|
|
345
|
+
[shnum].pack("S<") + # e_shnum
|
|
346
|
+
[shstrndx].pack("S<") # e_shstrndx
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def build_shdr(sec, name_offsets)
|
|
350
|
+
[sec.name ? name_offsets[sec.name] : 0].pack("L<") +
|
|
351
|
+
[sec.type].pack("L<") +
|
|
352
|
+
[sec.flags].pack("Q<") +
|
|
353
|
+
[sec.addr].pack("Q<") +
|
|
354
|
+
[sec.offset || 0].pack("Q<") +
|
|
355
|
+
[sec.size].pack("Q<") +
|
|
356
|
+
[sec.link].pack("L<") +
|
|
357
|
+
[sec.info].pack("L<") +
|
|
358
|
+
[sec.addralign].pack("Q<") +
|
|
359
|
+
[sec.entsize].pack("Q<")
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def sym_entry(name:, info:, other:, shndx:, value:, size:)
|
|
363
|
+
[name].pack("L<") + [info].pack("C") + [other].pack("C") +
|
|
364
|
+
[shndx].pack("S<") + [value].pack("Q<") + [size].pack("Q<")
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def align(value, alignment)
|
|
368
|
+
(value + alignment - 1) / alignment * alignment
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def pad_to(buffer, target_offset)
|
|
372
|
+
buffer << ("\0" * (target_offset - buffer.bytesize)).b if buffer.bytesize < target_offset
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Pkgconf
|
|
5
|
+
PROG = "rubycc-pkgconf"
|
|
6
|
+
|
|
7
|
+
# The exact option vocabulary mkmf's mkmf.rb#pkg_config can invoke
|
|
8
|
+
# $PKGCONFIG with (tallied against this environment's mkmf.rb — see the
|
|
9
|
+
# Step 59 report): `--exists` to probe availability, and, as `--#{option}`
|
|
10
|
+
# for each entry of pkg_config's own *options argument or of its built-in
|
|
11
|
+
# default path, `--modversion`/`--cflags`/`--cflags-only-I`/
|
|
12
|
+
# `--cflags-only-other`/`--libs`/`--libs-only-l`. No other pkg-config
|
|
13
|
+
# option (--static, --print-requires, ...) is in scope.
|
|
14
|
+
KNOWN_OPTIONS = %w[exists modversion cflags cflags-only-I cflags-only-other libs libs-only-l].freeze
|
|
15
|
+
|
|
16
|
+
# Runs the shim for +argv+ and returns the process exit status: 0 on
|
|
17
|
+
# success, 1 if a named module (or one of its Requires) cannot be found,
|
|
18
|
+
# the .pc text is malformed, or an unknown option/no module was given —
|
|
19
|
+
# the same pass/fail contract mkmf's xsystem(... "--exists" ...) and
|
|
20
|
+
# get[...] calls rely on.
|
|
21
|
+
def self.run(argv, stdout: $stdout, stderr: $stderr, resolver: Resolver.new, filter: SystemPathFilter.new)
|
|
22
|
+
Cli.new(argv, stdout: stdout, stderr: stderr, resolver: resolver, filter: filter).run
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Parses argv into requested options + module names and renders exactly
|
|
26
|
+
# the options given, in the order given, onto one line — mirroring how
|
|
27
|
+
# mkmf's pkg_config() invokes $PKGCONFIG with all of a single call's
|
|
28
|
+
# `--#{option}` flags at once and reads back one combined line of output.
|
|
29
|
+
class Cli
|
|
30
|
+
def initialize(argv, stdout:, stderr:, resolver:, filter:)
|
|
31
|
+
@argv = argv
|
|
32
|
+
@out = stdout
|
|
33
|
+
@err = stderr
|
|
34
|
+
@resolver = resolver
|
|
35
|
+
@filter = filter
|
|
36
|
+
@options = []
|
|
37
|
+
@modules = []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def run
|
|
41
|
+
parse_argv
|
|
42
|
+
return 1 if @options.empty?
|
|
43
|
+
|
|
44
|
+
if @modules.empty?
|
|
45
|
+
@err.puts "#{PROG}: no package name specified"
|
|
46
|
+
return 1
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if @options.include?("exists")
|
|
50
|
+
@modules.all? { |name| @resolver.exists?(name) } ? 0 : 1
|
|
51
|
+
else
|
|
52
|
+
render
|
|
53
|
+
end
|
|
54
|
+
rescue PkgconfError => e
|
|
55
|
+
@err.puts "#{PROG}: #{e.message}"
|
|
56
|
+
1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def parse_argv
|
|
62
|
+
@argv.each do |arg|
|
|
63
|
+
if arg.start_with?("--")
|
|
64
|
+
option = arg[2..]
|
|
65
|
+
unless KNOWN_OPTIONS.include?(option)
|
|
66
|
+
@err.puts "#{PROG}: unknown option '#{arg}'"
|
|
67
|
+
@options.clear
|
|
68
|
+
return
|
|
69
|
+
end
|
|
70
|
+
@options << option
|
|
71
|
+
else
|
|
72
|
+
@modules << arg
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def render
|
|
78
|
+
parts = @options.map { |option| render_option(option) }
|
|
79
|
+
@out.puts parts.reject(&:empty?).join(" ")
|
|
80
|
+
0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def render_option(option)
|
|
84
|
+
case option
|
|
85
|
+
when "modversion" then @modules.map { |name| @resolver.load(name).version.to_s }.join(" ")
|
|
86
|
+
when "cflags" then all_cflags.join(" ")
|
|
87
|
+
when "cflags-only-I" then all_cflags.select { |t| t.start_with?("-I") }.join(" ")
|
|
88
|
+
when "cflags-only-other" then all_cflags.reject { |t| t.start_with?("-I") }.join(" ")
|
|
89
|
+
when "libs" then all_libs.join(" ")
|
|
90
|
+
when "libs-only-l" then all_libs.select { |t| t.start_with?("-l") }.join(" ")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Multiple module arguments are concatenated in the order given — the
|
|
95
|
+
# "複数モジュール" case mkmf's pkg_config() call shape allows for.
|
|
96
|
+
#
|
|
97
|
+
# The system-path filter is applied here, once, rather than inside
|
|
98
|
+
# Resolver or per rendered option: Resolver's token lists are what the
|
|
99
|
+
# Requires chain says (the parser-level tests assert exactly that), while
|
|
100
|
+
# every option that renders those tokens — --cflags, --cflags-only-I,
|
|
101
|
+
# --cflags-only-other, --libs, --libs-only-l — has to agree on which of
|
|
102
|
+
# them are suppressed. --cflags-only-other and --libs-only-l are
|
|
103
|
+
# unaffected in practice, since they keep only tokens the filter never
|
|
104
|
+
# touches.
|
|
105
|
+
def all_cflags
|
|
106
|
+
dedupe_search_paths(@filter.cflags(@modules.flat_map { |name| @resolver.cflags_tokens(name) }), "-I")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def all_libs
|
|
110
|
+
dedupe_search_paths(@filter.libs(@modules.flat_map { |name| @resolver.libs_tokens(name) }), "-L")
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Collapse a repeated search-path token to its first occurrence, the way
|
|
114
|
+
# the real pkg-config does. A directory named twice searches the same
|
|
115
|
+
# directory twice, so dropping the later copy cannot change what is found,
|
|
116
|
+
# while keeping the first preserves the order the .pc files asked for.
|
|
117
|
+
#
|
|
118
|
+
# This is not hypothetical tidying: zlib.pc writes
|
|
119
|
+
# `Libs: -L${libdir} -L${sharedlibdir} -lz` and on this environment both
|
|
120
|
+
# variables expand to the same directory, so the shim emitted that -L
|
|
121
|
+
# twice where the real tool emitted it once. It went unnoticed because the
|
|
122
|
+
# system-path filter happened to remove both copies on Debian; measured on
|
|
123
|
+
# Alpine, where that directory is not a system directory and neither copy
|
|
124
|
+
# is removed (docs/development/STEPS.md Step 199).
|
|
125
|
+
#
|
|
126
|
+
# Only search paths are collapsed. `-l` is deliberately left alone: a
|
|
127
|
+
# repeated library can matter to a static link's resolution order, and no
|
|
128
|
+
# measurement here covers it.
|
|
129
|
+
def dedupe_search_paths(tokens, flag)
|
|
130
|
+
seen = {}
|
|
131
|
+
tokens.reject do |token|
|
|
132
|
+
next false unless token.start_with?(flag) && token.length > flag.length
|
|
133
|
+
|
|
134
|
+
seen.key?(token).tap { seen[token] = true }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
# The base error is normally provided by lib/rubycc.rb. Like rmake (M3 B1),
|
|
5
|
+
# Pkgconf is loadable on its own (its only caller so far is the
|
|
6
|
+
# exe/rubycc-pkgconf CLI, not the Rubycc aggregate), so define a stand-in only
|
|
7
|
+
# when the full library has not been required yet.
|
|
8
|
+
Error = Class.new(StandardError) unless defined?(Error)
|
|
9
|
+
|
|
10
|
+
module Pkgconf
|
|
11
|
+
# Base for every error this shim raises: a malformed .pc file, a module
|
|
12
|
+
# that cannot be found on the search path, or a construct outside the
|
|
13
|
+
# subset mkmf's pkg_config() actually calls for (R11/ROADMAP §6 B4).
|
|
14
|
+
class PkgconfError < Rubycc::Error; end
|
|
15
|
+
|
|
16
|
+
# The .pc text used a construct the parser does not accept: an
|
|
17
|
+
# unrecognized line, or a `${var}` reference to a variable that was never
|
|
18
|
+
# assigned (in this file, at the point the reference appears — pkg-config
|
|
19
|
+
# itself requires forward-references to be avoided the same way).
|
|
20
|
+
class ParseError < PkgconfError
|
|
21
|
+
def initialize(message, path:, line_number:)
|
|
22
|
+
super("#{path || '<pc>'}:#{line_number}: #{message}")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# No `<name>.pc` was found on the search path (PKG_CONFIG_PATH, then the
|
|
27
|
+
# default directories) — the same condition mkmf's `pkg-config --exists`
|
|
28
|
+
# probe reports as "not found".
|
|
29
|
+
class NotFoundError < PkgconfError; end
|
|
30
|
+
|
|
31
|
+
# A `Requires`/`Requires.private` entry carried a version comparison
|
|
32
|
+
# (`zlib >= 1.2`-style). mkmf's own `pkg_config(pkg, *options)` never
|
|
33
|
+
# passes a version constraint — the `pkg` argument is always a bare module
|
|
34
|
+
# name — so comparing versions is out of scope here (see the Step 59
|
|
35
|
+
# report for the mkmf.rb tally this is based on); a constraint inside a
|
|
36
|
+
# `Requires` field is diagnosed instead of silently ignored or guessed at.
|
|
37
|
+
class UnsupportedError < PkgconfError; end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Pkgconf
|
|
5
|
+
# One parsed .pc file: the handful of fields mkmf's pkg_config() and its
|
|
6
|
+
# own Requires chain ever touch. `cflags`/`libs`/`libs_private` are the
|
|
7
|
+
# field text after `${var}` expansion, still a raw (unsplit) string —
|
|
8
|
+
# Resolver is the one that shellsplits it into tokens, since only Resolver
|
|
9
|
+
# knows whether a field is being consumed for --cflags or --libs.
|
|
10
|
+
class Package
|
|
11
|
+
attr_reader :name, :description, :version, :requires, :requires_private,
|
|
12
|
+
:cflags, :libs, :libs_private, :path
|
|
13
|
+
|
|
14
|
+
def initialize(name:, description:, version:, requires:, requires_private:,
|
|
15
|
+
cflags:, libs:, libs_private:, path:)
|
|
16
|
+
@name = name
|
|
17
|
+
@description = description
|
|
18
|
+
@version = version
|
|
19
|
+
@requires = requires
|
|
20
|
+
@requires_private = requires_private
|
|
21
|
+
@cflags = cflags
|
|
22
|
+
@libs = libs
|
|
23
|
+
@libs_private = libs_private
|
|
24
|
+
@path = path
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Pkgconf
|
|
5
|
+
# Parses one .pc file's text into a Package. The grammar covered is
|
|
6
|
+
# exactly what the real .pc files this environment ships (zlib.pc,
|
|
7
|
+
# libffi.pc, openssl.pc's Requires chain — see test/fixtures/pkgconfig)
|
|
8
|
+
# and mkmf's own pkg_config() call shape actually use: `#`-comments and
|
|
9
|
+
# blank lines, `name=value` variable assignments, `${name}` expansion
|
|
10
|
+
# inside both variable and field values, and the seven fields
|
|
11
|
+
# Name/Description/Version/Requires/Requires.private/Libs/Libs.private/
|
|
12
|
+
# Cflags. Any other field line (URL:, Conflicts:, ...) is accepted and
|
|
13
|
+
# ignored — mkmf's pkg_config() never reads them — rather than rejected,
|
|
14
|
+
# so a real-world .pc with extra fields still parses.
|
|
15
|
+
class Parser
|
|
16
|
+
VARIABLE_LINE = /\A([A-Za-z_][A-Za-z0-9_.]*)=(.*)\z/
|
|
17
|
+
FIELD_LINE = /\A([A-Za-z][A-Za-z0-9.]*)\s*:\s*(.*)\z/
|
|
18
|
+
VARIABLE_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_.]*)\}/
|
|
19
|
+
|
|
20
|
+
# A single `Requires`/`Requires.private` entry with a version
|
|
21
|
+
# comparison attached, spaced (`zlib >= 1.2`) or not (`zlib>=1.2`).
|
|
22
|
+
REQUIRES_OPERATOR = /\A(<=|>=|==|!=|<|>|=)\z/
|
|
23
|
+
|
|
24
|
+
KNOWN_FIELDS = %w[Name Description Version Requires Requires.private Libs Libs.private Cflags].freeze
|
|
25
|
+
|
|
26
|
+
def self.parse(text, path: nil)
|
|
27
|
+
new(text, path: path).parse
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(text, path: nil)
|
|
31
|
+
@text = text
|
|
32
|
+
@path = path
|
|
33
|
+
@variables = {}
|
|
34
|
+
@fields = {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse
|
|
38
|
+
@text.each_line.with_index(1) do |raw_line, line_number|
|
|
39
|
+
parse_line(raw_line.chomp, line_number)
|
|
40
|
+
end
|
|
41
|
+
build_package
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def parse_line(line, line_number)
|
|
47
|
+
return if line.strip.empty?
|
|
48
|
+
return if line.lstrip.start_with?("#")
|
|
49
|
+
|
|
50
|
+
if (m = VARIABLE_LINE.match(line))
|
|
51
|
+
@variables[m[1]] = expand(m[2].strip, line_number)
|
|
52
|
+
elsif (m = FIELD_LINE.match(line))
|
|
53
|
+
@fields[m[1]] = expand(m[2].strip, line_number) if KNOWN_FIELDS.include?(m[1])
|
|
54
|
+
else
|
|
55
|
+
raise ParseError.new("unrecognized line: #{line.inspect}", path: @path, line_number: line_number)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Variables are expanded at the point they are assigned, against
|
|
60
|
+
# whatever variables are already known — the same order the real
|
|
61
|
+
# fixtures rely on (prefix, then exec_prefix=${prefix}, then
|
|
62
|
+
# libdir=${exec_prefix}/lib, ...). Fields are expanded the same way,
|
|
63
|
+
# against the full variable table (fields conventionally follow all
|
|
64
|
+
# variable assignments in a .pc file).
|
|
65
|
+
def expand(value, line_number)
|
|
66
|
+
value.gsub(VARIABLE_REFERENCE) do
|
|
67
|
+
name = Regexp.last_match(1)
|
|
68
|
+
@variables.fetch(name) do
|
|
69
|
+
raise ParseError.new("undefined variable '#{name}'", path: @path, line_number: line_number)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_package
|
|
75
|
+
Package.new(
|
|
76
|
+
name: @fields["Name"],
|
|
77
|
+
description: @fields["Description"],
|
|
78
|
+
version: @fields["Version"],
|
|
79
|
+
requires: parse_requires(@fields["Requires"]),
|
|
80
|
+
requires_private: parse_requires(@fields["Requires.private"]),
|
|
81
|
+
cflags: @fields["Cflags"] || "",
|
|
82
|
+
libs: @fields["Libs"] || "",
|
|
83
|
+
libs_private: @fields["Libs.private"] || "",
|
|
84
|
+
path: @path
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# A bare module-name list ("libssl libcrypto" or "libssl, libcrypto").
|
|
89
|
+
# A version comparison attached to an entry is diagnosed rather than
|
|
90
|
+
# evaluated: mkmf's pkg_config(pkg, *options) always passes `pkg` as a
|
|
91
|
+
# bare module name (see the Step 59 report), so this shim only needs to
|
|
92
|
+
# resolve Requires by name.
|
|
93
|
+
def parse_requires(value)
|
|
94
|
+
return [] if value.nil? || value.strip.empty?
|
|
95
|
+
|
|
96
|
+
spaced = value.gsub(/(<=|>=|==|!=|<|>|=)/) { " #{Regexp.last_match(1)} " }
|
|
97
|
+
tokens = spaced.split(/[,\s]+/).reject(&:empty?)
|
|
98
|
+
tokens.each do |token|
|
|
99
|
+
next unless REQUIRES_OPERATOR.match?(token)
|
|
100
|
+
|
|
101
|
+
raise UnsupportedError,
|
|
102
|
+
"version-constrained Requires (#{value.inspect}) is unsupported: mkmf's pkg_config() " \
|
|
103
|
+
"never passes a version constraint, so only bare module names in Requires are handled"
|
|
104
|
+
end
|
|
105
|
+
tokens
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|