tebako 0.10.0 → 0.12.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.
data/lib/tebako/cli.rb CHANGED
@@ -43,7 +43,7 @@ require_relative "version"
43
43
  module Tebako
44
44
  DEFAULT_TEBAFILE = ".tebako.yml"
45
45
  # Tebako packager front-end
46
- class Cli < Thor
46
+ class Cli < Thor # rubocop:disable Metrics/ClassLength
47
47
  package_name "Tebako"
48
48
  class_option :prefix, type: :string, aliases: "-p", required: false,
49
49
  desc: "A path to tebako packaging environment, '~/.tebako' ('$HOME/.tebako') by default"
@@ -82,28 +82,35 @@ module Tebako
82
82
  #{" " * 65}# If this parameter is not set, the application will start in the current directory of the host file system.
83
83
  DESC
84
84
 
85
+ REF_DESCRIPTION = <<~DESC
86
+ "Referenced tebako run-time package; 'tebako-runtime' by default".
87
+ #{" " * 65}# This option specifies the tebako runtime to be used by the application on Windows and if mode is 'application' only .
88
+ DESC
89
+
85
90
  RGP_DESCRIPTION = <<~DESC
86
- Activates removal a reference to GLIBC_PRIVATE version of libpthread from tebako package. This allows Linux Gnu packages to run against versions of
87
- #{" " * 65}# libpthread that differ from the version used for packaging. For example, package created at Ubuntu 20 system can be used on Ubuntu 22. This option works on Gnu Linux with
88
- #{" " * 65}# Gnu toolchain only (not for LLVM/clang). The feature is exeprimental, we may consider other approach in the future.
91
+ Current working directory for packaged application. This directory shall be specified relative to root.
92
+ #{" " * 65}# If this parameter is not set, the application will start in the current directory of the host file system.
89
93
  DESC
90
94
 
91
95
  desc "press", "Press tebako image"
92
- method_option :cwd, type: :string, aliases: "-c", required: false,
93
- desc: CWD_DESCRIPTION
94
- method_option :"entry-point", type: :string, aliases: ["-e", "--entry"], required: true,
95
- desc: "Ruby application entry point"
96
+ method_option :cwd, type: :string, aliases: "-c", required: false, desc: CWD_DESCRIPTION
96
97
  method_option :"log-level", type: :string, aliases: "-l", required: false, enum: %w[error warn debug trace],
97
98
  desc: "Tebako memfs logging level, 'error' by default"
98
99
  method_option :output, type: :string, aliases: "-o", required: false,
99
100
  desc: "Tebako package file name, entry point base file name in the current folder by default"
100
- method_option :root, type: :string, aliases: "-r", required: true, desc: "Root folder of the Ruby application"
101
+ method_option :"entry-point", type: :string, aliases: ["-e", "--entry"], required: false,
102
+ desc: "Ruby application entry point"
103
+ method_option :root, type: :string, aliases: "-r", required: false, desc: "Root folder of the Ruby application"
101
104
  method_option :Ruby, type: :string, aliases: "-R", required: false,
102
105
  enum: Tebako::RubyVersion::RUBY_VERSIONS.keys,
103
106
  desc: "Tebako package Ruby version, #{Tebako::RubyVersion::DEFAULT_RUBY_VERSION} by default"
104
- method_option :patchelf, aliases: "-P", type: :boolean,
105
- desc: RGP_DESCRIPTION
107
+ method_option :patchelf, aliases: "-P", type: :boolean, desc: RGP_DESCRIPTION
108
+ method_option :mode, type: :string, aliases: "-m", required: false, enum: %w[bundle both runtime application],
109
+ desc: "Tebako press mode, 'bundle' by default"
110
+ method_option :ref, type: :string, aliases: "-u", required: false, desc: REF_DESCRIPTION
111
+
106
112
  def press
113
+ validate_press_options
107
114
  (om, cm) = bootstrap
108
115
 
109
116
  do_press(om)
@@ -159,6 +166,22 @@ module Tebako
159
166
  end
160
167
  end
161
168
 
169
+ no_commands do
170
+ def source
171
+ c_path = Pathname.new(__FILE__).realpath
172
+ @source ||= File.expand_path("../../..", c_path)
173
+ end
174
+
175
+ def validate_press_options
176
+ return unless options["mode"] != "runtime"
177
+
178
+ opts = ""
179
+ opts += " '--root'" if options["root"].nil?
180
+ opts += " '--entry-point'" if options["entry-point"].nil?
181
+ raise Thor::Error, "No value provided for required options #{opts}" unless opts.empty?
182
+ end
183
+ end
184
+
162
185
  no_commands do
