gel 0.3.0 → 0.8.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -3
  3. data/RELEASING.md +12 -0
  4. data/exe/gel +4 -2
  5. data/gemlib/gel/stub.rb +20 -0
  6. data/lib/gel/catalog/common.rb +4 -2
  7. data/lib/gel/catalog/compact_index.rb +6 -10
  8. data/lib/gel/catalog/dependency_index.rb +10 -10
  9. data/lib/gel/catalog/legacy_index.rb +4 -6
  10. data/lib/gel/catalog/marshal_hacks.rb +2 -0
  11. data/lib/gel/catalog.rb +33 -52
  12. data/lib/gel/catalog_set.rb +100 -0
  13. data/lib/gel/command/help.rb +13 -2
  14. data/lib/gel/command/lock.rb +3 -3
  15. data/lib/gel/command/open.rb +24 -0
  16. data/lib/gel/command/shell_setup.rb +11 -8
  17. data/lib/gel/command/stub.rb +45 -2
  18. data/lib/gel/command/version.rb +7 -0
  19. data/lib/gel/command.rb +43 -6
  20. data/lib/gel/compatibility/rubygems.rb +10 -197
  21. data/lib/gel/compatibility.rb +2 -2
  22. data/lib/gel/config.rb +41 -7
  23. data/lib/gel/db.rb +93 -83
  24. data/lib/gel/direct_gem.rb +16 -4
  25. data/lib/gel/environment.rb +542 -249
  26. data/lib/gel/error.rb +156 -24
  27. data/lib/gel/gemfile_parser.rb +74 -12
  28. data/lib/gel/gemspec_parser.rb +26 -7
  29. data/lib/gel/git_catalog.rb +15 -3
  30. data/lib/gel/git_depot.rb +62 -28
  31. data/lib/gel/httpool.rb +5 -2
  32. data/lib/gel/installer.rb +61 -23
  33. data/lib/gel/lock_loader.rb +87 -112
  34. data/lib/gel/lock_parser.rb +23 -31
  35. data/lib/gel/locked_store.rb +30 -21
  36. data/lib/gel/multi_store.rb +13 -4
  37. data/lib/gel/null_solver.rb +67 -0
  38. data/lib/gel/package/abortable.rb +18 -0
  39. data/lib/gel/package/installer.rb +124 -49
  40. data/lib/gel/package.rb +21 -4
  41. data/lib/gel/path_catalog.rb +1 -1
  42. data/lib/gel/pinboard.rb +4 -2
  43. data/lib/gel/platform.rb +38 -0
  44. data/lib/gel/pub_grub/package.rb +67 -0
  45. data/lib/gel/pub_grub/preference_strategy.rb +10 -6
  46. data/lib/gel/pub_grub/solver.rb +37 -0
  47. data/lib/gel/pub_grub/source.rb +64 -92
  48. data/lib/gel/resolved_gem_set.rb +234 -0
  49. data/lib/gel/runtime.rb +3 -3
  50. data/lib/gel/set.rb +62 -0
  51. data/lib/gel/stdlib.rb +83 -0
  52. data/lib/gel/store.rb +94 -25
  53. data/lib/gel/store_catalog.rb +2 -2
  54. data/lib/gel/store_gem.rb +54 -6
  55. data/lib/gel/stub_set.rb +32 -2
  56. data/lib/gel/support/cgi_escape.rb +34 -0
  57. data/lib/gel/support/gem_platform.rb +0 -2
  58. data/lib/gel/support/sha512.rb +142 -0
  59. data/lib/gel/support/tar/tar_writer.rb +2 -2
  60. data/lib/gel/tail_file.rb +2 -1
  61. data/lib/gel/util.rb +108 -0
  62. data/lib/gel/vendor/pstore.rb +3 -0
  63. data/lib/gel/vendor/pub_grub.rb +3 -0
  64. data/lib/gel/vendor/ruby_digest.rb +3 -0
  65. data/lib/gel/vendor_catalog.rb +38 -0
  66. data/lib/gel/version.rb +1 -1
  67. data/lib/gel.rb +15 -0
  68. data/man/man1/gel-exec.1 +1 -1
  69. data/man/man1/gel-install.1 +1 -1
  70. data/man/man1/gel.1 +14 -1
  71. data/{lib/gel/compatibility → slib}/bundler/cli.rb +0 -0
  72. data/{lib/gel/compatibility → slib}/bundler/friendly_errors.rb +0 -0
  73. data/{lib/gel/compatibility/rubygems/dependency_installer.rb → slib/bundler/gem_helper.rb} +0 -0
  74. data/slib/bundler/gem_tasks.rb +0 -0
  75. data/{lib/gel/compatibility → slib}/bundler/setup.rb +0 -0
  76. data/{lib/gel/compatibility → slib}/bundler.rb +39 -3
  77. data/{lib/gel/compatibility → slib}/rubygems/command.rb +0 -0
  78. data/slib/rubygems/dependency_installer.rb +12 -0
  79. data/{lib/gel/compatibility → slib}/rubygems/gem_runner.rb +0 -0
  80. data/slib/rubygems/package.rb +6 -0
  81. data/slib/rubygems/package_task.rb +7 -0
  82. data/slib/rubygems/specification.rb +0 -0
  83. data/slib/rubygems/version.rb +0 -0
  84. data/slib/rubygems.rb +297 -0
  85. data/vendor/pstore/LICENSE.txt +22 -0
  86. data/vendor/pstore/lib/pstore.rb +488 -0
  87. data/vendor/pub_grub/LICENSE.txt +21 -0
  88. data/vendor/pub_grub/lib/pub_grub/assignment.rb +20 -0
  89. data/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +183 -0
  90. data/vendor/pub_grub/lib/pub_grub/failure_writer.rb +182 -0
  91. data/vendor/pub_grub/lib/pub_grub/incompatibility.rb +143 -0
  92. data/vendor/pub_grub/lib/pub_grub/package.rb +35 -0
  93. data/vendor/pub_grub/lib/pub_grub/partial_solution.rb +121 -0
  94. data/vendor/pub_grub/lib/pub_grub/rubygems.rb +45 -0
  95. data/vendor/pub_grub/lib/pub_grub/solve_failure.rb +17 -0
  96. data/vendor/pub_grub/lib/pub_grub/static_package_source.rb +53 -0
  97. data/vendor/pub_grub/lib/pub_grub/term.rb +105 -0
  98. data/vendor/pub_grub/lib/pub_grub/version.rb +3 -0
  99. data/vendor/pub_grub/lib/pub_grub/version_constraint.rb +124 -0
  100. data/vendor/pub_grub/lib/pub_grub/version_range.rb +399 -0
  101. data/vendor/pub_grub/lib/pub_grub/version_solver.rb +247 -0
  102. data/vendor/pub_grub/lib/pub_grub/version_union.rb +174 -0
  103. data/vendor/pub_grub/lib/pub_grub.rb +31 -0
  104. data/vendor/ruby-digest/UNLICENSE +24 -0
  105. data/vendor/ruby-digest/lib/ruby_digest.rb +812 -0
  106. metadata +95 -19
