bootsnap 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,6 @@
1
1
  #ifndef BOOTSNAP_H
2
2
  #define BOOTSNAP_H 1
3
3
 
4
- #include <stdint.h>
5
- #include <sys/types.h>
6
- #include "ruby.h"
7
-
8
- uint32_t crc32(const char *bytes, size_t size);
4
+ /* doesn't expose anything */
9
5
 
10
6
  #endif /* BOOTSNAP_H */
@@ -1,7 +1,17 @@
1
1
  require "mkmf"
2
- $CFLAGS << ' -O3 -msse4.2 -std=c99'
3
- have_header('x86intrin.h')
4
- $CFLAGS << ' -Wall -Wextra -Wpedantic -Werror'
5
- $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
6
- $CFLAGS << ' -Wno-keyword-macro' # hiding return
2
+ $CFLAGS << ' -O3 '
3
+ $CFLAGS << ' -std=c99'
4
+
5
+ # ruby.h has some -Wpedantic fails in some cases
6
+ # (e.g. https://github.com/Shopify/bootsnap/issues/15)
7
+ unless ['0', '', nil].include?(ENV['BOOTSNAP_PEDANTIC'])
8
+ $CFLAGS << ' -Wall'
9
+ $CFLAGS << ' -Werror'
10
+ $CFLAGS << ' -Wextra'
11
+ $CFLAGS << ' -Wpedantic'
12
+
13
+ $CFLAGS << ' -Wno-unused-parameter' # VALUE self has to be there but we don't care what it is.
14
+ $CFLAGS << ' -Wno-keyword-macro' # hiding return
15
+ end
16
+
7
17
  create_makefile("bootsnap/bootsnap")
@@ -4,6 +4,10 @@ require 'zlib'
4
4
  module Bootsnap
5
5
  module CompileCache
6
6
  module ISeq
7
+ class << self
8
+ attr_accessor :cache_dir
9
+ end
10
+
7
11
  def self.input_to_storage(_, path)
8
12
  RubyVM::InstructionSequence.compile_file(path).to_binary
9
13
  rescue SyntaxError
@@ -28,6 +32,7 @@ module Bootsnap
28
32
  module InstructionSequenceMixin
29
33
  def load_iseq(path)
30
34
  Bootsnap::CompileCache::Native.fetch(
35
+ Bootsnap::CompileCache::ISeq.cache_dir,
31
36
  path.to_s,
32
37
  Bootsnap::CompileCache::ISeq
33
38
  )
@@ -62,7 +67,8 @@ module Bootsnap
62
67
  Bootsnap::CompileCache::Native.compile_option_crc32 = crc
63
68
  end
64
69
 
65
- def self.install!
70
+ def self.install!(cache_dir)
71
+ Bootsnap::CompileCache::ISeq.cache_dir = cache_dir
66
72
  Bootsnap::CompileCache::ISeq.compile_option_updated
67
73
  class << RubyVM::InstructionSequence
68
74
  prepend InstructionSequenceMixin
@@ -30,7 +30,7 @@ module Bootsnap
30
30
  ::YAML.load(data)
31
31
  end
32
32
 
33
- def self.install!
33
+ def self.install!(cache_dir)
34
34
  require 'yaml'
35
35
  require 'msgpack'
36
36
 
@@ -44,6 +44,7 @@ module Bootsnap
44
44
  klass = class << ::YAML; self; end
45
45
  klass.send(:define_method, :load_file) do |path|
46
46
  Bootsnap::CompileCache::Native.fetch(
47
+ cache_dir,
47
48
  path.to_s,
48
49
  Bootsnap::CompileCache::YAML
49
50
  )
@@ -3,13 +3,13 @@ require_relative 'compile_cache/yaml'
3
3
 
4
4
  module Bootsnap
5
5
  module CompileCache
6
- def self.setup(iseq:, yaml:)
6
+ def self.setup(cache_dir:, iseq:, yaml:)
7
7
  if iseq
8
- Bootsnap::CompileCache::ISeq.install!
8
+ Bootsnap::CompileCache::ISeq.install!(cache_dir)
9
9
  end
10
10
 
11
11
  if yaml
12
- Bootsnap::CompileCache::YAML.install!
12
+ Bootsnap::CompileCache::YAML.install!(cache_dir)
13
13
  end
