tebako 0.14.0 → 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.
@@ -33,7 +33,9 @@ require "rbconfig"
33
33
  require_relative "codegen"
34
34
  require_relative "error"
35
35
  require_relative "options_manager"
36
+ require_relative "runtime_manager"
36
37
  require_relative "scenario_manager"
38
+ require_relative "stitcher"
37
39
  require_relative "packager_lite"
38
40
 
39
41
  # Tebako - an executable packager
@@ -85,10 +87,32 @@ module Tebako
85
87
  check_warnings(options_manager)
86
88
  puts options_manager.press_announce(scenario_manager.msys?)
87
89
 
90
+ if options_manager.prebuilt_runtime?
91
+ do_press_prebuilt(options_manager, scenario_manager)
92
+ else
93
+ do_press_source(options_manager, scenario_manager)
94
+ end
95
+ end
96
+
97
+ def do_press_source(options_manager, scenario_manager)
88
98
  do_press_runtime(options_manager, scenario_manager)
89
99
  do_press_application(options_manager, scenario_manager)
90
100
  end
91
101
 
102
+ # Press onto a prebuilt runtime package: resolve (download/verify/cache)
103
+ # the runtime, deploy the application image, stitch, re-sign on macOS.
104
+ def do_press_prebuilt(options_manager, scenario_manager)
105
+ Tebako::Packager.check_prebuilt_env!(options_manager.stash_dir, options_manager.deps_bin_dir)
106
+ runtime_path = Tebako::RuntimeManager.resolve(options_manager.ruby_ver, options_manager.host_platform)
107
+ app_image = Tebako::Packager.build_app_image(options_manager, scenario_manager)
108
+
109
+ images = [{ path: app_image, mount_point: scenario_manager.fs_mount_point,
110
+ format_id: Tebako::Stitcher::FORMAT_DWARFS }] + options_manager.images
111
+ package = "#{options_manager.package}#{scenario_manager.exe_suffix}"
112
+ Tebako::Stitcher.stitch(runtime_path, images: images, output: package)
113
+ puts "Created tebako #{options_manager.output_type_first} at \"#{package}\""
114
+ end
115
+
92
116
  def do_press_application(options_manager, scenario_manager)
93
117
  return unless %w[both application].include?(options_manager.mode)
94
118
 
data/lib/tebako/error.rb CHANGED
@@ -47,6 +47,16 @@ module Tebako
47
47
  117 => "Failed to load Gemfile.lock",
48
48
  118 => "Bundler version in Gemfile.lock does satisfy minimal Tebako version requirememnts",
49
49
  119 => "Failed to find compatible bundler version",
50
+ 120 => "No prebuilt tebako runtime package for the requested Ruby/platform combination",
51
+ 121 => "SHA256 checksum mismatch for downloaded tebako runtime package",
52
+ 122 => "Failed to download tebako runtime package",
53
+ 123 => "TEBAKO_OFFLINE is set and the requested tebako runtime package is not cached",
54
+ 124 => "Runtime package release carries no usable package index",
55
+ 125 => "Timed out waiting for the runtime package cache lock",
56
+ 126 => "Invalid stitch specification",
57
+ 127 => "Stitch input file is not accessible",
58
+ 128 => "Prebuilt runtime press requires the packaging environment (run 'tebako setup' first)",
59
+ 130 => "Option combination is not supported",
50
60
  201 => "Warning. Could not create cache version file"
51
61
  }.freeze