163
186
  include Tebako::CliHelpers
164
187
  end
@@ -34,6 +34,7 @@ require_relative "codegen"
34
34
  require_relative "error"
35
35
  require_relative "options_manager"
36
36
  require_relative "scenario_manager"
37
+ require_relative "packager_lite"
37
38
 
38
39
  # Tebako - an executable packager
39
40
  # Command-line interface methods
@@ -41,10 +42,27 @@ module Tebako
41
42
  # Cli helpers
42
43
  module CliHelpers
43
44
  def do_press(options_manager)
44
- puts options_manager.press_announce
45
+ scenario_manager = Tebako::ScenarioManager.new(options_manager.root, options_manager.fs_entrance)
46
+ puts options_manager.press_announce(scenario_manager.msys?)
47
+
48
+ if options_manager.mode == "both" || options_manager.mode == "runtime" || options_manager.mode == "bundle"
49
+ do_press_runtime(options_manager, scenario_manager)
50
+ end
51
+
52
+ if options_manager.mode == "both" || options_manager.mode == "application"
53
+ do_press_application(options_manager, scenario_manager)
54
+ end
55
+
56
+ true
57
+ end
45
58
 
46
- generate_files(options_manager)
59
+ def do_press_application(options_manager, scenario_manager)
60
+ packager = Tebako::PackagerLite.new(options_manager, scenario_manager)
61
+ packager.create_package
62
+ end
47
63
 
64
+ def do_press_runtime(options_manager, scenario_manager)
65
+ generate_files(options_manager, scenario_manager)
48
66
  cfg_cmd = "cmake -DSETUP_MODE:BOOLEAN=OFF #{options_manager.cfg_options} #{options_manager.press_options}"
49
67
  build_cmd = "cmake --build #{options_manager.output_folder} --target tebako --parallel #{Etc.nprocessors}"
50
68
  merged_env = ENV.to_h.merge(options_manager.b_env)
@@ -64,20 +82,18 @@ module Tebako
64
82
  true
65
83
  end
66
84
 
67
- def generate_files(options_manager)
85
+ def generate_files(options_manager, scenario_manager)
68
86
  puts "-- Generating files"
69
- scenario_manager = Tebako::ScenarioManager.new(options_manager.root, options_manager.fs_entrance)
70
87
  scenario_manager.configure_scenario
71
88
 
72
- puts " ... tebako-version.h"
73
89
  v_parts = Tebako::VERSION.split(".")
74
90
  Tebako::Codegen.generate_tebako_version_h(options_manager, v_parts)
75
-
76
- puts " ... tebako-fs.cpp"
77
91
  Tebako::Codegen.generate_tebako_fs_cpp(options_manager, scenario_manager)
78
-
79
- puts " ... deploy.rb"
80
92
  Tebako::Codegen.generate_deploy_rb(options_manager, scenario_manager)
93
+
94
+ return unless options_manager.mode == "both" || options_manager.mode == "runtime"
95
+
96
+ Tebako::Codegen.generate_stub_rb(options_manager)
81
97
  end
82
98
 
83
99
  def options_from_tebafile(tebafile)
@@ -28,6 +28,8 @@
28
28
  require "pathname"
29
29
  require "fileutils"
30
30
 
31
+ require_relative "package_descriptor"
32
+
31
33
  # Tebako - an executable packager
32
34
  module Tebako
33
35
  # Code geberation
@@ -47,20 +49,42 @@ module Tebako
47
49
 
48
50
  SUBST
49
51
 
50
- class << self
52
+ class << self # rubocop:disable Metrics/ClassLength
51
53
  def deploy_crt_implib(opt, scm)
52
54
  crt = ""
53
55
  if scm.msys?
54
56
  crt = <<~SUBST
55
57
  Tebako::Packager.create_implib("#{opt.ruby_src_dir}", "#{opt.data_src_dir}",
56
- "#{File.basename(opt.package)}", rv)
58
+ "#{opt.package}", rv)
57
59
  SUBST
58
60
  end
59
61
  crt
60
62
  end
61
63
 
