Class: Siding::LoadManifest

Inherits:
Object
  • Object
show all
Defined in:
lib/siding/load_manifest.rb

Defined Under Namespace

Modules: EnvProbe, LoadProbe Classes: DirectoryEntry, EnvEntry, FileEntry

Constant Summary collapse

RELOADABLE =
:reloadable
REBOOT =
:reboot
EXCLUDED_SUBDIRECTORIES =
%w[tmp log storage node_modules .git vendor/bundle].freeze
INITIALIZER_DIRECTORY =
File.join("config", "initializers")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app_root:, loaded:, env_reads: {}) ⇒ LoadManifest

Returns a new instance of LoadManifest.



87
88
89
90
91
92
93
94
95
96
# File 'lib/siding/load_manifest.rb', line 87

def initialize(app_root:, loaded:, env_reads: {})
  @app_root = realpath(app_root)
  @captured_at = Time.now
  @bundle_path = discover_bundle_path
  @bundle_files = discover_bundle_files
  @bundle_token = self.class.token_for(@bundle_files)
  @file_entries = capture_file_entries(loaded)
  @directory_entries = capture_directory_entries
  @env_entries = capture_env_entries(env_reads)
end

Instance Attribute Details

#app_rootObject (readonly)

Returns the value of attribute app_root.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def app_root
  @app_root
end

#bundle_filesObject (readonly)

Returns the value of attribute bundle_files.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def bundle_files
  @bundle_files
end

#bundle_pathObject (readonly)

Returns the value of attribute bundle_path.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def bundle_path
  @bundle_path
end

#bundle_tokenObject (readonly)

Returns the value of attribute bundle_token.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def bundle_token
  @bundle_token
end

#captured_atObject (readonly)

Returns the value of attribute captured_at.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def captured_at
  @captured_at
end

#directory_entriesObject (readonly)

Returns the value of attribute directory_entries.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def directory_entries
  @directory_entries
end

#env_entriesObject (readonly)

Returns the value of attribute env_entries.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def env_entries
  @env_entries
end

#file_entriesObject (readonly)

Returns the value of attribute file_entries.



21
22
23
# File 'lib/siding/load_manifest.rb', line 21

def file_entries
  @file_entries
end

Class Method Details

.around_boot(app_root:) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
# File 'lib/siding/load_manifest.rb', line 24

def around_boot(app_root:)
  env = EnvProbe.start
  loads = LoadProbe.start
  before = $LOADED_FEATURES.dup
  yield

  build(app_root:, loaded: ($LOADED_FEATURES - before) + loads.finish, env_reads: env.finish)
ensure
  env&.stop
  loads&.stop
end

.build(app_root:, loaded:, env_reads: {}) ⇒ Object



36
37
38
# File 'lib/siding/load_manifest.rb', line 36

def build(app_root:, loaded:, env_reads: {})
  new(app_root:, loaded:, env_reads:)
end

.child_names(path, recursive:) ⇒ Object



65
66
67
68
69
# File 'lib/siding/load_manifest.rb', line 65

def child_names(path, recursive:)
  recursive ? Dir.glob("**/*", base: path).sort : Dir.children(path).sort
rescue SystemCallError
  nil
end

.digest_env_value(value) ⇒ Object



71
72
73
# File 'lib/siding/load_manifest.rb', line 71

def digest_env_value(value)
  Digest::SHA256.hexdigest(value.nil? ? "0" : "1\0#{value}")
end

.entry_digest_for(path, recursive:) ⇒ Object



58
59
60
61
62
63
# File 'lib/siding/load_manifest.rb', line 58

def entry_digest_for(path, recursive:)
  names = child_names(path, recursive: recursive)
  return "missing" if names.nil?

  Digest::SHA256.hexdigest(names.sort.join("\n"))
end

.label_for(bundle_token:, files:, directories:, envs:) ⇒ Object



40
41
42
43
44
45
46
47
# File 'lib/siding/load_manifest.rb', line 40

def label_for(bundle_token:, files:, directories:, envs:)
  digest = Digest::SHA256.new
  digest << "bundle\0#{bundle_token}\n"
  files.each { |path, stamp| digest << "file\0#{path}\0#{stamp}\n" }
  directories.each { |path, entry_digest| digest << "dir\0#{path}\0#{entry_digest}\n" }
  envs.each { |key, value| digest << "env\0#{key}\0#{value.nil? ? "\1" : value}\n" }
  digest.hexdigest[0, 12]
end

.stamp_for(path) ⇒ Object



49
50
51
52
53
54
55
56
# File 'lib/siding/load_manifest.rb', line 49

def stamp_for(path)
  stat = File.stat(path)
  "#{stat.size}:#{format('%.6f', stat.mtime.to_f)}"
rescue SystemCallError
  # A loaded file that no longer exists is a change like any other, and one that must not be
  # mistaken for "unchanged" by a comparison against nil on both sides.
  "missing"
end

.token_for(paths) ⇒ Object



75
76
77
78
79
80
81
82
83
84
# File 'lib/siding/load_manifest.rb', line 75

def token_for(paths)
  digest = Digest::SHA256.new
  paths.each do |path|
    digest << path << "\0"
    digest << (File.file?(path) ? File.read(path) : "")
  end
  digest.hexdigest[0, 16]
rescue SystemCallError
  nil
end

Instance Method Details

#revision_labelObject



98
99
100
101
102
103
104
105
# File 'lib/siding/load_manifest.rb', line 98

def revision_label
  @revision_label ||= self.class.label_for(
    bundle_token: bundle_token,
    files: file_entries.map { |entry| [entry.path, "#{entry.size}:#{format('%.6f', entry.mtime)}"] },
    directories: directory_entries.map { |entry| [entry.path, entry.entry_digest] },
    envs: env_entries.map { |entry| [entry.key, entry.value_digest] }
  )
end

#to_sObject



107
108
# File 'lib/siding/load_manifest.rb', line 107

def to_s = "#{file_entries.size} files, #{directory_entries.size} directories, " \
"#{env_entries.size} environment variables"