squared 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json'
3
4
  require 'date'
4
5
  require 'logger'
5
6
 
@@ -12,9 +13,13 @@ module Squared
12
13
  include System
13
14
  include Shell
14
15
  include Task
16
+ include Utils
15
17
  include ::Rake::DSL
16
18
 
17
- SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+))?/.freeze
19
+ SEM_VER = /(\d+)(?:(\.)(\d+))?(?:(\.)(\d+)(\S+)?)?/.freeze
20
+
21
+ VAR_SET = %i[parent global envname theme run script env depend doc test copy clean].freeze
22
+ private_constant :VAR_SET
18
23
 
19
24
  class << self
20
25
  def populate(*); end
@@ -44,9 +49,12 @@ module Squared
44
49
  @@print_order = 0
45
50
  @@tasks = {}
46
51
 
47
- attr_reader :name, :project, :workspace, :group, :path, :theme
52
+ attr_reader :name, :project, :workspace, :group, :path, :theme, :parent
53
+ attr_accessor :exception, :pipe, :verbose
48
54
 
49
- def initialize(name, path, workspace, *, group: nil, **kwargs)
55
+ def initialize(name, path, workspace, *,
56
+ group: nil, verbose: nil, pass: nil, exclude: nil,
57
+ common: Common::KEY[:COMMON], pipe: Common::KEY[:PIPE], **kwargs)
50
58
  @name = name.to_s
51
59
  @path = path
52
60
  @project = @path.basename.to_s
@@ -55,23 +63,82 @@ module Squared
55
63
  @depend = kwargs[:depend]
56
64
  @doc = kwargs[:doc]
57
65
  @test = kwargs[:test]
58
- @output = [kwargs[:run], nil]
59
66
  @copy = kwargs[:copy]
60
67
  @clean = kwargs[:clean]
61
- @theme = if !workspace.verbose
68
+ @exception = workspace.exception
69
+ @pipe = env_pipe(pipe, workspace.pipe)
70
+ @verbose = verbose.nil? ? workspace.verbose : verbose
71
+ @theme = if !@verbose
62
72
  {}
63
- elsif kwargs.fetch(:common, true)
73
+ elsif common
64
74
  workspace.theme
65
75
  else
66
76
  __get__(:theme)[:project][to_sym] ||= {}
67
77
  end
78
+ @output = []
68
79
  @ref = []
69
- @exclude = as_a(kwargs[:exclude], :to_sym).freeze
80
+ @children = []
81
+ @pass = (pass ? as_a(pass, :to_sym) : []).freeze
82
+ @exclude = (exclude ? as_a(exclude, :to_sym) : []).freeze
83
+ @envname = @name.gsub(/[^\w]+/, '_').upcase
84
+ @desc = @name.split(':').join(' => ')
85
+ @global = false
86
+ run_set kwargs[:run], kwargs[:env]
70
87
  initialize_ref(Base.ref)
88
+ end
89
+
90
+ def initialize_ref(ref)
91
+ @ref << ref unless @exclude.include?(ref)
92
+ end
93
+
94
+ def initialize_build(ref, **kwargs)
95
+ initialize_ref(ref)
96
+ if (@script = @workspace.script(group: @group, ref: ref))
97
+ if @script[:log] && !kwargs.key?(:log)
98
+ kwargs[:log] = @script[:log]
99
+ @log = nil
100
+ end
101
+ @depend = @script[:depend] if @depend.nil?
102
+ @doc = @script[:doc] if @doc.nil?
103
+ @test = @script[:test] if @test.nil?
104
+ @clean = @script[:clean] if @clean.nil?
105
+ @exclude = @script[:exclude] if @exclude.empty? && @script.key?(:exclude)
106
+ end
71
107
  initialize_logger(**kwargs)
108
+ return if @output[0] == false
109
+
110
+ data = @workspace.script(*@ref, @group)
111
+ if @output[0].nil?
112
+ if (scr = data[:script])
113
+ @global = true
114
+ script_set(scr, prod: kwargs[:prod])
115
+ elsif (run = data[:run])
116
+ @global = true
117
+ run_set run
118
+ end
119
+ unless data[:env]
120
+ if (scr = kwargs[:script])
121
+ @global = false
122
+ script_set scr
123
+ elsif @script && !data[:global]
124
+ if (scr = @script[:script])
125
+ @global = false
126
+ script_set scr
127
+ elsif (run = @script[:run])
128
+ @global = false
129
+ run_set run
130
+ end
131
+ end
132
+ end
133
+ elsif data[:env] && data[:run]
134
+ @global = true
135
+ run_set data[:run]
136
+ end
72
137
  end
