diff --git a/.github/workflows/alpine.yml b/.github/workflows/alpine.yml index b19c73cc..5c7f53fe 100644 --- a/.github/workflows/alpine.yml +++ b/.github/workflows/alpine.yml @@ -121,7 +121,7 @@ jobs: env: - { CC: gcc, CXX: g++, ALPINE_VER: "3.17" } - { CC: clang, CXX: clang++, ALPINE_VER: "3.17" } - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6' ] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1' ] env: ${{ matrix.env }} steps: - name: Install packages @@ -178,7 +178,7 @@ jobs: env: - { CC: gcc, CXX: g++, ALPINE_VER: "3.17" } - { CC: clang, CXX: clang++, ALPINE_VER: "3.17" } - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6' ] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1' ] env: ${{ matrix.env }} steps: - name: Install packages @@ -234,7 +234,7 @@ jobs: env: - { CC: gcc, CXX: g++, ALPINE_VER: "3.17" } - { CC: clang, CXX: clang++, ALPINE_VER: "3.17" } - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6'] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1'] env: ${{ matrix.env }} steps: - name: Install packages diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 114dfb1a..bb93fd32 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -114,7 +114,7 @@ jobs: - { os: macos-14, xcode: 15.4 } - { os: macos-15, xcode: 16.1 } - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6' ] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1' ] ruby_ver: ['3.2.6'] steps: - name: Checkout tebako packaging environment @@ -167,7 +167,7 @@ jobs: - { os: macos-14, xcode: 15.4 } - { os: macos-15, xcode: 16.1 } - package_ruby_ver: [ '3.3.6' ] + package_ruby_ver: [ '3.3.6', '3.4.1' ] ruby_ver: ['3.2.6'] steps: - name: Checkout tebako packaging environment @@ -218,7 +218,7 @@ jobs: - { os: macos-14, xcode: 15.0.1, LG_VADDR: 39} - { os: macos-14, xcode: 15.4 } - { os: macos-15, xcode: 16.1 } - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6' ] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1' ] ruby_ver: ['3.2.6'] steps: - name: Checkout tebako packaging environment diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 4901092d..ed2c1efd 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -112,7 +112,7 @@ jobs: strategy: fail-fast: false matrix: - package_ruby_ver: [ '2.7.8', '3.0.7', '3.1.6', '3.2.4', '3.2.5', '3.2.6', '3.3.3', '3.3.4', '3.3.5', '3.3.6' ] + package_ruby_ver: [ '2.7.8', '3.0.7', '3.1.6', '3.2.4', '3.2.5', '3.2.6', '3.3.3', '3.3.4', '3.3.5', '3.3.6', '3.4.1' ] env: - { os: ubuntu-20.04, CC: gcc-10, CXX: g++-10 } - { os: ubuntu-20.04, CC: clang-12, CXX: clang++-12 } @@ -162,7 +162,7 @@ jobs: strategy: fail-fast: false matrix: - package_ruby_ver: [ '2.7.8', '3.0.7', '3.1.6', '3.2.4', '3.2.5', '3.2.6', '3.3.3', '3.3.4', '3.3.5', '3.3.6' ] + package_ruby_ver: [ '2.7.8', '3.0.7', '3.1.6', '3.2.4', '3.2.5', '3.2.6', '3.3.3', '3.3.4', '3.3.5', '3.3.6', '3.4.1' ] env: - { os: ubuntu-20.04, CC: gcc-10, CXX: g++-10 } - { os: ubuntu-20.04, CC: clang-12, CXX: clang++-12 } @@ -211,7 +211,7 @@ jobs: strategy: fail-fast: false matrix: - package_ruby_ver: [ '2.7.8', '3.0.7', '3.1.6', '3.2.4', '3.2.5', '3.2.6', '3.3.3', '3.3.4', '3.3.5', '3.3.6' ] + package_ruby_ver: [ '2.7.8', '3.0.7', '3.1.6', '3.2.4', '3.2.5', '3.2.6', '3.3.3', '3.3.4', '3.3.5', '3.3.6', '3.4.1' ] env: - { os: ubuntu-20.04, CC: gcc-10, CXX: g++-10 } - { os: ubuntu-20.04, CC: clang-12, CXX: clang++-12 } diff --git a/.github/workflows/windows-msys.yml b/.github/workflows/windows-msys.yml index a9cdca3b..568b896d 100644 --- a/.github/workflows/windows-msys.yml +++ b/.github/workflows/windows-msys.yml @@ -149,7 +149,7 @@ jobs: - sys: ucrt64 CC: gcc CXX: g++ - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6' ] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1' ] script: ['bundle', 'app'] env: ${{ matrix.env }} @@ -232,7 +232,7 @@ jobs: - sys: ucrt64 CC: gcc CXX: g++ - package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6' ] + package_ruby_ver: [ '3.1.6', '3.2.6', '3.3.6', '3.4.1' ] env: ${{ matrix.env }} defaults: diff --git a/README.adoc b/README.adoc index 8b362619..600175c4 100644 --- a/README.adoc +++ b/README.adoc @@ -106,6 +106,7 @@ higher. | 3.1.6 | Linux, macOS, Windows | 3.2.{4,5,6} | Linux, macOS, Windows | 3.3.{3,4,5,6} | Linux, macOS, Windows +| 3.4.1 | Linux, macOS, Windows |=== diff --git a/include/tebako/tebako-prism.h b/include/tebako/tebako-prism.h new file mode 100644 index 00000000..0480d8e2 --- /dev/null +++ b/include/tebako/tebako-prism.h @@ -0,0 +1,51 @@ +#include + +static pm_string_init_result_t +tebako_string_file_init(pm_string_t *string, const char *filepath) { + + // Open the file for reading + int fd = open(filepath, O_RDONLY); + if (fd == -1) { + return PM_STRING_INIT_ERROR_GENERIC; + } + + // Stat the file to get the file size + struct stat sb; + if (fstat(fd, &sb) == -1) { + close(fd); + return PM_STRING_INIT_ERROR_GENERIC; + } + + // Ensure it is a file and not a directory + if (S_ISDIR(sb.st_mode)) { + close(fd); + return PM_STRING_INIT_ERROR_DIRECTORY; + } + + // Check the size to see if it's empty + size_t size = (size_t) sb.st_size; + if (size == 0) { + close(fd); + const uint8_t source[] = ""; + *string = (pm_string_t) { .type = PM_STRING_CONSTANT, .source = source, .length = 0 }; + return PM_STRING_INIT_SUCCESS; + } + + size_t length = (size_t) size; + uint8_t *source = xmalloc(length); + if (source == NULL) { + close(fd); + return PM_STRING_INIT_ERROR_GENERIC; + } + + long bytes_read = (long) read(fd, source, length); + close(fd); + + if (bytes_read == -1) { + xfree(source); + return PM_STRING_INIT_ERROR_GENERIC; + } + + *string = (pm_string_t) { .type = PM_STRING_OWNED, .source = source, .length = length }; + return PM_STRING_INIT_SUCCESS; +} diff --git a/lib/tebako/packager.rb b/lib/tebako/packager.rb index 17df8048..a8fe2cff 100644 --- a/lib/tebako/packager.rb +++ b/lib/tebako/packager.rb @@ -33,8 +33,8 @@ require_relative "deploy_helper" require_relative "ruby_builder" require_relative "stripper" -require_relative "packager/pass1" -require_relative "packager/pass1a" +require_relative "packager/pass1_patch" +require_relative "packager/pass1a_patch" require_relative "packager/pass2" require_relative "packager/patch_helpers" @@ -119,7 +119,8 @@ def pass1(ostype, ruby_source_dir, mount_point, src_dir, ruby_ver) puts "-- Running pass1 script" PatchHelpers.recreate(src_dir) - do_patch(Pass1.get_patch_map(ostype, mount_point, ruby_ver), ruby_source_dir) + patch = crt_pass1_patch(ostype, mount_point, ruby_ver) + do_patch(patch.patch_map, ruby_source_dir) # Roll back pass1a, pass2 patches # Just in case we are recovering after some error @@ -132,8 +133,8 @@ def pass1(ostype, ruby_source_dir, mount_point, src_dir, ruby_ver) # Patch gem_prelude.rb def pass1a(ruby_source_dir) puts "-- Running pass1a script" - - do_patch(Pass1A.get_patch_map, ruby_source_dir) + patch = Pass1APatch.new.patch_map + do_patch(patch, ruby_source_dir) end # Pass2 diff --git a/lib/tebako/packager/pass1.rb b/lib/tebako/packager/pass1.rb deleted file mode 100755 index 4336d187..00000000 --- a/lib/tebako/packager/pass1.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true - -# Copyright (c) 2021-2024 [Ribose Inc](https://www.ribose.com). -# All rights reserved. -# This file is a part of tebako -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS -# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. - -require_relative "patch_literals" -require_relative "patch_helpers" -require_relative "patch_buildsystem" - -# Tebako - an executable packager -module Tebako - module Packager - # Ruby patching definitions (pass1) - module Pass1 - # [TODO] looks like it does not exist in 3.1.4 - # May be obsolete - TOOL_RBINSTALL_RB_PATCH = { - " next if files.empty?" => "# tebako patched next if files.empty?" - }.freeze - - RUBYGEM_OPENSSL_RB_SUBST = <<~SUBST - # Start of tebako patch - require "openssl" - # End of tebako patch - autoload :OpenSSL, "openssl" - SUBST - - RUBYGEM_OPENSSL_RB_PATCH = { - 'autoload :OpenSSL, "openssl"' => RUBYGEM_OPENSSL_RB_SUBST - }.freeze - - EXT_SETUP_PATCH = { - "#option nodynamic" => "option nodynamic" - }.freeze - - EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH = { - "#include \"ruby/ruby.h\"" => <<~SUBST - #include "ruby/ruby.h" - - /* -- Start of tebako patch -- */ - #ifndef HAVE_RB_SYM2STR - #define HAVE_RB_SYM2STR 1 - #endif - - #ifndef HAVE_RB_ARRAY_CONST_PTR - #define HAVE_RB_ARRAY_CONST_PTR 1 - #endif - - #ifndef HAVE_RB_RATIONAL_NUM - #define HAVE_RB_RATIONAL_NUM 1 - #endif - - #ifndef HAVE_RB_RATIONAL_DEN - #define HAVE_RB_RATIONAL_DEN 1 - #endif - - #ifndef HAVE_RB_COMPLEX_REAL - #define HAVE_RB_COMPLEX_REAL - #endif - - #ifndef HAVE_RB_COMPLEX_IMAG - #define HAVE_RB_COMPLEX_IMAG - #endif - /* -- End of tebako patch -- */ - - SUBST - }.freeze - - # The logic for statically linked extensions is broken entirely in the latest Ruby versions. - # The code below looks reasonble - we do not set -bundle_loader when building with -with-static-ext option - # However Gems bundled with Ruby just ignore with-static-ext option and build extensions as shared libraries - # So the -bundler_loader option is required for them to link. - # It is strange that it is disabled in any case because this option does not create any issues for static - # libraries. - # --------------------------------------------------- - # elif test "x$EXTSTATIC" = x - # then : - # - # # When building exts as bundles, a mach-o bundle needs to know its loader - # # program to bind symbols from the ruby executable - # EXTDLDFLAGS="-bundle_loader '\$(BUILTRUBY)'" - - DARWIN_CONFIGURE_PATCH = { - "elif test \"x$EXTSTATIC\" = x" => "elif true" - }.freeze - - OPENSSL_EXTCONF_RB_SUBST = <<~SUBST - # Start of tebako patch - $defs.push("-DRUBY_EXPORT=1") - # End of tebako patch - - Logging::message "=== Checking done. ===\\n" - SUBST - - OPENSSL_EXTCONF_RB_PATCH = { - "Logging::message \"=== Checking done. ===\\n\"" => OPENSSL_EXTCONF_RB_SUBST - }.freeze - - class << self - def get_base_patch_map(mount_point) - { - # .................................................... - # It won't install gems with no files defined in spec - # However if - # -- we are installing a default gem from extension - # -- extension is build statically - # there may be no files install in addition to spec - # Example: io/wait extension (and others) - # [TODO] Check if it is still required - # No match and patching on Ruby 3.1.4 but works wo issues - "tool/rbinstall.rb" => TOOL_RBINSTALL_RB_PATCH, - - # .................................................... - # This is something that I cannnot explain - # (this patch does not seem related to static compilation) - "ext/bigdecimal/bigdecimal.h" => EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH, - - # .................................................... - # Allow only packaged gems (from within memfs) - "lib/rubygems/path_support.rb" => rubygems_path_support_patch(mount_point), - - # .................................................... - # Disable dynamic extensions - "ext/Setup" => EXT_SETUP_PATCH - } - end - - def get_patch_map(ostype, mount_point, ruby_ver) - patch_map = get_base_patch_map(mount_point) - - # .................................................... - patch_map.store("configure", DARWIN_CONFIGURE_PATCH) if ostype =~ /darwin/ - - # .................................................... - # autoload :OpenSSL, "openssl" - # fails to deal with a default gem from statically linked extension - patch_map.store("lib/rubygems/openssl.rb", RUBYGEM_OPENSSL_RB_PATCH) if ruby_ver.ruby3x? - - if ostype =~ /msys/ - # .................................................... - # Generate export definitions; use WinMain to build rubyw.exe - patch_map.store("cygwin/GNUmakefile.in", get_gnumakefile_in_patch_p1(ruby_ver)) - # .................................................... - # RUBY_EXPORT=1 (shall be set for static builds but is missing in openssl extension) - patch_map.store("ext/openssl/extconf.rb", OPENSSL_EXTCONF_RB_PATCH) - end - - patch_map - end - - private - - include Tebako::Packager::PatchLiterals - include Tebako::Packager::PatchBuildsystem - - def get_gnumakefile_in_patch_p1(ruby_ver) # rubocop:disable Metrics/MethodLength - objext = ruby_ver.ruby32? ? "$(OBJEXT)" : "@OBJEXT@" - { - " DLLWRAP += -mno-cygwin" => - "# tebako patched DLLWRAP += -mno-cygwin", - - "$(WPROGRAM): $(RUBYW_INSTALL_NAME).res.#{objext}" => - "$(WPROGRAM): $(RUBYW_INSTALL_NAME).res.#{objext} $(WINMAINOBJ) # tebako patched", - - "$(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(LIBS) -o $@" => - "$(WINMAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(LIBS) -o $@ # tebako patched", - - "--output-exp=$(RUBY_EXP) \\" => - "--output-exp=$(RUBY_EXP) --output-lib=$(LIBRUBY) --output-def=tebako.def \\", - - "--export-all $(LIBRUBY_A) $(LIBS) -o $(PROGRAM)" => - "--export-all $(LIBRUBY_A) $(LIBS) -o program-stub.exe # tebako patched", - - "@rm -f $(PROGRAM)" => - "@rm -f program-stub.exe # tebako patched", - - " $(Q) $(LDSHARED) $(DLDFLAGS) $(OBJS) dmyext.o $(SOLIBS) -o $(PROGRAM)" => - "# tebako patched $(Q) $(LDSHARED) $(DLDFLAGS) $(OBJS) dmyext.o $(SOLIBS) -o $(PROGRAM)", - - "RUBYDEF = $(DLL_BASE_NAME).def" => GNUMAKEFILE_IN_WINMAIN_SUBST - } - end - - def rubygems_path_support_patch_one(mount_point) - <<~SUBST - @home = env["GEM_HOME"] || Gem.default_dir - # -- Start of tebako patch -- - unless env["TEBAKO_PASS_THROUGH"] - @home = Gem.default_dir unless @home.index("#{mount_point}") == 0 - end - # -- End of tebako patch -- - - SUBST - end - - def rubygems_path_support_patch_two(mount_point) - <<~SUBST - - @path = split_gem_path env["GEM_PATH"], @home - # -- Start of tebako patch -- - unless env["TEBAKO_PASS_THROUGH"] - @path.keep_if do |xpath| - xpath.index("#{mount_point}") == 0 - end - end - # -- End of tebako patch -- - - SUBST - end - - def rubygems_path_support_patch(mount_point) - { - ' @home = env["GEM_HOME"] || Gem.default_dir' => rubygems_path_support_patch_one(mount_point), - ' @path = split_gem_path env["GEM_PATH"], @home' => rubygems_path_support_patch_two(mount_point) - } - end - end - end - end -end diff --git a/lib/tebako/packager/pass1_patch.rb b/lib/tebako/packager/pass1_patch.rb new file mode 100644 index 00000000..e7bd0a7d --- /dev/null +++ b/lib/tebako/packager/pass1_patch.rb @@ -0,0 +1,301 @@ +# frozen_string_literal: true + +# Copyright (c) 2021-2025 [Ribose Inc](https://www.ribose.com). +# All rights reserved. +# This file is a part of tebako +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +require_relative "patch_helpers" +require_relative "patch_buildsystem" + +require_relative "patch" +# Tebako - an executable packager +module Tebako + # Packager module + module Packager + class << self + def crt_pass1_patch(os_type, mount_point, ruby_ver) + case os_type + when /darwin/ + Pass1DarwinPatch.new(mount_point, ruby_ver) + when /msys/ + Pass1MSysPatch.new(mount_point, ruby_ver) + else + Pass1Patch.new(mount_point, ruby_ver) + end + end + end + # Ruby patching definitions (pass1 - common) + class Pass1Patch < Patch + # [TODO] looks like it does not exist in 3.1.4 + # May be obsolete + TOOL_RBINSTALL_RB_PATCH = { + " next if files.empty?" => "# tebako patched next if files.empty?" + }.freeze + + RUBYGEM_OPENSSL_RB_SUBST = <<~SUBST + # Start of tebako patch + require "openssl" + # End of tebako patch + autoload :OpenSSL, "openssl" + SUBST + + RUBYGEM_OPENSSL_RB_PATCH = { + 'autoload :OpenSSL, "openssl"' => RUBYGEM_OPENSSL_RB_SUBST + }.freeze + + EXT_SETUP_PATCH = { + "#option nodynamic" => "option nodynamic" + }.freeze + + # .................................................... + # This is something that I cannnot explain + # (this patch does not seem related to static compilation) + + EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH = { + "#include \"ruby/ruby.h\"" => <<~SUBST + #include "ruby/ruby.h" + + /* -- Start of tebako patch -- */ + #ifndef HAVE_RB_SYM2STR + #define HAVE_RB_SYM2STR 1 + #endif + + #ifndef HAVE_RB_ARRAY_CONST_PTR + #define HAVE_RB_ARRAY_CONST_PTR 1 + #endif + + #ifndef HAVE_RB_RATIONAL_NUM + #define HAVE_RB_RATIONAL_NUM 1 + #endif + + #ifndef HAVE_RB_RATIONAL_DEN + #define HAVE_RB_RATIONAL_DEN 1 + #endif + + #ifndef HAVE_RB_COMPLEX_REAL + #define HAVE_RB_COMPLEX_REAL + #endif + + #ifndef HAVE_RB_COMPLEX_IMAG + #define HAVE_RB_COMPLEX_IMAG + #endif + /* -- End of tebako patch -- */ + + SUBST + }.freeze + + # The logic for statically linked extensions is broken entirely in the latest Ruby versions. + # The code below looks reasonble - we do not set -bundle_loader when building with -with-static-ext option + # However Gems bundled with Ruby just ignore with-static-ext option and build extensions as shared libraries + # So the -bundler_loader option is required for them to link. + # It is strange that it is disabled in any case because this option does not create any issues for static + # libraries. + # --------------------------------------------------- + # elif test "x$EXTSTATIC" = x + # then : + # + # # When building exts as bundles, a mach-o bundle needs to know its loader + # # program to bind symbols from the ruby executable + # EXTDLDFLAGS="-bundle_loader '\$(BUILTRUBY)'" + + OPENSSL_EXTCONF_RB_SUBST = <<~SUBST + # Start of tebako patch + $defs.push("-DRUBY_EXPORT=1") + # End of tebako patch + + Logging::message "=== Checking done. ===\\n" + SUBST + + OPENSSL_EXTCONF_RB_PATCH = { + "Logging::message \"=== Checking done. ===\\n\"" => OPENSSL_EXTCONF_RB_SUBST + }.freeze + + def initialize(mountpoint, ruby_ver) + super() + @mountpoint = mountpoint + @ruby_ver = ruby_ver + end + + def base_patch_map + { + # .................................................... + # It won't install gems with no files defined in spec + # However if + # -- we are installing a default gem from extension + # -- extension is build statically + # there may be no files install in addition to spec + # Example: io/wait extension (and others) + # [TODO] Check if it is still required + # No match and patching on Ruby 3.1.4 but works wo issues + "tool/rbinstall.rb" => TOOL_RBINSTALL_RB_PATCH, + + # .................................................... + # Allow only packaged gems (from within memfs) + "lib/rubygems/path_support.rb" => rubygems_path_support_patch, + + # .................................................... + # Disable dynamic extensions + "ext/Setup" => EXT_SETUP_PATCH + } + end + + def patch_map + pm = base_patch_map + pm.merge!(super) + + # .................................................... + pm.store("ext/bigdecimal/bigdecimal.h", EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH) unless @ruby_ver.ruby34? + + # .................................................... + # autoload :OpenSSL, "openssl" + # fails to deal with a default gem from statically linked extension + pm.store("lib/rubygems/openssl.rb", RUBYGEM_OPENSSL_RB_PATCH) if @ruby_ver.ruby3x? + + pm.freeze + end + + private + + def rubygems_path_support_patch_one + <<~SUBST + @home = env["GEM_HOME"] || Gem.default_dir + # -- Start of tebako patch -- + unless env["TEBAKO_PASS_THROUGH"] + @home = Gem.default_dir unless @home.index("#{@mount_point}") == 0 + end + # -- End of tebako patch -- + + SUBST + end + + def rubygems_path_support_patch_two + <<~SUBST + + @path = split_gem_path env["GEM_PATH"], @home + # -- Start of tebako patch -- + unless env["TEBAKO_PASS_THROUGH"] + @path.keep_if do |xpath| + xpath.index("#{@mount_point}") == 0 + end + end + # -- End of tebako patch -- + + SUBST + end + + def rubygems_path_support_patch + { + ' @home = env["GEM_HOME"] || Gem.default_dir' => rubygems_path_support_patch_one, + ' @path = split_gem_path env["GEM_PATH"], @home' => rubygems_path_support_patch_two + } + end + end + + # Ruby patching definitions (pass1 - darwin) + class Pass1DarwinPatch < Pass1Patch + DARWIN_CONFIGURE_PATCH = { + "elif test \"x$EXTSTATIC\" = x" => "elif true" + }.freeze + + def patch_map + pm = { "configure" => DARWIN_CONFIGURE_PATCH } + pm.merge!(super) + pm.freeze + end + end + + # Ruby patching definitions (pass1 - Windows) + class Pass1MSysPatch < Pass1Patch + INCLUDE_RUBY_ONIGMO_H_PATCH = { + "# define ONIG_EXTERN RUBY_EXTERN" => "# define ONIG_EXTERN extern" + }.freeze + + WIN32_WINMAIN_C_PATCH = { + "WinMain(HINSTANCE current, HINSTANCE prev, LPSTR cmdline, int showcmd)" => + "wWinMain(HINSTANCE current, HINSTANCE prev, LPWSTR cmdline, int showcmd)" + }.freeze + + def patch_map + pm = msys_patches + pm.merge!(super) + pm.freeze + end + + private + + include Tebako::Packager::PatchBuildsystem + + def gnumakefile_in_patch_p1 # rubocop:disable Metrics/MethodLength + objext = @ruby_ver.ruby32? ? "$(OBJEXT)" : "@OBJEXT@" + { + " DLLWRAP += -mno-cygwin" => + "# tebako patched DLLWRAP += -mno-cygwin", + + "$(WPROGRAM): $(RUBYW_INSTALL_NAME).res.#{objext}" => + "$(WPROGRAM): $(RUBYW_INSTALL_NAME).res.#{objext} $(WINMAINOBJ) # tebako patched", + + "$(MAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(LIBS) -o $@" => + "$(WINMAINOBJ) $(EXTOBJS) $(LIBRUBYARG) $(LIBS) -o $@ # tebako patched", + + "--output-exp=$(RUBY_EXP) \\" => + "--output-exp=$(RUBY_EXP) --output-lib=$(LIBRUBY) --output-def=tebako.def \\", + + "--export-all $(LIBRUBY_A) $(LIBS) -o $(PROGRAM)" => + "--export-all $(LIBRUBY_A) $(LIBS) -o program-stub.exe # tebako patched", + + "@rm -f $(PROGRAM)" => + "@rm -f program-stub.exe # tebako patched", + + " $(Q) $(LDSHARED) $(DLDFLAGS) $(OBJS) dmyext.o $(SOLIBS) -o $(PROGRAM)" => + "# tebako patched $(Q) $(LDSHARED) $(DLDFLAGS) $(OBJS) dmyext.o $(SOLIBS) -o $(PROGRAM)", + + "RUBYDEF = $(DLL_BASE_NAME).def" => GNUMAKEFILE_IN_WINMAIN_SUBST + } + end + + def msys_base_patches + { + # .................................................... + # Generate export definitions; use WinMain to build rubyw.exe + "cygwin/GNUmakefile.in" => gnumakefile_in_patch_p1, + # .................................................... + # RUBY_EXPORT=1 (shall be set for static builds but is missing in openssl extension) + "ext/openssl/extconf.rb" => OPENSSL_EXTCONF_RB_PATCH + } + end + + def msys_patches + pm = msys_base_patches + + if @ruby_ver.ruby34? + # .................................................... + # RUBY_EXTERN shall be extern for static build but is set to __declspec(dllimport) for encodin libarary + pm.store("include/ruby/onigmo.h", INCLUDE_RUBY_ONIGMO_H_PATCH) + pm.store("win32/winmain.c", WIN32_WINMAIN_C_PATCH) + end + pm + end + end + end +end diff --git a/lib/tebako/packager/pass1a_patch.rb b/lib/tebako/packager/pass1a_patch.rb new file mode 100644 index 00000000..574ce71b --- /dev/null +++ b/lib/tebako/packager/pass1a_patch.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# Copyright (c) 2024-2025 [Ribose Inc](https://www.ribose.com). +# All rights reserved. +# This file is a part of tebako +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +require_relative "patch" +# Tebako - an executable packager +module Tebako + module Packager + # Ruby patching definitions (pass1a) + class Pass1APatch < Patch + GEM_PRELUDE_RB_PATCH = { + "if defined?(DidYouMean)" => <<~SUBST + if defined?(DidYouMean) + + # -- Start of tebako patch -- + begin + require 'tebako-runtime' + rescue LoadError + warn "'tebako-runtime' was not loaded." + end + # -- End of tebako patch -- + SUBST + }.freeze + + def patch_map + { + "gem_prelude.rb" => GEM_PRELUDE_RB_PATCH + }.freeze + end + end + end +end diff --git a/lib/tebako/packager/pass2.rb b/lib/tebako/packager/pass2.rb index dd5587ce..8f56b03e 100755 --- a/lib/tebako/packager/pass2.rb +++ b/lib/tebako/packager/pass2.rb @@ -46,6 +46,8 @@ def get_patch_map(ostype, deps_lib_dir, ruby_ver) patch_map.store("common.mk", COMMON_MK_PATCH) end extend_patch_map_r33(patch_map, ostype, deps_lib_dir, ruby_ver) + patch_map.store("prism_compile.c", PRISM_PATCHES) if ruby_ver.ruby34? + patch_map end private @@ -68,11 +70,10 @@ def get_dir_c_patch(ostype) end def get_dln_c_patch(ostype, ruby_ver) + pattern = "#ifndef dln_loaderror" # Not using substitutions of dlxxx functions on Windows dln_c_patch = { - "static const char funcname_prefix[sizeof(FUNCNAME_PREFIX) - 1] = FUNCNAME_PREFIX;" => - "#{PatchHelpers.msys?(ostype) ? C_FILE_SUBST_LESS : C_FILE_SUBST}\n" \ - "static const char funcname_prefix[sizeof(FUNCNAME_PREFIX) - 1] = FUNCNAME_PREFIX;\n" + pattern => "#{PatchHelpers.msys?(ostype) ? C_FILE_SUBST_LESS : C_FILE_SUBST}\n#{pattern}\n" } if PatchHelpers.msys?(ostype) diff --git a/lib/tebako/packager/pass1a.rb b/lib/tebako/packager/patch.rb similarity index 79% rename from lib/tebako/packager/pass1a.rb rename to lib/tebako/packager/patch.rb index 1ebab355..0cfb21a6 100644 --- a/lib/tebako/packager/pass1a.rb +++ b/lib/tebako/packager/patch.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) 2024 [Ribose Inc](https://www.ribose.com). +# Copyright (c) 2025 [Ribose Inc](https://www.ribose.com). # All rights reserved. # This file is a part of tebako # @@ -25,21 +25,13 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -require_relative "patch_literals" - # Tebako - an executable packager module Tebako module Packager - # Ruby patching definitions (pass1a) - module Pass1A - class << self - def get_patch_map # rubocop:disable Naming/AccessorMethodName - { - "gem_prelude.rb" => GEM_PRELUDE_RB_PATCH - } - end - - include Tebako::Packager::PatchLiterals + # Ruby patching definitions (common base) + class Patch + def patch_map + {}.freeze end end end diff --git a/lib/tebako/packager/patch_literals.rb b/lib/tebako/packager/patch_literals.rb index 7d5e914a..a652fbcf 100755 --- a/lib/tebako/packager/patch_literals.rb +++ b/lib/tebako/packager/patch_literals.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -# Copyright (c) 2023-2024 [Ribose Inc](https://www.ribose.com). +# Copyright (c) 2023-2025 [Ribose Inc](https://www.ribose.com). # All rights reserved. # This file is a part of tebako # @@ -150,6 +150,10 @@ module PatchLiterals "else if (e == EIO /* tebako patch */ && !within_tebako_memfs(path)) {" }.freeze + DLN_C_PRE34_PATCH_PATTERN = "static const char funcname_prefix[sizeof(FUNCNAME_PREFIX) - 1] = FUNCNAME_PREFIX;" + + DLN_C_PATCH_PATTERN = "#define init_funcname(buf, file) build_funcname(FUNCNAME_PREFIX, buf, file)" + DLN_C_MSYS_PATCH_PRE32 = { " winfile = rb_w32_mbstr_to_wstr(CP_UTF8, file, -1, NULL);" => <<~SUBST /* -- Start of tebako patch -- */ @@ -209,20 +213,6 @@ module PatchLiterals /* -- End of tebako patch -- */ SUBST - GEM_PRELUDE_RB_PATCH = { - "if defined?(DidYouMean)" => <<~SUBST - if defined?(DidYouMean) - - # -- Start of tebako patch -- - begin - require 'tebako-runtime' - rescue LoadError - warn "'tebako-runtime' was not loaded." - end - # -- End of tebako patch -- - SUBST - }.freeze - IO_C_SUBST = <<~SUBST /* -- Start of tebako patch -- */ if (is_tebako_file_descriptor(fd)) return; @@ -328,6 +318,40 @@ module PatchLiterals "mf.macro \"EXTLIBS\", $extlibs" => "# mf.macro \"EXTLIBS\", $extlibs tebako patched" } }.freeze + + PRISM_PATTERN_1 = "pm_string_init_result_t init_result = pm_read_file(&result->input, RSTRING_PTR(filepath));" + + PRISM_SUBST_1 = <<~SUBST + /* -- Start of tebako patch -- */ + pm_string_init_result_t init_result; + if (within_tebako_memfs(RSTRING_PTR(filepath))) + { + init_result = tebako_string_file_init(&result->input, RSTRING_PTR(filepath)); + } + else + { + init_result = pm_read_file(&result->input, RSTRING_PTR(filepath)); + } + /* -- End of tebako patch -- */ + SUBST + + PRISM_PATTERN_2 = "#include \"prism.h\"" + PRISM_SUBST_2 = <<~SUBST + #{PRISM_PATTERN_2} + + /* -- Start of tebako patch -- */ + #include + #include + #include + #include + /* -- End of tebako patch -- */ + + SUBST + + PRISM_PATCHES = { + PRISM_PATTERN_1 => PRISM_SUBST_1, + PRISM_PATTERN_2 => PRISM_SUBST_2 + }.freeze end end end diff --git a/lib/tebako/ruby_version.rb b/lib/tebako/ruby_version.rb index dec09ce0..4af7ff52 100755 --- a/lib/tebako/ruby_version.rb +++ b/lib/tebako/ruby_version.rb @@ -41,7 +41,8 @@ class RubyVersion "3.3.3" => "83c05b2177ee9c335b631b29b8c077b4770166d02fa527f3a9f6a40d13f3cce2", "3.3.4" => "fe6a30f97d54e029768f2ddf4923699c416cdbc3a6e96db3e2d5716c7db96a34", "3.3.5" => "3781a3504222c2f26cb4b9eb9c1a12dbf4944d366ce24a9ff8cf99ecbce75196", - "3.3.6" => "8dc48fffaf270f86f1019053f28e51e4da4cce32a36760a0603a9aee67d7fd8d" + "3.3.6" => "8dc48fffaf270f86f1019053f28e51e4da4cce32a36760a0603a9aee67d7fd8d", + "3.4.1" => "3d385e5d22d368b064c817a13ed8e3cc3f71a7705d7ed1bae78013c33aa7c87f" }.freeze MIN_RUBY_VERSION_WINDOWS = "3.1.6" @@ -77,6 +78,10 @@ def ruby33? @ruby33 ||= ruby3x? && @ruby_version[2].to_i >= 3 end + def ruby34? + @ruby34 ||= ruby3x? && @ruby_version[2].to_i >= 4 + end + def api_version @api_version ||= "#{@ruby_version.split(".")[0..1].join(".")}.0" end diff --git a/lib/tebako/version.rb b/lib/tebako/version.rb index 9a06a894..97f95b00 100644 --- a/lib/tebako/version.rb +++ b/lib/tebako/version.rb @@ -26,5 +26,5 @@ # POSSIBILITY OF SUCH DAMAGE. module Tebako - VERSION = "0.11.0" + VERSION = "0.12.0" end diff --git a/spec/packager/pass1_patch_spec.rb b/spec/packager/pass1_patch_spec.rb new file mode 100644 index 00000000..d8976da2 --- /dev/null +++ b/spec/packager/pass1_patch_spec.rb @@ -0,0 +1,220 @@ +# frozen_string_literal: true + +# Copyright (c) 2025 [Ribose Inc](https://www.ribose.com). +# All rights reserved. +# This file is a part of tebako +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +RSpec.describe Tebako::Packager do + describe ".crt_pass1_patch" do + let(:mountpoint) { "/mnt" } + let(:ruby_ver) { "3.3.6" } + + it "returns Pass1DarwinPatch for darwin os_type" do + patch = described_class.crt_pass1_patch("darwin", mountpoint, ruby_ver) + expect(patch).to be_a(Tebako::Packager::Pass1DarwinPatch) + end + + it "returns Pass1MSysPatch for msys os_type" do + patch = described_class.crt_pass1_patch("msys", mountpoint, ruby_ver) + expect(patch).to be_a(Tebako::Packager::Pass1MSysPatch) + end + + it "returns Pass1Patch for other os_types" do + patch = described_class.crt_pass1_patch("linux-musl", mountpoint, ruby_ver) + expect(patch).to be_a(Tebako::Packager::Pass1Patch) + end + end +end + +RSpec.describe Tebako::Packager::Pass1Patch do # rubocop:disable Metrics/BlockLength + let(:mountpoint) { "/mnt" } + + describe "#initialize" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.3.6") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "initializes with mountpoint and ruby_ver" do + expect(patch.instance_variable_get(:@mountpoint)).to eq(mountpoint) + expect(patch.instance_variable_get(:@ruby_ver)).to eq(ruby_ver) + end + end + + describe "#patch_map" do # rubocop:disable Metrics/BlockLength + let(:base_patch_map) do + { + "tool/rbinstall.rb" => "TOOL_RBINSTALL_RB_PATCH", + "lib/rubygems/path_support.rb" => "rubygems_path_support_patch", + "ext/Setup" => "EXT_SETUP_PATCH" + } + end + + before do + allow_any_instance_of(described_class).to receive(:base_patch_map).and_return(base_patch_map) + end + + context "when ruby_ver is not ruby34 and is ruby3x" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.3.6") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "includes additional patches for ruby3x" do + expected_patch_map = base_patch_map.merge( + "ext/bigdecimal/bigdecimal.h" => described_class::EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH, + "lib/rubygems/openssl.rb" => described_class::RUBYGEM_OPENSSL_RB_PATCH + ) + expect(patch.patch_map).to eq(expected_patch_map) + end + end + + context "when ruby_ver is ruby34" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.4.1") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "does not include additional patches for ruby3x" do + expected_patch_map = base_patch_map + expect(patch.patch_map).to eq(expected_patch_map) + end + end + + context "when ruby_ver is not ruby3x" do + let(:ruby_ver) { Tebako::RubyVersion.new("2.7.8") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + before do + stub_const("RUBY_PLATFORM", "x86_64-linux") + end + + it "does not include additional patches for ruby3x" do + expected_patch_map = base_patch_map.merge( + "ext/bigdecimal/bigdecimal.h" => described_class::EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH + ) + expect(patch.patch_map).to eq(expected_patch_map) + end + end + + it "returns a frozen hash" do + ruby_ver = Tebako::RubyVersion.new("3.3.6") + patch = described_class.new(mountpoint, ruby_ver) + expect(patch.patch_map).to be_frozen + end + end +end + +RSpec.describe Tebako::Packager::Pass1DarwinPatch do # rubocop:disable Metrics/BlockLength + let(:mountpoint) { "/mnt" } + let(:ruby_ver) { double("RubyVersion", ruby34?: false, ruby3x?: true) } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + describe "#initialize" do + it "initializes with mountpoint and ruby_ver" do + expect(patch.instance_variable_get(:@mountpoint)).to eq(mountpoint) + expect(patch.instance_variable_get(:@ruby_ver)).to eq(ruby_ver) + end + end + + describe "#patch_map" do + let(:base_patch_map) do + { + "tool/rbinstall.rb" => "TOOL_RBINSTALL_RB_PATCH", + "lib/rubygems/path_support.rb" => "rubygems_path_support_patch", + "ext/Setup" => "EXT_SETUP_PATCH" + } + end + + before do + allow_any_instance_of(described_class).to receive(:base_patch_map).and_return(base_patch_map) + end + + context "when ruby_ver is not ruby34 and is ruby3x" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.3.6") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "includes additional patches for MacOs" do + expected_patch_map = base_patch_map.merge( + "configure" => described_class::DARWIN_CONFIGURE_PATCH, + "ext/bigdecimal/bigdecimal.h" => described_class::EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH, + "lib/rubygems/openssl.rb" => described_class::RUBYGEM_OPENSSL_RB_PATCH + ) + expect(patch.patch_map).to eq(expected_patch_map) + end + end + end +end + +RSpec.describe Tebako::Packager::Pass1MSysPatch do # rubocop:disable Metrics/BlockLength + let(:mountpoint) { "/mnt" } + let(:base_patch_map) do + { + "tool/rbinstall.rb" => "TOOL_RBINSTALL_RB_PATCH", + "lib/rubygems/path_support.rb" => "rubygems_path_support_patch", + "ext/Setup" => "EXT_SETUP_PATCH" + } + end + + before do + allow_any_instance_of(described_class).to receive(:base_patch_map).and_return(base_patch_map) + end + + describe "#initialize" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.3.6") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "initializes with mountpoint and ruby_ver" do + expect(patch.instance_variable_get(:@mountpoint)).to eq(mountpoint) + expect(patch.instance_variable_get(:@ruby_ver)).to eq(ruby_ver) + end + end + + describe "#patch_map" do # rubocop:disable Metrics/BlockLength + context "when ruby_ver is not ruby34 and is ruby3x" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.3.6") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "includes additional patches for MSys and ruby3x" do + expected_patch_map = base_patch_map.merge( + "ext/bigdecimal/bigdecimal.h" => described_class::EXT_BIGDECIMAL_BIGDECIMAL_H_PATCH, + "lib/rubygems/openssl.rb" => described_class::RUBYGEM_OPENSSL_RB_PATCH, + "cygwin/GNUmakefile.in" => patch.send(:gnumakefile_in_patch_p1), + "ext/openssl/extconf.rb" => described_class::OPENSSL_EXTCONF_RB_PATCH + ) + expect(patch.patch_map).to eq(expected_patch_map) + end + end + + context "when ruby_ver is ruby34" do + let(:ruby_ver) { Tebako::RubyVersion.new("3.4.1") } + let(:patch) { described_class.new(mountpoint, ruby_ver) } + + it "includes additional patches for MSys and ruby34" do + expected_patch_map = base_patch_map.merge( + "cygwin/GNUmakefile.in" => patch.send(:gnumakefile_in_patch_p1), + "ext/openssl/extconf.rb" => described_class::OPENSSL_EXTCONF_RB_PATCH, + "lib/rubygems/openssl.rb" => described_class::RUBYGEM_OPENSSL_RB_PATCH, + "include/ruby/onigmo.h" => described_class::INCLUDE_RUBY_ONIGMO_H_PATCH, + "win32/winmain.c" => described_class::WIN32_WINMAIN_C_PATCH + ) + expect(patch.patch_map).to eq(expected_patch_map) + end + end + end +end diff --git a/spec/packager/pass1a_patch_spec.rb b/spec/packager/pass1a_patch_spec.rb new file mode 100644 index 00000000..c707a1e3 --- /dev/null +++ b/spec/packager/pass1a_patch_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +# Copyright (c) 2025 [Ribose Inc](https://www.ribose.com). +# All rights reserved. +# This file is a part of tebako +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +RSpec.describe Tebako::Packager::Pass1APatch do + describe "#patch_map" do + it "returns a hash with the correct patch for gem_prelude.rb" do + patch = Tebako::Packager::Pass1APatch.new + expected_patch = { + "if defined?(DidYouMean)" => <<~SUBST + if defined?(DidYouMean) + + # -- Start of tebako patch -- + begin + require 'tebako-runtime' + rescue LoadError + warn "'tebako-runtime' was not loaded." + end + # -- End of tebako patch -- + SUBST + } + expect(patch.patch_map).to eq({ "gem_prelude.rb" => expected_patch }) + end + + it "returns a frozen hash" do + patch = Tebako::Packager::Pass1APatch.new + expect(patch.patch_map).to be_frozen + end + end +end diff --git a/spec/packager/patch_spec.rb b/spec/packager/patch_spec.rb new file mode 100644 index 00000000..5405c243 --- /dev/null +++ b/spec/packager/patch_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# Copyright (c) 2025 [Ribose Inc](https://www.ribose.com). +# All rights reserved. +# This file is a part of tebako +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +RSpec.describe Tebako::Packager::Patch do + describe "#patch_map" do + it "returns an empty hash" do + patch = Tebako::Packager::Patch.new + expect(patch.patch_map).to eq({}) + end + + it "returns a frozen hash" do + patch = Tebako::Packager::Patch.new + expect(patch.patch_map).to be_frozen + end + end +end diff --git a/spec/packager_spec.rb b/spec/packager_spec.rb index c7dee552..0b148e42 100644 --- a/spec/packager_spec.rb +++ b/spec/packager_spec.rb @@ -190,12 +190,14 @@ let(:ruby_source_dir) { "/path/to/ruby_source" } let(:mount_point) { "/__tebako_memfs__" } let(:src_dir) { "/path/to/src" } - let(:ruby_ver) { "2.7.2" } + let(:ruby_ver) { Tebako::RubyVersion.new("3.2.6") } let(:patch_map) { { "file1" => "patch1", "file2" => "patch2" } } before do allow(Tebako::Packager::PatchHelpers).to receive(:recreate) - allow(Tebako::Packager::Pass1).to receive(:get_patch_map).and_return(patch_map) + allow_any_instance_of(Tebako::Packager::Pass1Patch) + .to receive(:patch_map) + .and_return(patch_map) allow(Tebako::Packager).to receive(:do_patch) allow(Tebako::Packager::PatchHelpers).to receive(:restore_and_save_files) end @@ -242,12 +244,14 @@ let(:patch_map) { { "file1" => "patch1", "file2" => "patch2" } } before do - allow(Tebako::Packager::Pass1A).to receive(:get_patch_map).and_return(patch_map) + allow_any_instance_of(Tebako::Packager::Pass1APatch).to receive(:patch_map).and_return(patch_map) allow(Tebako::Packager).to receive(:do_patch) end it "calls do_patch with the correct parameters" do - expect(Tebako::Packager).to receive(:do_patch).with(patch_map, ruby_source_dir) + expect(Tebako::Packager) + .to receive(:do_patch) + .with(patch_map, ruby_source_dir) described_class.pass1a(ruby_source_dir) end end diff --git a/spec/ruby_version_spec.rb b/spec/ruby_version_spec.rb index 05faa2af..150d9a5f 100644 --- a/spec/ruby_version_spec.rb +++ b/spec/ruby_version_spec.rb @@ -79,6 +79,10 @@ expect(version.ruby33?).to be false end + it "returns false for ruby34?" do + expect(version.ruby34?).to be false + end + it "returns '3.1.0' for api_version" do expect(version.api_version).to eq("3.1.0") end @@ -111,6 +115,10 @@ expect(version.ruby33?).to be false end + it "returns false for ruby34?" do + expect(version.ruby34?).to be false + end + it "returns '3.2.0' for api_version" do expect(version.api_version).to eq("3.2.0") end @@ -143,6 +151,10 @@ expect(version.ruby33?).to be true end + it "returns false for ruby34?" do + expect(version.ruby34?).to be false + end + it "returns '3.3.0' for api_version" do expect(version.api_version).to eq("3.3.0") end @@ -152,6 +164,42 @@ end end + context "with version 3.4.1" do + let(:version) { Tebako::RubyVersion.new("3.4.1") } + + it "returns true for ruby3x?" do + expect(version.ruby3x?).to be true + end + + it "returns true for ruby31?" do + expect(version.ruby31?).to be true + end + + it "returns true for ruby32?" do + expect(version.ruby32?).to be true + end + + it "returns false for ruby32only?" do + expect(version.ruby32only?).to be false + end + + it "returns true for ruby33?" do + expect(version.ruby33?).to be true + end + + it "returns true for ruby34?" do + expect(version.ruby34?).to be true + end + + it "returns '3.4.0' for api_version" do + expect(version.api_version).to eq("3.4.0") + end + + it "returns '340' for lib_version" do + expect(version.lib_version).to eq("340") + end + end + context "with version 2.7.0" do unless RUBY_PLATFORM =~ /msys|mingw|cygwin/ @@ -177,6 +225,10 @@ expect(version.ruby33?).to be false end + it "returns false for ruby34?" do + expect(version.ruby34?).to be false + end + it "returns '2.7.0' for api_version" do expect(version.api_version).to eq("2.7.0") end diff --git a/tests/test-20/Gemfile b/tests/test-20/Gemfile index d4b69812..92643650 100644 --- a/tests/test-20/Gemfile +++ b/tests/test-20/Gemfile @@ -1,5 +1,5 @@ -# frozen_string_literal: true - -source "https://rubygems.org" - -gem "net-http" +# frozen_string_literal: true + +source "https://rubygems.org" + +gem "net-http"