bcpm 0.11

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,410 @@
1
+ require 'fileutils'
2
+
3
+ # :nodoc: namespace
4
+ module Bcpm
5
+
6
+ # Manages player packages.
7
+ module Player
8
+ # Clones a player code from a repository, and sets it up for development on the local machine.
9
+ #
10
+ # Returns the player's name, or nil if something went wrong.
11
+ def self.install(repo_uri, repo_branch)
12
+ name = player_name repo_uri
13
+ local_path = File.join local_root, name
14
+ if File.exist?(local_path)
15
+ puts "Player already installed at #{local_path}!"
16
+ return nil
17
+ end
18
+ return nil unless Bcpm::Git.clone_repo(repo_uri, repo_branch, local_path)
19
+
20
+ unless source_path = package_path(local_path)
21
+ puts "Repository #{repo_uri} doesn't seem to contain a player!"
22
+ FileUtils.rm_rf local_path
23
+ return nil
24
+ end
25
+
26
+ Bcpm::Dist.add_player source_path
27
+ configure local_path
28
+ name
29
+ end
30
+
31
+ # Downloads player code from a repository for one-time use, without linking it to the repository.
32
+ #
33
+ # Returns the path to the player on the local system, or nil if something went wrong.
34
+ def self.checkpoint(repo_uri, repo_branch, local_name)
35
+ old_name = player_name repo_uri
36
+ local_path = File.join local_root, local_name
37
+ if File.exist?(local_path)
38
+ puts "Player already installed at #{local_path}!"
39
+ return nil
40
+ end
41
+ if old_name == repo_uri
42
+ return nil unless Bcpm::Git.checkpoint_local_repo(File.join(local_root, old_name), local_path)
43
+ else
44
+ return nil unless Bcpm::Git.checkpoint_repo(repo_uri, repo_branch, local_path)
45
+ end
46
+ if local_name == old_name
47
+ source_path = package_path(local_path)
48
+ else
49
+ source_path = rename(local_path, old_name)
50
+ end
51
+ unless source_path
52
+ puts "Repository #{repo_uri} doesn't seem to contain a player!"
53
+ FileUtils.rm_rf local_path
54
+ return nil
55
+ end
56
+
57
+ Bcpm::Dist.add_player source_path
58
+ configure local_path
59
+ local_path
60
+ end
61
+
62
+ # Creates a player from the built-in template.
63
+ #
64
+ # Returns the path to the player on the local system, or nil if something went wrong.
65
+ def self.create(local_name)
66
+ old_name = 'template'
67
+ local_path = File.join local_root, local_name
68
+ if File.exist?(local_path)
69
+ puts "Player already installed at #{local_path}!"
70
+ return nil
71
+ end
72
+ write_template local_path
73
+ unless source_path = rename(local_path, old_name)
74
+ puts "Repository #{repo_uri} doesn't seem to contain a player!"
75
+ FileUtils.rm_rf local_path
76
+ return nil
77
+ end
78
+
79
+ Bcpm::Dist.add_player source_path
80
+ configure local_path
81
+ local_path
82
+ end
83
+
84
+ # Undoes the effects of an install or checkpoint call.
85
+ #
86
+ # Returns true for success, or false if something went wrong.
87
+ def self.uninstall(local_name)
88
+ local_path = File.join local_root, local_name
89
+ source_path = package_path local_path
90
+ Bcpm::Dist.remove_player source_path if source_path
91
+ return false unless File.exist? local_path
92
+ FileUtils.rm_rf local_path
93
+ true
94
+ end
95
+
96
+ # Re-configures a player's source code project.
97
+ def self.reconfigure(local_name)
98
+ local_path = File.join local_root, local_name
99
+ unless source_path = package_path(local_path)
100
+ puts "Directory #{local_path} doesn't seem to contain a player!"
101
+ FileUtils.rm_rf local_path
102
+ return nil
103
+ end
104
+ Bcpm::Dist.add_player source_path
105
+ configure local_path
106
+ end
107
+
108
+ # Runs the player's test suite.
109
+ def self.run_suite(local_name)
110
+ local_path = File.join local_root, local_name
111
+ unless has_suite? local_path
112
+ puts "No test suite found at #{local_path}!"
113
+ return false
114
+ end
115
+ new_suite(local_path).run false
116
+ end
117
+
118
+ # Runs a case in the test suite against a player codebase.
119
+ def self.run_case(case_name, live, local_name, branch = 'master')
120
+ local_path = File.join local_root, local_name
121
+ unless has_suite? local_path
122
+ puts "No test suite found at #{local_path}!"
123
+ return false
124
+ end
125
+
126
+ case_file = case_name + '.rb'
127
+ files = suite_files(local_path).select { |file| File.basename(file) == case_file }
128
+ unless files.length == 1
129
+ puts "Ambiguous case name! It matched #{files.count} cases.\n#{files.join("\n")}\n"
130
+ return false
131
+ end
132
+ suite = Bcpm::Tests::Suite.new(local_path)
133
+ suite.add_cases files
134
+ suite.run live
135
+ end
136
+
137
+ # Creates a Suite instance for running all the tests.
138
+ def self.new_suite(local_path)
139
+ suite = Bcpm::Tests::Suite.new local_path
140
+ suite.add_cases suite_files(local_path)
141
+ suite
142
+ end
143
+
144
+ # All the test cases in the suite of the given player.
145
+ def self.suite_files(local_path)
146
+ Dir.glob File.join(local_path, 'suite', '**', '*.rb')
147
+ end
148
+
149
+ # True if a battlecode distribution is installed on the local machine.
150
+ def self.has_suite?(local_path)
151
+ File.exist? File.join(local_path, 'suite')
152
+ end
153
+
154
+ # Configures a player's source code project.
155
+ def self.configure(local_path)
156
+ File.open File.join(local_path, '.project'), 'wb' do |f|
157
+ f.write eclipse_project(local_path)
158
+ end
159
+
160
+ File.open File.join(local_path, '.classpath'), 'wb' do |f|
161
+ f.write eclipse_classpath(local_path)
162
+ end
163
+
164
+ File.open File.join(local_path, 'build.xml'), 'wb' do |f|
165
+ f.write ant_config('bc.conf')
166
+ end
167
+ end
168
+
169
+ # The directory containing all players code.
170
+ def self.local_root
171
+ Bcpm::Config[:player_root] ||= default_local_root
172
+ end
173
+
174
+ # The directory containing all players code.
175
+ def self.default_local_root
176
+ path = Dir.pwd
177
+ unless File.exist?(File.join(path, '.metadata'))
178
+ puts "Please chdir into your Eclipse workspace!"
179
+ exit 1
180
+ end
181
+ path
182
+ end
183
+
184
+ # All installed players.
185
+ #
186
+ # This might contain false positives.
187
+ def self.list
188
+ Dir.glob(File.join(local_root, '*', '.project')).map do |project_file|
189
+ File.basename File.dirname(project_file)
190
+ end
191
+ end
192
+
193
+ # All installed players who are properly hooked into the distribution.
194
+ #
195
+ # These players can be used into tests and simulations.
196
+ def self.list_active
197
+ list.select { |player_name| wired? player_name }
198
+ end
199
+
200
+ # True if the given player name is installed and wired.
201
+ def self.wired?(player_name)
202
+ local_path = File.join local_root, player_name
203
+ source_path = package_path local_path, nil, true
204
+ (source_path && Bcpm::Dist.contains_player?(source_path)) ? true : false
205
+ end
206
+
207
+ # Cleans up all the installed players.
208
+ def self.uninstall_all
209
+ list_good.each { |player_name| uninstall player_name }
210
+ end
211
+
212
+ # Extracts the player name out of the git repository URI for the player's code.
213
+ def self.player_name(repo_uri)
214
+ name = File.basename(repo_uri)
215
+ name = name[0...-4] if name[-4, 4] == '.git'
216
+ name
217
+ end
218
+
219
+ # Renames a player to match its path on the local system.
220
+ #
221
+ # Returns the path to the player's source package.
222
+ def self.rename(local_path, old_name)
223
+ new_name = File.basename local_path
224
+ return nil unless old_source_dir = package_path(local_path, old_name)
225
+ new_source_dir = File.join File.dirname(old_source_dir), new_name
226
+ FileUtils.mv old_source_dir, new_source_dir
227
+
228
+ Dir.glob(File.join(new_source_dir, '**', '*.java')).each do |file|
229
+ contents = File.read file
230
+ contents.gsub! /(^|[^A-Za-z0-9_.])#{old_name}([^A-Za-z0-9_]|$)/, "\\1#{new_name}\\2"
231
+ File.open(file, 'wb') { |f| f.write contents }
232
+ end
233
+ new_source_dir
234
+ end
235
+
236
+ # Extracts the path to a player's source package given their repository.
237
+ #
238
+ # Args:
239
+ # local_path:: path to the player's git repository on the local machine
240
+ # name_override:: (optional) supplies the player name; if not set, the name is extracted from
241
+ # the path, by convention
242
+ # silent:: if set, won't output errors that help the user debug the problem
243
+ def self.package_path(local_path, name_override = nil, silent = false)
244
+ # All the packages should be in the 'src' directory.
245
+ source_dir = File.join local_path, 'src'
246
+ unless File.exist? source_dir
247
+ puts "Missing src directory" unless silent
248
+ return nil
249
+ end
250
+
251
+ # Ignore maintainance files/folder such as .gitignore / .svn.
252
+ package_dirs = Dir.glob(File.join(source_dir, '*')).
253
+ reject { |path| File.basename(path)[0, 1] == '.' }
254
+ unless package_dirs.length == 1
255
+ puts "src directory doesn't contain exactly one package directory!" unless silent
256
+ return nil
257
+ end
258
+
259
+ path = package_dirs.first
260
+ unless (name_override || File.basename(local_path)) == File.basename(path)
261
+ puts "The package in the src directory doesn't match the player name!" unless silent
262
+ return nil
263
+ end
264
+ path
265
+ end
266
+
267
+ # The contents of an Ant configuration file (build.xml) pointing to a simulator config file.
268
+ def self.ant_config(simulator_config)
269
+ contents = File.read Bcpm::Dist.ant_file
270
+ # Point to the distribution instead of current root.
271
+ contents.gsub! 'basedir="."', 'basedir="' + Bcpm::Dist.dist_path + '"'
272
+ contents.gsub! '<property name="path.base" location="."',
273
+ '<property name="path.base" location="' + Bcpm::Dist.dist_path + '"'
274
+ # Set VM memory.
275
+ contents.gsub! /\<jvmarg\s+value\=\"\-Xmx.*\"/,
276
+ "<jvmarg value=\"-Xmx#{Bcpm::Match::vm_ram}m\""
277
+ # Replace hardcoded bc.conf reference.
278
+ contents.gsub! 'bc.conf', simulator_config
279
+ contents
280
+ end
281
+
282
+ # The contents of an Eclipse .classpath for a player project.
283
+ def self.eclipse_classpath(local_path)
284
+ jar_path = File.join Bcpm::Dist.dist_path, 'lib', 'battlecode-server.jar'
285
+
286
+ <<END_CONFIG
287
+ <?xml version="1.0" encoding="UTF-8"?>
288
+ <classpath>
289
+ <classpathentry kind="src" path="src"/>
290
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
291
+ <classpathentry kind="lib" path="#{jar_path}"/>
292
+ <classpathentry kind="output" path="bin"/>
293
+ </classpath>
294
+ END_CONFIG
295
+ end
296
+
297
+ # The contents of an Eclipse .project file for a player project.
298
+ def self.eclipse_project(local_path)
299
+ project_name = File.basename local_path
300
+
301
+ <<END_CONFIG
302
+ <?xml version="1.0" encoding="UTF-8"?>
303
+ <projectDescription>
304
+ <name>#{project_name}</name>
305
+ <comment></comment>
306
+ <projects>
307
+ </projects>
308
+ <buildSpec>
309
+ <buildCommand>
310
+ <name>org.eclipse.jdt.core.javabuilder</name>
311
+ <arguments>
312
+ </arguments>
313
+ </buildCommand>
314
+ </buildSpec>
315
+ <natures>
316
+ <nature>org.eclipse.jdt.core.javanature</nature>
317
+ </natures>
318
+ </projectDescription>
319
+ END_CONFIG
320
+ end
321
+
322
+ # Writes the built-in player template.
323
+ def self.write_template(local_path)
324
+ src_path = File.join local_path, 'src', 'template'
325
+ FileUtils.mkdir_p src_path
326
+ File.open File.join(src_path, 'RobotPlayer.java'), 'wb' do |f|
327
+ f.write <<END_SOURCE
328
+ package template;
329
+
330
+ import battlecode.common.RobotController;
331
+
332
+ public class RobotPlayer implements Runnable {
333
+ public static RobotController rc;
334
+
335
+ public RobotPlayer(RobotController controller) {
336
+ rc = controller;
337
+ }
338
+
339
+ public void run() {
340
+ while (true) {
341
+ rc.yield();
342
+ }
343
+ }
344
+ }
345
+ END_SOURCE
346
+ end
347
+
348
+ src_path = File.join local_path, 'src', 'template', 'test', 'players'
349
+ FileUtils.mkdir_p src_path
350
+ File.open File.join(src_path, 'RobotPlayer.java'), 'wb' do |f|
351
+ f.write <<END_SOURCE
352
+ package template.test.players;
353
+
354
+ import battlecode.common.RobotController;
355
+
356
+ public class RobotPlayer implements Runnable {
357
+ public static RobotController rc;
358
+
359
+ public RobotPlayer(RobotController controller) {
360
+ rc = controller;
361
+ }
362
+
363
+ public void run() {
364
+ while (true) {
365
+ rc.yield();
366
+ }
367
+ }
368
+ }
369
+ END_SOURCE
370
+ end
371
+
372
+ suite_path = File.join local_path, 'suite'
373
+ FileUtils.mkdir_p suite_path
374
+ File.open File.join(suite_path, 'win_vs_yield.rb'), 'wb' do |f|
375
+ f.write <<END_SOURCE
376
+ vs 'yield'
377
+ suite_map '#{Bcpm::Dist.maps.first}'
378
+ replace_class 'RobotPlayer', 'test.players.RobotPlayer'
379
+
380
+ match do
381
+ it 'must win in any way' do
382
+ should_win
383
+ end
384
+ end
385
+ END_SOURCE
386
+ end
387
+
388
+ maps_path = File.join suite_path, 'maps'
389
+ FileUtils.mkdir_p maps_path
390
+ Bcpm::Dist.copy_map Bcpm::Dist.maps.first, maps_path
391
+
392
+ File.open File.join(local_path, '.gitignore'), 'wb' do |f|
393
+ f.write <<END_SOURCE
394
+ # Auto-generated by bcpm.
395
+ build.xml
396
+ .classpath
397
+ .project
398
+
399
+ # Build output.
400
+ bin
401
+
402
+ # Temporary files.
403
+ ~*
404
+ .DS_Store
405
+ END_SOURCE
406
+ end
407
+ end
408
+ end # module Bcpm::Player
409
+
410
+ end # namespace Bcpm
@@ -0,0 +1,102 @@
1
+ # :nodoc: namespace
2
+ module Bcpm
3
+
4
+ # Source code auto-generation capabilities.
5
+ module Regen
6
+ # Re-generates automatically generated source code files.
7
+ def self.run(file_names)
8
+ source_lines = {}
9
+ vars = {}
10
+
11
+ # Read in source blocks.
12
+ file_names.each do |file_name|
13
+ lines = File.open(file_name, 'rb') { |f| f.read.split "\n" }
14
+
15
+ current_block = nil
16
+ lines.each do |line|
17
+ if current_block
18
+ if /^\s+\/\/\$\s+\-gen\:source(\s.*)?$/ =~ line
19
+ current_block = nil
20
+ else
21
+ source_lines[current_block] << line
22
+ end
23
+ else
24
+ block_match = /^\s+\/\/\$\s+\+gen\:source\s+(\S+)\s+(.*)$/.match line
25
+ if block_match
26
+ current_block = block_match[1]
27
+ if source_lines[current_block]
28
+ print "Duplicate source block #{current_block}\n"
29
+ exit 1
30
+ end
31
+ source_lines[current_block] ||= []
32
+ vars[current_block] = block_match[2].scan /\S+/
33
+ end
34
+ end
35
+ end
36
+ if current_block
37
+ print "Un-closed source block #{current_block}\n"
38
+ exit
39
+ end
40
+ end
41
+
42
+ # Replace target blocks.
43
+ file_names.each do |file_name|
44
+ lines = File.open(file_name, 'rb') { |f| f.read.split "\n" }
45
+ output_lines = []
46
+
47
+ current_block = nil
48
+ disabled = false
49
+ lines.each do |line|
50
+ if current_block
51
+ if /^\s+\/\/\$\s+\-gen\:target(\s.*)?$/ =~ line
52
+ output_lines << line
53
+ current_block = nil
54
+ end
55
+ else
56
+ block_match = /^\s+\/\/\$\s+\+gen\:target\s+(\S+)\s+(.*)$/.match line
57
+ output_lines << line
58
+
59
+ if block_match
60
+ current_block = block_match[1]
61
+ source_vars = vars[current_block]
62
+ if !source_vars
63
+ print "Missing source block #{current_block}\n"
64
+ exit 1
65
+ end
66
+ target_vars = block_match[2].scan /\S+/
67
+ if target_vars.length != source_vars.length
68
+ print "Source/target variable mismatch.\n"
69
+ print "Source: #{vars.join(' ')}\nTarget: #{vars.join(' ')}\n"
70
+ exit 1
71
+ end
72
+
73
+ source_target = Hash[source_vars.zip(target_vars)]
74
+ regexp = Regexp.new source_vars.map { |var| "(#{var})" }.join('|')
75
+ new_lines = source_lines[current_block].map &:dup
76
+ disabled = false
77
+ new_lines.each do |line|
78
+ if disabled
79
+ disabled = false if /^\s+\/\/\$\s+\-gen\:off(\s.*)?$/ =~ line
80
+ else
81
+ if /^\s+\/\/\$\s+\+gen\:off(\s.*)?$/ =~ line
82
+ disabled = true
83
+ else
84
+ line.gsub!(regexp) { |match| source_target[match] }
85
+ end
86
+ end
87
+ end
88
+ output_lines.concat new_lines
89
+ end
90
+ end
91
+ end
92
+ if current_block
93
+ print "Un-closed target block #{current_block}\n"
94
+ exit
95
+ end
96
+
97
+ File.open(file_name, 'wb') { |f| f.write output_lines.join("\n") }
98
+ end
99
+ end
100
+ end # module Bcpm::Regen
101
+
102
+ end # namespace Bcpm