Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/ceedling/file_path_collection_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ def revise_filelist(list, revisions)
paths = (plus - minus).to_a
paths.map! {|path| shortest_path_from_working(path) }

return FileList.new( paths )
result = FileList.new( paths )
result.resolve() # Force expansion to prevent race conditions in threaded context
return result
end

def shortest_path_from_working(path)
Expand Down
128 changes: 71 additions & 57 deletions lib/ceedling/preprocessinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,64 +26,78 @@ def setup
# Aliases
@includes_handler = @preprocessinator_includes_handler
@file_handler = @preprocessinator_file_handler

# Thread-safe per-file locking for YAML cache operations
# Key: includes_list_filepath (String), Value: Mutex
@file_locks = {}
@file_locks_mutex = Mutex.new
end


def preprocess_includes(filepath:, test:, flags:, include_paths:, defines:, deep: false)
includes_list_filepath = @file_path_utils.form_preprocessed_includes_list_filepath( filepath, test )

includes = []
# Get or create a mutex for this specific cache file
file_lock = @file_locks_mutex.synchronize do
@file_locks[includes_list_filepath] ||= Mutex.new
end

# If existing YAML file of includes is newer than the file we're processing, skip preprocessing
if @file_wrapper.newer?( includes_list_filepath, filepath )
msg = @reportinator.generate_module_progress(
operation: "Loading #include statement listing file for",
module_name: test,
filename: File.basename(filepath)
)
@loginator.log( msg, Verbosity::NORMAL )

# Note: It's possible empty YAML content returns nil
includes = @yaml_wrapper.load( includes_list_filepath )

msg = "Loaded existing #include list from #{includes_list_filepath}:"

if includes.nil? or includes.empty?
# Ensure includes defaults to emtpy array to prevent external iteration problems
includes = []
msg += ' <empty>'
else
includes.each { |include| msg += "\n - #{include}" }
end
includes = []

@loginator.log( msg, Verbosity::DEBUG )
@loginator.log( '', Verbosity::DEBUG )

# Full preprocessing-based #include extraction with saving to YAML file
else
includes = @includes_handler.extract_includes(
filepath: filepath,
test: test,
flags: flags,
include_paths: include_paths,
defines: defines,
deep: deep
)

msg = "Extracted #include list from #{filepath}:"

if includes.nil? or includes.empty?
# Ensure includes defaults to emtpy array to prevent external iteration problems
includes = []
msg += ' <empty>'
# Wrap the entire check-read-or-extract-write operation in a mutex
# This prevents race conditions when multiple threads process the same file
file_lock.synchronize do
# If existing YAML file of includes is newer than the file we're processing, skip preprocessing
if @file_wrapper.newer?( includes_list_filepath, filepath )
msg = @reportinator.generate_module_progress(
operation: "Loading #include statement listing file for",
module_name: test,
filename: File.basename(filepath)
)
@loginator.log( msg, Verbosity::NORMAL )

# Note: It's possible empty YAML content returns nil
includes = @yaml_wrapper.load( includes_list_filepath )

msg = "Loaded existing #include list from #{includes_list_filepath}:"

if includes.nil? or includes.empty?
# Ensure includes defaults to emtpy array to prevent external iteration problems
includes = []
msg += ' <empty>'
else
includes.each { |include| msg += "\n - #{include}" }
end

@loginator.log( msg, Verbosity::DEBUG )
@loginator.log( '', Verbosity::DEBUG )

# Full preprocessing-based #include extraction with saving to YAML file
else
includes.each { |include| msg += "\n - #{include}" }
includes = @includes_handler.extract_includes(
filepath: filepath,
test: test,
flags: flags,
include_paths: include_paths,
defines: defines,
deep: deep
)

msg = "Extracted #include list from #{filepath}:"

if includes.nil? or includes.empty?
# Ensure includes defaults to emtpy array to prevent external iteration problems
includes = []
msg += ' <empty>'
else
includes.each { |include| msg += "\n - #{include}" }
end

@loginator.log( msg, Verbosity::DEBUG )
@loginator.log( '', Verbosity::DEBUG )

@includes_handler.write_includes_list( includes_list_filepath, includes )
end

@loginator.log( msg, Verbosity::DEBUG )
@loginator.log( '', Verbosity::DEBUG )

@includes_handler.write_includes_list( includes_list_filepath, includes )
end

return includes
Expand All @@ -102,7 +116,7 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de
test: test,
flags: flags,
include_paths: include_paths,
defines: defines
defines: defines
}

# Trigger pre_mock_preprocessing plugin hook
Expand All @@ -114,10 +128,10 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de
flags: flags,
include_paths: include_paths,
defines: defines,
deep: preprocess_deep
deep: preprocess_deep
}

# Extract includes & log progress and details
# Extract includes & log progress and details
includes = preprocess_file_common( **arg_hash )

arg_hash = {
Expand All @@ -140,7 +154,7 @@ def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, de
preprocessed_filepath: preprocessed_filepath,
contents: contents,
extras: extras,
includes: includes
includes: includes
}

# Create a reconstituted header file from preprocessing expansion and preserving any extras
Expand All @@ -165,7 +179,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
test: test,
flags: flags,
include_paths: include_paths,
defines: defines
defines: defines
}

# Trigger pre_test_preprocess plugin hook
Expand All @@ -177,7 +191,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
flags: flags,
include_paths: include_paths,
defines: defines,
deep: preprocess_deep
deep: preprocess_deep
}

# Extract includes & log progress and info
Expand All @@ -188,7 +202,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
test: test,
flags: flags,
include_paths: include_paths,
defines: defines
defines: defines
}

# `contents` & `extras` are arrays of text strings to be assembled in generating a new test file.
Expand All @@ -200,7 +214,7 @@ def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:)
preprocessed_filepath: preprocessed_filepath,
contents: contents,
extras: extras,
includes: includes
includes: includes
}

# Create a reconstituted test file from preprocessing expansion and preserving any extras
Expand Down Expand Up @@ -231,7 +245,7 @@ def preprocess_file_common(filepath:, test:, flags:, include_paths:, defines:, d
flags: flags,
include_paths: include_paths,
defines: defines,
deep: deep)
deep: deep)

return includes
end
Expand Down
Loading