Class: RakeTaskRegistry

Inherits:
Object show all
Defined in:
lib/ceedling/rake_app/rake_task_registry.rb

Overview

Tracks which Rake task namespaces are associated with which semantic tags (:test, :release, :build) so bin/ CLI code and other components can ask, e.g. "does invoking this task run tests?" without waiting for Rake to load .rake files and build real task objects.

The unit of registration is the root namespace (e.g. 'test', 'gcov'), not the full task name. This handles all Rake task name forms uniformly:

- Explicitly named tasks  (test:all)
- Rule-synthesized tasks  (test:foo_file)
- Bare top-level aliases  (test)

All share the same root namespace and are therefore all covered once that namespace is registered.

Namespaces are discovered by scanning .rake files as text for marker regexes (see register_tasks) rather than via Rake's task inspection API, because Rake tasks don't exist as objects until their .rake files are loaded. Namespace identification is needed before loading .rake files, so bin/ CLI code and other components can determine whether the user's requested tasks include tests or other build domains.

Registration happens in two passes over the Ceedling lifecycle:

Pass 1 — Early, from bin/cli_helper.rb before .rake files are loaded.
constants.rb is already required, so stock constants (TEST_SYM, RELEASE_SYM,
UTILS_SYM) resolve correctly via Object.const_get. Plugin-specific constants
(GCOV_SYM, VALGRIND_SYM, BULLSEYE_SYM, etc.) are defined in plugin lib/ files
that have not been loaded yet, causing Object.const_get to raise NameError.

Pass 2 — After .rake files are loaded in rakefile.rb.
All plugin lib/ paths have been added to $LOAD_PATH and all constants are
defined. Object.const_get succeeds. The registry is cleared and rebuilt.

Unresolvable constants (NameError in Pass 1) are handled by the _SYM convention fallback in find_enclosing_namespace.

Constant Summary collapse

TAG_TEST =

Semantic tag constants — used as keys in the namespace_tags registry

:test
TAG_RELEASE =
:release
TAG_BUILD =
:build
TAGS_TEST =

Composite tag sets — applied as a group when registering a domain

[TAG_TEST,    TAG_BUILD].freeze
TAGS_RELEASE =
[TAG_RELEASE, TAG_BUILD].freeze
MARKER_TEST_TASKS_SETUP_AND_INVOKE =

Marker regexes — a line matching any marker signals the enclosing namespace belongs to the associated domain.

MARKER_TEST_TASKS_SETUP_AND_INVOKE matches any call of the form:

@ceedling[:test_invoker].setup_and_invoke(

MARKER_RELEASE_TASKS matches any call of the form:

@ceedling[:release_invoker].setup_and_invoke_objects(
/\[\s*:test_invoker\s*\]\.setup_and_invoke/
MARKER_RELEASE_TASKS =
/\[\s*:release_invoker\s*\]\.setup_and_invoke_objects/

Instance Method Summary collapse

Constructor Details

#initializeRakeTaskRegistry

Returns a new instance of RakeTaskRegistry.



66
67
68
69
70
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 66

def initialize
  # Maps root namespace string → Array<Symbol> of semantic tags
  # e.g. { 'test' => [:test, :build], 'gcov' => [:test, :build], 'release' => [:release, :build] }
  @namespace_tags = {}
end

Instance Method Details

#namespaces_for_tag(tag) ⇒ Object

Returns all registered namespace strings that carry the given tag. Used by RakeInvocationTracker to build dynamic regex patterns for invocation checks.



90
91
92
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 90

def namespaces_for_tag(tag)
  @namespace_tags.select { |_, tags| tags.include?( tag ) }.keys
end

#register_namespace(namespace, *tags) ⇒ Object

Directly register a namespace string with one or more tags. Merges with any existing tags — does not overwrite prior registrations. Example: register_namespace( 'plugin', *TAGS_TEST )



97
98
99
100
101
102
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 97

def register_namespace(namespace, *tags)
  @namespace_tags[namespace] ||= []
  tags.each do |tag|
    @namespace_tags[namespace] << tag unless @namespace_tags[namespace].include?( tag )
  end
end

#register_release_tasks(rakefile_paths) ⇒ Object

Clears all release-tagged namespaces and re-scans for release pipeline invocations.



160
161
162
163
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 160

def register_release_tasks(rakefile_paths)
  @namespace_tags.delete_if { |_, tags| tags.include?( TAG_RELEASE ) }
  register_tasks( rakefile_paths, markers: [MARKER_RELEASE_TASKS], tags: TAGS_RELEASE )
end

#register_tasks(rakefile_paths, markers:, tags:) ⇒ Object

Scans .rake files as text to identify Rake namespaces that call certain marker code, registering each namespace's root with the given tags.

Algorithm per file:

1. Skip the file if no marker regex matches the full content (fast pre-check).
2. For each line matching any marker, search backwards to find the
 enclosing `namespace` declaration.
3. Resolve the namespace identifier to a string (see find_enclosing_namespace).
4. Register the namespace string with the provided tags.

Called once per pass (see class comment) — Pass 2 clears and rebuilds the registry so it doesn't accumulate stale Pass 1 entries.



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
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 116

def register_tasks(rakefile_paths, markers:, tags:)
  rakefile_paths.each do |filepath|
    begin
      # Force valid UTF-8: transcode BOM-prefixed files correctly and replace
      # any invalid/undefined byte sequences so regex matching below can't
      # raise on files containing arbitrary unicode content.
      content = File.read( filepath, encoding: 'bom|utf-8', invalid: :replace, undef: :replace, replace: '' )
    rescue
      next  # Skip unreadable files without failing
    end

    next unless markers.any? { |m| content.match?( m ) }

    lines = content.lines

    lines.each_with_index do |line, idx|
      next unless markers.any? { |m| line.match?( m ) }

      namespace_name = find_enclosing_namespace( lines, idx )
      next if namespace_name.nil?

      @namespace_tags[namespace_name] ||= []
      tags.each do |tag|
        @namespace_tags[namespace_name] << tag unless @namespace_tags[namespace_name].include?( tag )
      end
    end
  end
end

#register_test_tasks(rakefile_paths) ⇒ Object

Clears all test-tagged namespaces and re-scans for test pipeline invocations. The delete_if ensures Pass 2 replaces Pass 1 results cleanly without disturbing release-tagged entries registered independently.



148
149
150
151
152
153
154
155
156
157
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 148

def register_test_tasks(rakefile_paths)
  @namespace_tags.delete_if { |_, tags| tags.include?( TAG_TEST ) }
  register_tasks(
    rakefile_paths, 
    markers: [
      MARKER_TEST_TASKS_SETUP_AND_INVOKE,
      ],
    tags: TAGS_TEST
  )
end

#tags_for(task_name) ⇒ Object

Returns all tags associated with a task name. Any task form maps to its root namespace:

'test:all'      → root 'test'
'test:foo_file' → root 'test'  (rule-synthesized tasks handled for free)
'test'          → root 'test'  (bare alias handled for free)
'gcov:all'      → root 'gcov'


78
79
80
81
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 78

def tags_for(task_name)
  root = task_name.split(':').first
  @namespace_tags[root] || []
end

#task_is?(task_name, tag) ⇒ Boolean

Returns true if the task is associated with the given tag.

Returns:

  • (Boolean)


84
85
86
# File 'lib/ceedling/rake_app/rake_task_registry.rb', line 84

def task_is?(task_name, tag)
  tags_for( task_name ).include?( tag )
end