Skip to content

Commit

Permalink
Merge e5de679 into e3f7136
Browse files Browse the repository at this point in the history
  • Loading branch information
mkarlesky authored Jan 22, 2025
2 parents e3f7136 + e5de679 commit 9283b04
Show file tree
Hide file tree
Showing 10 changed files with 819 additions and 57 deletions.
1 change: 1 addition & 0 deletions bin/ceedling
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ begin
# This prevents double instantiation and preserves object state in handoff
handoff_objects = {}
handoff = [
:reportinator,
:loginator,
:file_wrapper,
:yaml_wrapper,
Expand Down
2 changes: 1 addition & 1 deletion bin/configinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ def loadinate(builtin_mixins:, filepath:nil, mixins:[], env:{}, silent:false)
)

# Merge mixins
@mixinator.merge( builtins:builtin_mixins, config:config, mixins:mixins_assembled )
@mixinator.mixin( builtins:builtin_mixins, config:config, mixins:mixins_assembled )

return project_filepath, config
end
Expand Down
85 changes: 85 additions & 0 deletions bin/merginator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# =========================================================================
# Ceedling - Test-Centered Build System for C
# ThrowTheSwitch.org
# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================

require 'deep_merge'

class Merginator

constructor :reportinator

def setup
# ...
end


def merge(config:, mixin:, warnings:)
# Find any incompatible merge values in config and mixin
validate = validate_merge( config:config, mixin:mixin, mismatches:warnings )

# Merge this bad boy
config.deep_merge!(
mixin,
# In cases of a primitive and a hash of primitives, add single item to array
# This handles merge cases where valid config entries can be a single string or array of strings
# Because of the nature of Ceedling configurations, this handling is primarily for the string case
:extend_existing_arrays => true
)

return validate
end

### Private

private

# Recursive inspection of mergeable base config & mixin
def validate_merge(config:, mixin:, key_path:[], mismatches:)
# Track whether all matching hash key paths have matching value types
valid = true

# Get all keys from both hashes
all_keys = (config.keys + mixin.keys).uniq

all_keys.each do |key|
current_path = key_path + [key]

# Skip if key doesn't exist in both hashes
next unless config.key?(key) && mixin.key?(key)

config_value = config[key]
mixin_value = mixin[key]

if config_value.is_a?(Hash) && mixin_value.is_a?(Hash)
# Recursively check nested hashes
sub_result = validate_merge(
config: config_value,
mixin: mixin_value,
key_path: current_path,
mismatches: mismatches
)

valid = false unless sub_result

# Skip comparing anything where the config value is an array as it will be extended by the mixin value
elsif !config_value.is_a?(Array)
# Compare types of non-hash values
unless config_value.class == mixin_value.class
# If mergeable values at key paths in common are not the same type, register this
valid = false
key_path_str = @reportinator.generate_config_walk( current_path )
warning = "Incompatible merge at key path #{key_path_str} ==> Project configuration has #{config_value.class} while Mixin has #{mixin_value.class}"

# Do not use `<<` as it is locally scoped
mismatches.push( warning )
end
end
end

return valid
end

end
123 changes: 123 additions & 0 deletions bin/mixin_standardizer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# =========================================================================
# Ceedling - Test-Centered Build System for C
# ThrowTheSwitch.org
# Copyright (c) 2010-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================

class MixinStandardizer

constructor :reportinator

def setup
# ...
end

def smart_standardize(config:, mixin:, notices:)
modified = false
modified |= smart_standardize_defines(config, mixin, notices)
modified |= smart_standardize_flags(config, mixin, notices)
return modified
end

### Private

private

def smart_standardize_defines(config, mixin, notices)
modified = false

# Bail out if config and mixin do noth both have :defines
return false unless config[:defines] && mixin[:defines]

# Iterate over :defines ↳ <context> keys
# If both config and mixin contain the same key paths, process their (matcher) values
config[:defines].each do |context, context_hash|
if mixin[:defines][context]

# Standardize :defines ↳ <context> matcher conventions if they differ so they can be merged later
standardized, notice = standardize_matchers(
config[:defines][context],
mixin[:defines][context],
config[:defines],
mixin[:defines],
context
)

if standardized
path, _ = @reportinator.generate_config_walk( [:defines, context] )
_notice = "At #{path}: #{notice}"
notices.push( _notice )
end

