Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
3dae6d9
- 🐎 Add lazy-evaluation option to logging and test with obnoxious mes…
mvandervoord Mar 23, 2025
0fd9fbb
Further work towards refining parallel handling
mvandervoord Apr 2, 2025
9df3569
further usage of lazy logging
mvandervoord Apr 2, 2025
4bc4076
🎨 Refactored handling of `batchinator` to return processed informatio…
mvandervoord Apr 15, 2025
8ba755a
Fix accidentally broken test.
mvandervoord Apr 15, 2025
9a4a321
Fix bug in gcovr plugin using wrong option set.
mvandervoord Apr 25, 2025
9acf20f
Clean up interface for creating a new project. Why was it so ugly?
mvandervoord Apr 25, 2025
1042757
Try that refactor again.
mvandervoord Apr 25, 2025
7c8ccb6
Fix issue #1024 - report build warnings log bug.
mvandervoord Apr 25, 2025
2558483
Fix bug where `gcov` section of `flags` was no longer supporting file…
mvandervoord May 5, 2025
7eac2e6
Fully parallelize compilation instead of semi-batching.
mvandervoord May 6, 2025
29112a5
Refactor includes processing to have fallback method serve as maximum…
mvandervoord Jun 11, 2025
d1c7aaf
For C23, don't redefine bool in the example
mvandervoord Jun 11, 2025
3efa432
stop using a reserved name in the example project.
mvandervoord Jun 11, 2025
2597764
clean the encoding line by line while chunking data from files.
mvandervoord Jun 12, 2025
1bfdf80
Fixed issue #1040 (default task of gcov not triggering gcov reports).
mvandervoord Jul 9, 2025
e4b77a3
Merge branch 'master' into feature/log_improvements
mvandervoord Jul 9, 2025
21e8bcb
Bump to latest dependency versions
mvandervoord Jul 9, 2025
740dfc3
Merge branch master into feature/log_improvements
mkarlesky Jan 1, 2026
f4237bf
🐛 Fixed logic inversion due to merge
mkarlesky Jan 1, 2026
1eac900
📝 Comment
mkarlesky Jan 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ gem "thor", "~> 1.3"
gem "deep_merge", "~> 1.2"
gem "unicode-display_width", "~> 3.1"
gem "erb", "~> 4.0"
gem "parallel", "~> 1.26"
6 changes: 3 additions & 3 deletions assets/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@

# Configuration Options specific to CMock. See CMock docs for details
:cmock:
# Core conffiguration
# Core configuration
:plugins: # What plugins should be used by CMock?
- :ignore
- :callback
Expand All @@ -166,8 +166,8 @@
- __stdcall
- __cdecl
- __fastcall
:treat_externs: :exclude # the options being :include or :exclud
:treat_inlines: :exclude # the options being :include or :exclud
:treat_externs: :exclude # the options being :include or :exclude
:treat_inlines: :exclude # the options being :include or :exclude

# Type handling configuration
#:unity_helper_path: '' # specify a string of where to find a unity_helper.h file to discover custom type assertions
Expand Down
4 changes: 2 additions & 2 deletions assets/test_example_file_crash.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void test_add_numbers_adds_numbers(void) {
void test_add_numbers_will_fail(void) {
// Platform-independent way of forcing a crash
// NOTE: Avoid `nullptr` as it is a keyword in C23
uint32_t* null_ptr = (void*) 0;
uint32_t i = *null_ptr;
uint32_t* a_null_pointer = (void*)0;
uint32_t i = *a_null_pointer;
TEST_ASSERT_EQUAL_INT(2, add_numbers(i,2));
}
10 changes: 4 additions & 6 deletions bin/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,8 @@ def help(command=nil)
<<-LONGDESC
`ceedling new` creates a new project structure.

NAME is required and will be the containing directory for the new project.

DEST is an optional directory path for the new project (e.g. <DEST>/<name>).
The default is your working directory. Nonexistent paths will be created.
DEST is an optional root path for the new project (e.g. <DEST>/project.yml).
The default is your working directory. Non-existent paths will be created.

Notes on Optional Flags:

Expand All @@ -232,7 +230,7 @@ def help(command=nil)
new project.
LONGDESC
) )
def new(name, dest=nil)
def new(dest=nil)
require 'version' # lib/version.rb for TAG constant

# Get unfrozen copies so we can add / modify
Expand All @@ -241,7 +239,7 @@ def new(name, dest=nil)

_options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil

@handler.new_project( ENV, @app_cfg, Ceedling::Version::TAG, _options, name, _dest )
@handler.new_project( ENV, @app_cfg, Ceedling::Version::TAG, _options, _dest )
end

desc "upgrade PATH", "Upgrade vendored installation of Ceedling for a project at PATH"
Expand Down
43 changes: 24 additions & 19 deletions bin/cli_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,21 @@ def app_help(env, app_cfg, options, command, &thor_help)
return if !command.nil?

# If project configuration is available, also display Rake tasks
@path_validator.standardize_paths( options[:project], *options[:mixin], )
return if !@projectinator.config_available?( filepath:options[:project], env:env )

