Class: Configurator

Inherits:
Object show all
Defined in:
lib/ceedling/config/configurator.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#exclude_test_caseObject

Returns the value of attribute exclude_test_case.



20
21
22
# File 'lib/ceedling/config/configurator.rb', line 20

def exclude_test_case
  @exclude_test_case
end

#include_test_caseObject

Returns the value of attribute include_test_case.



20
21
22
# File 'lib/ceedling/config/configurator.rb', line 20

def include_test_case
  @include_test_case
end

#programmatic_pluginsObject (readonly)

Returns the value of attribute programmatic_plugins.



19
20
21
# File 'lib/ceedling/config/configurator.rb', line 19

def programmatic_plugins
  @programmatic_plugins
end

#project_config_hashObject (readonly)

Returns the value of attribute project_config_hash.



19
20
21
# File 'lib/ceedling/config/configurator.rb', line 19

def project_config_hash
  @project_config_hash
end

#project_loggingObject

Returns the value of attribute project_logging.



20
21
22
# File 'lib/ceedling/config/configurator.rb', line 20

def project_logging
  @project_logging
end

#rake_pluginsObject (readonly)

Returns the value of attribute rake_plugins.



19
20
21
# File 'lib/ceedling/config/configurator.rb', line 19

def rake_plugins
  @rake_plugins
end

#sanity_checksObject

Returns the value of attribute sanity_checks.



20
21
22
# File 'lib/ceedling/config/configurator.rb', line 20

def sanity_checks
  @sanity_checks
end

Instance Method Details

#build(ceedling_lib_path, logging_path, config, *keys) ⇒ Object

Create constants and accessors (attached to this object) from given hash



771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
# File 'lib/ceedling/config/configurator.rb', line 771

def build(ceedling_lib_path, logging_path, config, *keys)
  flattened_config = @configurator_builder.flattenify( config )

  @configurator_setup.build_project_config( ceedling_lib_path, logging_path, flattened_config )

  @configurator_setup.build_directory_structure( flattened_config )

  # Copy Unity, CMock, CException into vendor directory within build directory
  @configurator_setup.vendor_frameworks_and_support_files( ceedling_lib_path, flattened_config )

  @configurator_setup.build_project_collections( flattened_config )

  @project_config_hash = flattened_config.clone

  @configurator_setup.build_constants_and_accessors( flattened_config, binding() )

  # Top-level keys disappear when we flatten, so create global constants & accessors to any specified keys
  keys.each do |key|
    hash = { key => config[key] }
    @configurator_setup.build_constants_and_accessors( hash, binding() )
  end
end

#build_supplement(config_base, config_more) ⇒ Object

Add to constants and accessors as post build step



814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
# File 'lib/ceedling/config/configurator.rb', line 814

def build_supplement(config_base, config_more)
  # merge in our post-build additions to base configuration hash
  config_base.deep_merge!( config_more )

  # flatten our addition hash
  config_more_flattened = @configurator_builder.flattenify( config_more )

  # merge our flattened hash with built hash from previous build
  @project_config_hash.deep_merge!( config_more_flattened )

  # create more constants and accessors
  @configurator_setup.build_constants_and_accessors(config_more_flattened, binding())

  # recreate constants & update accessors with new merged, base values
  config_more.keys.each do |key|
    hash = { key => config_base[key] }
    @configurator_setup.build_constants_and_accessors(hash, binding())
  end
end

#discover_plugins(paths_hash, config) ⇒ Object



510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
# File 'lib/ceedling/config/configurator.rb', line 510