73
138
 
74
139
  def initialize_logger(log: nil, **)
140
+ return if @log
141
+
75
142
  log = log.is_a?(::Hash) ? log.dup : { file: log }
76
143
  if (file = env('LOG_FILE')).nil? && (auto = env('LOG_AUTO'))
77
144
  file = case auto
@@ -89,49 +156,41 @@ module Squared
89
156
  begin
90
157
  file = file.realdirpath
91
158
  rescue StandardError => e
92
- raise if @workspace.exception
159
+ raise if @exception
93
160
 
94
161
  file = nil
95
- warn e if @workspace.warning
162
+ warn log_message(::Logger::WARN, e) if warning?
96
163
  end
97
164
  end
98
- log[:progname] = @name
99
- log[:level] = env('LOG_LEVEL', log[:level] || Logger::INFO, ignore: nil)
165
+ log[:progname] ||= @name
166
+ if (val = env('LOG_LEVEL', ignore: nil))
167
+ log[:level] = val
168
+ end
100
169
  log.delete(:file)
101
170
  @log = [file, log]
102
171
  end
103
172
 
104
- def initialize_build(ref, **kwargs)
105
- initialize_script(ref, **kwargs)
106
- if (val = env('BUILD', strict: true))
107
- @output[0] = val
108
- elsif @script && @output[0] != false
109
- @output[0] ||= @script[:run]
110
- end
111
- @dev = kwargs.delete(:dev)
112
- if env('BUILD', suffix: 'DEV', equals: '0')
113
- @dev = false
114
- elsif env('BUILD', suffix: 'DEV')
115
- @dev = true
116
- elsif @dev.nil?
117
- @dev = !group.nil? && workspace.dev?(group: group, global: true)
173
+ def initialize_env(dev: nil, prod: nil, **)
174
+ prefix = "BUILD_#{@envname}"
175
+ @dev = env_match("#{prefix}_DEV", dev)
176
+ @prod = env_match("#{prefix}_PROD", prod)
177
+ if (val = env('BUILD', suffix: 'ENV'))
178
+ begin
179
+ data = JSON.parse(val)
180
+ raise_error('invalid JSON object', val, hint: "#{prefix}_ENV") unless data.is_a?(::Hash)
181
+ @output[2] = data
182
+ rescue StandardError => e
183
+ log.warn e
184
+ end
118
185
  end
119
- end
186
+ return unless (val = env('BUILD', strict: true))
120
187
 
121
- def initialize_script(ref, **)
122
- initialize_ref(ref)
123
- return unless (script = workspace.script(group: group, ref: ref))
124
-
125
- @depend = script[:depend] if @depend.nil?
126
- @doc = script[:doc] if @doc.nil?
127
- @test = script[:test] if @test.nil?
128
- @clean = script[:clean] if @clean.nil?
129
- @exclude = script[:exclude] if @exclude.empty? && script.key?(:exclude)
130
- @script = script
131
- end
132
-
133
- def initialize_ref(ref)
134
- @ref << ref unless @exclude.include?(ref)
188
+ @global = false
189
+ if script?
190
+ script_set val
191
+ else
192
+ run_set(val, opts: false)
193
+ end
135
194
  end
136
195
 
137
196
  def ref
@@ -139,28 +198,51 @@ module Squared
139
198
  end
140
199
 
141
200
  def populate(*)
142
- valid = ref?(Base.ref)
143
- series = workspace.series
201
+ check = lambda do |proj, key|
202
+ workspace.series.include?(key) ? proj.has?(key, Base.ref) : workspace.task_extend?(proj, key)
203
+ end
144
204
 
145
205
  namespace name do
146
- series.each_key do |key|
147
- next unless series.include?(key) ? has?(key) && valid : workspace.task_extend?(self, key)
206
+ workspace.series.each_key do |key|
207
+ next unless check.(self, key)
148
208
 
