bcpm 0.11

Sign up to get free protection for your applications and to get access to all the features.
@@ -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