Class: Rscons::Environment

Inherits:
BasicEnvironment show all
Defined in:
lib/rscons/environment.rb

Overview

The Environment class is the main programmatic interface to Rscons. It contains a collection of construction variables, options, builders, and rules for building targets.

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BasicEnvironment

#[], #[]=, #append, #apply_configuration_data!, #dump, #expand_varref, #get_var, #load_configuration_data!, #load_task_param_variables!, #merge_flags, #parse_flags, #parse_flags!, #shell

Constructor Details

#initialize(*args, &block) ⇒ Environment

Create an Environment object.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/rscons/environment.rb', line 75

def initialize(*args, &block)
  @id = self.class.get_id
  if args.first.is_a?(String)
    base_name = args.slice!(0)
  else
    base_name = "e.#{@id}"
  end
  variant_keys = (Rscons.application.active_variants || []).map do |variant|
    variant[:key]
  end.compact
  @name = [base_name, *variant_keys].join("-")
  options = args.first || {}
  unless Cache.instance["configuration_data"]["configured"]
    raise "Project must be configured before creating an Environment"
  end
  super(options)
  # Hash of Thread object => {Command} or {Builder}.
  @threads = {}
  @registered_build_dependencies = {}
  # Set of side-effect files that have not yet been built.
  @side_effects = Set.new
  @builder_sets = []
  @build_targets = {}
  @user_deps = {}
  # Hash of builder name (String) => builder class (Class).
  @builders = {}
  @build_hooks = {pre: [], post: []}
  unless options[:exclude_builders]
    DEFAULT_BUILDERS.each do |builder_class_name|
      builder_class = Builders.const_get(builder_class_name)
      builder_class or raise "Could not find builder class #{builder_class_name}"
      add_builder(builder_class)
    end
  end
  @echo =
    if options[:echo]
      options[:echo]
    elsif Rscons.application.verbose
      :command
    else
      :short
    end
  @build_root = "#{Rscons.application.build_dir}/#{@name}"
  @n_threads = Rscons.application.n_threads
  @build_steps = 0
  self.class.register(self)
  if block
    Environment.running_environment = self
    block[self]
    Environment.running_environment = nil
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *args) ⇒ Builder

Define a build target.

Parameters:

  • method (Symbol)

    Method name.

  • args (Array)

    Method arguments.

Returns:



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
# File 'lib/rscons/environment.rb', line 341

def method_missing(method, *args)
  if @builders.has_key?(method.to_s)
    target, sources, vars, *rest = args
    vars ||= {}
    unless vars.is_a?(Hash) or vars.is_a?(VarSet)
      raise "Unexpected construction variable set: #{vars.inspect}"
    end
    target = expand(target)
    sources = Array(sources).map do |source|
      source = source.target if source.is_a?(Builder)
      expand(source)
    end.flatten
    builder = @builders[method.to_s].new(
      target: target,
      sources: sources,
      cache: Cache.instance,
      env: self,
      vars: vars)
    if @builder_sets.empty?
      @builder_sets << build_builder_set
    end
    @builder_sets.last << builder
    @build_steps += 1
    @build_targets[target] = builder
    builder
  else
    super
  end
end

Class Attribute Details

.running_environmentEnvironment

Returns The Environment that is currently executing a construction block.

Returns:

  • (Environment)

    The Environment that is currently executing a construction block.



15
16
17
# File 'lib/rscons/environment.rb', line 15

def running_environment
  @running_environment
end

Instance Attribute Details

#build_rootString (readonly)

Returns The build root.

Returns:

  • (String)

    The build root.



63
64
65
# File 'lib/rscons/environment.rb', line 63

def build_root
  @build_root
end

#buildersHash (readonly)

Returns Set of {“builder_name” => builder_object} pairs.

Returns:

  • (Hash)

    Set of {“builder_name” => builder_object} pairs.



57
58
59
# File 'lib/rscons/environment.rb', line 57

def builders
  @builders
end

#echoSymbol

Returns :command, :short, or :off.