149
- desc message(*name.split(':'), key)
150
- task key do
151
- __send__(key)
209
+ unless workspace.task_defined?("#{name}:#{key}")
210
+ desc message(@desc, key)
211
+ task key do
212
+ __send__(key)
213
+ end
152
214
  end
215
+ next if (items = @children.select { |item| check.(item, key) }).empty?
216
+
217
+ desc message(@desc, key, 'workspace')
218
+ task "#{key}:workspace" => items.map { |item| "#{item.name}:#{key}" }
153
219
  end
154
220
  end
155
221
  end
156
222
 
223
+ def add(path, name = nil, **kwargs, &blk)
224
+ return self unless source_path?(path = base_path(path))
225
+
226
+ kwargs[:group] = group unless kwargs.key?(:group)
227
+ kwargs[:ref] = ref unless kwargs.key?(:ref)
228
+ parent = self
229
+ proj = nil
230
+ workspace.add(path, name || path.basename.to_s, **kwargs) do
231
+ variable_set :parent, parent
232
+ proj = self
233
+ end
234
+ @children << proj
235
+ proj.instance_eval(&blk) if block_given?
236
+ self
237
+ end
238
+
157
239
  def build(*args, sync: true)
158
240
  if args.empty?
159
- cmd, opts = @output
160
- opts &&= shell_escape(opts)
241
+ cmd, opts, var = @output
242
+ opts &&= shell_split(opts, join: true)
161
243
  else
162
244
  cmd = args.shift
163
- opts = sanitize_args(*args)
245
+ opts = args.map { |val| shell_quote(val, force: false) }.join(' ')
164
246
  end
165
247
  if cmd
166
248
  if opts
@@ -168,24 +250,21 @@ module Squared
168
250
  elsif cmd.is_a?(::Array)
169
251
  cmd = cmd.join(' && ')
170
252
  end
171
- banner = verbose?
253
+ banner = verbose != false
172
254
  else
173
255
  return unless respond_to?(:compose)
174
256
 
175
257
  cmd = compose(opts)
176
- banner = env('REPO_BUILD') == 'verbose'
258
+ banner = verbose == 1
177
259
  end
178
- run(cmd, banner: banner, sync: sync)
260
+ run(cmd, var, banner: banner, sync: sync)
179
261
  end
180
262
 
181
263
  def refresh(*)
182
- build(sync: invoked_sync?('depend'))
183
- key = "#{name}:copy"
184
- if workspace.task_defined?(key)
185
- invoke(key, exception: workspace.exception, warning: workspace.warning)
186
- else
187
- copy
188
- end
264
+ build(sync: invoked_sync?('refresh') || invoked_sync?('build'))
265
+ return if run_task "#{name}:copy"
266
+
267
+ copy if copy?
189
268
  end
190
269
 
191
270
  def depend(*)
@@ -205,14 +284,12 @@ module Squared
205
284
  end
206
285
 
207
286
  def clean
208
- return unless @clean
209
-
210
- if @clean.is_a?(::String)
287
+ case @clean
288
+ when ::String
211
289
  run_s(@clean, sync: invoked_sync?('clean'))
212
- else
213
- @clean.each do |val|
214
- val = val.to_s
215
- if val =~ %r{[\\/]$}
290
+ when ::Enumerable
291
+ as_a(@clean).each do |val|
292
+ if (val = val.to_s) =~ %r{[\\/]$}
216
293
  dir = Pathname.new(val)
217
294
  dir = base_path(dir) unless dir.absolute?
218
295
  next unless dir.directory?
@@ -234,15 +311,43 @@ module Squared
234
311
  end
235
312
 
236
313
  def log
237
- return @log unless @log.is_a?(::Array)
238
-
239
- @log = Logger.new(enabled? ? @log[0] : nil, **@log[1])
314
+ if @log.is_a?(::Array)
315
+ @log = Logger.new(enabled? ? @log[0] : nil, **@log[1])
316
+ else
317
+ @log
318
+ end
240
319
  end
241
320
 
242
321
  def base_path(*args)
243
322
  path.join(*args)
244
323
  end
245
324
 
