rbe-tebako 0.15.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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.clang-format +4 -0
  3. data/Brewfile +23 -0
  4. data/CMakeLists.txt +464 -0
  5. data/CODE_OF_CONDUCT.adoc +128 -0
  6. data/CONTRIBUTING.adoc +294 -0
  7. data/Gemfile +31 -0
  8. data/INSTALLATION.adoc +233 -0
  9. data/LICENSE.md +33 -0
  10. data/README.adoc +1363 -0
  11. data/Rakefile +35 -0
  12. data/bin/console +15 -0
  13. data/bin/setup +8 -0
  14. data/cmake/copy_dir.cmake +29 -0
  15. data/common.env +5 -0
  16. data/exe/rbe-tebako +32 -0
  17. data/exe/rbe-tebako-packager +77 -0
  18. data/include/tebako/tebako-fs.h +43 -0
  19. data/include/tebako/tebako-main.h +42 -0
  20. data/include/tebako/tebako-prism.h +51 -0
  21. data/lib/rbe-tebako.rb +6 -0
  22. data/lib/tebako/build_helpers.rb +77 -0
  23. data/lib/tebako/cache_manager.rb +112 -0
  24. data/lib/tebako/cli.rb +220 -0
  25. data/lib/tebako/cli_helpers.rb +165 -0
  26. data/lib/tebako/codegen.rb +252 -0
  27. data/lib/tebako/deploy_helper.rb +258 -0
  28. data/lib/tebako/error.rb +70 -0
  29. data/lib/tebako/options_manager.rb +338 -0
  30. data/lib/tebako/package_descriptor.rb +143 -0
  31. data/lib/tebako/packager/pass1_patch.rb +266 -0
  32. data/lib/tebako/packager/pass1a_patch.rb +55 -0
  33. data/lib/tebako/packager/pass2_patch.rb +140 -0
  34. data/lib/tebako/packager/pass2_patch_crt.rb +46 -0
  35. data/lib/tebako/packager/pass2msys_patch.rb +190 -0
  36. data/lib/tebako/packager/patch.rb +54 -0
  37. data/lib/tebako/packager/patch_buildsystem.rb +124 -0
  38. data/lib/tebako/packager/patch_helpers.rb +109 -0
  39. data/lib/tebako/packager/patch_libraries.rb +188 -0
  40. data/lib/tebako/packager/patch_literals.rb +275 -0
  41. data/lib/tebako/packager/patch_main.rb +93 -0
  42. data/lib/tebako/packager/rubygems_patch.rb +83 -0
  43. data/lib/tebako/packager.rb +210 -0
  44. data/lib/tebako/packager_lite.rb +78 -0
  45. data/lib/tebako/ruby_builder.rb +71 -0
  46. data/lib/tebako/ruby_version.rb +206 -0
  47. data/lib/tebako/scenario_manager.rb +251 -0
  48. data/lib/tebako/stripper.rb +135 -0
  49. data/lib/tebako/version.rb +30 -0
  50. data/lib/tebako.rb +34 -0
  51. data/src/tebako-main.cpp +168 -0
  52. data/tools/.gitattributes +4 -0
  53. data/tools/.github/workflows/build-containers.yml +190 -0
  54. data/tools/.github/workflows/lint.yml +41 -0
  55. data/tools/.github/workflows/test.yml +331 -0
  56. data/tools/.gitignore +1 -0
  57. data/tools/README.md +49 -0
  58. data/tools/ci-scripts/arm-brew-install.sh +43 -0
  59. data/tools/ci-scripts/arm-brew-setup.sh +33 -0
  60. data/tools/ci-scripts/level-up.sh +35 -0
  61. data/tools/ci-scripts/patch-fbthrift.sh +94 -0
  62. data/tools/ci-scripts/patch-folly.sh +551 -0
  63. data/tools/ci-scripts/patch-glog.sh +57 -0
  64. data/tools/ci-scripts/patch-zstd.sh +57 -0
  65. data/tools/ci-scripts/tools.sh +63 -0
  66. data/tools/ci-scripts/x86-brew-install.sh +43 -0
  67. data/tools/ci-scripts/x86-brew-setup.sh +33 -0
  68. data/tools/cmake-scripts/def-external-project.cmake +96 -0
  69. data/tools/cmake-scripts/macos-environment.cmake +91 -0
  70. data/tools/cmake-scripts/msys-environment.cmake +78 -0
  71. data/tools/cmake-scripts/setup-libarchive.cmake +93 -0
  72. data/tools/cmake-scripts/setup-libfmt.cmake +61 -0
  73. data/tools/cmake-scripts/setup-libhowardhinnerdate.cmake +55 -0
  74. data/tools/cmake-scripts/setup-libutfcpp.cmake +56 -0
  75. data/tools/cmake-scripts/version.cmake +162 -0
  76. data/tools/docker/alpine-3.17-dev.Dockerfile +49 -0
  77. data/tools/docker/tools/tools.sh +66 -0
  78. data/tools/docker/ubuntu-20.04-dev.Dockerfile +51 -0
  79. data/tools/dwarfs-test-helper/CMakeLists.txt +34 -0
  80. data/tools/include/pro-statvfs.h +57 -0
  81. data/tools/tests/cmake/CMakeLists.txt +81 -0
  82. data/tools/tests/setup-libfmt/CMakeLists.txt +35 -0
  83. data/tools/tests/setup-libhowardhinnerdate/CMakeLists.txt +35 -0
  84. data/tools/tests/setup-librachive/CMakeLists.txt +35 -0
  85. data/tools/tests/setup-libutfcpp/CMakeLists.txt +36 -0
  86. data/tools/tests/setup-openssl/CMakeLists.txt +36 -0
  87. metadata +312 -0
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2023-2025 [Ribose Inc](https://www.ribose.com).
4
+ # All rights reserved.
5
+ # This file is a part of the Tebako project.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions
9
+ # are met:
10
+ # 1. Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # 2. Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
20
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ # POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ require "pathname"
29
+ require "fileutils"
30
+
31
+ require_relative "options_manager"
32
+ require_relative "scenario_manager"
33
+ require_relative "package_descriptor"
34
+ require_relative "packager"
35
+
36
+ module Tebako
37
+ # Tebako application package descriptor
38
+ class PackagerLite
39
+ def initialize(options_manager, scenario_manager)
40
+ @opts = options_manager
41
+ @scm = scenario_manager
42
+ @scm.configure_scenario
43
+ end
44
+
45
+ def codegen
46
+ puts "-- Generating files"
47
+ Tebako::Codegen.generate_package_descriptor(@opts, @scm)
48
+ end
49
+
50
+ def create_implib
51
+ rv = Tebako::RubyVersion.new(@opts.ruby_ver)
52
+ bname = if @opts.mode == "application"
53
+ @opts.ref
54
+ else # @opts.mode == "both"
55
+ @opts.package
56
+ end
57
+ Tebako::Packager.create_implib(@opts.ruby_src_dir, @opts.data_src_dir, bname, rv)
58
+ end
59
+
60
+ def create_package
61
+ deploy
62
+ FileUtils.rm_f(name)
63
+ Tebako::Packager.mkdwarfs(@opts.deps_bin_dir, name, @opts.data_src_dir, codegen)
64
+ puts "Created tebako #{@opts.output_type_second} at \"#{name}\""
65
+ end
66
+
67
+ def deploy
68
+ Tebako::Packager.init(@opts.stash_dir, @opts.data_src_dir, @opts.data_pre_dir, @opts.data_bin_dir)
69
+ create_implib if @scm.msys?
70
+ Tebako::Packager.deploy(@opts.data_src_dir, @opts.data_pre_dir, @opts.rv, @opts.root, @scm.fs_entrance, @opts.cwd)
71
+ end
72
+
73
+ def name
74
+ bname = Pathname.new(@opts.package).cleanpath.to_s
75
+ @name ||= "#{bname}.tebako"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024-2025 [Ribose Inc](https://www.ribose.com).
4
+ # All rights reserved.
5
+ # This file is a part of the Tebako project.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions
9
+ # are met:
10
+ # 1. Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # 2. Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
20
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ # POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ require "fileutils"
29
+ require "find"
30
+
31
+ require_relative "build_helpers"
32
+
33
+ # Tebako - an executable packager
34
+ module Tebako
35
+ # Tebako packaging support (ruby builder)
36
+ class RubyBuilder
37
+ def initialize(ruby_ver, src_dir)
38
+ @ruby_ver = ruby_ver
39
+ @src_dir = src_dir
40
+ @ncores = ScenarioManagerBase.new.ncores
41
+ end
42
+
43
+ # Ruby's parallel build can race on ext/extinit.o: the `ruby` link step can start before
44
+ # extinit.o is (re)built after configure-ext.mk is regenerated, giving
45
+ # "clang: error: no such file or directory: 'ext/extinit.o'". A second, serial pass is
46
+ # mostly cached and builds the raced target deterministically, so retry once serially.
47
+ def make_target(*args)
48
+ BuildHelpers.run_with_capture(["make", *args, "-j#{@ncores}"])
49
+ rescue Tebako::Error
50
+ BuildHelpers.run_with_capture(["make", *args])
51
+ end
52
+
53
+ # Final build of tebako package
54
+ def toolchain_build
55
+ puts " ... building toolchain Ruby"
56
+ Dir.chdir(@src_dir) do
57
+ make_target
58
+ make_target("install")
59
+ end
60
+ end
61
+
62
+ # Final build of tebako package
63
+ def target_build(output_type)
64
+ puts " ... building tebako #{output_type}"
65
+ Dir.chdir(@src_dir) do
66
+ make_target("ruby") if @ruby_ver.ruby3x?
67
+ make_target
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2023-2025 [Ribose Inc](https://www.ribose.com).
4
+ # All rights reserved.
5
+ # This file is a part of the Tebako project.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions
9
+ # are met:
10
+ # 1. Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # 2. Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
20
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ # POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ require "bundler"
29
+ require_relative "error"
30
+
31
+ # Tebako - an executable packager
32
+ module Tebako
33
+ # Ruby version
34
+ class RubyVersion
35
+ # Supported Ruby versions: latest patch release of each maintained line (3.2+ through 4.0).
36
+ # Support for 2.7.x, 3.0.x and 3.1.x was dropped.
37
+ RUBY_VERSIONS = {
38
+ "3.2.11" => "b3eeabd6636f334531db3ffdc3229eb05e524740e6c84fdc043720573cf2f8b2",
39
+ "3.3.11" => "59f0fafb1a59a05dc3765117af3fa68e153eb48254708549f321c1e9e078d7a0",
40
+ "3.4.10" => "ecee2d072a14f2d14347dd56dfd8fe5c3130abf5117bfaacbda0f4ef9cc429ec",
41
+ "4.0.6" => "837d299e8f7ddf2be31a229a7a7e019d354979825117989acb3b32b1a9be262a"
42
+ }.freeze
43
+
44
+ MIN_RUBY_VERSION_WINDOWS = "3.2.11"
45
+ DEFAULT_RUBY_VERSION = "4.0.6"
46
+
47
+ def initialize(ruby_version)
48
+ @ruby_version = ruby_version.nil? ? DEFAULT_RUBY_VERSION : ruby_version
49
+
50
+ run_checks
51
+ end
52
+
53
+ attr_reader :ruby_version
54
+
55
+ def api_version
56
+ @api_version ||= "#{@ruby_version.split(".")[0..1].join(".")}.0"
57
+ end
58
+
59
+ def extend_ruby_version
60
+ @extend_ruby_version ||= [@ruby_version, RUBY_VERSIONS[@ruby_version]]
61
+ end
62
+
63
+ def lib_version
64
+ @lib_version ||= "#{@ruby_version.split(".")[0..1].join}0"
65
+ end
66
+
67
+ # Version predicates are "this behavior applies to this version and newer", compared on
68
+ # Gem::Version so they are correct for Ruby 4.x (major != 3) and for two-digit patch levels
69
+ # (e.g. 3.3.11, 3.4.10). Ruby 4.0 satisfies the >= 3.x gates by design - it inherits the
70
+ # latest (3.4+) build behavior.
71
+ def ruby3x?
72
+ @ruby3x ||= at_least?("3.0.0")
73
+ end
74
+
75
+ def ruby31?
76
+ @ruby31 ||= at_least?("3.1.0")
77
+ end
78
+
79
+ def ruby32?
80
+ @ruby32 ||= at_least?("3.2.0")
81
+ end
82
+
83
+ def ruby32only?
84
+ @ruby32only ||= minor_line?(3, 2)
85
+ end
86
+
87
+ def ruby33?
88
+ @ruby33 ||= at_least?("3.3.0")
89
+ end
90
+
91
+ def ruby33only?
92
+ @ruby33only ||= minor_line?(3, 3)
93
+ end
94
+
95
+ def ruby3x7?
96
+ @ruby3x7 ||= ruby34? ||
97
+ (ruby32only? && teeny >= 7) ||
98
+ (ruby33only? && teeny >= 7)
99
+ end
100
+
101
+ def ruby34?
102
+ @ruby34 ||= at_least?("3.4.0")
103
+ end
104
+
105
+ def run_checks
106
+ version_check_format
107
+ version_check
108
+ version_check_msys
109
+ end
110
+
111
+ def version_check
112
+ return if RUBY_VERSIONS.key?(@ruby_version)
113
+
114
+ raise Tebako::Error.new(
115
+ "Ruby version #{@ruby_version} is not supported",
116
+ 110
117
+ )
118
+ end
119
+
120
+ def version_check_format
121
+ return if @ruby_version =~ /^\d+\.\d+\.\d+$/
122
+
123
+ raise Tebako::Error.new("Invalid Ruby version format '#{@ruby_version}'. Expected format: x.y.z", 109)
124
+ end
125
+
126
+ def version_check_msys
127
+ if Gem::Version.new(@ruby_version) < Gem::Version.new(MIN_RUBY_VERSION_WINDOWS) && ScenarioManagerBase.new.msys?
128
+ raise Tebako::Error.new("Ruby version #{@ruby_version} is not supported on Windows", 111)
129
+ end
130
+ end
131
+
132
+ private
133
+
134
+ def gem_version
135
+ @gem_version ||= Gem::Version.new(@ruby_version)
136
+ end
137
+
138
+ def segments
139
+ @segments ||= gem_version.segments
140
+ end
141
+
142
+ def at_least?(version)
143
+ gem_version >= Gem::Version.new(version)
144
+ end
145
+
146
+ def teeny
147
+ segments[2].to_i
148
+ end
149
+
150
+ def minor_line?(major, minor)
151
+ segments[0] == major && segments[1] == minor
152
+ end
153
+ end
154
+
155
+ # Ruby version with Gemfile definition
156
+ class RubyVersionWithGemfile < RubyVersion
157
+ def initialize(ruby_version, gemfile_path)
158
+ # Assuming that it does not attempt to load any gems or resolve dependencies
159
+ # this can be done with any bundler version
160
+ ruby_v = Bundler::Definition.build(gemfile_path, nil, nil).ruby_version&.versions
161
+ if ruby_v.nil?
162
+ super(ruby_version)
163
+ else
164
+ process_gemfile_ruby_version(ruby_version, ruby_v)
165
+ end
166
+ rescue Tebako::Error
167
+ raise
168
+ rescue StandardError => e
169
+ Tebako.packaging_error(115, e.message)
170
+ end
171
+
172
+ def process_gemfile_ruby_version(ruby_version, ruby_v)
173
+ puts "-- Found Gemfile with Ruby requirements #{ruby_v}"
174
+ requirement = Gem::Requirement.new(ruby_v)
175
+
176
+ if ruby_version.nil?
177
+ process_gemfile_ruby_version_ud(requirement)
178
+ else
179
+ process_gemfile_ruby_version_d(ruby_version, requirement)
180
+ end
181
+ run_checks
182
+ end
183
+
184
+ def process_gemfile_ruby_version_d(ruby_version, requirement)
185
+ current_version = Gem::Version.new(ruby_version)
186
+ unless requirement.satisfied_by?(current_version)
187
+ raise Tebako::Error.new("Ruby version #{ruby_version} does not satisfy requirement '#{requirement}'", 116)
188
+ end
189
+
190
+ @ruby_version = ruby_version
191
+ end
192
+
193
+ def process_gemfile_ruby_version_ud(requirement)
194
+ available_versions = RUBY_VERSIONS.keys.map { |v| Gem::Version.new(v) }
195
+ matching_version = available_versions.find { |v| requirement.satisfied_by?(v) }
196
+ puts "-- Found matching Ruby version #{matching_version}" if matching_version
197
+
198
+ unless matching_version
199
+ raise Tebako::Error.new("No available Ruby version satisfies requirement #{requirement}",
200
+ 116)
201
+ end
202
+
203
+ @ruby_version = matching_version.to_s
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,251 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2024-2025 [Ribose Inc](https://www.ribose.com).
4
+ # All rights reserved.
5
+ # This file is a part of the Tebako project.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions
9
+ # are met:
10
+ # 1. Redistributions of source code must retain the above copyright
11
+ # notice, this list of conditions and the following disclaimer.
12
+ # 2. Redistributions in binary form must reproduce the above copyright
13
+ # notice, this list of conditions and the following disclaimer in the
14
+ # documentation and/or other materials provided with the distribution.
15
+ #
16
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17
+ # ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18
+ # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
+ # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
20
+ # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
+ # POSSIBILITY OF SUCH DAMAGE.
27
+
28
+ require "pathname"
29
+ require "bundler"
30
+
31
+ require_relative "error"
32
+
33
+ # Tebako - an executable packager
34
+ module Tebako
35
+ # Minimum bundler / rubygems versions tebako pins when it has to install or update them
36
+ # (e.g. Gemfile-driven bundler resolution). These provide correct linux-gnu / linux-musl
37
+ # differentiation by bundler; supported Ruby lines (3.2+) ship compatible rubygems by default.
38
+ BUNDLER_VERSION = "2.4.22"
39
+ RUBYGEMS_VERSION = "3.4.22"
40
+
41
+ # A couple of static Scenario definitions
42
+ class ScenarioManagerBase
43
+ def initialize(ostype = RUBY_PLATFORM)
44
+ @ostype = ostype
45
+ @linux = @ostype =~ /linux/ ? true : false
46
+ @musl = @ostype =~ /linux-musl/ ? true : false
47
+ @macos = @ostype =~ /darwin/ ? true : false
48
+ @msys = @ostype =~ /msys|mingw|cygwin/ ? true : false
49
+
50
+ @fs_mount_point = @msys ? "A:/__tebako_memfs__" : "/__tebako_memfs__"
51
+ @exe_suffix = @msys ? ".exe" : ""
52
+ end
53
+
54
+ attr_reader :fs_mount_point, :exe_suffix
55
+
56
+ def b_env
57
+ u_flags = if @macos
58
+ "-DTARGET_OS_SIMULATOR=0 -DTARGET_OS_IPHONE=0 #{ENV.fetch("CXXFLAGS", nil)}"
59
+ elsif @msys
60
+ "-DGFLAGS_IS_A_DLL=0 #{ENV.fetch("CXXFLAGS", nil)}"
61
+ else
62
+ ENV.fetch("CXXFLAGS", nil)
63
+ end
64
+ @b_env ||= { "CXXFLAGS" => u_flags }
65
+ end
66
+
67
+ def linux?
68
+ @linux
69
+ end
70
+
71
+ def linux_gnu?
72
+ @linux && !@musl
73
+ end
74
+
75
+ def linux_musl?
76
+ @linux && @musl
77
+ end
78
+
79
+ def m_files
80
+ # [TODO]
81
+ # Ninja generates incorrect script for tebako press target -- gets lost in a chain custom targets
82
+ # Using makefiles has negative performance impact so it needs to be fixed
83
+
84
+ @m_files ||= if @linux || @macos
85
+ "Unix Makefiles"
86
+ elsif @msys
87
+ "MinGW Makefiles"
88
+ else
89
+ raise Tebako::Error.new("#{RUBY_PLATFORM} is not supported.", 112)
90
+ end
91
+ end
92
+
93
+ def macos?
94
+ @macos
95
+ end
96
+
97
+ def msys?
98
+ @msys
99
+ end
100
+
101
+ def musl?
102
+ @musl
103
+ end
104
+
105
+ def ncores
106
+ if @ncores.nil?
107
+ if @macos
108
+ out, st = Open3.capture2e("sysctl", "-n", "hw.ncpu")
109
+ else
110
+ out, st = Open3.capture2e("nproc", "--all")
111
+ end
112
+
113
+ @ncores = !st.signaled? && st.exitstatus.zero? ? out.strip.to_i : 4
114
+ end
115
+ @ncores
116
+ end
117
+ end
118
+
119
+ # Manages packaging scenario based on input files (gemfile, gemspec, etc)
120
+ class ScenarioManager < ScenarioManagerBase
121
+ def initialize(fs_root, fs_entrance)
122
+ super()
123
+ @with_gemfile = @with_lockfile = @needs_bundler = false
124
+ @bundler_version = BUNDLER_VERSION
125
+ initialize_root(fs_root)
126
+ initialize_entry_point(fs_entrance || "stub.rb")
127
+ end
128
+
129
+ attr_reader :fs_entry_point, :fs_entrance, :gemfile_path, :needs_bundler, :with_gemfile
130
+
131
+ def bundler_reference
132
+ @needs_bundler ? "_#{@bundler_version}_" : nil
133
+ end
134
+
135
+ def configure_scenario
136
+ lookup_files
137
+
138
+ case @gs_length
139
+ when 0
140
+ configure_scenario_no_gemspec
141
+ when 1
142
+ @scenario = @with_gemfile ? :gemspec_and_gemfile : :gemspec
143
+ else
144
+ raise Tebako::Error, "Multiple Ruby gemspecs found in #{@fs_root}"
145
+ end
146
+ end
147
+
148
+ private
149
+
150
+ def configure_scenario_no_gemspec
151
+ @fs_entry_point = "/local/#{@fs_entrance}" if @with_gemfile || @g_length.zero?
152
+
153
+ @scenario = if @with_gemfile
154
+ :gemfile
155
+ elsif @g_length.positive?
156
+ :gem
157
+ else
158
+ :simple_script
159
+ end
160
+ end
161
+
162
+ def initialize_entry_point(fs_entrance)
163
+ @fs_entrance = Pathname.new(fs_entrance).cleanpath.to_s
164
+
165
+ if Pathname.new(@fs_entrance).absolute?
166
+ Tebako.packaging_error 114 unless @fs_entrance.start_with?(@fs_root)
167
+
168
+ fetmp = @fs_entrance
169
+ @fs_entrance = Pathname.new(@fs_entrance).relative_path_from(Pathname.new(@fs_root)).to_s
170
+ puts "-- Absolute path to entry point '#{fetmp}' will be reduced to '#{@fs_entrance}' relative to '#{@fs_root}'"
171
+ end
172
+ # Can check after deploy, because entry point can be generated during bundle install or gem install
173
+ # Tebako.packaging_error 106 unless File.file?(File.join(@fs_root, @fs_entrance))
174
+ @fs_entry_point = "/bin/#{@fs_entrance}"
175
+ end
176
+
177
+ def initialize_root(fs_root)
178
+ Tebako.packaging_error 107 unless Dir.exist?(fs_root)
179
+ p_root = Pathname.new(fs_root).cleanpath
180
+ Tebako.packaging_error 113 unless p_root.absolute?
181
+ @fs_root = p_root.realpath.to_s
182
+ end
183
+
184
+ def lookup_files
185
+ @gs_length = Dir.glob(File.join(@fs_root, "*.gemspec")).length
186
+ @g_length = Dir.glob(File.join(@fs_root, "*.gem")).length
187
+ @with_gemfile = File.exist?(@gemfile_path = File.join(@fs_root, "Gemfile"))
188
+ @with_lockfile = File.exist?(@lockfile_path = File.join(@fs_root, "Gemfile.lock"))
189
+ end
190
+ end
191
+
192
+ # Configure scenraio and do bundler resolution
193
+ class ScenarioManagerWithBundler < ScenarioManager
194
+ protected
195
+
196
+ def lookup_files
197
+ super
198
+ if @with_lockfile
199
+ update_bundler_version_from_lockfile(@lockfile_path)
200
+ elsif @with_gemfile
201
+ update_bundler_version_from_gemfile(@gemfile_path)
202
+ end
203
+ end
204
+
205
+ private
206
+
207
+ def store_compatible_bundler_version(requirement)
208
+ fetcher = Gem::SpecFetcher.fetcher
209
+ tuples = fetcher.detect(:released) do |name_tuple|
210
+ name_tuple.name == "bundler" && requirement.satisfied_by?(name_tuple.version)
211
+ end
212
+
213
+ Tebako.packaging_error 119 if tuples.empty?
214
+
215
+ # Get latest compatible version
216
+ @bundler_version = tuples.map { |tuple, _| tuple.version }.max.to_s
217
+ end
218
+
219
+ def update_bundler_version_from_gemfile(gemfile_path)
220
+ # Build definition without lockfile
221
+ definition = Bundler::Definition.build(gemfile_path, nil, nil)
222
+
223
+ # Get bundler dependency from Gemfile
224
+ bundler_dep = definition.dependencies.find { |d| d.name == "bundler" }
225
+
226
+ return unless bundler_dep
227
+
228
+ @needs_bundler = true
229
+ min_requirement = Gem::Requirement.create(">= #{Tebako::BUNDLER_VERSION}")
230
+ requirement = Gem::Requirement.create(bundler_dep.requirement, min_requirement)
231
+
232
+ store_compatible_bundler_version(requirement)
233
+ end
234
+
235
+ def update_bundler_version_from_lockfile(lockfile_path)
236
+ puts " ... using lockfile at #{@lockfile_path}"
237
+ Tebako.packaging_error 117 unless File.exist?(lockfile_path)
238
+
239
+ lockfile_content = File.read(lockfile_path)
240
+ Tebako.packaging_error 117 unless lockfile_content =~ /BUNDLED WITH\n\s+(#{Gem::Version::VERSION_PATTERN})\n/
241
+
242
+ @bundler_version = ::Regexp.last_match(1)
243
+ @needs_bundler = true
244
+
245
+ bundler_requirement = Gem::Requirement.new(">= #{BUNDLER_VERSION}")
246
+ return if bundler_requirement.satisfied_by?(Gem::Version.new(@bundler_version))
247
+
248
+ Tebako.packaging_error 118, " : #{@bundler_version} requested, #{BUNDLER_VERSION} minimum required"
249
+ end
250
+ end
251
+ end