190 lines
7.7 KiB
Ruby
190 lines
7.7 KiB
Ruby
|
|
|
|
class PreprocessinatorIncludesHandler
|
|
|
|
constructor :configurator, :tool_executor, :task_invoker, :file_path_utils, :yaml_wrapper, :file_wrapper, :file_finder
|
|
@@makefile_cache = {}
|
|
|
|
# shallow includes: only those headers a source file explicitly includes
|
|
|
|
def invoke_shallow_includes_list(filepath)
|
|
@task_invoker.invoke_test_shallow_include_lists( [@file_path_utils.form_preprocessed_includes_list_filepath(filepath)] )
|
|
end
|
|
|
|
##
|
|
# Ask the preprocessor for a make-style dependency rule of only the headers
|
|
# the source file immediately includes.
|
|
#
|
|
# === Arguments
|
|
# +filepath+ _String_:: Path to the test file to process.
|
|
#
|
|
# === Return
|
|
# _String_:: The text of the dependency rule generated by the preprocessor.
|
|
def form_shallow_dependencies_rule(filepath)
|
|
if @@makefile_cache.has_key?(filepath)
|
|
return @@makefile_cache[filepath]
|
|
end
|
|
# change filename (prefix of '_') to prevent preprocessor from finding
|
|
# include files in temp directory containing file it's scanning
|
|
temp_filepath = @file_path_utils.form_temp_path(filepath, '_')
|
|
|
|
# read the file and replace all include statements with a decorated version
|
|
# (decorating the names creates file names that don't exist, thus preventing
|
|
# the preprocessor from snaking out and discovering the entire include path
|
|
# that winds through the code). The decorated filenames indicate files that
|
|
# are included directly by the test file.
|
|
contents = @file_wrapper.read(filepath)
|
|
|
|
if !contents.valid_encoding?
|
|
contents = contents.encode("UTF-16be", :invalid=>:replace, :replace=>"?").encode('UTF-8')
|
|
end
|
|
|
|
contents.gsub!( /^\s*#include\s+[\"<]\s*(\S+)\s*[\">]/, "#include \"\\1\"\n#include \"@@@@\\1\"" )
|
|
contents.gsub!( /^\s*TEST_FILE\(\s*\"\s*(\S+)\s*\"\s*\)/, "#include \"\\1\"\n#include \"@@@@\\1\"")
|
|
@file_wrapper.write( temp_filepath, contents )
|
|
|
|
# extract the make-style dependency rule telling the preprocessor to
|
|
# ignore the fact that it can't find the included files
|
|
command = @tool_executor.build_command_line(@configurator.tools_test_includes_preprocessor, [], temp_filepath)
|
|
shell_result = @tool_executor.exec(command[:line], command[:options])
|
|
|
|
@@makefile_cache[filepath] = shell_result[:output]
|
|
return shell_result[:output]
|
|
end
|
|
|
|
##
|
|
# Extract the headers that are directly included by a source file using the
|
|
# provided, annotated Make dependency rule.
|
|
#
|
|
# === Arguments
|
|
# +filepath+ _String_:: C source or header file to extract includes for.
|
|
#
|
|
# === Return
|
|
# _Array_ of _String_:: Array of the direct dependencies for the source file.
|
|
def extract_includes(filepath)
|
|
to_process = [filepath]
|
|
ignore_list = []
|
|
list = []
|
|
all_mocks = []
|
|
|
|
include_paths = @configurator.project_config_hash[:collection_paths_include]
|
|
include_paths = [] if include_paths.nil?
|
|
include_paths.map! {|path| File.expand_path(path)}
|
|
|
|
while to_process.length > 0
|
|
target = to_process.shift()
|
|
ignore_list << target
|
|
new_deps, new_to_process, all_mocks = extract_includes_helper(target, include_paths, ignore_list, all_mocks)
|
|
list += new_deps
|
|
to_process += new_to_process
|
|
if !@configurator.project_config_hash[:project_auto_link_deep_dependencies]
|
|
break
|
|
else
|
|
list = list.uniq()
|
|
to_process = to_process.uniq()
|
|
end
|
|
end
|
|
|
|
return list
|
|
end
|
|
|
|
def extract_includes_helper(filepath, include_paths, ignore_list, mocks)
|
|
# Extract the dependencies from the make rule
|
|
make_rule = self.form_shallow_dependencies_rule(filepath)
|
|
target_file = make_rule.split[0].gsub(':', '').gsub('\\','/')
|
|
base = File.basename(target_file, File.extname(target_file))
|
|
make_rule_dependencies = make_rule.gsub(/.*\b#{Regexp.escape(base)}\S*/, '').gsub(/\\$/, '')
|
|
|
|
# Extract the headers dependencies from the make rule
|
|
hdr_ext = @configurator.extension_header
|
|
headers_dependencies = make_rule_dependencies.split.find_all {|path| path.end_with?(hdr_ext) }.uniq
|
|
headers_dependencies.map! {|hdr| hdr.gsub('\\','/') }
|
|
full_path_headers_dependencies = extract_full_path_dependencies(headers_dependencies)
|
|
|
|
# Extract the sources dependencies from the make rule
|
|
src_ext = @configurator.extension_source
|
|
sources_dependencies = make_rule_dependencies.split.find_all {|path| path.end_with?(src_ext) }.uniq
|
|
sources_dependencies.map! {|src| src.gsub('\\','/') }
|
|
full_path_sources_dependencies = extract_full_path_dependencies(sources_dependencies)
|
|
|
|
list = full_path_headers_dependencies + full_path_sources_dependencies
|
|
|
|
mock_prefix = @configurator.project_config_hash[:cmock_mock_prefix]
|
|
# Creating list of mocks
|
|
mocks += full_path_headers_dependencies.find_all do |header|
|
|
File.basename(header) =~ /^#{mock_prefix}.*$/
|
|
end.compact
|
|
|
|
# ignore real file when both mock and real file exist
|
|
mocks.each do |mock|
|
|
list.each do |filename|
|
|
if File.basename(filename) == File.basename(mock).sub(mock_prefix, '')
|
|
ignore_list << filename
|
|
end
|
|
end
|
|
end.compact
|
|
|
|
# Filtering list of final includes to only include mocks and anything that is NOT in the ignore_list
|
|
list = list.select do |item|
|
|
mocks.include? item or !(ignore_list.any? { |ignore_item| !item.match(/^(.*\/)?#{Regexp.escape(ignore_item)}$/).nil? })
|
|
end
|
|
|
|
to_process = []
|
|
|
|
if @configurator.project_config_hash[:project_auto_link_deep_dependencies]
|
|
# Creating list of headers that should be recursively pre-processed
|
|
# Skipping mocks and vendor headers
|
|
headers_to_deep_link = full_path_headers_dependencies.select do |hdr|
|
|
!(mocks.include? hdr) and (hdr.match(/^(.*\/)(#{VENDORS_FILES.join('|')}) + #{Regexp.escape(hdr_ext)}$/).nil?)
|
|
end
|
|
headers_to_deep_link.map! {|hdr| File.expand_path(hdr) }
|
|
headers_to_deep_link.compact!
|
|
|
|
headers_to_deep_link.each do |hdr|
|
|
if (ignore_list.none? {|ignore_header| hdr.match(/^(.*\/)?#{Regexp.escape(ignore_header)}$/)} and
|
|
include_paths.none? {|include_path| hdr =~ /^#{include_path}\.*/})
|
|
if File.exist?(hdr)
|
|
to_process << hdr
|
|
src = @file_finder.find_compilation_input_file(hdr, :ignore)
|
|
to_process << src if src
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return list, to_process, mocks
|
|
|
|
end
|
|
|
|
def write_shallow_includes_list(filepath, list)
|
|
@yaml_wrapper.dump(filepath, list)
|
|
end
|
|
|
|
private
|
|
|
|
def extract_full_path_dependencies(dependencies)
|
|
# Separate the real files form the annotated ones and remove the '@@@@'
|
|
annotated_files, real_files = dependencies.partition {|file| file =~ /^@@@@/}
|
|
annotated_files.map! {|file| file.gsub('@@@@','') }
|
|
# Matching annotated_files values against real_files to ensure that
|
|
# annotated_files contain full path entries (as returned by make rule)
|
|
annotated_files.map! {|file| real_files.find {|real| !real.match(/^(.*\/)?#{Regexp.escape(file)}$/).nil?}}
|
|
annotated_files = annotated_files.compact
|
|
|
|
# Find which of our annotated files are "real" dependencies. This is
|
|
# intended to weed out dependencies that have been removed due to build
|
|
# options defined in the project yaml and/or in the files themselves.
|
|
return annotated_files.find_all do |annotated_file|
|
|
# find the index of the "real" file that matches the annotated one.
|
|
idx = real_files.find_index do |real_file|
|
|
real_file =~ /^(.*\/)?#{Regexp.escape(annotated_file)}$/
|
|
end
|
|
# If we found a real file, delete it from the array and return it,
|
|
# otherwise return nil. Since nil is falsy this has the effect of making
|
|
# find_all return only the annotated filess for which a real file was
|
|
# found/deleted
|
|
idx ? real_files.delete_at(idx) : nil
|
|
end.compact
|
|
end
|
|
end
|