325
+ def color(val)
326
+ ret = theme[val]
327
+ ret && !ret.empty? ? ret : [val]
328
+ end
329
+
330
+ def variable_set(key, *val, **kwargs)
331
+ if variable_all.include?(key)
332
+ case key
333
+ when :run
334
+ run_set(*val, opts: false, **kwargs)
335
+ when :script
336
+ script_set(*val, **kwargs)
337
+ when :env
338
+ run_set(output[0], *val, opts: false, **kwargs)
339
+ else
340
+ instance_variable_set :"@#{key}", val.first
341
+ end
342
+ else
343
+ log.warn "variable_set: @#{key} (not defined)"
344
+ end
345
+ end
346
+
347
+ def variable_all
348
+ VAR_SET
349
+ end
350
+
246
351
  def inspect
247
352
  "#<#{self.class}: #{name} => #{self}>"
248
353
  end
@@ -255,12 +360,16 @@ module Squared
255
360
  name.to_sym
256
361
  end
257
362
 
258
- def enabled?
363
+ def enabled?(ref = nil)
364
+ return false if ref && !ref?(ref)
365
+
259
366
  path.directory? && !path.empty?
260
367
  end
261
368
 
262
- def has?(method)
263
- respond_to?(m = :"#{method}?") && __send__(m)
369
+ def has?(meth, ref = nil)
370
+ return false if ref && !ref?(ref)
371
+
372
+ respond_to?(meth = :"#{meth}?") && __send__(meth)
264
373
  end
265
374
 
266
375
  def ref?(val)
@@ -268,11 +377,15 @@ module Squared
268
377
  end
269
378
 
270
379
  def build?
271
- !!@output[0]
380
+ !!@output[0] || script?
381
+ end
382
+
383
+ def script?
384
+ @output[0].nil? && !!@output[1] && respond_to?(:compose)
272
385
  end
273
386
 
274
387
  def refresh?
275
- build? && (copy? || workspace.task_defined?("#{name}:copy"))
388
+ build? && copy?
276
389
  end
277
390
 
278
391
  def depend?
@@ -288,20 +401,24 @@ module Squared
288
401
  end
289
402
 
290
403
  def copy?
291
- @copy.is_a?(::String)
404
+ runnable?(@copy) || workspace.task_defined?("#{name}:copy")
292
405
  end
293
406
 
294
407
  def clean?
295
- !!@clean
408
+ runnable?(@clean) || workspace.task_defined?("#{name}:clean")
296
409
  end
297
410
 
298
411
  def dev?
299
- !!@dev
412
+ @dev != false && workspace.dev?(pat: @dev, **scriptargs)
413
+ end
414
+
415
+ def prod?
416
+ @prod != false && workspace.prod?(pat: @prod, **scriptargs)
300
417
  end
301
418
 
302
419
  private
303
420
 
304
- def run(cmd = @session, exception: workspace.exception, banner: true, sync: true, req: nil, **)
421
+ def run(cmd = @session, var = nil, exception: @exception, banner: true, sync: true, req: nil, **)
305
422
  if req && !base_path(req).exist?
306
423
  log.warn "#{req} (not found)"
307
424
  return
@@ -311,10 +428,12 @@ module Squared
311
428
  begin
312
429
  if cmd =~ /^\S+:(\S+:?)+$/ && workspace.task_defined?(cmd)
313
430
  print_item if sync
314
- invoke(cmd, exception: exception, warning: workspace.warning)
431
+ log.warn "ENV was discarded: #{var}" if var
432
+ invoke(cmd, exception: exception, warning: warning?)
315
433
  else
316
434
  print_item format_banner(cmd, banner: banner) if sync
317
- shell(cmd, chdir: path, exception: exception)
435
+ args = var.is_a?(::Hash) ? [var, cmd] : [cmd]
436
+ shell(*args, chdir: path, exception: exception)
318
437
  end
319
438
  rescue StandardError => e
320
439
  log.error e
@@ -323,15 +442,21 @@ module Squared
323
442
  end
324
443
 
325
444
  def run_s(cmd, **kwargs)
326
- run(cmd, banner: verbose?, **kwargs) if cmd.is_a?(::String)
445
+ as_a(cmd).each { |val| run(val, banner: !!verbose, **kwargs) if val.is_a?(::String) }
446
+ end
447
+
448
+ def run_task(key)
449
+ return false unless workspace.task_defined?(key)
450
+
451
+ invoke(key, exception: exception, warning: warning?)
452
+ true
327
453
  end