14
14
  end
15
15
  end
@@ -22,6 +22,27 @@ module Bootsnap
22
22
  @mutex.synchronize { @dirs[dir] }
23
23
  end
24
24
 
25
+ # { 'enumerator' => nil, 'enumerator.so' => nil, ... }
26
+ BUILTIN_FEATURES = $LOADED_FEATURES.reduce({}) do |acc, feat|
27
+ # Builtin features are of the form 'enumerator.so'.
28
+ # All others include paths.
29
+ next acc unless feat.size < 20 && !feat.include?('/')
30
+
31
+ base = File.basename(feat, '.*') # enumerator.so -> enumerator
32
+ ext = File.extname(feat) # .so
33
+
34
+ acc[feat] = nil # enumerator.so
35
+ acc[base] = nil # enumerator
36
+
37
+ if [DOT_SO, *DL_EXTENSIONS].include?(ext)
38
+ DL_EXTENSIONS.each do |ext|
39
+ acc["#{base}#{ext}"] = nil # enumerator.bundle
40
+ end
41
+ end
42
+
43
+ acc
44
+ end.freeze
45
+
25
46
  # Try to resolve this feature to an absolute path without traversing the
26
47
  # loadpath.
27
48
  def find(feature)
@@ -33,22 +54,20 @@ module Bootsnap
33
54
  x = search_index(feature)
34
55
  return x if x
35
56
 
57
+ # Ruby has some built-in features that require lies about.
58
+ # For example, 'enumerator' is built in. If you require it, ruby
59
+ # returns false as if it were already loaded; however, there is no
60
+ # file to find on disk. We've pre-built a list of these, and we
61
+ # return false if any of them is loaded.
62
+ raise LoadPathCache::ReturnFalse if BUILTIN_FEATURES.key?(feature)
63
+
36
64
  # The feature wasn't found on our preliminary search through the index.
37
65
  # We resolve this differently depending on what the extension was.
38
66
  case File.extname(feature)
39
67
  # If the extension was one of the ones we explicitly cache (.rb and the
40
68
  # native dynamic extension, e.g. .bundle or .so), we know it was a
41
69
  # failure and there's nothing more we can do to find the file.
42
- when *CACHED_EXTENSIONS # .rb, .bundle or .so
43
- nil
44
- # If no extension was specified, it's the same situation, since we
45
- # try appending both cachable extensions in search_index. However,
46
- # there's a special-case for 'enumerator'. Before ruby 1.9, you had
47
- # to `require 'enumerator'` to use it. In 1.9+, it's pre-loaded, but
48
- # doesn't correspond to any entry on the filesystem. Ruby lies. So we
49
- # lie too, forcing our monkeypatch to return false like ruby would.
50
- when ""
51
- raise LoadPathCache::ReturnFalse if feature == 'enumerator' || feature == 'thread'
70
+ when '', *CACHED_EXTENSIONS # no extension, .rb, (.bundle or .so)
52
71
  nil
53
72
  # Ruby allows specifying native extensions as '.so' even when DLEXT
54
73
  # is '.bundle'. This is where we handle that case.
@@ -99,8 +118,9 @@ module Bootsnap
99
118
 
100
119
  def push_paths_locked(*paths)
101
120
  @store.transaction do
102
- paths.each do |path|
121
+ paths.map(&:to_s).each do |path|
103
122
  p = Path.new(path)
123
+ next if p.non_directory?
104
124
  entries, dirs = p.entries_and_dirs(@store)
105
125
  # push -> low precedence -> set only if unset
106
126
  dirs.each { |dir| @dirs[dir] ||= true }
@@ -111,8 +131,9 @@ module Bootsnap
111
131
 
112
132
  def unshift_paths_locked(*paths)
113
133
  @store.transaction do
114
- paths.reverse.each do |path|
134
+ paths.map(&:to_s).reverse.each do |path|
115
135
  p = Path.new(path)
136
+ next if p.non_directory?
116
137
  entries, dirs = p.entries_and_dirs(@store)
117
138
  # unshift -> high precedence -> unconditional set
118
139
  dirs.each { |dir| @dirs[dir] = true }
@@ -12,25 +12,25 @@ module Bootsnap
12
12
  sc = arr.singleton_class
