/
fetch-rbis.rb
executable file
·139 lines (116 loc) · 4.8 KB
/
fetch-rbis.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env ruby
# typed: false
require_relative './step_interface'
require_relative './t'
require 'bundler'
require 'fileutils'
require 'set'
class Sorbet; end
module Sorbet::Private; end
class Sorbet::Private::FetchRBIs
SORBET_DIR = 'sorbet'
SORBET_CONFIG_FILE = "#{SORBET_DIR}/config"
SORBET_RBI_LIST = "#{SORBET_DIR}/rbi_list"
SORBET_RBI_SORBET_TYPED = "#{SORBET_DIR}/rbi/sorbet-typed/"
XDG_CACHE_HOME = ENV['XDG_CACHE_HOME'] || "#{ENV['HOME']}/.cache"
RBI_CACHE_DIR = "#{XDG_CACHE_HOME}/sorbet/sorbet-typed"
SORBET_TYPED_REPO = ENV['SRB_SORBET_TYPED_REPO'] || 'https://github.com/sorbet/sorbet-typed.git'
SORBET_TYPED_REVISION = ENV['SRB_SORBET_TYPED_REVISION'] || 'origin/master'
HEADER = Sorbet::Private::Serialize.header(false, 'sorbet-typed')
include Sorbet::Private::StepInterface
# Ensure our cache is up-to-date
T::Sig::WithoutRuntime.sig {void}
def self.fetch_sorbet_typed
if File.directory?(RBI_CACHE_DIR)
cached_remote = IO.popen(["git", "-C", RBI_CACHE_DIR, "config", "--get", "remote.origin.url"]) {|pipe| pipe.read}.strip
# Compare the <owner>/<repo>.git to be agnostic of https vs ssh urls
cached_remote_repo = cached_remote.split(%r{github.com[:/]}).last
requested_remote_repo = SORBET_TYPED_REPO.split(%r{github.com[:/]}).last
if cached_remote_repo != requested_remote_repo
raise "Cached remote #{cached_remote_repo} does not match requested remote #{requested_remote_repo}. Delete #{RBI_CACHE_DIR} and try again."
end
else
IO.popen(["git", "clone", SORBET_TYPED_REPO, RBI_CACHE_DIR]) {|pipe| pipe.read}
raise "Failed to git pull" if $?.exitstatus != 0
end
FileUtils.cd(RBI_CACHE_DIR) do
IO.popen(%w{git fetch --all}) {|pipe| pipe.read}
raise "Failed to git fetch" if $?.exitstatus != 0
IO.popen(%w{git checkout -q} + [SORBET_TYPED_REVISION]) {|pipe| pipe.read}
raise "Failed to git checkout" if $?.exitstatus != 0
end
end
# List of directories whose names satisfy the given Gem::Version (+ 'all/')
T::Sig::WithoutRuntime.sig do
params(
root: String,
version: Gem::Version,
)
.returns(T::Array[String])
end
def self.matching_version_directories(root, version)
paths = Dir.glob("#{root}/*/").select do |dir|
basename = File.basename(dir.chomp('/'))
requirements = basename.split(/[,&-]/) # split using ',', '-', or '&'
requirements.all? do |requirement|
Gem::Requirement::PATTERN =~ requirement &&
Gem::Requirement.create(requirement).satisfied_by?(version)
end
end
paths = paths.map {|dir| dir.chomp('/')}
all_dir = "#{root}/all"
paths << all_dir if Dir.exist?(all_dir)
paths
end
# List of directories in lib/ruby whose names satisfy the current RUBY_VERSION
T::Sig::WithoutRuntime.sig {params(ruby_version: Gem::Version).returns(T::Array[String])}
def self.paths_for_ruby_version(ruby_version)
ruby_dir = "#{RBI_CACHE_DIR}/lib/ruby"
matching_version_directories(ruby_dir, ruby_version)
end
# List of directories in lib/gemspec.name whose names satisfy gemspec.version
T::Sig::WithoutRuntime.sig {params(gemspec: T.untyped).returns(T::Array[String])}
def self.paths_for_gem_version(gemspec)
local_dir = "#{RBI_CACHE_DIR}/lib/#{gemspec.name}"
matching_version_directories(local_dir, gemspec.version)
end
# Copy the relevant RBIs into their repo, with matching folder structure.
T::Sig::WithoutRuntime.sig {params(vendor_paths: T::Array[String]).void}
def self.vendor_rbis_within_paths(vendor_paths)
vendor_paths.each do |vendor_path|
relative_vendor_path = vendor_path.sub(RBI_CACHE_DIR, '')
dest = "#{SORBET_RBI_SORBET_TYPED}/#{relative_vendor_path}"
FileUtils.mkdir_p(dest)
Dir.glob("#{vendor_path}/*.rbi").each do |rbi|
extra_header = "#
# If you would like to make changes to this file, great! Please upstream any changes you make here:
#
# https://github.com/sorbet/sorbet-typed/edit/master#{relative_vendor_path}/#{File.basename(rbi)}
#
"
File.write("#{dest}/#{File.basename(rbi)}", HEADER + extra_header + File.read(rbi))
end
end
end
T::Sig::WithoutRuntime.sig {void}
def self.main
fetch_sorbet_typed
gemspecs = Bundler.load.specs.sort_by(&:name)
vendor_paths = T.let([], T::Array[String])
vendor_paths += paths_for_ruby_version(Gem::Version.create(RUBY_VERSION))
gemspecs.each do |gemspec|
vendor_paths += paths_for_gem_version(gemspec)
end
# Remove the sorbet-typed directory before repopulating it.
FileUtils.rm_r(SORBET_RBI_SORBET_TYPED) if Dir.exist?(SORBET_RBI_SORBET_TYPED)
if vendor_paths.length > 0
vendor_rbis_within_paths(vendor_paths)
end
end
def self.output_file
SORBET_RBI_SORBET_TYPED
end
end
if $PROGRAM_NAME == __FILE__
Sorbet::Private::FetchRBIs.main
end