62
- def deploy_cwd(opt)
63
- opt.cwd.nil? ? "nil" : "\"#{opt.cwd}\""
64
+ def deploy_mk(opt, scm)
65
+ case opt.mode
66
+ when "bundle"
67
+ deploy_mk_bundle(opt, scm)
68
+ when /runtime|both/
69
+ deploy_mk_stub(opt)
70
+ end
71
+ end
72
+
73
+ def deploy_mk_bundle(opt, scm)
74
+ <<~SUBST
75
+ Tebako::Packager.deploy("#{opt.data_src_dir}", "#{opt.data_pre_dir}",
76
+ rv , "#{opt.root}", "#{scm.fs_entrance}", "#{opt.cwd}")
77
+ Tebako::Packager.mkdwarfs("#{opt.deps_bin_dir}", "#{opt.data_bundle_file}",
78
+ "#{opt.data_src_dir}")
79
+ SUBST
80
+ end
81
+
82
+ def deploy_mk_stub(opt)
83
+ <<~SUBST
84
+ Tebako::Packager.deploy("#{opt.data_src_dir}", "#{opt.data_pre_dir}",
85
+ rv, "#{File.join(opt.deps, "src", "tebako", "local")}", "stub.rb", nil)
86
+ Tebako::Packager.mkdwarfs("#{opt.deps_bin_dir}", "#{opt.data_stub_file}", "#{opt.data_src_dir}")
87
+ SUBST
64
88
  end
65
89
 
66
90
  def deploy_rb(opt, scm)
@@ -71,22 +95,41 @@ module Tebako
71
95
  Tebako::Packager::init("#{opt.stash_dir}", "#{opt.data_src_dir}",
72
96
  "#{opt.data_pre_dir}", "#{opt.data_bin_dir}")
73
97
  #{deploy_crt_implib(opt, scm)}