Returns:

  • (Symbol)

    :command, :short, or :off



60
61
62
# File 'lib/rscons/environment.rb', line 60

def echo
  @echo
end

#n_threadsInteger

Returns The number of threads to use for this Environment. Defaults to the global Rscons.application.n_threads value.

Returns:

  • (Integer)

    The number of threads to use for this Environment. Defaults to the global Rscons.application.n_threads value.



68
69
70
# File 'lib/rscons/environment.rb', line 68

def n_threads
  @n_threads
end

#nameString (readonly)

Returns Environment name.

Returns:

  • (String)

    Environment name.



72
73
74
# File 'lib/rscons/environment.rb', line 72

def name
  @name
end

Class Method Details

.[](name = nil) ⇒ Object

Get an Environment by name.

Parameters:

  • name (String) (defaults to: nil)

    Environment name.



46
47
48
49
50
51
52
# File 'lib/rscons/environment.rb', line 46

def [](name = nil)
  if name
    @environments_by_name[name]
  else
    @environments
  end
end

.class_initObject

Initialize class instance variables.



18
19
20
21
# File 'lib/rscons/environment.rb', line 18

def class_init
  @environments_by_name = {}
  @environments = []
end

.get_idInteger

Get an ID for a new Environment. This is a monotonically increasing integer.

Returns:

  • (Integer)

    Environment ID.



28
29
30
31
32
# File 'lib/rscons/environment.rb', line 28

def get_id
  @id ||= 0
  @id += 1
  @id
end

.register(env) ⇒ Object

Register an Environment.



35
36
37
38
39
40
# File 'lib/rscons/environment.rb', line 35

def register(env)
  @environments << env
  if env.name
    @environments_by_name[env.name] = env
  end
end

Instance Method Details

#add_build_hook {|build_op| ... } ⇒ void

This method returns an undefined value.

Add a build hook to the Environment.

Build hooks are Ruby blocks which are invoked immediately before a build operation takes place. Build hooks have an opportunity to modify the construction variables in use for the build operation based on the builder in use, target file name, or sources. Build hooks can also register new build targets.

Yields:

  • (build_op)

    Invoke the given block with the current build operation.

Yield Parameters:

  • build_op (Hash)

    Hash with keys:

    • :builder - The builder object in use.

    • :target - Target file name.

    • :sources - List of source file(s).

    • :vars - Set of construction variable values in use.

    • :env - The Environment invoking the builder.



236
237
238
# File 'lib/rscons/environment.rb', line 236

def add_build_hook(&block)
  @build_hooks[:pre] << block
end

#add_builder(builder_class) ⇒ void #add_builder(name, &action) ⇒ void

This method returns an undefined value.

Add a Builder to the Environment.

Overloads:

  • #add_builder(builder_class) ⇒ void

    Add the given builder to the Environment.

    Parameters:

    • builder_class (Class)

      A builder class to register.

  • #add_builder(name, &action) ⇒ void

    Create a new Builders::SimpleBuilder instance and add it to the environment.

    Parameters:

    • name (String, Symbol)

      The name of the builder to add.

    • action (Block)

      A block that will be called when the builder is executed to generate a target file. The provided block should have the same prototype as Builder#run.



207
208
209
210
211
212
213
214
215
# File 'lib/rscons/environment.rb', line 207

def add_builder(builder_class, &action)
  if builder_class.is_a?(String) or builder_class.is_a?(Symbol)
    name = builder_class.to_s
    builder_class = BuilderBuilder.new(Rscons::Builders::SimpleBuilder, name, &action)
  else
    name = builder_class.name
  end
  @builders[name] = builder_class
end

#add_post_build_hook {|build_op| ... } ⇒ void

This method returns an undefined value.

Add a post build hook to the Environment.

Post-build hooks are Ruby blocks which are invoked immediately after a build operation takes place. Post-build hooks are only invoked if the build operation succeeded. Post-build hooks can register new build targets.

Yields:

  • (build_op)

    Invoke the given block with the current build operation.

