Class: Includes

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

Overview

=========================================================================

Ceedling - Test-Centered Build System for C ThrowTheSwitch.org Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams SPDX-License-Identifier: MIT

Class Method Summary collapse

Class Method Details

.contains?(includes, filename) ⇒ Boolean

Class method to check for a filename in the collection

Returns:

  • (Boolean)


98
99
100
# File 'lib/ceedling/includes/includes.rb', line 98

def self.contains?(includes, filename)
  includes.any? { |include| include.filename == filename }
end

.filter(includes, pattern) ⇒ Object

Class method to extract all matching includes by filename pattern



83
84
85
# File 'lib/ceedling/includes/includes.rb', line 83

def self.filter(includes, pattern)
  includes.select { |include| include.filename =~ pattern }
end

.from_hashes(hashes) ⇒ Array<Include>

Class method to convert a list of hashes back into Include objects

Examples:

hashes = [
  { 'type' => 'user', 'filepath' => 'header.h' },
  { 'type' => 'system', 'filepath' => 'stdio.h' },
  { 'type' => 'user', 'filepath' => 'module.h' }
]
Include.from_hashes(hashes)
# => [
#   UserInclude.new("header.h"),
#   SystemInclude.new("stdio.h"),
#   UserInclude.new("module.h")
# ]

Parameters:

  • hashes (Array<Hash>)

    Array of hashes with 'type' and 'filepath' keys

Returns:

  • (Array<Include>)

    List of UserInclude and SystemInclude objects

Raises:

  • (ArgumentError)

    If hash is missing required keys or has invalid type



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ceedling/includes/includes.rb', line 64

def self.from_hashes(hashes)
  return hashes.map do |hash|
    raise ArgumentError, "Hash missing 'type' key" unless hash.key?('type')
    raise ArgumentError, "Hash missing 'filepath' key" unless hash.key?('filepath')
    
    case hash['type']
    when 'user'
      UserInclude.new(hash['filepath'], include_path: hash['include_path'])
    when 'mock'
      MockInclude.new(hash['filepath'])
    when 'system'
      SystemInclude.new(hash['filepath'], include_path: hash['include_path'])
    else
      raise ArgumentError, "Invalid include type: #{hash['type']}. Must be 'user' or 'system'"
    end
  end
end

.reconcile(bare:, user:, system:) ⇒ Object

Class method to reconcile bare, user, and system includes returning a list of reconciled user and system includes.

Purpose

Bare include preprocessing extracts user and system includes, but there's no way to explicitly differentiate these. Meanwhile, by necessity, user and system include extraction can identify too many includes. This class method uses the knowledeg of the different types of extraction to reconcile the two lists. It accomplishes:

  1. Paring down system includes to the include directives used in original file.
  2. Paring down user includes to the include directives used in original file.
  3. Reconciling a list of user & system includes properly distinguished.

Method

Compares bare includes against user and system includes and applies the following rules:

  1. Intersection of bare includes and system includes.
  2. Intersection of bare includes and user includes.

A matched system include's filepath is its real, resolved location (possibly absolute), which is what identity and deduplication need but is wrong to render verbatim into a generated file. Each matched system include is therefore rebuilt carrying include_path: recovered from bare -- the original, as-written directive text -- via best_bare_match, so <sys/stat.h> renders as written instead of collapsing to <stat.h>. User includes are left untouched; unlike a system header's <...> text, a quoted include's own bare-includes pass can be resolved by GCC's directory-relative search before reconciliation ever sees it, so bare isn't a reliable source of "the literal, as-written spelling" for a user include.



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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/ceedling/includes/includes.rb', line 171

def self.reconcile(bare:, user:, system:)
  # Validate input types

  # `bare` can only be base Include objects, no sub-classes.
  unless bare.is_a?(Array) && bare.all? { |include| include.class == Include }
    raise ArgumentError, "`bare` must be an Array of Include objects"
  end

  # Ensure `user` is an array of UserInclude objects or sub-classes
  unless user.is_a?(Array) && user.all? { |include| include.is_a?(UserInclude) }    
    raise ArgumentError, "`user` must be an Array of UserInclude objects"
  end
  
  # Ensure `system` is an array of SystemInclude objects or sub-classes
  unless system.is_a?(Array) && system.all? { |include| include.is_a?(SystemInclude) }    
    raise ArgumentError, "`system` must be an Array of SystemInclude objects"
  end

  return [] if bare.empty?

  system_includes = []
  user_includes = []

  # Create set of bare include filenames for O(1) lookup
  bare_filenames = Set.new(bare.map(&:filename))

  # Intersect system includes with bare includes based on filename, then rebuild each
  # matched system include carrying the original, as-written directive spelling
  # recovered from bare via best_bare_match, since filepath alone (the real, resolved
  # location) is wrong to render verbatim.
  system_includes = system.select do |include|
    bare_filenames.include?(include.filename)
  end.map do |include|
    original = best_bare_match(bare, include.filepath)
    SystemInclude.new(include.filepath, include_path: original.filepath)
  end

  # Intersect user includes with bare includes based on filename.
  # Keep user includes (including subclasses) that have matching filenames in bare list.
  user_includes = user.select do |include|
    bare_filenames.include?(include.filename)
  end    

  # Construct reconciled list of includes with reconciled results.
  # Always system includes first (C best practice).
  return (system_includes + user_includes)
