tebako 0.15.1 → 0.15.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 349d5b0a59f98021f05bae37a7c62efd9282900f5d4c161e8567424a082d21a1
4
- data.tar.gz: 74dabe977337bcbe8b63a82cc82653242e852542e629a8698f3f6828c59cebf3
3
+ metadata.gz: 40cccec56036f14133e05046f858915c7f6ae657c11f3560b647d791c2cc952a
4
+ data.tar.gz: 79022b3acb1d0457a996f671ca93e69ac024107156f31445f27159a1d81ac7da
5
5
  SHA512:
6
- metadata.gz: eed8e60a9fe1c1185478c7f9c70d4a4e1af7c5f24c853c6a1ac85527c04bb7b281c5efdbc3cb9afd2ee31dd56e4d5a6d0e7975388ea180dc03d93fa4d703b845
7
- data.tar.gz: 1355806d02bbe169d73d6ca746a5052d24dba4f49c398f514d25d8c35592b2d451f97629e60801d656ad755b70356d201a1211dd882a611fe1a083900b28c0e1
6
+ metadata.gz: 318cab0bce4b6528efe1e1c215b473344311f68fcef2bc7ac379cdc81248c06afac942fff0b55a9dd5e45b88f5989031597ef633c4e6e6d69ed2478a83138b13
7
+ data.tar.gz: 0f3b13569b1ffd26de43d6233bfb2579fe91e1d6107b51370562c64e2f0f87021813bb1129eaafaae9f68202e0c37d9d03cf9d439ce5610ff857493278fe6838
data/CMakeLists.txt CHANGED
@@ -175,7 +175,7 @@ string(CONCAT RUBY_API_VER ${RUBY_VER_BASE} ".0")
175
175
  # NOTE: the env override in def_ext_prj_g (tebako-tools) is currently broken
176
176
  # (`if (DEFINED ENV{${TAG})` is missing a closing brace, so the test never
177
177
  # fires); this default is the operative tag selection.
178
- def_ext_prj_g(DWARFS_WR "v0.12.8")
178
+ def_ext_prj_g(DWARFS_WR "v0.12.9")
179
179
 
180
180
  # Provisions libtfs (prebuilt package or vcpkg toolchain for the source
181
181
  # build), the prebuilt mkdwarfs, and -- for the prebuilt path -- the
data/README.adoc CHANGED
@@ -420,7 +420,7 @@ tebako press \
420
420
  [-c|--cwd=<package-current-working-directory>] \
421
421
  [-D|--devmode] \
422
422
  [-P|--patchelf] \
423
- [-m|--mode=<bundle|both|application|runtime>] \
423
+ [-m|--mode=<lean|fat|classic|bundle|both|application|runtime>] \
424
424
  [-u|--ref=<runtime-reference>] \
425
425
  [-t|--tebafile=<path-to-tebafile>]
426
426
  ----
@@ -452,6 +452,7 @@ On Windows, the output file is automatically appended the `.exe` extension.
452
452
 
453
453
  *** in `runtime` mode, the runtime package is created at `tebako-runtime`.
454
454
  *** in `application` mode, the application package is created at `<current-folder>/<entry-point-base-name>`.
455
+ *** in `lean`, `fat` and `classic` modes, the package is created at `<current-folder>/<entry-point-base-name>`.
455
456
  *** in `bundle` mode, the bundled package is created at `<current-folder>/<entry-point-base-name>`.
456
457
  *** in `both` mode, the runtime package is created at `<current-folder>/<entry-point-base-name>`,
457
458
  and the application package is created at `<current-folder>/<entry-point-base-name>.tebako`.