Yield Parameters:

  • build_op (Hash)

    Hash with keys:

    • :builder - The builder object in use.

    • :target - Target file name.

    • :sources - List of source file(s).

    • :vars - Set of construction variable values in use.

    • :env - The Environment invoking the builder.



258
259
260
# File 'lib/rscons/environment.rb', line 258

def add_post_build_hook(&block)
  @build_hooks[:post] << block
end

#barrierObject

Mark a “barrier” point.

Rscons will wait for all build targets registered before the barrier to be built before beginning to build any build targets registered after the barrier. In other words, Rscons will not parallelize build operations across a barrier.



584
585
586
# File 'lib/rscons/environment.rb', line 584

def barrier
  @builder_sets << build_builder_set
end

#build_after(targets, prerequisites) ⇒ void

This method returns an undefined value.

Manually record the given target(s) as needing to be built after the given prerequisite(s).

For example, consider a builder registered to generate gen.c which also generates gen.h as a side-effect. If program.c includes gen.h, then it should not be compiled before gen.h has been generated. When using multiple threads to build, Rscons may attempt to compile program.c before gen.h has been generated because it does not know that gen.h will be generated along with gen.c. One way to prevent that situation would be to first process the Environment with just the code-generation builders in place and then register the compilation builders. Another way is to use this method to record that a certain target should not be built until another has completed. For example, for the situation previously described:

env.build_after("program.o", "gen.c")

Parameters:

  • targets (String, Array<String>)

    Target files to wait to build until the prerequisites are finished building.

  • prerequisites (String, Builder, Array<String, Builder>)

    Files that must be built before building the specified targets.



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# File 'lib/rscons/environment.rb', line 416

def build_after(targets, prerequisites)
  targets = Array(targets)
  prerequisites = Array(prerequisites)
  targets.each do |target|
    target = expand(target)
    @registered_build_dependencies[target] ||= Set.new
    prerequisites.each do |prerequisite|
      if prerequisite.is_a?(Builder)
        prerequisite = prerequisite.target
      end
      prerequisite = expand(prerequisite)
      @registered_build_dependencies[target] << prerequisite
    end
  end
end

#builder_for(target) ⇒ Builder?

Get the Builder for a target.

Returns:



574
575
576
# File 'lib/rscons/environment.rb', line 574

def builder_for(target)
  @build_targets[target]
end

#clear_targetsvoid

This method returns an undefined value.

Clear all targets registered for the Environment.



329
330
331
# File 'lib/rscons/environment.rb', line 329

def clear_targets
  @builder_sets.clear
end

#clone(*args, &block) ⇒ Environment

Make a copy of the Environment object.

By default, a cloned environment will contain a copy of all environment options, construction variables, and builders, but not a copy of the targets, build hooks, build directories, or the build root.

Exactly which items are cloned are controllable via the optional :clone parameter, which can be :none, :all, or a set or array of any of the following:

  • :variables to clone construction variables (on by default)

  • :builders to clone the builders (on by default)

  • :build_hooks to clone the build hooks (on by default)

If a block is given, the Environment object is yielded to the block and when the block returns, the #process method is automatically called.

Any options that #initialize receives can also be specified here.

Returns:



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rscons/environment.rb', line 147

def clone(*args, &block)
  if args.first.is_a?(String)
    name = args.slice!(0)
  end
  options = args.first || {}
  options = options.dup
  clone = options[:clone] || :all
  clone = Set[:variables, :builders, :build_hooks] if clone == :all
  clone = Set[] if clone == :none
  clone = Set.new(clone) if clone.is_a?(Array)
  clone.delete(:builders) if options[:exclude_builders]
  options[:echo] ||= @echo
  new_args = name ? [name] : []
  new_args << options.merge(exclude_builders: true)
  env = self.class.new(*new_args)
  if clone.include?(:builders)
    @builders.each do |builder_name, builder|
      env.add_builder(builder)
    end
  end
  env.append(@varset) if clone.include?(:variables)
  if clone.include?(:build_hooks)
    @build_hooks[:pre].each do |build_hook_block|
      env.add_build_hook(&build_hook_block)
    end
    @build_hooks[:post].each do |build_hook_block|
      env.add_post_build_hook(&build_hook_block)
    end
  end
  env.instance_variable_set(:@n_threads, @n_threads)
  if block
    Environment.running_environment = self
    block[env]
    Environment.running_environment = nil
  end
  env
