traveling-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE.md +19 -0
- data/README.md +148 -0
- data/exe/traveling-ruby +5 -0
- data/exe/truby +5 -0
- data/lib/traveling_ruby/bundle.rb +171 -0
- data/lib/traveling_ruby/cli.rb +210 -0
- data/lib/traveling_ruby/config.rb +84 -0
- data/lib/traveling_ruby/fetcher.rb +103 -0
- data/lib/traveling_ruby/launcher.rb +65 -0
- data/lib/traveling_ruby/packager.rb +433 -0
- data/lib/traveling_ruby/rake_task.rb +56 -0
- data/lib/traveling_ruby/release.rb +136 -0
- data/lib/traveling_ruby/ui.rb +78 -0
- data/lib/traveling_ruby/version.rb +3 -0
- data/lib/traveling_ruby.rb +31 -0
- metadata +62 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
require "set"
|
|
3
|
+
|
|
4
|
+
module TravelingRuby
|
|
5
|
+
# One Ruby version and revision of Traveling Ruby, published as a release per
|
|
6
|
+
# target tagged ruby-<ruby>-<revision>-<target>. Each release has the runtime,
|
|
7
|
+
# a tarball per native gem, SHA256SUMS and manifest.json.
|
|
8
|
+
#
|
|
9
|
+
# The source is a GitHub repository URL, or a local directory holding a
|
|
10
|
+
# directory per release tag (handy for mirrors and testing).
|
|
11
|
+
class Release
|
|
12
|
+
DEFAULT_SOURCE = "https://github.com/trubygems/traveling-ruby".freeze
|
|
13
|
+
|
|
14
|
+
attr_reader :ruby, :revision
|
|
15
|
+
|
|
16
|
+
def self.resolve(fetcher:, source: DEFAULT_SOURCE, ruby: nil, revision: nil)
|
|
17
|
+
source = source.chomp("/")
|
|
18
|
+
if ruby.nil?
|
|
19
|
+
raise Error, "set the Ruby version to use a local source" if fetcher.local?(source)
|
|
20
|
+
tag = fetcher.redirect_location("#{source}/releases/latest").to_s.split("/").last
|
|
21
|
+
match = tag.to_s.match(/\Aruby-([^-]+)-(\d+)-/)
|
|
22
|
+
raise Error, "could not find the latest release at #{source}; set the Ruby version" unless match
|
|
23
|
+
ruby = match[1]
|
|
24
|
+
revision ||= match[2]
|
|
25
|
+
end
|
|
26
|
+
revision ||= newest_revision(fetcher, source, ruby)
|
|
27
|
+
new(fetcher: fetcher, source: source, ruby: ruby, revision: revision.to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# From versions/RUBY_REVISIONS.txt, one "<ruby> <revision>" per line
|
|
31
|
+
def self.newest_revision(fetcher, source, ruby)
|
|
32
|
+
url =
|
|
33
|
+
if fetcher.local?(source)
|
|
34
|
+
File.join(source, "RUBY_REVISIONS.txt")
|
|
35
|
+
elsif (repo = source[%r{\Ahttps://github\.com/([^/]+/[^/]+)\z}, 1])
|
|
36
|
+
"https://raw.githubusercontent.com/#{repo}/main/versions/RUBY_REVISIONS.txt"
|
|
37
|
+
end
|
|
38
|
+
revisions = url && fetcher.read(url)
|
|
39
|
+
line = revisions.to_s.lines.map(&:split).find { |version, _| version == ruby }
|
|
40
|
+
line ? line[1] : "0"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(fetcher:, source:, ruby:, revision:)
|
|
44
|
+
@fetcher = fetcher
|
|
45
|
+
@source = source
|
|
46
|
+
@ruby = ruby
|
|
47
|
+
@revision = revision
|
|
48
|
+
@checksums = {}
|
|
49
|
+
@manifests = {}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_s
|
|
53
|
+
"#{ruby}-#{revision}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def key(target)
|
|
57
|
+
"#{ruby}-#{revision}-#{target}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def tag(target)
|
|
61
|
+
"ruby-#{key(target)}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The runtime tarball, downloaded and verified. The full runtime keeps what
|
|
65
|
+
# the slim one leaves out, such as Ruby's headers and static libraries.
|
|
66
|
+
def runtime(target, full: false)
|
|
67
|
+
file =
|
|
68
|
+
if full
|
|
69
|
+
manifest(target).dig("runtime_full", "file") || "traveling-ruby-#{key(target)}-full.tar.gz"
|
|
70
|
+
else
|
|
71
|
+
manifest(target).dig("runtime", "file") || "traveling-ruby-#{key(target)}.tar.gz"
|
|
72
|
+
end
|
|
73
|
+
fetch(target, file)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Full names (name-version) of the gems the runtime comes with: Ruby's
|
|
77
|
+
# default and bundled gems
|
|
78
|
+
def runtime_gems(target, full: false)
|
|
79
|
+
@runtime_gems ||= {}
|
|
80
|
+
@runtime_gems[[target, full]] ||= begin
|
|
81
|
+
listing = IO.popen(["tar", "-tzf", runtime(target, full: full)], &:read)
|
|
82
|
+
raise Error, "could not list #{File.basename(runtime(target, full: full))}" unless $?.success?
|
|
83
|
+
listing.scan(%r{/specifications/(?:default/)?([^/\n]+)\.gemspec$}).flatten.to_set
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Native gems built for target, as {name, version, file, sha256}
|
|
88
|
+
def gems(target)
|
|
89
|
+
manifest(target).fetch("gems", [])
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Versions of a native gem built for target
|
|
93
|
+
def built_versions(target, name)
|
|
94
|
+
gems(target).select { |gem| gem["name"] == name }.map { |gem| gem["version"] }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The tarball of a native gem's extension, or nil when it was not built
|
|
98
|
+
def gem_extension(target, name, version)
|
|
99
|
+
gem = gems(target).find { |g| g["name"] == name && g["version"] == version }
|
|
100
|
+
gem && fetch(target, gem["file"])
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def manifest(target)
|
|
104
|
+
@manifests[target] ||= begin
|
|
105
|
+
body = @fetcher.read(url(target, "manifest.json"))
|
|
106
|
+
raise Error, "no Traveling Ruby #{self} release for #{target} (#{url(target, "manifest.json")} not found)" unless body
|
|
107
|
+
JSON.parse(body)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
def fetch(target, file)
|
|
114
|
+
sha256 = checksums(target)[file] or raise Error, "#{file} is not listed in #{tag(target)}'s SHA256SUMS"
|
|
115
|
+
@fetcher.fetch(url(target, file), sha256: sha256, cache_key: File.join(tag(target), file))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def checksums(target)
|
|
119
|
+
@checksums[target] ||= begin
|
|
120
|
+
body = @fetcher.read(url(target, "SHA256SUMS")) or raise Error, "#{tag(target)} has no SHA256SUMS"
|
|
121
|
+
body.lines.to_h do |line|
|
|
122
|
+
sha256, file = line.strip.split(/ [ *]/, 2)
|
|
123
|
+
[file, sha256]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def url(target, file)
|
|
129
|
+
if @fetcher.local?(@source)
|
|
130
|
+
File.join(@source, tag(target), file)
|
|
131
|
+
else
|
|
132
|
+
"#{@source}/releases/download/#{tag(target)}/#{file}"
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
module TravelingRuby
|
|
2
|
+
# Terminal output: colored when the stream is a terminal and NO_COLOR is not
|
|
3
|
+
# set, plain otherwise (CI logs, pipes, tests)
|
|
4
|
+
class UI
|
|
5
|
+
COLORS = {red: 31, green: 32, yellow: 33, cyan: 36, bold: 1, dim: 2}.freeze
|
|
6
|
+
|
|
7
|
+
attr_reader :io
|
|
8
|
+
|
|
9
|
+
def initialize(io = $stderr, verbose: false, color: nil)
|
|
10
|
+
@io = io
|
|
11
|
+
@verbose = verbose
|
|
12
|
+
@color = color.nil? ? io.respond_to?(:tty?) && io.tty? && ENV["NO_COLOR"].to_s.empty? : color
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def verbose?
|
|
16
|
+
@verbose
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def say(message = "")
|
|
20
|
+
@io.puts message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def title(message)
|
|
24
|
+
say paint(message, :bold)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# A finished step: " ✓ label detail"
|
|
28
|
+
def done(label, detail = nil)
|
|
29
|
+
say " #{paint("✓", :green)} #{label}#{" #{paint(detail, :dim)}" if detail}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def failed(label, detail = nil)
|
|
33
|
+
say " #{paint("✗", :red)} #{label}#{" #{detail}" if detail}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def hint(message)
|
|
37
|
+
say " #{paint("→ #{message}", :cyan)}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def warn(message)
|
|
41
|
+
say "#{paint("warning:", :yellow, :bold)} #{message}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def error(message)
|
|
45
|
+
say "#{paint("error:", :red, :bold)} #{message}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Shown with --verbose only
|
|
49
|
+
def detail(message)
|
|
50
|
+
say paint(" #{message}", :dim) if @verbose
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Downloads are worth seeing: they explain a slow first run
|
|
54
|
+
def fetching(what)
|
|
55
|
+
say paint(" ↓ #{what}", :dim)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def paint(text, *styles)
|
|
59
|
+
return text.to_s unless @color
|
|
60
|
+
"\e[#{styles.map { |style| COLORS.fetch(style) }.join(";")}m#{text}\e[0m"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.size(bytes)
|
|
64
|
+
units = %w[B KB MB GB]
|
|
65
|
+
value = bytes.to_f
|
|
66
|
+
unit = units.shift
|
|
67
|
+
while value >= 1024 && units.any?
|
|
68
|
+
value /= 1024
|
|
69
|
+
unit = units.shift
|
|
70
|
+
end
|
|
71
|
+
unit == "B" ? "#{bytes} B" : format("%.1f %s", value, unit)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.duration(seconds)
|
|
75
|
+
seconds < 60 ? format("%.1fs", seconds) : format("%dm %ds", seconds / 60, seconds % 60)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(__dir__) unless $LOAD_PATH.include?(__dir__)
|
|
2
|
+
|
|
3
|
+
require "traveling_ruby/version"
|
|
4
|
+
|
|
5
|
+
module TravelingRuby
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# Targets that have a release, as named in release tags
|
|
9
|
+
TARGETS = %w[
|
|
10
|
+
linux-x86_64 linux-arm64
|
|
11
|
+
linux-musl-x86_64 linux-musl-arm64
|
|
12
|
+
macos-x86_64 macos-arm64
|
|
13
|
+
windows-x86_64 windows-arm64
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
# Packaged when no targets are given
|
|
17
|
+
DEFAULT_TARGETS = %w[linux-x86_64 linux-arm64 macos-x86_64 macos-arm64 windows-x86_64].freeze
|
|
18
|
+
|
|
19
|
+
def self.windows?(target)
|
|
20
|
+
target.start_with?("windows-")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
autoload :UI, "traveling_ruby/ui"
|
|
24
|
+
autoload :Config, "traveling_ruby/config"
|
|
25
|
+
autoload :Fetcher, "traveling_ruby/fetcher"
|
|
26
|
+
autoload :Release, "traveling_ruby/release"
|
|
27
|
+
autoload :Bundle, "traveling_ruby/bundle"
|
|
28
|
+
autoload :Launcher, "traveling_ruby/launcher"
|
|
29
|
+
autoload :Packager, "traveling_ruby/packager"
|
|
30
|
+
autoload :CLI, "traveling_ruby/cli"
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: traveling-ruby
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Yousaf Nabi
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: |
|
|
13
|
+
Builds self-contained packages of a Ruby app for each platform Traveling Ruby
|
|
14
|
+
supports: the app, its gems, the prebuilt native extensions they need and a
|
|
15
|
+
portable Ruby, behind a launcher script. No Ruby is needed to run them.
|
|
16
|
+
executables:
|
|
17
|
+
- traveling-ruby
|
|
18
|
+
- truby
|
|
19
|
+
extensions: []
|
|
20
|
+
extra_rdoc_files: []
|
|
21
|
+
files:
|
|
22
|
+
- LICENSE.md
|
|
23
|
+
- README.md
|
|
24
|
+
- exe/traveling-ruby
|
|
25
|
+
- exe/truby
|
|
26
|
+
- lib/traveling_ruby.rb
|
|
27
|
+
- lib/traveling_ruby/bundle.rb
|
|
28
|
+
- lib/traveling_ruby/cli.rb
|
|
29
|
+
- lib/traveling_ruby/config.rb
|
|
30
|
+
- lib/traveling_ruby/fetcher.rb
|
|
31
|
+
- lib/traveling_ruby/launcher.rb
|
|
32
|
+
- lib/traveling_ruby/packager.rb
|
|
33
|
+
- lib/traveling_ruby/rake_task.rb
|
|
34
|
+
- lib/traveling_ruby/release.rb
|
|
35
|
+
- lib/traveling_ruby/ui.rb
|
|
36
|
+
- lib/traveling_ruby/version.rb
|
|
37
|
+
homepage: https://github.com/trubygems/traveling-ruby
|
|
38
|
+
licenses:
|
|
39
|
+
- MIT
|
|
40
|
+
metadata:
|
|
41
|
+
source_code_uri: https://github.com/trubygems/traveling-ruby/tree/main/packager
|
|
42
|
+
bug_tracker_uri: https://github.com/trubygems/traveling-ruby/issues
|
|
43
|
+
rubygems_mfa_required: 'true'
|
|
44
|
+
rdoc_options: []
|
|
45
|
+
require_paths:
|
|
46
|
+
- lib
|
|
47
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3.1'
|
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
53
|
+
requirements:
|
|
54
|
+
- - ">="
|
|
55
|
+
- !ruby/object:Gem::Version
|
|
56
|
+
version: '0'
|
|
57
|
+
requirements: []
|
|
58
|
+
rubygems_version: 3.6.9
|
|
59
|
+
specification_version: 4
|
|
60
|
+
summary: 'truby: package Ruby apps to run on Linux, macOS and Windows with no Ruby
|
|
61
|
+
installed'
|
|
62
|
+
test_files: []
|