@@ -0,0 +1,488 @@
1
+ # frozen_string_literal: true
2
+ # = Gel::Vendor::PStore -- Transactional File Storage for Ruby Objects
3
+ #
4
+ # pstore.rb -
5
+ # originally by matz
6
+ # documentation by Kev Jackson and James Edward Gray II
7
+ # improved by Hongli Lai
8
+ #
9
+ # See Gel::Vendor::PStore for documentation.
10
+
11
+ require_relative "../../ruby-digest/lib/ruby_digest"
12
+
13
+ #
14
+ # Gel::Vendor::PStore implements a file based persistence mechanism based on a Hash. User
15
+ # code can store hierarchies of Ruby objects (values) into the data store file
16
+ # by name (keys). An object hierarchy may be just a single object. User code
17
+ # may later read values back from the data store or even update data, as needed.
18
+ #
19
+ # The transactional behavior ensures that any changes succeed or fail together.
20
+ # This can be used to ensure that the data store is not left in a transitory
21
+ # state, where some values were updated but others were not.
22
+ #
23
+ # Behind the scenes, Ruby objects are stored to the data store file with
24
+ # Marshal. That carries the usual limitations. Proc objects cannot be
25
+ # marshalled, for example.
26
+ #
27
+ # == Usage example:
28
+ #
29
+ # require_relative "pstore"
30
+ #
31
+ # # a mock wiki object...
32
+ # class WikiPage
33
+ # def initialize( page_name, author, contents )
34
+ # @page_name = page_name
35
+ # @revisions = Array.new
36
+ #
37
+ # add_revision(author, contents)
38
+ # end
39
+ #
40
+ # attr_reader :page_name
41
+ #
42
+ # def add_revision( author, contents )
43
+ # @revisions << { :created => Time.now,
44
+ # :author => author,
45
+ # :contents => contents }
46
+ # end
47
+ #
48
+ # def wiki_page_references
49
+ # [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
50
+ # end
51
+ #
52
+ # # ...
53
+ # end
54
+ #
55
+ # # create a new page...
56
+ # home_page = WikiPage.new( "HomePage", "James Edward Gray II",
57
+ # "A page about the JoysOfDocumentation..." )
58
+ #
59
+ # # then we want to update page data and the index together, or not at all...
60
+ # wiki = Gel::Vendor::PStore.new("wiki_pages.pstore")
61
+ # wiki.transaction do # begin transaction; do all of this or none of it
62
+ # # store page...
63
+ # wiki[home_page.page_name] = home_page
64
+ # # ensure that an index has been created...
65
+ # wiki[:wiki_index] ||= Array.new
66
+ # # update wiki index...
67
+ # wiki[:wiki_index].push(*home_page.wiki_page_references)
68
+ # end # commit changes to wiki data store file
69
+ #
70
+ # ### Some time later... ###
71
+ #
72
+ # # read wiki data...
73
+ # wiki.transaction(true) do # begin read-only transaction, no changes allowed
74
+ # wiki.roots.each do |data_root_name|
75
+ # p data_root_name
76
+ # p wiki[data_root_name]
77
+ # end
78
+ # end
79
+ #
80
+ # == Transaction modes
81
+ #
82
+ # By default, file integrity is only ensured as long as the operating system
83
+ # (and the underlying hardware) doesn't raise any unexpected I/O errors. If an
84
+ # I/O error occurs while Gel::Vendor::PStore is writing to its file, then the file will
85
+ # become corrupted.
86
+ #
87
+ # You can prevent this by setting <em>pstore.ultra_safe = true</em>.
88
+ # However, this results in a minor performance loss, and only works on platforms
89
+ # that support atomic file renames. Please consult the documentation for
90
+ # +ultra_safe+ for details.
91
+ #
92
+ # Needless to say, if you're storing valuable data with Gel::Vendor::PStore, then you should
93
+ # backup the Gel::Vendor::PStore files from time to time.
94
+ class Gel::Vendor::PStore
95
+ VERSION = "0.1.1"
96
+
97
+ RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
98
+ RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
99
+ WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
100
+
101
+ # The error type thrown by all Gel::Vendor::PStore methods.
102
+ class Error < StandardError
103
+ end
104
+
105
+ # Whether Gel::Vendor::PStore should do its best to prevent file corruptions, even when under
106
+ # unlikely-to-occur error conditions such as out-of-space conditions and other
107
+ # unusual OS filesystem errors. Setting this flag comes at the price in the form
108
+ # of a performance loss.
109
+ #
110
+ # This flag only has effect on platforms on which file renames are atomic (e.g.
111
+ # all POSIX platforms: Linux, MacOS X, FreeBSD, etc). The default value is false.
112
+ attr_accessor :ultra_safe
113
+
114
+ #
115
+ # To construct a Gel::Vendor::PStore object, pass in the _file_ path where you would like
116
+ # the data to be stored.
117
+ #
118
+ # Gel::Vendor::PStore objects are always reentrant. But if _thread_safe_ is set to true,
119
+ # then it will become thread-safe at the cost of a minor performance hit.
120
+ #
121
+ def initialize(file, thread_safe = false)
122
+ dir = File::dirname(file)
123
+ unless File::directory? dir
124
+ raise Gel::Vendor::PStore::Error, format("directory %s does not exist", dir)
125
+ end
126
+ if File::exist? file and not File::readable? file
127
+ raise Gel::Vendor::PStore::Error, format("file %s not readable", file)
128
+ end
129
+ @filename = file
130
+ @abort = false
131
+ @ultra_safe = false
132
+ @thread_safe = thread_safe
133
+ @lock = Thread::Mutex.new
134
+ end
135
+
136
+ # Raises Gel::Vendor::PStore::Error if the calling code is not in a Gel::Vendor::PStore#transaction.
137
+ def in_transaction
138
+ raise Gel::Vendor::PStore::Error, "not in transaction" unless @lock.locked?
139
+ end
140
+ #
141
+ # Raises Gel::Vendor::PStore::Error if the calling code is not in a Gel::Vendor::PStore#transaction or
142
+ # if the code is in a read-only Gel::Vendor::PStore#transaction.
143
+ #
144
+ def in_transaction_wr
145
+ in_transaction
146
+ raise Gel::Vendor::PStore::Error, "in read-only transaction" if @rdonly
147
+ end
148
+ private :in_transaction, :in_transaction_wr
149
+
150
+ #
151
+ # Retrieves a value from the Gel::Vendor::PStore file data, by _name_. The hierarchy of
152
+ # Ruby objects stored under that root _name_ will be returned.
153
+ #
154
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction. It will
155
+ # raise Gel::Vendor::PStore::Error if called at any other time.
156
+ #
157
+ def [](name)
158
+ in_transaction
159
+ @table[name]
160
+ end
161
+ #
162
+ # This method is just like Gel::Vendor::PStore#[], save that you may also provide a
163
+ # _default_ value for the object. In the event the specified _name_ is not
164
+ # found in the data store, your _default_ will be returned instead. If you do
165
+ # not specify a default, Gel::Vendor::PStore::Error will be raised if the object is not
166
+ # found.
167
+ #
168
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction. It will
169
+ # raise Gel::Vendor::PStore::Error if called at any other time.
170
+ #
171
+ def fetch(name, default=Gel::Vendor::PStore::Error)
172
+ in_transaction
173
+ unless @table.key? name
174
+ if default == Gel::Vendor::PStore::Error
175
+ raise Gel::Vendor::PStore::Error, format("undefined root name `%s'", name)
176
+ else
177
+ return default
178
+ end
179
+ end
180
+ @table[name]
181
+ end
182
+ #
183
+ # Stores an individual Ruby object or a hierarchy of Ruby objects in the data
184
+ # store file under the root _name_. Assigning to a _name_ already in the data
185
+ # store clobbers the old data.
186
+ #
187
+ # == Example:
188
+ #
189
+ # require_relative "pstore"
190
+ #
191
+ # store = Gel::Vendor::PStore.new("data_file.pstore")
192
+ # store.transaction do # begin transaction
193
+ # # load some data into the store...
194
+ # store[:single_object] = "My data..."
195
+ # store[:obj_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
196
+ # "James Gray" => ["erb.rb", "pstore.rb"] }
197
+ # end # commit changes to data store file
198
+ #
199
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction and it cannot
200
+ # be read-only. It will raise Gel::Vendor::PStore::Error if called at any other time.
201
+ #
202
+ def []=(name, value)
203
+ in_transaction_wr
204
+ @table[name] = value
205
+ end
206
+ #
207
+ # Removes an object hierarchy from the data store, by _name_.
208
+ #
209
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction and it cannot
210
+ # be read-only. It will raise Gel::Vendor::PStore::Error if called at any other time.
211
+ #
212
+ def delete(name)
213
+ in_transaction_wr
214
+ @table.delete name
215
+ end
216
+
217
+ #
218
+ # Returns the names of all object hierarchies currently in the store.
219
+ #
220
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction. It will
221
+ # raise Gel::Vendor::PStore::Error if called at any other time.
222
+ #
223
+ def roots
224
+ in_transaction
225
+ @table.keys
226
+ end
227
+ #
228
+ # Returns true if the supplied _name_ is currently in the data store.
229
+ #
230
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction. It will
231
+ # raise Gel::Vendor::PStore::Error if called at any other time.
232
+ #
233
+ def root?(name)
234
+ in_transaction
235
+ @table.key? name
236
+ end
237
+ # Returns the path to the data store file.
238
+ def path
239
+ @filename
240
+ end
241
+
242
+ #
243
+ # Ends the current Gel::Vendor::PStore#transaction, committing any changes to the data
244
+ # store immediately.
245
+ #
246
+ # == Example:
247
+ #
248
+ # require_relative "pstore"
249
+ #
250
+ # store = Gel::Vendor::PStore.new("data_file.pstore")
251
+ # store.transaction do # begin transaction
252
+ # # load some data into the store...
253
+ # store[:one] = 1
254
+ # store[:two] = 2
255
+ #
256
+ # store.commit # end transaction here, committing changes
257
+ #
258
+ # store[:three] = 3 # this change is never reached
259
+ # end
260
+ #
261
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction. It will
262
+ # raise Gel::Vendor::PStore::Error if called at any other time.
263
+ #
264
+ def commit
265
+ in_transaction
266
+ @abort = false
267
+ throw :pstore_abort_transaction
268
+ end
269
+ #
270
+ # Ends the current Gel::Vendor::PStore#transaction, discarding any changes to the data
271
+ # store.
272
+ #
273
+ # == Example:
274
+ #
275
+ # require_relative "pstore"
276
+ #
277
+ # store = Gel::Vendor::PStore.new("data_file.pstore")
278
+ # store.transaction do # begin transaction
279
+ # store[:one] = 1 # this change is not applied, see below...
280
+ # store[:two] = 2 # this change is not applied, see below...
281
+ #
282
+ # store.abort # end transaction here, discard all changes
283
+ #
284
+ # store[:three] = 3 # this change is never reached
285
+ # end
286
+ #
287
+ # *WARNING*: This method is only valid in a Gel::Vendor::PStore#transaction. It will
288
+ # raise Gel::Vendor::PStore::Error if called at any other time.
289
+ #
290
+ def abort
291
+ in_transaction
292
+ @abort = true
293
+ throw :pstore_abort_transaction
294
+ end
295
+
296
+ #
297
+ # Opens a new transaction for the data store. Code executed inside a block
298
+ # passed to this method may read and write data to and from the data store
299
+ # file.
300
+ #
301
+ # At the end of the block, changes are committed to the data store
302
+ # automatically. You may exit the transaction early with a call to either
303
+ # Gel::Vendor::PStore#commit or Gel::Vendor::PStore#abort. See those methods for details about how
304
+ # changes are handled. Raising an uncaught Exception in the block is
305
+ # equivalent to calling Gel::Vendor::PStore#abort.
306
+ #
307
+ # If _read_only_ is set to +true+, you will only be allowed to read from the
308
+ # data store during the transaction and any attempts to change the data will
309
+ # raise a Gel::Vendor::PStore::Error.
310
+ #
311
+ # Note that Gel::Vendor::PStore does not support nested transactions.
312
+ #
313
+ def transaction(read_only = false) # :yields: pstore
314
+ value = nil
315
+ if !@thread_safe
316
+ raise Gel::Vendor::PStore::Error, "nested transaction" unless @lock.try_lock
317
+ else
318
+ begin
319
+ @lock.lock
320
+ rescue ThreadError
321
+ raise Gel::Vendor::PStore::Error, "nested transaction"
322
+ end
323
+ end
324
+ begin
325
+ @rdonly = read_only
326
+ @abort = false
327
+ file = open_and_lock_file(@filename, read_only)
328
+ if file
329
+ begin
330
+ @table, checksum, original_data_size = load_data(file, read_only)
331
+
332
+ catch(:pstore_abort_transaction) do
333
+ value = yield(self)
334
+ end
335
+
336
+ if !@abort && !read_only
337
+ save_data(checksum, original_data_size, file)
338
+ end
339
+ ensure
340
+ file.close
341
+ end
342
+ else
343
+ # This can only occur if read_only == true.
344
+ @table = {}
345
+ catch(:pstore_abort_transaction) do
346
+ value = yield(self)
347
+ end
348
+ end
349
+ ensure
350
+ @lock.unlock
351
+ end
352
+ value
353
+ end
354
+
355
+ private
356
+ # Constant for relieving Ruby's garbage collector.
357
+ CHECKSUM_ALGO = Gel::Vendor::RubyDigest::SHA256
358
+ EMPTY_STRING = ""
359
+ EMPTY_MARSHAL_DATA = Marshal.dump({})
360
+ EMPTY_MARSHAL_CHECKSUM = CHECKSUM_ALGO.digest(EMPTY_MARSHAL_DATA)
361
+
362
+ #
363
+ # Open the specified filename (either in read-only mode or in
364
+ # read-write mode) and lock it for reading or writing.
365
+ #
366
+ # The opened File object will be returned. If _read_only_ is true,
367
+ # and the file does not exist, then nil will be returned.
368
+ #
369
+ # All exceptions are propagated.
370
+ #
371
+ def open_and_lock_file(filename, read_only)
372
+ if read_only
373
+ begin
374
+ file = File.new(filename, **RD_ACCESS)
375
+ begin
376
+ file.flock(File::LOCK_SH)
377
+ return file
378
+ rescue
379
+ file.close
380
+ raise
381
+ end
382
+ rescue Errno::ENOENT
383
+ return nil
384
+ end
385
+ else
386
+ file = File.new(filename, **RDWR_ACCESS)
387
+ file.flock(File::LOCK_EX)
388
+ return file
389
+ end
390
+ end
391
+
392
+ # Load the given Gel::Vendor::PStore file.
393
+ # If +read_only+ is true, the unmarshalled Hash will be returned.
394
+ # If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
395
+ # Hash, a checksum of the data, and the size of the data.
396
+ def load_data(file, read_only)
397
+ if read_only
398
+ begin
399
+ table = load(file)
400
+ raise Error, "Gel::Vendor::PStore file seems to be corrupted." unless table.is_a?(Hash)
401
+ rescue EOFError
402
+ # This seems to be a newly-created file.
403
+ table = {}
404
+ end
405
+ table
406
+ else
407
+ data = file.read
408
+ if data.empty?
409
+ # This seems to be a newly-created file.
410
+ table = {}
411
+ checksum = empty_marshal_checksum
412
+ size = empty_marshal_data.bytesize
413
+ else
414
+ table = load(data)
415
+ checksum = CHECKSUM_ALGO.digest(data)
416
+ size = data.bytesize
417
+ raise Error, "Gel::Vendor::PStore file seems to be corrupted." unless table.is_a?(Hash)
418
+ end
419
+ data.replace(EMPTY_STRING)
420
+ [table, checksum, size]
421
+ end
422
+ end
423
+
424
+ def on_windows?
425
+ is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
426
+ self.class.__send__(:define_method, :on_windows?) do
427
+ is_windows
428
+ end
429
+ is_windows
430
+ end
431
+
432
+ def save_data(original_checksum, original_file_size, file)
433
+ new_data = dump(@table)
434
+
435
+ if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
436
+ if @ultra_safe && !on_windows?
437
+ # Windows doesn't support atomic file renames.
438
+ save_data_with_atomic_file_rename_strategy(new_data, file)
439
+ else
440
+ save_data_with_fast_strategy(new_data, file)
441
+ end
442
+ end
443
+
444
+ new_data.replace(EMPTY_STRING)
445
+ end
446
+
447
+ def save_data_with_atomic_file_rename_strategy(data, file)
448
+ temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
449
+ temp_file = File.new(temp_filename, **WR_ACCESS)
450
+ begin
451
+ temp_file.flock(File::LOCK_EX)
452
+ temp_file.write(data)
453
+ temp_file.flush
454
+ File.rename(temp_filename, @filename)
455
+ rescue
456
+ File.unlink(temp_file) rescue nil
457
+ raise
458
+ ensure
459
+ temp_file.close
460
+ end
461
+ end
462
+
463
+ def save_data_with_fast_strategy(data, file)
464
+ file.rewind
465
+ file.write(data)
466
+ file.truncate(data.bytesize)
467
+ end
468
+
469
+
470
+ # This method is just a wrapped around Marshal.dump
471
+ # to allow subclass overriding used in YAML::Store.
472
+ def dump(table) # :nodoc:
473
+ Marshal::dump(table)
474
+ end
475
+
476
+ # This method is just a wrapped around Marshal.load.
477
+ # to allow subclass overriding used in YAML::Store.
478
+ def load(content) # :nodoc:
479
+ Marshal::load(content)
480
+ end
481
+
482
+ def empty_marshal_data
483
+ EMPTY_MARSHAL_DATA
484
+ end
485
+ def empty_marshal_checksum
486
+ EMPTY_MARSHAL_CHECKSUM
487
+ end
488
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 John Hawthorn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,20 @@
1
+ module Gel::Vendor::PubGrub
2
+ class Assignment
3
+ attr_reader :term, :cause, :decision_level, :index
4
+ def initialize(term, cause, decision_level, index)
5
+ @term = term
6
+ @cause = cause
7
+ @decision_level = decision_level
8
+ @index = index
9
+ end
10
+
11
+ def self.decision(package, version, decision_level, index)
12
+ term = Term.new(VersionConstraint.exact(package, version), true)
13
+ new(term, :decision, decision_level, index)
14
+ end
15
+
16
+ def decision?
17
+ cause == :decision
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,183 @@
1
+ require_relative '../pub_grub/version_constraint'
2
+ require_relative '../pub_grub/incompatibility'
3
+
4
+ module Gel::Vendor::PubGrub
5
+ # Types:
6
+ #
7
+ # Where possible, Gel::Vendor::PubGrub will accept user-defined types, so long as they quack.
8
+ #
9
+ # ## "Package":
10
+ #
11
+ # This class will be used to represent the various packages being solved for.
12
+ # .to_s will be called when displaying errors and debugging info, it should
13
+ # probably return the package's name.
14
+ # It must also have a reasonable definition of #== and #hash
15
+ #
16
+ # Example classes: String ("rails")
17
+ #
18
+ #
19
+ # ## "Version":
20
+ #
21
+ # This class will be used to represent a single version number.
22
+ #
23
+ # Versions don't need to store their associated package, however they will
24
+ # only be compared against other versions of the same package.
25
+ #
26
+ # It must be Comparible (and implement <=> reasonably)
27
+ #
28
+ # Example classes: Gem::Version, Integer
29
+ #
30
+ #
31
+ # ## "Dependency"
32
+ #
33
+ # This class represents the requirement one package has on another. It is
34
+ # returned by dependencies_for(package, version) and will be passed to
35
+ # parse_dependency to convert it to a format Gel::Vendor::PubGrub understands.
36
+ #
37
+ # It must also have a reasonable definition of #==
38
+ #
39
+ # Example classes: String ("~> 1.0"), Gem::Requirement
40
+ #
41
+ class BasicPackageSource
42
+ # Override me!
43
+ #
44
+ # This is called per package to find all possible versions of a package.
45
+ #
46
+ # It is called at most once per-package
47
+ #
48
+ # Returns: Array of versions for a package, in preferred order of selection
49
+ def all_versions_for(package)
50
+ raise NotImplementedError
51
+ end
52
+
53
+ # Override me!
54
+ #
55
+ # Returns: Hash in the form of { package => requirement, ... }
56
+ def dependencies_for(package, version)
57
+ raise NotImplementedError
58
+ end
59
+
60
+ # Override me!
61
+ #
62
+ # Convert a (user-defined) dependency into a format Gel::Vendor::PubGrub understands.
63
+ #
64
+ # Package is passed to this method but for many implementations is not
65
+ # needed.
66
+ #
67
+ # Returns: either a Gel::Vendor::PubGrub::VersionRange, Gel::Vendor::PubGrub::VersionUnion, or a
68
+ # Gel::Vendor::PubGrub::VersionConstraint
69
+ def parse_dependency(package, dependency)
70
+ raise NotImplementedError
71
+ end
72
+
73
+ # Override me!
74
+ #
75
+ # If not overridden, this will call dependencies_for with the root package.
76
+ #
77
+ # Returns: Hash in the form of { package => requirement, ... } (see dependencies_for)
78
+ def root_dependencies
79
+ dependencies_for(@root_package, @root_version)
80
+ end
81
+
82
+ # Override me (maybe)
83
+ #
84
+ # If not overridden, the order returned by all_versions_for will be used
85
+ #
86
+ # Returns: Array of versions in preferred order
87
+ def sort_versions_by_preferred(package, sorted_versions)
88
+ indexes = @version_indexes[package]
89
+ sorted_versions.sort_by { |version| indexes[version] }
90
+ end
91
+
92
+ def initialize
93
+ @root_package = Package.root
94
+ @root_version = Package.root_version
95
+
96
+ @cached_versions = Hash.new do |h,k|
97
+ if k == @root_package
98
+ h[k] = [@root_version]
99
+ else
100
+ h[k] = all_versions_for(k)
101
+ end
102
+ end
103
+ @sorted_versions = Hash.new { |h,k| h[k] = @cached_versions[k].sort }
104
+ @version_indexes = Hash.new { |h,k| h[k] = @cached_versions[k].each.with_index.to_h }
105
+
106
+ @cached_dependencies = Hash.new do |packages, package|
107
+ if package == @root_package
108
+ packages[package] = {
109
+ @root_version => root_dependencies
110
+ }
111
+ else
112
+ packages[package] = Hash.new do |versions, version|
113
+ versions[version] = dependencies_for(package, version)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ def versions_for(package, range=VersionRange.any)
120
+ versions = range.select_versions(@sorted_versions[package])
121
+
122
+ # Conditional avoids (among other things) calling
123
+ # sort_versions_by_preferred with the root package
124
+ if versions.size > 1
125
+ sort_versions_by_preferred(package, versions)
126
+ else
127
+ versions
128
+ end
129
+ end
130
+
131
+ def incompatibilities_for(package, version)
132
+ package_deps = @cached_dependencies[package]
133
+ sorted_versions = @sorted_versions[package]
134
+ package_deps[version].map do |dep_package, dep_constraint_name|
135
+ low = high = sorted_versions.index(version)
136
+
137
+ # find version low such that all >= low share the same dep
138
+ while low > 0 &&
139
+ package_deps[sorted_versions[low - 1]][dep_package] == dep_constraint_name
140
+ low -= 1
141
+ end
142
+ low =
143
+ if low == 0
144
+ nil
145
+ else
146
+ sorted_versions[low]
147
+ end
148
+
149
+ # find version high such that all < high share the same dep
150
+ while high < sorted_versions.length &&
151
+ package_deps[sorted_versions[high]][dep_package] == dep_constraint_name
152
+ high += 1
153
+ end
154
+ high =
155
+ if high == sorted_versions.length
156
+ nil
157
+ else
158
+ sorted_versions[high]
159
+ end
160
+
161
+ range = VersionRange.new(min: low, max: high, include_min: true)
162
+
163
+ self_constraint = VersionConstraint.new(package, range: range)
164
+
165
+ if !@packages.include?(dep_package)
166
+ # no such package -> this version is invalid
167
+ end
168
+
169
+ dep_constraint = parse_dependency(dep_package, dep_constraint_name)
170
+ if !dep_constraint
171
+ # falsey indicates this dependency was invalid
172
+ cause = Gel::Vendor::PubGrub::Incompatibility::InvalidDependency.new(dep_package, dep_constraint_name)
173
+ return [Incompatibility.new([Term.new(self_constraint, true)], cause: cause)]
174
+ elsif !dep_constraint.is_a?(VersionConstraint)
175
+ # Upgrade range/union to VersionConstraint
176
+ dep_constraint = VersionConstraint.new(dep_package, range: dep_constraint)
177
+ end
178
+
179
+ Incompatibility.new([Term.new(self_constraint, true), Term.new(dep_constraint, false)], cause: :dependency)
180
+ end
181
+ end
182
+ end
183
+ end