@@ -460,6 +461,7 @@ and the application package is created at `<current-folder>/<entry-point-base-na
460
461
 
461
462
  *** in `runtime` mode, the runtime package is named according to the `-o` option.
462
463
  *** in `application` mode, the application package is named according to the `-o` option.
464
+ *** in `lean`, `fat` and `classic` modes, the package is named according to the `-o` option.
463
465
  *** in `bundle` mode, the bundled package is named according to the `-o` option.
464
466
  *** in `both` mode, the runtime package is named according to the `-o` option,
465
467
  and the application package is named according to the `-o` option with the `.tebako` extension.
@@ -502,10 +504,13 @@ For example, a package created at Ubuntu 20 system can be used on Ubuntu 22.
502
504
  NOTE: The feature is exeprimental, we may consider another approach in the future.
503
505
 
504
506
  `--mode=<mode>`::
505
- (optional, defaults to `bundle`)
506
- Package output mode, determines whether the runtime and/or application are
507
- to be separately packaged.
507
+ (optional, defaults to `lean`)
508
+ Package output mode, determines the package layout (three-part or runtime-stitched)
509
+ and whether the runtime and/or application are separately packaged.
508
510
 
511
+ `lean`::: Create a three-part package: the tebako-bootstrap launcher plus the application image slot(s) and a tpkg manifest trailer carrying the runtime reference. At first run the bootstrap resolves (downloads, verifies and caches) the referenced prebuilt runtime into the machine-wide cache (`~/.tebako` or `$TEBAKO_HOME`); subsequent runs of any lean package built for the same runtime share that cache entry. The output file is named according to the `-o` option.
512
+ `fat`::: Like `lean`, but the runtime package is also embedded as a payload slot. The first run installs it into the cache without network access (works with `TEBAKO_OFFLINE=1`). The output file is named according to the `-o` option.
513
+ `classic`::: Stitch the application image onto a prebuilt runtime package (the Stage-3A layout; identical to `bundle` with the default `--runtime prebuilt`). The output file is named according to the `-o` option.
509
514
  `bundle`::: Create a single package bundling both the runtime and application. The output file is named according to the `-o` option.
510
515
  `both`::: Create separate packages for the runtime and application. Outputs two separate files: one for the runtime at the location specified at the `-o {filename}` and one for the application at `{filename}.package`.
511
516
  `application`::: Create the application package only. The output file is named according to the `-o` option.
@@ -82,9 +82,7 @@ endif()
82
82
  if(IS_GNU)
83
83
  set(LIBTFS_PLATFORM "linux-gnu-${__LIBTFS_ARCH}")
84
84
  elseif(IS_MUSL)
85
- if(NOT __LIBTFS_ARCH STREQUAL "x86_64")
86
- message(FATAL_ERROR "No prebuilt libtfs package for linux-musl-${__LIBTFS_ARCH}; use -DDWARFS_PRELOAD=OFF (source build)")
87
- endif()
85
+ # musl-arm64 prebuilt packages first ship in libtfs v0.12.9
88
86
  set(LIBTFS_PLATFORM "linux-musl-${__LIBTFS_ARCH}")
89
87
  elseif(IS_DARWIN)
90
88
  set(LIBTFS_PLATFORM "macos-${__LIBTFS_ARCH}")
@@ -48,9 +48,9 @@
48
48
  * offset size field
49
49
  * 0 8 u64 offset (image start, absolute file offset)
50
50
  * 8 8 u64 size (image length in bytes)
51
- * 16 4 u32 format_id (0=auto(magic), 1=dwarfs, 2=squashfs, 3=zip)
51
+ * 16 4 u32 format_id (0=auto(magic), 1=dwarfs, 2=squashfs, 3=zip, 4=runtime payload)
52
52
  * 20 4 u32 flags
53
- * 24 256 char mount_point[256] (UTF-8, NUL-padded)
53
+ * 24 256 char mount_point[256] (UTF-8, NUL-padded; empty for runtime payloads)
54
54
  *
55
55
  * Reader algorithm: read the fixed-size header at EOF - TPKG_HEADER_SIZE,
56
56
  * check the magic, verify header_crc32, then read slot_count slot records at
@@ -108,6 +108,9 @@ extern "C" {
108
108
  #define TPKG_FORMAT_DWARFS 1u
109
109
  #define TPKG_FORMAT_SQUASHFS 2u
110
110
  #define TPKG_FORMAT_ZIP 3u
111
+ /* runtime payload slot of a fat package: the bootstrap installs it into the
112
+ * shared cache at first run; it is never mounted as a filesystem image */
113
+ #define TPKG_FORMAT_RUNTIME 4u
111
114
 
112
115
  /* ---- error codes (returned by tpkg_errno) -------------------------------- */
113
116
 
@@ -157,7 +160,7 @@ int tpkg_read_mem(const void* data, size_t size, tpkg_manifest* out);
157
160
  int tpkg_write_fd(int fd, const tpkg_manifest* m);
158
161
 
159
162
  /* Magic-independent structural checks: version supported, slot_count in
160
- * 1..TPKG_MAX_SLOTS, offset+size non-overflowing, format_id <= TPKG_FORMAT_ZIP,
163
+ * 1..TPKG_MAX_SLOTS, offset+size non-overflowing, format_id <= TPKG_FORMAT_RUNTIME,
161
164
  * runtime_ref and mount_points NUL-terminated within their fixed fields. */
162
165
  int tpkg_validate(const tpkg_manifest* m);
163
166
 
@@ -397,7 +400,7 @@ int tpkg_validate(const tpkg_manifest* m)
397
400
  if (s->size > UINT64_MAX - s->offset) {
398
401
  return tpkg__fail(TPKG_ERR_INVALID);
399
402
  }
400
- if (s->format_id > TPKG_FORMAT_ZIP) {
403
+ if (s->format_id > TPKG_FORMAT_RUNTIME) {
401
404
  return tpkg__fail(TPKG_ERR_INVALID);
402
405
  }
403
406
  if (tpkg__strnlen(s->mount_point, TPKG_MOUNT_POINT_LEN) == TPKG_MOUNT_POINT_LEN) {
@@ -0,0 +1,158 @@
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
+ require_relative "runtime_manager"
29
+
30
+ # Tebako - an executable packager
31
+ module Tebako
32
+ # Resolution, download, verification and machine-wide caching of the
33
+ # tebako-bootstrap launcher binaries published by tamatebako/tebako-bootstrap.
34
+ # The launcher is the base binary of lean/fat three-part packages.
35
+ #
36
+ # Cache layout (rooted at $TEBAKO_HOME or ~/.tebako, shared with the
37
+ # runtime package cache):
38
+ # bootstraps/tebako-bootstrap-<version>-<platform>/
39
+ # tebako-bootstrap-<version>-<platform>[.exe]
40
+ # sha256 -- digest the installed file was verified against
41
+ # origin -- URL the binary was downloaded from
42
+ #
43
+ # All download/verify/lock machinery is inherited from RuntimeManager; only
44
+ # the release-shape specifics (asset naming, manifest.json object format,
45
+ # SHA256SUMS index name, mirror env var) differ.
46
+ class BootstrapManager < RuntimeManager
47
+ DEFAULT_MIRROR = "https://github.com/tamatebako/tebako-bootstrap/releases/download"
48
+ BOOTSTRAPS_DIR = "bootstraps"
49
+ INDEX_FILES = ["manifest.json", "SHA256SUMS"].freeze
50
+
51
+ # tebako-bootstrap release consumed by default (TEBAKO_BOOTSTRAP_VERSION
52
+ # overrides). 0.2.0 is the first release that installs a fat package's
53
+ # runtime payload slot into the cache at first run.
54
+ BOOTSTRAP_VERSION = "0.2.0"
55
+ PAYLOAD_MIN_VERSION = "0.2.0"
56
+
57
+ class << self
58
+ def default_version
59
+ version = ENV.fetch("TEBAKO_BOOTSTRAP_VERSION", nil)
60
+ version.nil? || version.empty? ? BOOTSTRAP_VERSION : version.sub(/\Av/, "")
61
+ end
62
+
63
+ def resolve(platform, bootstrap_version: default_version)
64
+ new.resolve(platform, bootstrap_version: bootstrap_version)
65
+ end
66
+ end
67
+
68
+ def resolve(platform, bootstrap_version: self.class.default_version)
69
+ super(bootstrap_version, platform, tebako_version: bootstrap_version)
70
+ end
71
+
72
+ private
73
+
74
+ def default_mirror
75
+ DEFAULT_MIRROR
76
+ end
77
+
78
+ def mirror_env_var
79
+ "TEBAKO_BOOTSTRAP_MIRROR"
80
+ end
81
+
82
+ def index_files
83
+ INDEX_FILES
84
+ end
85
+
86
+ def cache_subdir
87
+ BOOTSTRAPS_DIR
88
+ end
89
+
90
+ def release_name
91
+ "tebako-bootstrap"
92
+ end
93
+
94
+ def entry_dir(_version, platform, bootstrap_version)
95
+ File.join(@cache_root, BOOTSTRAPS_DIR, "tebako-bootstrap-#{bootstrap_version}-#{platform}")
96
+ end
97
+
98
+ def runtime_filename(_version, platform, bootstrap_version)
99
+ suffix = platform.start_with?("windows") ? ".exe" : ""
100
+ "tebako-bootstrap-#{bootstrap_version}-#{platform}#{suffix}"
101
+ end
102
+
103
+ def runtime_ref(_version, platform, bootstrap_version)
104
+ "tebako-bootstrap@#{bootstrap_version} (#{platform})"
105
+ end
106
+
107
+ def find_entry(index, _version, platform, bootstrap_version)
108
+ entry = index.find { |candidate| candidate["platform"] == platform }
109
+ return entry if entry
110
+
111
+ platforms = index.map { |candidate| candidate["platform"] }
112
+ Tebako.packaging_error(131, "no tebako-bootstrap package for #{platform} (bootstrap #{bootstrap_version}). " \
113
+ "Available: #{platforms.join(", ")}.")
114
+ end
115
+
116
+ def offline_check(ref, bootstrap_version)
117
+ return unless offline?
118
+
119
+ Tebako.packaging_error(132, "#{ref} is not cached and downloads are disabled " \
120
+ "(release index: #{index_urls(bootstrap_version).join(", ")}; " \
121
+ "#{mirror_env_var}=#{@mirror})")
122
+ end
123
+
124
+ # tebako-bootstrap's manifest.json is an object
125
+ # ({name:, version:, assets: [{platform:, file:, sha256:}]}), not the
126
+ # entry array tebako-runtime-ruby publishes
127
+ def parse_manifest(body, _bootstrap_version)
128
+ manifest_assets(body)
129
+ .select { |asset| asset["platform"] && asset["file"] && asset["sha256"] }
130
+ .map { |asset| manifest_entry(asset) }
131
+ end
132
+
133
+ def manifest_assets(body)
134
+ data = JSON.parse(body)
135
+ assets = data.is_a?(Hash) ? data["assets"] : nil
136
+ raise IndexUnavailable, "manifest.json has no assets array" unless assets.is_a?(Array)
137
+
138
+ assets
139
+ rescue JSON::ParserError
140
+ raise IndexUnavailable, "manifest.json is not valid JSON"
141
+ end
142
+
143
+ def manifest_entry(asset)
144
+ { "platform" => asset["platform"], "filename" => asset["file"], "sha256" => asset["sha256"].downcase }
145
+ end
146
+
147
+ def parse_sha256sums(body, bootstrap_version)
148
+ pattern = /\Atebako-bootstrap-#{Regexp.escape(bootstrap_version)}-(.+?)(?:\.exe)?\z/
149
+ body.each_line.filter_map do |line|
150
+ sha256, file = line.strip.split(/\s+/, 2)
151
+ match = file && pattern.match(file.sub(/\A\*/, ""))
152
+ next unless match
153
+
154
+ { "platform" => match[1], "filename" => file.sub(/\A\*/, ""), "sha256" => sha256.downcase }
155
+ end
156
+ end
157
+ end
158
+ end
data/lib/tebako/cli.rb CHANGED
@@ -174,9 +174,10 @@ module Tebako
174
174
  DESC
175
175
 
176
176
  RUNTIME_DESCRIPTION = <<~DESC
177
- Runtime provenance: 'prebuilt' resolves/downloads a prebuilt tebako runtime package and stitches the
178
- #{" " * 65}# application image onto it (default for the 'bundle' mode); 'source' keeps the Stage-2 source build.
179
- #{" " * 65}# Modes other than 'bundle' always build from source.
177
+ Runtime provenance: 'prebuilt' resolves/downloads a prebuilt tebako runtime package (default for the
178
+ #{" " * 65}# 'bundle', 'classic', 'lean' and 'fat' modes); 'source' keeps the Stage-2 source build
179
+ #{" " * 65}# (not available for 'lean'/'fat' -- the bootstrap resolves tebako-runtime-ruby releases
180
+ #{" " * 65}# at run time). Modes other than those always build from source.
180
181
  DESC
181
182
 
182
183
  IMAGE_DESCRIPTION = <<~DESC
@@ -184,6 +185,16 @@ module Tebako
184
185
  #{" " * 65}# must be distinct. Prebuilt runtime only.
185
186
  DESC
186
187
 
188
+ MODE_DESCRIPTION = <<~DESC
189
+ Tebako press mode, 'lean' by default.
190
+ #{" " * 65}# 'lean' presses a three-part package (tebako-bootstrap + application image(s) + tpkg trailer);
191
+ #{" " * 65}# the runtime is resolved into the shared cache at first run.
192
+ #{" " * 65}# 'fat' is 'lean' plus the runtime package as a payload slot -- the first run installs it
193
+ #{" " * 65}# into the cache without network access.
194
+ #{" " * 65}# 'classic' stitches the application image onto a prebuilt runtime (Stage-3A layout);
195
+ #{" " * 65}# 'bundle', 'both', 'application' and 'runtime' keep their legacy behaviors.
196
+ DESC
197
+
187
198
  desc "press", "Press tebako image"
188
199
  method_option :cwd, type: :string, aliases: "-c", required: false, desc: CWD_DESCRIPTION
189
200
  method_option :"log-level", type: :string, aliases: "-l", required: false, enum: %w[error warn debug trace],
@@ -197,8 +208,9 @@ module Tebako
197
208
  enum: Tebako::RubyVersion::RUBY_VERSIONS.keys,
198
209
  desc: "Tebako package Ruby version, #{Tebako::RubyVersion::DEFAULT_RUBY_VERSION} by default"
199
210
  method_option :patchelf, aliases: "-P", type: :boolean, desc: RGP_DESCRIPTION
200
- method_option :mode, type: :string, aliases: "-m", required: false, enum: %w[bundle both runtime application],
201
- desc: "Tebako press mode, 'bundle' by default"
211
+ method_option :mode, type: :string, aliases: "-m", required: false,
212
+ enum: %w[lean fat classic bundle both runtime application],
213
+ desc: MODE_DESCRIPTION
202
214
  method_option :ref, type: :string, aliases: "-u", required: false, desc: REF_DESCRIPTION
203
215
  method_option :runtime, type: :string, required: false, enum: %w[prebuilt source], desc: RUNTIME_DESCRIPTION
204
216
  method_option :"build-runtime", type: :boolean, required: false,
@@ -25,13 +25,16 @@
25
25
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26
26
  # POSSIBILITY OF SUCH DAMAGE.
27
27
 
28
+ require "digest"
28
29
  require "etc"
29
30
  require "fileutils"
30
31
  require "pathname"
31
32
  require "rbconfig"
32
33
 
34
+ require_relative "bootstrap_manager"
33
35
  require_relative "codegen"
34
36
  require_relative "error"
37
+ require_relative "launcher_abi"
35
38
  require_relative "options_manager"
36
39
  require_relative "runtime_manager"
37
40
  require_relative "scenario_manager"
@@ -86,8 +89,13 @@ module Tebako
86
89
  options_manager.process_gemfile(scenario_manager.gemfile_path) if scenario_manager.with_gemfile
87
90
  check_warnings(options_manager)
88
91
  puts options_manager.press_announce(scenario_manager.msys?)
92
+ dispatch_press(options_manager, scenario_manager)
93
+ end
89
94
 
90
- if options_manager.prebuilt_runtime?
95
+ def dispatch_press(options_manager, scenario_manager)
96
+ if options_manager.three_part?
97
+ do_press_three_part(options_manager, scenario_manager)
98
+ elsif options_manager.prebuilt_runtime?
91
99
  do_press_prebuilt(options_manager, scenario_manager)
92
100
  else
93
101
  do_press_source(options_manager, scenario_manager)
@@ -113,6 +121,66 @@ module Tebako
113
121
  puts "Created tebako #{options_manager.output_type_first} at \"#{package}\""
114
122
  end
115
123
 
124
+ # Press a three-part package (Stage 3B): tebako-bootstrap + application
125
+ # image slot(s) + tpkg trailer with the runtime reference (launcher ABI
126
+ # v1). 'lean' (the default) resolves the runtime into the shared cache at
127
+ # first run; 'fat' additionally embeds the runtime package as a payload
128
+ # slot, so the first run installs it without any network access.
129
+ def do_press_three_part(options_manager, scenario_manager)
130
+ bootstrap_path, runtime_path = resolve_three_part_parts(options_manager)
131
+ app_image = Tebako::Packager.build_app_image(options_manager, scenario_manager)
132
+ images = three_part_images(options_manager, scenario_manager, app_image, runtime_path)
133
+ package = "#{options_manager.package}#{scenario_manager.exe_suffix}"
134
+ runtime_sha256 = runtime_path && Digest::SHA256.file(runtime_path).hexdigest
135
+ Tebako::Stitcher.stitch(bootstrap_path, images: images, output: package, lean: true,
136
+ ruby_version: options_manager.ruby_ver,
137
+ launcher_abi: Tebako::LauncherAbi::VERSION,
138
+ runtime_sha256: runtime_sha256)
139
+ puts "Created tebako #{options_manager.output_type_first} at \"#{package}\""
140
+ end
141
+
142
+ # Validate the options and the packaging environment, then resolve the
143
+ # bootstrap (and, for fat, the runtime payload) into the shared cache
144
+ def resolve_three_part_parts(options_manager)
145
+ options_manager.runtime_source # validates the mode/provenance combination
146
+ check_bootstrap_version!(options_manager)
147
+ Tebako::Packager.check_prebuilt_env!(options_manager.stash_dir, options_manager.deps_bin_dir)
148
+ runtime_path = fat_runtime_path(options_manager)
149
+ [Tebako::BootstrapManager.resolve(options_manager.host_platform), runtime_path]
150
+ end
151
+
152
+ def fat_runtime_path(options_manager)
153
+ return nil unless options_manager.fat?
154
+
155
+ Tebako::RuntimeManager.resolve(options_manager.ruby_ver, options_manager.host_platform)
156
+ end
157
+
158
+ def three_part_images(options_manager, scenario_manager, app_image, runtime_path)
159
+ images = [{ path: app_image, mount_point: scenario_manager.fs_mount_point,
160
+ format_id: Tebako::Stitcher::FORMAT_DWARFS }] + options_manager.images
161
+ images << { path: runtime_path, mount_point: "", format_id: Tebako::Stitcher::FORMAT_RUNTIME } if runtime_path
162
+ images
163
+ end
164
+
165
+ # The fat payload slot is installed by the bootstrap at first run — a
166
+ # capability added in tebako-bootstrap 0.2.0
167
+ def check_bootstrap_version!(options_manager)
168
+ return unless options_manager.fat?
169
+
170
+ version = Tebako::BootstrapManager.default_version
171
+ minimum = Tebako::BootstrapManager::PAYLOAD_MIN_VERSION
172
+ return if payload_capable?(version, minimum)
173
+
174
+ Tebako.packaging_error(134, "fat mode requires tebako-bootstrap >= #{minimum} (selected: #{version}; " \
175
+ "set TEBAKO_BOOTSTRAP_VERSION to a payload-capable release)")
176
+ end
177
+
178
+ def payload_capable?(version, minimum)
179
+ Gem::Version.new(version) >= Gem::Version.new(minimum)
180
+ rescue ArgumentError
181
+ false
182
+ end
183
+
116
184
  def do_press_application(options_manager, scenario_manager)
117
185
  return unless %w[both application].include?(options_manager.mode)
118
186
 
@@ -121,7 +189,7 @@ module Tebako
121
189
  end
122
190
 
123
191
  def do_press_runtime(options_manager, scenario_manager)
124
- return unless %w[both runtime bundle].include?(options_manager.mode)
192
+ return unless %w[both runtime bundle classic].include?(options_manager.mode)
125
193
 
126
194
  generate_files(options_manager, scenario_manager)
127
195
  merged_env = ENV.to_h.merge(scenario_manager.b_env)
data/lib/tebako/error.rb CHANGED
@@ -57,6 +57,9 @@ module Tebako
57
57
  127 => "Stitch input file is not accessible",
58
58
  128 => "Prebuilt runtime press requires the packaging environment (run 'tebako setup' first)",
59
59
  130 => "Option combination is not supported",
60
+ 131 => "No tebako-bootstrap package for the requested platform",
61
+ 132 => "TEBAKO_OFFLINE is set and the requested tebako-bootstrap package is not cached",
62
+ 134 => "Fat mode requires a payload-capable tebako-bootstrap release",
60
63
  201 => "Warning. Could not create cache version file"
61
64
  }.freeze
62
65
 
@@ -30,6 +30,12 @@ module Tebako
30
30
  # three-part package model (spec §4.4).
31
31
  #
32
32
  # A lean package is [tebako-bootstrap][.tfs image slots][tpkg trailer].
33
+ # A fat package has the same layout plus the runtime package itself as a
34
+ # payload slot (format_id TPKG_FORMAT_RUNTIME, empty mount point,
35
+ # ";sha256=<hex>" appended to the trailer's runtime_ref): at first run the
36
+ # bootstrap installs the payload into the shared cache instead of
37
+ # downloading it. Payload slots are never handed over as --tebako-image.
38
+ #
33
39
  # The bootstrap (tamatebako/tebako-bootstrap) parses the trailer of its own
34
40
  # executable, resolves the language runtime into the shared cache, and
35
41
  # execs it as:
@@ -41,6 +41,13 @@ require_relative "version"
41
41
  module Tebako
42
42
  # Cli helpers
43
43
  class OptionsManager # rubocop:disable Metrics/ClassLength
44
+ # Press modes producing three-part packages (tebako-bootstrap + image
45
+ # slots + tpkg trailer): 'lean' resolves the runtime at first run, 'fat'
46
+ # additionally embeds it as a payload slot (self-installing, offline)
47
+ THREE_PART_MODES = %w[lean fat].freeze
48
+ # Modes whose default runtime provenance is a prebuilt runtime package
49
+ PREBUILT_DEFAULT_MODES = %w[bundle classic lean fat].freeze
50
+
44
51
  def initialize(options)
45
52
  @options = options
46
53
  @rv = Tebako::RubyVersion.new(@options["Ruby"])
@@ -157,14 +164,24 @@ module Tebako
157
164
  end
158
165
 
159
166
  def mode
160
- @mode ||= @options["mode"].nil? ? "bundle" : @options["mode"]
167
+ @mode ||= @options["mode"].nil? ? "lean" : @options["mode"]
168
+ end
169
+
170
+ def three_part?
171
+ THREE_PART_MODES.include?(mode)
172
+ end
173
+
174
+ def fat?
175
+ mode == "fat"
161
176
  end
162
177
 
163
178
  # 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').
179
+ # package) or "source" (the Stage-2 source build). Default is "prebuilt"
180
+ # for the bundle/classic/lean/fat modes; other modes always build from
181
+ # source, and --build-runtime forces the source path everywhere it is
182
+ # allowed (back-compat alias for '--runtime source'). The three-part
183
+ # modes (lean/fat) require prebuilt runtime packages: the bootstrap
184
+ # resolves tebako-runtime-ruby releases at run time.
168
185
  def runtime_source
169
186
  @runtime_source ||= checked_runtime_source(requested_runtime_source)
170
187
  end
@@ -246,7 +263,7 @@ module Tebako
246
263
  press_announce_application(is_msys)
247
264
  when "both"
248
265
  press_announce_both
249
- when "bundle"
266
+ when "bundle", "classic", "lean", "fat"
250
267
  press_announce_bundle
251
268
  when "runtime"
252
269
  press_announce_runtime
@@ -290,7 +307,7 @@ module Tebako
290
307
  def press_announce_bundle
291
308
  <<~ANN
292
309
  Running tebako press at #{prefix}
293
- Mode: 'bundle'
310
+ Mode: '#{mode}'
294
311
  Ruby version: '#{@ruby_ver}'
295
312
  Project root: '#{root}'
296
313
  Application entry point: '#{fs_entrance}'
@@ -374,14 +391,19 @@ module Tebako
374
391
  def requested_runtime_source
375
392
  explicit = @options["runtime"]
376
393
  return "source" if @options["build-runtime"]
377
- return mode == "bundle" ? "prebuilt" : "source" if explicit.nil?
394
+ return PREBUILT_DEFAULT_MODES.include?(mode) ? "prebuilt" : "source" if explicit.nil?
378
395
 
379
396
  explicit
380
397
  end
381
398
 
382
399
  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}')")
400
+ if source == "prebuilt" && !PREBUILT_DEFAULT_MODES.include?(mode)
401
+ Tebako.packaging_error(130, "--runtime prebuilt is supported in bundle, classic, lean and fat modes only " \
402
+ "(mode: '#{mode}')")
403
+ end
404
+ if source == "source" && three_part?
405
+ Tebako.packaging_error(130, "mode '#{mode}' requires a prebuilt runtime package " \
406
+ "(the bootstrap resolves tebako-runtime-ruby releases at first run)")
385
407
  end
386
408
  source
387
409
  end
@@ -399,7 +421,7 @@ module Tebako
399
421
 
400
422
  def host_arch_id(arch)
401
423
  case arch
402
- when /^(x86_64|amd64)$/ then "x86_64"
424
+ when /^(x86_64|amd64|x64)$/ then "x86_64"
403
425
  when /^(aarch64|arm64)$/ then "arm64"
404
426
  else
405
427
  Tebako.packaging_error(112, arch)
@@ -203,6 +203,21 @@ module Tebako
203
203
  "/* tebako patched */ static const struct vktable *console_win32_vk(const char *, size_t);"
204
204
  }.freeze
205
205
 
206
+ # Ruby's win32.c defines clock_gettime/clock_getres fallbacks with no
207
+ # HAVE_* configure guard (at least through 3.4); modern mingw-w64
208
+ # winpthreads headers (pthread_time.h, pulled in by time.h) carry
209
+ # static-inline definitions of the same symbols, so the toolchain
210
+ # build fails with redefinition errors. Rename ruby's fallbacks
211
+ # before the toolchain build; the CRT/winpthreads implementations
212
+ # (which configure detects) serve the calls. Pass2 re-applies the
213
+ # same rename from pristine sources for the final build.
214
+ WIN32_WIN32_C_CLOCK_PATCH = {
215
+ "clock_gettime(clockid_t clock_id, struct timespec *sp)" =>
216
+ "_dummy_clock_gettime(clockid_t clock_id, struct timespec *sp)",
217
+ "clock_getres(clockid_t clock_id, struct timespec *sp)" =>
218
+ "_dummy_clock_getres(clockid_t clock_id, struct timespec *sp)"
219
+ }.freeze
220
+
206
221
  def patch_map
207
222
  pm = msys_patches
208
223
  pm.merge!(super)
@@ -246,7 +261,11 @@ module Tebako
246
261
  "ext/openssl/extconf.rb" => OPENSSL_EXTCONF_RB_PATCH,
247
262
  # ....................................................
248
263
  # Fix the signature of console_win32_vk
249
- "ext/io/console/win32_vk.inc" => EXT_IO_CONSOLE_WIN32_VK_INC_PATCH
264
+ "ext/io/console/win32_vk.inc" => EXT_IO_CONSOLE_WIN32_VK_INC_PATCH,
265
+ # ....................................................
266
+ # Rename ruby's clock_gettime/clock_getres fallbacks before the
267
+ # toolchain build (winpthreads static-inline collision)
268
+ "win32/win32.c" => WIN32_WIN32_C_CLOCK_PATCH
250
269
  }
251
270
  end
252
271
 
@@ -114,6 +114,9 @@ module Tebako
114
114
  "#if defined _MSC_VER && _MSC_VER <= 1200" =>
115
115
  "#{PatchLiterals::C_FILE_SUBST_LESS}\n#if defined _MSC_VER && _MSC_VER <= 1200",
116
116
  "len = GetCurrentDirectoryW(0, NULL);" => WIN32_WIN32_C_MSYS_SUBST,
117
+ # Keep the clock renames in sync with Pass1MSysPatch::WIN32_WIN32_C_CLOCK_PATCH:
118
+ # pass1 applies them for the toolchain build, this pass re-applies them
119
+ # from pristine sources for the final build
117
120
  "clock_gettime(clockid_t clock_id, struct timespec *sp)" =>
118
121
  "_dummy_clock_gettime(clockid_t clock_id, struct timespec *sp)",
119
122
  "clock_getres(clockid_t clock_id, struct timespec *sp)" =>
@@ -76,7 +76,9 @@ module Tebako
76
76
 
77
77
  def recreate(dirname)
78
78
  FileUtils.rm_rf(dirname, noop: nil, verbose: nil, secure: true)
79
- FileUtils.mkdir(dirname)
79
+ # mkdir_p: in three-part/prebuilt press flows no cmake configure runs,
80
+ # so the parent output folder may not exist yet
81
+ FileUtils.mkdir_p(dirname)
80
82
  end
81
83
 
82
84
  def restore_and_save(fname, strict: true)
@@ -87,7 +87,7 @@ module Tebako
87
87
 
88
88
  def initialize(cache_root: nil, mirror: nil, lock_timeout: LOCK_TIMEOUT, retry_delay: RETRY_DELAY)
89
89
  @cache_root = cache_root || self.class.default_cache_root
90
- @mirror = (mirror || ENV.fetch("TEBAKO_RUNTIME_MIRROR", nil) || DEFAULT_MIRROR).sub(%r{/+\z}, "")
90
+ @mirror = (mirror || ENV.fetch(mirror_env_var, nil) || default_mirror).sub(%r{/+\z}, "")
91
91
  @lock_timeout = lock_timeout
92
92
  @retry_delay = retry_delay
93
93
  end
@@ -106,7 +106,7 @@ module Tebako
106
106
  end
107
107
 
108
108
  def entries
109
- base = File.join(@cache_root, RUNTIMES_DIR)
109
+ base = File.join(@cache_root, cache_subdir)
110
110
  return [] unless Dir.exist?(base)
111
111
 
112
112
  Dir.children(base).sort.filter_map do |name|
@@ -131,6 +131,29 @@ module Tebako
131
131
 
132
132
  private
133
133
 
134
+ # ---- extension points for BootstrapManager (tebako-bootstrap release
135
+ # resolution); overridden there, everything else is shared machinery ----
136
+
137
+ def default_mirror
138
+ DEFAULT_MIRROR
139
+ end
140
+
141
+ def mirror_env_var
142
+ "TEBAKO_RUNTIME_MIRROR"
143
+ end
144
+
145
+ def index_files
146
+ INDEX_FILES
147
+ end
148
+
149
+ def cache_subdir
150
+ RUNTIMES_DIR
151
+ end
152
+
153
+ def release_name
154
+ "tebako-runtime-ruby"
155
+ end
156
+
134
157
  def install(executable, ruby_version, platform, tebako_version)
135
158
  ref = runtime_ref(ruby_version, platform, tebako_version)
136
159
  offline_check(ref, tebako_version)
@@ -146,7 +169,7 @@ module Tebako
146
169
 
147
170
  Tebako.packaging_error(123, "#{ref} is not cached and downloads are disabled " \
148
171
  "(release index: #{index_urls(tebako_version).join(", ")}; " \
149
- "TEBAKO_RUNTIME_MIRROR=#{@mirror})")
172
+ "#{mirror_env_var}=#{@mirror})")
150
173
  end
151
174
 
152
175
  def find_entry(index, ruby_version, platform, tebako_version)
@@ -178,14 +201,14 @@ module Tebako
178
201
 
179
202
  def fetch_index(tebako_version)
180
203
  tried = []
181
- INDEX_FILES.each do |name|
204
+ index_files.each do |name|
182
205
  return parse_index(name, fetch_text(index_url(name, tebako_version)), tebako_version)
183
206
  rescue IndexUnavailable
184
207
  tried << index_url(name, tebako_version)
185
208
  rescue DownloadFailed => e
186
209
  Tebako.packaging_error(122, e.message)
187
210
  end
188
- Tebako.packaging_error(124, "tebako-runtime-ruby release v#{tebako_version} provides no usable package " \
211
+ Tebako.packaging_error(124, "#{release_name} release v#{tebako_version} provides no usable package " \
189
212
  "index (tried: #{tried.join(", ")})")
190
213
  end
191
214
 
@@ -333,7 +356,7 @@ module Tebako
333
356
  end
334
357
 
335
358
  def index_urls(tebako_version)
336
- INDEX_FILES.map { |name| index_url(name, tebako_version) }
359
+ index_files.map { |name| index_url(name, tebako_version) }
337
360
  end
338
361
 
339
362
  def package_url(filename, tebako_version)
@@ -75,7 +75,10 @@ module Tebako
75
75
  FORMAT_DWARFS = 1
76
76
  FORMAT_SQUASHFS = 2
77
77
  FORMAT_ZIP = 3
78
- FORMAT_IDS = (FORMAT_AUTO..FORMAT_ZIP).freeze
78
+ # Runtime payload slot of a fat package: installed into the shared cache
79
+ # by the bootstrap at first run, never mounted (tpkg.h TPKG_FORMAT_RUNTIME)
80
+ FORMAT_RUNTIME = 4
81
+ FORMAT_IDS = (FORMAT_AUTO..FORMAT_RUNTIME).freeze
79
82
 
80
83
  # Header field offsets
81
84
  OFF_PACKAGE_FLAGS = 14
@@ -89,17 +92,21 @@ module Tebako
89
92
  # copy of the runtime at +runtime_path+, writing +output+.
90
93
  # lean: true marks the package LEAN and records runtime_ref
91
94
  # ("ruby@<ruby_version>;tebako=<tebako_version>"); classic packages
92
- # carry an empty runtime_ref.
95
+ # carry an empty runtime_ref. runtime_sha256 appends ";sha256=<hex>" to
96
+ # the runtime_ref — the checksum the bootstrap verifies a fat package's
97
+ # runtime payload slot against before installing it into the cache.
93
98
  def stitch(runtime_path, images:, output:, lean: false, ruby_version: nil, # rubocop:disable Metrics/ParameterLists
94
- tebako_version: Tebako::VERSION, launcher_abi: 0)
99
+ tebako_version: Tebako::VERSION, launcher_abi: 0, runtime_sha256: nil)
95
100
  images = normalize_images(images)
96
101
  validate_inputs!(runtime_path, images, lean, ruby_version)
102
+ validate_runtime_sha256!(runtime_sha256)
97
103
 
98
104
  FileUtils.mkdir_p(File.dirname(output))
99
105
  FileUtils.cp(runtime_path, output)
100
106
  FileUtils.chmod(0o755, output)
101
107
 
102
- append(images, output, package_flags(lean), runtime_ref(lean, ruby_version, tebako_version), launcher_abi)
108
+ append(images, output, package_flags(lean), runtime_ref(lean, ruby_version, tebako_version, runtime_sha256),
109
+ launcher_abi)
103
110
  resign_if_needed(output)
104
111
  output
105
112
  end
@@ -157,6 +164,8 @@ module Tebako
157
164
  seen = {}
158
165
  images.each do |image|
159
166
  validate_image!(image)
167
+ next if image[:format_id] == FORMAT_RUNTIME # payload slots are never mounted
168
+
160
169
  mount = image[:mount_point]
161
170
  Tebako.packaging_error(126, "duplicate mount point '#{mount}'") if seen[mount]
162
171
 
@@ -166,7 +175,7 @@ module Tebako
166
175
 
167
176
  def validate_image!(image)
168
177
  unless FORMAT_IDS.cover?(image[:format_id])
169
- Tebako.packaging_error(126, "invalid format_id #{image[:format_id]} (0..3 expected)")
178
+ Tebako.packaging_error(126, "invalid format_id #{image[:format_id]} (0..4 expected)")
170
179
  end
171
180
  return unless image[:mount_point].bytesize >= MOUNT_POINT_LEN
172
181
 
@@ -174,14 +183,21 @@ module Tebako
174
183
  "mount point '#{image[:mount_point][0, 32]}...' exceeds #{MOUNT_POINT_LEN - 1} bytes")
175
184
  end
176
185
 
186
+ def validate_runtime_sha256!(runtime_sha256)
187
+ return if runtime_sha256.nil? || runtime_sha256.match?(/\A[0-9a-f]{64}\z/)
188
+
189
+ Tebako.packaging_error(126, "runtime_sha256 must be 64 lowercase hex characters")
190
+ end
191
+
177
192
  def package_flags(lean)
178
193
  lean ? FLAG_LEAN : 0
179
194
  end
180
195
 
181
- def runtime_ref(lean, ruby_version, tebako_version)
196
+ def runtime_ref(lean, ruby_version, tebako_version, runtime_sha256)
182
197
  return "" unless lean
183
198
 
184
199
  ref = "ruby@#{ruby_version};tebako=#{tebako_version}"
200
+ ref += ";sha256=#{runtime_sha256}" if runtime_sha256
185
201
  if ref.bytesize >= RUNTIME_REF_LEN
186
202
  Tebako.packaging_error(126, "runtime_ref '#{ref}' exceeds #{RUNTIME_REF_LEN - 1} bytes")
187
203
  end
@@ -26,5 +26,5 @@
26
26
  # POSSIBILITY OF SUCH DAMAGE.
27
27
 
28
28
  module Tebako
29
- VERSION = "0.15.1"
29
+ VERSION = "0.15.2"
30
30
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tebako
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.15.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -212,6 +212,7 @@ files:
212
212
  - include/tebako/tebako-prism.h
213
213
  - include/tebako/tpkg.h
214
214
  - lib/tebako.rb
215
+ - lib/tebako/bootstrap_manager.rb
215
216
  - lib/tebako/build_helpers.rb
216
217
  - lib/tebako/cache_manager.rb
217
218
  - lib/tebako/cli.rb