rscons 1.9.3 → 1.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rscons.rb +53 -1
- data/lib/rscons/builder.rb +184 -11
- data/lib/rscons/builders/cfile.rb +18 -3
- data/lib/rscons/builders/command.rb +24 -9
- data/lib/rscons/builders/disassemble.rb +20 -4
- data/lib/rscons/builders/library.rb +37 -16
- data/lib/rscons/builders/object.rb +35 -22
- data/lib/rscons/builders/preprocess.rb +25 -17
- data/lib/rscons/builders/program.rb +35 -12
- data/lib/rscons/builders/shared_library.rb +116 -0
- data/lib/rscons/builders/shared_object.rb +113 -0
- data/lib/rscons/cache.rb +7 -2
- data/lib/rscons/cli.rb +4 -0
- data/lib/rscons/environment.rb +394 -104
- data/lib/rscons/job_set.rb +93 -0
- data/lib/rscons/threaded_command.rb +63 -0
- data/lib/rscons/version.rb +1 -1
- metadata +7 -3
data/lib/rscons/cache.rb
CHANGED
@@ -2,7 +2,6 @@ require "digest/md5"
|
|
2
2
|
require "fileutils"
|
3
3
|
require "json"
|
4
4
|
require "set"
|
5
|
-
require "singleton"
|
6
5
|
require "rscons/version"
|
7
6
|
|
8
7
|
module Rscons
|
@@ -51,7 +50,6 @@ module Rscons
|
|
51
50
|
# },
|
52
51
|
# }
|
53
52
|
class Cache
|
54
|
-
include Singleton
|
55
53
|
|
56
54
|
# Name of the file to store cache information in
|
57
55
|
CACHE_FILE = ".rsconscache"
|
@@ -59,6 +57,13 @@ module Rscons
|
|
59
57
|
# Prefix for phony cache entries.
|
60
58
|
PHONY_PREFIX = ":PHONY:"
|
61
59
|
|
60
|
+
class << self
|
61
|
+
# Access the singleton instance.
|
62
|
+
def instance
|
63
|
+
@instance ||= Cache.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
62
67
|
# Create a Cache object and load in the previous contents from the cache
|
63
68
|
# file.
|
64
69
|
def initialize
|
data/lib/rscons/cli.rb
CHANGED
@@ -35,6 +35,10 @@ module Rscons
|
|
35
35
|
rsconsfile = f
|
36
36
|
end
|
37
37
|
|
38
|
+
opts.on("-j NTHREADS", "Use NTHREADS parallel jobs (local default #{Rscons.n_threads})") do |n_threads|
|
39
|
+
Rscons.n_threads = n_threads.to_i
|
40
|
+
end
|
41
|
+
|
38
42
|
opts.on_tail("--version", "Show version") do
|
39
43
|
puts "Rscons version #{Rscons::VERSION}"
|
40
44
|
exit 0
|
data/lib/rscons/environment.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "fileutils"
|
2
2
|
require "set"
|
3
3
|
require "shellwords"
|
4
|
+
require "thwait"
|
4
5
|
|
5
6
|
module Rscons
|
6
7
|
# The Environment class is the main programmatic interface to Rscons. It
|
@@ -13,15 +14,15 @@ module Rscons
|
|
13
14
|
# @return [Symbol] :command, :short, or :off
|
14
15
|
attr_accessor :echo
|
15
16
|
|
16
|
-
# @return [String
|
17
|
+
# @return [String] The build root.
|
17
18
|
attr_reader :build_root
|
18
19
|
|
19
20
|
# Set the build root.
|
20
21
|
#
|
21
22
|
# @param build_root [String] The build root.
|
22
23
|
def build_root=(build_root)
|
23
|
-
|
24
|
-
@build_root.gsub
|
24
|
+
raise "build_root must be non-nil" unless build_root
|
25
|
+
@build_root = build_root.gsub("\\", "/")
|
25
26
|
end
|
26
27
|
|
27
28
|
# Create an Environment object.
|
@@ -30,15 +31,17 @@ module Rscons
|
|
30
31
|
# @option options [Symbol] :echo
|
31
32
|
# :command, :short, or :off (default :short)
|
32
33
|
# @option options [String] :build_root
|
33
|
-
# Build root directory (default
|
34
|
+
# Build root directory (default "build")
|
34
35
|
# @option options [Boolean] :exclude_builders
|
35
36
|
# Whether to omit adding default builders (default false)
|
36
37
|
#
|
37
38
|
# If a block is given, the Environment object is yielded to the block and
|
38
39
|
# when the block returns, the {#process} method is automatically called.
|
39
40
|
def initialize(options = {})
|
41
|
+
@threaded_commands = Set.new
|
42
|
+
@registered_build_dependencies = {}
|
40
43
|
@varset = VarSet.new
|
41
|
-
@
|
44
|
+
@job_set = JobSet.new(@registered_build_dependencies)
|
42
45
|
@user_deps = {}
|
43
46
|
@builders = {}
|
44
47
|
@build_dirs = []
|
@@ -51,7 +54,7 @@ module Rscons
|
|
51
54
|
end
|
52
55
|
end
|
53
56
|
@echo = options[:echo] || :short
|
54
|
-
@build_root = options[:build_root]
|
57
|
+
@build_root = options[:build_root] || "build"
|
55
58
|
|
56
59
|
if block_given?
|
57
60
|
yield self
|
@@ -227,27 +230,40 @@ module Rscons
|
|
227
230
|
#
|
228
231
|
# This method takes into account the Environment's build directories.
|
229
232
|
#
|
230
|
-
# @param source_fname [String]
|
231
|
-
#
|
233
|
+
# @param source_fname [String]
|
234
|
+
# Source file name.
|
235
|
+
# @param suffix [String]
|
236
|
+
# Suffix, including "." if desired.
|
237
|
+
# @param options [Hash]
|
238
|
+
# Extra options.
|
239
|
+
# @option options [Array<String>] :features
|
240
|
+
# Builder features to be used for this build. See {#register_builds}.
|
232
241
|
#
|
233
242
|
# @return [String]
|
234
243
|
# The file name to be built from +source_fname+ with suffix +suffix+.
|
235
|
-
def get_build_fname(source_fname, suffix)
|
244
|
+
def get_build_fname(source_fname, suffix, options = {})
|
236
245
|
build_fname = Rscons.set_suffix(source_fname, suffix).gsub('\\', '/')
|
246
|
+
options[:features] ||= []
|
247
|
+
extra_path = options[:features].include?("shared") ? "/_shared" : ""
|
237
248
|
found_match = @build_dirs.find do |src_dir, obj_dir|
|
238
249
|
if src_dir.is_a?(Regexp)
|
239
|
-
build_fname.sub!(src_dir, obj_dir)
|
250
|
+
build_fname.sub!(src_dir, "#{obj_dir}#{extra_path}")
|
240
251
|
else
|
241
|
-
build_fname.sub!(%r{^#{src_dir}/}, "#{obj_dir}/")
|
252
|
+
build_fname.sub!(%r{^#{src_dir}/}, "#{obj_dir}#{extra_path}/")
|
242
253
|
end
|
243
254
|
end
|
244
|
-
|
245
|
-
|
246
|
-
build_fname
|
255
|
+
unless found_match
|
256
|
+
if Rscons.absolute_path?(build_fname)
|
257
|
+
if build_fname =~ %r{^(\w):(.*)$}
|
258
|
+
build_fname = "#{@build_root}#{extra_path}/_#{$1}#{$2}"
|
259
|
+
else
|
260
|
+
build_fname = "#{@build_root}#{extra_path}/_#{build_fname}"
|
261
|
+
end
|
262
|
+
elsif !build_fname.start_with?("#{@build_root}/")
|
263
|
+
build_fname = "#{@build_root}#{extra_path}/#{build_fname}"
|
247
264
|
end
|
248
265
|
end
|
249
|
-
build_fname.gsub
|
250
|
-
build_fname
|
266
|
+
build_fname.gsub('\\', '/')
|
251
267
|
end
|
252
268
|
|
253
269
|
# Get a construction variable's value.
|
@@ -280,40 +296,62 @@ module Rscons
|
|
280
296
|
#
|
281
297
|
# @return [void]
|
282
298
|
def process
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
299
|
+
cache = Cache.instance
|
300
|
+
begin
|
301
|
+
while @job_set.size > 0 or @threaded_commands.size > 0
|
302
|
+
|
303
|
+
targets_still_building = @threaded_commands.map do |tc|
|
304
|
+
tc.build_operation[:target]
|
305
|
+
end
|
306
|
+
job = @job_set.get_next_job_to_run(targets_still_building)
|
307
|
+
|
308
|
+
# TODO: have Cache determine when checksums may be invalid based on
|
309
|
+
# file size and/or timestamp.
|
310
|
+
cache.clear_checksum_cache!
|
311
|
+
|
312
|
+
if job
|
313
|
+
result = run_builder(job[:builder],
|
314
|
+
job[:target],
|
315
|
+
job[:sources],
|
316
|
+
cache,
|
317
|
+
job[:vars],
|
318
|
+
allow_delayed_execution: true,
|
319
|
+
setup_info: job[:setup_info])
|
320
|
+
unless result
|
321
|
+
raise BuildError.new("Failed to build #{job[:target]}")
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
completed_tcs = Set.new
|
326
|
+
# First do a non-blocking wait to pick up any threads that have
|
327
|
+
# completed since last time.
|
328
|
+
while tc = wait_for_threaded_commands(nonblock: true)
|
329
|
+
completed_tcs << tc
|
330
|
+
end
|
331
|
+
|
332
|
+
# If needed, do a blocking wait.
|
333
|
+
if (completed_tcs.empty? and job.nil?) or @threaded_commands.size >= Rscons.n_threads
|
334
|
+
completed_tcs << wait_for_threaded_commands
|
335
|
+
end
|
336
|
+
|
337
|
+
# Process all completed {ThreadedCommand} objects.
|
338
|
+
completed_tcs.each do |tc|
|
339
|
+
result = finalize_builder(tc)
|
340
|
+
if result
|
341
|
+
@build_hooks[:post].each do |build_hook_block|
|
342
|
+
build_hook_block.call(tc.build_operation)
|
298
343
|
end
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
cache,
|
303
|
-
target_params[:vars] || {})
|
304
|
-
unless result
|
305
|
-
raise BuildError.new("Failed to build #{target}")
|
344
|
+
else
|
345
|
+
unless @echo == :command
|
346
|
+
$stdout.puts "Failed command was: #{command_to_s(tc.command)}"
|
306
347
|
end
|
348
|
+
raise BuildError.new("Failed to build #{tc.build_operation[:target]}")
|
307
349
|
end
|
308
350
|
end
|
351
|
+
|
309
352
|
end
|
310
|
-
|
311
|
-
|
312
|
-
process_target.call(target)
|
313
|
-
end
|
314
|
-
ensure
|
315
|
-
cache.write
|
316
|
-
end
|
353
|
+
ensure
|
354
|
+
cache.write
|
317
355
|
end
|
318
356
|
end
|
319
357
|
|
@@ -321,7 +359,7 @@ module Rscons
|
|
321
359
|
#
|
322
360
|
# @return [void]
|
323
361
|
def clear_targets
|
324
|
-
@
|
362
|
+
@job_set.clear!
|
325
363
|
end
|
326
364
|
|
327
365
|
# Expand a construction variable reference.
|
@@ -354,11 +392,8 @@ module Rscons
|
|
354
392
|
#
|
355
393
|
# @return [true,false,nil] Return value from Kernel.system().
|
356
394
|
def execute(short_desc, command, options = {})
|
357
|
-
print_command = proc do
|
358
|
-
puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
|
359
|
-
end
|
360
395
|
if @echo == :command
|
361
|
-
|
396
|
+
puts command_to_s(command)
|
362
397
|
elsif @echo == :short
|
363
398
|
puts short_desc
|
364
399
|
end
|
@@ -366,8 +401,7 @@ module Rscons
|
|
366
401
|
options_args = options[:options] ? [options[:options]] : []
|
367
402
|
system(*env_args, *Rscons.command_executer, *command, *options_args).tap do |result|
|
368
403
|
unless result or @echo == :command
|
369
|
-
$stdout.
|
370
|
-
print_command.call
|
404
|
+
$stdout.puts "Failed command was: #{command_to_s(command)}"
|
371
405
|
end
|
372
406
|
end
|
373
407
|
end
|
@@ -383,12 +417,16 @@ module Rscons
|
|
383
417
|
def method_missing(method, *args)
|
384
418
|
if @builders.has_key?(method.to_s)
|
385
419
|
target, sources, vars, *rest = args
|
386
|
-
|
420
|
+
vars ||= {}
|
421
|
+
unless vars.is_a?(Hash) or vars.is_a?(VarSet)
|
387
422
|
raise "Unexpected construction variable set: #{vars.inspect}"
|
388
423
|
end
|
389
|
-
sources = Array(sources)
|
390
424
|
builder = @builders[method.to_s]
|
391
|
-
|
425
|
+
target = expand_path(expand_varref(target))
|
426
|
+
sources = Array(sources).map do |source|
|
427
|
+
expand_path(expand_varref(source))
|
428
|
+
end.flatten
|
429
|
+
build_target = builder.create_build_target(env: self, target: target, sources: sources, vars: vars)
|
392
430
|
add_target(build_target.to_s, builder, sources, vars, rest)
|
393
431
|
build_target
|
394
432
|
else
|
@@ -396,25 +434,6 @@ module Rscons
|
|
396
434
|
end
|
397
435
|
end
|
398
436
|
|
399
|
-
# Add a build target.
|
400
|
-
#
|
401
|
-
# @param target [String] Build target file name.
|
402
|
-
# @param builder [Builder] The {Builder} to use to build the target.
|
403
|
-
# @param sources [Array<String>] Source file name(s).
|
404
|
-
# @param vars [Hash] Construction variable overrides.
|
405
|
-
# @param args [Object] Any extra arguments passed to the {Builder}.
|
406
|
-
#
|
407
|
-
# @return [void]
|
408
|
-
def add_target(target, builder, sources, vars, args)
|
409
|
-
@targets[target] ||= []
|
410
|
-
@targets[target] << {
|
411
|
-
builder: builder,
|
412
|
-
sources: sources,
|
413
|
-
vars: vars,
|
414
|
-
args: args,
|
415
|
-
}
|
416
|
-
end
|
417
|
-
|
418
437
|
# Manually record a given target as depending on the specified files.
|
419
438
|
#
|
420
439
|
# @param target [String,BuildTarget] Target file.
|
@@ -428,6 +447,42 @@ module Rscons
|
|
428
447
|
@user_deps[target] = (@user_deps[target] + user_deps).uniq
|
429
448
|
end
|
430
449
|
|
450
|
+
# Manually record the given target(s) as needing to be built after the
|
451
|
+
# given prerequisite(s).
|
452
|
+
#
|
453
|
+
# For example, consider a builder registered to generate gen.c which also
|
454
|
+
# generates gen.h as a side-effect. If program.c includes gen.h, then it
|
455
|
+
# should not be compiled before gen.h has been generated. When using
|
456
|
+
# multiple threads to build, Rscons may attempt to compile program.c before
|
457
|
+
# gen.h has been generated because it does not know that gen.h will be
|
458
|
+
# generated along with gen.c. One way to prevent that situation would be
|
459
|
+
# to first process the Environment with just the code-generation builders
|
460
|
+
# in place and then register the compilation builders. Another way is to
|
461
|
+
# use this method to record that a certain target should not be built until
|
462
|
+
# another has completed. For example, for the situation previously
|
463
|
+
# described:
|
464
|
+
# env.build_after("program.o", "gen.c")
|
465
|
+
#
|
466
|
+
# @since 1.10.0
|
467
|
+
#
|
468
|
+
# @param targets [String, Array<String>]
|
469
|
+
# Target files to wait to build until the prerequisites are finished
|
470
|
+
# building.
|
471
|
+
# @param prerequisites [String, Array<String>]
|
472
|
+
# Files that must be built before building the specified targets.
|
473
|
+
#
|
474
|
+
# @return [void]
|
475
|
+
def build_after(targets, prerequisites)
|
476
|
+
targets = Array(targets)
|
477
|
+
prerequisites = Array(prerequisites)
|
478
|
+
targets.each do |target|
|
479
|
+
@registered_build_dependencies[target] ||= Set.new
|
480
|
+
prerequisites.each do |prerequisite|
|
481
|
+
@registered_build_dependencies[target] << prerequisite
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
431
486
|
# Return the list of user dependencies for a given target.
|
432
487
|
#
|
433
488
|
# @param target [String] Target file name.
|
@@ -444,6 +499,8 @@ module Rscons
|
|
444
499
|
#
|
445
500
|
# This method is used internally by Rscons builders.
|
446
501
|
#
|
502
|
+
# @deprecated Use {#register_builds} instead.
|
503
|
+
#
|
447
504
|
# @param sources [Array<String>] List of source files to build.
|
448
505
|
# @param suffixes [Array<String>]
|
449
506
|
# List of suffixes to try to convert source files into.
|
@@ -459,8 +516,7 @@ module Rscons
|
|
459
516
|
converted = nil
|
460
517
|
suffixes.each do |suffix|
|
461
518
|
converted_fname = get_build_fname(source, suffix)
|
462
|
-
builder =
|
463
|
-
if builder
|
519
|
+
if builder = find_builder_for(converted_fname, source, [])
|
464
520
|
converted = run_builder(builder, converted_fname, [source], cache, vars)
|
465
521
|
return nil unless converted
|
466
522
|
break
|
@@ -471,6 +527,54 @@ module Rscons
|
|
471
527
|
end
|
472
528
|
end
|
473
529
|
|
530
|
+
# Find and register builders to build source files into files containing
|
531
|
+
# one of the suffixes given by suffixes.
|
532
|
+
#
|
533
|
+
# This method is used internally by Rscons builders. It should be called
|
534
|
+
# from the builder's #setup method.
|
535
|
+
#
|
536
|
+
# @since 1.10.0
|
537
|
+
#
|
538
|
+
# @param target [String]
|
539
|
+
# The target that depends on these builds.
|
540
|
+
# @param sources [Array<String>]
|
541
|
+
# List of source file(s) to build.
|
542
|
+
# @param suffixes [Array<String>]
|
543
|
+
# List of suffixes to try to convert source files into.
|
544
|
+
# @param vars [Hash]
|
545
|
+
# Extra variables to pass to the builders.
|
546
|
+
# @param options [Hash]
|
547
|
+
# Extra options.
|
548
|
+
# @option options [Array<String>] :features
|
549
|
+
# Set of features the builder must provide. Each feature can be proceeded
|
550
|
+
# by a "-" character to indicate that the builder must /not/ provide the
|
551
|
+
# given feature.
|
552
|
+
# * shared - builder builds a shared object/library
|
553
|
+
#
|
554
|
+
# @return [Array<String>]
|
555
|
+
# List of the output file name(s).
|
556
|
+
def register_builds(target, sources, suffixes, vars, options = {})
|
557
|
+
options[:features] ||= []
|
558
|
+
@registered_build_dependencies[target] ||= Set.new
|
559
|
+
sources.map do |source|
|
560
|
+
if source.end_with?(*suffixes)
|
561
|
+
source
|
562
|
+
else
|
563
|
+
output_fname = nil
|
564
|
+
suffixes.each do |suffix|
|
565
|
+
attempt_output_fname = get_build_fname(source, suffix, features: options[:features])
|
566
|
+
if builder = find_builder_for(attempt_output_fname, source, options[:features])
|
567
|
+
output_fname = attempt_output_fname
|
568
|
+
self.__send__(builder.name, output_fname, source, vars)
|
569
|
+
@registered_build_dependencies[target] << output_fname
|
570
|
+
break
|
571
|
+
end
|
572
|
+
end
|
573
|
+
output_fname or raise "Could not find a builder for #{source.inspect}."
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
474
578
|
# Invoke a builder to build the given target based on the given sources.
|
475
579
|
#
|
476
580
|
# @param builder [Builder] The Builder to use.
|
@@ -478,25 +582,66 @@ module Rscons
|
|
478
582
|
# @param sources [Array<String>] List of source files.
|
479
583
|
# @param cache [Cache] The Cache.
|
480
584
|
# @param vars [Hash] Extra variables to pass to the builder.
|
585
|
+
# @param options [Hash]
|
586
|
+
# @since 1.10.0
|
587
|
+
# Options.
|
588
|
+
# @option options [Boolean] :allow_delayed_execution
|
589
|
+
# @since 1.10.0
|
590
|
+
# Allow a threaded command to be scheduled but not yet completed before
|
591
|
+
# this method returns.
|
592
|
+
# @option options [Object] :setup_info
|
593
|
+
# Arbitrary builder info returned by Builder#setup.
|
481
594
|
#
|
482
595
|
# @return [String,false] Return value from the {Builder}'s +run+ method.
|
483
|
-
def run_builder(builder, target, sources, cache, vars)
|
596
|
+
def run_builder(builder, target, sources, cache, vars, options = {})
|
484
597
|
vars = @varset.merge(vars)
|
598
|
+
build_operation = {
|
599
|
+
builder: builder,
|
600
|
+
target: target,
|
601
|
+
sources: sources,
|
602
|
+
cache: cache,
|
603
|
+
env: self,
|
604
|
+
vars: vars,
|
605
|
+
setup_info: options[:setup_info]
|
606
|
+
}
|
485
607
|
call_build_hooks = lambda do |sec|
|
486
608
|
@build_hooks[sec].each do |build_hook_block|
|
487
|
-
build_operation = {
|
488
|
-
builder: builder,
|
489
|
-
target: target,
|
490
|
-
sources: sources,
|
491
|
-
vars: vars,
|
492
|
-
env: self,
|
493
|
-
}
|
494
609
|
build_hook_block.call(build_operation)
|
495
610
|
end
|
496
611
|
end
|
612
|
+
|
613
|
+
# Invoke pre-build hooks.
|
497
614
|
call_build_hooks[:pre]
|
498
|
-
|
499
|
-
|
615
|
+
|
616
|
+
# Call the builder's #run method.
|
617
|
+
if builder.method(:run).arity == 5
|
618
|
+
rv = builder.run(*build_operation.values_at(:target, :sources, :cache, :env, :vars))
|
619
|
+
else
|
620
|
+
rv = builder.run(build_operation)
|
621
|
+
end
|
622
|
+
|
623
|
+
if rv.is_a?(ThreadedCommand)
|
624
|
+
# Store the build operation so the post-build hooks can be called
|
625
|
+
# with it when the threaded command completes.
|
626
|
+
rv.build_operation = build_operation
|
627
|
+
start_threaded_command(rv)
|
628
|
+
unless options[:allow_delayed_execution]
|
629
|
+
# Delayed command execution is not allowed, so we need to execute
|
630
|
+
# the command and finalize the builder now.
|
631
|
+
tc = wait_for_threaded_commands(which: [rv])
|
632
|
+
rv = finalize_builder(tc)
|
633
|
+
if rv
|
634
|
+
call_build_hooks[:post]
|
635
|
+
else
|
636
|
+
unless @echo == :command
|
637
|
+
$stdout.puts "Failed command was: #{command_to_s(tc.command)}"
|
638
|
+
end
|
639
|
+
end
|
640
|
+
end
|
641
|
+
else
|
642
|
+
call_build_hooks[:post] if rv
|
643
|
+
end
|
644
|
+
|
500
645
|
rv
|
501
646
|
end
|
502
647
|
|
@@ -505,14 +650,20 @@ module Rscons
|
|
505
650
|
# Paths beginning with "^/" are expanded by replacing "^" with the
|
506
651
|
# Environment's build root.
|
507
652
|
#
|
508
|
-
# @param path [String]
|
653
|
+
# @param path [String, Array<String>]
|
654
|
+
# The path(s) to expand.
|
509
655
|
#
|
510
|
-
# @return [String]
|
656
|
+
# @return [String, Array<String>]
|
657
|
+
# The expanded path(s).
|
511
658
|
def expand_path(path)
|
512
659
|
if Rscons.phony_target?(path)
|
513
660
|
path
|
661
|
+
elsif path.is_a?(Array)
|
662
|
+
path.map do |path|
|
663
|
+
expand_path(path)
|
664
|
+
end
|
514
665
|
else
|
515
|
-
path.sub(%r{^\^(?=[\\/])}, @build_root)
|
666
|
+
path.sub(%r{^\^(?=[\\/])}, @build_root).gsub("\\", "/")
|
516
667
|
end
|
517
668
|
end
|
518
669
|
|
@@ -676,25 +827,164 @@ module Rscons
|
|
676
827
|
|
677
828
|
private
|
678
829
|
|
679
|
-
#
|
830
|
+
# Add a build target.
|
831
|
+
#
|
832
|
+
# @param target [String] Build target file name.
|
833
|
+
# @param builder [Builder] The {Builder} to use to build the target.
|
834
|
+
# @param sources [Array<String>] Source file name(s).
|
835
|
+
# @param vars [Hash] Construction variable overrides.
|
836
|
+
# @param args [Object] Deprecated; unused.
|
837
|
+
#
|
838
|
+
# @return [void]
|
839
|
+
def add_target(target, builder, sources, vars, args)
|
840
|
+
setup_info = builder.setup(
|
841
|
+
target: target,
|
842
|
+
sources: sources,
|
843
|
+
env: self,
|
844
|
+
vars: vars)
|
845
|
+
@job_set.add_job(
|
846
|
+
builder: builder,
|
847
|
+
target: target,
|
848
|
+
sources: sources,
|
849
|
+
vars: vars,
|
850
|
+
setup_info: setup_info)
|
851
|
+
end
|
852
|
+
|
853
|
+
# Start a threaded command in a new thread.
|
680
854
|
#
|
681
|
-
#
|
682
|
-
#
|
683
|
-
# "^/" prefixes to the Environment's build root if a build root is defined.
|
855
|
+
# @param tc [ThreadedCommand]
|
856
|
+
# The ThreadedCommand to start.
|
684
857
|
#
|
685
858
|
# @return [void]
|
686
|
-
def
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
859
|
+
def start_threaded_command(tc)
|
860
|
+
if @echo == :command
|
861
|
+
puts command_to_s(tc.command)
|
862
|
+
elsif @echo == :short
|
863
|
+
if tc.short_description
|
864
|
+
puts tc.short_description
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
env_args = tc.system_env ? [tc.system_env] : []
|
869
|
+
options_args = tc.system_options ? [tc.system_options] : []
|
870
|
+
system_args = [*env_args, *Rscons.command_executer, *tc.command, *options_args]
|
871
|
+
|
872
|
+
tc.thread = Thread.new do
|
873
|
+
system(*system_args)
|
874
|
+
end
|
875
|
+
@threaded_commands << tc
|
876
|
+
end
|
877
|
+
|
878
|
+
# Wait for threaded commands to complete.
|
879
|
+
#
|
880
|
+
# @param options [Hash]
|
881
|
+
# Options.
|
882
|
+
# @option options [Set<ThreadedCommand>, Array<ThreadedCommand>] :which
|
883
|
+
# Which {ThreadedCommand} objects to wait for. If not specified, this
|
884
|
+
# method will wait for any.
|
885
|
+
# @option options [Boolean] :nonblock
|
886
|
+
# Set to true to not block.
|
887
|
+
#
|
888
|
+
# @return [ThreadedCommand, nil]
|
889
|
+
# The {ThreadedCommand} object that is finished.
|
890
|
+
def wait_for_threaded_commands(options = {})
|
891
|
+
options[:which] ||= @threaded_commands
|
892
|
+
threads = options[:which].map(&:thread)
|
893
|
+
if finished_thread = find_finished_thread(threads, options[:nonblock])
|
894
|
+
threaded_command = @threaded_commands.find do |tc|
|
895
|
+
tc.thread == finished_thread
|
896
|
+
end
|
897
|
+
@threaded_commands.delete(threaded_command)
|
898
|
+
threaded_command
|
899
|
+
end
|
900
|
+
end
|
901
|
+
|
902
|
+
# Check if any of the requested threads are finished.
|
903
|
+
#
|
904
|
+
# @param threads [Array<Thread>]
|
905
|
+
# The threads to check.
|
906
|
+
# @param nonblock [Boolean]
|
907
|
+
# Whether to be non-blocking. If true, nil will be returned if no thread
|
908
|
+
# is finished. If false, the method will wait until one of the threads
|
909
|
+
# is finished.
|
910
|
+
#
|
911
|
+
# @return [Thread, nil]
|
912
|
+
# The finished thread, if any.
|
913
|
+
def find_finished_thread(threads, nonblock)
|
914
|
+
if nonblock
|
915
|
+
threads.find do |thread|
|
916
|
+
!thread.alive?
|
917
|
+
end
|
918
|
+
else
|
919
|
+
if threads.empty?
|
920
|
+
raise "No threads to wait for"
|
921
|
+
end
|
922
|
+
ThreadsWait.new(*threads).next_wait
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
# Return a string representation of a command.
|
927
|
+
#
|
928
|
+
# @param command [Array<String>]
|
929
|
+
# The command.
|
930
|
+
#
|
931
|
+
# @return [String]
|
932
|
+
# The string representation of the command.
|
933
|
+
def command_to_s(command)
|
934
|
+
command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
|
935
|
+
end
|
936
|
+
|
937
|
+
# Call a builder's #finalize method after a ThreadedCommand terminates.
|
938
|
+
#
|
939
|
+
# @param tc [ThreadedCommand]
|
940
|
+
# The ThreadedCommand returned from the builder's #run method.
|
941
|
+
#
|
942
|
+
# @return [String, false]
|
943
|
+
# Result of Builder#finalize.
|
944
|
+
def finalize_builder(tc)
|
945
|
+
tc.build_operation[:builder].finalize(
|
946
|
+
tc.build_operation.merge(
|
947
|
+
command_status: tc.thread.value,
|
948
|
+
tc: tc))
|
949
|
+
end
|
950
|
+
|
951
|
+
# Find a builder that meets the requested features and produces a target
|
952
|
+
# of the requested name.
|
953
|
+
#
|
954
|
+
# @param target [String]
|
955
|
+
# Target file name.
|
956
|
+
# @param source [String]
|
957
|
+
# Source file name.
|
958
|
+
# @param features [Array<String>]
|
959
|
+
# See {#register_builds}.
|
960
|
+
#
|
961
|
+
# @return [Builder, nil]
|
962
|
+
# The builder found, if any.
|
963
|
+
def find_builder_for(target, source, features)
|
964
|
+
@builders.values.find do |builder|
|
965
|
+
features_met?(builder, features) and builder.produces?(target, source, self)
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
969
|
+
# Determine if a builder meets the requested features.
|
970
|
+
#
|
971
|
+
# @param builder [Builder]
|
972
|
+
# The builder.
|
973
|
+
# @param features [Array<String>]
|
974
|
+
# See {#register_builds}.
|
975
|
+
#
|
976
|
+
# @return [Boolean]
|
977
|
+
# Whether the builder meets the requested features.
|
978
|
+
def features_met?(builder, features)
|
979
|
+
builder_features = builder.features
|
980
|
+
features.all? do |feature|
|
981
|
+
want_feature = true
|
982
|
+
if feature =~ /^-(.*)$/
|
983
|
+
want_feature = false
|
984
|
+
feature = $1
|
696
985
|
end
|
697
|
-
|
986
|
+
builder_has_feature = builder_features.include?(feature)
|
987
|
+
want_feature ? builder_has_feature : !builder_has_feature
|
698
988
|
end
|
699
989
|
end
|
700
990
|
|