end

#depends(target, *user_deps) ⇒ void

This method returns an undefined value.

Manually record a given target as depending on the specified files.

Parameters:

  • target (String, Builder)

    Target file.

  • user_deps (Array<String, Builder>)

    Dependency files.



377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/rscons/environment.rb', line 377

def depends(target, *user_deps)
  if target.is_a?(Builder)
    target = target.target
  end
  target = expand(target.to_s)
  user_deps = user_deps.map do |ud|
    if ud.is_a?(Builder)
      ud = ud.target
    end
    expand(ud)
  end
  @user_deps[target] ||= []
  @user_deps[target] = (@user_deps[target] + user_deps).uniq
  build_after(target, user_deps)
end

#expand(expr) ⇒ String+

Expand construction variable references and paths.

Parameters:

  • expr (String)

    Expression to expand. Can contain construction variable references and a path.

Returns:

  • (String, Array<String>)

    Expanded value.



534
535
536
# File 'lib/rscons/environment.rb', line 534

def expand(expr)
  expand_path(expand_varref(expr))
end

#expand_path(path) ⇒ String+

Expand paths.

Paths beginning with “^/” are expanded by replacing “^” with the Environment’s build root (e.g. “build/envname”). Paths beginning with “^^/” are expanded by replacing “^^” with the top-level build directory (e.g. “build”).

Parameters:

  • path (String, Array<String>)

    The path(s) to expand.

Returns:

  • (String, Array<String>)

    The expanded path(s).



514
515
516
517
518
519
520
521
522
523
524
# File 'lib/rscons/environment.rb', line 514

def expand_path(path)
  if Rscons.phony_target?(path)
    path
  elsif path.is_a?(Array)
    path.map do |path|
      expand_path(path)
    end
  else
    path.sub(%r{^\^\^(?=[\\/])}, Rscons.application.build_dir).sub(%r{^\^(?=[\\/])}, @build_root).gsub("\\", "/")
  end
end

#get_build_fname(source_fname, suffix, builder_class) ⇒ String

Return the file name to be built from source_fname with suffix suffix.

This method takes into account the Environment’s build directories.

Parameters:

  • source_fname (String)

    Source file name.

  • suffix (String)

    Suffix, including “.” if desired.

  • builder_class (Class)

    The builder in use.

Returns:

  • (String)

    The file name to be built from source_fname with suffix suffix.



276
277
278
279
280
281
# File 'lib/rscons/environment.rb', line 276

def get_build_fname(source_fname, suffix, builder_class)
  if extra_path = builder_class.extra_path
    extra_path = "/#{extra_path}"
  end
  "#{@build_root}#{extra_path}/#{Util.make_relative_path("#{source_fname}#{suffix}")}".gsub("\\", "/")
end

#get_user_deps(target) ⇒ Array<String>?

Return the list of user dependencies for a given target.

Parameters:

  • target (String)

    Target file name.

Returns:

  • (Array<String>, nil)

    List of user-specified dependencies for the target, or nil if none were specified.



471
472
473
# File 'lib/rscons/environment.rb', line 471

def get_user_deps(target)
  @user_deps[target]
end

This method returns an undefined value.

Print the builder run message, depending on the Environment’s echo mode.

Parameters:

  • builder (Builder)

    The Builder that is executing.

  • short_description (String)

    Builder short description, printed if the echo mode is :short, or if there is no command.

  • command (Array<String>, nil)

    Builder command, printed if the echo mode is :command.



549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/rscons/environment.rb', line 549

