fig 0.1.67 → 0.1.69

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.
@@ -0,0 +1,318 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+ require 'socket'
4
+ require 'sys/admin'
5
+ require 'tmpdir'
6
+
7
+ require 'fig'
8
+ require 'fig/at_exit'
9
+ require 'fig/logging'
10
+ require 'fig/not_found_error'
11
+ require 'fig/package_cache'
12
+ require 'fig/package_descriptor'
13
+ require 'fig/parser'
14
+ require 'fig/repository'
15
+ require 'fig/repository_error'
16
+ require 'fig/statement/archive'
17
+ require 'fig/statement/resource'
18
+
19
+ module Fig; end
20
+
21
+ class Fig::RepositoryPackagePublisher
22
+ attr_accessor :operating_system
23
+ attr_accessor :publish_listeners
24
+ attr_accessor :package_statements
25
+ attr_accessor :descriptor
26
+ attr_accessor :source_package
27
+ attr_accessor :was_forced
28
+ attr_accessor :base_temp_dir
29
+ attr_accessor :local_dir_for_package
30
+ attr_accessor :remote_dir_for_package
31
+ attr_accessor :local_fig_file_for_package
32
+ attr_accessor :remote_fig_file_for_package
33
+ attr_accessor :local_only
34
+
35
+ def publish_package()
36
+ derive_publish_metadata()
37
+ validate_asset_names()
38
+
39
+ temp_dir = publish_temp_dir()
40
+ @operating_system.delete_and_recreate_directory(temp_dir)
41
+ @operating_system.delete_and_recreate_directory(@local_dir_for_package)
42
+
43
+ fig_file = File.join(temp_dir, Fig::Repository::PACKAGE_FILE_IN_REPO)
44
+ content = publish_package_content_and_derive_definition_file()
45
+ @operating_system.write(fig_file, content)
46
+
47
+ if not @local_only
48
+ @operating_system.upload(fig_file, remote_fig_file_for_package())
49
+ end
50
+ @operating_system.copy(fig_file, local_fig_file_for_package())
51
+
52
+ notify_listeners
53
+
54
+ FileUtils.rm_rf(temp_dir)
55
+
56
+ return true
57
+ end
58
+
59
+ private
60
+
61
+ def validate_asset_names()
62
+ asset_statements = @package_statements.select { |s| s.is_asset? }
63
+
64
+ asset_names = Set.new()
65
+ asset_statements.each do
66
+ |statement|
67
+
68
+ asset_name = statement.asset_name()
69
+ if not asset_name.nil?
70
+ if asset_name == Fig::Repository::RESOURCES_FILE
71
+ Fig::Logging.fatal \
72
+ %Q<You cannot have an asset with the name "#{Fig::Repository::RESOURCES_FILE}"#{statement.position_string()} due to Fig implementation details.>
73
+ end
74
+
75
+ if asset_names.include?(asset_name)
76
+ Fig::Logging.fatal \
77
+ %Q<Found multiple archives with the name "#{asset_name}"#{statement.position_string()}. If these were allowed, archives would overwrite each other.>
78
+ raise Fig::RepositoryError.new
79
+ else
80
+ asset_names.add(asset_name)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def derive_publish_metadata()
87
+ @publish_time = Time.now()
88
+ @publish_login = Sys::Admin.get_login()
89
+ @publish_host = Socket.gethostname()
90
+
91
+ return
92
+ end
93
+
94
+ def publish_package_content_and_derive_definition_file()
95
+ @definition_file_lines = []
96
+
97
+ add_package_metadata_comments()
98
+ publish_package_content()
99
+ add_unparsed_text()
100
+
101
+ @definition_file_lines.flatten!
102
+ file_content = @definition_file_lines.join("\n")
103
+ file_content.gsub!(/\n{3,}/, "\n\n")
104
+
105
+ return file_content.strip() + "\n"
106
+ end
107
+
108
+ def add_package_metadata_comments()
109
+ @definition_file_lines <<
110
+ %Q<# Publishing information for #{@descriptor.to_string()}:>
111
+ @definition_file_lines << %q<#>
112
+
113
+ @definition_file_lines <<
114
+ %Q<# Time: #{@publish_time} (epoch: #{@publish_time.to_i()})>
115
+
116
+ @definition_file_lines << %Q<# User: #{@publish_login}>
117
+ @definition_file_lines << %Q<# Host: #{@publish_host}>
118
+ @definition_file_lines << %Q<# Args: "#{ARGV.join %q[", "]}">
119
+ @definition_file_lines << %Q<# Fig: v#{Fig::VERSION}>
120
+ @definition_file_lines << %q<#>
121
+
122
+ asset_statements =
123
+ @package_statements.select { |statement| statement.is_asset? }
124
+ asset_strings =
125
+ asset_statements.collect { |statement| statement.unparse('# ') }
126
+
127
+ if asset_strings.empty?
128
+ @definition_file_lines <<
129
+ %q<# There were no asset statements in the unpublished package definition.>
130
+ else
131
+ @definition_file_lines << %q<# Original asset statements: >
132
+ @definition_file_lines << %q<#>
133
+ @definition_file_lines << asset_strings
134
+ end
135
+
136
+ @definition_file_lines << %Q<\n>
137
+
138
+ return
139
+ end
140
+
141
+ # Deals with Archive and Resource statements. It downloads any remote
142
+ # files (those where the statement references a URL as opposed to a local
143
+ # file) and then copies all files into the local repository and the remote
144
+ # repository (if not a local-only publish).
145
+ def publish_package_content()
146
+ initialize_statements_to_publish()
147
+ create_resource_archive()
148
+
149
+ @statements_to_publish.each do
150
+ |statement|
151
+
152
+ if statement.is_asset?
153
+ publish_asset(statement)
154
+ else
155
+ @definition_file_lines << statement.unparse('')
156
+ end
157
+ end
158
+
159
+ return
160
+ end
161
+
162
+ def initialize_statements_to_publish()
163
+ @resource_paths = []
164
+
165
+ @statements_to_publish = @package_statements.reject do |statement|
166
+ if (
167
+ statement.is_a?(Fig::Statement::Resource) &&
168
+ ! Fig::Repository.is_url?(statement.url)
169
+ )
170
+ @resource_paths << statement.url
171
+ true
172
+ else
173
+ false
174
+ end
175
+ end
176
+
177
+ return
178
+ end
179
+
180
+ def create_resource_archive()
181
+ if @resource_paths.size > 0
182
+ asset_paths = expand_globs_from(@resource_paths)
183
+ check_asset_paths(asset_paths)
184
+
185
+ file = Fig::Repository::RESOURCES_FILE
186
+ @operating_system.create_archive(file, asset_paths)
187
+ Fig::AtExit.add { File.delete(file) }
188
+
189
+ @statements_to_publish.unshift(
190
+ Fig::Statement::Archive.new(nil, nil, file)
191
+ )
192
+ end
193
+
194
+ return
195
+ end
196
+
197
+ def publish_asset(asset_statement)
198
+ asset_name = asset_statement.asset_name()
199
+ asset_remote = "#{remote_dir_for_package()}/#{asset_name}"
200
+
201
+ if Fig::Repository.is_url?(asset_statement.url)
202
+ asset_local = File.join(publish_temp_dir(), asset_name)
203
+
204
+ begin
205
+ @operating_system.download(asset_statement.url, asset_local)
206
+ rescue Fig::NotFoundError
207
+ Fig::Logging.fatal "Could not download #{asset_statement.url}."
208
+ raise Fig::RepositoryError.new
209
+ end
210
+ else
211
+ asset_local = asset_statement.url
212
+ check_asset_path(asset_local)
213
+ end
214
+
215
+ if not @local_only
216
+ @operating_system.upload(
217
+ asset_local, asset_remote
218
+ )
219
+ end
220
+
221
+ @operating_system.copy(
222
+ asset_local, @local_dir_for_package + '/' + asset_name
223
+ )
224
+ if asset_statement.is_a?(Fig::Statement::Archive)
225
+ @operating_system.unpack_archive(@local_dir_for_package, asset_name)
226
+ end
227
+
228
+ @definition_file_lines <<
229
+ asset_statement.class.new(nil, nil, asset_name).unparse('')
230
+
231
+ return
232
+ end
233
+
234
+ def add_unparsed_text()
235
+ if @source_package && @source_package.unparsed_text
236
+
237
+ @definition_file_lines << ''
238
+ @definition_file_lines << '# Original, unparsed package text:'
239
+ @definition_file_lines << '# '
240
+ @definition_file_lines << @source_package.unparsed_text.gsub(/^/, '# ')
241
+ end
242
+
243
+ return
244
+ end
245
+
246
+ def notify_listeners()
247
+ publish_information = {}
248
+ publish_information[:descriptor] = @descriptor
249
+ publish_information[:time] = @publish_time
250
+ publish_information[:login] = @publish_login
251
+ publish_information[:host] = @publish_host
252
+
253
+ # Ensure that we've really got booleans and not merely true or false
254
+ # values.
255
+ publish_information[:was_forced] = @was_forced ? true : false
256
+ publish_information[:local_only] = @local_only ? true : false
257
+
258
+ publish_information[:local_destination] = @local_dir_for_package
259
+ publish_information[:remote_destination] = @remote_dir_for_package
260
+
261
+ @publish_listeners.each do
262
+ |listener|
263
+
264
+ listener.published(publish_information)
265
+ end
266
+
267
+ return
268
+ end
269
+
270
+ def publish_temp_dir()
271
+ File.join(base_temp_dir(), 'publish')
272
+ end
273
+
274
+ def check_asset_path(asset_path)
275
+ if not File.exist?(asset_path)
276
+ Fig::Logging.fatal "Could not find file #{asset_path}."
277
+ raise Fig::RepositoryError.new
278
+ end
279
+
280
+ return
281
+ end
282
+
283
+ def check_asset_paths(asset_paths)
284
+ non_existing_paths =
285
+ asset_paths.select {|path| ! File.exist?(path) && ! File.symlink?(path) }
286
+
287
+ if not non_existing_paths.empty?
288
+ if non_existing_paths.size > 1
289
+ Fig::Logging.fatal "Could not find files: #{ non_existing_paths.join(', ') }"
290
+ else
291
+ Fig::Logging.fatal "Could not find file #{non_existing_paths[0]}."
292
+ end
293
+
294
+ raise Fig::RepositoryError.new
295
+ end
296
+
297
+ return
298
+ end
299
+
300
+ # 'resources' is an Array of fileglob patterns: ['tmp/foo/file1',
301
+ # 'tmp/foo/*.jar']
302
+ def expand_globs_from(resources)
303
+ expanded_files = []
304
+
305
+ resources.each do
306
+ |path|
307
+
308
+ globbed_files = Dir.glob(path)
309
+ if globbed_files.empty?
310
+ expanded_files << path
311
+ else
312
+ expanded_files.concat(globbed_files)
313
+ end
314
+ end
315
+
316
+ return expanded_files
317
+ end
318
+ end
@@ -0,0 +1,68 @@
1
+ require 'fileutils'
2
+
3
+ require 'fig/user_input_error'
4
+
5
+ module Fig; end
6
+
7
+ class Fig::UpdateLock
8
+ def initialize(lock_directory, response)
9
+ set_up_lock(lock_directory, response)
10
+
11
+ return
12
+ end
13
+
14
+ def close()
15
+ @lock.close
16
+
17
+ return
18
+ end
19
+
20
+ private
21
+
22
+ def set_up_lock(lock_directory, response)
23
+ FileUtils.mkdir_p(lock_directory)
24
+
25
+ # Tried using the directory itself as the lock, but Windows is
26
+ # non-cooperative.
27
+ lock_file = lock_directory + '/lock'
28
+
29
+ # Yes, there's a race condition here, but with the way Windows file locking
30
+ # works, it's better than a boot to the head.
31
+ if ! File.exists? lock_file
32
+ created_file = File.new(lock_file, 'w')
33
+ created_file.close
34
+ end
35
+
36
+ @lock = File.new(lock_file)
37
+
38
+ # *sigh* Ruby 1.8 doesn't support close_on_exec(), but we'll still use it
39
+ # if we can as a better attempt at safety.
40
+ if @lock.respond_to? :close_on_exec=
41
+ @lock.close_on_exec = true
42
+ end
43
+
44
+ if response == :wait
45
+ @lock.flock(File::LOCK_EX)
46
+ else
47
+ if ! @lock.flock(File::LOCK_EX | File::LOCK_NB)
48
+ raise_lock_usage_error(lock_directory)
49
+ end
50
+ end
51
+
52
+ return
53
+ end
54
+
55
+ def raise_lock_usage_error(lock_directory)
56
+ raise Fig::UserInputError.new(<<-END_MESSAGE)
57
+ Cannot update while another instance of Fig is updating #{lock_directory}.
58
+
59
+ You can tell Fig to wait for update with
60
+
61
+ fig --update --update-lock-response wait ...
62
+
63
+ or you can throw caution to the wind with
64
+
65
+ fig --update --update-lock-response ignore ...
66
+ END_MESSAGE
67
+ end
68
+ end
@@ -95,44 +95,78 @@ class Fig::WorkingDirectoryMaintainer
95
95
 