52
62
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 [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
+ module Tebako
29
+ # Launcher ABI v1 — the bootstrap → runtime handoff contract of the
30
+ # three-part package model (spec §4.4).
31
+ #
32
+ # A lean package is [tebako-bootstrap][.tfs image slots][tpkg trailer].
33
+ # The bootstrap (tamatebako/tebako-bootstrap) parses the trailer of its own
34
+ # executable, resolves the language runtime into the shared cache, and
35
+ # execs it as:
36
+ #
37
+ # <runtime> --tebako-image <file>:<slot>:<mount-point> ...
38
+ # --tebako-entry <argv0> <user args...>
39
+ #
40
+ # The runtime (a tebako-packaged interpreter built from this repo) consumes
41
+ # that handoff in src/tebako-main.cpp:
42
+ #
43
+ # --tebako-image <file>:<slot>:<mount-point>
44
+ # Repeatable, one per image slot, in slot order. <file> is a package
45
+ # file carrying a tpkg manifest trailer (spec §4.3); <slot> is the
46
+ # 0-based index into that file's slot table; <mount-point> is the
47
+ # memfs path the image is mounted at. The runtime reads the trailer
48
+ # of <file>, takes the slot's recorded offset/size, and mounts the
49
+ # image directly out of <file> — no extraction, no temp copies.
50
+ # The image whose mount point equals the runtime's compiled-in
51
+ # fs_mount_point becomes the root filesystem (fallback: the first
52
+ # image given); the remaining images are mounted as extra slots.
53
+ #
54
+ # --tebako-entry <argv0>
55
+ # Not repeatable. <argv0> is the lean package's original argv[0] (a
56
+ # host path as invoked by the user — not a path inside the memfs);
57
+ # the runtime presents it as the application program name. Every
58
+ # argument after <argv0> is application argv and is passed through
59
+ # to the interpreter untouched; tebako option scanning stops here.
60
+ # The runtime then runs its compiled-in entry point (for prebuilt
61
+ # runtimes, the /local/stub.rb dispatcher inside the root image).
62
+ #
63
+ # --tebako-launcher-abi <n>
64
+ # Not repeatable, optional. States the launcher ABI version the
65
+ # bootstrap speaks; the runtime refuses the handoff when <n> exceeds
66
+ # the ABI it supports, naming both versions. tebako-bootstrap v1 does
67
+ # not emit this option — it validates the trailer's launcher_abi
68
+ # field itself and encodes the ABI in the runtime name it resolves
69
+ # (runtime_ref "...;tebako=<abi>") — but the runtime accepts the
70
+ # option so the check exists on both sides of the handoff.
71
+ #
72
+ # The three option spellings above are exactly what tebako-bootstrap
73
+ # v1 emits (space-separated values, images before --tebako-entry); the
74
+ # runtime additionally accepts the --opt=value form of each.
75
+ #
76
+ # Versioning: VERSION is bumped whenever the handoff changes incompatibly.
77
+ # This file is the Ruby-side constant set; src/tebako-main.cpp carries the
78
+ # matching C++ constants — keep the two in sync.
79
+ module LauncherAbi
80
+ # Launcher ABI version implemented by this gem and by tebako-main.cpp
81
+ VERSION = 1
82
+
83
+ # Repeatable: hand one image slot of a package file to the runtime
84
+ IMAGE_ARG = "--tebako-image"
85
+
86
+ # Separator: value is the package argv[0]; the rest is application argv
87
+ ENTRY_ARG = "--tebako-entry"
88
+
89
+ # Optional: launcher ABI version the bootstrap speaks
90
+ VERSION_ARG = "--tebako-launcher-abi"
91
+
92
+ # Format one --tebako-image value per the ABI: "<file>:<slot>:<mount-point>".
93
+ # (The parser splits on the last two colons, so <file> may itself contain
94
+ # colons — e.g. Windows drive prefixes.)
95
+ def self.image_spec(file, slot, mount_point)
96
+ "#{file}:#{slot}:#{mount_point}"
97
+ end
98
+ end
99
+ end
@@ -33,6 +33,7 @@ require "rbconfig"
33
33
  require_relative "codegen"
34
34
  require_relative "error"
35
35
  require_relative "ruby_version"
36
+ require_relative "stitcher"
36
37
  require_relative "version"
37
38
 
38
39
  # Tebako - an executable packager
@@ -159,6 +160,39 @@ module Tebako
159
160
  @mode ||= @options["mode"].nil? ? "bundle" : @options["mode"]
160
161
  end
161
162
 
163
+ # Runtime provenance: "prebuilt" (resolve/download a prebuilt runtime
164
+ # package and stitch the application image onto it) or "source" (the
165
+ # Stage-2 source build). Default is "prebuilt" for the bundle mode;
166
+ # other modes always build from source, and --build-runtime forces the
167
+ # source path everywhere (back-compat alias for '--runtime source').
168
+ def runtime_source
169
+ @runtime_source ||= checked_runtime_source(requested_runtime_source)
170
+ end
171
+
172
+ def prebuilt_runtime?
173
+ runtime_source == "prebuilt"
174
+ end
175
+
176
+ # Additional images for the stitched package, from repeatable
177
+ # '--image <path>:<mount-point>' (split on the last colon so Windows
178
+ # drive-letter paths survive)
179
+ def images
180
+ @images ||= Array(@options["image"]).map do |spec|
181
+ path, _sep, mount = spec.to_s.rpartition(":")
182
+ if path.empty? || mount.empty?
183
+ Tebako.packaging_error(130, "invalid --image specification '#{spec}' ('<path>:<mount-point>' expected)")
184
+ end
185
+
186
+ { path: path, mount_point: mount, format_id: Tebako::Stitcher::FORMAT_DWARFS }
187
+ end
188
+ end
189
+
190
+ # Platform id of the host, as used by tebako-runtime-ruby package names
191
+ # (e.g. "macos-arm64", "linux-gnu-x86_64")
192
+ def host_platform(ostype = RUBY_PLATFORM, arch = RbConfig::CONFIG["host_cpu"])
193
+ @host_platform ||= "#{host_os_id(ostype)}-#{host_arch_id(arch)}"
194
+ end
195
+
162
196
  def output_folder
163
197
  @output_folder ||= File.join(prefix, "o")
164
198
  end
@@ -334,5 +368,42 @@ module Tebako
334
368
  def stash_dir_all
335
369
  @stash_dir_all ||= File.join(deps, "stash")
336
370
  end
371
+
372
+ private
373
+
374
+ def requested_runtime_source
375
+ explicit = @options["runtime"]
376
+ return "source" if @options["build-runtime"]
377
+ return mode == "bundle" ? "prebuilt" : "source" if explicit.nil?
378
+
379
+ explicit
380
+ end
381
+
382
+ def checked_runtime_source(source)
383
+ if source == "prebuilt" && mode != "bundle"
384
+ Tebako.packaging_error(130, "--runtime prebuilt is supported in bundle mode only (mode: '#{mode}')")
385
+ end
386
+ source
387
+ end
388
+
389
+ def host_os_id(ostype)
390
+ case ostype
391
+ when /msys|mingw|cygwin/ then "windows"
392
+ when /darwin/ then "macos"
393
+ when /linux-musl/ then "linux-musl"
394
+ when /linux/ then "linux-gnu"
395
+ else
396
+ Tebako.packaging_error(112, ostype)
397
+ end
398
+ end
399
+
400
+ def host_arch_id(arch)
401
+ case arch
402
+ when /^(x86_64|amd64)$/ then "x86_64"
403
+ when /^(aarch64|arm64)$/ then "arm64"
404
+ else
405
+ Tebako.packaging_error(112, arch)
406
+ end
407
+ end
337
408
  end
338
409
  end
@@ -36,37 +36,52 @@ module Tebako
36
36
  # rubocop:disable Style/WordArray
37
37
  DARWIN_BREW_LIBS = [
38
38
  ["zlib", "z"], ["gdbm", "gdbm"], ["readline", "readline"], ["libffi", "ffi"],
39
- ["ncurses", "ncurses"], ["lz4", "lz4"], ["xz", "lzma"], ["libyaml", "yaml"],
40
- ["boost", "boost_chrono"], ["double-conversion", "double-conversion"]
39
+ ["ncurses", "ncurses"], ["lz4", "lz4"], ["xz", "lzma"], ["libyaml", "yaml"]
41
40
  ].freeze
42
41
 
43
42
  DARWIN_BREW_LIBS_PRE_31 = [["openssl@1.1", "ssl"], ["openssl@1.1", "crypto"]].freeze
44
43
 
45
44
  DARWIN_BREW_LIBS_31 = [["openssl@3", "ssl"], ["openssl@3", "crypto"]].freeze
46
45
 
47
- DARWIN_DEP_LIBS_1 = ["folly", "fsst", "metadata_thrift", "thrift_light", "xxhash", "zstd"].freeze
48
- DARWIN_DEP_LIBS_2 = ["glog", "gflags", "brotlienc", "brotlidec", "brotlicommon", "fmt"].freeze
46
+ DARWIN_DEP_LIBS_1 = ["tfs", "tebako_dirent_helper_c"].freeze
47
+ # Referenced by full path from the vcpkg triplet lib dir (see
48
+ # darwin_libraries): Apple ld does not implement the GNU-style
49
+ # -l:<filename> library search, so -l:libX.a refs do not resolve.
50
+ DARWIN_DEP_LIBS_2 = ["dwarfs_reader", "dwarfs_common", "dwarfs_metadata_legacy",
51
+ "dwarfs_decompressor", "flatbuffers", "zip",
52
+ "fmt", "xxhash", "zstd",
53
+ "brotlidec", "brotlienc", "brotlicommon",
54
+ "bz2", "boost_filesystem", "boost_chrono"].freeze
49
55
  # rubocop:enable Style/WordArray
50
56
 
51
- LIBTEBAKOFS = "-Wl,--push-state,--whole-archive -l:libtebako-fs.a -Wl,--pop-state"
52
-
57
+ # --start-group/--end-group around the libtfs + transitive static archives:
58
+ # the dwarfs reader set has circular member-level references that trip GNU
59
+ # ld's single-pass scanning when built with clang (compression registrar).
60
+ # libtfs (libtfs.a + its pure-C dirent helper) and the transitive static
61
+ # set resolved by vcpkg into deps/vcpkg_installed/<triplet>/lib: the
62
+ # dwarfs reader side, flatbuffers, zip and the C++ support libs.
63
+ # Compression codecs register explicitly (compression_registry ctor),
64
+ # so no --whole-archive compression lib is needed anymore.
53
65
  COMMON_LINUX_LIBRARIES = [
54
- LIBTEBAKOFS, "-l:libdwarfs-wr.a", "-l:libdwarfs.a", "LIBCOMPRESSION",
55
- "-l:libfolly.a", "-l:libfsst.a", "-l:libmetadata_thrift.a", "-l:libthrift_light.a",
56
- "-l:libxxhash.a", "-l:libfmt.a", "-l:libdouble-conversion.a", "-l:libglog.a",
57
- "-l:libgflags.a", "-l:libevent.a"
66
+ "-Wl,--push-state,--whole-archive -l:libtebako-fs.a -Wl,--pop-state",
67
+ "-l:libtfs.a", "-l:libtebako_dirent_helper_c.a",
68
+ "-l:libdwarfs_reader.a", "-l:libdwarfs_common.a", "-l:libdwarfs_metadata_legacy.a",
69
+ "-l:libdwarfs_decompressor.a", "-l:libflatbuffers.a", "-l:libzip.a",
70
+ "-l:libfmt.a", "-l:libxxhash.a", "-l:libboost_filesystem.a",
71
+ "-l:libboost_chrono.a"
58
72
  ].freeze
59
73
 
60
74
  COMMON_ARCHIEVE_LIBRARIES = [
61
- "-l:libarchive.a", "-l:liblz4.a", "-l:libz.a", "-l:libzstd.a",
62
- "-l:libbrotlienc.a", "-l:libbrotlidec.a", "-l:libbrotlicommon.a", "-l:liblzma.a"
75
+ "-l:liblz4.a", "-l:libz.a", "-l:libzstd.a",
76
+ "-l:libbrotlienc.a", "-l:libbrotlidec.a", "-l:libbrotlicommon.a",
77
+ "-l:liblzma.a", "-l:libbz2.a"
63
78
  ].freeze
64
79
 
65
80
  LINUX_GNU_LIBRARIES = [
66
81
  "-l:libiberty.a", "-l:libacl.a", "-l:libssl.a", "-l:libcrypto.a",
67
82
  "-l:libgdbm.a", "-l:libreadline.a", "-l:libtinfo.a", "-l:libffi.a",
68
83
  "-l:libncurses.a", "-l:libjemalloc.a", "-l:libcrypt.a", "-l:libanl.a",
69
- "LIBYAML", "-l:libboost_system.a", "-l:libboost_chrono.a", "-l:libutil.a",
84
+ "LIBYAML", "-l:libutil.a",
70
85
  "-l:libstdc++.a", "-lgcc_eh", "-l:libunwind.a", "-l:liblzma.a",
71
86
  "-l:librt.a", "-ldl", "-lpthread", "-lm"
72
87
  ].freeze
@@ -74,15 +89,15 @@ module Tebako
74
89
  LINUX_MUSL_LIBRARIES = [
75
90
  "-l:libiberty.a", "-l:libacl.a", "-l:libssl.a", "-l:libcrypto.a",
76
91
  "-l:libreadline.a", "-l:libgdbm.a", "-l:libffi.a", "-l:libncurses.a",
77
- "-l:libjemalloc.a", "-l:libcrypt.a", "LIBYAML", "-l:libboost_system.a",
78
- "-l:libboost_chrono.a", "-l:librt.a", "-l:libstdc++.a", "-lgcc_eh",
79
- " -l:libunwind.a", "-l:liblzma.a", "-ldl", "-lpthread"
92
+ "-l:libjemalloc.a", "-l:libcrypt.a", "LIBYAML",
93
+ "-l:librt.a", "-l:libstdc++.a", "-lgcc_eh",
94
+ " -l:libunwind.a", "-l:liblzma.a", "-ldl", "-lpthread"
80
95
  ].freeze
81
96
 
82
97
  MSYS_LIBRARIES = [
83
98
  "-l:liblz4.a", "-l:libz.a", "-l:libzstd.a", "-l:liblzma.a",
84
99
  "-l:libncurses.a", "-l:liblzma.a", "-l:libiberty.a", "LIBYAML",
85
- "-l:libffi.a", "-l:libboost_chrono-mt.a", "-l:libstdc++.a", "-l:libdl.a",
100
+ "-l:libffi.a", "-l:libstdc++.a", "-l:libdl.a",
86
101
  "-static-libgcc", "-static-libstdc++", "-l:libssl.a", "-l:libcrypto.a",
87
102
  "-l:libz.a", "-l:libwinpthread.a", "-lcrypt32", "-lshlwapi",
88
103
  "-lwsock32", "-liphlpapi", "-limagehlp", "-lbcrypt",
@@ -90,21 +105,21 @@ module Tebako
90
105
  ].freeze
91
106
 
92
107
  def linux_gnu_libraries(ruby_ver, with_compression)
93
- libraries = COMMON_LINUX_LIBRARIES + COMMON_ARCHIEVE_LIBRARIES + LINUX_GNU_LIBRARIES
108
+ libraries = ["-Wl,--start-group"] + COMMON_LINUX_LIBRARIES + COMMON_ARCHIEVE_LIBRARIES +
109
+ ["-Wl,--end-group"] + LINUX_GNU_LIBRARIES
94
110
  linux_libraries(libraries, ruby_ver, with_compression)
95
111
  end
96
112
 
97
113
  def linux_musl_libraries(ruby_ver, with_compression)
98
- libraries = COMMON_LINUX_LIBRARIES + COMMON_ARCHIEVE_LIBRARIES + LINUX_MUSL_LIBRARIES
114
+ libraries = ["-Wl,--start-group"] + COMMON_LINUX_LIBRARIES + COMMON_ARCHIEVE_LIBRARIES +
115
+ ["-Wl,--end-group"] + LINUX_MUSL_LIBRARIES
99
116
  linux_libraries(libraries, ruby_ver, with_compression)
100
117
  end
101
118
 
102
- def linux_libraries(libraries, ruby_ver, with_compression)
119
+ def linux_libraries(libraries, ruby_ver, _with_compression)
103
120
  libraries.map! do |lib|
104
121
  if lib == "LIBYAML"
105
122
  PatchHelpers.yaml_reference(ruby_ver)
106
- elsif lib == "LIBCOMPRESSION"
107
- with_compression ? "-Wl,--push-state,--whole-archive -l:libdwarfs_compression.a -Wl,--pop-state" : ""
108
123
  else
109
124
  lib
110
125
  end
@@ -122,17 +137,22 @@ module Tebako
122
137
  brew_libs.each { |lib| libs << "#{PatchHelpers.get_prefix_macos(lib[0]).chop}/lib/lib#{lib[1]}.a " }
123
138
  end
124
139
 
125
- def darwin_libraries(deps_lib_dir, ruby_ver, with_compression)
140
+ def darwin_libraries(deps_lib_dir, ruby_ver, _with_compression)
126
141
  libs = String.new
127
142
 
128
143
  DARWIN_DEP_LIBS_1.each { |lib| libs << "#{deps_lib_dir}/lib#{lib}.a " }
129
144
  process_brew_libs!(libs, ruby_ver.ruby31? ? DARWIN_BREW_LIBS_31 : DARWIN_BREW_LIBS_PRE_31)
130
145
  process_brew_libs!(libs, DARWIN_BREW_LIBS)
131
146
 
132
- DARWIN_DEP_LIBS_2.each { |lib| libs << "#{deps_lib_dir}/lib#{lib}.a " }
133
-
134
- compression_lib = with_compression ? "-force_load #{deps_lib_dir}/libdwarfs_compression.a" : ""
135
- "-ltebako-fs -ldwarfs-wr -ldwarfs #{compression_lib} #{libs} -ljemalloc -lc++ -lc++abi"
147
+ # The vcpkg set by full path: Apple ld does not implement -l:<filename>
148
+ vcpkg_lib_dir = Dir.glob(File.join(deps_lib_dir, "..", "vcpkg_installed", "*", "lib")).min
149
+ DARWIN_DEP_LIBS_2.each { |lib| libs << "#{vcpkg_lib_dir}/lib#{lib}.a " }
150
+ # No allocator link: brew's static libjemalloc.a crashes tebako
151
+ # binaries at startup on the XCode 15.4 runners (je_arena_ralloc
152
+ # SEGV at builtin init), and the dylib fails the test_101
153
+ # no-shared-libs assertion. System malloc until a properly built
154
+ # static jemalloc ships with libtfs-deps.
155
+ "-ltebako-fs #{libs}-lc++ -lc++abi"
136
156
  end
137
157
 
138
158
  # .....................................................
@@ -68,7 +68,7 @@ module Tebako
68
68
  win32/file.c
69
69
  ].freeze