74
- Tebako::Packager.deploy("#{opt.data_src_dir}", "#{opt.data_pre_dir}",
75
- rv , "#{opt.root}",
76
- "#{scm.fs_entrance}", #{deploy_cwd(opt)})
77
- Tebako::Packager.mkdwarfs("#{opt.deps_bin_dir}", "#{opt.data_bin_file}",
78
- "#{opt.data_src_dir}")
98
+ #{deploy_mk(opt, scm)}
79
99
  SUBST
80
100
  end
81
101
 
82
102
  def deploy_rq
83
103
  <<~SUBST
104
+ require "#{File.join(__dir__, "package_descriptor.rb")}"
84
105
  require "#{File.join(__dir__, "packager.rb")}"
85
106
  require "#{File.join(__dir__, "ruby_version.rb")}"
86
107
  SUBST
87
108
  end
88
109
 
110
+ def stub_rb(opt)
111
+ <<~SUBST
112
+ puts "Copyright (c) 2024 Ribose Inc (https://www.ribose.com)"
113
+ puts "Tebako runtime stub v#{Tebako::VERSION}"
114
+ puts "To run your application please call #{File.basename(opt.package)} --tebako-run <your tebako package>"
115
+ SUBST
116
+ end
117
+
118
+ def generate_stub_rb(options_manager)
119
+ puts " ... stub.rb"
120
+
121
+ fname = File.join(options_manager.deps, "src", "tebako", "local", "stub.rb")
122
+ FileUtils.mkdir_p(File.dirname(fname))
123
+
124
+ File.open(fname, "w") do |file|
125
+ file.write(COMMON_RUBY_HEADER)
126
+ file.write(stub_rb(options_manager))
127
+ end
128
+ end
129
+
89
130
  def generate_deploy_rb(options_manager, scenario_manager)
131
+ puts " ... deploy.rb"
132
+
90
133
  fname = File.join(options_manager.deps, "bin", "deploy.rb")
91
134
  FileUtils.mkdir_p(File.dirname(fname))
92
135
 
@@ -96,7 +139,20 @@ module Tebako
96
139
  end
97
140
  end
98
141
 
142
+ def generate_package_descriptor(options_manager, scenario_manager)
143
+ puts " ... package_descriptor"
144
+ fname = File.join(options_manager.deps, "src", "tebako", "package_descriptor")
145
+ FileUtils.mkdir_p(File.dirname(fname))
146
+ descriptor = Tebako::PackageDescriptor.new(options_manager.ruby_ver, Tebako::VERSION,
147
+ scenario_manager.fs_mount_point, scenario_manager.fs_entry_point,
148
+ options_manager.cwd)
149
+ File.binwrite(fname, descriptor.serialize)
150
+ fname
151
+ end
152
+
99
153
  def generate_tebako_fs_cpp(options_manager, scenario_manager)
154
+ puts " ... tebako-fs.cpp"
155
+
100
156
  fname = File.join(options_manager.deps, "src", "tebako", "tebako-fs.cpp")
101
157
  FileUtils.mkdir_p(File.dirname(fname))
102
158
 
@@ -107,6 +163,8 @@ module Tebako
107
163
  end
108
164
 
109
165
  def generate_tebako_version_h(options_manager, v_parts)
166
+ puts " ... tebako-version.h"
167
+
110
168
  fname = File.join(options_manager.deps, "include", "tebako", "tebako-version.h")
111
169
  FileUtils.mkdir_p(File.dirname(fname))
112
170
 
@@ -125,7 +183,17 @@ module Tebako
125
183
  end
126
184
 
127
185
  def tebako_fs_cpp(options_manager, scenario_manager)
186
+ case options_manager.mode
187
+ when "bundle"
188
+ tebako_fs_cpp_bundle(options_manager, scenario_manager)
189
+ when /runtime|both/
190
+ tebako_fs_cpp_stub(options_manager, scenario_manager)
191
+ end
192
+ end
193
+
194
+ def tebako_fs_cpp_bundle(options_manager, scenario_manager)
128
195
  <<~SUBST
196
+ #include <limits.h>
129
197
  #include <incbin/incbin.h>
130
198
 
131
199
  namespace tebako {
@@ -135,7 +203,24 @@ module Tebako
135
203
  const char * package_cwd = #{package_cwd(options_manager, scenario_manager)};
136
204
  char original_cwd[PATH_MAX];
137
205
 
138
- INCBIN(fs, "#{options_manager.output_folder}/p/fs.bin");
206
+ INCBIN(fs, "#{options_manager.data_bundle_file}");
207
+ }
208
+ SUBST
209
+ end
210
+
211
+ def tebako_fs_cpp_stub(options_manager, scenario_manager)
212
+ <<~SUBST
213
+ #include <limits.h>
214
+ #include <incbin/incbin.h>
215
+
216
+ namespace tebako {
217
+ const char * fs_log_level = "#{options_manager.l_level}";
218
+ const char * fs_mount_point = "#{scenario_manager.fs_mount_point}";
219
+ const char * fs_entry_point = "/local/stub.rb";
220
+ const char * package_cwd = nullptr;
221
+ char original_cwd[PATH_MAX];
222
+
223
+ INCBIN(fs, "#{options_manager.data_stub_file}");
139
224
  }
140
225
  SUBST
141
226
  end
@@ -70,7 +70,7 @@ module Tebako
70
70
  def deploy
71
71
  BuildHelpers.with_env(deploy_env) do
72
72
  update_rubygems
73
- system("#{gem_command} env")
73
+ system("#{gem_command} env") if @verbose
74
74
  install_gem("tebako-runtime")
75
75
  install_gem("bundler", BUNDLER_VERSION) if needs_bundler?
76
76
  deploy_solution
@@ -73,16 +73,32 @@ module Tebako
73
73
  @cwd ||= f_cwd
74
74
  end
75
75
 
76
+ def cwd_announce
77
+ @cwd_announce ||= cwd.nil? ? "<Host current directory>" : cwd
78
+ end
79
+
76
80
  # DATA_BIN_DIR folder is used to create packaged filesystem
77
81
  # set(DATA_BIN_DIR ${CMAKE_CURRENT_BINARY_DIR}/p)
78
82
  def data_bin_dir
79
83
  @data_bin_dir ||= File.join(output_folder, "p")
80
84
  end
81
85
 
82
- # DATA_BIN_FILE is packaged filesystem itself
83
- # set(DATA_BIN_FILE ${DATA_BIN_DIR}/fs.bin)
84
- def data_bin_file
85
- @data_bin_file ||= File.join(data_bin_dir, "fs.bin")
86
+ # Mode File(s) Content
87
+ # bundle fs.bin Application
88
+ # both fs.bin, fs2.bin Stub, application respectively
89
+ # runtime fs.bin Stub
90
+ # app fs2.bin Application
91
+
92
+ def data_bundle_file
93
+ @data_bundle_file ||= File.join(data_bin_dir, "fs.bin")
94
+ end
95
+
96
+ def data_stub_file
97
+ @data_stub_file ||= File.join(data_bin_dir, "fs.bin")
98
+ end
99
+
100
+ def data_app_file
101
+ @data_app_file ||= File.join(data_bin_dir, "fs2.bin")
86
102
  end
87
103
 
88
104
  # DATA_PRE_DIR folder is used to build gems that need to be packaged
@@ -131,11 +147,11 @@ module Tebako
131
147
  end
132
148
 
133
149
  def l_level
134
- @l_level ||= if @options["log-level"].nil?
135
- "error"
136
- else
137
- @options["log-level"]
138
- end
150
+ @l_level ||= @options["log-level"].nil? ? "error" : @options["log-level"]
151
+ end
152
+
153
+ def mode
154
+ @mode ||= @options["mode"].nil? ? "bundle" : @options["mode"]
139
155
  end
140
156
 
141
157
  def m_files
@@ -179,10 +195,57 @@ module Tebako
179
195
  end
180
196
  end
181
197
 
182
- def press_announce
183
- cwd_announce = cwd.nil? ? "<Host current directory>" : cwd
184
- @press_announce ||= <<~ANN
198
+ def press_announce(is_msys)
199
+ case mode
200
+ when "application"
201
+ press_announce_application(is_msys)
202
+ when "both"
203
+ press_announce_both
204
+ when "bundle"
205
+ press_announce_bundle
206
+ when "runtime"
207
+ press_announce_runtime
208
+ end
209
+ end
210
+
211
+ def press_announce_ref(is_msys)
212
+ if is_msys
213
+ " referencing runtime at '#{ref}'"
214
+ else
215
+ ""
216
+ end
217
+ end
218
+
219
+ def press_announce_application(is_msys)
220
+ <<~ANN
185
221
  Running tebako press at #{prefix}
222
+ Mode: 'application'
223
+ Ruby version: '#{@ruby_ver}'
224
+ Project root: '#{root}'
225
+ Application entry point: '#{fs_entrance}'
226
+ Package file name: '#{package}.tebako'#{press_announce_ref(is_msys)}
227
+ Package working directory: '#{cwd_announce}'
228
+ ANN
229
+ end
230
+
231
+ def press_announce_both
232
+ <<~ANN
233
+ Running tebako press at #{prefix}
234
+ Mode: 'both'
235
+ Ruby version: '#{@ruby_ver}'
236
+ Project root: '#{root}'
237
+ Application entry point: '#{fs_entrance}'
238
+ Runtime file name: '#{package}'
239
+ Package file name: '#{package}.tebako'
240
+ Loging level: '#{l_level}'
241
+ Package working directory: '#{cwd_announce}'
242
+ ANN
243
+ end
244
+
245
+ def press_announce_bundle
246
+ <<~ANN
247
+ Running tebako press at #{prefix}
248
+ Mode: 'bundle'
186
249
  Ruby version: '#{@ruby_ver}'
187
250
  Project root: '#{root}'
188
251
  Application entry point: '#{fs_entrance}'
@@ -192,6 +255,16 @@ module Tebako
192
255
  ANN
193
256
  end
194
257
 
258
+ def press_announce_runtime
259
+ <<~ANN
260
+ Running tebako press at #{prefix}
261
+ Mode: 'runtime'
262
+ Ruby version: '#{@ruby_ver}'
263
+ Runtime file name: '#{package}'
264
+ Loging level: '#{l_level}'
265
+ ANN
266
+ end
267
+
195
268
  def press_options
196
269
  @press_options ||= "-DPCKG:STRING='#{package}' -DLOG_LEVEL:STRING='#{l_level}' " \
197
270
  end
@@ -200,6 +273,10 @@ module Tebako
200
273
  Pathname.new(path).relative?
201
274
  end
202
275
 
276
+ def ref
277
+ @ref ||= @options["ref"].nil? ? "tebako-runtime" : @options["ref"].gsub("\\", "/")
278
+ end
279
+
203
280
  def remove_glibc_private
204
281
  @remove_glibc_private ||= if RUBY_PLATFORM.end_with?("linux") || RUBY_PLATFORM.end_with?("linux-gnu")
205
282
  "-DREMOVE_GLIBC_PRIVATE=#{@options["patchelf"] ? "ON" : "OFF"}"
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2023-2024 [Ribose Inc](https://www.ribose.com).
4
+ # All rights reserved.
5
+ # This file is a part of tebako
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 "stringio"
29
+
30
+ module Tebako
31
+ # Tebako application package descriptor
32
+ class PackageDescriptor
33
+ SIGNATURE = "TAMATEBAKO"
34
+
35
+ attr_reader :ruby_version_major, :ruby_version_minor, :ruby_version_patch, :tebako_version_major,
36
+ :tebako_version_minor, :tebako_version_patch, :mount_point, :entry_point, :cwd
37
+
38
+ def initialize(*args)
39
+ if args.size == 1 && args[0].is_a?(Array)
40
+ deserialize(args[0])
41
+ elsif args.size == 5
42
+ construct_from_params(*args)
43
+ else
44
+ raise ArgumentError, "Invalid arguments"
45
+ end
46
+ end
47
+
48
+ def serialize
49
+ buffer = StringIO.new
50
+
51
+ buffer.write(SIGNATURE)
52
+ serialize_versions(buffer)
53
+
54
+ write_string(buffer, @mount_point)
55
+ write_string(buffer, @entry_point)
56
+
57
+ serialize_cwd(buffer)
58
+ buffer.string
59
+ end
60
+
61
+ private
62
+
63
+ def deserialize(buffer)
64
+ stream = StringIO.new(buffer.pack("C*"))
65
+
66
+ signature = stream.read(SIGNATURE.size)
67
+ raise ArgumentError, "Invalid or missing signature" if signature != SIGNATURE
68
+
69
+ deserialize_versions(stream)
70
+ @mount_point = read_string(stream)
71
+ @entry_point = read_string(stream)
72
+
73
+ cwd_present = stream.read(1).unpack1("C")
74
+ @cwd = cwd_present == 1 ? read_string(stream) : nil
75
+ end
76
+
77
+ def construct_from_params(ruby_version, tebako_version, mount_point, entry_point, cwd)
78
+ parse_version(ruby_version, :@ruby_version_major, :@ruby_version_minor, :@ruby_version_patch)
79
+ parse_version(tebako_version, :@tebako_version_major, :@tebako_version_minor, :@tebako_version_patch)
80
+
81
+ @mount_point = mount_point
82
+ @entry_point = entry_point
83
+ @cwd = cwd
84
+ end
85
+
86
+ def deserialize_versions(stream)
87
+ @ruby_version_major = read_uint16(stream)
88
+ @ruby_version_minor = read_uint16(stream)
89
+ @ruby_version_patch = read_uint16(stream)
90
+ @tebako_version_major = read_uint16(stream)
91
+ @tebako_version_minor = read_uint16(stream)
92
+ @tebako_version_patch = read_uint16(stream)
93
+ end
94
+
95
+ def parse_version(version, major_sym, minor_sym, patch_sym)
96
+ major, minor, patch = version.split(".").map(&:to_i)
97
+ raise ArgumentError, "Invalid version format" unless major && minor && patch
98
+
99
+ instance_variable_set(major_sym, major)
100
+ instance_variable_set(minor_sym, minor)
101
+ instance_variable_set(patch_sym, patch)
102
+ end
103
+
104
+ def write_uint16(buffer, value)
105
+ buffer.write([value].pack("v"))
106
+ end
107
+
108
+ def read_uint16(stream)
109
+ data = stream.read(2)
110
+ raise ArgumentError, "Unexpected end of stream" if data.nil?
111
+
112
+ data.unpack1("v")
113
+ end
114
+
115
+ def serialize_cwd(buffer)
116
+ if @cwd
117
+ buffer.write([1].pack("C"))
118
+ write_string(buffer, @cwd)
119
+ else
120
+ buffer.write([0].pack("C"))
121
+ end
122
+ end
123
+
124
+ def serialize_versions(buffer)
125
+ write_uint16(buffer, @ruby_version_major)
126
+ write_uint16(buffer, @ruby_version_minor)
127
+ write_uint16(buffer, @ruby_version_patch)
128
+ write_uint16(buffer, @tebako_version_major)
129
+ write_uint16(buffer, @tebako_version_minor)
130
+ write_uint16(buffer, @tebako_version_patch)
131
+ end
132
+
133
+ def write_string(buffer, str)
134
+ write_uint16(buffer, str.size)
135
+ buffer.write(str)
136
+ end
137
+
138
+ def read_string(stream)
139
+ size = read_uint16(stream)
140
+ stream.read(size)
141
+ end
142
+ end
143
+ end