96
96
  def copy(source, relpath)
97
97
  target = File.join(@base_dir, relpath)
98
+
99
+ if source_and_target_are_same?(source, target)
100
+ # Actually happened: Retrieve and "set" both set to ".". Victim's current
101
+ # directory included a ".git" directory. Update was done and then later,
102
+ # an update with different dependencies. Fig proceeded to delete all
103
+ # files that had previously existed in the current directory, including
104
+ # out of the git repo. Whoops.
105
+ Fig::Logging.warn %Q<Skipping copying "#{source}" to itself.>
106
+ return
107
+ end
108
+
98
109
  if File.directory?(source)
99
- FileUtils.mkdir_p(target)
100
- Fig::Logging.debug "Copying directory #{source} to #{target}."
101
- Dir.foreach(source) do |child|
102
- if child != '.' and child != '..'
103
- source_file = File.join(source, child)
104
- target_file = File.join(relpath, child)
105
- copy(source_file, target_file)
106
- end
107
- end
110
+ copy_directory(source, relpath, target)
108
111
  else
109
- if should_copy_file?(source, target)
110
- if Fig::Logging.debug?
111
- Fig::Logging.debug \
112
- "Copying file from #{source} to #{target}."
113
- else
114
- Fig::Logging.info(
115
- Fig::Logging::Colorizable.new(
116
- "+ [#{formatted_meta()}] #{relpath}",
117
- :green,
118
- nil
119
- )
120
- )
121
- end
122
- FileUtils.mkdir_p(File.dirname(target))
112
+ copy_file(source, relpath, target)
113
+ end
123
114
 
