cook 2.0.1
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/.gemtest +0 -0
- data/.gitignore +21 -0
- data/History.txt +19 -0
- data/README.txt +89 -0
- data/Rakefile +45 -0
- data/bin/cook +44 -0
- data/bin/cookDecrypt +75 -0
- data/bin/cookEncrypt +95 -0
- data/lib/cook.rb +3 -0
- data/lib/rake/config.rb +642 -0
- data/lib/rake/cookUtils.rb +322 -0
- data/lib/rake/extensions.rb +199 -0
- data/test/test_cook.rb +12 -0
- metadata +203 -0
@@ -0,0 +1,322 @@
|
|
1
|
+
|
2
|
+
require 'openssl';
|
3
|
+
require 'securerandom';
|
4
|
+
require 'base64';
|
5
|
+
|
6
|
+
class CookEncryptionError < StandardError; end
|
7
|
+
class CookDecryptionError < StandardError; end
|
8
|
+
|
9
|
+
def areYouSure?(prompt, defaultAnswer = false, askUser = true)
|
10
|
+
if askUser then
|
11
|
+
defaultPrompt = "yN";
|
12
|
+
defaultRegExp = /^[yY]/;
|
13
|
+
if defaultAnswer then
|
14
|
+
defaultPrompt = "Yn"
|
15
|
+
defaultRegExp = /^[nN]/;
|
16
|
+
end
|
17
|
+
puts "\n*************************************************************\n#{prompt}\n\tAre you sure you want to do this? [#{defaultPrompt}]";
|
18
|
+
return !defaultAnswer if STDIN.gets =~ defaultRegExp;
|
19
|
+
end
|
20
|
+
defaultAnswer;
|
21
|
+
end
|
22
|
+
|
23
|
+
def ensureDirExists(aDirectory)
|
24
|
+
unless File.directory?(aDirectory)
|
25
|
+
Rake::Task.local_sh("mkdir -p #{aDirectory}");
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# encryptData2File and decryptFile2Data are both based on:
|
30
|
+
# "Encrypting and decrypting some data"
|
31
|
+
# at: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/Cipher.html
|
32
|
+
# see also:
|
33
|
+
# http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKCS5.html
|
34
|
+
|
35
|
+
def openSslEncryptData2File(plainText, encryptedFilePath)
|
36
|
+
# check to ensure the file name conforms to our naming conventions...
|
37
|
+
#
|
38
|
+
if encryptedFilePath !~ /\.enc$/ then
|
39
|
+
raise CookEncryptionError, "The file in which to store the encrypted data (#{encryptedFilePath}) MUST have the file extension '.enc'";
|
40
|
+
end
|
41
|
+
|
42
|
+
salt = SecureRandom.random_bytes(32);
|
43
|
+
|
44
|
+
encrypter = OpenSSL::Cipher.new('AES-256-CBC');
|
45
|
+
encrypter.encrypt;
|
46
|
+
encrypter.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(Conf.get_pass_phrase(true),
|
47
|
+
salt, 2000, 256);
|
48
|
+
iv = encrypter.random_iv;
|
49
|
+
|
50
|
+
encrypted = encrypter.update plainText;
|
51
|
+
encrypted << encrypter.final
|
52
|
+
|
53
|
+
File.open(encryptedFilePath, 'w') do | cypherFile |
|
54
|
+
cypherFile.puts "--salt32bytes-iv-cypherText--";
|
55
|
+
cypherFile.puts Base64.encode64(salt);
|
56
|
+
cypherFile.puts Base64.encode64(iv);
|
57
|
+
cypherFile.puts Base64.encode64(encrypted);
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def openSslDecryptFile2Data(encryptedFilePath)
|
62
|
+
|
63
|
+
# check to ensure the file name conforms to our naming conventions...
|
64
|
+
#
|
65
|
+
if encryptedFilePath !~ /\.enc$/ then
|
66
|
+
raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) MUST have the file extension '.enc' ";
|
67
|
+
end
|
68
|
+
|
69
|
+
# check to make sure the file exists...
|
70
|
+
#
|
71
|
+
if !File.exists?(encryptedFilePath) then
|
72
|
+
raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) does not exist";
|
73
|
+
end
|
74
|
+
|
75
|
+
cypherFile = File.open(encryptedFilePath);
|
76
|
+
headerLine = cypherFile.gets;
|
77
|
+
if headerLine !~ /^--salt32bytes-iv-cypherText--$/ then
|
78
|
+
raise CookDecryptionError, "Attempting to decrypt a file which was not encrypted using cookUtils openSslEncryptData2File (header line: [#{headerLine}]).";
|
79
|
+
end
|
80
|
+
salt = Base64.decode64(cypherFile.gets);
|
81
|
+
iv = Base64.decode64(cypherFile.gets);
|
82
|
+
cypherText = Base64.decode64(cypherFile.read);
|
83
|
+
|
84
|
+
decrypter = OpenSSL::Cipher.new('AES-256-CBC');
|
85
|
+
decrypter.decrypt;
|
86
|
+
decrypter.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(Conf.get_pass_phrase,
|
87
|
+
salt, 2000, 256);
|
88
|
+
decrypter.iv = iv;
|
89
|
+
|
90
|
+
decrypted = decrypter.update cypherText;
|
91
|
+
decrypted << decrypter.final
|
92
|
+
|
93
|
+
return decrypted;
|
94
|
+
end
|
95
|
+
|
96
|
+
# gpgEncryptData2File and gpgDecryptFile2Data are both based on the
|
97
|
+
# ruby gpgme bindings documentation. (See:
|
98
|
+
|
99
|
+
def gpgEncryptData2File(plainText, encryptedFilePath)
|
100
|
+
|
101
|
+
if !Conf.gpg.has_key?(:recipientUID) then
|
102
|
+
raise CookEncryptionError, "No GnuPG recipient UID specified in configuration";
|
103
|
+
end
|
104
|
+
|
105
|
+
if !ENV.has_key?("GPG_AGENT_INFO") then
|
106
|
+
raise CookEncryptionError, "Could not find GnuPG agent (GPG_AGENT_INFO environment variable), is it running?";
|
107
|
+
end
|
108
|
+
|
109
|
+
begin
|
110
|
+
require 'gpgme';
|
111
|
+
rescue LoadError
|
112
|
+
raise CookEncryptionError, "The required gpgme (GnuPG Made Easy) ruby gem could not be loaded";
|
113
|
+
end
|
114
|
+
|
115
|
+
crypto = GPGME::Crypto.new
|
116
|
+
encrypted = crypto.encrypt(plainText, { :recipients => Conf.gpg.recipientUID, :always_trust => true } ).read;
|
117
|
+
|
118
|
+
File.open(encryptedFilePath, 'w') do | cypherFile |
|
119
|
+
cypherFile.puts "--gpgRecipientUID-cypherText--";
|
120
|
+
cypherFile.puts Conf.gpg.recipientUID;
|
121
|
+
cypherFile.puts Base64.encode64(encrypted);
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def gpgDecryptFile2Data(encryptedFilePath)
|
126
|
+
|
127
|
+
if !Conf.gpg.has_key?(:recipientUID) then
|
128
|
+
raise CookDecryptionError, "No GnuPG recipient UID (Conf.gpg.recipientUID) specified in configuration";
|
129
|
+
end
|
130
|
+
|
131
|
+
if !ENV.has_key?("GPG_AGENT_INFO") then
|
132
|
+
raise CookDecryptionError, "Could not find GnuPG agent (GPG_AGENT_INFO environment variable), is it running?";
|
133
|
+
end
|
134
|
+
|
135
|
+
begin
|
136
|
+
require 'gpgme';
|
137
|
+
rescue LoadError
|
138
|
+
raise CookDecryptionError, "The required gpgme (GnuPG Made Easy) ruby gem could not be loaded";
|
139
|
+
end
|
140
|
+
|
141
|
+
# check to ensure the file name conforms to our naming conventions...
|
142
|
+
#
|
143
|
+
if encryptedFilePath !~ /\.enc$/ then
|
144
|
+
raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) MUST have the file extension '.enc'";
|
145
|
+
end
|
146
|
+
|
147
|
+
# check to make sure the file exists...
|
148
|
+
#
|
149
|
+
if !File.exists?(encryptedFilePath) then
|
150
|
+
raise CookDecryptionError, "The encrypted file (#{encryptedFilePath}) does not exist";
|
151
|
+
end
|
152
|
+
|
153
|
+
cypherFile = File.open(encryptedFilePath);
|
154
|
+
header = cypherFile.gets;
|
155
|
+
if header !~ /^--gpgRecipientUID-cypherText--$/ then
|
156
|
+
raise CookDecryptionError, "Attempting to decrypt a file which was not encrypted using cookUtils gpgEncryptData2File (header: [#{header}]).";
|
157
|
+
end
|
158
|
+
recipientUID = cypherFile.gets.chomp;
|
159
|
+
if recipientUID != Conf.gpg.recipientUID then
|
160
|
+
require 'pp';
|
161
|
+
pp recipientUID;
|
162
|
+
pp Conf.gpg.recipientUID;
|
163
|
+
raise CookDecryptionError, "Configured recipient UID (#{Conf.gpg.recipientUID}) does not match the recipient UID (#{recipientUID}) used to encrypt the file: [#{encryptedFilePath}]";
|
164
|
+
end
|
165
|
+
cypherText = Base64.decode64(cypherFile.read);
|
166
|
+
|
167
|
+
crypto = GPGME::Crypto.new
|
168
|
+
decrypted = crypto.decrypt(cypherText, :recipients => Conf.gpg.recipientUID).read;
|
169
|
+
|
170
|
+
return decrypted;
|
171
|
+
end
|
172
|
+
|
173
|
+
#############################################################################
|
174
|
+
# Walk resources, scripts and binaries
|
175
|
+
|
176
|
+
def walkResourceScriptTemplates(templateType, serverType, targetServer, targetMachine, &aBlock)
|
177
|
+
mesg "";
|
178
|
+
targetResourceScriptsDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resourceScripts';
|
179
|
+
targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resources';
|
180
|
+
ensureDirExists(targetResourceScriptsDir);
|
181
|
+
ensureDirExists(targetDir);
|
182
|
+
resourceScripts = Hash.new();
|
183
|
+
each_resource(templateType + '/' + serverType.to_s + '/resourceScriptTemplates') do | aResourceScriptPath |
|
184
|
+
mesg "walking resource scripts in [#{aResourceScriptPath}]";
|
185
|
+
Dir.glob(aResourceScriptPath + '/*.erb').sort.each do | aResourceScriptERB |
|
186
|
+
baseName = File.basename(aResourceScriptERB, '.erb');
|
187
|
+
resourceScripts[baseName] = aResourceScriptERB;
|
188
|
+
end
|
189
|
+
end
|
190
|
+
resourceScripts.keys.sort.each do | aResourceScriptBaseName |
|
191
|
+
aResourceScriptName = targetResourceScriptsDir + '/' + aResourceScriptBaseName;
|
192
|
+
aResourceName = targetDir + '/' + aResourceScriptBaseName;
|
193
|
+
aBlock.call(resourceScripts[aResourceScriptBaseName], aResourceScriptName, aResourceName);
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def walkScriptTemplates(templateType, serverType, targetServer, targetMachine, &aBlock)
|
198
|
+
mesg "";
|
199
|
+
targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/scripts';
|
200
|
+
ensureDirExists(targetDir);
|
201
|
+
scripts = Hash.new();
|
202
|
+
each_resource(templateType.to_s + '/' + serverType.to_s + '/scriptTemplates') do | aResourcePath |
|
203
|
+
mesg "walking scripts in [#{aResourcePath}]";
|
204
|
+
Dir.glob(aResourcePath + '/*.erb').sort.each do | aScriptERB |
|
205
|
+
baseName = File.basename(aScriptERB, '.erb');
|
206
|
+
scripts[serverType+baseName] = aScriptERB;
|
207
|
+
end
|
208
|
+
end
|
209
|
+
scripts.keys.sort.each do | aScriptBaseName |
|
210
|
+
aScriptName = targetDir + '/' + aScriptBaseName;
|
211
|
+
aBlock.call(scripts[aScriptBaseName], aScriptName);
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def walkResourceTemplates(templateType, serverType, targetServer, targetMachine, &aBlock)
|
216
|
+
mesg "";
|
217
|
+
targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resources';
|
218
|
+
ensureDirExists(targetDir);
|
219
|
+
resources = Hash.new();
|
220
|
+
each_resource(templateType.to_s + '/' + serverType.to_s + '/resourceTemplates') do | aResourcePath |
|
221
|
+
mesg "walking resources in [#{aResourcePath}]";
|
222
|
+
Dir.glob(aResourcePath + '/*.erb').sort.each do | aResourceERB |
|
223
|
+
baseName = File.basename(aResourceERB, '.erb');
|
224
|
+
resources[baseName] = aResourceERB;
|
225
|
+
end
|
226
|
+
end
|
227
|
+
resources.keys.sort.each do | aResourceBaseName |
|
228
|
+
aResourceName = targetDir + '/' + aResourceBaseName;
|
229
|
+
aBlock.call(resources[aResourceBaseName], aResourceName);
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def walkResourceBinaries(templateType, serverType, targetServer, targetMachine, &aBlock)
|
234
|
+
mesg "";
|
235
|
+
targetDir = 'upload/' + targetMachine.to_s + '/' + targetServer.to_s + '/' + templateType.to_s + '/resources';
|
236
|
+
ensureDirExists(targetDir);
|
237
|
+
resources = Hash.new();
|
238
|
+
each_resource(templateType.to_s + '/' + serverType.to_s + '/resourceTemplates') do | aResourcePath |
|
239
|
+
mesg "walking binaries in [#{aResourcePath}]";
|
240
|
+
Dir.glob(aResourcePath + '/*.bin').sort.each do | aResourceBIN |
|
241
|
+
baseName = File.basename(aResourceBIN, '.bin');
|
242
|
+
resources[baseName] = aResourceBIN;
|
243
|
+
end
|
244
|
+
end
|
245
|
+
resources.keys.sort.each do | aResourceBaseName |
|
246
|
+
aResourceName = targetDir + '/' + aResourceBaseName;
|
247
|
+
aBlock.call(resources[aResourceBaseName], aResourceName);
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
#############################################################################
|
252
|
+
# Apply templates and/or copy resources
|
253
|
+
|
254
|
+
def applyTemplate(templateFileName, resultFileName, mode = nil, owner = nil, group = nil)
|
255
|
+
require 'erubis'
|
256
|
+
mesg "Applying erubis template: [#{templateFileName}]\nto produce: [#{resultFileName}]";
|
257
|
+
eruby = Erubis::Eruby.new(IO.read(templateFileName));
|
258
|
+
File.open(resultFileName, 'w') do | io |
|
259
|
+
io.write(eruby.result(binding()));
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def useOverrideOrApplyTemplate(templatePath, resultPath)
|
264
|
+
overridePath = resultPath.sub(/^upload/, 'override');
|
265
|
+
if File.exists?(overridePath) then
|
266
|
+
local_sh("cp #{overridePath} #{resultPath}");
|
267
|
+
else
|
268
|
+
applyTemplate(templatePath, resultPath);
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def copyOverrideOrBinary(binaryPath, resultPath)
|
273
|
+
overridePath = resultPath.sub(/^upload/, 'override');
|
274
|
+
if File.exists?(overridePath) then
|
275
|
+
local_sh("cp #{overridePath} #{resultPath}");
|
276
|
+
else
|
277
|
+
local_sh("cp #{binaryPath} #{resultPath}");
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def fileMatches(fileName, aRegExp)
|
282
|
+
fileContents = File.open(fileName, "r").read;
|
283
|
+
fileContents =~ aRegExp;
|
284
|
+
end
|
285
|
+
|
286
|
+
def replaceLines(fileName, replaceLinesHash)
|
287
|
+
tmpFileName = fileName + '.tmp';
|
288
|
+
inFile = File.open(fileName, 'r');
|
289
|
+
outFile = File.open(tmpFileName, 'w');
|
290
|
+
inFile.each_line() do | aLine |
|
291
|
+
replaceLinesHash.keys.each() do | aRegExp |
|
292
|
+
if aRegExp.match(aLine) then
|
293
|
+
aLine = replaceLinesHash[aRegExp];
|
294
|
+
end
|
295
|
+
end
|
296
|
+
outFile.puts aLine;
|
297
|
+
end
|
298
|
+
outFile.close();
|
299
|
+
inFile.close();
|
300
|
+
File.rename(tmpFileName, fileName);
|
301
|
+
end
|
302
|
+
|
303
|
+
def walkThroughDirectoriesDoing(curDir, &aBlock)
|
304
|
+
Dir.entries(curDir).sort.each do | aFile |
|
305
|
+
next if aFile =~ /^\.$/;
|
306
|
+
next if aFile =~ /^\.\.$/;
|
307
|
+
fullPath = curDir + '/' + aFile;
|
308
|
+
aBlock.call(fullPath);
|
309
|
+
if File.directory?(fullPath) then
|
310
|
+
walkThroughDirectoriesDoing(fullPath, &aBlock);
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
def toSymbolHash(aHash)
|
316
|
+
aHash.keys.each do | anOldKey |
|
317
|
+
next if anOldKey.is_a?(Symbol);
|
318
|
+
if anOldKey.is_a?(String) then
|
319
|
+
aHash[anOldKey.to_sym] = aHash.delete(anOldKey);
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require 'logger';
|
2
|
+
require 'pp'
|
3
|
+
require 'stringio'
|
4
|
+
require 'greenletters';
|
5
|
+
|
6
|
+
module Greenletters
|
7
|
+
#
|
8
|
+
# We need to add the Greenletters::Process#result method to return the
|
9
|
+
# contents of the output_buffer...
|
10
|
+
#
|
11
|
+
class Process
|
12
|
+
def result
|
13
|
+
@output_buffer.string
|
14
|
+
end
|
15
|
+
|
16
|
+
def recentResult
|
17
|
+
@history.buffer
|
18
|
+
end
|
19
|
+
end
|
20
|
+
#
|
21
|
+
# Monkey patch GreenLetters so that we can optionally ignore the
|
22
|
+
# exist status using the :any pattern.
|
23
|
+
#
|
24
|
+
class ExitTrigger < Trigger
|
25
|
+
def call(process)
|
26
|
+
if process.status then
|
27
|
+
if pattern == :any then
|
28
|
+
@block.call(process, process.status)
|
29
|
+
true
|
30
|
+
elsif pattern === process.status.exitstatus
|
31
|
+
@block.call(process, process.status)
|
32
|
+
true
|
33
|
+
else
|
34
|
+
false
|
35
|
+
end
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module Rake
|
44
|
+
##
|
45
|
+
# Base error class for all Vlad errors.
|
46
|
+
class Error < RuntimeError; end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Raised when a remote command fails.
|
50
|
+
class CommandFailedError < Error
|
51
|
+
attr_reader :status
|
52
|
+
def initialize( status )
|
53
|
+
@status = status
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Application
|
58
|
+
|
59
|
+
def self.openLogger(logFileBaseName)
|
60
|
+
timeStamp = Time.now.utc.strftime("%Y-%m-%d-%H-%M-%S-%L.log");
|
61
|
+
@logFile = File.open(logFileBaseName+'-'+timeStamp, 'w');
|
62
|
+
@logger = Logger.new(@logFile);
|
63
|
+
@logger.datetime_format = "%H-%M-%S-%L";
|
64
|
+
@logToStderr = true;
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.catchStdout()
|
68
|
+
@oldStdout = $stdout;
|
69
|
+
$stdout = @logFile;
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.catchStderr()
|
73
|
+
@logToStderr = false;
|
74
|
+
@oldStderr = $stderr;
|
75
|
+
$stderr = @logFile;
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.setLogToStderr(logToStderr)
|
79
|
+
@logToStderr = logToStderr;
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.flushLog()
|
83
|
+
@logFile.flush();
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.logger()
|
87
|
+
return @logger;
|
88
|
+
end
|
89
|
+
def self.logFile()
|
90
|
+
return @logFile;
|
91
|
+
end
|
92
|
+
def self.logData(someData)
|
93
|
+
@logFile.write(someData) if @logFile;
|
94
|
+
end
|
95
|
+
def self.set_logger(aLogger, aLogFile=nil)
|
96
|
+
@logFile = aLogFile;
|
97
|
+
oldLogger = @logger;
|
98
|
+
@logger = aLogger;
|
99
|
+
return oldLogger;
|
100
|
+
end
|
101
|
+
def self.log(*args)
|
102
|
+
@logger.log(*args) if @logger;
|
103
|
+
end
|
104
|
+
def self.fatal(*args)
|
105
|
+
@logger.fatal(*args) if @logger;
|
106
|
+
end
|
107
|
+
def self.error(*args)
|
108
|
+
@logger.error(*args) if @logger;
|
109
|
+
end
|
110
|
+
def self.warn(*args)
|
111
|
+
@logger.warn(*args) if @logger;
|
112
|
+
end
|
113
|
+
def self.info(*args)
|
114
|
+
@logger.info(*args) if @logger;
|
115
|
+
end
|
116
|
+
def self.debug(*args)
|
117
|
+
@logger.debug(*args) if @logger;
|
118
|
+
end
|
119
|
+
def self.mesg(*args)
|
120
|
+
@logger.info(*args) if @logger;
|
121
|
+
$stderr.puts(*args) if @logToStderr;
|
122
|
+
end
|
123
|
+
|
124
|
+
# taken from http://www.ruby-forum.com/topic/43725 (2012/01/21)
|
125
|
+
def self.mesg_pp(*args)
|
126
|
+
old_out = $stdout
|
127
|
+
begin
|
128
|
+
s=StringIO.new
|
129
|
+
$stdout=s
|
130
|
+
pp(*args)
|
131
|
+
ensure
|
132
|
+
$stdout=old_out
|
133
|
+
end
|
134
|
+
@logger.info(s.string);
|
135
|
+
$stderr.puts(s.string) if @logToStderr;
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
module DSL
|
141
|
+
|
142
|
+
def mesg(*args)
|
143
|
+
Rake::Application.mesg(*args);
|
144
|
+
end
|
145
|
+
|
146
|
+
def mesg_pp(*args)
|
147
|
+
Rake::Application.mesg_pp(*args);
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
class Task
|
153
|
+
|
154
|
+
# Same as invoke, but explicitly pass a call chain to detect
|
155
|
+
# circular dependencies.
|
156
|
+
def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
|
157
|
+
new_chain = InvocationChain.append(self, invocation_chain)
|
158
|
+
@lock.synchronize do
|
159
|
+
if application.options.trace
|
160
|
+
Rake::Application.mesg "** Invoke #{name} #{format_trace_flags}"
|
161
|
+
end
|
162
|
+
return if @already_invoked
|
163
|
+
@already_invoked = true
|
164
|
+
invoke_prerequisites(task_args, new_chain)
|
165
|
+
execute(task_args) if needed?
|
166
|
+
end
|
167
|
+
rescue Exception => ex
|
168
|
+
add_chain_to(ex, new_chain)
|
169
|
+
raise ex
|
170
|
+
end
|
171
|
+
|
172
|
+
# Execute the actions associated with this task.
|
173
|
+
def execute(args=nil)
|
174
|
+
args ||= EMPTY_TASK_ARGS
|
175
|
+
if application.options.dryrun
|
176
|
+
Rake::Application.mesg "** Execute (dry run) #{name}"
|
177
|
+
return
|
178
|
+
end
|
179
|
+
if application.options.trace
|
180
|
+
Rake::Application.mesg "** Execute #{name}"
|
181
|
+
end
|
182
|
+
application.enhance_with_matching_rule(name) if @actions.empty?
|
183
|
+
@actions.each do |act|
|
184
|
+
case act.arity
|
185
|
+
when 1
|
186
|
+
act.call(self)
|
187
|
+
else
|
188
|
+
act.call(self, args)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
197
|
+
self.extend Rake::DSL
|
198
|
+
|
199
|
+
|