70
70
 
71
- class << self
71
+ class << self # rubocop:disable Metrics/ClassLength
72
72
  # Create implib
73
73
  def create_implib(src_dir, package_src_dir, app_name, ruby_ver)
74
74
  a_name = File.basename(app_name, ".*")
@@ -115,6 +115,45 @@ module Tebako
115
115
  FileUtils.cp_r "#{stash_dir}/.", src_dir
116
116
  end
117
117
 
118
+ # Check that the packaging environment the prebuilt-runtime press
119
+ # needs (pristine Ruby stash + mkdwarfs) is in place
120
+ def check_prebuilt_env!(stash_dir, deps_bin_dir)
121
+ missing = []
122
+ missing << "Ruby environment stash (#{stash_dir})" unless Dir.exist?(stash_dir)
123
+ missing << "mkdwarfs (#{deps_bin_dir})" if Dir.glob(File.join(deps_bin_dir, "mkdwarfs*")).empty?
124
+ return if missing.empty?
125
+
126
+ Tebako.packaging_error(128, missing.join(", "))
127
+ end
128
+
129
+ # Deploy the application and build its DwarFS image for stitching onto
130
+ # a prebuilt runtime. Same layout as the bundle-mode image, plus an
131
+ # entry dispatcher at /local/stub.rb (the runtime's compiled-in entry).
132
+ def build_app_image(options_manager, scenario_manager) # rubocop:disable Metrics/AbcSize
133
+ init(options_manager.stash_dir, options_manager.data_src_dir, options_manager.data_pre_dir,
134
+ options_manager.data_bin_dir)
135
+ deploy(options_manager.data_src_dir, options_manager.data_pre_dir, options_manager.rv,
136
+ options_manager.root, scenario_manager.fs_entrance, options_manager.cwd)
137
+ write_entry_dispatcher(options_manager.data_src_dir, scenario_manager, options_manager.cwd)
138
+ mkdwarfs(options_manager.deps_bin_dir, options_manager.data_bundle_file, options_manager.data_src_dir)
139
+ options_manager.data_bundle_file
140
+ end
141
+
142
+ # The prebuilt runtime packages are pressed in 'runtime' mode, so their
143
+ # compiled-in entry point is /local/stub.rb. A stitched package mounts
144
+ # the application image as the root filesystem; the dispatcher written
145
+ # here receives control from the runtime and loads the real entry point
146
+ # (replicating the bundle-mode working directory when --cwd was given).
147
+ def write_entry_dispatcher(data_src_dir, scenario_manager, cwd)
148
+ dispatcher = +""
149
+ dispatcher << "Dir.chdir(\"#{scenario_manager.fs_mount_point}/#{cwd}\")\n" unless cwd.nil?
150
+ dispatcher << "load \"#{scenario_manager.fs_mount_point}#{scenario_manager.fs_entry_point}\"\n"
151
+
152
+ local_dir = File.join(data_src_dir, "local")
153
+ FileUtils.mkdir_p(local_dir)
154
+ File.write(File.join(local_dir, "stub.rb"), dispatcher)
155
+ end
156
+
118
157
  def mkdwarfs(deps_bin_dir, data_bin_file, data_src_dir, descriptor = nil)
119
158
  puts "-- Running mkdwarfs script"
120
159
  FileUtils.chmod("a+x", Dir.glob(File.join(deps_bin_dir, "mkdwarfs*")))