Class: PreprocessinatorIncludesHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/ceedling/preprocess/preprocessinator_includes_handler.rb

Instance Method Summary collapse

Instance Method Details

#extract_bare_includes(test:, filepath:, search_paths:, flags:, defines:) ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 34

def extract_bare_includes(test:, filepath:, search_paths:, flags:, defines:)
  filename = File.basename(filepath)

  msg = @reportinator.generate_module_progress(
    operation: "Extracting bare #includes via preprocessing from",
    module_name: test,
    filename: filename
  )
  @loginator.log( msg, Verbosity::OBNOXIOUS )

  # Creation:
  #  - This output is created with the -MM -MG -MP command line options.
  #  - Limited search paths are used towards shallow extracting of only the user #include statements of the file.
  #    This preprocessor mode assumes any includes discovered outside of a search path will be generated.
  #
  # Notes:
  #  - This approach can have gaps with advacnced user-level macros like `#include <MACRO>`.
  #    By including Ceedling's vendor search path, we support Partials macros of this sort.
  #  - Gaps can be minimized with proper defines in the project file. However, needed, complex macros
  #    located in other header files could still gum up the works.
  #  - Many errors can occur but may not necessarily prevent usable results.
  #
  # GCC's quoted #include resolution always checks the directory of the file it's currently
  # processing, independent of search paths -- a real sibling header on disk is opened and
  # recursed into regardless of the restricted search paths above. Staging an isolated,
  # sibling-free copy of the file being scanned keeps this pass's output limited to genuine
  # top-level #include statements only. The isolation directory is minted directly inside the
  # test's own preprocess-files build directory (already created in an earlier build stage) --
  # no dedicated subdirectory or filename-based naming, to keep the resulting path as short as
  # possible for platforms with tight path length limits.
  isolation_parent = @file_path_utils.form_test_preprocess_files_path( test )
  isolation_dir = @file_wrapper.mkdir_tmp( nil, isolation_parent )
  isolated_filepath = File.join( isolation_dir, filename )

  begin
    @file_wrapper.cp( filepath, isolated_filepath )

    msg = @reportinator.generate_module_progress(
      operation: "Isolating a sibling-free copy for bare-includes extraction at",
      module_name: test,
      filename: isolated_filepath
    )
    @loginator.log( msg, Verbosity::OBNOXIOUS )

    command =
      @tool_executor.build_command_line(
        @configurator.tools_test_bare_includes_preprocessor,
        # No additional arguments
        [],
        # Argument replacement
        isolated_filepath,
        defines,
        flags,
        search_paths
      )

    # Assume possible errors so we have best shot at extracting results from preprocessing.
    # Full code compilation will catch any breaking code errors
    command[:options][:boom] = false
    shell_result = @tool_executor.exec( command )
  ensure
    @file_wrapper.rm_rf( isolation_dir )
  end

  make_rules = shell_result[:output]

  # Do not check exit code for success. In some error conditions we still get usable output.
  # Look for the first line of the make rule output.
  if not make_rules =~ PreprocessinatorBareIncludesExtractor::MAKE_RULE_MATCHER
    @loginator.lazy( Verbosity::DEBUG ) do
      "Preprocessor bare #include extraction failed: #{shell_result[:output]}"
    end
    return []
  end

  includes = PreprocessinatorBareIncludesExtractor.extract_includes( make_rules )
  includes = clean_self_reference(filepath, includes)

  header = "Extracted bare #includes from #{filepath}:"
  @loginator.log_list( includes, header, Verbosity::DEBUG )

  return includes
end

#extract_system_includes_from_text(name:, filepath:, defines: []) ⇒ Object



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
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 190