328
454
 
329
455
  def env(key, default = nil, equals: nil, ignore: ['0'].freeze, suffix: nil, strict: false)
330
- @env ||= name.gsub(/[^\w]+/, '_').upcase
331
- a = "#{key}_#{@env}"
456
+ a = "#{key}_#{@envname}"
332
457
  b = ''
333
458
  if suffix
334
- a = [a, suffix].flatten.join('_')
459
+ a = "#{a}_#{suffix}"
335
460
  elsif !strict
336
461
  b = ENV.fetch(key, '')
337
462
  end
@@ -341,8 +466,12 @@ module Squared
341
466
  ret.empty? || as_a(ignore).any? { |val| ret == val.to_s } ? default : ret
342
467
  end
343
468
 
344
- def session(*cmd, options: nil)
345
- if (val = ENV["#{(options || cmd.first).upcase}_OPTIONS"])
469
+ def puts(*args)
470
+ pipe == 2 ? $stderr.puts(*args) : $stdout.puts(*args)
471
+ end
472
+
473
+ def session(*cmd, prefix: nil)
474
+ if (val = ENV["#{(prefix || cmd.first).upcase}_OPTIONS"])
346
475
  split_escape(val).each { |opt| cmd << fill_option(opt) }
347
476
  end
348
477
  @session = JoinSet.new(cmd)
@@ -351,13 +480,13 @@ module Squared
351
480
  def close_session(cmd)
352
481
  return cmd unless cmd.respond_to?(:done)
353
482
 
354
- raise_error('none were provided', hint: name) if cmd.empty?
483
+ raise_error('no args were added', hint: cmd.first || name) unless cmd.size > 1
355
484
  @session = nil if cmd == @session
356
485
  cmd.done
357
486
  end
358
487
 
359
488
  def print_item(*val)
360
- puts unless @@print_order == 0 || pipe?
489
+ puts unless @@print_order == 0 || stdin?
361
490
  @@print_order += 1
362
491
  puts val unless val.empty? || (val.size == 1 && val.first.nil?)
363
492
  end
@@ -407,7 +536,7 @@ module Squared
407
536
  action = ''
408
537
  end
409
538
  req = opts ? "#{req}," : "[#{req}]" unless req.to_s.empty?
410
- message(*name.split(':'), action, opts ? "#{flag}[#{req}#{opts}]" : flag.to_s + req)
539
+ message(@desc, action, opts ? "#{flag}[#{req}#{opts}]" : flag.to_s + req)
411
540
  end
412
541
 
413
542
  def format_banner(cmd, banner: true, multiple: false)
@@ -418,7 +547,7 @@ module Squared
418
547
  else
419
548
  data = { command: true, order: %i[path], styles: theme[:banner], border: theme[:border] }
420
549
  end
421
- if verbose?
550
+ if verbose
422
551
  out = []
423
552
  out << cmd.sub(/^\S+/, &:upcase) if data[:command]
424
553
  data[:order].each { |val| out << val.to_s if (val = __send__(val)) }
@@ -432,8 +561,16 @@ module Squared
432
561
  "#{msg}#{!always && (!obj || obj == 0 || obj.to_s.empty?) ? '' : message(hint: message(title, obj.to_s))}"
433
562
  end
434
563
 
564
+ def append_repeat(flag, opts)
565
+ opts.each { |val| @session << "--#{flag}=#{shell_escape(val, quote: true)}" }
566
+ end
567
+
568
+ def append_value(opts)
569
+ opts.each { |val| @session << val }
570
+ end
571
+
435
572
  def append_nocolor
436
- @session << (!ENV.fetch('NO_COLOR', '').empty? || pipe? ? '--no-color' : '')
573
+ @session << (!ENV.fetch('NO_COLOR', '').empty? || stdin? ? '--no-color' : '')
437
574
  end
438
575
 
439
576
  def guard_params(action, flag, args: nil, key: nil, pat: nil)
@@ -450,12 +587,26 @@ module Squared
450
587
  end
451
588
  end
452
589
 