124
- # If the source is a dangling symlink, then there's no time, etc. to
125
- # preserve.
126
- preserve = File.exist?(source) && ! File.symlink?(source)
115
+ return
116
+ end
127
117
 
128
- FileUtils.copy_entry(
129
- source, target, preserve, false, :remove_destination
130
- )
118
+ def source_and_target_are_same?(source, target)
119
+ # Ruby 1.8 doesn't have File.absolute_path(), so we have to fall back to
120
+ # .expand_path().
121
+ source_absolute = File.expand_path(source)
122
+ target_absolute = File.expand_path(target)
123
+
124
+ return source_absolute == target_absolute
125
+ end
126
+
127
+ def copy_directory(source, relpath, target)
128
+ FileUtils.mkdir_p(target)
129
+ Fig::Logging.debug "Copying directory #{source} to #{target}."
130
+
131
+ Dir.foreach(source) do |child|
132
+ if child != '.' and child != '..'
133
+ source_file = File.join(source, child)
134
+ target_file = File.join(relpath, child)
135
+ copy(source_file, target_file)
131
136
  end
132
- if @package_meta
133
- @package_meta.add_file(relpath)
134
- @package_meta.mark_as_retrieved()
137
+ end
138
+
139
+ return
140
+ end
141
+
142
+ def copy_file(source, relpath, target)
143
+ if should_copy_file?(source, target)
144
+ if Fig::Logging.debug?
145
+ Fig::Logging.debug \
146
+ "Copying file from #{source} to #{target}."
147
+ else
148
+ Fig::Logging.info(
149
+ Fig::Logging::Colorizable.new(
150
+ "+ [#{formatted_meta()}] #{relpath}",
151
+ :green,
152
+ nil
153
+ )
154
+ )
135
155
  end
156
+ FileUtils.mkdir_p(File.dirname(target))
157
+
158
+ # If the source is a dangling symlink, then there's no time, etc. to
159
+ # preserve.
160
+ preserve = File.exist?(source) && ! File.symlink?(source)
161
+
162
+ FileUtils.copy_entry(
163
+ source, target, preserve, false, :remove_destination
164
+ )
165
+ end
166
+
167
+ if @package_meta
168
+ @package_meta.add_file(relpath)
169
+ @package_meta.mark_as_retrieved()
136
170
  end
137
171
 
138
172
  return