def print_builder_run_message(builder, short_description, command)
  case @echo
  when :command
    if command.is_a?(Array)
      message = Util.command_to_s(command)
    elsif command.is_a?(String)
      message = command
    elsif short_description.is_a?(String)
      message = short_description
    end
  when :short
    message = short_description if short_description
  end
  if message
    total_build_steps = @build_steps.to_s
    this_build_step = sprintf("%#{total_build_steps.size}d", builder.build_step)
    progress = "[#{this_build_step}/#{total_build_steps}]"
    Ansi.write($stdout, *Util.colorize_markup("#{progress} #{message}"), "\n")
  end
end

#processvoid

This method returns an undefined value.

Build all build targets specified in the Environment.

When a block is passed to Environment.new, this method is automatically called after the block returns.



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/rscons/environment.rb', line 289

def process
  Cache.instance.clear_checksum_cache!
  @process_failures = []
  @process_blocking_wait = false
  @process_commands_waiting_to_run = []
  @process_builder_waits = {}
  @process_builders_to_run = []
  @build_step = 0
  @build_steps = @builder_sets.reduce(0) do |result, builder_set|
    result + builder_set.build_steps_remaining
  end
  begin
    while @builder_sets.size > 0 or @threads.size > 0 or @process_commands_waiting_to_run.size > 0
      process_step
      if @builder_sets.size > 0 and @builder_sets.first.empty? and @threads.empty? and @process_commands_waiting_to_run.empty? and @process_builders_to_run.empty?
        # Remove empty BuilderSet when all other operations have completed.
        @builder_sets.slice!(0)
      end
      unless @process_failures.empty?
        # On a build failure, do not start any more builders or commands,
        # but let the threads that have already been started complete.
        @builder_sets.clear
        @process_commands_waiting_to_run.clear
      end
    end
  ensure
    Cache.instance.write
  end
  unless @process_failures.empty?
    msg = @process_failures.join("\n")
    if Cache.instance["failed_commands"].size > 0
      msg += "\nUse `#{Util.command_to_execute_me} -F` to view the failed command log from the previous build operation"
    end
    raise RsconsError.new(msg)
  end
end

#produces(target, *side_effects) ⇒ void

This method returns an undefined value.

Manually record the given side effect file(s) as being produced when the named target is produced.

Parameters:

  • target (String)

    Target of a build operation.

  • side_effects (Array<String>)

    File(s) produced when the target file is produced.



441
442
443
444
445
446
447
448
449
450
# File 'lib/rscons/environment.rb', line 441

def produces(target, *side_effects)
  target = expand(target)
  @builder_sets.reverse.each do |builder_set|
    if builders = builder_set[target]
      builders.last.produces(*side_effects)
      return
    end
  end
  raise "Could not find a registered build target #{target.inspect}"
end

#register_dependency_build(target, source, suffix, vars, builder_class) ⇒ String

Register a builder to build a source file into an output with the given suffix.

This method is used internally by Rscons builders. It can be called from the builder’s #initialize method.

Parameters:

  • target (String)

    The target that depends on these builds.

  • source (String)

    Source file to build.

  • suffix (String)

    Suffix to try to convert source files into.

  • vars (Hash)

    Extra variables to pass to the builders.

  • builder_class (Class)

    The builder class to use.

Returns:

  • (String)

    Output file name.



494
495
496
497
498
499
500
# File 'lib/rscons/environment.rb', line 494

def register_dependency_build(target, source, suffix, vars, builder_class)
  output_fname = get_build_fname(source, suffix, builder_class)
  self.__send__(builder_class.name, output_fname, source, vars)
  @registered_build_dependencies[target] ||= Set.new
  @registered_build_dependencies[target] << output_fname
  output_fname
end

#register_side_effect(side_effect) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Register a side effect file.

This is an internally used method.

Parameters:

  • side_effect (String)

    Side effect fiel name.



460
461
462
# File 'lib/rscons/environment.rb', line 460

def register_side_effect(side_effect)
  @side_effects << side_effect
end