def extract_system_includes_from_text(name:, filepath:, defines: [])
  includes = []

  filename = File.basename(filepath)

  msg = @reportinator.generate_module_progress(
    operation: "Extracting system #includes from original file using fallback method",
    module_name: name,
    filename: filename
  )
  @loginator.log( msg, Verbosity::OBNOXIOUS, LogLabels::WARNING )

  cond_tracker = CPreprocessorConditionals.new( defines )

  # Open in binary mode: code_lines applies clean_encoding per-line, but each_line
  # itself can raise on invalid byte sequences before clean_encoding is reached.
  @file_wrapper.open(filepath, 'rb') do |input|
    @parsing_parcels.code_lines( input ) do |line|
      cond_tracker.process_directive( line )
      next unless cond_tracker.active?
      _include = @include_factory.system_include_from_directive( line )
      includes << _include if !_include.nil?
    end
  end

  return clean_self_reference( filepath, includes )
end

#extract_system_includes_preprocess(name:, filepath:, preprocessed_filepath:) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 168

def extract_system_includes_preprocess(name:, filepath:, preprocessed_filepath:)
  includes = []

  filename = File.basename(filepath)

  msg = @reportinator.generate_module_progress(
    operation: "Extracting system #includes from preprocessed output",
    module_name: name,
    filename: filename
  )
  @loginator.log(msg, Verbosity::OBNOXIOUS)

  includes = 
    @line_marker_includes_extractor.extract_includes_from_file(
      preprocessed_filepath,
      PreprocessinatorLineMarkerIncludesExtractor::SYSTEM,
      5 # Practical max depth limit for system headers (to avoid noisy length)
    )

  return clean_self_reference( filepath, includes )
end

#extract_user_includes_from_text(name:, filepath:, defines: []) ⇒ Object



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 140

def extract_user_includes_from_text(name:, filepath:, defines: [])
  includes = []

  filename = File.basename(filepath)

  msg = @reportinator.generate_module_progress(
    operation: "Extracting user #includes from original file using fallback method",
    module_name: name,
    filename: filename
  )
  @loginator.log( msg, Verbosity::OBNOXIOUS, LogLabels::WARNING )

  cond_tracker = CPreprocessorConditionals.new( defines )

  # Open in binary mode: code_lines applies clean_encoding per-line, but each_line
  # itself can raise on invalid byte sequences before clean_encoding is reached.
  @file_wrapper.open(filepath, 'rb') do |input|
    @parsing_parcels.code_lines( input ) do |line|
      cond_tracker.process_directive( line )
      next unless cond_tracker.active?
      _include = @include_factory.user_include_from_directive( line )
      includes << _include if !_include.nil?
    end
  end

  return clean_self_reference( filepath, includes )
end

#extract_user_includes_preprocess(name:, filepath:, preprocessed_filepath:) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 118

def extract_user_includes_preprocess(name:, filepath:, preprocessed_filepath:)
  includes = []

  filename = File.basename(filepath)

  msg = @reportinator.generate_module_progress(
    operation: "Extracting user #includes from preprocessed output",
    module_name: name,
    filename: filename
  )
  @loginator.log(msg, Verbosity::OBNOXIOUS)

  includes = 
    @line_marker_includes_extractor.extract_includes_from_file(
      preprocessed_filepath,
      PreprocessinatorLineMarkerIncludesExtractor::USER
      # Note: No limit to max depth to search for user includes
    )

  return clean_self_reference( filepath, includes )
end

#load_includes_list(filepath) ⇒ Object



223
224
225
226
227
228
229
230
231
232
233
234
235
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 223

def load_includes_list(filepath)
  loaded = begin
    @yaml_wrapper.load( filepath )
  rescue YamlLoadException => e
    raise YamlLoadException.new(
      reason: e.reason, source: e.source, original_error: e.original_error,
      message: "Cached #include list is corrupted or unreadable ⏩️ #{e.message}"
    )
  end

  # Note: It's possible empty YAML content returns nil so ensure empty list
  return Includes.from_hashes( loaded || [] )
end

#setupObject



29
30
31
32
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 29

def setup()
  # Aliases
  @line_marker_includes_extractor = @preprocessinator_line_marker_includes_extractor
end

#write_includes_list(filepath, list) ⇒ Object

Write to disk a yaml representation of a list of includes



219
220
221
# File 'lib/ceedling/preprocess/preprocessinator_includes_handler.rb', line 219

def write_includes_list(filepath, list)
  @yaml_wrapper.dump(filepath, Includes.to_hashes(list))
end