453
- def store_pwd(done = nil)
454
- if @pwd
590
+ def pwd_set(done = nil, &blk)
591
+ pwd = Pathname.pwd
592
+ if block_given?
593
+ if path == pwd
594
+ instance_eval(&blk)
595
+ else
596
+ Dir.chdir(path)
597
+ instance_eval(&blk)
598
+ Dir.chdir(pwd)
599
+ end
600
+ elsif @pwd == pwd
601
+ @pwd = nil
602
+ pwd unless done
603
+ elsif @pwd
604
+ return unless path == pwd
605
+
455
606
  Dir.chdir(@pwd)
456
607
  @pwd = nil
457
- elsif !done && Dir.pwd != path.to_s
458
- @pwd = Dir.pwd
608
+ elsif !done && path != pwd
609
+ @pwd = pwd
459
610
  Dir.chdir(path)
460
611
  @pwd
461
612
  end
@@ -474,37 +625,72 @@ module Squared
474
625
  end
475
626
 
476
627
  def semmajor(cur, want)
477
- cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]
628
+ (cur[0] == '0' && want[0] == '0' ? cur[2] != want[2] : cur[0] != want[0]) && !want[5]
478
629
  end
479
630
 
480
- def verbose?
481
- workspace.verbose
631
+ def scriptargs
632
+ { target: script? ? @output[1] : @output[0], ref: ref, group: group, global: @global }
482
633
  end
483
634
 
484
- def pipe?
485
- workspace.pipe
635
+ def run_set(cmd, val = nil, *, opts: true, **)
636
+ @output[0] = cmd
637
+ @output[1] = if cmd && opts
638
+ env('BUILD', opts.is_a?(::String) ? opts : nil, suffix: 'OPTS')
639
+ end
640
+ if val.is_a?(::Hash)
641
+ @output[2] = val
642
+ elsif val == false
643
+ @output[2] = nil
644
+ end
486
645
  end
487
646
 
488
- def invoked_sync?(action, flag = nil)
489
- action = workspace.task_name(action)
490
- return true if !flag.nil? || workspace.series.sync?("#{action}:sync")
647
+ def script_set(cmd, *, prod: nil, **)
648
+ @output[0] = nil
649
+ @output[1] = if @global && cmd.is_a?(::Array)
650
+ cmd[prod == true ? 1 : 0]
651
+ else
652
+ cmd
653
+ end
654
+ end
491
655
 
492
- check = lambda do |val|
493
- if invoked?(val)
494
- !workspace.task_defined?("#{val}:sync")
495
- elsif workspace.series.sync?("#{val}:sync")
496
- true
497
- end
498
- end
499
- if group
500
- ret = check.("#{action}:#{group}")
501
- return ret unless ret.nil?
656
+ def source_path?(val)
657
+ Pathname.new(val).absolute? ? val.to_s.start_with?(File.join(path, '')) : !val.to_s.match?(%r{^\.\.[/\\]})
658
+ end
659
+
660
+ def warning?
661
+ workspace.warning
662
+ end
663
+
664
+ def stdin?
665
+ pipe == 0
666
+ end
667
+
668
+ def runnable?(val)
669
+ case val
670
+ when ::String
671
+ true
672
+ when ::Enumerable
673
+ !val.is_a?(::Hash)
674
+ else
675
+ false
502
676
  end
503
- if (base = workspace.find_base(self))
504
- ret = check.("#{action}:#{base.ref}")
505
- return ret unless ret.nil?
677
+ end
678
+
679
+ def from_sync?(val)
680
+ if invoked?(val)
681
+ !workspace.task_defined?("#{val}:sync")
682
+ elsif workspace.series.sync?("#{val}:sync")
683
+ true
506
684
  end
507
- invoked?("#{name}:#{action}") && (!invoked?(action) || !workspace.task_defined?("#{action}:sync"))
685
+ end
686
+
687
+ def invoked_sync?(action, val = nil)
688
+ return true if !val.nil? || from_sync?(ac = workspace.task_name(action))
689
+
690
+ return val if group && !(val = from_sync?("#{ac}:#{group}")).nil?
691
+ return val if (base = workspace.find_base(self)) && !(val = from_sync?("#{ac}:#{base.ref}")).nil?
692
+
693
+ invoked?("#{name}:#{ac}") && (!invoked?(ac) || !workspace.task_defined?("#{ac}:sync"))
508
694
  end
509
695
  end
510
696
  end