13
13
  sc.send(:alias_method, :shovel_without_lpc, :<<)
14
14
  arr.define_singleton_method(:<<) do |entry|
15
- observer.push_paths(self, entry)
15
+ observer.push_paths(self, entry.to_s)
16
16
  shovel_without_lpc(entry)
17
17
  end
18
18
 
19
19
  sc.send(:alias_method, :push_without_lpc, :push)
20
20
  arr.define_singleton_method(:push) do |*entries|
21
- observer.push_paths(self, *entries)
21
+ observer.push_paths(self, *entries.map(&:to_s))
22
22
  push_without_lpc(*entries)
23
23
  end
24
24
 
25
25
  sc.send(:alias_method, :unshift_without_lpc, :unshift)
26
26
  arr.define_singleton_method(:unshift) do |*entries|
27
- observer.unshift_paths(self, *entries)
27
+ observer.unshift_paths(self, *entries.map(&:to_s))
28
28
  unshift_without_lpc(*entries)
29
29
  end
30
30
 
31
31
  sc.send(:alias_method, :concat_without_lpc, :concat)
32
32
  arr.define_singleton_method(:concat) do |entries|
33
- observer.push_paths(self, *entries)
33
+ observer.push_paths(self, *entries.map(&:to_s))
34
34
  concat_without_lpc(entries)
35
35
  end
36
36
 
@@ -4,7 +4,10 @@ module Bootsnap
4
4
  module ActiveSupport
5
5
  def self.with_bootsnap_fallback(error)
6
6
  yield
7
- rescue error
7
+ rescue error => e
8
+ # NoMethodError is a NameError, but we only want to handle actual
9
+ # NameError instances.
10
+ raise unless e.class == error
8
11
  without_bootsnap_cache { yield }
9
12
  end
10
13
 
@@ -50,7 +53,9 @@ module Bootsnap
50
53
  CoreExt::ActiveSupport.with_bootsnap_fallback(NameError) { super }
51
54
  end
52
55
 
53
- def depend_on(file_name, message = "No such file to load -- %s.rb")
56
+ # Signature has changed a few times over the years; easiest to not
57
+ # reiterate it with version polymorphism here...
58
+ def depend_on(*)
54
59
  CoreExt::ActiveSupport.with_bootsnap_fallback(LoadError) { super }
55
60
  end
56
61
  end
@@ -20,7 +20,14 @@ module Bootsnap
20
20
  attr_reader :path
21
21
 
22
22
  def initialize(path)
23
- @path = path
23
+ @path = path.to_s
24
+ end
25
+
26
+ # True if the path exists, but represents a non-directory object
27
+ def non_directory?
28
+ !File.stat(path).directory?
29
+ rescue Errno::ENOENT
30
+ false
24
31
  end
25
32
 
26
33
  # Return a list of all the requirable files and all of the subdirectories
@@ -75,13 +82,16 @@ module Bootsnap
75
82
  STABLE = :stable
76
83
  VOLATILE = :volatile
77
84
 
85
+ # Built-in ruby lib stuff doesn't change, but things can occasionally be
86
+ # installed into sitedir, which often lives under prefix.
78
87
  RUBY_PREFIX = RbConfig::CONFIG['prefix']
88
+ SITE_DIR = RbConfig::CONFIG['sitedir']
79
89
 
80
90
  def stability
81
91
  @stability ||= begin
82
- if Gem.path.detect { |p| path.start_with?(p) }
92
+ if Gem.path.detect { |p| path.start_with?(p.to_s) }
83
93
  STABLE
84
- elsif path.start_with?(RUBY_PREFIX)
94
+ elsif path.start_with?(RUBY_PREFIX) && !path.start_with?(SITE_DIR)
85
95
  STABLE
86
96
  else
87
97
  VOLATILE
@@ -12,6 +12,7 @@ module Bootsnap
12
12
  BUNDLE_PATH = (Bundler.bundle_path.cleanpath.to_s << LoadPathCache::SLASH).freeze
13
13
 
14
14
  def self.call(path)
15
+ path = path.to_s
15
16
  raise RelativePathNotSupported unless path.start_with?(SLASH)
16
17
 
17
18
  relative_slice = (path.size + 1)..-1