def discover_plugins(paths_hash, config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Discovering all plugins' )
  end

  # Rake-based plugins
  @rake_plugins = @configurator_plugins.find_rake_plugins( config, paths_hash )
  if !@configurator_plugins.rake_plugins.empty?
    @loginator.lazy( Verbosity::DEBUG ) do
      " > Rake plugins: " + @configurator_plugins.rake_plugins.map{|p| p[:plugin]}.join( ', ' )
    end
  end

  # Ruby `Plugin` subclass programmatic plugins
  @programmatic_plugins = @configurator_plugins.find_programmatic_plugins( config, paths_hash )
  if !@configurator_plugins.programmatic_plugins.empty?
    @loginator.lazy( Verbosity::DEBUG ) do
      " > Programmatic plugins: " + @configurator_plugins.programmatic_plugins.map{|p| p[:plugin]}.join( ', ' )
    end
  end
  
  # Config plugins
  @configurator_plugins.find_config_plugins( config, paths_hash )
  if !@configurator_plugins.config_plugins.empty?
    @loginator.lazy( Verbosity::DEBUG ) do
      " > Config plugins: " + @configurator_plugins.config_plugins.map{|p| p[:plugin]}.join( ', ' )
    end
  end
end

#eval_defines(config) ⇒ Object

Handle any Ruby string replacement for :defines string arrays



670
671
672
673
674
675
676
677
# File 'lib/ceedling/config/configurator.rb', line 670

def eval_defines(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Expanding any string replacements in :defines entries' )
  end

  # Descend down to array of #define strings regardless of depth in config block
  traverse_hash_eval_string_arrays( config[:defines], source: ":defines" )
end

#eval_environment_variables(config) ⇒ Object

Process environment variables set in configuration file (Each entry within the :environment array is a hash)



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
# File 'lib/ceedling/config/configurator.rb', line 586

def eval_environment_variables(config)
  return if config[:environment].nil?

  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Processing environment variables' )
  end

  config[:environment].each do |hash|
    key   = hash.keys[0] # Get first (should be only) environment variable entry
    value = hash[key]    # Get associated value
    items = []

    # Special case handling for :path environment variable entry
    # File::PATH_SEPARATOR => ':' (Unix-ish) or ';' (Windows)
    interstitial = ((key == :path) ? File::PATH_SEPARATOR : ' ')

    # Create an array container for the value of this entry
    #  - If the value is an array, get it
    #  - Otherwise, place value in a single item array
    items = ((value.class == Array) ? hash[key] : [value])

    # Process value array
    items.each do |item|
      # Process each item for Ruby string replacement
      if item.is_a? String
        item.replace( @ruby_expandinator.expand( item, source: ":environment ↳ #{key}" ) )
      end
    end

    # Join any value items (become a flattened string)
    #  - With path separator if the key was :path
    #  - With space otherwise
    hash[key] = items.join( interstitial )

    # Set the environment variable for our session
    @system_wrapper.env_set( key.to_s.upcase, hash[key] )
    @loginator.lazy( Verbosity::DEBUG ) do 
      " - #{key.to_s.upcase}: \"#{hash[key]}\""
    end
  end
end

#eval_flags(config) ⇒ Object

Handle any Ruby string replacement for :flags string arrays



659
660
661
662
663
664
665
666
# File 'lib/ceedling/config/configurator.rb', line 659

def eval_flags(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Expanding any string replacements in :flags entries' )
  end

  # Descend down to array of command line flags strings regardless of depth in config block
  traverse_hash_eval_string_arrays( config[:flags], source: ":flags" )
end

#eval_paths(config) ⇒ Object

Eval config path lists (convert any strings to array of size 1) and handle any Ruby string replacement



630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
# File 'lib/ceedling/config/configurator.rb', line 630

def eval_paths(config)
  # :plugins ↳ :load_paths already handled

  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Processing path entries and expanding any string replacements' )
  end

  eval_path_entries( config[:project][:build_root], source: ":project ↳ :build_root" )
  eval_path_entries( config[:release_build][:artifacts], source: ":release_build ↳ :artifacts" )

  config[:paths].each_pair do |entry, paths|
    # :paths sub-entries (e.g. :test) could be a single string -> make array
    reform_path_entries_as_lists( config[:paths], entry, paths )
    eval_path_entries( paths, source: ":paths ↳ #{entry}" )
  end

  config[:files].each_pair do |entry, files|
    # :files sub-entries (e.g. :test) could be a single string -> make array
    reform_path_entries_as_lists( config[:files], entry, files )
    eval_path_entries( files, source: ":files ↳ #{entry}" )
  end

  # All other paths at secondary hash key level processed by convention (`_path`):
  # ex. :toplevel ↳ :foo_path & :toplevel ↳ :bar_paths are evaluated
  config.each_pair { |key, child| eval_path_entries( collect_path_list( child ), source: ":#{key} ↳ a *_path/*_paths entry" ) }
end

#get_cmock_configObject



416
417
418
419
420
# File 'lib/ceedling/config/configurator.rb', line 416

def get_cmock_config
  # Clone because test mock generation is not thread-safe;
  # The mock generator is manufactured for each use with configuration changes for each use.
  return @cmock_config.clone
end

#get_runner_configObject



409
410
411
412
413
# File 'lib/ceedling/config/configurator.rb', line 409

def get_runner_config
  # Clone because test runner generation is not thread-safe;
  # The runner generator is manufactured for each use with configuration changes for each use.
  return @runner_config.clone
end

#insert_rake_plugins(plugins) ⇒ Object



835
836
837
838
839
840
841
842
843
# File 'lib/ceedling/config/configurator.rb', line 835

def insert_rake_plugins(plugins)
  plugins.each do |hash|
    @loginator.lazy( Verbosity::OBNOXIOUS ) do
      @reportinator.generate_progress( "Adding plugin #{hash[:plugin]} to Rake load list" )
    end

    @project_config_hash[:project_rakefile_component_files] << hash[:path]
  end
end

#inspectObject

Override to prevent exception handling from walking & stringifying the object variables. Object variables are gigantic and produce a flood of output.



44
45
46
47
# File 'lib/ceedling/config/configurator.rb', line 44

def inspect
  # TODO: When identifying information is added to constructor, insert it into `inspect()` string
  return this.class.name
end

#merge_ceedling_runtime_config(config, runtime_config) ⇒ Object



289
290
291
292
# File 'lib/ceedling/config/configurator.rb', line 289

def merge_ceedling_runtime_config(config, runtime_config)
  # Merge Ceedling's internal runtime configuration settings
  config.deep_merge( runtime_config )
end

#merge_config_plugins(config) ⇒ Object



550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'lib/ceedling/config/configurator.rb', line 550

def merge_config_plugins(config)
  return if @configurator_plugins.config_plugins.empty?

  # Merge plugin configuration values (like Ceedling project file)
  @configurator_plugins.config_plugins.each do |hash|
    _config = begin
      @yaml_wrapper.load( hash[:path] )
    rescue YamlLoadException => e
      raise YamlLoadException.new(
        reason: e.reason, source: e.source, original_error: e.original_error,
        message: "Could not load configuration from plugin '#{hash[:plugin]}' ⏩️ #{e.message}"
      )
    end

    @loginator.lazy( Verbosity::OBNOXIOUS ) do
      @reportinator.generate_progress( "Merging configuration from plugin #{hash[:plugin]}" )
    end
    @loginator.lazy( Verbosity::DEBUG ) do 
      _config.to_s
    end

    # Special handling for plugin paths
    if (_config.include?( :paths ))
      _config[:paths].update( _config[:paths] ) do |k,v| 
        plugin_path = hash[:path].match( /(.*)[\/]config[\/]\w+\.yml/ )[1]
        v.map {|vv| File.expand_path( vv.gsub!( /\$PLUGIN_PATH/, plugin_path) ) }
      end
    end

    config.deep_merge( _config )
  end
end

#merge_plugins_defaults(paths_hash, config, default_config) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/ceedling/config/configurator.rb', line 240

def merge_plugins_defaults(paths_hash, config, default_config)
  # Config YAML defaults plugins
  plugin_yml_defaults = @configurator_plugins.find_plugin_yml_defaults( config, paths_hash )
  
  # Config Ruby-based hash defaults plugins
  plugin_hash_defaults = @configurator_plugins.find_plugin_hash_defaults( config, paths_hash )


  if !plugin_hash_defaults.empty?
    @loginator.lazy( Verbosity::OBNOXIOUS ) do 
      @reportinator.generate_progress( 'Collecting Plugin YAML defaults' )
    end
  end

  # Load base configuration values (defaults) from YAML
  plugin_yml_defaults.each do |plugin, defaults|
    _defaults = begin
      @yaml_wrapper.load( defaults )
    rescue YamlLoadException => e
      raise YamlLoadException.new(
        reason: e.reason, source: e.source, original_error: e.original_error,
        message: "Could not load default configuration for plugin '#{plugin}' ⏩️ #{e.message}"
      )
    end

    @loginator.lazy( Verbosity::DEBUG ) do
      " - #{plugin} >> " + _defaults.to_s()
    end

    default_config.deep_merge( _defaults )
  end

  if !plugin_hash_defaults.empty?
    @loginator.lazy( Verbosity::OBNOXIOUS ) do 
      @reportinator.generate_progress( 'Collecting Plugin Ruby hash defaults' )
    end
  end

  # Load base configuration values (defaults) as hash from Ruby
  plugin_hash_defaults.each do |plugin, defaults|
    @loginator.lazy( Verbosity::DEBUG ) do 
      " - #{plugin} >> " + defaults.to_s()
    end

    default_config.deep_merge( defaults )
  end
end

#merge_tools_defaults(config, default_config) ⇒ Object

The default tools (eg. DEFAULT_TOOLS_TEST) are merged into default config hash



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/ceedling/config/configurator.rb', line 160

def merge_tools_defaults(config, default_config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do 
    @reportinator.generate_progress( 'Collecting default tool configurations' )
  end

  # config[:project] is guaranteed to exist / validated to exist but may not include elements referenced below
  # config[:test_build] and config[:release_build] are optional in a user project configuration


  release_build, _      = @config_walkinator.fetch_value( :project, :release_build, 
                            hash:config,
                            default: DEFAULT_CEEDLING_PROJECT_CONFIG[:project][:release_build]
                          )

  test_preprocessing, _ = @config_walkinator.fetch_value( :project, :use_test_preprocessor,
                            hash:config,
                            default: DEFAULT_CEEDLING_PROJECT_CONFIG[:project][:use_test_preprocessor]
                          )

  backtrace, _          = @config_walkinator.fetch_value( :project, :use_backtrace,
                            hash:config,
                            default: DEFAULT_CEEDLING_PROJECT_CONFIG[:project][:use_backtrace]
                          )

  release_assembly, _   = @config_walkinator.fetch_value( :release_build, :use_assembly,
                            hash:config,
                            default: DEFAULT_CEEDLING_PROJECT_CONFIG[:release_build][:use_assembly]
                          )

  test_assembly, _      = @config_walkinator.fetch_value( :test_build, :use_assembly,
                            hash:config,
                            default: DEFAULT_CEEDLING_PROJECT_CONFIG[:test_build][:use_assembly]
                          )

  default_config.deep_merge( DEFAULT_TOOLS_TEST.deep_clone() )

  default_config.deep_merge( DEFAULT_TOOLS_TEST_PREPROCESSORS.deep_clone() ) if (test_preprocessing != :none)
  default_config.deep_merge( DEFAULT_TOOLS_TEST_ASSEMBLER.deep_clone() )     if test_assembly
  default_config.deep_merge( DEFAULT_TOOLS_TEST_GDB_BACKTRACE.deep_clone() ) if (backtrace == :gdb)

  default_config.deep_merge( DEFAULT_TOOLS_RELEASE.deep_clone() )            if release_build
  default_config.deep_merge( DEFAULT_TOOLS_RELEASE_ASSEMBLER.deep_clone() )  if (release_build and release_assembly)
end

#populate_cmock_config(config) ⇒ Object



320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
# File 'lib/ceedling/config/configurator.rb', line 320

def populate_cmock_config(config)
  # Save CMock config reference
  @cmock_config = config[:cmock]

  cmock = config[:cmock]

  # Do no more prep if we're not using mocks
  if !config[:project][:use_mocks]
    @loginator.lazy( Verbosity::DEBUG ) do 
      "CMock configuration >> #{cmock}"
    end
    return
  end

  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Processing CMock configuration' )
  end

  # Plugins housekeeping
  cmock[:plugins].map! { |plugin| plugin.to_sym() }
  cmock[:plugins].uniq!

  # Reformulate CMock helper path value as array of one element if it's a string in config
  cmock[:unity_helper_path] = [cmock[:unity_helper_path]] if cmock[:unity_helper_path].is_a?( String )

  # CMock Unity helper handling
  cmock[:unity_helper_path].each do |path|
    cmock[:includes] << File.basename( path )
  end

  cmock[:includes].uniq!

  # Add mocking prefix symbol for all test compilation
  cmock[:defines] << "CMOCK_MOCK_PREFIX=#{cmock[:mock_prefix]}"

  @loginator.lazy( Verbosity::DEBUG ) do
    "CMock configuration >> #{cmock}"
  end
end

#populate_cmock_defaults(config, default_config) ⇒ Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/ceedling/config/configurator.rb', line 205

def populate_cmock_defaults(config, default_config)
  # Cmock has its own internal defaults handling, but we need to set these specific values
  # so they're guaranteed values and present for the Ceedling environment to access

  @loginator.lazy( Verbosity::OBNOXIOUS ) do 
    @reportinator.generate_progress( 'Collecting CMock defaults' )
  end

  # Begin populating defaults with CMock defaults as set by Ceedling
  default_cmock = default_config[:cmock]

  # Fill in default settings programmatically
  default_cmock[:mock_path] = File.join(config[:project][:build_root], TESTS_BASE_PATH, 'mocks')
  default_cmock[:verbosity] = project_verbosity()
end

#populate_exceptions_config(config) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/ceedling/config/configurator.rb', line 393

def populate_exceptions_config(config)
  # Automagically set exception handling if CMock is configured for it
  if config[:cmock][:plugins] && config[:cmock][:plugins].include?(:cexception)
    @loginator.lazy( Verbosity::OBNOXIOUS ) do
      @reportinator.generate_progress( 'Enabling CException use based on CMock plugins settings' )
    end   

    config[:project][:use_exceptions] = true
  end

  @loginator.lazy( Verbosity::DEBUG ) do
    "CException configuration >> #{config[:cexception]}"
  end
end

#populate_plugins_config(paths_hash, config) ⇒ Object



541
542
543
544
545
546
547
# File 'lib/ceedling/config/configurator.rb', line 541

def populate_plugins_config(paths_hash, config)
  # Set special plugin setting for results printing if unset
  config[:plugins][:display_raw_test_results] = true if (config[:plugins][:display_raw_test_results].nil?)

  # Add corresponding path to each plugin's configuration
  paths_hash.each_pair { |name, path| config[:plugins][name] = path }
end

#populate_test_runner_generation_config(config) ⇒ Object



361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/ceedling/config/configurator.rb', line 361

def populate_test_runner_generation_config(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Populating test runner generation settings' )
  end    

  use_backtrace = config[:project][:use_backtrace]

  # Force command line argument option for any backtrace option
  if use_backtrace != :none
    if config[:test_runner][:cmdline_args] == false
      config[:test_runner][:cmdline_args] = true
      @loginator.log( "Enabled :test_runner ↳ :cmdline_args because :project ↳ :use_backtrace is enabled.", Verbosity::COMPLAIN, LogLabels::NOTICE )
    end
  end

  # Copy CMock options used by test runner generation
  config[:test_runner][:mock_prefix] = config[:cmock][:mock_prefix]
  config[:test_runner][:mock_suffix] = config[:cmock][:mock_suffix]
  config[:test_runner][:enforce_strict_ordering] = config[:cmock][:enforce_strict_ordering]

  # Merge Unity options used by test runner generation
  config[:test_runner][:defines] += config[:unity][:defines]
  config[:test_runner][:use_param_tests] = config[:unity][:use_param_tests]

  @runner_config = config[:test_runner]

  @loginator.lazy( Verbosity::DEBUG ) do
    "Test Runner configuration >> #{config[:test_runner]}"
  end
end

#populate_tools_config(config) ⇒ Object

Process our tools

  • :tools entries
    • Insert missing names for
    • Handle needed defaults
  • Configure test runner from backtrace configuration


428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
# File 'lib/ceedling/config/configurator.rb', line 428

def populate_tools_config(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Populating tool definition settings and expanding any string replacements' )
  end

  config[:tools].each_key do |name|
    tool = config[:tools][name]

    if not tool.is_a?(Hash)
      raise CeedlingException.new( "Expected configuration for tool :#{name} is a Hash but found #{tool.class}" )
    end

    # Populate name if not given
    ToolExecutor.default_name!( tool, name.to_s )

    # Populate $stderr redirect option
    tool[:stderr_redirect] = StdErrRedirect::NONE if (tool[:stderr_redirect].nil?)

    # Populate optional option to control verification of executable in search paths
    tool[:optional] = false if (tool[:optional].nil?)
  end
end

#populate_tools_shortcuts(config) ⇒ Object

Process any tool definition shortcuts

  • Append extra arguments
  • Redefine executable

:tools_ :arguments: [...] :executable: '...'



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
# File 'lib/ceedling/config/configurator.rb', line 459

def populate_tools_shortcuts(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Processing tool definition shortcuts' )
  end

  prefix = 'tools_'
  config[:tools].each do |name, tool|
    # Lookup shortcut tool definition (:tools_<name>)
    shortcut = (prefix + name.to_s).to_sym

    # Try to lookup the executable from user config
    executable, _  = @config_walkinator.fetch_value(shortcut, :executable, 
                       hash:config
                     )

    # Try to lookup arguments from user config
    args_to_add, _ = @config_walkinator.fetch_value(shortcut, :arguments, 
                       hash:config,
                       default: []
                     )

    # Redefine the tool config
    if !executable.nil?
      tool[:executable] = executable
    end

    # Add to the tool config
    if !args_to_add.empty?
      tool[:arguments].concat( args_to_add )
    end

    # Log
    if !args_to_add.empty? or !executable.nil?
      @loginator.lazy( Verbosity::DEBUG ) do 
        msg = " > #{name}\n"

        if !executable.nil?
          msg += "   executable: \"#{executable}\"\n"
        end

        if !args_to_add.empty?
          msg += "   arguments: " + args_to_add.map{|arg| "\"#{arg}\""}.join( ', ' ) + "\n"
        end

        msg
      end
    end
  end
end

#populate_unity_config(config) ⇒ Object



304
305
306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/ceedling/config/configurator.rb', line 304

def populate_unity_config(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do 
    @reportinator.generate_progress( 'Processing Unity configuration' )
  end

  if config[:unity][:use_param_tests]
    config[:unity][:defines] << 'UNITY_SUPPORT_TEST_CASES'
    config[:unity][:defines] << 'UNITY_SUPPORT_VARIADIC_MACROS'
  end

  @loginator.lazy( Verbosity::DEBUG ) do 
    "Unity configuration >> #{config[:unity]}"
  end
end

#populate_with_defaults(config_hash, defaults_hash) ⇒ Object



295
296
297
298
299
300
301
# File 'lib/ceedling/config/configurator.rb', line 295

def populate_with_defaults( config_hash, defaults_hash )
  @loginator.lazy( Verbosity::OBNOXIOUS ) do 
    @reportinator.generate_progress( 'Populating project configuration with collected default values' )
  end

  @configurator_builder.populate_with_defaults( config_hash, defaults_hash )
end

#prepare_plugins_load_paths(plugins_load_path, config) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/ceedling/config/configurator.rb', line 222

def prepare_plugins_load_paths(plugins_load_path, config)
  # Plugins must be loaded before generic path evaluation & magic that happen later.
  # So, perform path magic here as discrete step.
  # (String replacement and standardization require DI objects not available in bin/ scope.)
  config[:plugins][:load_paths].each do |path|
    path.replace( @ruby_expandinator.expand( path, source: ":plugins ↳ :load_paths" ) )
    FilePathUtils::standardize_in_place( path )
  end

  # Delegate list construction to the shared helper (user paths first, built-in last).
  # This mirrors what RakefileComponentResolver.resolve() does in the bin/ CLI scope,
  # ensuring both scopes search plugins in the same priority order.
  config[:plugins][:load_paths] = RakefileComponentResolver.prepare_plugin_load_paths( config, plugins_load_path )

  return @configurator_plugins.process_aux_load_paths( config )
end

#redefine_element(elem, value) ⇒ Object



795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
# File 'lib/ceedling/config/configurator.rb', line 795

def redefine_element(elem, value)
  # Ensure elem is a symbol
  elem = elem.to_sym if elem.class != Symbol

  # Ensure element already exists
  if not @project_config_hash.include?(elem)
    error = "Could not redefine #{elem} in configurator ⏩️ Element does not exist"
    raise CeedlingException.new(error)
  end

  # Update internal hash
  @project_config_hash[elem] = value

  # Update global constant
  @configurator_builder.build_global_constant(elem, value)
end

#replace_flattened_config(config) ⇒ Object



49
50
51
52
# File 'lib/ceedling/config/configurator.rb', line 49

def replace_flattened_config(config)
  @project_config_hash.merge!(config)
  @configurator_setup.build_constants_and_accessors(@project_config_hash, binding())
end

#resolve_directives_only_preprocessing(config, tool_executor) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/ceedling/config/configurator.rb', line 104

def resolve_directives_only_preprocessing(config, tool_executor)
  # Nothing to probe if preprocessing is disabled
  preprocessing = config[:project][:use_test_preprocessor]
  config[:test_build][:preprocess_directives_only_available] = false
  return if preprocessing == :none

  # When forced fallback is enabled, skip the probe and mark directives-only unavailable
  if config[:test_build][:preprocess_force_fallback]
    @loginator.log(
      "Forcing fallback text-based preprocessing in place of directives-only (:test_build ↳ :preprocess_force_fallback is enabled).",
      Verbosity::COMPLAIN,
      LogLabels::NOTICE
    )
    return
  end

  # Probe whether the configured C preprocessor supports -fdirectives-only.
  # Use the Unity header as it is always-available, self-contained, no include paths needed.
  # Output goes to stdout (no -o flag) to avoid platform-specific null device paths.
  probe_filepath = File.join( CEEDLING_VENDOR, UNITY_LIB_PATH, UNITY_H_FILE )

  # Minimal subset of the directives-only preprocessor tool: no defines, no include paths,
  # no output file. Sufficient to detect `-fdirectives-only` support via warning or exit code.
  probe_tool = {
    executable: config[:tools][:test_file_directives_only_preprocessor][:executable],
    name:       'directives_only_probe',
    arguments:  [
      '-E',
      '-fdirectives-only',
      '-x c',
      "\"${1}\""
    ]
  }

  command = tool_executor.build_command_line( probe_tool, [], probe_filepath )
  # Exception if something goes wrong; we want to detect errors here
  command[:options][:boom] = false
  results = tool_executor.exec( command )

  # Clang and some older GCC emit a warning (not an error) when -fdirectives-only is unsupported
  warning_detected = results[:output].match?( /warning[^\n]+-fdirectives-only/ )

  if warning_detected || results[:exit_code] != 0
    @loginator.log(
      "Preprocessor lacks -fdirectives-only support ➡️ Ceedling will use text-based fallback for preprocessing.",
      Verbosity::COMPLAIN,
      LogLabels::NOTICE
    )
    # :preprocess_directives_only_available already set to false above
  else
    config[:test_build][:preprocess_directives_only_available] = true
  end
end

#set_partials_derived_config(config) ⇒ Object



73
74
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
# File 'lib/ceedling/config/configurator.rb', line 73

def set_partials_derived_config(config)
  return if !config[:project][:use_partials]

  # If partials enabled, enable mocking
  config[:project][:use_mocks] = true
  @loginator.log( " > Enabled mocking." )

  # If partials enabled, enable full test preprocessing
  config[:project][:use_test_preprocessor] = :all
  @loginator.log( " > Enabled preprocessing." )

  # If partials enabled and CMock's `:treat_inlines` is enabled, quietly disable.
  # Enabling Partials and this feature causes extra processing for Ceedling and CMock that is never used.
  if config[:cmock][:treat_inlines] == :include
    config[:cmock][:treat_inlines] = :exclude
    @loginator.log( "Reverted :cmock ↳ :treat_inlines to :exclude because this CMock feature is superseded by Partials.", Verbosity::COMPLAIN, LogLabels::NOTICE )
  end

  # If partials enabled, inject partials name prefix symbol to all test compilation
  # and, if a project defines its own preprocess-scoped defines matcher, to
  # preprocessing as well -- `:preprocess:` defines are looked up independently
  # of `:test:` defines and only fall back to the latter when `:preprocess:`
  # is absent entirely, so a project-defined `:preprocess:` matcher needs this
  # symbol appended directly or Partials' own macro-based #includes break.
  # Handle both the simple list and matcher hash config formats.
  _partials_prefix_symbol = "CEEDLING_PARTIALS_PREFIX=#{PARTIAL_FILENAME_PREFIX}"
  ConfigMatchinator.append_matcher_entries( config[:defines][:test], _partials_prefix_symbol )
  ConfigMatchinator.append_matcher_entries( config[:defines][:preprocess], _partials_prefix_symbol )
end

#set_verbosity(config) ⇒ Object

Set up essential flattened config related to verbosity. We do this because early config validation failures may need access to verbosity, but the accessors won't be available until after configuration is validated.



58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/ceedling/config/configurator.rb', line 58

def set_verbosity(config)
  # PROJECT_VERBOSITY and PROJECT_DEBUG set at command line processing before Ceedling is loaded

  if (!!defined?(PROJECT_DEBUG) and PROJECT_DEBUG)
    eval("def project_debug() return true end", binding())
  else
    eval("def project_debug() return false end", binding())      
  end

  if !!defined?(PROJECT_VERBOSITY)
    eval("def project_verbosity() return #{PROJECT_VERBOSITY} end", binding())
  end
end

#setupObject



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/ceedling/config/configurator.rb', line 24

def setup()
  # Cmock config reference to provide to CMock for mock generation
  @cmock_config = {} # Default empty hash, replaced by reference below

  # Runner config reference to provide to runner generation
  @runner_config = {} # Default empty hash, replaced by reference below

  # Note: project_config_hash is an instance variable so constants and accessors created
  # in eval() statements in build() have something of proper scope and persistence to reference
  @project_config_hash = {}

  @programmatic_plugins = []
  @rake_plugins   = []

  @project_logging = false
  @sanity_checks   = TestResultsSanityChecks::NORMAL
end

#standardize_paths(config) ⇒ Object



680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/ceedling/config/configurator.rb', line 680

def standardize_paths(config)
  @loginator.lazy( Verbosity::OBNOXIOUS ) do
    @reportinator.generate_progress( 'Standardizing all paths' )
  end

  # :project ↳ :build_root and :release_build ↳ :artifacts are individual paths
  # that don't follow the _path/_paths key convention — handle them explicitly.
  # :release_build may be absent in minimal configs; guard prevents NoMethodError.
  paths = [config[:project][:build_root]]
  if config[:release_build]
    paths << config[:release_build][:artifacts]
  else
    @loginator.log(
      ":release_build section absent from config ➡️ skipping :artifacts path standardization.",
      Verbosity::COMPLAIN,
      LogLabels::NOTICE
    )
  end

  paths.flatten.each { |path| FilePathUtils::standardize_in_place( path ) }

  config[:paths].each_pair do |collection, paths|
    # Flatten to handle single strings or nested arrays; reject nils (non-String passthrough
    # from standardize) and empty strings left after stripping whitespace-only entries.
    config[:paths][collection] = [paths].flatten
      .map    { |path| FilePathUtils::standardize_in_place( path ) }
      .reject { |path| path.nil? || (path.is_a?( String ) && path.empty?) }
  end

  config[:files].each_pair do |collection, files|
    # Same sanitization as :paths — replace array to remove any nil or empty entries
    # produced by standardize on non-String or whitespace-only values.
    config[:files][collection] = [files].flatten
      .map    { |path| FilePathUtils::standardize_in_place( path ) }
      .reject { |path| path.nil? || (path.is_a?( String ) && path.empty?) }
  end

  config[:tools].each_pair { |_, config| FilePathUtils::standardize_in_place( config[:executable] ) if (config.include? :executable) }

  # All other paths at secondary hash key level processed by convention (`_path`):
  # ex. :toplevel ↳ :foo_path & :toplevel ↳ :bar_paths are standardized
  config.each_pair do |_, child|
    collect_path_list( child ).each { |path| FilePathUtils::standardize_in_place( path ) }
  end
end

#validate_essential(config) ⇒ Object



727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
# File 'lib/ceedling/config/configurator.rb', line 727

def validate_essential(config)
  # Collect all infractions, everybody on probation until final adjudication
  blotter = true

  blotter &= @configurator_setup.validate_required_sections( config )
  blotter &= @configurator_setup.validate_required_section_values( config )

  # Configuration sections can reference environment variables that are evaluated early on.
  # So, we validate :environment early as an essential section.
  blotter &= @configurator_setup.validate_environment_vars( config )

  if !blotter
    raise CeedlingException.new("Ceedling configuration failed validation")
  end
end

#validate_final(config, app_cfg) ⇒ Object



744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'lib/ceedling/config/configurator.rb', line 744

def validate_final(config, app_cfg)
  # Collect all infractions, everybody on probation until final adjudication
  blotter = true
  blotter &= @configurator_setup.validate_paths( config )
  blotter &= @configurator_setup.validate_tools( config )
  blotter &= @configurator_setup.validate_test_runner_generation(
               config,
               app_cfg[:include_test_case],
               app_cfg[:exclude_test_case]
             )
  blotter &= @configurator_setup.validate_defines( config )
  blotter &= @configurator_setup.validate_flags( config )
  blotter &= @configurator_setup.validate_test_preprocessor( config )
  blotter &= @configurator_setup.validate_backtrace( config )
  blotter &= @configurator_setup.validate_threads( config )
  blotter &= @configurator_setup.validate_plugins( config )

  # Informational notices
  @configurator_setup.warnings_for_problematic_configs( config )

  if !blotter
    raise CeedlingException.new( "Ceedling configuration failed validation" )
  end
end