rbe-tebako 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CMakeLists.txt +19 -3
- data/README.adoc +201 -12
- data/benchmark/press.rb +130 -0
- data/docs/layered-package-format.md +56 -0
- data/docs/macos-entitlements.plist +10 -0
- data/format/layered_package.json +20 -0
- data/format/single_file_bundle.json +12 -0
- data/include/tebako/layered-format.h +16 -0
- data/include/tebako/single-file-bundle-format.h +17 -0
- data/lib/tebako/application_build_state.rb +124 -0
- data/lib/tebako/application_builder.rb +96 -0
- data/lib/tebako/build_commands.rb +396 -0
- data/lib/tebako/build_reporter.rb +110 -0
- data/lib/tebako/bundle_builder.rb +57 -0
- data/lib/tebako/cache_catalog.rb +195 -0
- data/lib/tebako/cli.rb +42 -3
- data/lib/tebako/cli_helpers.rb +42 -18
- data/lib/tebako/codegen.rb +60 -30
- data/lib/tebako/content_manifest.rb +131 -0
- data/lib/tebako/deploy_helper.rb +160 -18
- data/lib/tebako/deployment_cache.rb +226 -0
- data/lib/tebako/error.rb +2 -0
- data/lib/tebako/filesystem_cache.rb +139 -0
- data/lib/tebako/finalized_runtime_cache.rb +143 -0
- data/lib/tebako/generated/layered_format.rb +14 -0
- data/lib/tebako/generated/single_file_bundle_format.rb +15 -0
- data/lib/tebako/layer_planner.rb +140 -0
- data/lib/tebako/layered_package.rb +198 -0
- data/lib/tebako/native_gem_cache.rb +195 -0
- data/lib/tebako/options_manager.rb +68 -0
- data/lib/tebako/packager/pass2_patch.rb +6 -2
- data/lib/tebako/packager/patch_buildsystem.rb +34 -3
- data/lib/tebako/packager.rb +51 -24
- data/lib/tebako/packager_lite.rb +170 -6
- data/lib/tebako/release_store.rb +190 -0
- data/lib/tebako/ruby_builder.rb +110 -2
- data/lib/tebako/runtime_builder.rb +146 -0
- data/lib/tebako/runtime_descriptor.rb +142 -0
- data/lib/tebako/runtime_sdk.rb +345 -0
- data/lib/tebako/scenario_manager.rb +1 -1
- data/lib/tebako/single_file_bundle.rb +232 -0
- data/lib/tebako/single_file_bundle_builder.rb +113 -0
- data/lib/tebako/version.rb +1 -1
- data/scripts/generate_layered_format.rb +95 -0
- data/src/tebako-main.cpp +277 -6
- metadata +31 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d051f3caca43f3080ba1793827fe2390b7a9410ed53cd8c5426856c70592cd9
|
|
4
|
+
data.tar.gz: 9466ae396ba0171287dd792fb99cc58995aa76b750772b146f92687f862e28e1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3bcdf7fd8770090d25ca313808091f3785c5fbac4015af47a6bd745acaae341efcd9381225eeae1dd1d986cfcb4ad895d92c4215586e338c1127be8a9004c15
|
|
7
|
+
data.tar.gz: 6151c0a0d960ef89d768a3510400a6bd4ba2fa01b4756e224222e026cc776ad2ddf17978bc9b561a230251c6ffa191ec0833c31cea717cd0db499507b1c705c8
|
data/CMakeLists.txt
CHANGED
|
@@ -436,12 +436,23 @@ if (${SETUP_MODE})
|
|
|
436
436
|
# ...................................................................
|
|
437
437
|
# Packaged filesystem
|
|
438
438
|
|
|
439
|
-
|
|
439
|
+
file(GLOB_RECURSE TEBAKO_PACKAGER_SOURCES CONFIGURE_DEPENDS
|
|
440
|
+
${CMAKE_CURRENT_SOURCE_DIR}/lib/tebako/*.rb
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
add_custom_command(
|
|
444
|
+
OUTPUT ${DATA_BIN_FILE}
|
|
440
445
|
COMMAND ruby ${DEPS_BIN_DIR}/deploy.rb ${RUBY_VER}
|
|
441
|
-
DEPENDS
|
|
442
|
-
|
|
446
|
+
DEPENDS ${DEPS_BIN_DIR}/deploy.rb ${PACKAGE_MANIFEST} ${TEBAKO_PACKAGER_SOURCES}
|
|
447
|
+
COMMENT "Building packaged filesystem from changed inputs"
|
|
448
|
+
VERBATIM
|
|
443
449
|
)
|
|
444
450
|
|
|
451
|
+
add_custom_target(packaged_filesystem
|
|
452
|
+
DEPENDS ${DATA_BIN_FILE}
|
|
453
|
+
)
|
|
454
|
+
add_dependencies(packaged_filesystem setup)
|
|
455
|
+
|
|
445
456
|
set(CMAKE_CXX_FLAGS "${RUBY_C_FLAGS}")
|
|
446
457
|
|
|
447
458
|
add_library(tebako-fs STATIC
|
|
@@ -452,6 +463,11 @@ if (${SETUP_MODE})
|
|
|
452
463
|
${DEPS_INCLUDE_DIR}/tebako/tebako-version.h
|
|
453
464
|
)
|
|
454
465
|
|
|
466
|
+
set_source_files_properties(
|
|
467
|
+
${DEPS_SRC_DIR}/tebako/tebako-fs.cpp
|
|
468
|
+
PROPERTIES OBJECT_DEPENDS ${DATA_BIN_FILE}
|
|
469
|
+
)
|
|
470
|
+
|
|
455
471
|
if(${RUBY_VER} VERSION_LESS "3.3.0" AND ("${OSTYPE_TXT}" MATCHES "^msys*" OR "${OSTYPE_TXT}" MATCHES "^cygwin*"))
|
|
456
472
|
target_compile_definitions(tebako-fs PUBLIC RB_W32_PRE_33)
|
|
457
473
|
endif(${RUBY_VER} VERSION_LESS "3.3.0" AND ("${OSTYPE_TXT}" MATCHES "^msys*" OR "${OSTYPE_TXT}" MATCHES "^cygwin*"))
|
data/README.adoc
CHANGED
|
@@ -294,7 +294,7 @@ single, performant, executable binary.
|
|
|
294
294
|
- Packages all files and dependencies into a single binary
|
|
295
295
|
- Supports multiple operating systems and architectures
|
|
296
296
|
- Provides efficient packaging and execution with DwarFS
|
|
297
|
-
-
|
|
297
|
+
- Supports macOS signing with the legacy embedded bundle format
|
|
298
298
|
- Simplifies distribution and deployment
|
|
299
299
|
- Native running speed
|
|
300
300
|
|
|
|
@@ -403,8 +403,9 @@ the packaging environment and collects the required dependencies. Subsequent
|
|
|
403
403
|
invocations are much faster.
|
|
404
404
|
====
|
|
405
405
|
|
|
406
|
-
Upon the next invocation, Tebako
|
|
407
|
-
|
|
406
|
+
Upon the next invocation, Tebako reuses its packaging environment and
|
|
407
|
+
content-addressed artifacts. An unchanged bundle normally returns after
|
|
408
|
+
verification, and an application-only edit reuses the finalized Ruby runtime.
|
|
408
409
|
|
|
409
410
|
You can manage setup of packaging environment manually; please refer to
|
|
410
411
|
description of setup and clean commands below.
|
|
@@ -416,9 +417,15 @@ tebako press \
|
|
|
416
417
|
-r|--root=<project-root-folder> \
|
|
417
418
|
[-p|--prefix=<tebako-root-folder>] \
|
|
418
419
|
[-R|--Ruby=<ruby-version>] \
|
|
419
|
-
[-o|--output=<packaged-file-name>] \
|
|
420
|
-
[-l|--log-level=<error|warn|debug|trace>] \
|
|
421
|
-
[-
|
|
420
|
+
[-o|--output=<packaged-file-name>] \
|
|
421
|
+
[-l|--log-level=<error|warn|debug|trace>] \
|
|
422
|
+
[--compression-level=<0..9>] \
|
|
423
|
+
[--[no-]deployment-cache] \
|
|
424
|
+
[--explain] \
|
|
425
|
+
[--report=json] \
|
|
426
|
+
[--layer-strategy=<coarse|semantic>] \
|
|
427
|
+
[--bundle-format=<layered|legacy>] \
|
|
428
|
+
[-c|--cwd=<package-current-working-directory>] \
|
|
422
429
|
[-D|--devmode] \
|
|
423
430
|
[-P|--patchelf] \
|
|
424
431
|
[-m|--mode=<bundle|both|application|runtime>] \
|
|
@@ -470,8 +477,82 @@ and the application package is named according to the `-o` option with the `.teb
|
|
|
470
477
|
(optional, defaults to `error`)
|
|
471
478
|
logging level for the Tebako built-in memory filesystem driver.
|
|
472
479
|
|
|
473
|
-
`--
|
|
474
|
-
(optional)
|
|
480
|
+
`--compression-level=<0..9>`::
|
|
481
|
+
(optional, defaults to `5`)
|
|
482
|
+
DwarFS compression level for the packaged filesystem. Level `0` prioritizes
|
|
483
|
+
packaging speed, while level `9` prioritizes the smallest package size.
|
|
484
|
+
|
|
485
|
+
When an application has a `Gemfile`, Tebako caches its installed gems under the
|
|
486
|
+
Tebako prefix. Later presses restore the matching dependency set and use
|
|
487
|
+
`bundle check`, running `bundle install` only when gems are missing. The cache
|
|
488
|
+
key includes the lockfile when one exists, plus the Gemfile, gemspec, Ruby ABI,
|
|
489
|
+
platform, Bundler configuration, and native build inputs.
|
|
490
|
+
|
|
491
|
+
`--deployment-cache`::
|
|
492
|
+
(optional, enabled by default)
|
|
493
|
+
Restores a verified, content-addressed deployment tree when the application,
|
|
494
|
+
lockfile, Ruby ABI, platform, and packaging inputs are unchanged. Use
|
|
495
|
+
`--no-deployment-cache` to force deployment work.
|
|
496
|
+
|
|
497
|
+
`--explain`::
|
|
498
|
+
(optional)
|
|
499
|
+
Prints why each fast-build stage was rebuilt or reused.
|
|
500
|
+
|
|
501
|
+
`--report=json`::
|
|
502
|
+
(optional)
|
|
503
|
+
Emits the same build decisions as structured JSON for benchmarks and CI.
|
|
504
|
+
|
|
505
|
+
`--layer-strategy=<coarse|semantic>`::
|
|
506
|
+
(optional, defaults to `coarse`)
|
|
507
|
+
Controls application-layer planning where the selected runtime supports
|
|
508
|
+
multiple secondary filesystems. The current default runtime uses one
|
|
509
|
+
application capsule because its filesystem wrapper supports one secondary
|
|
510
|
+
mount. The planner remains available for runtimes that advertise wider mount
|
|
511
|
+
support.
|
|
512
|
+
|
|
513
|
+
`--bundle-format=<layered|legacy>`::
|
|
514
|
+
(optional, defaults to `layered`)
|
|
515
|
+
Selects the internal representation of a one-file `bundle`.
|
|
516
|
+
+
|
|
517
|
+
`layered` reuses a verified finalized Ruby runtime and combines it with one
|
|
518
|
+
checksummed, compressed application capsule. Linux and Windows store the
|
|
519
|
+
capsule in a versioned trailer. macOS performs a targeted final link that
|
|
520
|
+
places the capsule in the `__TEBAKO,__app` Mach-O section, allowing the complete
|
|
521
|
+
executable to be signed. Application edits never recompile the Ruby runtime.
|
|
522
|
+
The deployed result is still exactly one executable and does not need Ruby on
|
|
523
|
+
the destination machine.
|
|
524
|
+
+
|
|
525
|
+
`legacy` embeds the complete filesystem during the native link. It is slower
|
|
526
|
+
for application edits but remains available for compatibility.
|
|
527
|
+
+
|
|
528
|
+
Layered macOS bundles support normal `codesign` replacement and strict
|
|
529
|
+
verification. Sign with Hardened Runtime and Tebako's supplied entitlements:
|
|
530
|
+
+
|
|
531
|
+
[source,shell]
|
|
532
|
+
----
|
|
533
|
+
codesign --force --timestamp --options runtime \
|
|
534
|
+
--entitlements docs/macos-entitlements.plist \
|
|
535
|
+
--sign "Developer ID Application: YOUR NAME (TEAMID)" \
|
|
536
|
+
./my-tebako-app
|
|
537
|
+
|
|
538
|
+
codesign --verify --deep --strict --verbose=4 ./my-tebako-app
|
|
539
|
+
----
|
|
540
|
+
+
|
|
541
|
+
Ruby's YJIT allocates executable memory without `MAP_JIT`, which requires
|
|
542
|
+
`com.apple.security.cs.allow-unsigned-executable-memory` under Hardened Runtime.
|
|
543
|
+
Packaged native gems are extracted as independently ad-hoc-signed `.bundle`
|
|
544
|
+
files, which requires `com.apple.security.cs.disable-library-validation`.
|
|
545
|
+
The supplied link:docs/macos-entitlements.plist[macOS entitlements file]
|
|
546
|
+
contains those two runtime exceptions.
|
|
547
|
+
+
|
|
548
|
+
Release CI should then submit a ZIP, DMG, or package containing the signed
|
|
549
|
+
executable to `notarytool`, staple the accepted artifact where that container
|
|
550
|
+
supports stapling, and execute the distributed result. Developer ID signing and
|
|
551
|
+
the Apple notary service require project credentials and therefore remain
|
|
552
|
+
release-CI checks.
|
|
553
|
+
|
|
554
|
+
`--cwd=<package-current-working-directory>`::
|
|
555
|
+
(optional)
|
|
475
556
|
A folder within Tebako memfs where the packaged application will start. This
|
|
476
557
|
folder should be specified relative to the memfs root.
|
|
477
558
|
+
|
|
@@ -542,10 +623,118 @@ tebako press \
|
|
|
542
623
|
--root='~/projects/myproject' \
|
|
543
624
|
--entry=start.rb \
|
|
544
625
|
--output=/temp/myproject.tebako
|
|
545
|
-
----
|
|
546
|
-
====
|
|
547
|
-
|
|
548
|
-
|
|
626
|
+
----
|
|
627
|
+
====
|
|
628
|
+
|
|
629
|
+
===== Incremental presses
|
|
630
|
+
|
|
631
|
+
Tebako keeps content-addressed DwarFS images under the Tebako prefix. Cache keys
|
|
632
|
+
include file contents and modes, the DwarFS writer, the compression level, and
|
|
633
|
+
package metadata; timestamps alone do not invalidate an image. Generated files
|
|
634
|
+
are updated only when their contents change, and CMake rebuilds or relinks the
|
|
635
|
+
runtime only when the packaged filesystem or native inputs change.
|
|
636
|
+
|
|
637
|
+
In the default `bundle` path, Tebako reuses an immutable finalized runtime and
|
|
638
|
+
compresses the deployed app, executables, gems, and package metadata into one
|
|
639
|
+
application capsule. The capsule is appended with a versioned, checksummed
|
|
640
|
+
footer, producing exactly one executable. An application-only edit therefore
|
|
641
|
+
does not rebuild the runtime filesystem or relink Ruby.
|
|
642
|
+
+
|
|
643
|
+
`both` mode uses the same capsule representation for its separate application
|
|
644
|
+
package. `application` mode continues to emit a monolithic image so it remains
|
|
645
|
+
compatible with previously built runtime executables.
|
|
646
|
+
|
|
647
|
+
The installed DwarFS writer does not expose a persistent compressed-chunk cache.
|
|
648
|
+
Layer-sized, content-addressed images therefore provide incremental compression
|
|
649
|
+
without changing the DwarFS format itself.
|
|
650
|
+
|
|
651
|
+
To compare warm, unchanged, and application-changed presses on the same machine,
|
|
652
|
+
run:
|
|
653
|
+
|
|
654
|
+
[source,sh]
|
|
655
|
+
----
|
|
656
|
+
ruby benchmark/press.rb \
|
|
657
|
+
--root tests/test-00 \
|
|
658
|
+
--entry test.rb \
|
|
659
|
+
--prefix PWD \
|
|
660
|
+
--mode bundle
|
|
661
|
+
----
|
|
662
|
+
|
|
663
|
+
The command emits JSON timings, cache-hit counts, native-link reuse, and a
|
|
664
|
+
runtime validation result. Use `--mode bundle` to measure the primary
|
|
665
|
+
single-executable workflow or `--mode both` to measure split runtime/application
|
|
666
|
+
packaging. Keep the mode, Ruby version, compression level, prefix, machine load,
|
|
667
|
+
and application fixed when comparing revisions.
|
|
668
|
+
|
|
669
|
+
===== Explicit fast-build commands
|
|
670
|
+
|
|
671
|
+
The runtime and application phases can be invoked independently so application
|
|
672
|
+
edits never enter the runtime build:
|
|
673
|
+
|
|
674
|
+
[source,sh]
|
|
675
|
+
----
|
|
676
|
+
rbe-tebako runtime build \
|
|
677
|
+
--Ruby=4.0.6 \
|
|
678
|
+
--output=build/tebako-runtime \
|
|
679
|
+
--sdk-output=build/tebako-runtime-sdk.tar.gz
|
|
680
|
+
|
|
681
|
+
SDK_SHA256="$(cut -d' ' -f1 build/tebako-runtime-sdk.tar.gz.sha256)"
|
|
682
|
+
rbe-tebako runtime install build/tebako-runtime-sdk.tar.gz \
|
|
683
|
+
--destination=$HOME/.tebako/runtime-sdk \
|
|
684
|
+
--sha256="$SDK_SHA256"
|
|
685
|
+
|
|
686
|
+
rbe-tebako application build \
|
|
687
|
+
--prefix=$HOME/.tebako/runtime-sdk \
|
|
688
|
+
--runtime=$HOME/.tebako/runtime-sdk/runtime/tebako-runtime \
|
|
689
|
+
--root=. \
|
|
690
|
+
--entry-point=bin/my-app \
|
|
691
|
+
--output=build/my-app \
|
|
692
|
+
--explain
|
|
693
|
+
----
|
|
694
|
+
|
|
695
|
+
`runtime build --sdk-output` writes the SDK and a conventional adjacent
|
|
696
|
+
`.sha256` checksum file. Distribute or pin that digest through a trusted channel;
|
|
697
|
+
a checksum downloaded from the same untrusted location as the SDK provides
|
|
698
|
+
corruption detection, not publisher authentication.
|
|
699
|
+
+
|
|
700
|
+
SDK installation checks the trusted SHA-256 before extracting anything, verifies
|
|
701
|
+
the archive manifest and individual file checksums, and atomically replaces the
|
|
702
|
+
selected installation. It relocates text-based build configuration from the
|
|
703
|
+
producer's prefix to the installation prefix, registers the packaged runtime in
|
|
704
|
+
the finalized-runtime cache, and includes the prepared Ruby link tree needed for
|
|
705
|
+
fast signable single-file assembly on macOS.
|
|
706
|
+
+
|
|
707
|
+
Set `TEBAKO_PREFIX` to the installation root to make ordinary `press` commands
|
|
708
|
+
reuse the SDK runtime without adding SDK-specific arguments:
|
|
709
|
+
|
|
710
|
+
[source,sh]
|
|
711
|
+
----
|
|
712
|
+
export TEBAKO_PREFIX=$HOME/.tebako/runtime-sdk
|
|
713
|
+
rbe-tebako press \
|
|
714
|
+
--Ruby=4.0.6 \
|
|
715
|
+
--root=. \
|
|
716
|
+
--entry-point=bin/my-app \
|
|
717
|
+
--output=build/my-app \
|
|
718
|
+
--explain
|
|
719
|
+
----
|
|
720
|
+
|
|
721
|
+
The runtime is reused only when its descriptor matches the current platform,
|
|
722
|
+
architecture, Ruby version, Tebako source, compression and logging options, and
|
|
723
|
+
native build environment. A mismatch is safe: Tebako builds and caches a
|
|
724
|
+
compatible runtime instead.
|
|
725
|
+
+
|
|
726
|
+
Tebako also exposes cache and package diagnostics:
|
|
727
|
+
|
|
728
|
+
[source,sh]
|
|
729
|
+
----
|
|
730
|
+
rbe-tebako cache stats
|
|
731
|
+
rbe-tebako cache verify
|
|
732
|
+
rbe-tebako cache prune --max-size=20GB
|
|
733
|
+
rbe-tebako package inspect build/my-app.tebako
|
|
734
|
+
rbe-tebako package verify build/my-app.tebako
|
|
735
|
+
----
|
|
736
|
+
|
|
737
|
+
==== Setup
|
|
549
738
|
|
|
550
739
|
Tebako requires post-install setup after gem installation .
|
|
551
740
|
Post-intall setup is called automatically during the first packaging.
|
data/benchmark/press.rb
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "json"
|
|
6
|
+
require "open3"
|
|
7
|
+
require "optparse"
|
|
8
|
+
require "rbconfig"
|
|
9
|
+
require "tmpdir"
|
|
10
|
+
|
|
11
|
+
# Measures warm, unchanged, and application-changed Tebako presses.
|
|
12
|
+
class PressBenchmark
|
|
13
|
+
def initialize(options)
|
|
14
|
+
@options = options
|
|
15
|
+
@repo = File.expand_path("..", __dir__)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def run
|
|
19
|
+
Dir.mktmpdir("tebako-press-benchmark") do |directory|
|
|
20
|
+
puts JSON.pretty_generate(results_for(directory))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def results_for(directory)
|
|
27
|
+
root, output = prepare_workspace(directory)
|
|
28
|
+
results = { mode: @options[:mode], warm: press(root, output), unchanged: press(root, output) }
|
|
29
|
+
add_probe(root)
|
|
30
|
+
results[:application_changed] = press(root, output)
|
|
31
|
+
results[:runtime_validation] = validate(output) unless @options[:skip_run]
|
|
32
|
+
add_comparisons(results)
|
|
33
|
+
results
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prepare_workspace(directory)
|
|
37
|
+
root = File.join(directory, "application")
|
|
38
|
+
FileUtils.cp_r(File.join(File.expand_path(@options[:root]), "."), root)
|
|
39
|
+
[root, File.join(directory, "tebako-benchmark")]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def add_probe(root)
|
|
43
|
+
timestamp = Process.clock_gettime(Process::CLOCK_REALTIME).to_s
|
|
44
|
+
File.write(File.join(root, ".tebako-benchmark-probe"), timestamp)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def add_comparisons(results)
|
|
48
|
+
warm = results[:warm][:seconds]
|
|
49
|
+
results[:comparisons] = {
|
|
50
|
+
unchanged_vs_warm: ratio(results[:unchanged][:seconds], warm),
|
|
51
|
+
application_changed_vs_warm: ratio(results[:application_changed][:seconds], warm)
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ratio(value, baseline)
|
|
56
|
+
(value / baseline).round(3)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def press(root, output)
|
|
60
|
+
command = press_command(root, output)
|
|
61
|
+
warn "Running #{command.join(" ")}"
|
|
62
|
+
started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
63
|
+
log, status = Open3.capture2e(*command, chdir: @repo)
|
|
64
|
+
abort log unless status.success?
|
|
65
|
+
|
|
66
|
+
press_result(log, Process.clock_gettime(Process::CLOCK_MONOTONIC) - started)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def press_result(log, elapsed)
|
|
70
|
+
report = parse_report(log)
|
|
71
|
+
{
|
|
72
|
+
seconds: elapsed.round(3),
|
|
73
|
+
reused_filesystems: log.scan("reusing packaged filesystem").length,
|
|
74
|
+
reused_native_link: log.include?("reusing Ruby executable"),
|
|
75
|
+
rebuilt_runtime_filesystem: log.include?("Building packaged filesystem from changed inputs"),
|
|
76
|
+
stages: report.fetch("events", []).map do |event|
|
|
77
|
+
event.slice("stage", "status", "duration_seconds", "reason", "details")
|
|
78
|
+
end
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def parse_report(log)
|
|
83
|
+
line = log.lines.reverse.find { |candidate| candidate.start_with?("{\"schema_version\"") }
|
|
84
|
+
line ? JSON.parse(line) : {}
|
|
85
|
+
rescue JSON::ParserError
|
|
86
|
+
{}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def press_command(root, output)
|
|
90
|
+
[
|
|
91
|
+
RbConfig.ruby, "-I#{File.join(@repo, "lib")}", File.join(@repo, "exe", "rbe-tebako"),
|
|
92
|
+
"press", "--mode", @options[:mode], "--prefix", @options[:prefix], "--Ruby", @options[:ruby],
|
|
93
|
+
"--root", root, "--entry-point", @options[:entry], "--output", output,
|
|
94
|
+
"--compression-level", @options[:compression].to_s, "--report", "json"
|
|
95
|
+
]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate(output)
|
|
99
|
+
command = @options[:mode] == "bundle" ? [output] : [output, "--tebako-run", "#{output}.tebako"]
|
|
100
|
+
log, status = Open3.capture2e(*command)
|
|
101
|
+
{ success: status.success?, output: log }
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
options = {
|
|
106
|
+
root: File.expand_path("../tests/test-00", __dir__),
|
|
107
|
+
entry: "test.rb",
|
|
108
|
+
prefix: "PWD",
|
|
109
|
+
ruby: "4.0.6",
|
|
110
|
+
compression: 5,
|
|
111
|
+
mode: "both",
|
|
112
|
+
skip_run: false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
OptionParser.new do |parser|
|
|
116
|
+
parser.banner = "Usage: benchmark/press.rb [options]"
|
|
117
|
+
parser.on("--root PATH", "Application root (default: tests/test-00)") { |value| options[:root] = value }
|
|
118
|
+
parser.on("--entry PATH", "Entry point relative to root (default: test.rb)") { |value| options[:entry] = value }
|
|
119
|
+
parser.on("--prefix PATH", "Tebako prefix (default: PWD)") { |value| options[:prefix] = value }
|
|
120
|
+
parser.on("--ruby VERSION", "Packaged Ruby version (default: 4.0.6)") { |value| options[:ruby] = value }
|
|
121
|
+
parser.on("--mode MODE", %w[bundle both], "Package mode: bundle or both (default: both)") do |value|
|
|
122
|
+
options[:mode] = value
|
|
123
|
+
end
|
|
124
|
+
parser.on("--compression N", Integer, "DwarFS compression level (default: 5)") do |value|
|
|
125
|
+
options[:compression] = value
|
|
126
|
+
end
|
|
127
|
+
parser.on("--skip-run", "Do not execute the resulting package") { options[:skip_run] = true }
|
|
128
|
+
end.parse!
|
|
129
|
+
|
|
130
|
+
PressBenchmark.new(options).run
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Tebako layered application package format
|
|
2
|
+
|
|
3
|
+
The source of truth for fixed fields is
|
|
4
|
+
[`format/layered_package.json`](../format/layered_package.json). Run
|
|
5
|
+
`ruby scripts/generate_layered_format.rb` after changing it.
|
|
6
|
+
|
|
7
|
+
Version 1.0 uses little-endian integers and ends with the eight-byte magic
|
|
8
|
+
`TEBAKOL1`. The version is encoded in that magic; readers must reject unknown
|
|
9
|
+
magic values rather than guessing a compatible layout.
|
|
10
|
+
|
|
11
|
+
The package layout is:
|
|
12
|
+
|
|
13
|
+
1. Tebako application descriptor.
|
|
14
|
+
2. One or more DwarFS layer payloads.
|
|
15
|
+
3. Layer manifest.
|
|
16
|
+
4. Footer.
|
|
17
|
+
|
|
18
|
+
The footer contains a `uint64` manifest byte length followed by the magic.
|
|
19
|
+
The manifest starts with a `uint32` layer count. Each record contains a
|
|
20
|
+
`uint16` UTF-8 mount-point length, the mount-point bytes, a `uint64` payload
|
|
21
|
+
offset, and a `uint64` payload length.
|
|
22
|
+
|
|
23
|
+
Mount points are relative, non-empty paths. Empty components and the `.` and
|
|
24
|
+
`..` components are forbidden. Mount points must be unique. Payload ranges
|
|
25
|
+
must be non-empty, must end before the manifest, and must not overlap.
|
|
26
|
+
|
|
27
|
+
Version 1 does not carry embedded payload checksums. `tebako package verify`
|
|
28
|
+
therefore validates its complete structure, bounds, and mount rules and reports
|
|
29
|
+
computed SHA-256 identities for inspection; it cannot authenticate a version 1
|
|
30
|
+
payload against an embedded digest. A checksum-bearing format requires a new
|
|
31
|
+
magic/version and corresponding runtime reader support.
|
|
32
|
+
|
|
33
|
+
## One-file executable containers
|
|
34
|
+
|
|
35
|
+
A layered package is wrapped in a single-file envelope containing the package
|
|
36
|
+
bytes, a little-endian `uint64` package length, a 32-byte SHA-256 digest, and
|
|
37
|
+
the eight-byte `TEBAKOB1` magic.
|
|
38
|
+
|
|
39
|
+
Linux and Windows place that envelope after the native executable. macOS places
|
|
40
|
+
the same envelope in the `__TEBAKO,__app` section of a dedicated `__TEBAKO`
|
|
41
|
+
Mach-O segment. The section is created during a targeted final link so it is
|
|
42
|
+
part of the signed Mach-O rather than unstructured trailing data. The runtime
|
|
43
|
+
locates the section through the loaded Mach-O header, validates the envelope
|
|
44
|
+
digest, and then parses the layered package.
|
|
45
|
+
|
|
46
|
+
The macOS link reuses the already-built Ruby objects, static extensions, and
|
|
47
|
+
runtime libraries. It does not compile Ruby or regenerate the runtime
|
|
48
|
+
filesystem. The resulting executable supports signature replacement followed
|
|
49
|
+
by `codesign --verify --deep --strict`.
|
|
50
|
+
|
|
51
|
+
Hardened Runtime signing must use `docs/macos-entitlements.plist`. Ruby's YJIT
|
|
52
|
+
requires executable anonymous memory, and native gem `.bundle` files are
|
|
53
|
+
extracted with independent ad-hoc signatures. The signing profile therefore
|
|
54
|
+
allows unsigned executable memory and disables library validation. Both pure
|
|
55
|
+
Ruby and native-gem fixtures are exercised with that profile; credentialed
|
|
56
|
+
Developer ID signing and notarization belong in release CI.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
|
6
|
+
<true/>
|
|
7
|
+
<key>com.apple.security.cs.disable-library-validation</key>
|
|
8
|
+
<true/>
|
|
9
|
+
</dict>
|
|
10
|
+
</plist>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Tebako layered application package",
|
|
3
|
+
"major_version": 1,
|
|
4
|
+
"minor_version": 0,
|
|
5
|
+
"magic": "TEBAKOL1",
|
|
6
|
+
"endianness": "little",
|
|
7
|
+
"footer": [
|
|
8
|
+
["manifest_size", "uint64"],
|
|
9
|
+
["magic", "bytes[8]"]
|
|
10
|
+
],
|
|
11
|
+
"manifest_header": [
|
|
12
|
+
["layer_count", "uint32"]
|
|
13
|
+
],
|
|
14
|
+
"layer_record": [
|
|
15
|
+
["mount_point_size", "uint16"],
|
|
16
|
+
["mount_point", "bytes[mount_point_size]"],
|
|
17
|
+
["payload_offset", "uint64"],
|
|
18
|
+
["payload_size", "uint64"]
|
|
19
|
+
]
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Tebako single-file layered bundle",
|
|
3
|
+
"major_version": 1,
|
|
4
|
+
"minor_version": 0,
|
|
5
|
+
"magic": "TEBAKOB1",
|
|
6
|
+
"endianness": "little",
|
|
7
|
+
"footer": [
|
|
8
|
+
["application_size", "uint64"],
|
|
9
|
+
["application_sha256", "bytes[32]"],
|
|
10
|
+
["magic", "bytes[8]"]
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Generated by scripts/generate_layered_format.rb. Do not edit.
|
|
2
|
+
#ifndef TEBAKO_LAYERED_FORMAT_H
|
|
3
|
+
#define TEBAKO_LAYERED_FORMAT_H
|
|
4
|
+
|
|
5
|
+
#include <cstddef>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
|
|
8
|
+
namespace tebako::layered_format {
|
|
9
|
+
inline constexpr std::uint16_t major_version = 1;
|
|
10
|
+
inline constexpr std::uint16_t minor_version = 0;
|
|
11
|
+
inline constexpr char magic[] = "TEBAKOL1";
|
|
12
|
+
inline constexpr std::size_t magic_size = sizeof(magic) - 1;
|
|
13
|
+
inline constexpr std::size_t footer_size = sizeof(std::uint64_t) + magic_size;
|
|
14
|
+
} // namespace tebako::layered_format
|
|
15
|
+
|
|
16
|
+
#endif
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Generated by scripts/generate_layered_format.rb. Do not edit.
|
|
2
|
+
#ifndef TEBAKO_SINGLE_FILE_BUNDLE_FORMAT_H
|
|
3
|
+
#define TEBAKO_SINGLE_FILE_BUNDLE_FORMAT_H
|
|
4
|
+
|
|
5
|
+
#include <cstddef>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
|
|
8
|
+
namespace tebako::single_file_bundle_format {
|
|
9
|
+
inline constexpr std::uint16_t major_version = 1;
|
|
10
|
+
inline constexpr std::uint16_t minor_version = 0;
|
|
11
|
+
inline constexpr char magic[] = "TEBAKOB1";
|
|
12
|
+
inline constexpr std::size_t magic_size = sizeof(magic) - 1;
|
|
13
|
+
inline constexpr std::size_t digest_size = 32;
|
|
14
|
+
inline constexpr std::size_t footer_size = sizeof(std::uint64_t) + digest_size + magic_size;
|
|
15
|
+
} // namespace tebako::single_file_bundle_format
|
|
16
|
+
|
|
17
|
+
#endif
|