diff --git a/doc/contributor/workarounds.md b/doc/contributor/workarounds.md index 24cfbb73dfc9..2a2e55085be4 100644 --- a/doc/contributor/workarounds.md +++ b/doc/contributor/workarounds.md @@ -1,17 +1,23 @@ -# Patching system - -The patching system is loaded in `post-boot.rb` and is able to patch stdlib and -installed gems. The code implementing the patching itself can be found in -`lib/truffle/truffle/patching.rb`. It can be disabled by passing the -`-Xpatching=false` option. - -The patching system works for gems as follows. When a gem *g* is activated, -there is a directory `g` in `lib/patches`, and the directory is listed in -`lib/truffle/truffle/patching.rb`, the directory is inserted into `$LOAD_PATH` -before the original load-paths of the *g* gem. As a result the patching files -are loaded first before the original files in the gem. The patching file is -responsible for loading the original file (if desirable), which can be done with -`Truffle::Patching.require_original __FILE__`. +# Ruby files patching system + +When `require 'some/patch'` (has to be a relative path) is called +and an original file exists on `$LOAD_PATH` +and there is a patch file `some/patch.rb` in a special directory `lib/patches` +then the TruffleRuby will first evaluate (not require) the file +`lib/patches/some/patch.rb` instead. + +To require the original file call `require 'some/patch'` in the +`lib/patches/some/patch.rb` file. It will require the original ruby +file found on `$LOAD_PATH` ( stdlib file or a gem file). + +When requiring the original file is not desired just omit the +`require 'some/patch'` in the patch file. The patch file will be evaluated +only once. + +The evaluated patch files are not visible in `$LOAD_PATH` nor `$LOADED_FEATURES`. + +Patching can be disabled by passing the `-Xpatching=false` option. +`-X.log=CONFIG` can be used to see paths of loaded patch files. # C file preprocessing diff --git a/lib/patches/bundler/bundler.rb b/lib/patches/bundler.rb similarity index 73% rename from lib/patches/bundler/bundler.rb rename to lib/patches/bundler.rb index f4e1b5adc690..8eb251a7ce6f 100644 --- a/lib/patches/bundler/bundler.rb +++ b/lib/patches/bundler.rb @@ -1,4 +1,4 @@ -Truffle::Patching.require_original __FILE__ +require 'bundler' unless Bundler::VERSION =~ /^1\.16\./ raise "unsupported bundler version #{Bundler::VERSION}, please use 1.16.x" diff --git a/lib/patches/bundler/bundler/runtime.rb b/lib/patches/bundler/bundler/runtime.rb deleted file mode 100644 index 3faae4ec20de..000000000000 --- a/lib/patches/bundler/bundler/runtime.rb +++ /dev/null @@ -1,19 +0,0 @@ -Truffle::Patching.require_original __FILE__ - -module Bundler - class Runtime - - alias_method :original_setup, :setup - - def setup(*groups) - original_setup(*groups) - - groups.map!(&:to_sym) - specs = groups.any? ? @definition.specs_for(groups) : requested_specs - - specs.each { |spec| Truffle::Patching.insert_patching_dir(spec.name, *spec.load_paths) } - - self - end - end -end diff --git a/lib/patches/bundler/bundler/cli/exec.rb b/lib/patches/bundler/cli/exec.rb similarity index 97% rename from lib/patches/bundler/bundler/cli/exec.rb rename to lib/patches/bundler/cli/exec.rb index db2a46c1153b..5bf202c1196a 100644 --- a/lib/patches/bundler/bundler/cli/exec.rb +++ b/lib/patches/bundler/cli/exec.rb @@ -22,7 +22,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'bundler/cli/exec' module Bundler class CLI::Exec diff --git a/lib/patches/bundler/bundler/current_ruby.rb b/lib/patches/bundler/current_ruby.rb similarity index 97% rename from lib/patches/bundler/bundler/current_ruby.rb rename to lib/patches/bundler/current_ruby.rb index 8f4e4ed1fa3c..9a67d7453b51 100644 --- a/lib/patches/bundler/bundler/current_ruby.rb +++ b/lib/patches/bundler/current_ruby.rb @@ -22,7 +22,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'bundler/current_ruby' module Bundler class CurrentRuby diff --git a/lib/patches/bundler/bundler/dependency.rb b/lib/patches/bundler/dependency.rb similarity index 97% rename from lib/patches/bundler/bundler/dependency.rb rename to lib/patches/bundler/dependency.rb index 4a14c0163c8d..6fca63b076ea 100644 --- a/lib/patches/bundler/bundler/dependency.rb +++ b/lib/patches/bundler/dependency.rb @@ -22,7 +22,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'bundler/dependency' class Bundler::Dependency diff --git a/lib/patches/bundler/bundler/deprecate.rb b/lib/patches/bundler/deprecate.rb similarity index 100% rename from lib/patches/bundler/bundler/deprecate.rb rename to lib/patches/bundler/deprecate.rb diff --git a/lib/patches/bundler/bundler/ruby_version.rb b/lib/patches/bundler/ruby_version.rb similarity index 98% rename from lib/patches/bundler/bundler/ruby_version.rb rename to lib/patches/bundler/ruby_version.rb index 1094b61f3c2a..eae4c98d7b60 100644 --- a/lib/patches/bundler/bundler/ruby_version.rb +++ b/lib/patches/bundler/ruby_version.rb @@ -22,7 +22,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'bundler/ruby_version' module Bundler class RubyVersion diff --git a/lib/patches/bundler/bundler/source/rubygems.rb b/lib/patches/bundler/source/rubygems.rb similarity index 98% rename from lib/patches/bundler/bundler/source/rubygems.rb rename to lib/patches/bundler/source/rubygems.rb index 6787a50c6741..781eb7da2937 100644 --- a/lib/patches/bundler/bundler/source/rubygems.rb +++ b/lib/patches/bundler/source/rubygems.rb @@ -24,7 +24,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'bundler/source/rubygems' # TruffleRuby: do not skips gems with extensions not built diff --git a/lib/patches/launchy/launchy/detect/ruby_engine.rb b/lib/patches/launchy/detect/ruby_engine.rb similarity index 95% rename from lib/patches/launchy/launchy/detect/ruby_engine.rb rename to lib/patches/launchy/detect/ruby_engine.rb index 21f30153223c..29c1b74d6ce2 100644 --- a/lib/patches/launchy/launchy/detect/ruby_engine.rb +++ b/lib/patches/launchy/detect/ruby_engine.rb @@ -12,7 +12,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'launchy/detect/ruby_engine' module Launchy::Detect class RubyEngine diff --git a/lib/patches/launchy/launchy/detect/runner.rb b/lib/patches/launchy/detect/runner.rb similarity index 96% rename from lib/patches/launchy/launchy/detect/runner.rb rename to lib/patches/launchy/detect/runner.rb index 21fcc816c290..32c8a58b3f07 100644 --- a/lib/patches/launchy/launchy/detect/runner.rb +++ b/lib/patches/launchy/detect/runner.rb @@ -12,7 +12,7 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'launchy/detect/runner' module Launchy::Detect class Runner diff --git a/lib/patches/rake-compiler/rake/baseextensiontask.rb b/lib/patches/rake/baseextensiontask.rb similarity index 89% rename from lib/patches/rake-compiler/rake/baseextensiontask.rb rename to lib/patches/rake/baseextensiontask.rb index 6670cd89db2e..e646e6762ae7 100644 --- a/lib/patches/rake-compiler/rake/baseextensiontask.rb +++ b/lib/patches/rake/baseextensiontask.rb @@ -1,4 +1,4 @@ -Truffle::Patching.require_original __FILE__ +require 'rake/baseextensiontask' module Truffle::Patching::RakeBaseExtensionTaskOverrideBinaryExt def binary(platform = nil) diff --git a/lib/patches/rake-compiler/rake/extensiontask.rb b/lib/patches/rake/extensiontask.rb similarity index 96% rename from lib/patches/rake-compiler/rake/extensiontask.rb rename to lib/patches/rake/extensiontask.rb index 6fb287fcc418..cba13448d2ae 100644 --- a/lib/patches/rake-compiler/rake/extensiontask.rb +++ b/lib/patches/rake/extensiontask.rb @@ -1,5 +1,5 @@ # Copyright (c) 2008-2011 Luis Lavena. -# +# # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including @@ -7,10 +7,10 @@ # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: -# +# # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND @@ -19,7 +19,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'rake/extensiontask' module Truffle::Patching::RakeExtensionTaskOverridePatterns def init(name = nil, gem_spec = nil) diff --git a/lib/patches/rspec-core/rspec/core/source.rb b/lib/patches/rspec/core/source.rb similarity index 96% rename from lib/patches/rspec-core/rspec/core/source.rb rename to lib/patches/rspec/core/source.rb index 93adb9f76704..108c8f43712f 100644 --- a/lib/patches/rspec-core/rspec/core/source.rb +++ b/lib/patches/rspec/core/source.rb @@ -9,10 +9,10 @@ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -21,7 +21,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'rspec/core/source' module RSpec module Core diff --git a/lib/patches/rspec-support/rspec/support.rb b/lib/patches/rspec/support.rb similarity index 97% rename from lib/patches/rspec-support/rspec/support.rb rename to lib/patches/rspec/support.rb index 298ca16af136..0ee838c225b6 100644 --- a/lib/patches/rspec-support/rspec/support.rb +++ b/lib/patches/rspec/support.rb @@ -7,10 +7,10 @@ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: -# +# # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. -# +# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -19,7 +19,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -Truffle::Patching.require_original __FILE__ +require 'rspec/support' module RSpec module Support diff --git a/lib/patches/stdlib/rubygems.rb b/lib/patches/rubygems.rb similarity index 89% rename from lib/patches/stdlib/rubygems.rb rename to lib/patches/rubygems.rb index 8c94333a0110..f8ecde0a09df 100644 --- a/lib/patches/stdlib/rubygems.rb +++ b/lib/patches/rubygems.rb @@ -1,5 +1,4 @@ -Truffle::Patching.require_original __FILE__ -Truffle::Patching.install_gem_activation_hook +require 'rubygems' # Because did_you_mean was required directly without RubyGems if Truffle::Boot.get_option 'did_you_mean' diff --git a/lib/patches/stdlib/rubygems/basic_specification.rb b/lib/patches/rubygems/basic_specification.rb similarity index 93% rename from lib/patches/stdlib/rubygems/basic_specification.rb rename to lib/patches/rubygems/basic_specification.rb index 81d635117a30..a7487d44e213 100644 --- a/lib/patches/stdlib/rubygems/basic_specification.rb +++ b/lib/patches/rubygems/basic_specification.rb @@ -1,4 +1,4 @@ -Truffle::Patching.require_original __FILE__ +require 'rubygems/basic_specification' if ENV['TRUFFLERUBY_CEXT_ENABLED'] == "false" module Truffle::Patching::NoWarnIfBuildingCextDisabled diff --git a/lib/patches/stdlib/rubygems/ext.rb b/lib/patches/rubygems/ext.rb similarity index 90% rename from lib/patches/stdlib/rubygems/ext.rb rename to lib/patches/rubygems/ext.rb index 7cc76f35c9df..0073960e4253 100644 --- a/lib/patches/stdlib/rubygems/ext.rb +++ b/lib/patches/rubygems/ext.rb @@ -1,4 +1,4 @@ -Truffle::Patching.require_original __FILE__ +require 'rubygems/ext' # TruffleRuby: build C extensions conditionally diff --git a/lib/patches/stdlib/rubygems/remote_fetcher.rb b/lib/patches/rubygems/remote_fetcher.rb similarity index 94% rename from lib/patches/stdlib/rubygems/remote_fetcher.rb rename to lib/patches/rubygems/remote_fetcher.rb index 775af4267698..28cecb93b358 100644 --- a/lib/patches/stdlib/rubygems/remote_fetcher.rb +++ b/lib/patches/rubygems/remote_fetcher.rb @@ -1,5 +1,5 @@ -Truffle::Patching.require_original __FILE__ +require 'rubygems/remote_fetcher' # Hardcode DNS Resolution to rubygems.org for gem install diff --git a/lib/patches/stdlib/rubygems/request_set/gem_dependency_api.rb b/lib/patches/rubygems/request_set/gem_dependency_api.rb similarity index 87% rename from lib/patches/stdlib/rubygems/request_set/gem_dependency_api.rb rename to lib/patches/rubygems/request_set/gem_dependency_api.rb index 50c763574769..1617315afd0b 100644 --- a/lib/patches/stdlib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/patches/rubygems/request_set/gem_dependency_api.rb @@ -1,4 +1,4 @@ -Truffle::Patching.require_original __FILE__ +require 'rubygems/request_set/gem_dependency_api' class Gem::RequestSet::GemDependencyAPI diff --git a/lib/patches/thread_safe/thread_safe.rb b/lib/patches/thread_safe.rb similarity index 99% rename from lib/patches/thread_safe/thread_safe.rb rename to lib/patches/thread_safe.rb index 7a09ece1abac..c630e0610471 100644 --- a/lib/patches/thread_safe/thread_safe.rb +++ b/lib/patches/thread_safe.rb @@ -1,4 +1,4 @@ -Truffle::Patching.require_original __FILE__ +require 'thread_safe' # Copyright (c) 2012 thedarkone # @@ -177,4 +177,4 @@ def #{method}(*args) RUBY_EVAL end end -end \ No newline at end of file +end diff --git a/lib/truffle/truffle/patching.rb b/lib/truffle/truffle/patching.rb deleted file mode 100644 index 2ee2845ba315..000000000000 --- a/lib/truffle/truffle/patching.rb +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) 2017 Oracle and/or its affiliates. All rights reserved. This -# code is released under a tri EPL/GPL/LGPL license. You can use it, -# redistribute it and/or modify it under the terms of the: -# -# Eclipse Public License version 1.0, or -# GNU General Public License version 2, or -# GNU Lesser General Public License version 2.1. - -module Truffle::Patching - extend self - - ORIGINALS = {} - - DIR = "#{Truffle::Boot.ruby_home}/lib/patches" - - PATCHES = { - 'stdlib' => "#{DIR}/stdlib", - 'bundler' => "#{DIR}/bundler", - 'launchy' => "#{DIR}/launchy", - 'rake-compiler' => "#{DIR}/rake-compiler", - 'rspec-core' => "#{DIR}/rspec-core", - 'rspec-support' => "#{DIR}/rspec-support", - 'thread_safe' => "#{DIR}/thread_safe", - } - - def self.paths_depending_on_home - raise 'patching: should only have the stdlib path' unless ORIGINALS.size == 1 - [ - DIR, - *PATCHES.values, - *ORIGINALS.values[0] - ] - end - - def log(name, path) - Truffle::System.log :CONFIG, - "patching '#{name}' by inserting directory '#{path}' in LOAD_PATH before the original paths" - end - - def insert_patching_dir(name, *paths) - path = PATCHES[name] - if path - insertion_point = paths. - map { |gem_require_path| $LOAD_PATH.index gem_require_path }. - min - raise "Could not find paths #{paths} in $LOAD_PATH (#{$LOAD_PATH})" unless insertion_point - ORIGINALS[name] = paths - Truffle::Patching.log(name, path) - $LOAD_PATH.insert insertion_point, path if $LOAD_PATH[insertion_point-1] != path - true - else - false - end - end - - def require_original(file) - relative_path = file[DIR.length+1..-1] - slash = relative_path.index '/' - name = relative_path[0...slash] - require_path = relative_path[slash+1..-1] - - begin - original = ORIGINALS.fetch(name).find do |original_path| - path = "#{original_path}/#{require_path}" - break path if File.file?(path) - end - rescue KeyError - # Somehow we've encountered a patch on the $LOAD_PATH for a gem that hasn't been registered. - # RSpec does this, for instance, by taking the $LOAD_PATH and passing it as an "-I" argument to a Ruby - # subprocess. Since registered patches should always directly precede the library being patched on - # the $LOAD_PATH, assume this invariant is held and construct the filename to load from the next - # entry on the $LOAD_PATH. If this invariant isn't held, `require` will simply fail. - pos = $LOAD_PATH.find_index("#{DIR}/#{name}") - original_gem_path = $LOAD_PATH[pos + 1] - ORIGINALS[name] = [original_gem_path] - - retry - end - - Kernel.require original - end - - def install_gem_activation_hook - Gem::Specification.class_eval do - alias_method :activate_without_truffle_patching, :activate - - def activate - result = activate_without_truffle_patching - Truffle::Patching.insert_patching_dir name, *full_require_paths - result - end - end - end - - # Allows specifying patching outside the context of RubyGems by setting an environment variable value. - # E.g., setting TRUFFLERUBY_CUSTOM_PATCH="launchy:$PWD/lib,$PWD/spec" will allow patching of files in the 'lib' and 'spec' - # of a local checkout of the 'launchy' gem. - def install_local_patches - custom_patch = Truffle.invoke_primitive :java_get_env, 'TRUFFLERUBY_CUSTOM_PATCH' # Use the primitive here rather than ENV so it works if native is disabled. - - if custom_patch - name, paths = custom_patch.split(':') - begin - Truffle::Patching.insert_patching_dir name, *paths.split(',') - rescue # rubocop:disable Lint/HandleExceptions - # We don't want to fail patching just because an environment variable is visible to a process. This might be - # the case when running with rake where the rake process won't need the patches but the tests it spawns will. - end - end - end - -end diff --git a/src/main/java/org/truffleruby/RubyContext.java b/src/main/java/org/truffleruby/RubyContext.java index 31242ea80ba8..af2c9872c406 100644 --- a/src/main/java/org/truffleruby/RubyContext.java +++ b/src/main/java/org/truffleruby/RubyContext.java @@ -818,7 +818,8 @@ private String searchRubyHome(Options options) throws IOException { private boolean isRubyHome(File path) { return Paths.get(path.toString(), "lib", "truffle").toFile().isDirectory() && - Paths.get(path.toString(), "lib", "ruby").toFile().isDirectory(); + Paths.get(path.toString(), "lib", "ruby").toFile().isDirectory() && + Paths.get(path.toString(), "lib", "patches").toFile().isDirectory(); } public TruffleNFIPlatform getTruffleNFI() { diff --git a/src/main/java/org/truffleruby/core/CoreLibrary.java b/src/main/java/org/truffleruby/core/CoreLibrary.java index 45d9386dde25..fe596a3b6132 100644 --- a/src/main/java/org/truffleruby/core/CoreLibrary.java +++ b/src/main/java/org/truffleruby/core/CoreLibrary.java @@ -9,6 +9,20 @@ */ package org.truffleruby.core; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; @@ -61,12 +75,6 @@ import org.truffleruby.shared.BuildInformationImpl; import org.truffleruby.shared.TruffleRuby; -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; - public class CoreLibrary { private static final String ERRNO_CONFIG_PREFIX = NativeConfiguration.PREFIX + "errno."; @@ -213,6 +221,8 @@ public class CoreLibrary { @CompilationFinal private GlobalVariableStorage verboseStorage; @CompilationFinal private GlobalVariableStorage stderrStorage; + private final ConcurrentMap patchFiles; + private final String coreLoadPath; @TruffleBoundary @@ -567,6 +577,35 @@ public CoreLibrary(RubyContext context) { // No need for new version since it's null before which is not cached assert Layouts.CLASS.getSuperclass(basicObjectClass) == null; Layouts.CLASS.setSuperclass(basicObjectClass, nil); + + patchFiles = initializePatching(context); + } + + private ConcurrentMap initializePatching(RubyContext context) { + defineModule(truffleModule, "Patching"); + final ConcurrentMap patchFiles = new ConcurrentHashMap<>(); + + if (context.getOptions().PATCHING) { + try { + final Path patchesDirectory = Paths.get(context.getRubyHome(), "lib", "patches"); + Files.walkFileTree( + patchesDirectory, + new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { + String relativePath = patchesDirectory.relativize(path).toString(); + if (relativePath.endsWith(".rb")) { + patchFiles.put(relativePath.substring(0, relativePath.length() - 3), false); + } + return FileVisitResult.CONTINUE; + } + } + ); + } catch (IOException ignored) { + // bad ruby home + } + } + return patchFiles; } private static DynamicObjectFactory alwaysFrozen(DynamicObjectFactory factory) { @@ -1135,6 +1174,10 @@ public DynamicObjectFactory getThreadBacktraceLocationFactory() { return threadBacktraceLocationFactory; } + public ConcurrentMap getPatchFiles() { + return patchFiles; + } + public boolean isInitializing() { return state == State.INITIALIZING; } diff --git a/src/main/java/org/truffleruby/language/loader/RequireNode.java b/src/main/java/org/truffleruby/language/loader/RequireNode.java index e112f712daa9..82d5e313b517 100644 --- a/src/main/java/org/truffleruby/language/loader/RequireNode.java +++ b/src/main/java/org/truffleruby/language/loader/RequireNode.java @@ -9,6 +9,15 @@ */ package org.truffleruby.language.loader; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Locale; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; + import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.dsl.Cached; @@ -27,7 +36,6 @@ import com.oracle.truffle.api.profiles.ConditionProfile; import com.oracle.truffle.api.source.Source; import com.oracle.truffle.api.source.SourceSection; - import org.jcodings.specific.UTF8Encoding; import org.truffleruby.RubyLanguage; import org.truffleruby.core.rope.CodeRange; @@ -42,12 +50,6 @@ import org.truffleruby.parser.ParserContext; import org.truffleruby.parser.RubySource; -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.locks.ReentrantLock; - @NodeChild("feature") public abstract class RequireNode extends RubyNode { @@ -92,13 +94,23 @@ private boolean doRequire(String feature, String expandedPathRaw, DynamicObject final FeatureLoader featureLoader = getContext().getFeatureLoader(); final ReentrantLockFreeingMap fileLocks = featureLoader.getFileLocks(); final String expandedPath = expandedPathRaw.intern(); + final ConcurrentMap patchFiles = getContext().getCoreLibrary().getPatchFiles(); + Boolean patchLoaded = patchFiles.get(feature); + final boolean isPatched = patchLoaded != null; while (true) { final ReentrantLock lock = fileLocks.get(expandedPath); if (lock.isHeldByCurrentThread()) { - warnCircularRequire(expandedPath); - return false; + if (isPatched && !patchLoaded) { + // it is loading the original of the patched file for the first time + // it has to allow this one case of circular require where the first require was the patch + patchLoaded = true; + patchFiles.put(feature, true); + } else { + warnCircularRequire(expandedPath); + return false; + } } if (!fileLocks.lock(this, getContext().getThreadManager(), expandedPath, lock)) { @@ -106,39 +118,29 @@ private boolean doRequire(String feature, String expandedPathRaw, DynamicObject } try { - if (isFeatureLoaded(pathString)) { - return false; + if (isPatched && !patchLoaded) { + Path expandedPatchPath = + Paths.get(getContext().getRubyHome(), "lib", "patches", feature + ".rb"); + RubyLanguage.LOGGER.config("patch file used: " + expandedPatchPath); + final boolean loaded = parseAndCall(feature, expandedPatchPath.toString()); + assert loaded; + + final boolean originalLoaded = patchFiles.get(feature); + if (!originalLoaded) { + addToLoadedFeatures(pathString); + // if original is not loaded make sure we set the patch to loaded + patchFiles.put(feature, true); + } + + return true; } - final RubySource source; - try { - source = getContext().getSourceLoader().load(expandedPath); - } catch (IOException e) { + if (isFeatureLoaded(pathString)) { return false; } - final String mimeType = getSourceMimeType(source.getSource()); - - if (RubyLanguage.MIME_TYPE.equals(mimeType)) { - final RubyRootNode rootNode = getContext().getCodeLoader().parse( - source, - ParserContext.TOP_LEVEL, - null, - true, - this); - - final CodeLoader.DeferredCall deferredCall = getContext().getCodeLoader().prepareExecute( - ParserContext.TOP_LEVEL, - DeclarationContext.topLevel(getContext()), - rootNode, - null, - coreLibrary().getMainObject()); - - deferredCall.call(callNode); - } else if (RubyLanguage.CEXT_MIME_TYPE.equals(mimeType)) { - requireCExtension(feature, expandedPath); - } else { - throw new RaiseException(getContext(), mimeTypeNotFound(expandedPath, mimeType)); + if (!parseAndCall(feature, expandedPath)) { + return false; } addToLoadedFeatures(pathString); @@ -150,6 +152,40 @@ private boolean doRequire(String feature, String expandedPathRaw, DynamicObject } } + private boolean parseAndCall(String feature, String expandedPath) { + final RubySource source; + try { + source = getContext().getSourceLoader().load(expandedPath); + } catch (IOException e) { + return false; + } + + final String mimeType = getSourceMimeType(source.getSource()); + + if (RubyLanguage.MIME_TYPE.equals(mimeType)) { + final RubyRootNode rootNode = getContext().getCodeLoader().parse( + source, + ParserContext.TOP_LEVEL, + null, + true, + this); + + final CodeLoader.DeferredCall deferredCall = getContext().getCodeLoader().prepareExecute( + ParserContext.TOP_LEVEL, + DeclarationContext.topLevel(getContext()), + rootNode, + null, + coreLibrary().getMainObject()); + + deferredCall.call(callNode); + } else if (RubyLanguage.CEXT_MIME_TYPE.equals(mimeType)) { + requireCExtension(feature, expandedPath); + } else { + throw new RaiseException(getContext(), mimeTypeNotFound(expandedPath, mimeType)); + } + return true; + } + @TruffleBoundary private DynamicObject mimeTypeNotFound(String expandedPath, String mimeType) { if (expandedPath.toLowerCase(Locale.ENGLISH).endsWith(".su")) { diff --git a/src/main/ruby/core/truffle/boot.rb b/src/main/ruby/core/truffle/boot.rb index 123c459b3ca1..05f102ee5c23 100644 --- a/src/main/ruby/core/truffle/boot.rb +++ b/src/main/ruby/core/truffle/boot.rb @@ -12,8 +12,6 @@ module Truffle::Boot - PATCHING = Truffle::Boot.get_option 'patching' - def self.check_syntax(source_or_file) inner_check_syntax source_or_file STDOUT.puts 'Syntax OK' diff --git a/src/main/ruby/post-boot/post-boot.rb b/src/main/ruby/post-boot/post-boot.rb index dd9061f2c8b0..8cbda72c70c7 100644 --- a/src/main/ruby/post-boot/post-boot.rb +++ b/src/main/ruby/post-boot/post-boot.rb @@ -19,13 +19,6 @@ require 'rational' require 'complex' require 'unicode_normalize' - if Truffle::Boot.get_option('patching') - Truffle::Boot.print_time_metric :'before-patching' - require 'truffle/patching' - Truffle::Patching.insert_patching_dir 'stdlib', "#{Truffle::Boot.ruby_home}/lib/mri" - Truffle::Boot.delay { Truffle::Patching.install_local_patches } - Truffle::Boot.print_time_metric :'after-patching' - end rescue LoadError => e Truffle::Debug.log_warning "#{File.basename(__FILE__)}:#{__LINE__} #{e.message}" end @@ -76,10 +69,9 @@ if old_home # We need to fix all paths which capture the image build-time home to point # to the runtime home. - patching_paths = Truffle::Patching.paths_depending_on_home Truffle::Boot.delay do new_home = Truffle::Boot.ruby_home - [$LOAD_PATH, $LOADED_FEATURES, patching_paths].each do |array| + [$LOAD_PATH, $LOADED_FEATURES].each do |array| array.each do |path| if path.start_with?(old_home) path.replace(new_home + path[old_home.size..-1])