end

.sanitize(includes, &block) {|include| ... } ⇒ Array<Include>

Class method for non-mutating sanitize

Examples:

Basic usage

Includes.sanitize(includes)

Custom rejection

Includes.sanitize(includes) { |include, all| ... }

Parameters:

  • includes (Array<Include>)

    List of includes to sanitize

  • block (Proc)

    Optional block passed to reject! for custom filtering

Yields:

  • (include)

    Each include object for custom rejection logic

Returns:

  • (Array<Include>)

    New sanitized list



112
113
114
115
116
# File 'lib/ceedling/includes/includes.rb', line 112

def self.sanitize(includes, &block)
  _includes = includes.clone
  self.sanitize!(_includes, &block)
  return _includes
end

.sanitize!(includes, &block) {|include| ... } ⇒ Array<Include>

Class method for mutating sanitize

Examples:

Basic usage

Includes.sanitize!(includes)

Custom rejection

Includes.sanitize!(includes) { |include, all| ... }

Parameters:

  • includes (Array<Include>)

    List of includes to sanitize in place

  • block (Proc)

    Optional block passed to reject! for custom filtering

Yields:

  • (include)

    Each include object for custom rejection logic

Returns:

  • (Array<Include>)

    The modified includes list



128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/ceedling/includes/includes.rb', line 128

def self.sanitize!(includes, &block)
  # Remove any duplicates
  includes.uniq!

  # Apply custom rejection with access to full list if block provided
  if block_given?
    includes.reject! { |include| block.call(include, includes) }
  end

  # Ensure system includes come first
  self.sort!(includes)

  return includes
end

.sort(includes) ⇒ Object

Sort list so system includes are at the beginning (Best practice)



251
252
253
254
255
# File 'lib/ceedling/includes/includes.rb', line 251

def self.sort(includes)
  _includes = includes.clone
  self.sort!(_includes)
  return _includes
end

.sort!(includes) ⇒ Object



257
258
259
260
# File 'lib/ceedling/includes/includes.rb', line 257

def self.sort!(includes)
  includes.sort_by! { |include| include.is_a?(SystemInclude) ? 0 : 1 }
  return includes
end

.system(includes) ⇒ Object

Class method to extract all system includes



88
89
90
# File 'lib/ceedling/includes/includes.rb', line 88

def self.system(includes)
  includes.select { |include| include.is_a?(SystemInclude) }
end

.to_hashes(includes) ⇒ Array<Hash>

Class method to convert mixed list of Include objects into an order-preserving list of hashes

Examples:

includes = [
  UserInclude.new("header.h"),
  SystemInclude.new("stdio.h"),
  UserInclude.new("module.h")
]
Include.to_hash(includes)
# => [
#   { 'type' => 'user', 'filepath' => 'header.h' },
#   { 'type' => 'system', 'filepath' => 'stdio.h' },
#   { 'type' => 'user', 'filepath' => 'module.h' }
# ]

Parameters:

  • includes (Array<Include>)

    List of UserInclude and SystemInclude objects

Returns:

  • (Array<Hash>)

    Array of hashes, each with 'type' and 'filepath' keys



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/ceedling/includes/includes.rb', line 25

def self.to_hashes(includes)
  return includes.map do |include|
    type = 
      case include
      when MockInclude then 'mock'
      when UserInclude then 'user'
      when SystemInclude then 'system'
      else raise ArgumentError, "Unknown Include type: #{include.class}"
      end

    hash = {
      'type' => type,
      'filepath' => include.filepath,
    }
    # `include_path` is only ever meaningful on a reconciled SystemInclude, so it's
    # written only when actually set -- a cached build's YAML stays uncluttered for
    # the common, plain case.
    hash['include_path'] = include.include_path if include.include_path
    hash
  end
end

.user(includes) ⇒ Object

Class method to extract all user includes



93
94
95
# File 'lib/ceedling/includes/includes.rb', line 93

def self.user(includes)
  includes.select { |include| include.is_a?(UserInclude) }
end