modified |= standardized
end
end

return modified
end

def smart_standardize_flags(config, mixin, notices)
modified = false

# Bail out if config and mixin do noth both have :flags
return false unless config[:flags] && mixin[:flags]

# Iterate over :flags ↳ <context> ↳ <operation> keys
# If both config and mixin contain the same key paths, process their (matcher) values
config[:flags].each do |context, context_hash|
next unless mixin[:flags][context]

context_hash.each do |operation, operation_hash|
if mixin[:flags][context][operation]

# Standardize :flags ↳ <context> ↳ <operation> matcher conventions if they differ so they can be merged later
standardized, notice = standardize_matchers(
config[:flags][context][operation],
mixin[:flags][context][operation],
config[:flags][context],
mixin[:flags][context],
operation
)

if standardized
path, _ = @reportinator.generate_config_walk( [:flags, context, operation] )
_notice = "At #{path}: #{notice}"
notices.push( _notice )
end

modified |= standardized
end
end
end

return modified
end

def standardize_matchers(config_value, mixin_value, config_parent, mixin_parent, key)
# If both values are the same type, do nothing
return false, nil if (config_value.class == mixin_value.class)

# Promote mixin value list to all-matches matcher hash
if config_value.is_a?(Hash) && mixin_value.is_a?(Array)
# Ensure all-matches matcher key is a symbol and not a string
config_value[:*] = value if value = config_value.delete( '*' )

# Replace the value of a simple array list with a matcher hash that stores the original list
mixin_parent[key] = {:* => mixin_value}
return true, 'Converted mixin list to matcher hash to facilitate merging with configuration'
end

# Promote config value list to all-matches matcher hash
if config_value.is_a?(Array) && mixin_value.is_a?(Hash)
# Ensure all-matches matcher key is a symbol and not a string
mixin_value[:*] = value if value = mixin_value.delete( '*' )

# Replace the value of a simple array list with a matcher hash that stores the original list
config_parent[key] = {:* => config_value}
return true, 'Converted configuration list to matcher hash to facilitate merging with mixin'
end

return false, nil
end
end
23 changes: 16 additions & 7 deletions bin/mixinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
# SPDX-License-Identifier: MIT
# =========================================================================

require 'deep_merge'

class Mixinator

constructor :path_validator, :yaml_wrapper, :loginator
constructor :mixin_standardizer, :merginator, :path_validator, :yaml_wrapper, :loginator

def setup
# ...
# Aliases
@standardinator = @mixin_standardizer
end

def validate_cmdline_filepaths(paths)
Expand Down Expand Up @@ -96,7 +95,7 @@ def assemble_mixins(config:, env:, cmdline:)
return assembly.reverse()
end

def merge(builtins:, config:, mixins:)
def mixin(builtins:, config:, mixins:)
mixins.each do |mixin|
source = mixin.keys.first
filepath = mixin.values.first
Expand Down Expand Up @@ -124,8 +123,18 @@ def merge(builtins:, config:, mixins:)
# Sanitize the mixin config by removing any :mixins section (these should not end up in merges)
_mixin.delete(:mixins)

# Merge this bad boy
config.deep_merge( _mixin )
# Run special handling using knowledge of Ceedling configuration conventions
notices = []
if @standardinator.smart_standardize( config:config, mixin:_mixin, notices:notices )
notices.each { |msg| @loginator.log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE ) }
end

warnings = []
if !@merginator.merge( config:config, mixin:_mixin, warnings:warnings )
msg = "Mixin values from #{filepath} will replace configuration values for incompatible merges..."
@loginator.log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE )
warnings.each { |msg| @loginator.log( msg, Verbosity::COMPLAIN ) }
end
end

# Validate final configuration
Expand Down
12 changes: 12 additions & 0 deletions bin/objects.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ system_wrapper:

verbosinator:

reportinator:

loginator:
compose:
- verbosinator
Expand Down Expand Up @@ -59,10 +61,20 @@ path_validator:

mixinator:
compose:
- mixin_standardizer
- merginator
- path_validator
- yaml_wrapper
- loginator

mixin_standardizer:
compose:
- reportinator

merginator:
compose:
- reportinator

projectinator:
compose:
- file_wrapper
Expand Down
Loading

0 comments on commit 9283b04

Please sign in to comment.