File: bootsnap.rb

package info (click to toggle)
ruby-bootsnap 1.22.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 520 kB
  • sloc: ruby: 3,637; ansic: 844; sh: 14; makefile: 9
file content (169 lines) | stat: -rw-r--r-- 5,091 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# frozen_string_literal: true

require_relative "bootsnap/version"
require_relative "bootsnap/bundler"

module Bootsnap
  InvalidConfiguration = Class.new(StandardError)

  class << self
    attr_reader :cache_dir, :logger

    def log_stats!
      stats = {hit: 0, revalidated: 0, miss: 0, stale: 0}
      self.instrumentation = ->(event, _path) { stats[event] += 1 }
      Kernel.at_exit do
        stats.each do |event, count|
          $stderr.puts "bootsnap #{event}: #{count}"
        end
      end
    end

    def log!
      self.logger = $stderr.method(:puts)
    end

    def logger=(logger)
      @logger = logger
      self.instrumentation = if logger.respond_to?(:debug)
        ->(event, path) { @logger.debug("[Bootsnap] #{event} #{path}") unless event == :hit }
      else
        ->(event, path) { @logger.call("[Bootsnap] #{event} #{path}") unless event == :hit }
      end
    end

    def instrumentation=(callback)
      @instrumentation = callback
      if respond_to?(:instrumentation_enabled=, true)
        self.instrumentation_enabled = !!callback
      end
    end

    def _instrument(event, path)
      @instrumentation.call(event, path)
    end

    def setup(
      cache_dir:,
      development_mode: true,
      load_path_cache: true,
      ignore_directories: nil,
      readonly: false,
      revalidation: false,
      compile_cache_iseq: true,
      compile_cache_yaml: true,
      compile_cache_json: (compile_cache_json_unset = true)
    )
      unless compile_cache_json_unset
        warn("Bootsnap.setup `compile_cache_json` argument is deprecated and has no effect")
      end

      @cache_dir = "#{cache_dir}/bootsnap"

      if load_path_cache
        Bootsnap::LoadPathCache.setup(
          cache_path: "#{@cache_dir}/load-path-cache",
          development_mode: development_mode,
          ignore_directories: ignore_directories,
          readonly: readonly,
        )
      end

      Bootsnap::CompileCache.setup(
        cache_dir: "#{@cache_dir}/compile-cache",
        iseq: compile_cache_iseq,
        yaml: compile_cache_yaml,
        readonly: readonly,
        revalidation: revalidation,
      )
    end

    def unload_cache!
      LoadPathCache.unload!
    end

    def default_setup
      env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["ENV"]
      development_mode = ["", nil, "development"].include?(env)

      if enabled?("BOOTSNAP")
        cache_dir = ENV["BOOTSNAP_CACHE_DIR"]
        unless cache_dir
          config_dir_frame = caller.detect do |line|
            line.include?("/config/")
          end

          unless config_dir_frame
            $stderr.puts("[bootsnap/setup] couldn't infer cache directory! Either:")
            $stderr.puts("[bootsnap/setup]   1. require bootsnap/setup from your application's config directory; or")
            $stderr.puts("[bootsnap/setup]   2. Define the environment variable BOOTSNAP_CACHE_DIR")

            raise("couldn't infer bootsnap cache directory")
          end

          path = config_dir_frame.split(/:\d+:/).first
          path = File.dirname(path) until File.basename(path) == "config"
          app_root = File.dirname(path)

          cache_dir = File.join(app_root, "tmp", "cache")
        end

        ignore_directories = if ENV.key?("BOOTSNAP_IGNORE_DIRECTORIES")
          ENV["BOOTSNAP_IGNORE_DIRECTORIES"].split(",")
        end

        setup(
          cache_dir: cache_dir,
          development_mode: development_mode,
          load_path_cache: enabled?("BOOTSNAP_LOAD_PATH_CACHE"),
          compile_cache_iseq: enabled?("BOOTSNAP_COMPILE_CACHE"),
          compile_cache_yaml: enabled?("BOOTSNAP_COMPILE_CACHE"),
          readonly: bool_env("BOOTSNAP_READONLY"),
          revalidation: bool_env("BOOTSNAP_REVALIDATE"),
          ignore_directories: ignore_directories,
        )

        if ENV["BOOTSNAP_LOG"]
          log!
        elsif ENV["BOOTSNAP_STATS"]
          log_stats!
        end
      end
    end

    if RbConfig::CONFIG["host_os"] =~ /mswin|mingw|cygwin/
      def absolute_path?(path)
        path[1] == ":"
      end
    else
      def absolute_path?(path)
        path.start_with?("/")
      end
    end

    # This is a semi-accurate ruby implementation of the native `rb_get_path(VALUE)` function.
    # The native version is very intricate and may behave differently on windows etc.
    # But we only use it for non-MRI platform.
    def rb_get_path(fname)
      path_path = fname.respond_to?(:to_path) ? fname.to_path : fname
      String.try_convert(path_path) || raise(TypeError, "no implicit conversion of #{path_path.class} into String")
    end

    # Allow the C extension to redefine `rb_get_path` without warning.
    alias_method :rb_get_path, :rb_get_path # rubocop:disable Lint/DuplicateMethods

    private

    def enabled?(key)
      !ENV["DISABLE_#{key}"]
    end

    def bool_env(key, default: false)
      value = ENV.fetch(key) { default }
      !["0", "false", false].include?(value)
    end
  end
end

require_relative "bootsnap/compile_cache"
require_relative "bootsnap/load_path_cache"