list_rake_tasks(
env:env,
app_cfg: app_cfg,
filepath: options[:project],
mixins: options[:mixin],
# Silent Ceedling loading unless debug verbosity
silent: !(verbosity == Verbosity::DEBUG)
)
@path_validator.standardize_paths( options[:project], *options[:mixin] )
if @projectinator.config_available?( filepath:options[:project], env:env )
list_rake_tasks(
env:env,
app_cfg: app_cfg,
filepath: options[:project],
mixins: options[:mixin],
# Silent Ceedling loading unless debug verbosity
silent: !(verbosity == Verbosity::DEBUG)
)
else
# If no project configuration is available then note why we aren't displaying more
msg = "Run help commands in a directory with a project file to list additional options"
@loginator.log( msg, Verbosity::NORMAL, LogLabels::NOTICE )
end

version = @helper.manufacture_app_version( app_cfg )

Expand All @@ -79,18 +83,17 @@ def rake_help(env:, app_cfg:)
end


def new_project(env, app_cfg, ceedling_tag, options, name, dest)
def new_project(env, app_cfg, ceedling_tag, options, dest)
@helper.set_verbosity( options[:verbosity] )

@path_validator.standardize_paths( dest )

# If destination is nil, reassign it to name
# Otherwise, join the destination and name into a new path
dest = dest.nil? ? ('./' + name) : File.join( dest, name )
# If destination is nil, assume it's the working directory
dest ||= '.'

# Check for existing project (unless --force)
if @helper.project_exists?( dest, :|, DEFAULT_PROJECT_FILENAME, 'src', 'test' )
msg = "It appears a project already exists at #{dest}/. Use --force to destroy it and create a new project."
msg = "It appears a project already exists at \"#{dest}/\"! Use --force to destroy it and create a new project."
raise msg
end unless options[:force]

Expand Down Expand Up @@ -128,7 +131,7 @@ def new_project(env, app_cfg, ceedling_tag, options, name, dest)
end

@loginator.log() # Blank line
@loginator.log( "New project '#{name}' created at #{dest}/\n", Verbosity::NORMAL, LogLabels::TITLE )
@loginator.log( "New project created at #{dest}/\n", Verbosity::NORMAL, LogLabels::TITLE )
end


Expand Down Expand Up @@ -228,8 +231,10 @@ def build(env:, app_cfg:, options:{}, tasks:)
CException => #{_version.cexception_tag}
VERSION

@loginator.log( '', Verbosity::OBNOXIOUS )
@loginator.log( version, Verbosity::OBNOXIOUS, LogLabels::CONSTRUCT )
@loginator.lazy( Verbosity::OBNOXIOUS )
@loginator.lazy( Verbosity::OBNOXIOUS, LogLabels::CONSTRUCT ) do
version
end

