Module: Tebako::Packager
- Defined in:
- lib/tebako/packager.rb,
lib/tebako/packager/patch.rb,
lib/tebako/packager/patch_main.rb,
lib/tebako/packager/pass1_patch.rb,
lib/tebako/packager/pass2_patch.rb,
lib/tebako/packager/pass1a_patch.rb,
lib/tebako/packager/patch_helpers.rb,
lib/tebako/packager/patch_literals.rb,
lib/tebako/packager/rubygems_patch.rb,
lib/tebako/packager/pass2_patch_crt.rb,
lib/tebako/packager/pass2msys_patch.rb,
lib/tebako/packager/patch_libraries.rb,
lib/tebako/packager/patch_buildsystem.rb
Overview
Packager module
Defined Under Namespace
Modules: PatchBuildsystem, PatchHelpers, PatchLibraries, PatchLiterals, PatchMain Classes: Pass1APatch, Pass1DarwinPatch, Pass1MSysPatch, Pass1Patch, Pass2MSysPatch, Pass2NonMSysPatch, Pass2Patch, Patch, RubygemsPatch, RubygemsUpdatePatch
Constant Summary collapse
- FILES_TO_RESTORE =
%w[ common.mk configure config.status dir.c dln.c file.c gem_prelude.rb io.c main.c Makefile ruby.c thread_pthread.c util.c ext/bigdecimal/bigdecimal.h ext/Setup cygwin/GNUmakefile.in include/ruby/onigmo.h lib/rubygems/openssl.rb lib/rubygems/path_support.rb template/Makefile.in tool/mkconfig.rb win32/winmain.c win32/file.c ].freeze
- GNUMAKEFILE_IN_DLLTOOL_SUBST =
Substitutions for cygwin/Gnumakefile.in shared across Pass1 and Pass2
<<~SUBST $(Q) dlltool --output-lib=$(LIBRUBY) --output-def=tebako.def --export-all $(LIBRUBY_A) --output-exp=$(RUBY_EXP) # tebako patched SUBST
- GNUMAKEFILE_IN_WINMAIN_SUBST =
This MSYS specific thing ensure compilation of winmain.c Did try to understand why it did not work out of the box
<<~SUBST RUBYDEF = $(DLL_BASE_NAME).def # Start of tebako patch WINMAINOBJ = win32/winmain.$(OBJEXT) $(WINMAINOBJ): win32/winmain.c # End of tebako patch SUBST
- COMMON_MK_STATIC_RUBY_LINK_PATCH =
The static-ext ruby.exe link (common.mk STATIC_RUBY rule, identical in ruby 3.1-4.0) puts the vcpkg static OpenSSL ahead of the Windows system libs it references (Cert* from crypt32, WspiapiGet* from ws2_32); GNU ld's single pass then leaves them unresolved (tebako#345). Group-wrap the libs so resolution is order-insensitive. Shared across Pass1 (toolchain build) and Pass2 (final build, re-patched from pristine).
{ "$(MAINOBJ) $(DLDOBJS) $(LIBRUBY_A) $(MAINLIBS) $(EXTLIBS) $(LIBS) $(OUTFLAG)$@" => "$(MAINOBJ) $(DLDOBJS) $(LIBRUBY_A) -Wl,--start-group $(MAINLIBS) $(EXTLIBS) $(LIBS) " \ "-Wl,--end-group $(OUTFLAG)$@ # tebako patched" }.freeze
- TEMPLATE_MAKEFILE_IN_STATIC_RUBY_LINK_PATCH =
The PROGRAM link in template/Makefile.in (the generated Makefile -- the one the exts.mk submake actually links ruby.exe with) has the same static-lib ordering problem on msys. Ruby 3.3+ links MAINLIBS+EXTLIBS; 3.1/3.2 link MAINLIBS+LIBS+EXTLIBS -- wrap both shapes (only one matches per version, the other is a harmless no-op).
{ "$(LIBRUBYARG) $(MAINLIBS) $(EXTLIBS) $(OUTFLAG)$@" => "$(LIBRUBYARG) -Wl,--start-group $(MAINLIBS) $(EXTLIBS) -Wl,--end-group $(OUTFLAG)$@ " \ "# tebako patched", "$(LIBRUBYARG) $(MAINLIBS) $(LIBS) $(EXTLIBS) $(OUTFLAG)$@" => "$(LIBRUBYARG) -Wl,--start-group $(MAINLIBS) $(LIBS) $(EXTLIBS) -Wl,--end-group " \ "$(OUTFLAG)$@ # tebako patched" }.freeze
- IO_C_MSYS_BASE_PATCH =
{ "#define open rb_w32_uopen" => "#define open(p, f, m) tebako_open(3, (p), (f), (m))" }.freeze
- IO_C_MSYS_PATCH_PRE_32 =
{ "(rb_w32_io_cancelable_p((fptr)->fd) ? Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil))" => "((is_tebako_file_descriptor((fptr)->fd) || rb_w32_io_cancelable_p((fptr)->fd)) ? \\\n" \ "Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), Qnil))" }.freeze
- IO_C_MSYS_PATCH =
{ "(rb_w32_io_cancelable_p((fptr)->fd) ? Qnil : rb_io_wait(fptr->self, " \ "RB_INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT))" => "((is_tebako_file_descriptor((fptr)->fd) || rb_w32_io_cancelable_p((fptr)->fd)) ? \\\n" \ "Qnil : rb_io_wait(fptr->self, RB_INT2NUM(RUBY_IO_READABLE), RUBY_IO_TIMEOUT_DEFAULT))" }.freeze
- RUBY_C_MSYS_PATH_SUBST =
<<~SUBST /* -- Start of tebako patch -- */ VALUE path = within_tebako_memfs(paths) ? rb_str_new_cstr(paths) : RUBY_RELATIVE(paths, len); /* -- End of tebako patch -- */ SUBST
- RUBY_C_MSYS_PATCHES =
{ "#ifndef MAXPATHLEN" => "#{PatchLiterals::C_FILE_SUBST_LESS}\n#ifndef MAXPATHLEN", "VALUE path = RUBY_RELATIVE(paths, len);" => RUBY_C_MSYS_PATH_SUBST }.freeze
- WIN32_FILE_C_MSYS_SUBST =
<<~SUBST /* -- Start of tebako patch -- */ if (tebako_file_load_ok(path)) return ret; /* -- End of tebako patch -- */ wpath = mbstr_to_wstr(CP_UTF8, path, -1, &len); SUBST
- WIN32_FILE_C_MSYS_PATCHES =
{ "#ifndef INVALID_FILE_ATTRIBUTES" => "#{PatchLiterals::C_FILE_SUBST_LESS}\n#ifndef INVALID_FILE_ATTRIBUTES", "wpath = mbstr_to_wstr(CP_UTF8, path, -1, &len);" => WIN32_FILE_C_MSYS_SUBST }.freeze
- WIN32_WIN32_C_MSYS_SUBST =
<<~SUBST /* -- Start of tebako patch -- */ if (is_tebako_cwd()) { char* tebako_cwd = tebako_getcwd(NULL,0); if (tebako_cwd == NULL) { errno = ENOMEM; return NULL; } len = strlen(tebako_cwd) + 1; if (buffer) { if (size < len) { free(tebako_cwd); errno = ERANGE; return NULL; } } else { buffer = (*alloc)(len, arg); if (!buffer) { free(tebako_cwd); errno = ENOMEM; return NULL; } } translate_char(tebako_cwd, (char)0x5c, '/', CP_UTF8); strcpy(buffer, tebako_cwd); free(tebako_cwd); return buffer; } /* -- End of tebako patch -- */ len = GetCurrentDirectoryW(0, NULL); SUBST
- WIN32_WIN32_C_MSYS_PATCHES =
{ "#if defined _MSC_VER && _MSC_VER <= 1200" => "#{PatchLiterals::C_FILE_SUBST_LESS}\n#if defined _MSC_VER && _MSC_VER <= 1200", "len = GetCurrentDirectoryW(0, NULL);" => WIN32_WIN32_C_MSYS_SUBST, # Keep the clock renames in sync with Pass1MSysPatch::WIN32_WIN32_C_CLOCK_PATCH: # pass1 applies them for the toolchain build, this pass re-applies them # from pristine sources for the final build "clock_gettime(clockid_t clock_id, struct timespec *sp)" => "_dummy_clock_gettime(clockid_t clock_id, struct timespec *sp)", "clock_getres(clockid_t clock_id, struct timespec *sp)" => "_dummy_clock_getres(clockid_t clock_id, struct timespec *sp)" }.freeze
Class Method Summary collapse
-
.align_gem_ext_arch!(data_src_dir, layout_dir, api_ver) ⇒ Object
Native-gem extensions dir uses the dashed flavor of the arch string (arm64-darwin-23); align it to the runtime's as well.
-
.align_layout_to_runtime!(data_src_dir, layout_dir, ruby_ver) ⇒ Object
Rename the image's arch directories to the runtime's names and drop in the runtime's own rbconfig.rb.
- .align_stdlib_arch!(data_src_dir, layout_dir, api_ver) ⇒ Object
- .arch_dir_of(dir, marker) ⇒ Object
-
.build_app_image(options_manager, scenario_manager, layout_dir = nil) ⇒ Object
Deploy the application and build its DwarFS image for stitching onto a prebuilt runtime.
-
.check_prebuilt_env!(stash_dir, deps_bin_dir) ⇒ Object
Check that the packaging environment the prebuilt-runtime press needs (pristine Ruby stash + mkdwarfs) is in place.
-
.create_implib(src_dir, package_src_dir, app_name, ruby_ver) ⇒ Object
Create implib.
- .crt_pass1_patch(ostype, mount_point, ruby_ver) ⇒ Object
- .crt_pass2_patch(ostype, deps_lib_dir, ruby_ver) ⇒ Object
-
.deploy(target_dir, pre_dir, ruby_ver, fs_root, fs_entrance, cwd) ⇒ Object
Deploy.
- .do_patch(patch_map, root) ⇒ Object
- .finalize(src_dir, app_name, ruby_ver, patchelf, output_type) ⇒ Object
- .first_dir(dir) ⇒ Object
-
.init(stash_dir, src_dir, pre_dir, bin_dir) ⇒ Object
Init.
- .mkdwarfs(deps_bin_dir, data_bin_file, data_src_dir, descriptor = nil) ⇒ Object
-
.pass1(ostype, ruby_source_dir, mount_point, src_dir, ruby_ver) ⇒ Object
Pass1 Executed before Ruby build, patching ensures that Ruby itself is linked statically.
-
.pass1a(ruby_source_dir) ⇒ Object
Pass1A Patch gem_prelude.rb.
-
.pass2(ostype, ruby_source_dir, deps_lib_dir, ruby_ver) ⇒ Object
Pass2 Creates packaging environment, patching ensures that tebako package is linked statically.
-
.stash(src_dir, stash_dir, ruby_source_dir, ruby_ver) ⇒ Object
Stash Created and saves pristine Ruby environment that is used to deploy applications for packaging.
-
.write_entry_dispatcher(data_src_dir, scenario_manager, cwd) ⇒ Object
The prebuilt runtime packages are pressed in 'runtime' mode, so their compiled-in entry point is /local/stub.rb.
Class Method Details
.align_gem_ext_arch!(data_src_dir, layout_dir, api_ver) ⇒ Object
Native-gem extensions dir uses the dashed flavor of the arch string (arm64-darwin-23); align it to the runtime's as well
173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/tebako/packager.rb', line 173 def align_gem_ext_arch!(data_src_dir, layout_dir, api_ver) img_ext = File.join(data_src_dir, "lib", "ruby", "gems", api_ver, "extensions") rt_ext = File.join(layout_dir, "lib", "ruby", "gems", api_ver, "extensions") runtime_ext = first_dir(rt_ext) return if runtime_ext.nil? || !Dir.exist?(img_ext) Dir.children(img_ext).each do |d| next if d == runtime_ext || !File.directory?(File.join(img_ext, d)) FileUtils.mv(File.join(img_ext, d), File.join(img_ext, runtime_ext)) end end |
.align_layout_to_runtime!(data_src_dir, layout_dir, ruby_ver) ⇒ Object
Rename the image's arch directories to the runtime's names and drop in the runtime's own rbconfig.rb. No-op when the conventions already match (always the case off macOS, where the arch string carries no OS version).
154 155 156 157 |
# File 'lib/tebako/packager.rb', line 154 def align_layout_to_runtime!(data_src_dir, layout_dir, ruby_ver) align_stdlib_arch!(data_src_dir, layout_dir, ruby_ver.api_version) align_gem_ext_arch!(data_src_dir, layout_dir, ruby_ver.api_version) end |
.align_stdlib_arch!(data_src_dir, layout_dir, api_ver) ⇒ Object
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/tebako/packager.rb', line 159 def align_stdlib_arch!(data_src_dir, layout_dir, api_ver) runtime_arch = arch_dir_of(File.join(layout_dir, "lib", "ruby", api_ver), "rbconfig.rb") image_arch = arch_dir_of(File.join(data_src_dir, "lib", "ruby", api_ver), "rbconfig.rb") return if runtime_arch.nil? || image_arch.nil? || runtime_arch == image_arch puts " ... aligning app image layout to the runtime (#{image_arch} -> #{runtime_arch})" FileUtils.mv(File.join(data_src_dir, "lib", "ruby", api_ver, image_arch), File.join(data_src_dir, "lib", "ruby", api_ver, runtime_arch)) FileUtils.cp(File.join(layout_dir, "lib", "ruby", api_ver, runtime_arch, "rbconfig.rb"), File.join(data_src_dir, "lib", "ruby", api_ver, runtime_arch, "rbconfig.rb")) end |
.arch_dir_of(dir, marker) ⇒ Object
186 187 188 189 190 |
# File 'lib/tebako/packager.rb', line 186 def arch_dir_of(dir, marker) return nil unless Dir.exist?(dir) Dir.children(dir).find { |d| File.exist?(File.join(dir, d, marker)) } end |
.build_app_image(options_manager, scenario_manager, layout_dir = nil) ⇒ Object
Deploy the application and build its DwarFS image for stitching onto a prebuilt runtime. Same layout as the bundle-mode image, plus an entry dispatcher at /local/stub.rb (the runtime's compiled-in entry). When layout_dir is given (the resolved runtime's extracted layout), the image's arch conventions are aligned to the runtime's: ruby's compiled-in search paths come from the runtime build, while the local packaging environment names arch directories after the press machine -- on macOS the arch string embeds the kernel version (arm64-darwin23 vs arm64-darwin24), so they differ whenever press and runtime were built on different macOS releases.
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/tebako/packager.rb', line 139 def build_app_image(, scenario_manager, layout_dir = nil) # rubocop:disable Metrics/AbcSize init(.stash_dir, .data_src_dir, .data_pre_dir, .data_bin_dir) deploy(.data_src_dir, .data_pre_dir, .rv, .root, scenario_manager.fs_entrance, .cwd) align_layout_to_runtime!(.data_src_dir, layout_dir, .rv) if layout_dir write_entry_dispatcher(.data_src_dir, scenario_manager, .cwd) mkdwarfs(.deps_bin_dir, .data_bundle_file, .data_src_dir) .data_bundle_file end |
.check_prebuilt_env!(stash_dir, deps_bin_dir) ⇒ Object
Check that the packaging environment the prebuilt-runtime press needs (pristine Ruby stash + mkdwarfs) is in place
120 121 122 123 124 125 126 127 |
# File 'lib/tebako/packager.rb', line 120 def check_prebuilt_env!(stash_dir, deps_bin_dir) missing = [] missing << "Ruby environment stash (#{stash_dir})" unless Dir.exist?(stash_dir) missing << "mkdwarfs (#{deps_bin_dir})" if Dir.glob(File.join(deps_bin_dir, "mkdwarfs*")).empty? return if missing.empty? Tebako.packaging_error(128, missing.join(", ")) end |
.create_implib(src_dir, package_src_dir, app_name, ruby_ver) ⇒ Object
Create implib
73 74 75 76 77 78 79 80 |
# File 'lib/tebako/packager.rb', line 73 def create_implib(src_dir, package_src_dir, app_name, ruby_ver) a_name = File.basename(app_name, ".*") create_def(src_dir, a_name) puts " ... creating Windows import library" params = ["dlltool", "-d", def_fname(src_dir, a_name), "-D", out_fname(a_name), "--output-lib", lib_fname(package_src_dir, ruby_ver)] BuildHelpers.run_with_capture(params) end |
.crt_pass1_patch(ostype, mount_point, ruby_ver) ⇒ Object
38 39 40 41 42 43 44 45 46 47 |
# File 'lib/tebako/packager/pass1_patch.rb', line 38 def crt_pass1_patch(ostype, mount_point, ruby_ver) scmb = ScenarioManagerBase.new(ostype) if scmb.macos? Pass1DarwinPatch.new(mount_point, ruby_ver) elsif scmb.msys? Pass1MSysPatch.new(mount_point, ruby_ver) else Pass1Patch.new(mount_point, ruby_ver) end end |
.crt_pass2_patch(ostype, deps_lib_dir, ruby_ver) ⇒ Object
36 37 38 39 40 41 42 43 |
# File 'lib/tebako/packager/pass2_patch_crt.rb', line 36 def crt_pass2_patch(ostype, deps_lib_dir, ruby_ver) scmb = ScenarioManagerBase.new(ostype) if scmb.msys? Pass2MSysPatch.new(ostype, deps_lib_dir, ruby_ver) else Pass2NonMSysPatch.new(ostype, deps_lib_dir, ruby_ver) end end |
.deploy(target_dir, pre_dir, ruby_ver, fs_root, fs_entrance, cwd) ⇒ Object
Deploy
83 84 85 86 87 88 89 90 |
# File 'lib/tebako/packager.rb', line 83 def deploy(target_dir, pre_dir, ruby_ver, fs_root, fs_entrance, cwd) # rubocop:disable Metrics/ParameterLists puts "-- Running deploy script" deploy_helper = Tebako::DeployHelper.new(fs_root, fs_entrance, target_dir, pre_dir) deploy_helper.configure(ruby_ver, cwd) deploy_helper.deploy Tebako::Stripper.strip(deploy_helper, target_dir) end |
.do_patch(patch_map, root) ⇒ Object
92 93 94 |
# File 'lib/tebako/packager.rb', line 92 def do_patch(patch_map, root) patch_map.each { |fname, mapping| PatchHelpers.patch_file("#{root}/#{fname}", mapping) } end |
.finalize(src_dir, app_name, ruby_ver, patchelf, output_type) ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/tebako/packager.rb', line 96 def finalize(src_dir, app_name, ruby_ver, patchelf, output_type) puts "-- Running finalize script" RubyBuilder.new(ruby_ver, src_dir).target_build(output_type) exe_suffix = ScenarioManagerBase.new.exe_suffix src_name = File.join(src_dir, "ruby#{exe_suffix}") patchelf(src_name, patchelf) package_name = "#{app_name}#{exe_suffix}" # strip_or_copy(os_type, src_name, package_name) Tebako::Stripper.strip_file(src_name, package_name) puts "Created tebako #{output_type} at \"#{package_name}\"" end |
.first_dir(dir) ⇒ Object
192 193 194 195 196 |
# File 'lib/tebako/packager.rb', line 192 def first_dir(dir) return nil unless Dir.exist?(dir) Dir.children(dir).find { |d| File.directory?(File.join(dir, d)) } end |
.init(stash_dir, src_dir, pre_dir, bin_dir) ⇒ Object
Init
110 111 112 113 114 115 116 |
# File 'lib/tebako/packager.rb', line 110 def init(stash_dir, src_dir, pre_dir, bin_dir) puts "-- Running init script" puts " ... creating packaging environment at #{src_dir}" PatchHelpers.recreate([src_dir, pre_dir, bin_dir]) FileUtils.cp_r "#{stash_dir}/.", src_dir end |
.mkdwarfs(deps_bin_dir, data_bin_file, data_src_dir, descriptor = nil) ⇒ Object
213 214 215 216 217 218 219 |
# File 'lib/tebako/packager.rb', line 213 def mkdwarfs(deps_bin_dir, data_bin_file, data_src_dir, descriptor = nil) puts "-- Running mkdwarfs script" FileUtils.chmod("a+x", Dir.glob(File.join(deps_bin_dir, "mkdwarfs*"))) params = [File.join(deps_bin_dir, "mkdwarfs"), "-o", data_bin_file, "-i", data_src_dir, "--no-progress"] params << "--header" << descriptor if descriptor BuildHelpers.run_with_capture_v(params) end |
.pass1(ostype, ruby_source_dir, mount_point, src_dir, ruby_ver) ⇒ Object
Pass1 Executed before Ruby build, patching ensures that Ruby itself is linked statically
223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/tebako/packager.rb', line 223 def pass1(ostype, ruby_source_dir, mount_point, src_dir, ruby_ver) puts "-- Running pass1 script" PatchHelpers.recreate(src_dir) # Roll all known patches # Just in case we are recovering after some error PatchHelpers.restore_and_save_files(FILES_TO_RESTORE, ruby_source_dir, strict: false) patch = crt_pass1_patch(ostype, mount_point, ruby_ver) do_patch(patch.patch_map, ruby_source_dir) end |
.pass1a(ruby_source_dir) ⇒ Object
Pass1A Patch gem_prelude.rb
237 238 239 240 241 |
# File 'lib/tebako/packager.rb', line 237 def pass1a(ruby_source_dir) puts "-- Running pass1a script" patch = Pass1APatch.new do_patch(patch.patch_map, ruby_source_dir) end |
.pass2(ostype, ruby_source_dir, deps_lib_dir, ruby_ver) ⇒ Object
Pass2 Creates packaging environment, patching ensures that tebako package is linked statically
245 246 247 248 249 250 |
# File 'lib/tebako/packager.rb', line 245 def pass2(ostype, ruby_source_dir, deps_lib_dir, ruby_ver) puts "-- Running pass2 script" patch = crt_pass2_patch(ostype, deps_lib_dir, ruby_ver) do_patch(patch.patch_map, ruby_source_dir) end |
.stash(src_dir, stash_dir, ruby_source_dir, ruby_ver) ⇒ Object
Stash Created and saves pristine Ruby environment that is used to deploy applications for packaging
254 255 256 257 258 259 260 261 |
# File 'lib/tebako/packager.rb', line 254 def stash(src_dir, stash_dir, ruby_source_dir, ruby_ver) puts "-- Running stash script" RubyBuilder.new(ruby_ver, ruby_source_dir).toolchain_build puts " ... saving pristine Ruby environment to #{stash_dir}" PatchHelpers.recreate(stash_dir) FileUtils.cp_r "#{src_dir}/.", stash_dir end |
.write_entry_dispatcher(data_src_dir, scenario_manager, cwd) ⇒ Object
The prebuilt runtime packages are pressed in 'runtime' mode, so their compiled-in entry point is /local/stub.rb. A stitched package mounts the application image as the root filesystem; the dispatcher written here receives control from the runtime and loads the real entry point (replicating the bundle-mode working directory when --cwd was given).
203 204 205 206 207 208 209 210 211 |
# File 'lib/tebako/packager.rb', line 203 def write_entry_dispatcher(data_src_dir, scenario_manager, cwd) dispatcher = +"" dispatcher << "Dir.chdir(\"#{scenario_manager.fs_mount_point}/#{cwd}\")\n" unless cwd.nil? dispatcher << "load \"#{scenario_manager.fs_mount_point}#{scenario_manager.fs_entry_point}\"\n" local_dir = File.join(data_src_dir, "local") FileUtils.mkdir_p(local_dir) File.write(File.join(local_dir, "stub.rb"), dispatcher) end |