@@ -1,6 +1,5 @@
1
1
  require_relative '../explicit_require'
2
2
 
3
- Bootsnap::ExplicitRequire.with_gems('snappy') { require 'snappy' }
4
3
  Bootsnap::ExplicitRequire.with_gems('msgpack') { require 'msgpack' }
5
4
  Bootsnap::ExplicitRequire.from_rubylibdir('fileutils')
6
5
 
@@ -58,8 +57,9 @@ module Bootsnap
58
57
 
59
58
  def load_data
60
59
  @data = begin
61
- MessagePack.load(Snappy.inflate(File.binread(@store_path)))
62
- rescue Errno::ENOENT, Snappy::Error
60
+ MessagePack.load(File.binread(@store_path))
61
+ # handle malformed data due to upgrade incompatability
62
+ rescue Errno::ENOENT, MessagePack::MalformedFormatError, MessagePack::UnknownExtTypeError
63
63
  {}
64
64
  end
65
65
  end
@@ -69,7 +69,7 @@ module Bootsnap
69
69
  # caches if they read at an inopportune time.
70
70
  tmp = "#{@store_path}.#{(rand * 100000).to_i}.tmp"
71
71
  FileUtils.mkpath(File.dirname(tmp))
72
- File.binwrite(tmp, Snappy.deflate(MessagePack.dump(@data)))
72
+ File.binwrite(tmp, MessagePack.dump(@data))
73
73
  FileUtils.mv(tmp, @store_path)
74
74
  end
75
75
  end
@@ -1,3 +1,3 @@
1
1
  module Bootsnap
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/bootsnap.rb CHANGED
@@ -27,6 +27,7 @@ module Bootsnap
27
27
  ) if load_path_cache
28
28
 
29
29
  Bootsnap::CompileCache.setup(
30
+ cache_dir: cache_dir + '/bootsnap-compile-cache',
30
31
  iseq: compile_cache_iseq,
31
32
  yaml: compile_cache_yaml
32
33
  )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bootsnap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-04-05 00:00:00.000000000 Z
11
+ date: 2017-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -81,33 +81,33 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.2'
83
83
  - !ruby/object:Gem::Dependency
84
- name: msgpack
84
+ name: ffi-xattr
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '1.0'
90
- type: :runtime
89
+ version: 0.1.2
90
+ type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '1.0'
96
+ version: 0.1.2
97
97
  - !ruby/object:Gem::Dependency
98
- name: snappy
98
+ name: msgpack
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.0.15
103
+ version: '1.0'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.0.15
110
+ version: '1.0'
111
111
  description: wip.
112
112
  email:
113
113
  - burke.libbey@shopify.com
@@ -118,6 +118,8 @@ extra_rdoc_files: []
118
118
  files:
119
119
  - ".gitignore"
120
120
  - ".rubocop.yml"
121
+ - ".travis.yml"
122
+ - CHANGELOG.md
121
123
  - CONTRIBUTING.md
122
124
  - Gemfile
123
125
  - LICENSE
@@ -130,7 +132,6 @@ files:
130
132
  - dev.yml
131
133
  - ext/bootsnap/bootsnap.c
132
134
  - ext/bootsnap/bootsnap.h
133
- - ext/bootsnap/crc32.c
134
135
  - ext/bootsnap/extconf.rb
135
136
  - lib/bootsnap.rb
136
137
  - lib/bootsnap/compile_cache.rb
@@ -158,7 +159,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
158
159
  requirements:
159
160
  - - ">="
160
161
  - !ruby/object:Gem::Version
161
- version: '0'
162
+ version: 2.3.0
162
163
  required_rubygems_version: !ruby/object:Gem::Requirement
163
164
  requirements:
164
165
  - - ">="
data/ext/bootsnap/crc32.c DELETED
@@ -1,16 +0,0 @@
1
- #include "bootsnap.h"
2
-
3
- #include <x86intrin.h>
4
-
5
- uint32_t
6
- crc32(const char *bytes, size_t size)
7
- {
8
- size_t i;
9
- uint32_t hash = 0;
10
-
11
- for (i = 0; i < size; i++) {
12
- hash = _mm_crc32_u8(hash, bytes[i]);
13
- }
14
- return hash;
15
- }
16
-