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,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "uri"
|
|
5
|
+
require "json"
|
|
6
|
+
require "rubygems/package"
|
|
7
|
+
|
|
8
|
+
module Rubycc
|
|
9
|
+
module Doctor
|
|
10
|
+
# Downloads a gem's package from rubygems.org and reads its real
|
|
11
|
+
# specification. This is the authoritative source of a gem's `extensions`
|
|
12
|
+
# list: neither the versions API nor the "quick" (light) gemspec carries the
|
|
13
|
+
# extensions field, so the only reliable way to know whether a gem has a C
|
|
14
|
+
# extension is to read the full .gem's embedded gemspec. That full package is
|
|
15
|
+
# also exactly what an on-the-fly build needs, so probe and build share it.
|
|
16
|
+
#
|
|
17
|
+
# Everything here touches the network; callers treat any failure (offline,
|
|
18
|
+
# 404, timeout) as "unknown" and degrade gracefully.
|
|
19
|
+
class Fetcher
|
|
20
|
+
HOST = "https://rubygems.org"
|
|
21
|
+
# A .gem is a few hundred KB at most for the extension gems we target; this
|
|
22
|
+
# only guards against a hung connection.
|
|
23
|
+
OPEN_TIMEOUT = 15
|
|
24
|
+
READ_TIMEOUT = 60
|
|
25
|
+
|
|
26
|
+
class FetchError < StandardError; end
|
|
27
|
+
|
|
28
|
+
def initialize(cache_dir)
|
|
29
|
+
@cache_dir = cache_dir
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Resolve the latest released (non-prerelease) version of +name+ via the v1
|
|
33
|
+
# API. Returns a version string, or nil when the lookup fails.
|
|
34
|
+
def latest_version(name)
|
|
35
|
+
body = get("#{HOST}/api/v1/gems/#{name}.json")
|
|
36
|
+
JSON.parse(body)["version"]
|
|
37
|
+
rescue StandardError
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Download NAME-VERSION.gem into the cache and return its local path. Raises
|
|
42
|
+
# FetchError on any network failure so the caller can report "unknown".
|
|
43
|
+
def download(name, version)
|
|
44
|
+
dest = File.join(@cache_dir, "#{name}-#{version}.gem")
|
|
45
|
+
return dest if File.file?(dest) && File.size(dest).positive?
|
|
46
|
+
|
|
47
|
+
body = get("#{HOST}/gems/#{name}-#{version}.gem")
|
|
48
|
+
File.binwrite(dest, body)
|
|
49
|
+
dest
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
raise FetchError, e.message
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The Gem::Specification embedded in a downloaded .gem.
|
|
55
|
+
def spec(gem_path)
|
|
56
|
+
Gem::Package.new(gem_path).spec
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
# A GET that follows rubygems.org's CDN redirects and returns the body,
|
|
62
|
+
# raising on any non-success status.
|
|
63
|
+
def get(url, limit = 5)
|
|
64
|
+
raise FetchError, "too many redirects" if limit.zero?
|
|
65
|
+
|
|
66
|
+
uri = URI(url)
|
|
67
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
68
|
+
http.use_ssl = uri.scheme == "https"
|
|
69
|
+
http.open_timeout = OPEN_TIMEOUT
|
|
70
|
+
http.read_timeout = READ_TIMEOUT
|
|
71
|
+
|
|
72
|
+
res = http.get(uri.request_uri)
|
|
73
|
+
case res
|
|
74
|
+
when Net::HTTPSuccess
|
|
75
|
+
res.body
|
|
76
|
+
when Net::HTTPRedirection
|
|
77
|
+
get(URI.join(url, res["location"]).to_s, limit - 1)
|
|
78
|
+
else
|
|
79
|
+
raise FetchError, "#{url} -> HTTP #{res.code}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rubycc
|
|
4
|
+
module Doctor
|
|
5
|
+
# Extracts the gems an application uses from its Gemfile.lock (preferred, for
|
|
6
|
+
# exact resolved versions) or, failing that, a bare Gemfile.
|
|
7
|
+
#
|
|
8
|
+
# Bundler's Gemfile is a Ruby DSL; evaluating it safely means sandboxing
|
|
9
|
+
# arbitrary code, which is heavy and a security hazard. The lock file, by
|
|
10
|
+
# contrast, is a fixed indented text format with no code in it, so parsing it
|
|
11
|
+
# is both safe and gives the *resolved* versions and the full transitive
|
|
12
|
+
# dependency closure (its `specs:` section is exactly that closure). rubycc
|
|
13
|
+
# doctor therefore prefers the lock and treats it as authoritative.
|
|
14
|
+
#
|
|
15
|
+
# When only a Gemfile is present it is parsed naively — `gem "name"` lines are
|
|
16
|
+
# read with a regexp, never `eval`'d. That yields direct dependencies only
|
|
17
|
+
# (no closure) and often no version; callers surface that as a limitation.
|
|
18
|
+
module Gemfile
|
|
19
|
+
# One resolved gem: its name, version (nil when unknown), the names of its
|
|
20
|
+
# direct runtime dependencies (from the lock; empty otherwise) and the
|
|
21
|
+
# source section it came from (:gem for rubygems.org, :path/:git for local
|
|
22
|
+
# or VCS gems that cannot be fetched from rubygems.org).
|
|
23
|
+
Entry = Struct.new(:name, :version, :dependencies, :source, keyword_init: true)
|
|
24
|
+
|
|
25
|
+
# The parsed picture the CLI drives off: the list of gem entries, the names
|
|
26
|
+
# of the directly-declared dependencies, and where the data came from
|
|
27
|
+
# (:lock exact versions, or :gemfile direct-only best effort).
|
|
28
|
+
Result = Struct.new(:entries, :direct, :origin, :path, keyword_init: true)
|
|
29
|
+
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# Load gem information from +dir+ (or an explicit --gemfile path). A
|
|
33
|
+
# Gemfile.lock next to the Gemfile is preferred; otherwise the Gemfile
|
|
34
|
+
# itself is parsed. Returns a Result, or nil when neither file exists.
|
|
35
|
+
def load(gemfile_path)
|
|
36
|
+
lock = "#{gemfile_path}.lock"
|
|
37
|
+
if File.file?(lock)
|
|
38
|
+
parse_lock(File.read(lock), path: lock)
|
|
39
|
+
elsif File.file?(gemfile_path)
|
|
40
|
+
parse_gemfile(File.read(gemfile_path), path: gemfile_path)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Parse the text of a Gemfile.lock. The format is a sequence of sections
|
|
45
|
+
# introduced by an unindented header (GEM, PATH, GIT, DEPENDENCIES, ...).
|
|
46
|
+
# Inside GEM/PATH/GIT a `specs:` line is followed by the resolved gems at
|
|
47
|
+
# 4-space indent (`name (version)`), each optionally trailed by its own
|
|
48
|
+
# dependencies at 6-space indent (`dep (constraint)`). DEPENDENCIES lists
|
|
49
|
+
# the directly-declared gems at 2-space indent.
|
|
50
|
+
def parse_lock(text, path: nil)
|
|
51
|
+
entries = []
|
|
52
|
+
direct = []
|
|
53
|
+
section = nil # current top-level section header
|
|
54
|
+
source = nil # :gem / :path / :git for the current specs block
|
|
55
|
+
in_specs = false
|
|
56
|
+
current = nil # the Entry whose dependency lines we are reading
|
|
57
|
+
|
|
58
|
+
text.each_line do |raw|
|
|
59
|
+
line = raw.chomp
|
|
60
|
+
next if line.strip.empty?
|
|
61
|
+
|
|
62
|
+
if line == line.lstrip # unindented -> a section header
|
|
63
|
+
section = line
|
|
64
|
+
source = { "GEM" => :gem, "PATH" => :path, "GIT" => :git }[section]
|
|
65
|
+
in_specs = false
|
|
66
|
+
current = nil
|
|
67
|
+
next
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
if section == "DEPENDENCIES"
|
|
71
|
+
direct << line.strip.sub(/[!<>=~ ].*\z/, "").strip
|
|
72
|
+
next
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
if source
|
|
76
|
+
if line.strip == "specs:"
|
|
77
|
+
in_specs = true
|
|
78
|
+
next
|
|
79
|
+
end
|
|
80
|
+
next unless in_specs
|
|
81
|
+
|
|
82
|
+
indent = line[/\A */].length
|
|
83
|
+
if indent == 4 && (m = line.strip.match(/\A(\S+) \(([^)]+)\)\z/))
|
|
84
|
+
current = Entry.new(name: m[1], version: m[2].split.first, dependencies: [], source: source)
|
|
85
|
+
entries << current
|
|
86
|
+
elsif indent >= 6 && current && (m = line.strip.match(/\A(\S+)/))
|
|
87
|
+
current.dependencies << m[1]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
Result.new(entries: entries, direct: direct.uniq, origin: :lock, path: path)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Parse a bare Gemfile naively: pick up every `gem "name"` (or 'name')
|
|
96
|
+
# declaration and an optional literal version argument. This never evaluates
|
|
97
|
+
# the file, so conditionals, variables and computed names are invisible —
|
|
98
|
+
# the caller reports the reduced fidelity. Group/platform blocks are not
|
|
99
|
+
# tracked; every declared gem is returned.
|
|
100
|
+
def parse_gemfile(text, path: nil)
|
|
101
|
+
entries = []
|
|
102
|
+
text.each_line do |raw|
|
|
103
|
+
line = raw.sub(/#.*/, "")
|
|
104
|
+
m = line.match(/\bgem\s*\(?\s*["']([^"']+)["']\s*(?:,\s*["']([^"']+)["'])?/)
|
|
105
|
+
next unless m
|
|
106
|
+
|
|
107
|
+
version = m[2]&.sub(/\A[~><=!\s]+/, "")&.strip
|
|
108
|
+
version = nil if version && version.empty?
|
|
109
|
+
entries << Entry.new(name: m[1], version: version, dependencies: [], source: :gem)
|
|
110
|
+
end
|
|
111
|
+
Result.new(entries: entries.uniq(&:name), direct: entries.map(&:name).uniq, origin: :gemfile, path: path)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Rubycc
|
|
6
|
+
module Doctor
|
|
7
|
+
# The build-verified gem database (data/verified_gems.json), the primary
|
|
8
|
+
# reference rubycc doctor consults before ever attempting a build. A gem whose
|
|
9
|
+
# resolved version satisfies one of the recorded `versions` entries is reported
|
|
10
|
+
# as verified with no network access and no build.
|
|
11
|
+
#
|
|
12
|
+
# The schema is documented in data/README.md. One gem is one entry, and an
|
|
13
|
+
# entry holds a list of verifications -- one per environment it was confirmed
|
|
14
|
+
# in, in insertion (oldest-first) order:
|
|
15
|
+
#
|
|
16
|
+
# { "json": { "verifications": [ { "versions": ["2.21.1"],
|
|
17
|
+
# "environment": "glibc x86_64 / ruby 3.4.5",
|
|
18
|
+
# "verified_at": "2026-07-17",
|
|
19
|
+
# "evidence": "..." } ],
|
|
20
|
+
# "notes": "..." } }
|
|
21
|
+
#
|
|
22
|
+
# `versions` sits inside each record rather than once at the entry level
|
|
23
|
+
# because the versions actually exercised may differ per environment; hoisting
|
|
24
|
+
# them would claim more than was measured. The absence of a record is itself
|
|
25
|
+
# the statement that the gem is unverified in that environment.
|
|
26
|
+
#
|
|
27
|
+
# Version entries are matched with Gem::Requirement, so both exact pins
|
|
28
|
+
# ("2.21.1") and ranges (">= 1.8, < 2") work.
|
|
29
|
+
class VerifiedGems
|
|
30
|
+
# data/verified_gems.json relative to this file (lib/rubycc/doctor/).
|
|
31
|
+
DEFAULT_PATH = File.expand_path("../../../data/verified_gems.json", __dir__)
|
|
32
|
+
|
|
33
|
+
# One environment's verification of a gem, mirroring the JSON schema.
|
|
34
|
+
Verification = Struct.new(:versions, :environment, :verified_at, :evidence, keyword_init: true)
|
|
35
|
+
|
|
36
|
+
# A verified-gem entry: the gem's name, every verification recorded for it,
|
|
37
|
+
# and the human-owned notes.
|
|
38
|
+
Record = Struct.new(:name, :verifications, :notes, keyword_init: true) do
|
|
39
|
+
# Every version any environment verified, de-duplicated and in the order
|
|
40
|
+
# the records list them. Callers that only ask "which versions are known
|
|
41
|
+
# good at all?" (the schema test, listings) want this rather than a
|
|
42
|
+
# per-environment breakdown.
|
|
43
|
+
def versions
|
|
44
|
+
verifications.flat_map(&:versions).uniq
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Load the database from +path+ (defaults to the shipped file).
|
|
49
|
+
def self.load(path = DEFAULT_PATH)
|
|
50
|
+
new(JSON.parse(File.read(path)))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def initialize(raw)
|
|
54
|
+
@records = {}
|
|
55
|
+
raw.each do |name, attrs|
|
|
56
|
+
verifications = Array(attrs["verifications"]).map do |v|
|
|
57
|
+
Verification.new(
|
|
58
|
+
versions: Array(v["versions"]),
|
|
59
|
+
environment: v["environment"],
|
|
60
|
+
verified_at: v["verified_at"],
|
|
61
|
+
evidence: v["evidence"]
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
@records[name] = Record.new(name: name, verifications: verifications, notes: attrs["notes"])
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# All records (used by the schema test and for listing).
|
|
69
|
+
attr_reader :records
|
|
70
|
+
|
|
71
|
+
# The record for +name+, or nil.
|
|
72
|
+
def [](name)
|
|
73
|
+
@records[name]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Whether +name+ at +version+ is verified. A nil version can never match an
|
|
77
|
+
# exact pin, so an unknown version is treated as not verified.
|
|
78
|
+
def verified?(name, version)
|
|
79
|
+
!match(name, version).nil?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The Record that verifies +name+ at +version+, or nil. A version satisfies
|
|
83
|
+
# an entry when at least one of its verifications covers it -- being
|
|
84
|
+
# verified in any one environment is what "verified" has always meant here,
|
|
85
|
+
# and splitting the schema per environment must not narrow it.
|
|
86
|
+
def match(name, version)
|
|
87
|
+
record = @records[name]
|
|
88
|
+
return nil if record.nil? || matching_verifications(name, version).empty?
|
|
89
|
+
|
|
90
|
+
record
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Every Verification of +name+ whose `versions` cover +version+, in the
|
|
94
|
+
# order they are recorded; empty when nothing matches. This is what a caller
|
|
95
|
+
# asks when it needs to say *where* the version was verified, not just
|
|
96
|
+
# whether it was.
|
|
97
|
+
def matching_verifications(name, version)
|
|
98
|
+
record = @records[name]
|
|
99
|
+
return [] unless record && version
|
|
100
|
+
|
|
101
|
+
gem_version = Gem::Version.new(version)
|
|
102
|
+
record.verifications.select do |verification|
|
|
103
|
+
verification.versions.any? do |req|
|
|
104
|
+
# A range entry may be comma-joined (">= 1.8, < 2"); split it into the
|
|
105
|
+
# individual constraints Gem::Requirement expects as separate arguments.
|
|
106
|
+
Gem::Requirement.new(*req.split(",").map(&:strip)).satisfied_by?(gem_version)
|
|
107
|
+
rescue ArgumentError
|
|
108
|
+
false
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "doctor/gemfile"
|
|
4
|
+
require_relative "doctor/verified_gems"
|
|
5
|
+
require_relative "doctor/fetcher"
|
|
6
|
+
require_relative "doctor/builder"
|
|
7
|
+
require_relative "doctor/cli"
|
|
8
|
+
|
|
9
|
+
module Rubycc
|
|
10
|
+
# rubycc doctor: the adoption-check command (ROADMAP §6 "M3 完了後のツール").
|
|
11
|
+
# Given an application's Gemfile.lock/Gemfile it reports, per gem, whether
|
|
12
|
+
# rubycc can build its C extension — first from the shipped build-verified
|
|
13
|
+
# database, then by an on-the-spot build for anything unverified.
|
|
14
|
+
module Doctor
|
|
15
|
+
end
|
|
16
|
+
end
|