@helper.load_ceedling(
config: config,
Expand Down
8 changes: 4 additions & 4 deletions bin/cli_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def which_ceedling?(env:, config:{}, app_cfg:)

# Environment variable
if !env['WHICH_CEEDLING'].nil?
@loginator.log( " > Set which Ceedling using environment variable WHICH_CEEDLING", Verbosity::OBNOXIOUS )
@loginator.log( " > Set which Ceedling using environment variable WHICH_CEEDLING", Verbosity::OBNOXIOUS )
which_ceedling = env['WHICH_CEEDLING'].strip()
which_ceedling = :gem if (which_ceedling.casecmp( 'gem' ) == 0)
end
Expand All @@ -118,7 +118,7 @@ def which_ceedling?(env:, config:{}, app_cfg:)
value, _ = @config_walkinator.fetch_value( :project, :which_ceedling, hash:config )
if !value.nil?
which_ceedling = value.strip()
@loginator.log( " > Set which Ceedling from config :project ↳ :which_ceedling => #{which_ceedling}", Verbosity::OBNOXIOUS )
@loginator.lazy( Verbosity::OBNOXIOUS ) { " > Set which Ceedling from config :project ↳ :which_ceedling => #{which_ceedling}" }
which_ceedling = :gem if (which_ceedling.casecmp( 'gem' ) == 0)
end
end
Expand All @@ -139,7 +139,7 @@ def which_ceedling?(env:, config:{}, app_cfg:)

# If we're launching from the gem, return :gem and initial Rakefile path
if which_ceedling == :gem
@loginator.log( " > Launching Ceedling from #{app_cfg[:ceedling_root_path]}/", Verbosity::OBNOXIOUS )
@loginator.lazy( Verbosity::OBNOXIOUS ) { " > Launching Ceedling from #{app_cfg[:ceedling_root_path]}/" }
return which_ceedling, app_cfg[:ceedling_rakefile_filepath]
end

Expand All @@ -161,7 +161,7 @@ def which_ceedling?(env:, config:{}, app_cfg:)
# Update variable to full application start path
ceedling_path = app_cfg[:ceedling_rakefile_filepath]

@loginator.log( " > Launching Ceedling from #{app_cfg[:ceedling_root_path]}/", Verbosity::OBNOXIOUS )
@loginator.lazy( Verbosity::OBNOXIOUS ) { " > Launching Ceedling from #{app_cfg[:ceedling_root_path]}/" }

return :path, ceedling_path
end
Expand Down
4 changes: 2 additions & 2 deletions bin/mixinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,14 @@ def mixin(builtins:, config:, mixins:)
_mixin = @yaml_wrapper.load( filepath )

# Log what filepath we used for this mixin
@loginator.log( " + Merging #{'(empty) ' if _mixin.nil?}#{source} mixin using #{filepath}", Verbosity::OBNOXIOUS )
@loginator.lazy( Verbosity::OBNOXIOUS ) { " + Merging #{'(empty) ' if _mixin.nil?}#{source} mixin using #{filepath}" }

# Reference mixin from built-in hash-based mixins
else
_mixin = builtins[filepath.to_sym()]

# Log built-in mixin we used
@loginator.log( " + Merging built-in mixin '#{filepath}' from #{source}", Verbosity::OBNOXIOUS )
@loginator.lazy( Verbosity::OBNOXIOUS ) { " + Merging built-in mixin '#{filepath}' from #{source}" }
end

# Hnadle an empty mixin (it's unlikely but logically coherent and a good safety check)
Expand Down
13 changes: 6 additions & 7 deletions bin/projectinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ def load(filepath:nil, env:{}, silent:false)
config = load_and_log( _filepath, "from working directory", silent )
return _filepath, config

# If no user provided filepath and the default filepath does not exist,
# we have a big problem
# If no user-provided filepath and the default filepath does not exist, we have a big problem
else
raise "No project filepath provided and default #{DEFAULT_PROJECT_FILEPATH} not found"
end
Expand Down Expand Up @@ -221,11 +220,11 @@ def load_and_log(filepath, method, silent)

# Log what the heck we loaded
if !silent
msg = "Loaded #{'(empty) ' if config.empty?}project configuration #{method}.\n"
msg += " > Using: #{filepath}\n"
msg += " > Working directory: #{Dir.pwd()}"

@loginator.log( msg, Verbosity::NORMAL, LogLabels::CONSTRUCT )
@loginator.lazy( Verbosity::NORMAL, LogLabels::CONSTRUCT ) do
"Loaded #{'(empty) ' if config.empty?}project configuration #{method}.\n" +
" > Using: #{filepath}\n" +
" > Working directory: #{Dir.pwd()}"
end
end

return config
Expand Down
1 change: 1 addition & 0 deletions ceedling.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Ceedling projects are created with a YAML configuration file. A variety of conve
s.add_dependency "diy", "~> 1.1"
s.add_dependency "constructor", "~> 2"
s.add_dependency "unicode-display_width", "~> 3.1"
s.add_dependency "parallel", "~> 1.26"

# Files needed from submodules
s.files = []
Expand Down
76 changes: 76 additions & 0 deletions lib/ceedling/batchinator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# =========================================================================
# Ceedling - Test-Centered Build System for C
# ThrowTheSwitch.org
# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams
# SPDX-License-Identifier: MIT
# =========================================================================

require 'benchmark'
require 'parallel'

class Batchinator

constructor :configurator, :loginator, :reportinator

def setup
@queue = Queue.new
end

# Neaten up a build step with progress message and some scope encapsulation
def build_step(msg, heading: true, &block)
if heading
msg = @reportinator.generate_heading( @loginator.decorate( msg, LogLabels::RUN ) )
else # Progress message
msg = "\n" + @reportinator.generate_progress( @loginator.decorate( msg, LogLabels::RUN ) )
end

@loginator.log( msg )

yield # Execute build step block
end

# Parallelize work to be done:
# - Enqueue things (thread-safe)
# - Spin up a number of worker threads within constraints of project file config and amount of work
# - Each worker thread consumes one item from queue and runs the block against its details
# - When the queue is empty, the worker threads wind down
def exec(workload:, things:, &job_block)

batch_results = []
sum_elapsed = 0.0

all_elapsed = Benchmark.realtime do
# Determine number of worker threads to run
workers = 1
case workload
when :compile
workers = @configurator.project_compile_threads
when :test
workers = @configurator.project_test_threads
else
raise NameError.new("Unrecognized batch workload type: #{workload}")
end

# Perform the actual parallelized work and collect the results and timing
batch_results = Parallel.map(things, in_threads: workers) do |key, value|
this_results = ''
this_elapsed = Benchmark.realtime { this_results = job_block.call(key, value) }
[this_results, this_elapsed]
end

# Separate the elapsed time and results
if batch_results.size > 0
batch_results, batch_elapsed = batch_results.transpose
sum_elapsed = batch_elapsed.sum()
end
end

# Report the timing if requested
@loginator.lazy(Verbosity::OBNOXIOUS) do
"\nBatch Elapsed: (All: %.3fsec Sum: %.3fsec)\n" % [all_elapsed, sum_elapsed]
end

batch_results
end
end

Loading
Loading