From 3e34262f5c53377b36f50a6a958f257dc917a1ef Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Fri, 19 Apr 2024 16:42:00 -0400 Subject: [PATCH] Refactoring for application install file path handling - Removed all global constant filepaths - Centralized all application paths into CeedlingApplicationConfig object that can be updated from :which_ceedling settings - Streamlined and centralized which Ceedling handling - Added WHICH_CEEDLING environment variable to support developer needs or unusual build scenarios and match the basic flow of processing project file configuration handling options --- bin/actions_wrapper.rb | 2 +- bin/app_cfg.rb | 105 +++++++++++++++++++++------ bin/ceedling | 41 +++++++---- bin/cli.rb | 12 ++- bin/cli_handler.rb | 103 ++++++++++++++------------ bin/cli_helper.rb | 101 +++++++++++++++++--------- bin/configinator.rb | 3 +- bin/mixinator.rb | 2 +- lib/ceedling/config_matchinator.rb | 4 +- lib/ceedling/configurator.rb | 4 +- lib/ceedling/configurator_builder.rb | 20 ++--- lib/ceedling/configurator_setup.rb | 4 +- lib/ceedling/defaults.rb | 6 +- lib/ceedling/rakefile.rb | 12 +-- lib/ceedling/setupinator.rb | 2 +- lib/ceedling/version.rb | 50 +++++-------- 16 files changed, 287 insertions(+), 184 deletions(-) diff --git a/bin/actions_wrapper.rb b/bin/actions_wrapper.rb index 91541b73b..f5a06c464 100755 --- a/bin/actions_wrapper.rb +++ b/bin/actions_wrapper.rb @@ -13,7 +13,7 @@ class ActionsWrapper include Thor::Base include Thor::Actions - source_root( CEEDLING_ROOT ) + # Most important mixin method is Thor::Actions class method `source_root()` we call externally def _directory(src, *args) directory( src, *args ) diff --git a/bin/app_cfg.rb b/bin/app_cfg.rb index 53ac04f81..7a0740be6 100755 --- a/bin/app_cfg.rb +++ b/bin/app_cfg.rb @@ -5,38 +5,97 @@ # SPDX-License-Identifier: MIT # ========================================================================= +require "io/console" + # Create our global application configuration option set # This approach bridges clean Ruby and Rake -def get_app_cfg() - require "io/console" - app_cfg = { - # Blank initial value for completeness - :project_config => {}, +class CeedlingAppConfig + + def initialize() + # Installation location determined from the location of this file + ceedling_root_path = File.expand_path( File.join( File.dirname( __FILE__ ), '..' ) ) + + # Create internal hash of needed values + @app_cfg = { + # Base path of any Ceedling installation + :ceedling_root_path => '', + + # Ceedling installation base path + /lib + :ceedling_lib_base_path => '', + + # Ceedling installation base path + /lib/ceedling + :ceedling_lib_path => '', + + # Ceedling installation base path + /vendor + :ceedling_vendor_path => '', + + # Ceedling installation base path + /examples + :ceedling_examples_path => '', + + # Blank initial value for completeness + :project_config => {}, + + # Default, blank value + :log_filepath => '', + + # Only specified in project config (no command line or environment variable) + :default_tasks => ['test:all'], + + # Default, blank test case filters + :include_test_case => '', + :exclude_test_case => '', + + # Default to no duration logging for setup & build ops in Rake context + :stopwatch => false, + + # Default to `exit(1)` upon failing test cases + :tests_graceful_fail => false, + + # Get terminal width in columns + :terminal_width => (IO.console.winsize)[1], + } + + set_paths( ceedling_root_path ) + end + + def set_project_config(config) + @app_cfg[:project_config] = config + end + + def set_log_filepath(filepath) + @app_cfg[:log_filepath] = filepath + end - # Default, blank value - :log_filepath => '', + def set_include_test_case(matcher) + @app_cfg[:include_test_case] = matcher + end - # Only specified in project config (no command line or environment variable) - :default_tasks => ['test:all'], + def set_exclude_test_case(matcher) + @app_cfg[:exclude_test_case] = matcher + end - # Basic check from working directory - # If vendor/ceedling exists, default to running vendored Ceedling - :which_ceedling => (Dir.exist?( 'vendor/ceedling' ) ? 'vendor/ceedling' : 'gem'), + def set_stopwatch(enable) + @app_cfg[:stopwatch] = enable + end - # Default, blank test case filters - :include_test_case => '', - :exclude_test_case => '', + def set_tests_graceful_fail(enable) + @app_cfg[:tests_graceful_fail] = enable + end - # Default to no duration logging for setup & build ops in Rake context - :stopwatch => false, + def set_paths(root_path) + lib_base_path = File.join( root_path, 'lib' ) - # Default to `exit(1)` upon failing test cases - :tests_graceful_fail => false, + @app_cfg[:ceedling_root_path] = root_path + @app_cfg[:ceedling_lib_base_path] = lib_base_path + @app_cfg[:ceedling_lib_path] = File.join( lib_base_path, 'ceedling' ) + @app_cfg[:ceedling_vendor_path] = File.join( root_path, 'vendor' ) + @app_cfg[:ceedling_examples_path] = File.join( root_path, 'examples' ) + end - # Get terminal width in columns - :terminal_width => (IO.console.winsize)[1], - } + # External accessor to preserve hash-like read accesses + def [](key) + return @app_cfg[key] + end - return app_cfg end diff --git a/bin/ceedling b/bin/ceedling index 277eea334..9217b0176 100755 --- a/bin/ceedling +++ b/bin/ceedling @@ -8,38 +8,47 @@ require 'rubygems' -CEEDLING_ROOT = File.expand_path( File.join( File.dirname( __FILE__ ), ".." ) ) -CEEDLING_BIN = File.join( CEEDLING_ROOT, 'bin' ) -CEEDLING_LIB_BASE = File.join( CEEDLING_ROOT, 'lib' ) -CEEDLING_LIB = File.join( CEEDLING_LIB_BASE, 'ceedling' ) -CEEDLING_VENDOR = File.join( CEEDLING_ROOT, 'vendor' ) +# Get the path for our current code directory +ceedling_bin_path = File.expand_path( File.join( File.dirname( __FILE__ ), '..', 'bin' ) ) -# Add load path for `require 'ceedling/*'` statements and bin/ code -$LOAD_PATH.unshift( CEEDLING_BIN, CEEDLING_LIB_BASE ) +# Add load path so we can `require` files in bin/ +$LOAD_PATH.unshift( ceedling_bin_path ) + +# Pull in our startup configuration code in bin/ +require 'app_cfg' +CEEDLING_APPCFG = CeedlingAppConfig.new() + +# Add load paths for `require 'ceedling/*'` statements in bin/ code +$LOAD_PATH.unshift( CEEDLING_APPCFG[:ceedling_lib_base_path] ) require 'cli' # Located alongside this file in CEEDLING_BIN require 'constructor' # Assumed installed via Ceedling gem dependencies -require 'app_cfg' # Located alongside this file in CEEDLING_BIN - -CEEDLING_APPCFG = get_app_cfg() # Entry point begin + diy_vendor_path = File.join( CEEDLING_APPCFG[:ceedling_vendor_path], 'diy/lib' ) + # Construct all bootloader objects # 1. Add full path to $LOAD_PATH to simplify objects.yml # 2. Add vendored DIY to $LOAD_PATH so we can use it # 3. Require DIY (used by Ceedling application too) # 4. Perform object construction + dependency injection from bin/objects.yml - # 5. Remove unneeded / potentially problematic paths from $LOAD_PATH - $LOAD_PATH.unshift( CEEDLING_LIB ) - $LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'diy/lib') ) + # 5. Remove all paths added to $LOAD_PATH + # (Main application will restore certain paths -- possibly updated by :which_ceedling) + $LOAD_PATH.unshift( + CEEDLING_APPCFG[:ceedling_lib_path], + diy_vendor_path + ) require 'diy' - objects = DIY::Context.from_yaml( File.read( File.join( CEEDLING_BIN, 'objects.yml' ) ) ) + bin_objects_filepath = File.join( ceedling_bin_path, 'objects.yml' ) + objects = DIY::Context.from_yaml( File.read( bin_objects_filepath ) ) objects.build_everything() - $LOAD_PATH.delete( CEEDLING_BIN ) # Loaded in top-level `ceedling` script - $LOAD_PATH.delete( CEEDLING_LIB ) + # Remove all load paths we've relied on (main application will set up load paths again) + $LOAD_PATH.delete( ceedling_bin_path ) + $LOAD_PATH.delete( CEEDLING_APPCFG[:ceedling_lib_path] ) + $LOAD_PATH.delete( diy_vendor_path ) # Keep a copy of the command line for edge case CLI hacking (Thor consumes ARGV) _ARGV = ARGV.clone diff --git a/bin/cli.rb b/bin/cli.rb index 5d95d08d4..a5af1c6dc 100755 --- a/bin/cli.rb +++ b/bin/cli.rb @@ -101,8 +101,6 @@ module CeedlingTasks YAML file. See documentation for complete details. \x5> --mixin my_compiler --mixin my/path/mixin.yml" - CEEDLING_EXAMPLES_PATH = File.join( CEEDLING_ROOT, 'examples' ) - class CLI < Thor include Thor::Actions extend PermissiveCLI @@ -185,7 +183,7 @@ def new(name, dest=nil) _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil - @handler.new_project( CEEDLING_ROOT, _options, name, _dest ) + @handler.new_project( ENV, @app_cfg, _options, name, _dest ) end @@ -198,7 +196,7 @@ def new(name, dest=nil) PATH is required and should be the root of the project to upgrade. This command only meaningfully operates on projects wth a local vendored copy - of Ceedlng (in /vendor/) and optionally documentation (in + of Ceedling (in /vendor/) and optional documentation (in /docs/). Running this command replaces vendored Ceedling with the version running @@ -222,7 +220,7 @@ def upgrade(path) _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil - @handler.upgrade_project( CEEDLING_ROOT, _options, _path ) + @handler.upgrade_project( ENV, @app_cfg, _options, _path ) end @@ -336,7 +334,7 @@ def environment() to extract an example project to your filesystem. LONGDESC def examples() - @handler.list_examples( CEEDLING_EXAMPLES_PATH ) + @handler.list_examples( ENV, @app_cfg ) end @@ -369,7 +367,7 @@ def example(name, dest=nil) _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil - @handler.create_example( CEEDLING_ROOT, CEEDLING_EXAMPLES_PATH, _options, name, _dest ) + @handler.create_example( ENV, @app_cfg, _options, name, _dest ) end diff --git a/bin/cli_handler.rb b/bin/cli_handler.rb index ae28f68ec..6cdd3cd93 100755 --- a/bin/cli_handler.rb +++ b/bin/cli_handler.rb @@ -52,14 +52,14 @@ def app_help(env, app_cfg, options, command, &thor_help) # Public to be used by `-T` ARGV hack handling - def rake_help( env:, app_cfg:) + def rake_help(env:, app_cfg:) @helper.set_verbosity() # Default to normal list_rake_tasks( env:env, app_cfg:app_cfg ) end - def new_project(ceedling_root, options, name, dest) + def new_project(env, app_cfg, options, name, dest) @helper.set_verbosity( options[:verbosity] ) @path_validator.standardize_paths( dest ) @@ -74,6 +74,11 @@ def new_project(ceedling_root, options, name, dest) raise msg end unless options[:force] + # Update app_cfg paths (ignore return value) + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + ActionsWrapper.source_root( app_cfg[:ceedling_root_path] ) + # Blow away any existing directories and contents if --force @actions.remove_dir( dest ) if options[:force] @@ -83,25 +88,29 @@ def new_project(ceedling_root, options, name, dest) end # Vendor the tools and install command line helper scripts - @helper.vendor_tools( ceedling_root, dest ) if options[:local] + @helper.vendor_tools( app_cfg[:ceedling_root_path], dest ) if options[:local] # Copy in documentation - @helper.copy_docs( ceedling_root, dest ) if options[:docs] + @helper.copy_docs( app_cfg[:ceedling_root_path], dest ) if options[:docs] # Copy / set up project file - @helper.create_project_file( ceedling_root, dest, options[:local] ) if options[:configs] + @helper.create_project_file( app_cfg[:ceedling_root_path], dest, options[:local] ) if options[:configs] # Copy Git Ignore file if options[:gitsupport] - @actions._copy_file( File.join(ceedling_root,'assets','default_gitignore'), File.join(dest,'.gitignore'), :force => true ) - @actions._touch_file( File.join(dest, 'test/support', '.gitkeep') ) + @actions._copy_file( + File.join( app_cfg[:ceedling_root_path], 'assets', 'default_gitignore' ), + File.join( dest, '.gitignore' ), + :force => true + ) + @actions._touch_file( File.join( dest, 'test/support', '.gitkeep') ) end @streaminator.stream_puts( "\nNew project '#{name}' created at #{dest}/\n" ) end - def upgrade_project(ceedling_root, options, path) + def upgrade_project(env, app_cfg, options, path) @path_validator.standardize_paths( path, options[:project] ) # Check for existing project @@ -110,25 +119,24 @@ def upgrade_project(ceedling_root, options, path) raise msg end - project_filepath = File.join( path, options[:project] ) - _, config = @projectinator.load( filepath:project_filepath ) - - if (@helper.which_ceedling?( config ) == 'gem') + if (@helper.which_ceedling?( env:env, app_cfg:app_cfg ) == :gem) msg = "Project configuration specifies the Ceedling gem, not vendored Ceedling" raise msg end + ActionsWrapper.source_root( app_cfg[:ceedling_root_path] ) + # Recreate vendored tools vendor_path = File.join( path, 'vendor', 'ceedling' ) @actions.remove_dir( vendor_path ) - @helper.vendor_tools( ceedling_root, path ) + @helper.vendor_tools( app_cfg[:ceedling_root_path], path ) # Recreate documentation if we find docs/ subdirectory docs_path = File.join( path, 'docs' ) founds_docs = @helper.project_exists?( path, :&, File.join( 'docs', 'CeedlingPacket.md' ) ) if founds_docs @actions.remove_dir( docs_path ) - @helper.copy_docs( ceedling_root, path ) + @helper.copy_docs( app_cfg[:ceedling_root_path], path ) end @streaminator.stream_puts( "\nUpgraded project at #{path}/\n" ) @@ -140,7 +148,7 @@ def build(env:, app_cfg:, options:{}, tasks:) @path_validator.standardize_paths( options[:project], options[:logfile], *options[:mixin] ) - project_filepath, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) + _, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) default_tasks = @configinator.default_tasks( config:config, default_tasks:app_cfg[:default_tasks] ) @@ -165,27 +173,27 @@ def build(env:, app_cfg:, options:{}, tasks:) end # Save references - app_cfg[:project_config] = config - app_cfg[:log_filepath] = log_filepath - app_cfg[:include_test_case] = options[:test_case] - app_cfg[:exclude_test_case] = options[:exclude_test_case] + app_cfg.set_project_config( config ) + app_cfg.set_log_filepath( log_filepath ) + app_cfg.set_include_test_case( options[:test_case] ) + app_cfg.set_exclude_test_case( options[:exclude_test_case] ) # Set graceful_exit from command line & configuration options - app_cfg[:tests_graceful_fail] = - @helper.process_graceful_fail( + app_cfg.set_tests_graceful_fail( + @helper.process_graceful_fail( config: config, cmdline_graceful_fail: options[:graceful_fail], tasks: tasks, default_tasks: default_tasks ) + ) # Enable setup / operations duration logging in Rake context - app_cfg[:stopwatch] = @helper.process_stopwatch( tasks:tasks, default_tasks:default_tasks ) + app_cfg.set_stopwatch( @helper.process_stopwatch( tasks:tasks, default_tasks:default_tasks ) ) @helper.load_ceedling( - project_filepath: project_filepath, config: config, - which: app_cfg[:which_ceedling], + which: @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ), default_tasks: default_tasks ) @@ -199,7 +207,7 @@ def dumpconfig(env, app_cfg, options, filepath, sections) @path_validator.standardize_paths( filepath, options[:project], *options[:mixin] ) - project_filepath, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) + _, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) # Exception handling to ensure we dump the configuration regardless of config validation errors begin @@ -208,12 +216,11 @@ def dumpconfig(env, app_cfg, options, filepath, sections) default_tasks = @configinator.default_tasks( config:config, default_tasks:app_cfg[:default_tasks] ) # Save references - app_cfg[:project_config] = config + app_cfg.set_project_config( config ) config = @helper.load_ceedling( - project_filepath: project_filepath, config: config, - which: app_cfg[:which_ceedling], + which: @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ), default_tasks: default_tasks ) else @@ -231,15 +238,14 @@ def environment(env, app_cfg, options) @path_validator.standardize_paths( options[:project], *options[:mixin] ) - project_filepath, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) + _, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) # Save references - app_cfg[:project_config] = config + app_cfg.set_project_config( config ) - config = @helper.load_ceedling( - project_filepath: project_filepath, + config = @helper.load_ceedling( config: config, - which: app_cfg[:which_ceedling] + which: @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ) ) env_list = [] @@ -269,8 +275,11 @@ def environment(env, app_cfg, options) end - def list_examples(examples_path) - examples = @helper.lookup_example_projects( examples_path ) + def list_examples(env, app_cfg) + # Process which_ceedling for app_cfg but ignore return + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + examples = @helper.lookup_example_projects( app_cfg[:ceedling_examples_path] ) raise( "No examples projects found") if examples.empty? @@ -282,12 +291,15 @@ def list_examples(examples_path) end - def create_example(ceedling_root, examples_path, options, name, dest) + def create_example(env, app_cfg, options, name, dest) @helper.set_verbosity( options[:verbosity] ) @path_validator.standardize_paths( dest ) - examples = @helper.lookup_example_projects( examples_path ) + # Process which_ceedling for app_cfg but ignore return + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + examples = @helper.lookup_example_projects( app_cfg[:ceedling_examples_path] ) if !examples.include?( name ) raise( "No example project '#{name}' could be found" ) @@ -301,15 +313,17 @@ def create_example(ceedling_root, examples_path, options, name, dest) dest_test = File.join( dest, 'test' ) dest_project = File.join( dest, DEFAULT_PROJECT_FILENAME ) + ActionsWrapper.source_root( app_cfg[:ceedling_root_path] ) + @actions._directory( "examples/#{name}/src", dest_src, :force => true ) @actions._directory( "examples/#{name}/test", dest_test, :force => true ) @actions._copy_file( "examples/#{name}/#{DEFAULT_PROJECT_FILENAME}", dest_project, :force => true ) # Vendor the tools and install command line helper scripts - @helper.vendor_tools( ceedling_root, dest ) if options[:local] + @helper.vendor_tools( app_cfg[:ceedling_root_path], dest ) if options[:local] # Copy in documentation - @helper.copy_docs( ceedling_root, dest ) if options[:docs] + @helper.copy_docs( app_cfg[:ceedling_root_path], dest ) if options[:docs] @streaminator.stream_puts( "\nExample project '#{name}' created at #{dest}/\n" ) end @@ -332,7 +346,7 @@ def version() private def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[]) - project_filepath, config = + _, config = @configinator.loadinate( filepath: filepath, mixins: mixins, @@ -340,17 +354,16 @@ def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[]) ) # Save reference to loaded configuration - app_cfg[:project_config] = config - - @streaminator.stream_puts( "Ceedling Build & Plugin Tasks:\n(Parameterized tasks tend to require enclosing quotes and/or escape sequences in most shells)" ) + app_cfg.set_project_config( config ) @helper.load_ceedling( - project_filepath: project_filepath, config: config, - which: app_cfg[:which_ceedling], + which: @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ), default_tasks: app_cfg[:default_tasks] ) + @streaminator.stream_puts( "Ceedling Build & Plugin Tasks:\n(Parameterized tasks tend to need enclosing quotes or escape sequences in most shells)" ) + @helper.print_rake_tasks() end diff --git a/bin/cli_helper.rb b/bin/cli_helper.rb index 8d4b05151..095017cb9 100755 --- a/bin/cli_helper.rb +++ b/bin/cli_helper.rb @@ -6,6 +6,7 @@ # ========================================================================= require 'rbconfig' +require 'app_cfg' require 'ceedling/constants' # From Ceedling application class CliHelper @@ -49,48 +50,80 @@ def create_project_file(ceedling_root, dest, local) end - def which_ceedling?(config) - walked = @config_walkinator.fetch_value( config, :project, :which_ceedling ) - return 'gem' if walked[:value].nil? - return walked[:value] - end + def which_ceedling?(env:, config:{}, app_cfg:) + # Determine which Ceedling we're running (in priority) + # 1. If there's an environment variable set, validate it, and return :gem or a path + # 2. If :project ↳ :which_ceedling exists in the config, validate it, and return :gem or a path + # 3. If there's a vendor/ceedling/ path in our working directory, return it as a path + # 4. If nothing is set, default to :gem and return it + # 5. Update app_cfg paths if not the gem + # Nil for prioritized case checking logic blocks that follow + which_ceedling = nil - def load_ceedling(project_filepath:, config:, which:, default_tasks:[]) - # Determine which Ceedling we're running - # 1. Copy the which value passed in (most likely a default determined in the first moments of startup) - # 2. If a :project -> :which_ceedling entry exists in the config, use it instead - _which = which.dup() - walked = @config_walkinator.fetch_value( config, :project, :which_ceedling ) - _which = walked[:value] if !walked[:value].nil? + # Environment variable + if !env['WHICH_CEEDLING'].nil? + @streaminator.stream_puts( " > 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 - @path_validator.standardize_paths( _which ) + # Configuration file + if which_ceedling.nil? + walked = @config_walkinator.fetch_value( config, :project, :which_ceedling ) + if !walked[:value].nil? + @streaminator.stream_puts( " > Set which Ceedling from config entry :project -> :which_ceedling", Verbosity::OBNOXIOUS ) + which_ceedling = walked[:value].strip() + which_ceedling = :gem if (which_ceedling.casecmp( 'gem' ) == 0) + end + end - # Load Ceedling from the gem - if (_which == 'gem') - require 'ceedling' + # Working directory + if which_ceedling.nil? + if @file_wrapper.directory?( 'vendor/ceedling' ) + which_ceedling = 'vendor/ceedling' + @streaminator.stream_puts( " > Set which Ceedling to be vendored installation", Verbosity::OBNOXIOUS ) + end + end + + # Default to gem + if which_ceedling.nil? + which_ceedling = :gem + @streaminator.stream_puts( " > Defaulting to running Ceedling from Gem", Verbosity::OBNOXIOUS ) + end + + # Default gem path + ceedling_path = app_cfg[:ceedling_root_path] + if which_ceedling != :gem + ceedling_path = which_ceedling + @path_validator.standardize_paths( ceedling_path ) + if !@file_wrapper.directory?( ceedling_path ) + raise "Configured Ceedling launch path #{ceedling_path}/ does not exist" + end + + # Update installation paths + app_cfg.set_paths( ceedling_path ) + end + + @streaminator.stream_puts( " > Launching Ceedling from #{ceedling_path}/", Verbosity::OBNOXIOUS ) + + return ceedling_path + end + + + def load_ceedling(config:, which:, default_tasks:[]) + # Load Ceedling from the gem + if (which == :gem) + require( 'ceedling' ) # Load Ceedling from a path else - # If a relative :which_ceedling, load in relation to project file location - if @file_wrapper.relative?( _which ) - project_path = File.dirname( project_filepath ) - ceedling_path = File.join( project_path, _which ) - ceedling_path = File.expand_path( ceedling_path ) - - if !@file_wrapper.directory?( ceedling_path ) - raise "Configuration value :project -> :which_ceedling => '#{_which}' points to a path relative to your project file that contains no Ceedling installation" - end - - # Otherwise, :which_ceedling is an absolute path - else - if !@file_wrapper.exist?( ceedling_path ) - raise "Configuration value :project -> :which_ceedling => '#{_which}' points to a path that contains no Ceedling installation" - end + ceedling_path = File.join( File.expand_path( which ), 'lib/ceedling.rb' ) + if !@file_wrapper.exist?( ceedling_path ) + raise "Configured Ceedling launch path #{which}/ contains no Ceedling installation" end - require( File.join( ceedling_path, '/lib/ceedling.rb' ) ) - @streaminator.stream_puts( " > Running Ceedling from #{ceedling_path}/", Verbosity::OBNOXIOUS ) + require( ceedling_path ) end # Set default tasks @@ -99,7 +132,7 @@ def load_ceedling(project_filepath:, config:, which:, default_tasks:[]) # Load Ceedling Ceedling.load_rakefile() - # Processing the Rakefile in the preceeding line processes the config hash + # Loading the Rakefile manipulates the config hash, return it as a convenience return config end diff --git a/bin/configinator.rb b/bin/configinator.rb index 6f92286da..0386db91c 100755 --- a/bin/configinator.rb +++ b/bin/configinator.rb @@ -9,7 +9,8 @@ class Configinator - MIXINS_BASE_PATH = File.join( CEEDLING_ROOT, 'mixins' ) + # TODO: Temporary path until built-in mixins load path handling is replaced with internal hash + MIXINS_BASE_PATH = '.' constructor :config_walkinator, :projectinator, :mixinator diff --git a/bin/mixinator.rb b/bin/mixinator.rb index 447c58c61..8037e575c 100755 --- a/bin/mixinator.rb +++ b/bin/mixinator.rb @@ -104,7 +104,7 @@ def merge(config:, mixins:) config.deep_merge( _mixin ) # Log what filepath we used for this mixin - @streaminator.stream_puts( " + Merged #{'(empty) ' if _mixin.empty?}#{source} mixin using #{filepath}", Verbosity::DEBUG ) + @streaminator.stream_puts( " + Merged #{'(empty) ' if _mixin.empty?}#{source} mixin using #{filepath}", Verbosity::OBNOXIOUS ) end # Validate final configuration diff --git a/lib/ceedling/config_matchinator.rb b/lib/ceedling/config_matchinator.rb index b9480e9a7..bd123572d 100644 --- a/lib/ceedling/config_matchinator.rb +++ b/lib/ceedling/config_matchinator.rb @@ -92,8 +92,8 @@ def validate_matchers(hash:, section:, context:, operation:nil) # Look for matcher keys with missing values hash.each do |k, v| if v == nil - path = generate_matcher_path(section, context, operation) - error = "ERROR: Missing list of values for [#{path} -> '#{k}' matcher in project configuration." + path = generate_matcher_path( section, context, operation ) + error = "ERROR: Missing list of values for #{path} -> '#{k}' matcher in project configuration." raise CeedlingException.new(error) end end diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index b7c0c6ee1..aff78943d 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -376,10 +376,10 @@ def validate_final(config) # Create constants and accessors (attached to this object) from given hash - def build(config, *keys) + def build(build_project_config, config, *keys) flattened_config = @configurator_builder.flattenify( config ) - @configurator_setup.build_project_config( flattened_config ) + @configurator_setup.build_project_config( build_project_config, flattened_config ) @configurator_setup.build_directory_structure( flattened_config ) diff --git a/lib/ceedling/configurator_builder.rb b/lib/ceedling/configurator_builder.rb index 89c0283f8..c044e7835 100644 --- a/lib/ceedling/configurator_builder.rb +++ b/lib/ceedling/configurator_builder.rb @@ -172,16 +172,18 @@ def set_build_paths(in_hash) end - def set_rakefile_components(in_hash) + def set_rakefile_components(ceedling_lib_path, in_hash) out_hash = { - :project_rakefile_component_files => - [File.join(CEEDLING_LIB, 'tasks_base.rake'), - File.join(CEEDLING_LIB, 'tasks_filesystem.rake'), - File.join(CEEDLING_LIB, 'tasks_tests.rake'), - File.join(CEEDLING_LIB, 'rules_tests.rake')]} - - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'rules_release.rake') if (in_hash[:project_release_build]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'tasks_release.rake') if (in_hash[:project_release_build]) + :project_rakefile_component_files => [ + File.join( ceedling_lib_path, 'tasks_base.rake' ), + File.join( ceedling_lib_path, 'tasks_filesystem.rake' ), + File.join( ceedling_lib_path, 'tasks_tests.rake' ), + File.join( ceedling_lib_path, 'rules_tests.rake' ) + ] + } + + out_hash[:project_rakefile_component_files] << File.join( ceedling_lib_path, 'rules_release.rake' ) if (in_hash[:project_release_build]) + out_hash[:project_rakefile_component_files] << File.join( ceedling_lib_path, 'tasks_release.rake' ) if (in_hash[:project_release_build]) return out_hash end diff --git a/lib/ceedling/configurator_setup.rb b/lib/ceedling/configurator_setup.rb index 29bba17e4..5a755e7e7 100644 --- a/lib/ceedling/configurator_setup.rb +++ b/lib/ceedling/configurator_setup.rb @@ -29,14 +29,14 @@ def inspect return this.class.name end - def build_project_config(flattened_config) + def build_project_config(ceedling_lib_path, flattened_config) ### flesh out config @configurator_builder.cleanup( flattened_config ) @configurator_builder.set_exception_handling( flattened_config ) ### add to hash values we build up from configuration & file system contents flattened_config.merge!( @configurator_builder.set_build_paths( flattened_config ) ) - flattened_config.merge!( @configurator_builder.set_rakefile_components( flattened_config ) ) + flattened_config.merge!( @configurator_builder.set_rakefile_components( ceedling_lib_path, flattened_config ) ) flattened_config.merge!( @configurator_builder.set_release_target( flattened_config ) ) flattened_config.merge!( @configurator_builder.set_build_thread_counts( flattened_config ) ) diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index 187c96d7e..72b432e02 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -9,8 +9,10 @@ require 'ceedling/system_wrapper' require 'ceedling/file_path_utils' -#this should be defined already, but not always during system specs -CEEDLING_VENDOR = File.expand_path(File.dirname(__FILE__) + '/../../vendor') unless defined? CEEDLING_VENDOR +# Assign a default value for system testing where CEEDLING_APPCFG may not be present +# TODO: Create code config & test structure that does not internalize a test path like this +CEEDLING_VENDOR = defined?( CEEDLING_APPCFG ) ? CEEDLING_APPCFG[:ceedling_vendor_path] : File.expand_path( File.dirname(__FILE__) + '/../../vendor' ) + CEEDLING_PLUGINS = [] unless defined? CEEDLING_PLUGINS DEFAULT_TEST_COMPILER_TOOL = { diff --git a/lib/ceedling/rakefile.rb b/lib/ceedling/rakefile.rb index 541cc2ad5..acb813a83 100644 --- a/lib/ceedling/rakefile.rb +++ b/lib/ceedling/rakefile.rb @@ -7,8 +7,9 @@ require 'fileutils' -$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'unity/auto') ) -$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'cmock/lib') ) +# Add Unity and CMock's Ruby code paths to $LOAD_PATH for runner generation and mocking +$LOAD_PATH.unshift( File.join( CEEDLING_APPCFG[:ceedling_vendor_path], 'unity/auto') ) +$LOAD_PATH.unshift( File.join( CEEDLING_APPCFG[:ceedling_vendor_path], 'cmock/lib') ) require 'rake' @@ -59,10 +60,11 @@ def boom_handler(exception:, debug:) # 1. Add full path to $LOAD_PATH to simplify objects.yml # 2. Perform object construction + dependency injection # 3. Remove full path from $LOAD_PATH - $LOAD_PATH.unshift( CEEDLING_LIB ) - @ceedling = DIY::Context.from_yaml( File.read( File.join( CEEDLING_LIB, 'objects.yml' ) ) ) + $LOAD_PATH.unshift( CEEDLING_APPCFG[:ceedling_lib_path] ) + objects_filepath = File.join( CEEDLING_APPCFG[:ceedling_lib_path], 'objects.yml' ) + @ceedling = DIY::Context.from_yaml( File.read( objects_filepath ) ) @ceedling.build_everything() - $LOAD_PATH.delete( CEEDLING_LIB ) + $LOAD_PATH.delete( CEEDLING_APPCFG[:ceedling_lib_path] ) # One-stop shopping for all our setup and such after construction @ceedling[:setupinator].ceedling = @ceedling diff --git a/lib/ceedling/setupinator.rb b/lib/ceedling/setupinator.rb index 6d221a5db..3fc7d4db7 100644 --- a/lib/ceedling/setupinator.rb +++ b/lib/ceedling/setupinator.rb @@ -46,7 +46,7 @@ def do_setup( app_cfg ) @ceedling[:configurator].tools_setup( config_hash ) @ceedling[:configurator].validate_final( config_hash ) # Partially flatten config + build Configurator accessors and globals - @ceedling[:configurator].build( config_hash, :environment ) + @ceedling[:configurator].build( app_cfg[:ceedling_lib_path], config_hash, :environment ) @ceedling[:configurator].insert_rake_plugins( @ceedling[:configurator].rake_plugins ) @ceedling[:configurator].tools_supplement_arguments( config_hash ) diff --git a/lib/ceedling/version.rb b/lib/ceedling/version.rb index 861983390..d4a59beca 100644 --- a/lib/ceedling/version.rb +++ b/lib/ceedling/version.rb @@ -5,56 +5,40 @@ # SPDX-License-Identifier: MIT # ========================================================================= -# @private +require 'ceedling/exceptions' + module Ceedling module Version - { "UNITY" => File.join("unity","src","unity.h"), - "CMOCK" => File.join("cmock","src","cmock.h"), - "CEXCEPTION" => File.join("c_exception","lib","CException.h") + # If this file is loaded, we know it is next to the vendor path to use for version lookups + vendor_path = File.expand_path( File.join( File.dirname( __FILE__ ), '../../vendor' ) ) + + # Anonymous hash + { + 'UNITY' => File.join( 'unity', 'src', 'unity.h' ), + 'CMOCK' => File.join( 'cmock', 'src', 'cmock.h' ), + 'CEXCEPTION' => File.join( 'c_exception', 'lib', 'CException.h' ) }.each_pair do |name, path| - # Check for local or global version of vendor directory in order to look up versions - path1 = File.expand_path( File.join("..","..","vendor",path) ) - path2 = File.expand_path( File.join(File.dirname(__FILE__),"..","..","vendor",path) ) - filename = if (File.exist?(path1)) - path1 - elsif (File.exist?(path2)) - path2 - elsif File.exist?(CEEDLING_VENDOR) - path3 = File.expand_path( File.join(CEEDLING_VENDOR,path) ) - if (File.exist?(path3)) - path3 - else - basepath = File.join( CEEDLING_VENDOR, path.split(/\\\//)[0], 'release') - begin - [ @ceedling[:file_wrapper].read( File.join(base_path, 'release', 'version.info') ).strip, - @ceedling[:file_wrapper].read( File.join(base_path, 'release', 'build.info') ).strip ].join('.') - rescue - "#{name}" - end - end - else - module_eval("#{name} = 'unknown'") - continue - end + filename = File.join( vendor_path, path ) # Actually look up the versions a = [0,0,0] + begin - File.readlines(filename).each do |line| - ["VERSION_MAJOR", "VERSION_MINOR", "VERSION_BUILD"].each_with_index do |field, i| + File.readlines( filename ).each do |line| + ['VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_BUILD'].each_with_index do |field, i| m = line.match(/#{name}_#{field}\s+(\d+)/) a[i] = m[1] unless (m.nil?) end end rescue - abort("Can't collect data for vendor component: \"#{filename}\" . \nPlease check your setup.") + raise CeedlingException.new( "Could not collect version information for vendor component: #{filename}" ) end - # splat it to return the final value + # Splat it to crete the final constant eval("#{name} = '#{a.join(".")}'") end - GEM = "0.32.0" + GEM = "1.0.0" CEEDLING = GEM puts CEEDLING if __FILE__ == $0