rant 0.5.6 → 0.5.7
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.
- data/NEWS +12 -0
- data/README +3 -3
- data/Rantfile +7 -1
- data/doc/Untitled-1~ +7 -0
- data/doc/command.rdoc +1 -1
- data/doc/csharp.rdoc +49 -72
- data/doc/csharp.rdoc~ +37 -0
- data/doc/csharp_deprecated.rdoc +73 -0
- data/doc/homepage/index.html +5 -8
- data/doc/homepage/index.html~ +134 -0
- data/doc/sys.rdoc +14 -0
- data/lib/rant/coregen.rb~ +262 -0
- data/lib/rant/csharp/base_compiler_adapter.rb +125 -0
- data/lib/rant/csharp/base_compiler_adapter.rb~ +105 -0
- data/lib/rant/csharp/compiler_adapter_factory.rb +40 -0
- data/lib/rant/csharp/compiler_adapter_factory.rb~ +39 -0
- data/lib/rant/csharp/csc_compiler.rb +15 -0
- data/lib/rant/csharp/csc_compiler.rb~ +39 -0
- data/lib/rant/csharp/gmcs_compiler.rb +9 -0
- data/lib/rant/csharp/gmcs_compiler.rb~ +10 -0
- data/lib/rant/csharp/mcs_compiler.rb +13 -0
- data/lib/rant/csharp/mcs_compiler.rb~ +40 -0
- data/lib/rant/csharp/msc_compiler.rb~ +39 -0
- data/lib/rant/import/csharp.rb +48 -0
- data/lib/rant/import/csharp.rb~ +58 -0
- data/lib/rant/import/nunittest.rb +32 -0
- data/lib/rant/import/resgen.rb +21 -0
- data/lib/rant/import/resgen.rb~ +20 -0
- data/lib/rant/init.rb +1 -1
- data/lib/rant/rantlib.rb +1 -0
- data/lib/rant/rantlib.rb~ +1376 -0
- data/lib/rant/rantsys.rb +6 -0
- data/run_import +1 -1
- data/run_rant +1 -1
- data/test/import/package/test_package.rb~ +628 -0
- data/test/rule.rf +4 -0
- data/test/test_filetask.rb~ +97 -0
- data/test/test_rule.rb +10 -0
- data/test/test_sys_methods.rb +22 -0
- data/test/units/csharp/test_base_compiler_adapter.rb +107 -0
- data/test/units/csharp/test_base_compiler_adapter.rb~ +73 -0
- data/test/units/csharp/test_compiler_adapter_factory.rb +66 -0
- data/test/units/csharp/test_compiler_adapter_factory.rb~ +66 -0
- data/test/units/csharp/test_csc_compiler.rb +23 -0
- data/test/units/csharp/test_csc_compiler.rb~ +23 -0
- data/test/units/csharp/test_gmsc_compiler.rb +19 -0
- data/test/units/csharp/test_gmsc_compiler.rb~ +19 -0
- data/test/units/csharp/test_msc_compiler.rb +23 -0
- data/test/units/csharp/test_msc_compiler.rb~ +23 -0
- data/test/units/csharp_test_helper.rb +6 -0
- data/test/units/import/test_csharp.rb +127 -0
- data/test/units/import/test_csharp.rb~ +126 -0
- data/test/units/import/test_nunittest.rb +122 -0
- data/test/units/import/test_resgen.rb +107 -0
- data/test/units/import/test_resgen.rb~ +88 -0
- metadata +269 -224
data/lib/rant/rantlib.rb
CHANGED
@@ -452,6 +452,7 @@ class Rant::RantApp
|
|
452
452
|
sub.empty? ? @rootdir : File.join(@rootdir, sub)
|
453
453
|
end
|
454
454
|
def abs_path(subdir, fn)
|
455
|
+
return fn if Rant::Sys.absolute_path?(fn)
|
455
456
|
path = File.join(@rootdir, subdir, fn)
|
456
457
|
path.gsub!(%r{/+}, "/")
|
457
458
|
path.sub!(%r{/$}, "") if path.length > 1
|
@@ -0,0 +1,1376 @@
|
|
1
|
+
|
2
|
+
# rantlib.rb - The core of Rant.
|
3
|
+
#
|
4
|
+
# Copyright (C) 2005 Stefan Lang <langstefan@gmx.at>
|
5
|
+
#
|
6
|
+
# This program is free software.
|
7
|
+
# You can distribute/modify this program under the terms of
|
8
|
+
# the GNU LGPL, Lesser General Public License version 2.1.
|
9
|
+
|
10
|
+
require 'getoptlong'
|
11
|
+
require 'rant/init'
|
12
|
+
require 'rant/rantvar'
|
13
|
+
require 'rant/rantsys'
|
14
|
+
require 'rant/node'
|
15
|
+
require 'rant/import/nodes/default' # could be optimized away
|
16
|
+
require 'rant/coregen'
|
17
|
+
|
18
|
+
# There is one problem with executing Rantfiles in a special context:
|
19
|
+
# In the top-level execution environment, there are some methods
|
20
|
+
# available which are not available to all objects. One example is the
|
21
|
+
# +include+ method.
|
22
|
+
#
|
23
|
+
# To (at least partially) solve this problem, we capture the `main'
|
24
|
+
# object here and delegate methods from RantContext#method_missing to
|
25
|
+
# this object.
|
26
|
+
Rant::MAIN_OBJECT = self
|
27
|
+
|
28
|
+
class String
|
29
|
+
alias sub_ext _rant_sub_ext
|
30
|
+
def to_rant_target
|
31
|
+
self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# This module and its methods don't belong to Rant's public API.
|
36
|
+
# For (Rant) internal usage only!
|
37
|
+
module Rant::Lib
|
38
|
+
# Parses one string (elem) as it occurs in the array
|
39
|
+
# which is returned by caller.
|
40
|
+
# E.g.:
|
41
|
+
# p parse_caller_elem "/usr/local/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'"
|
42
|
+
# prints:
|
43
|
+
# {:ln=>52, :file=>"/usr/local/lib/ruby/1.8/irb/workspace.rb"}
|
44
|
+
#
|
45
|
+
# Note: This method splits on the pattern <tt>:(\d+)(:|$)</tt>,
|
46
|
+
# assuming anything before is the filename.
|
47
|
+
def parse_caller_elem(elem)
|
48
|
+
return { :file => "", :ln => 0 } unless elem
|
49
|
+
if elem =~ /^(.+):(\d+)(?::|$)/
|
50
|
+
{ :file => $1, :ln => $2.to_i }
|
51
|
+
else
|
52
|
+
# should never occur
|
53
|
+
$stderr.puts "parse_caller_elem: #{elem.inspect}"
|
54
|
+
{ :file => elem, :ln => 0 }
|
55
|
+
end
|
56
|
+
|
57
|
+
#parts = elem.split(":")
|
58
|
+
#{ :file => parts[0], :ln => parts[1].to_i }
|
59
|
+
end
|
60
|
+
module_function :parse_caller_elem
|
61
|
+
end # module Lib
|
62
|
+
|
63
|
+
module Rant::Console
|
64
|
+
RANT_PREFIX = "rant: "
|
65
|
+
ERROR_PREFIX = "[ERROR] "
|
66
|
+
WARN_PREFIX = "[WARNING] "
|
67
|
+
def msg_prefix
|
68
|
+
if defined? @msg_prefix and @msg_prefix
|
69
|
+
@msg_prefix
|
70
|
+
else
|
71
|
+
RANT_PREFIX
|
72
|
+
end
|
73
|
+
end
|
74
|
+
def msg(*text)
|
75
|
+
pre = msg_prefix
|
76
|
+
$stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
|
77
|
+
end
|
78
|
+
def vmsg(importance, *text)
|
79
|
+
msg(*text) if verbose >= importance
|
80
|
+
end
|
81
|
+
def err_msg(*text)
|
82
|
+
pre = msg_prefix + ERROR_PREFIX
|
83
|
+
$stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
|
84
|
+
end
|
85
|
+
def warn_msg(*text)
|
86
|
+
pre = msg_prefix + WARN_PREFIX
|
87
|
+
$stderr.puts "#{pre}#{text.join("\n" + ' ' * pre.length)}"
|
88
|
+
end
|
89
|
+
def ask_yes_no text
|
90
|
+
$stderr.print msg_prefix + text + " [y|n] "
|
91
|
+
case $stdin.readline
|
92
|
+
when /y|yes/i: true
|
93
|
+
when /n|no/i: false
|
94
|
+
else
|
95
|
+
$stderr.puts(' ' * msg_prefix.length +
|
96
|
+
"Please answer with `yes' or `no'")
|
97
|
+
ask_yes_no text
|
98
|
+
end
|
99
|
+
end
|
100
|
+
def prompt text
|
101
|
+
$stderr.print msg_prefix + text
|
102
|
+
input = $stdin.readline
|
103
|
+
input ? input.chomp : input
|
104
|
+
end
|
105
|
+
def option_listing opts
|
106
|
+
rs = ""
|
107
|
+
opts.each { |lopt, *opt_a|
|
108
|
+
if opt_a.size == 2
|
109
|
+
# no short option
|
110
|
+
mode, desc = opt_a
|
111
|
+
else
|
112
|
+
sopt, mode, desc = opt_a
|
113
|
+
end
|
114
|
+
next unless desc # "private" option
|
115
|
+
optstr = ""
|
116
|
+
arg = nil
|
117
|
+
if mode != GetoptLong::NO_ARGUMENT
|
118
|
+
if desc =~ /(\b[A-Z_]{2,}\b)/
|
119
|
+
arg = $1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
if lopt
|
123
|
+
optstr << lopt
|
124
|
+
if arg
|
125
|
+
optstr << " " << arg
|
126
|
+
end
|
127
|
+
optstr = optstr.ljust(30)
|
128
|
+
end
|
129
|
+
if sopt
|
130
|
+
optstr << " " unless optstr.empty?
|
131
|
+
optstr << sopt
|
132
|
+
if arg
|
133
|
+
optstr << " " << arg
|
134
|
+
end
|
135
|
+
end
|
136
|
+
rs << " #{optstr}\n"
|
137
|
+
rs << " #{desc.split("\n").join("\n ")}\n"
|
138
|
+
}
|
139
|
+
rs
|
140
|
+
end
|
141
|
+
extend self
|
142
|
+
end # module Rant::Console
|
143
|
+
|
144
|
+
# The methods in this module are the public interface to Rant that can
|
145
|
+
# be used in Rantfiles.
|
146
|
+
module RantContext
|
147
|
+
include Rant::Generators
|
148
|
+
|
149
|
+
Env = Rant::Env
|
150
|
+
FileList = Rant::FileList
|
151
|
+
|
152
|
+
# Define a basic task.
|
153
|
+
def task(targ, &block)
|
154
|
+
rant.task(targ, &block)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Define a file task.
|
158
|
+
def file(targ, &block)
|
159
|
+
rant.file(targ, &block)
|
160
|
+
end
|
161
|
+
|
162
|
+
# Add code and/or prerequisites to existing task.
|
163
|
+
def enhance(targ, &block)
|
164
|
+
rant.enhance(targ, &block)
|
165
|
+
end
|
166
|
+
|
167
|
+
def desc(*args)
|
168
|
+
rant.desc(*args)
|
169
|
+
end
|
170
|
+
|
171
|
+
def gen(*args, &block)
|
172
|
+
rant.gen(*args, &block)
|
173
|
+
end
|
174
|
+
|
175
|
+
def import(*args, &block)
|
176
|
+
rant.import(*args, &block)
|
177
|
+
end
|
178
|
+
|
179
|
+
def plugin(*args, &block)
|
180
|
+
rant.plugin(*args, &block)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Look in the subdirectories, given by args,
|
184
|
+
# for rantfiles.
|
185
|
+
def subdirs(*args)
|
186
|
+
rant.subdirs(*args)
|
187
|
+
end
|
188
|
+
|
189
|
+
def source(opt, rantfile = nil)
|
190
|
+
rant.source(opt, rantfile)
|
191
|
+
end
|
192
|
+
|
193
|
+
def sys(*args, &block)
|
194
|
+
rant.sys(*args, &block)
|
195
|
+
end
|
196
|
+
|
197
|
+
def var(*args, &block)
|
198
|
+
rant.var(*args, &block)
|
199
|
+
end
|
200
|
+
|
201
|
+
def make(*args, &block)
|
202
|
+
rant.make(*args, &block)
|
203
|
+
end
|
204
|
+
|
205
|
+
=begin
|
206
|
+
# +rac+ stands for "rant compiler"
|
207
|
+
def rac
|
208
|
+
ch = Rant::Lib.parse_caller_elem caller[0]
|
209
|
+
rant.warn_msg(rant.pos_text(ch[:file], ch[:ln]),
|
210
|
+
"Method `rac' is deprecated. Use `rant' instead.")
|
211
|
+
rant
|
212
|
+
end
|
213
|
+
=end
|
214
|
+
end # module RantContext
|
215
|
+
|
216
|
+
class RantAppContext
|
217
|
+
include RantContext
|
218
|
+
|
219
|
+
def initialize(app)
|
220
|
+
@__rant__ = app
|
221
|
+
end
|
222
|
+
|
223
|
+
def rant
|
224
|
+
@__rant__
|
225
|
+
end
|
226
|
+
|
227
|
+
def method_missing(sym, *args)
|
228
|
+
# See the documentation for Rant::MAIN_OBJECT why we're doing
|
229
|
+
# this...
|
230
|
+
# Note also that the +send+ method also invokes private
|
231
|
+
# methods, this is very important for our intent.
|
232
|
+
Rant::MAIN_OBJECT.send(sym, *args)
|
233
|
+
rescue NoMethodError
|
234
|
+
raise NameError, "NameError: undefined local " +
|
235
|
+
"variable or method `#{sym}' for main:Object", caller
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
module Rant
|
240
|
+
|
241
|
+
@__rant__ = nil
|
242
|
+
class << self
|
243
|
+
|
244
|
+
# Run a new rant application in the current working directory.
|
245
|
+
# This has the same effect as running +rant+ from the
|
246
|
+
# commandline. You can give arguments as you would give them
|
247
|
+
# on the commandline. If no argument is given, ARGV will be
|
248
|
+
# used.
|
249
|
+
#
|
250
|
+
# This method returns 0 if the rant application was
|
251
|
+
# successfull and 1 on failure. So if you need your own rant
|
252
|
+
# startscript, it could look like:
|
253
|
+
#
|
254
|
+
# exit Rant.run
|
255
|
+
#
|
256
|
+
# This runs rant in the current directory, using the arguments
|
257
|
+
# given to your script and the exit code as suggested by the
|
258
|
+
# rant application.
|
259
|
+
#
|
260
|
+
# Or if you want rant to always be quiet with this script,
|
261
|
+
# use:
|
262
|
+
#
|
263
|
+
# exit Rant.run("--quiet", ARGV)
|
264
|
+
#
|
265
|
+
# Of course, you can invoke rant directly at the bottom of
|
266
|
+
# your rantfile, so you can run it directly with ruby.
|
267
|
+
def run(first_arg=nil, *other_args)
|
268
|
+
other_args = other_args.flatten
|
269
|
+
args = first_arg.nil? ? ARGV.dup : ([first_arg] + other_args)
|
270
|
+
if rant && !rant.run?
|
271
|
+
rant.run(args.flatten)
|
272
|
+
else
|
273
|
+
@__rant__ = Rant::RantApp.new
|
274
|
+
rant.run(args)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def rant
|
279
|
+
@__rant__
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end # module Rant
|
284
|
+
|
285
|
+
class Rant::RantApp
|
286
|
+
include Rant::Console
|
287
|
+
|
288
|
+
class AutoLoadNodeFactory
|
289
|
+
def initialize(rant)
|
290
|
+
@rant = rant
|
291
|
+
end
|
292
|
+
def method_missing(sym, *args, &block)
|
293
|
+
@rant.import "nodes/default"
|
294
|
+
@rant.node_factory.send(sym, *args, &block)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
# Important: We try to synchronize all tasks referenced indirectly
|
299
|
+
# by @rantfiles with the task hash @tasks. The task hash is
|
300
|
+
# intended for fast task lookup per task name.
|
301
|
+
#
|
302
|
+
# All tasks are registered to the system by the +prepare_task+
|
303
|
+
# method.
|
304
|
+
|
305
|
+
# The RantApp class has no own state.
|
306
|
+
|
307
|
+
OPTIONS = [
|
308
|
+
[ "--help", "-h", GetoptLong::NO_ARGUMENT,
|
309
|
+
"Print this help and exit." ],
|
310
|
+
[ "--version", "-V", GetoptLong::NO_ARGUMENT,
|
311
|
+
"Print version of Rant and exit." ],
|
312
|
+
[ "--verbose", "-v", GetoptLong::NO_ARGUMENT,
|
313
|
+
"Print more messages to stderr." ],
|
314
|
+
[ "--quiet", "-q", GetoptLong::NO_ARGUMENT,
|
315
|
+
"Don't print commands." ],
|
316
|
+
[ "--err-commands", GetoptLong::NO_ARGUMENT,
|
317
|
+
"Print failed commands and their exit status." ],
|
318
|
+
[ "--directory","-C", GetoptLong::REQUIRED_ARGUMENT,
|
319
|
+
"Run rant in DIRECTORY." ],
|
320
|
+
[ "--cd-parent","-c", GetoptLong::NO_ARGUMENT,
|
321
|
+
"Run rant in parent directory with Rantfile." ],
|
322
|
+
[ "--look-up", "-u", GetoptLong::NO_ARGUMENT,
|
323
|
+
"Look in parent directories for root Rantfile." ],
|
324
|
+
[ "--rantfile", "-f", GetoptLong::REQUIRED_ARGUMENT,
|
325
|
+
"Process RANTFILE instead of standard rantfiles.\n" +
|
326
|
+
"Multiple files may be specified with this option." ],
|
327
|
+
[ "--force-run","-a", GetoptLong::REQUIRED_ARGUMENT,
|
328
|
+
"Force rebuild of TARGET and all dependencies." ],
|
329
|
+
[ "--dry-run", "-n", GetoptLong::NO_ARGUMENT,
|
330
|
+
"Print info instead of actually executing actions." ],
|
331
|
+
[ "--tasks", "-T", GetoptLong::NO_ARGUMENT,
|
332
|
+
"Show a list of all described tasks and exit." ],
|
333
|
+
|
334
|
+
# "private" options intended for debugging, testing and
|
335
|
+
# internal use. A private option is distuingished from others
|
336
|
+
# by having +nil+ as description!
|
337
|
+
|
338
|
+
[ "--import", "-i", GetoptLong::REQUIRED_ARGUMENT, nil ],
|
339
|
+
[ "--stop-after-load", GetoptLong::NO_ARGUMENT, nil ],
|
340
|
+
# Print caller to $stderr on abort.
|
341
|
+
[ "--trace-abort", GetoptLong::NO_ARGUMENT, nil ],
|
342
|
+
]
|
343
|
+
|
344
|
+
# Reference project's root directory in task names by preceding
|
345
|
+
# them with this character.
|
346
|
+
ROOT_DIR_ID = "@"
|
347
|
+
ESCAPE_ID = "\\"
|
348
|
+
|
349
|
+
# Arguments, usually those given on commandline.
|
350
|
+
attr_reader :args
|
351
|
+
# A list of all Rantfiles used by this app.
|
352
|
+
attr_reader :rantfiles
|
353
|
+
# A list of target names to be forced (run even
|
354
|
+
# if not required). Each of these targets will be removed
|
355
|
+
# from this list after the first run.
|
356
|
+
#
|
357
|
+
# Forced targets will be run before other targets.
|
358
|
+
attr_reader :force_targets
|
359
|
+
# A list of all registered plugins.
|
360
|
+
attr_reader :plugins
|
361
|
+
# The context in which Rantfiles are loaded. RantContext methods
|
362
|
+
# may be called through this object (e.g. from plugins).
|
363
|
+
attr_reader :context
|
364
|
+
alias cx context
|
365
|
+
# A hash with all tasks. For fast task lookup use this hash with
|
366
|
+
# the taskname as key.
|
367
|
+
#
|
368
|
+
# See also: #resolve, #make
|
369
|
+
attr_reader :tasks
|
370
|
+
# A list of all imports (code loaded with +import+).
|
371
|
+
attr_reader :imports
|
372
|
+
# Current subdirectory relative to project's root directory
|
373
|
+
# (#rootdir).
|
374
|
+
attr_reader :current_subdir
|
375
|
+
# List of proc objects used to automatically create required
|
376
|
+
# tasks. (Especially used for Rules.)
|
377
|
+
#
|
378
|
+
# Note: Might change before 1.0
|
379
|
+
attr_reader :resolve_hooks
|
380
|
+
# Root directory of project. Will be initialized to working
|
381
|
+
# directory in #initialize. This is always an absolute path
|
382
|
+
# beginning with a <tt>/</tt> and not ending in a slash (unless
|
383
|
+
# rootdir is <tt>/</tt>).
|
384
|
+
attr_reader :rootdir
|
385
|
+
|
386
|
+
attr_accessor :node_factory
|
387
|
+
|
388
|
+
def initialize
|
389
|
+
@args = []
|
390
|
+
# Rantfiles will be loaded in the context of this object.
|
391
|
+
@context = RantAppContext.new(self)
|
392
|
+
@sys = ::Rant::SysObject.new(self)
|
393
|
+
@rantfiles = []
|
394
|
+
@tasks = {}
|
395
|
+
@opts = {
|
396
|
+
:verbose => 0,
|
397
|
+
:quiet => false,
|
398
|
+
}
|
399
|
+
@rootdir = Dir.pwd # root directory of project
|
400
|
+
@arg_rantfiles = [] # rantfiles given in args
|
401
|
+
@arg_targets = [] # targets given in args
|
402
|
+
@force_targets = [] # targets given with -a option
|
403
|
+
@run = false # run method was called at least once
|
404
|
+
@done = false # run method was successful
|
405
|
+
@plugins = []
|
406
|
+
@var = Rant::RantVar::Space.new
|
407
|
+
@var.query :ignore, :AutoList, []
|
408
|
+
@imports = []
|
409
|
+
|
410
|
+
@task_desc = nil
|
411
|
+
@last_build_subdir = ""
|
412
|
+
|
413
|
+
@current_subdir = ""
|
414
|
+
@resolve_hooks = []
|
415
|
+
|
416
|
+
@node_factory = AutoLoadNodeFactory.new(self)
|
417
|
+
end
|
418
|
+
|
419
|
+
def [](opt)
|
420
|
+
@opts[opt]
|
421
|
+
end
|
422
|
+
|
423
|
+
def []=(opt, val)
|
424
|
+
@opts[opt] = val
|
425
|
+
end
|
426
|
+
|
427
|
+
### support for subdirectories ###################################
|
428
|
+
def expand_path(subdir, path)
|
429
|
+
case path
|
430
|
+
when nil: subdir.dup
|
431
|
+
when "": subdir.dup
|
432
|
+
when /^@/: path.sub(/^@/, '')
|
433
|
+
else
|
434
|
+
path = path.sub(/^\\(?=@)/, '')
|
435
|
+
if subdir.empty?
|
436
|
+
# we are in project's root directory
|
437
|
+
path
|
438
|
+
else
|
439
|
+
File.join(subdir, path)
|
440
|
+
end
|
441
|
+
end
|
442
|
+
end
|
443
|
+
def resolve_root_ref(path)
|
444
|
+
return File.join(@rootdir, path[1..-1]) if path =~ /^@/
|
445
|
+
path.sub(/^\\(?=@)/, '')
|
446
|
+
end
|
447
|
+
# Returns an absolute path. If path resolves to a directory this
|
448
|
+
# method ensures that the returned absolute path doesn't end in a
|
449
|
+
# slash.
|
450
|
+
def project_to_fs_path(path)
|
451
|
+
sub = expand_path(@current_subdir, path)
|
452
|
+
sub.empty? ? @rootdir : File.join(@rootdir, sub)
|
453
|
+
end
|
454
|
+
def abs_path(subdir, fn)
|
455
|
+
return fn if Rant::Sys.absolute_path?(fn)
|
456
|
+
path = File.join(@rootdir, subdir, fn)
|
457
|
+
path.gsub!(%r{/+}, "/")
|
458
|
+
path.sub!(%r{/$}, "") if path.length > 1
|
459
|
+
path
|
460
|
+
end
|
461
|
+
def goto(dir)
|
462
|
+
goto_project_dir(expand_path(@current_subdir, dir))
|
463
|
+
end
|
464
|
+
# +dir+ is a path relative to +rootdir+. It has to be a "clean"
|
465
|
+
# path string, i.e. it mustn't start with <tt>./</tt>, contain any
|
466
|
+
# <tt>..</tt> parent reference and it mustn't have a trailing
|
467
|
+
# slash.
|
468
|
+
#
|
469
|
+
# To go to the root directory, dir has to be an empty string,
|
470
|
+
# which is the default value.
|
471
|
+
def goto_project_dir(dir='')
|
472
|
+
@current_subdir = dir
|
473
|
+
abs_path = @current_subdir.empty? ?
|
474
|
+
@rootdir : File.join(@rootdir, @current_subdir)
|
475
|
+
unless Dir.pwd == abs_path
|
476
|
+
Dir.chdir abs_path
|
477
|
+
vmsg 1, "in #{abs_path}"
|
478
|
+
end
|
479
|
+
end
|
480
|
+
##################################################################
|
481
|
+
|
482
|
+
def run?
|
483
|
+
@run
|
484
|
+
end
|
485
|
+
|
486
|
+
def done?
|
487
|
+
@done
|
488
|
+
end
|
489
|
+
|
490
|
+
# Run this Rant application with the given arguments. The process
|
491
|
+
# working directory after this method returns, will be the same as
|
492
|
+
# before invocation.
|
493
|
+
#
|
494
|
+
# Returns 0 on success and 1 on failure.
|
495
|
+
def run(*args)
|
496
|
+
@run = true
|
497
|
+
@args.concat(args.flatten)
|
498
|
+
# remind pwd
|
499
|
+
orig_pwd = @rootdir = Dir.pwd
|
500
|
+
# Process commandline.
|
501
|
+
process_args
|
502
|
+
Dir.chdir(@rootdir) rescue abort $!.message
|
503
|
+
# read rantfiles, might change @rootdir and Dir.pwd
|
504
|
+
load_rantfiles
|
505
|
+
|
506
|
+
raise Rant::RantDoneException if @opts[:stop_after_load]
|
507
|
+
|
508
|
+
# Notify plugins before running tasks
|
509
|
+
@plugins.each { |plugin| plugin.rant_start }
|
510
|
+
if @opts[:tasks]
|
511
|
+
show_descriptions
|
512
|
+
raise Rant::RantDoneException
|
513
|
+
end
|
514
|
+
# run tasks
|
515
|
+
run_tasks
|
516
|
+
raise Rant::RantDoneException
|
517
|
+
rescue Rant::RantDoneException
|
518
|
+
@done = true
|
519
|
+
# Notify plugins
|
520
|
+
@plugins.each { |plugin| plugin.rant_done }
|
521
|
+
return 0
|
522
|
+
rescue Rant::RantAbortException
|
523
|
+
$stderr.puts "rant aborted!"
|
524
|
+
return 1
|
525
|
+
rescue Exception => e
|
526
|
+
ch = get_ch_from_backtrace(e.backtrace)
|
527
|
+
if ch && !@opts[:trace_abort]
|
528
|
+
err_msg(pos_text(ch[:file], ch[:ln]), e.message)
|
529
|
+
else
|
530
|
+
err_msg e.message, e.backtrace[0..4]
|
531
|
+
end
|
532
|
+
$stderr.puts "rant aborted!"
|
533
|
+
return 1
|
534
|
+
ensure
|
535
|
+
# TODO: exception handling!
|
536
|
+
Dir.chdir @rootdir if test ?d, @rootdir
|
537
|
+
hooks = var._get("__at_return__")
|
538
|
+
hooks.each { |hook| hook.call } if hooks
|
539
|
+
@plugins.each { |plugin| plugin.rant_plugin_stop }
|
540
|
+
@plugins.each { |plugin| plugin.rant_quit }
|
541
|
+
# restore pwd
|
542
|
+
Dir.chdir orig_pwd
|
543
|
+
end
|
544
|
+
|
545
|
+
###### methods accessible through RantContext ####################
|
546
|
+
|
547
|
+
def desc(*args)
|
548
|
+
if args.empty? || (args.size == 1 && args.first.nil?)
|
549
|
+
@task_desc = nil
|
550
|
+
else
|
551
|
+
@task_desc = args.join("\n")
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
def task(targ, &block)
|
556
|
+
prepare_task(targ, block) { |name,pre,blk|
|
557
|
+
@node_factory.new_task(self, name, pre, blk)
|
558
|
+
}
|
559
|
+
end
|
560
|
+
|
561
|
+
def file(targ, &block)
|
562
|
+
prepare_task(targ, block) { |name,pre,blk|
|
563
|
+
@node_factory.new_file(self, name, pre, blk)
|
564
|
+
}
|
565
|
+
end
|
566
|
+
|
567
|
+
def gen(*args, &block)
|
568
|
+
# retrieve caller info
|
569
|
+
ch = Rant::Lib::parse_caller_elem(caller[1])
|
570
|
+
# validate args
|
571
|
+
generator = args.shift
|
572
|
+
unless generator.respond_to? :rant_gen
|
573
|
+
abort_at(ch,
|
574
|
+
"gen: First argument has to be a task-generator.")
|
575
|
+
end
|
576
|
+
# ask generator to produce a task for this application
|
577
|
+
generator.rant_gen(self, ch, args, &block)
|
578
|
+
end
|
579
|
+
|
580
|
+
# Currently ignores block.
|
581
|
+
def import(*args, &block)
|
582
|
+
ch = Rant::Lib::parse_caller_elem(caller[1])
|
583
|
+
if block
|
584
|
+
warn_msg pos_text(ch[:file], ch[:ln]),
|
585
|
+
"import: ignoring block"
|
586
|
+
end
|
587
|
+
args.flatten.each { |arg|
|
588
|
+
unless String === arg
|
589
|
+
abort_at(ch, "import: only strings allowed as arguments")
|
590
|
+
end
|
591
|
+
unless @imports.include? arg
|
592
|
+
unless Rant::CODE_IMPORTS.include? arg
|
593
|
+
begin
|
594
|
+
vmsg 2, "import #{arg}"
|
595
|
+
require "rant/import/#{arg}"
|
596
|
+
rescue LoadError => e
|
597
|
+
abort_at(ch, "No such import - #{arg}")
|
598
|
+
end
|
599
|
+
Rant::CODE_IMPORTS << arg.dup
|
600
|
+
end
|
601
|
+
init_msg = "init_import_#{arg.gsub(/[^\w]/, '__')}"
|
602
|
+
Rant.send init_msg, self if Rant.respond_to? init_msg
|
603
|
+
@imports << arg.dup
|
604
|
+
end
|
605
|
+
}
|
606
|
+
end
|
607
|
+
|
608
|
+
def plugin(*args, &block)
|
609
|
+
# retrieve caller info
|
610
|
+
clr = caller[1]
|
611
|
+
ch = Rant::Lib::parse_caller_elem(clr)
|
612
|
+
name = nil
|
613
|
+
pre = []
|
614
|
+
ln = ch[:ln] || 0
|
615
|
+
file = ch[:file]
|
616
|
+
|
617
|
+
pl_name = args.shift
|
618
|
+
pl_name = pl_name.to_str if pl_name.respond_to? :to_str
|
619
|
+
pl_name = pl_name.to_s if pl_name.is_a? Symbol
|
620
|
+
unless pl_name.is_a? String
|
621
|
+
abort(pos_text(file, ln),
|
622
|
+
"Plugin name has to be a string or symbol.")
|
623
|
+
end
|
624
|
+
lc_pl_name = pl_name.downcase
|
625
|
+
import_name = "plugin/#{lc_pl_name}"
|
626
|
+
unless Rant::CODE_IMPORTS.include? import_name
|
627
|
+
begin
|
628
|
+
require "rant/plugin/#{lc_pl_name}"
|
629
|
+
Rant::CODE_IMPORTS << import_name
|
630
|
+
rescue LoadError
|
631
|
+
abort(pos_text(file, ln),
|
632
|
+
"no such plugin library -- #{lc_pl_name}")
|
633
|
+
end
|
634
|
+
end
|
635
|
+
pl_class = nil
|
636
|
+
begin
|
637
|
+
pl_class = ::Rant::Plugin.const_get(pl_name)
|
638
|
+
rescue NameError, ArgumentError
|
639
|
+
abort(pos_text(file, ln),
|
640
|
+
"no such plugin -- #{pl_name}")
|
641
|
+
end
|
642
|
+
|
643
|
+
plugin = pl_class.rant_plugin_new(self, ch, *args, &block)
|
644
|
+
# TODO: check for rant_plugin?
|
645
|
+
@plugins << plugin
|
646
|
+
vmsg 2, "Plugin `#{plugin.rant_plugin_name}' registered."
|
647
|
+
plugin.rant_plugin_init
|
648
|
+
# return plugin instance
|
649
|
+
plugin
|
650
|
+
end
|
651
|
+
|
652
|
+
# Add block and prerequisites to the task specified by the
|
653
|
+
# name given as only key in targ.
|
654
|
+
# If there is no task with the given name, generate a warning
|
655
|
+
# and a new file task.
|
656
|
+
def enhance(targ, &block)
|
657
|
+
prepare_task(targ, block) { |name,pre,blk|
|
658
|
+
t = resolve(name).last
|
659
|
+
if t
|
660
|
+
unless t.respond_to? :enhance
|
661
|
+
abort("Can't enhance task `#{name}'")
|
662
|
+
end
|
663
|
+
t.enhance(pre, &blk)
|
664
|
+
# Important: return from method, don't break to
|
665
|
+
# prepare_task which would add task t again
|
666
|
+
return t
|
667
|
+
end
|
668
|
+
warn_msg "enhance \"#{name}\": no such task",
|
669
|
+
"Generating a new file task with the given name."
|
670
|
+
@node_factory.new_file(self, name, pre, blk)
|
671
|
+
}
|
672
|
+
end
|
673
|
+
|
674
|
+
# Returns the value of the last expression executed in +rantfile+.
|
675
|
+
def source(opt, rantfile = nil)
|
676
|
+
unless rantfile
|
677
|
+
rantfile = opt
|
678
|
+
opt = nil
|
679
|
+
end
|
680
|
+
make_rf = opt != :n && opt != :now
|
681
|
+
rf, is_new = rantfile_for_path(rantfile)
|
682
|
+
return false unless is_new
|
683
|
+
make rantfile if make_rf
|
684
|
+
unless File.exist? rf.path
|
685
|
+
abort("source: No such file -- #{rantfile}")
|
686
|
+
end
|
687
|
+
|
688
|
+
load_file rf
|
689
|
+
end
|
690
|
+
|
691
|
+
# Search the given directories for Rantfiles.
|
692
|
+
def subdirs(*args)
|
693
|
+
args.flatten!
|
694
|
+
ch = Rant::Lib::parse_caller_elem(caller[1])
|
695
|
+
args.each { |arg|
|
696
|
+
if arg.respond_to? :to_str
|
697
|
+
arg = arg.to_str
|
698
|
+
else
|
699
|
+
abort_at(ch, "subdirs: arguments must be strings")
|
700
|
+
end
|
701
|
+
loaded = false
|
702
|
+
prev_subdir = @current_subdir
|
703
|
+
begin
|
704
|
+
#puts "* subdir *",
|
705
|
+
# " rootdir: #{rootdir}",
|
706
|
+
# " current subdir: #@current_subdir",
|
707
|
+
# " pwd: #{Dir.pwd}",
|
708
|
+
# " arg: #{arg}"
|
709
|
+
goto arg
|
710
|
+
if test(?f, Rant::SUB_RANTFILE)
|
711
|
+
path = Rant::SUB_RANTFILE
|
712
|
+
else
|
713
|
+
path = rantfile_in_dir
|
714
|
+
end
|
715
|
+
if path
|
716
|
+
if defined? @initial_subdir and
|
717
|
+
@initial_subdir == @current_subdir
|
718
|
+
rf, is_new = rantfile_for_path(path, false)
|
719
|
+
@rantfiles.unshift rf if is_new
|
720
|
+
else
|
721
|
+
rf, is_new = rantfile_for_path(path)
|
722
|
+
end
|
723
|
+
load_file rf if is_new
|
724
|
+
elsif !@opts[:no_warn_subdir]
|
725
|
+
warn_msg(pos_text(ch[:file], ch[:ln]),
|
726
|
+
"subdirs: No Rantfile in subdir `#{arg}'.")
|
727
|
+
end
|
728
|
+
ensure
|
729
|
+
#puts " going back to project dir: #{prev_subdir}"
|
730
|
+
goto_project_dir prev_subdir
|
731
|
+
end
|
732
|
+
}
|
733
|
+
rescue SystemCallError => e
|
734
|
+
abort_at(ch, "subdirs: " + e.message)
|
735
|
+
end
|
736
|
+
|
737
|
+
def sys(*args, &block)
|
738
|
+
args.empty? ? @sys : @sys.sh(*args, &block)
|
739
|
+
end
|
740
|
+
|
741
|
+
# The [] and []= operators may be used to set/get values from this
|
742
|
+
# object (like a hash). It is intended to let the different
|
743
|
+
# modules, plugins and tasks to communicate with each other.
|
744
|
+
def var(*args, &block)
|
745
|
+
args.empty? ? @var : @var.query(*args, &block)
|
746
|
+
end
|
747
|
+
##################################################################
|
748
|
+
|
749
|
+
# Pop (remove and return) current pending task description.
|
750
|
+
def pop_desc
|
751
|
+
td = @task_desc
|
752
|
+
@task_desc = nil
|
753
|
+
td
|
754
|
+
end
|
755
|
+
|
756
|
+
# Prints msg as error message and raises an RantAbortException.
|
757
|
+
def abort(*msg)
|
758
|
+
err_msg(msg) unless msg.empty?
|
759
|
+
$stderr.puts caller if @opts[:trace_abort]
|
760
|
+
raise Rant::RantAbortException
|
761
|
+
end
|
762
|
+
|
763
|
+
def abort_at(ch, *msg)
|
764
|
+
err_msg(pos_text(ch[:file], ch[:ln]), msg)
|
765
|
+
$stderr.puts caller if @opts[:trace_abort]
|
766
|
+
raise Rant::RantAbortException
|
767
|
+
end
|
768
|
+
|
769
|
+
def show_help
|
770
|
+
puts "rant [-f Rantfile] [Options] [targets]"
|
771
|
+
puts
|
772
|
+
puts "Options are:"
|
773
|
+
print option_listing(OPTIONS)
|
774
|
+
end
|
775
|
+
|
776
|
+
def show_descriptions
|
777
|
+
tlist = select_tasks { |t| t.description }
|
778
|
+
# +target_list+ aborts if no task defined, so we can be sure
|
779
|
+
# that +default+ is not nil
|
780
|
+
def_target = target_list.first
|
781
|
+
if tlist.empty?
|
782
|
+
puts "rant # => " + list_task_names(
|
783
|
+
resolve(def_target)).join(', ')
|
784
|
+
msg "No described tasks."
|
785
|
+
return
|
786
|
+
end
|
787
|
+
prefix = "rant "
|
788
|
+
infix = " # "
|
789
|
+
name_length = (tlist.map{ |t| t.to_s.length } << 7).max
|
790
|
+
cmd_length = prefix.length + name_length
|
791
|
+
unless tlist.first.to_s == def_target
|
792
|
+
defaults = list_task_names(
|
793
|
+
resolve(def_target)).join(', ')
|
794
|
+
puts "#{prefix}#{' ' * name_length}#{infix}=> #{defaults}"
|
795
|
+
end
|
796
|
+
tlist.each { |t|
|
797
|
+
print(prefix + t.to_s.ljust(name_length) + infix)
|
798
|
+
dt = t.description.sub(/\s+$/, "")
|
799
|
+
puts dt.gsub(/\n/, "\n" + ' ' * cmd_length + infix + " ")
|
800
|
+
}
|
801
|
+
true
|
802
|
+
end
|
803
|
+
|
804
|
+
def list_task_names(*tasks)
|
805
|
+
rsl = []
|
806
|
+
tasks.flatten.each { |t|
|
807
|
+
if t.respond_to?(:has_actions?) && t.has_actions?
|
808
|
+
rsl << t
|
809
|
+
elsif t.respond_to? :prerequisites
|
810
|
+
if t.prerequisites.empty?
|
811
|
+
rsl << t
|
812
|
+
else
|
813
|
+
t.prerequisites.each { |pre|
|
814
|
+
rsl.concat(list_task_names(
|
815
|
+
resolve(pre, t.project_subdir)))
|
816
|
+
}
|
817
|
+
end
|
818
|
+
else
|
819
|
+
rsl << t
|
820
|
+
end
|
821
|
+
}
|
822
|
+
rsl
|
823
|
+
end
|
824
|
+
private :list_task_names
|
825
|
+
|
826
|
+
# This is actually an integer indicating the verbosity level.
|
827
|
+
# Usual values range from 0 to 3.
|
828
|
+
def verbose
|
829
|
+
@opts[:verbose]
|
830
|
+
end
|
831
|
+
|
832
|
+
def quiet?
|
833
|
+
@opts[:quiet]
|
834
|
+
end
|
835
|
+
|
836
|
+
def pos_text(file, ln)
|
837
|
+
t = "in file `#{file}'"
|
838
|
+
t << ", line #{ln}" if ln && ln > 0
|
839
|
+
t << ": "
|
840
|
+
end
|
841
|
+
|
842
|
+
# Print a command message as would be done from a call to a
|
843
|
+
# sys method.
|
844
|
+
def cmd_msg(cmd)
|
845
|
+
puts cmd unless quiet?
|
846
|
+
end
|
847
|
+
|
848
|
+
def cmd_print(text)
|
849
|
+
print text unless quiet?
|
850
|
+
$stdout.flush
|
851
|
+
end
|
852
|
+
|
853
|
+
# All targets given on commandline, including those given
|
854
|
+
# with the -a option. The list will be in processing order.
|
855
|
+
def cmd_targets
|
856
|
+
@force_targets + @arg_targets
|
857
|
+
end
|
858
|
+
|
859
|
+
def running_task(task)
|
860
|
+
if @current_subdir != @last_build_subdir
|
861
|
+
cmd_msg "(in #{@current_subdir.empty? ?
|
862
|
+
@rootdir : @current_subdir})"
|
863
|
+
@last_build_subdir = @current_subdir
|
864
|
+
end
|
865
|
+
# TODO: model feels sick... this functionality should
|
866
|
+
# be implemented in Node
|
867
|
+
if @opts[:dry_run]
|
868
|
+
task.dry_run
|
869
|
+
true
|
870
|
+
end
|
871
|
+
end
|
872
|
+
|
873
|
+
private
|
874
|
+
def have_any_task?
|
875
|
+
!@tasks.empty?
|
876
|
+
end
|
877
|
+
|
878
|
+
def target_list
|
879
|
+
if !have_any_task? && @resolve_hooks.empty?
|
880
|
+
abort("No tasks defined for this rant application!")
|
881
|
+
end
|
882
|
+
|
883
|
+
# Target selection strategy:
|
884
|
+
# Run tasks specified on commandline, if not given:
|
885
|
+
# run default task, if not given:
|
886
|
+
# run first defined task.
|
887
|
+
target_list = @force_targets + @arg_targets
|
888
|
+
# The target list is a list of strings, not node objects!
|
889
|
+
if target_list.empty?
|
890
|
+
def_tasks = resolve "default"
|
891
|
+
unless def_tasks.empty?
|
892
|
+
target_list << "default"
|
893
|
+
else
|
894
|
+
@rantfiles.each { |f|
|
895
|
+
first = f.tasks.first
|
896
|
+
if first
|
897
|
+
target_list << first.reference_name
|
898
|
+
break
|
899
|
+
end
|
900
|
+
}
|
901
|
+
end
|
902
|
+
end
|
903
|
+
target_list
|
904
|
+
end
|
905
|
+
|
906
|
+
# If this method returns (i.e. no exception was risen),
|
907
|
+
# current_subdir is the same as before invocation.
|
908
|
+
def run_tasks
|
909
|
+
# Now, run all specified tasks in all rantfiles,
|
910
|
+
# rantfiles in reverse order.
|
911
|
+
target_list.each { |target|
|
912
|
+
# build ensures that current_subdir is the same before
|
913
|
+
# and after invocation
|
914
|
+
if build(target) == 0
|
915
|
+
abort("Don't know how to make `#{target}'.")
|
916
|
+
end
|
917
|
+
}
|
918
|
+
end
|
919
|
+
|
920
|
+
def make(target, *args, &block)
|
921
|
+
ch = nil
|
922
|
+
if target.respond_to? :to_hash
|
923
|
+
targ = target.to_hash
|
924
|
+
ch = Rant::Lib.parse_caller_elem(caller[1])
|
925
|
+
abort_at(ch, "make: too many arguments") unless args.empty?
|
926
|
+
tn = nil
|
927
|
+
prepare_task(targ, block, ch) { |name,pre,blk|
|
928
|
+
tn = name
|
929
|
+
@node_factory.new_file(self, name, pre, blk)
|
930
|
+
}
|
931
|
+
build(tn)
|
932
|
+
elsif target.respond_to? :to_rant_target
|
933
|
+
rt = target.to_rant_target
|
934
|
+
opt = args.shift
|
935
|
+
unless args.empty?
|
936
|
+
ch ||= Rant::Lib.parse_caller_elem(caller[1])
|
937
|
+
abort_at(ch, "make: too many arguments")
|
938
|
+
end
|
939
|
+
if block
|
940
|
+
# create a file task
|
941
|
+
ch ||= Rant::Lib.parse_caller_elem(caller[1])
|
942
|
+
prepare_task(rt, block, ch) { |name,pre,blk|
|
943
|
+
@node_factory.new_file(self, name, pre, blk)
|
944
|
+
}
|
945
|
+
build(rt)
|
946
|
+
else
|
947
|
+
build(rt, opt||{})
|
948
|
+
end
|
949
|
+
elsif target.respond_to? :rant_gen
|
950
|
+
ch = Rant::Lib.parse_caller_elem(caller[1])
|
951
|
+
rv = target.rant_gen(self, ch, args, &block)
|
952
|
+
unless rv.respond_to? :to_rant_target
|
953
|
+
abort_at(ch, "make: invalid generator return value")
|
954
|
+
end
|
955
|
+
build(rv.to_rant_target)
|
956
|
+
rv
|
957
|
+
else
|
958
|
+
ch = Rant::Lib.parse_caller_elem(caller[1])
|
959
|
+
abort_at(ch,
|
960
|
+
"make: generator or target as first argument required.")
|
961
|
+
end
|
962
|
+
end
|
963
|
+
public :make
|
964
|
+
|
965
|
+
# Invoke all tasks necessary to build +target+. Returns the number
|
966
|
+
# of tasks invoked.
|
967
|
+
def build(target, opt = {})
|
968
|
+
opt[:force] = true if @force_targets.delete(target)
|
969
|
+
# Currently either the whole application has to by run in
|
970
|
+
# dry-run mode or nothing.
|
971
|
+
opt[:dry_run] = @opts[:dry_run]
|
972
|
+
matching_tasks = 0
|
973
|
+
old_subdir = @current_subdir
|
974
|
+
old_pwd = Dir.pwd
|
975
|
+
resolve(target).each { |t|
|
976
|
+
unless opt[:type] == :file && !t.file_target?
|
977
|
+
matching_tasks += 1
|
978
|
+
begin
|
979
|
+
t.invoke(opt)
|
980
|
+
rescue Rant::TaskFail => e
|
981
|
+
err_task_fail(e)
|
982
|
+
abort
|
983
|
+
end
|
984
|
+
end
|
985
|
+
}
|
986
|
+
@current_subdir = old_subdir
|
987
|
+
Dir.chdir old_pwd
|
988
|
+
matching_tasks
|
989
|
+
end
|
990
|
+
public :build
|
991
|
+
|
992
|
+
# Currently always returns an array (which might actually be an
|
993
|
+
# empty array, but never nil).
|
994
|
+
def resolve(task_name, rel_project_dir = @current_subdir)
|
995
|
+
# alternative implementation:
|
996
|
+
# rec_save_resolve(task_name, nil, rel_project_dir)
|
997
|
+
s = @tasks[expand_path(rel_project_dir, task_name)]
|
998
|
+
case s
|
999
|
+
when nil
|
1000
|
+
@resolve_hooks.each { |s|
|
1001
|
+
# Note: will probably change to get more params
|
1002
|
+
s = s[task_name, rel_project_dir]
|
1003
|
+
#if s
|
1004
|
+
# puts s.size
|
1005
|
+
# t = s.first
|
1006
|
+
# puts t.full_name
|
1007
|
+
# puts t.name
|
1008
|
+
# puts t.deps
|
1009
|
+
#end
|
1010
|
+
return s if s
|
1011
|
+
}
|
1012
|
+
[]
|
1013
|
+
when Rant::Node: [s]
|
1014
|
+
else # assuming list of tasks
|
1015
|
+
s
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
public :resolve
|
1019
|
+
|
1020
|
+
def rec_save_resolve(task_name, excl_hook, rel_project_dir = @current_subdir)
|
1021
|
+
s = @tasks[expand_path(rel_project_dir, task_name)]
|
1022
|
+
case s
|
1023
|
+
when nil
|
1024
|
+
@resolve_hooks.each { |s|
|
1025
|
+
next if s == excl_hook
|
1026
|
+
s = s[task_name, rel_project_dir]
|
1027
|
+
return s if s
|
1028
|
+
}
|
1029
|
+
[]
|
1030
|
+
when Rant::Node: [s]
|
1031
|
+
else
|
1032
|
+
s
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
public :rec_save_resolve
|
1036
|
+
|
1037
|
+
# This hook will be invoked when no matching task is found for a
|
1038
|
+
# target. It may create one or more tasks for the target, which is
|
1039
|
+
# given as argument, on the fly and return an array of the created
|
1040
|
+
# tasks or nil.
|
1041
|
+
def at_resolve(&block)
|
1042
|
+
@resolve_hooks << block if block
|
1043
|
+
end
|
1044
|
+
public :at_resolve
|
1045
|
+
|
1046
|
+
# block will be called before this rant returns from #run
|
1047
|
+
# pwd will be the projects root directory
|
1048
|
+
def at_return(&block)
|
1049
|
+
hooks = var._get("__at_return__")
|
1050
|
+
if hooks
|
1051
|
+
hooks << block
|
1052
|
+
else
|
1053
|
+
var._set("__at_return__", [block])
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
public :at_return
|
1057
|
+
|
1058
|
+
# Returns a list with all tasks for which yield
|
1059
|
+
# returns true.
|
1060
|
+
def select_tasks
|
1061
|
+
selection = []
|
1062
|
+
@rantfiles.each { |rf|
|
1063
|
+
rf.tasks.each { |t|
|
1064
|
+
selection << t if yield t
|
1065
|
+
}
|
1066
|
+
}
|
1067
|
+
selection
|
1068
|
+
end
|
1069
|
+
public :select_tasks
|
1070
|
+
|
1071
|
+
def load_rantfiles
|
1072
|
+
# Take care: When rant isn't invoked from commandline,
|
1073
|
+
# some "rant code" could already have run!
|
1074
|
+
# We run the default Rantfiles only if no tasks where
|
1075
|
+
# already defined and no Rantfile was given in args.
|
1076
|
+
unless @arg_rantfiles.empty?
|
1077
|
+
@arg_rantfiles.each { |fn|
|
1078
|
+
if test(?f, fn)
|
1079
|
+
rf, is_new = rantfile_for_path(fn)
|
1080
|
+
load_file rf if is_new
|
1081
|
+
else
|
1082
|
+
abort "No such file -- #{fn}"
|
1083
|
+
end
|
1084
|
+
}
|
1085
|
+
return
|
1086
|
+
end
|
1087
|
+
return if have_any_task?
|
1088
|
+
# look for standard Rantfile in working directory
|
1089
|
+
fn = rantfile_in_dir
|
1090
|
+
if @opts[:cd_parent]
|
1091
|
+
# search for Rantfile in parent directories
|
1092
|
+
old_root = @rootdir
|
1093
|
+
until fn or @rootdir == "/"
|
1094
|
+
@rootdir = File.dirname(@rootdir)
|
1095
|
+
fn = rantfile_in_dir(@rootdir)
|
1096
|
+
end
|
1097
|
+
if @rootdir != old_root and fn
|
1098
|
+
Dir.chdir @rootdir
|
1099
|
+
cmd_msg "(in #@rootdir)"
|
1100
|
+
end
|
1101
|
+
end
|
1102
|
+
if fn
|
1103
|
+
rf, is_new = rantfile_for_path(fn)
|
1104
|
+
load_file rf if is_new
|
1105
|
+
return
|
1106
|
+
end
|
1107
|
+
have_sub_rantfile = test(?f, Rant::SUB_RANTFILE)
|
1108
|
+
if have_sub_rantfile || @opts[:look_up]
|
1109
|
+
# search for "root" Rantfile in parent directories, treat
|
1110
|
+
# current working directory as project subdirectory
|
1111
|
+
cur_dir = Dir.pwd
|
1112
|
+
until cur_dir == "/"
|
1113
|
+
cur_dir = File.dirname(cur_dir)
|
1114
|
+
Dir.chdir cur_dir
|
1115
|
+
fn = rantfile_in_dir
|
1116
|
+
if fn
|
1117
|
+
@initial_subdir = @rootdir.sub(
|
1118
|
+
/^#{Regexp.escape cur_dir}\//, '')
|
1119
|
+
# adjust rootdir
|
1120
|
+
@rootdir = cur_dir
|
1121
|
+
cmd_msg "(root is #@rootdir, in #@initial_subdir)"
|
1122
|
+
@last_build_subdir = @initial_subdir
|
1123
|
+
rf, is_new = rantfile_for_path(fn)
|
1124
|
+
load_file rf if is_new
|
1125
|
+
goto_project_dir @initial_subdir
|
1126
|
+
# ensure to read sub.rant in initial subdir even
|
1127
|
+
# if it wasn't mentioned with +subdirs+.
|
1128
|
+
if have_sub_rantfile
|
1129
|
+
rf, is_new = rantfile_for_path(
|
1130
|
+
Rant::SUB_RANTFILE, false)
|
1131
|
+
if is_new
|
1132
|
+
@rantfiles.unshift rf
|
1133
|
+
load_file rf
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
break
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
if @rantfiles.empty?
|
1141
|
+
abort("No Rantfile found, looking for:",
|
1142
|
+
Rant::RANTFILES.join(", "))
|
1143
|
+
end
|
1144
|
+
end
|
1145
|
+
|
1146
|
+
# Returns the value of the last expression executed in +rantfile+.
|
1147
|
+
# +rantfile+ has to be an Rant::Rantfile instance.
|
1148
|
+
def load_file(rantfile)
|
1149
|
+
vmsg 1, "source #{rantfile}"
|
1150
|
+
@context.instance_eval(File.read(rantfile), rantfile)
|
1151
|
+
end
|
1152
|
+
private :load_file
|
1153
|
+
|
1154
|
+
# Get path to Rantfile in +dir+ or nil if dir doesn't contain an
|
1155
|
+
# Rantfile.
|
1156
|
+
#
|
1157
|
+
# If dir is nil, look in current directory.
|
1158
|
+
def rantfile_in_dir(dir=nil)
|
1159
|
+
::Rant::RANTFILES.each { |rfn|
|
1160
|
+
path = dir ? File.join(dir, rfn) : rfn
|
1161
|
+
return path if test ?f, path
|
1162
|
+
}
|
1163
|
+
nil
|
1164
|
+
end
|
1165
|
+
|
1166
|
+
def process_args
|
1167
|
+
# WARNING: we currently have to fool getoptlong,
|
1168
|
+
# by temporary changing ARGV!
|
1169
|
+
# This could cause problems (e.g. multithreading).
|
1170
|
+
old_argv = ARGV.dup
|
1171
|
+
ARGV.replace(@args.dup)
|
1172
|
+
cmd_opts = GetoptLong.new(*OPTIONS.collect { |lst| lst[0..-2] })
|
1173
|
+
cmd_opts.quiet = true
|
1174
|
+
cmd_opts.each { |opt, value|
|
1175
|
+
case opt
|
1176
|
+
when "--verbose": @opts[:verbose] += 1
|
1177
|
+
when "--version"
|
1178
|
+
puts "rant #{Rant::VERSION}"
|
1179
|
+
raise Rant::RantDoneException
|
1180
|
+
when "--help"
|
1181
|
+
show_help
|
1182
|
+
raise Rant::RantDoneException
|
1183
|
+
when "--directory"
|
1184
|
+
@rootdir = File.expand_path(value)
|
1185
|
+
when "--rantfile"
|
1186
|
+
@arg_rantfiles << value
|
1187
|
+
when "--force-run"
|
1188
|
+
@force_targets << value
|
1189
|
+
when "--import"
|
1190
|
+
import value
|
1191
|
+
else
|
1192
|
+
# simple switch
|
1193
|
+
@opts[opt.sub(/^--/, '').tr('-', "_").to_sym] = true
|
1194
|
+
end
|
1195
|
+
}
|
1196
|
+
rescue GetoptLong::Error => e
|
1197
|
+
abort(e.message)
|
1198
|
+
ensure
|
1199
|
+
rem_args = ARGV.dup
|
1200
|
+
ARGV.replace(old_argv)
|
1201
|
+
rem_args.each { |ra|
|
1202
|
+
if ra =~ /(^[^=]+)=([^=]+)$/
|
1203
|
+
vmsg 2, "var: #$1=#$2"
|
1204
|
+
@var[$1] = $2
|
1205
|
+
else
|
1206
|
+
@arg_targets << ra
|
1207
|
+
end
|
1208
|
+
}
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
# Every task has to be registered with this method.
|
1212
|
+
def prepare_task(targ, block, clr = caller[2])
|
1213
|
+
#STDERR.puts "prepare task (#@current_subdir):\n #{targ.inspect}"
|
1214
|
+
|
1215
|
+
# Allow override of caller, useful for plugins and libraries
|
1216
|
+
# that define tasks.
|
1217
|
+
if targ.is_a? Hash
|
1218
|
+
targ.reject! { |k, v| clr = v if k == :__caller__ }
|
1219
|
+
end
|
1220
|
+
ch = Hash === clr ? clr : Rant::Lib::parse_caller_elem(clr)
|
1221
|
+
|
1222
|
+
name, pre = normalize_task_arg(targ, ch)
|
1223
|
+
|
1224
|
+
file, is_new = rantfile_for_path(ch[:file])
|
1225
|
+
nt = yield(name, pre, block)
|
1226
|
+
nt.rantfile = file
|
1227
|
+
#nt.project_subdir = file.project_subdir
|
1228
|
+
nt.project_subdir = @current_subdir
|
1229
|
+
nt.line_number = ch[:ln]
|
1230
|
+
nt.description = @task_desc
|
1231
|
+
@task_desc = nil
|
1232
|
+
file.tasks << nt
|
1233
|
+
hash_task nt
|
1234
|
+
nt
|
1235
|
+
end
|
1236
|
+
public :prepare_task
|
1237
|
+
|
1238
|
+
def hash_task(task)
|
1239
|
+
n = task.full_name
|
1240
|
+
#STDERR.puts "hash_task: `#{n}'"
|
1241
|
+
et = @tasks[n]
|
1242
|
+
case et
|
1243
|
+
when nil
|
1244
|
+
@tasks[n] = task
|
1245
|
+
when Rant::Node
|
1246
|
+
mt = [et, task]
|
1247
|
+
@tasks[n] = mt
|
1248
|
+
else # assuming list of tasks
|
1249
|
+
et << task
|
1250
|
+
end
|
1251
|
+
end
|
1252
|
+
|
1253
|
+
# Tries to extract task name and prerequisites from the typical
|
1254
|
+
# argument to the +task+ command. +targ+ should be one of String,
|
1255
|
+
# Symbol or Hash. ch is the caller (hash with the elements :file
|
1256
|
+
# and :ln) and is used for error reporting and debugging.
|
1257
|
+
#
|
1258
|
+
# Returns two values, the first is a string which is the task name
|
1259
|
+
# and the second is an array with the prerequisites.
|
1260
|
+
def normalize_task_arg(targ, ch)
|
1261
|
+
name = nil
|
1262
|
+
pre = []
|
1263
|
+
|
1264
|
+
# process and validate targ
|
1265
|
+
if targ.is_a? Hash
|
1266
|
+
if targ.empty?
|
1267
|
+
abort_at(ch, "Empty hash as task argument, " +
|
1268
|
+
"task name required.")
|
1269
|
+
end
|
1270
|
+
if targ.size > 1
|
1271
|
+
abort_at(ch, "Too many hash elements, " +
|
1272
|
+
"should only be one.")
|
1273
|
+
end
|
1274
|
+
targ.each_pair { |k,v|
|
1275
|
+
name = normalize_task_name(k, ch)
|
1276
|
+
pre = v
|
1277
|
+
}
|
1278
|
+
unless ::Rant::FileList === pre
|
1279
|
+
if pre.respond_to? :to_ary
|
1280
|
+
pre = pre.to_ary.dup
|
1281
|
+
pre.map! { |elem|
|
1282
|
+
normalize_task_name(elem, ch)
|
1283
|
+
}
|
1284
|
+
else
|
1285
|
+
pre = [normalize_task_name(pre, ch)]
|
1286
|
+
end
|
1287
|
+
end
|
1288
|
+
else
|
1289
|
+
name = normalize_task_name(targ, ch)
|
1290
|
+
end
|
1291
|
+
|
1292
|
+
[name, pre]
|
1293
|
+
end
|
1294
|
+
public :normalize_task_arg
|
1295
|
+
|
1296
|
+
# Tries to make a task name out of arg and returns
|
1297
|
+
# the valid task name. If not possible, calls abort
|
1298
|
+
# with an appropriate error message using file and ln.
|
1299
|
+
def normalize_task_name(arg, ch)
|
1300
|
+
return arg if arg.is_a? String
|
1301
|
+
if Symbol === arg
|
1302
|
+
arg.to_s
|
1303
|
+
elsif arg.respond_to? :to_str
|
1304
|
+
arg.to_str
|
1305
|
+
else
|
1306
|
+
abort_at(ch, "Task name has to be a string or symbol.")
|
1307
|
+
end
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
# Returns a Rant::Rantfile object as first value
|
1311
|
+
# and a boolean value as second. If the second is true,
|
1312
|
+
# the rantfile was created and added, otherwise the rantfile
|
1313
|
+
# already existed.
|
1314
|
+
def rantfile_for_path(path, register=true)
|
1315
|
+
# all rantfiles have an absolute path as path attribute
|
1316
|
+
abs_path = File.expand_path(path)
|
1317
|
+
file = @rantfiles.find { |rf| rf.path == abs_path }
|
1318
|
+
if file
|
1319
|
+
[file, false]
|
1320
|
+
else
|
1321
|
+
# create new Rantfile object
|
1322
|
+
file = Rant::Rantfile.new abs_path
|
1323
|
+
file.project_subdir = @current_subdir
|
1324
|
+
@rantfiles << file if register
|
1325
|
+
[file, true]
|
1326
|
+
end
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
# Returns the usual hash with :file and :ln as keys for the first
|
1330
|
+
# element in backtrace which comes from an Rantfile, or nil if no
|
1331
|
+
# Rantfile is involved.
|
1332
|
+
#
|
1333
|
+
# Note that this method is very time consuming!
|
1334
|
+
def get_ch_from_backtrace(backtrace)
|
1335
|
+
backtrace.each { |clr|
|
1336
|
+
ch = ::Rant::Lib.parse_caller_elem(clr)
|
1337
|
+
if ::Rant::Env.on_windows?
|
1338
|
+
return ch if @rantfiles.any? { |rf|
|
1339
|
+
# sigh... a bit hackish: replace any backslash
|
1340
|
+
# with a slash and remove any leading drive (e.g.
|
1341
|
+
# C:) from the path
|
1342
|
+
rf.path.tr("\\", "/").sub(/^\w\:/, '') ==
|
1343
|
+
ch[:file].tr("\\", "/").sub(/^\w\:/, '')
|
1344
|
+
}
|
1345
|
+
else
|
1346
|
+
return ch if @rantfiles.any? { |rf|
|
1347
|
+
rf.path == ch[:file]
|
1348
|
+
}
|
1349
|
+
end
|
1350
|
+
}
|
1351
|
+
nil
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
def err_task_fail(e)
|
1355
|
+
msg = []
|
1356
|
+
t_msg = ["Task `#{e.tname}' fail."]
|
1357
|
+
orig = e
|
1358
|
+
loop { orig = orig.orig; break unless Rant::TaskFail === orig }
|
1359
|
+
if orig && orig != e && !(Rant::RantAbortException === orig)
|
1360
|
+
ch = get_ch_from_backtrace(orig.backtrace)
|
1361
|
+
msg << pos_text(ch[:file], ch[:ln]) if ch
|
1362
|
+
unless Rant::CommandError === orig && !@opts[:err_commands]
|
1363
|
+
msg << orig.message
|
1364
|
+
msg << orig.backtrace[0..4] unless ch
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
if e.msg && !e.msg.empty?
|
1368
|
+
ch = get_ch_from_backtrace(e.backtrace)
|
1369
|
+
t_msg.unshift(e.msg)
|
1370
|
+
t_msg.unshift(pos_text(ch[:file], ch[:ln])) if ch
|
1371
|
+
end
|
1372
|
+
err_msg msg unless msg.empty?
|
1373
|
+
err_msg t_msg
|
1374
|
+
end
|
1375
|
+
end # class Rant::RantApp
|
1376
|
+
# this line prevents ruby 1.8.3 from segfaulting
|