rscons 1.9.3 → 1.10.0
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.
- 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
|
|