From 0fa891c3ae72e2fb94d596ffd6afdc3defe83631 Mon Sep 17 00:00:00 2001 From: "Houlton Zachary A (zah574)" Date: Fri, 13 Jun 2025 16:38:57 -0500 Subject: [PATCH 01/10] Add CI with build and format checks --- .gitignore | 2 ++ ci/check_build.rb | 53 ++++++++++++++++++++++++++++++++++++++++++++++ ci/check_format.rb | 33 +++++++++++++++++++++++++++++ ci/run.rb | 20 +++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100755 ci/check_build.rb create mode 100755 ci/check_format.rb create mode 100755 ci/run.rb diff --git a/.gitignore b/.gitignore index b06888a..8fa1c4c 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ build/ CMakeFiles/ Makefile compile_commands.json +logs/ +Miniforge3-*.sh diff --git a/ci/check_build.rb b/ci/check_build.rb new file mode 100755 index 0000000..55c0fb9 --- /dev/null +++ b/ci/check_build.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +#TODO: Add support for MSVC builds + +require 'fileutils' + +commit_hash = `git rev-parse --short HEAD`.strip +log_dir = "logs/commit-#{commit_hash}" +FileUtils.mkdir_p(log_dir) + +#gcc build +gcc_log = "#{log_dir}/build_gcc.log" +gcc_dir = "build" + +puts "[GCC] Running CMake..." +gcc_cmake = system("cmake -S . -B #{gcc_dir} > #{gcc_log} 2>&1") + +if gcc_cmake + puts "[GCC] Running Make..." + gcc_make = system("cmake --build #{gcc_dir} >> #{gcc_log} 2>&1") +end + +if !gcc_cmake || !gcc_make + puts "[GCC] Build FAILED. See #{gcc_log}" + exit 1 +else + puts "[GCC] Build succeeded." +end + +#clang 20 build +clang_bin = "/home/jab23579/useful/clang-20/bin/clang++" +clang_dir = "build_clang" +clang_log = "#{log_dir}/build_clang.log" +FileUtils.mkdir_p(clang_dir) + +puts "[Clang] Running CMake with Clang 20..." +clang_cmake = system("cmake -S . -B #{clang_dir} -DCMAKE_CXX_COMPILER=#{clang_bin} > #{clang_log} 2>&1") + +if clang_cmake + puts "[Clang] Running Make with Clang 20..." + clang_make = system("cmake --build #{clang_dir} >> #{clang_log} 2>&1") +end + +if !clang_cmake || !clang_make + puts "[Clang] Build FAILED. See #{clang_log}" + exit 1 +else + puts "[Clang] Build succeeded." +end + +exit 0 + + diff --git a/ci/check_format.rb b/ci/check_format.rb new file mode 100755 index 0000000..58912f6 --- /dev/null +++ b/ci/check_format.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +#paths +commit_hash = `git rev-parse --short HEAD`.strip +log_file = "logs/commit-#{commit_hash}/format.log" + +#find cpp files +cpp_files = Dir.glob("**/*.cpp") + +failed_files = [] + +File.open(log_file, "w") do |log| + cpp_files.each do |file| + result = `clang-format --dry-run --Werror #{file} 2>&1` + if $?.exitstatus != 0 + failed_files << file + log.puts "[FAIL] #{file}" + log.puts result + else + log.puts "[PASS] #{file}" + end + end + + if failed_files.empty? + log.puts "\nAll files passed clang-format check." + else + log.puts "\n#{failed_files.size} file(s) failed formatting." + end +end + +exit(failed_files.empty? ? 0 : 1) diff --git a/ci/run.rb b/ci/run.rb new file mode 100755 index 0000000..492ca1b --- /dev/null +++ b/ci/run.rb @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +#paths +commit_hash = `git rev-parse --short HEAD`.strip +log_dir = "logs/commit-#{commit_hash}" +FileUtils.mkdir_p(log_dir) + +puts "Running SANA-FE CI for commit #{commit_hash}" +puts "Logs will be saved to #{log_dir}/" +puts "-------------------------------" + +#run build checker +build_status = system("ruby ci/check_build.rb") +puts "Build Check: #{build_status ? 'PASS' : 'FAIL'}" + +#run format checker +format_status = system("ruby ci/check_format.rb") +puts "Format Check: #{format_status ? 'PASS' : 'FAIL'}" \ No newline at end of file From 43154d04e8a1c62a68602ca096b72896713fcd45 Mon Sep 17 00:00:00 2001 From: Zachary Houlton Date: Mon, 16 Jun 2025 02:20:18 -0500 Subject: [PATCH 02/10] Updated build checker --- ci/check_build.rb | 97 +++++++++++++++++++++++++------------- ci/check_format.rb | 8 +++- ci/check_tidy.rb | 24 ++++++++++ ci/run.rb | 8 ++-- plugins/hodgkin_huxley.cpp | 3 +- src/chip.cpp | 6 +-- src/core.cpp | 4 +- src/main.cpp | 3 +- src/models.cpp | 4 +- src/pipeline.cpp | 22 ++++----- src/schedule.cpp | 12 ++--- src/yaml_arch.cpp | 4 +- src/yaml_common.cpp | 12 ++--- src/yaml_snn.cpp | 27 +++++------ 14 files changed, 139 insertions(+), 95 deletions(-) create mode 100644 ci/check_tidy.rb diff --git a/ci/check_build.rb b/ci/check_build.rb index 55c0fb9..092b594 100755 --- a/ci/check_build.rb +++ b/ci/check_build.rb @@ -1,53 +1,84 @@ #!/usr/bin/env ruby -#TODO: Add support for MSVC builds - require 'fileutils' commit_hash = `git rev-parse --short HEAD`.strip log_dir = "logs/commit-#{commit_hash}" FileUtils.mkdir_p(log_dir) -#gcc build -gcc_log = "#{log_dir}/build_gcc.log" -gcc_dir = "build" +#build standalone simulator +def build_cpp(label:, build_dir:, compiler: nil, log_file:) + puts "[#{label}] Building standalone simulator..." -puts "[GCC] Running CMake..." -gcc_cmake = system("cmake -S . -B #{gcc_dir} > #{gcc_log} 2>&1") + cmake_cmd = "cmake -S . -B #{build_dir}" + cmake_cmd += " -DCMAKE_CXX_COMPILER=#{compiler}" if compiler + cmake_ok = system("#{cmake_cmd} > #{log_file} 2>&1") -if gcc_cmake - puts "[GCC] Running Make..." - gcc_make = system("cmake --build #{gcc_dir} >> #{gcc_log} 2>&1") -end + if cmake_ok + make_ok = system("cmake --build #{build_dir} >> #{log_file} 2>&1") + end -if !gcc_cmake || !gcc_make - puts "[GCC] Build FAILED. See #{gcc_log}" - exit 1 -else - puts "[GCC] Build succeeded." + if cmake_ok && make_ok + puts "[#{label}] Simulator build: PASS" + true + else + puts "[#{label}] Simulator build: FAIL (see #{log_file})" + false + end end -#clang 20 build -clang_bin = "/home/jab23579/useful/clang-20/bin/clang++" -clang_dir = "build_clang" -clang_log = "#{log_dir}/build_clang.log" -FileUtils.mkdir_p(clang_dir) +#build python shared lib +def build_python(label:, build_dir:, compiler: nil, log_file:) + puts "[#{label}] Building Python shared library..." -puts "[Clang] Running CMake with Clang 20..." -clang_cmake = system("cmake -S . -B #{clang_dir} -DCMAKE_CXX_COMPILER=#{clang_bin} > #{clang_log} 2>&1") + cmake_cmd = "cmake -S . -B #{build_dir} -DENABLE_PYTHON=ON" + cmake_cmd += " -DCMAKE_CXX_COMPILER=#{compiler}" if compiler + cmake_ok = system("#{cmake_cmd} > #{log_file} 2>&1") -if clang_cmake - puts "[Clang] Running Make with Clang 20..." - clang_make = system("cmake --build #{clang_dir} >> #{clang_log} 2>&1") -end + if cmake_ok + make_ok = system("cmake --build #{build_dir} >> #{log_file} 2>&1") + end -if !clang_cmake || !clang_make - puts "[Clang] Build FAILED. See #{clang_log}" - exit 1 -else - puts "[Clang] Build succeeded." + if cmake_ok && make_ok + puts "[#{label}] Python build: PASS" + true + else + puts "[#{label}] Python build: FAIL (see #{log_file})" + false + end end -exit 0 +#clang path +clang_path = "/home/jab23579/useful/clang-20/bin/clang++" + +#gcc builds +gcc_sim_ok = build_cpp( + label: "GCC", + build_dir: "build_gcc", + log_file: "#{log_dir}/build_gcc_sim.log" +) +gcc_py_ok = build_python( + label: "GCC", + build_dir: "build_gcc_py", + log_file: "#{log_dir}/build_gcc_py.log" +) + +#clang builds +clang_sim_ok = build_cpp( + label: "Clang", + build_dir: "build_clang", + compiler: clang_path, + log_file: "#{log_dir}/build_clang_sim.log" +) +clang_py_ok = build_python( + label: "Clang", + build_dir: "build_clang_py", + compiler: clang_path, + log_file: "#{log_dir}/build_clang_py.log" +) + +all_ok = gcc_sim_ok && gcc_py_ok && clang_sim_ok && clang_py_ok +puts all_ok ? "All builds succeeded." : "One or more builds failed." +exit(all_ok ? 0 : 1) diff --git a/ci/check_format.rb b/ci/check_format.rb index 58912f6..6be3c11 100755 --- a/ci/check_format.rb +++ b/ci/check_format.rb @@ -7,7 +7,7 @@ log_file = "logs/commit-#{commit_hash}/format.log" #find cpp files -cpp_files = Dir.glob("**/*.cpp") +cpp_files = Dir.glob("src/**/*.cpp") + Dir.glob("plugins/**/*.cpp") failed_files = [] @@ -30,4 +30,10 @@ end end +if failed_files.empty? + puts "Format Check: PASS" +else + puts "Format Check: FAIL (#{failed_files.size} file(s) failed, see #{log_file})" +end + exit(failed_files.empty? ? 0 : 1) diff --git a/ci/check_tidy.rb b/ci/check_tidy.rb new file mode 100644 index 0000000..2f704ef --- /dev/null +++ b/ci/check_tidy.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +commit_hash = `git rev-parse --short HEAD`.strip +log_file = "logs/commit-#{commit_hash}/tidy.log" +FileUtils.mkdir_p("logs/commit-#{commit_hash}") + +#all .cpp files in src +cpp_files = Dir.glob("src/**/*.cpp") + +failed = false + +File.open(log_file, "w") do |log| + cpp_files.each do |file| + log.puts "----- #{file} -----" + result = `clang-tidy #{file} -- -I./src 2>&1` + log.puts result + + failed ||= result.include?("warning:") || result.include?("error:") + end +end + +exit failed ? 1 : 0 diff --git a/ci/run.rb b/ci/run.rb index 492ca1b..adec1f6 100755 --- a/ci/run.rb +++ b/ci/run.rb @@ -13,8 +13,10 @@ #run build checker build_status = system("ruby ci/check_build.rb") -puts "Build Check: #{build_status ? 'PASS' : 'FAIL'}" -#run format checker +#run clang-format format_status = system("ruby ci/check_format.rb") -puts "Format Check: #{format_status ? 'PASS' : 'FAIL'}" \ No newline at end of file + +#run clang-tidy +tidy_status = system("ruby ci/check_tidy.rb") +puts "Tidy Check: #{tidy_status ? 'PASS' : 'FAIL'}" diff --git a/plugins/hodgkin_huxley.cpp b/plugins/hodgkin_huxley.cpp index 588a62a..dd83ca0 100644 --- a/plugins/hodgkin_huxley.cpp +++ b/plugins/hodgkin_huxley.cpp @@ -24,7 +24,8 @@ class HodgkinHuxley : public sanafe::SomaUnit // HodgkinHuxley specific public: // system variables - double C_m{10.0}; // Effective capacitance per area of membrane; default is 1 + double C_m{ + 10.0}; // Effective capacitance per area of membrane; default is 1 double g_Na{1200.0}; // Conductance of sodium double g_K{360.0}; // Conductance of potassium double g_L{3.0}; // Conductance of leak channel diff --git a/src/chip.cpp b/src/chip.cpp index 44c902b..d375eef 100644 --- a/src/chip.cpp +++ b/src/chip.cpp @@ -437,10 +437,8 @@ void sanafe::SpikingChip::update_run_data( sanafe::RunData sanafe::SpikingChip::sim(const long int timesteps, const TimingModel timing_model, const int scheduler_thread_count, - const bool record_spikes, - const bool record_potentials, - const bool record_perf, - const bool record_messages, + const bool record_spikes, const bool record_potentials, + const bool record_perf, const bool record_messages, std::string output_dir) { RunData rd(total_timesteps + 1); diff --git a/src/core.cpp b/src/core.cpp index 6b5b7ad..b80e64c 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -136,8 +136,8 @@ void sanafe::Core::map_neuron( // Map the neuron to the core and its hardware units const size_t address = neurons.size(); - neurons.emplace_back(neuron_to_map, neuron_id, this, - mapped_soma, address, mapped_axon_out, mapped_dendrite); + neurons.emplace_back(neuron_to_map, neuron_id, this, mapped_soma, address, + mapped_axon_out, mapped_dendrite); } sanafe::AxonInUnit &sanafe::Core::create_axon_in( diff --git a/src/main.cpp b/src/main.cpp index 3fe1a4a..d4dab99 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -253,8 +253,7 @@ std::vector program_args_to_vector( // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,readability-function-size) int main(int argc, const char *argv[]) { - const std::vector arg_vec = - program_args_to_vector(argc, argv); + const std::vector arg_vec = program_args_to_vector(argc, argv); const OptionalProgramFlags optional_flags = parse_command_line_flags(arg_vec); argc -= optional_flags.total_args_parsed; diff --git a/src/models.cpp b/src/models.cpp index df88618..9d3407d 100644 --- a/src/models.cpp +++ b/src/models.cpp @@ -275,8 +275,8 @@ void sanafe::MultiTapModel1D::print_taps() } } -void sanafe::MultiTapModel1D::input_current(const double current, - const std::optional synapse_address) +void sanafe::MultiTapModel1D::input_current( + const double current, const std::optional synapse_address) { constexpr int default_tap = 0; diff --git a/src/pipeline.cpp b/src/pipeline.cpp index a2cab29..6055cce 100644 --- a/src/pipeline.cpp +++ b/src/pipeline.cpp @@ -145,7 +145,6 @@ void sanafe::PipelineUnit::check_implemented( INFO("Error: %s\n", error.c_str()); throw std::runtime_error(error); } - } void sanafe::PipelineUnit::check_outputs( @@ -267,9 +266,8 @@ void sanafe::PipelineUnit::calculate_synapse_default_energy_latency( con.synapse_hw->default_energy_process_spike.has_value(); if (energy_simulated && default_synapse_energy_metrics_set) { - const std::string error( - "Synapse unit simulates energy and also has " - "default energy metrics set."); + const std::string error("Synapse unit simulates energy and also has " + "default energy metrics set."); throw std::runtime_error(error); } if (default_synapse_energy_metrics_set) @@ -330,9 +328,8 @@ void sanafe::PipelineUnit::calculate_dendrite_default_energy_latency( n.dendrite_hw->default_energy_update.has_value(); if (energy_simulated && default_dendrite_energy_metrics_set) { - const std::string error( - "Dendrite unit simulates energy and also has " - "default energy metrics set."); + const std::string error("Dendrite unit simulates energy and also has " + "default energy metrics set."); throw std::runtime_error(error); } if (default_dendrite_energy_metrics_set) @@ -344,9 +341,8 @@ void sanafe::PipelineUnit::calculate_dendrite_default_energy_latency( n.dendrite_hw->default_latency_update.has_value(); if (latency_simulated && default_dendrite_latency_metrics_set) { - const std::string error( - "Dendrite unit simulates latency and also has " - "default latency metrics set."); + const std::string error("Dendrite unit simulates latency and also has " + "default latency metrics set."); throw std::runtime_error(error); } @@ -552,8 +548,7 @@ void sanafe::PipelineUnit::soma_set_default_attributes() { if (!key_exists(metric)) { - const std::string error = - "Metric not defined: " + metric; + const std::string error = "Metric not defined: " + metric; INFO("Error: %s\n", error.c_str()); throw std::invalid_argument(error); } @@ -579,8 +574,7 @@ void sanafe::PipelineUnit::soma_set_default_attributes() { if (!key_exists(metric)) { - const std::string error = - "Missing metric: " + metric; + const std::string error = "Missing metric: " + metric; INFO("Error: %s\n", error.c_str()); throw std::invalid_argument(error); } diff --git a/src/schedule.cpp b/src/schedule.cpp index 56aa0b2..b2b744f 100644 --- a/src/schedule.cpp +++ b/src/schedule.cpp @@ -62,8 +62,7 @@ void sanafe::schedule_messages(TimestepHandle &ts, Scheduler &scheduler, } } -void sanafe::schedule_messages_simple( - TimestepHandle &ts, Scheduler &scheduler) +void sanafe::schedule_messages_simple(TimestepHandle &ts, Scheduler &scheduler) { // Simple analytical model, that takes the maximum of either neuron or // message processing for each core, and takes the maximum latency of @@ -181,7 +180,8 @@ void sanafe::schedule_create_threads( // **** Detailed scheduler implementation **** -void sanafe::schedule_messages_detailed(TimestepHandle &ts, Scheduler &scheduler) +void sanafe::schedule_messages_detailed( + TimestepHandle &ts, Scheduler &scheduler) { if (scheduler.scheduler_threads.empty()) { @@ -284,8 +284,8 @@ double sanafe::schedule_messages_timestep( return ts_data.sim_time; } -std::vector -sanafe::schedule_init_message_queues(const Timestep &ts, NocInfo &noc) +std::vector sanafe::schedule_init_message_queues( + const Timestep &ts, NocInfo &noc) { const size_t total_links = noc.noc_height_in_tiles * noc.noc_width_in_tiles * @@ -588,7 +588,6 @@ double sanafe::NocInfo::calculate_route_congestion(const Message &m) const return flow_density; } - std::pair sanafe::NocInfo::get_route_xy_increments( const Message &m) noexcept { @@ -598,7 +597,6 @@ std::pair sanafe::NocInfo::get_route_xy_increments( return std::make_pair(x_increment, y_increment); } - // **** Thread management **** // TODO: make this agnostic to scheduling algorithm, so it can be applied to // the simple and cycle accurate models in future diff --git a/src/yaml_arch.cpp b/src/yaml_arch.cpp index 9b595a1..f2dfcf6 100644 --- a/src/yaml_arch.cpp +++ b/src/yaml_arch.cpp @@ -395,8 +395,8 @@ void sanafe::description_parse_tile_section_yaml(const ryml::Parser &parser, for (int t = range.first; t <= range.second; t++) { - std::string name = tile_name.substr(0, tile_name.find('[')) + - "[" + std::to_string(t) + "]"; + std::string name = tile_name.substr(0, tile_name.find('[')) + "[" + + std::to_string(t) + "]"; const TilePowerMetrics power_metrics = description_parse_tile_metrics_yaml( parser, tile_node["attributes"]); diff --git a/src/yaml_common.cpp b/src/yaml_common.cpp index f8ae7e6..af8b29c 100644 --- a/src/yaml_common.cpp +++ b/src/yaml_common.cpp @@ -118,8 +118,7 @@ sanafe::description_parse_model_attributes_yaml( // NOLINT(misc-no-recursion) if (unit_specific_keys.find(key_str) == unit_specific_keys.end()) { //INFO("Parsing attribute: %s\n", key.c_str()); - model_attributes[key_str] = - yaml_parse_attribute(parser, node); + model_attributes[key_str] = yaml_parse_attribute(parser, node); } } } @@ -140,13 +139,11 @@ sanafe::ModelAttribute sanafe::yaml_parse_attribute( if (attribute_node.is_seq()) { - attribute.value = - yaml_parse_attribute_list(parser, attribute_node); + attribute.value = yaml_parse_attribute_list(parser, attribute_node); } else if (attribute_node.is_map()) { - attribute.value = - yaml_parse_attribute_map(parser, attribute_node); + attribute.value = yaml_parse_attribute_map(parser, attribute_node); } else { @@ -260,8 +257,7 @@ sanafe::AttributeVariant sanafe::yaml_parse_attribute_scalar( } // NOLINTEND(readability-function-cognitive-complexity) -std::pair sanafe::yaml_parse_range( - const std::string &range_str) +std::pair sanafe::yaml_parse_range(const std::string &range_str) { constexpr std::string_view range_delimiter = ".."; const size_t delimiter_pos = range_str.find(range_delimiter); diff --git a/src/yaml_snn.cpp b/src/yaml_snn.cpp index 2359996..5ebdf11 100644 --- a/src/yaml_snn.cpp +++ b/src/yaml_snn.cpp @@ -663,8 +663,7 @@ void sanafe::yaml_parse_conv2d(NeuronGroup &source_group, std::vector attribute_list; for (const auto &el : attribute) { - ModelAttribute value = - yaml_parse_attribute(parser, el); + ModelAttribute value = yaml_parse_attribute(parser, el); attribute_list.push_back(std::move(value)); } std::string attribute_name; @@ -707,8 +706,7 @@ void sanafe::yaml_parse_dense(NeuronGroup &source_group, attribute >> ryml::key(attribute_name); for (const auto &el : attribute) { - ModelAttribute value = - yaml_parse_attribute(parser, el); + ModelAttribute value = yaml_parse_attribute(parser, el); attribute_list.push_back(std::move(value)); } attribute_lists[attribute_name] = std::move(attribute_list); @@ -1052,8 +1050,7 @@ ryml::NodeRef sanafe::yaml_serialize_network(ryml::NodeRef root, edge_node |= ryml::FLOW_SL; // NOLINT(misc-include-cleaner) // For now assume there are no default connection attributes const std::map default_attributes{}; - yaml_serialize_model_attributes( - default_attributes, edge_node, + yaml_serialize_model_attributes(default_attributes, edge_node, connection.synapse_attributes); // TODO: support synapse-specific attributes @@ -1079,9 +1076,8 @@ ryml::NodeRef sanafe::yaml_serialize_network(ryml::NodeRef root, return network_node; } -ryml::NodeRef sanafe::yaml_serialize_neuron_group( - ryml::NodeRef parent, const sanafe::NeuronGroup &group, - std::list &strings) +ryml::NodeRef sanafe::yaml_serialize_neuron_group(ryml::NodeRef parent, + const sanafe::NeuronGroup &group, std::list &strings) { auto group_node = parent.append_child(); group_node |= ryml::MAP; @@ -1095,8 +1091,8 @@ ryml::NodeRef sanafe::yaml_serialize_neuron_group( const std::map no_default_attributes{}; if (!group.default_neuron_config.model_attributes.empty()) { - yaml_serialize_model_attributes(no_default_attributes, - attr_node, group.default_neuron_config.model_attributes); + yaml_serialize_model_attributes(no_default_attributes, attr_node, + group.default_neuron_config.model_attributes); } // Add neurons @@ -1124,16 +1120,15 @@ ryml::NodeRef sanafe::yaml_serialize_neuron_group( for (const auto &neuron_run : neuron_runs) { - yaml_serialize_neuron_run( - neurons_node, neuron_run, group, strings); + yaml_serialize_neuron_run(neurons_node, neuron_run, group, strings); } return group_node; } -ryml::NodeRef sanafe::yaml_serialize_neuron_run( - ryml::NodeRef neurons_node, const std::tuple &neuron_run, - const NeuronGroup &group, std::list &strings) +ryml::NodeRef sanafe::yaml_serialize_neuron_run(ryml::NodeRef neurons_node, + const std::tuple &neuron_run, const NeuronGroup &group, + std::list &strings) { auto [start_offset, end_offset] = neuron_run; From 21b33fbe4fc198d6e798ccf182a6f6d7f7671673 Mon Sep 17 00:00:00 2001 From: Zachary Houlton Date: Tue, 17 Jun 2025 20:18:08 -0500 Subject: [PATCH 03/10] Support pybind11 shared lib build --- CMakeLists.txt | 8 ++++++++ ci/check_build.rb | 49 ++++++++++++++++++++++++++++++++++------------- ci/check_tidy.rb | 22 ++++++++++++++++----- ci/run.rb | 1 - setup.py | 4 ++++ 5 files changed, 65 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 764afe2..0d82724 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,9 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++") endif() @@ -230,6 +233,9 @@ if(PYTHON_BUILD_ENABLED) ${SOURCE_FILES} ) + set_target_properties(sanafecpp PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/sanafe + ) target_link_libraries(sanafecpp PRIVATE ${PYTHON_LIBRARIES}) target_link_libraries(sanafecpp PRIVATE pybind11::pybind11) target_link_libraries(sanafecpp PUBLIC ryml::ryml) @@ -247,6 +253,8 @@ if(PYTHON_BUILD_ENABLED) target_compile_definitions(sanafecpp PRIVATE ENABLE_SOURCE_INFO) endif() endif() + + install(TARGETS sanafecpp DESTINATION sanafe) endif() if(STANDALONE_BUILD_ENABLED) diff --git a/ci/check_build.rb b/ci/check_build.rb index 092b594..9e033b8 100755 --- a/ci/check_build.rb +++ b/ci/check_build.rb @@ -2,11 +2,12 @@ require 'fileutils' +#setup log directory commit_hash = `git rev-parse --short HEAD`.strip log_dir = "logs/commit-#{commit_hash}" FileUtils.mkdir_p(log_dir) -#build standalone simulator +#build standalone sim def build_cpp(label:, build_dir:, compiler: nil, log_file:) puts "[#{label}] Building standalone simulator..." @@ -27,19 +28,42 @@ def build_cpp(label:, build_dir:, compiler: nil, log_file:) end end -#build python shared lib -def build_python(label:, build_dir:, compiler: nil, log_file:) - puts "[#{label}] Building Python shared library..." +#build and install python .so +def build_python(label:, build_dir:, log_file:) + puts "[#{label}] Building python extension..." + + FileUtils.rm_f("CMakeCache.txt") + FileUtils.mkdir_p(build_dir) + FileUtils.mkdir_p(File.dirname(log_file)) + + full_log_path = File.expand_path(log_file) + install_ok = nil + import_ok = false + + File.open(full_log_path, "w") do |log| + Dir.chdir(build_dir) do + IO.popen("pip install .. > /dev/null 2>&1") do |io| + io.each { |line| log.puts line } + end + install_ok = $?.success? + end + end - cmake_cmd = "cmake -S . -B #{build_dir} -DENABLE_PYTHON=ON" - cmake_cmd += " -DCMAKE_CXX_COMPILER=#{compiler}" if compiler - cmake_ok = system("#{cmake_cmd} > #{log_file} 2>&1") + #find and copy .so into site packages + built_so = Dir.glob("#{build_dir}/../**/sanafecpp*.so").find { |f| File.file?(f) } + dest_dir = File.join(ENV["CONDA_PREFIX"], "lib", "python#{RUBY_VERSION[/\d+\.\d+/]}", "site-packages", "sanafe") - if cmake_ok - make_ok = system("cmake --build #{build_dir} >> #{log_file} 2>&1") + if built_so && File.exist?(built_so) + FileUtils.mkdir_p(dest_dir) + FileUtils.cp(built_so, dest_dir) end - if cmake_ok && make_ok + #check that import works + import_output = `python3 -c 'import sanafe' 2>&1` + import_ok = $?.success? + File.open(log_file, "a") { |f| f.puts "\n[Import Test]\n#{import_output}" } + + if install_ok && import_ok puts "[#{label}] Python build: PASS" true else @@ -48,7 +72,7 @@ def build_python(label:, build_dir:, compiler: nil, log_file:) end end -#clang path +#set clang path clang_path = "/home/jab23579/useful/clang-20/bin/clang++" #gcc builds @@ -73,12 +97,11 @@ def build_python(label:, build_dir:, compiler: nil, log_file:) clang_py_ok = build_python( label: "Clang", build_dir: "build_clang_py", - compiler: clang_path, log_file: "#{log_dir}/build_clang_py.log" ) +#final summary all_ok = gcc_sim_ok && gcc_py_ok && clang_sim_ok && clang_py_ok puts all_ok ? "All builds succeeded." : "One or more builds failed." exit(all_ok ? 0 : 1) - diff --git a/ci/check_tidy.rb b/ci/check_tidy.rb index 2f704ef..20bebbc 100644 --- a/ci/check_tidy.rb +++ b/ci/check_tidy.rb @@ -6,10 +6,8 @@ log_file = "logs/commit-#{commit_hash}/tidy.log" FileUtils.mkdir_p("logs/commit-#{commit_hash}") -#all .cpp files in src cpp_files = Dir.glob("src/**/*.cpp") - -failed = false +failed_files = [] File.open(log_file, "w") do |log| cpp_files.each do |file| @@ -17,8 +15,22 @@ result = `clang-tidy #{file} -- -I./src 2>&1` log.puts result - failed ||= result.include?("warning:") || result.include?("error:") + if result.include?("warning:") || result.include?("error:") + failed_files << file + end + end + + if failed_files.empty? + log.puts "\nAll files passed clang-tidy check." + else + log.puts "\n#{failed_files.size} file(s) had warnings/errors." end end -exit failed ? 1 : 0 +if failed_files.empty? + puts "Tidy Check: PASS" +else + puts "Tidy Check: FAIL (#{failed_files.size} file(s) had issues, see #{log_file})" +end + +exit(failed_files.empty? ? 0 : 1) diff --git a/ci/run.rb b/ci/run.rb index adec1f6..7548a50 100755 --- a/ci/run.rb +++ b/ci/run.rb @@ -19,4 +19,3 @@ #run clang-tidy tidy_status = system("ruby ci/check_tidy.rb") -puts "Tidy Check: #{tidy_status ? 'PASS' : 'FAIL'}" diff --git a/setup.py b/setup.py index d20e9e8..73f520b 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ def build_extension(self, ext): "-DPYTHON_EXECUTABLE=" + sys.executable, "-DPYTHON_INCLUDE_DIRS=" + sysconfig.get_path('include'), "-DSTANDALONE_BUILD_ENABLED=OFF", + "-DPYTHON_BUILD_ENABLED=ON", "-DPYTHON_FROM_SETUP=ON"] print(f"CMake Arguments: {cmake_args}") cfg = "Debug" if self.debug else "Release" @@ -66,6 +67,9 @@ def build_extension(self, ext): env["CMAKE_BUILD_PARALLEL_LEVEL"] = jobs if not os.path.exists(self.build_temp): os.makedirs(self.build_temp) + env["PYTHON_EXECUTABLE"] = sys.executable + env["PYTHON_INCLUDE_DIRS"] = sysconfig.get_path('include') + subprocess.check_call(["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) subprocess.check_call(["cmake", "--build", "."] + build_args, cwd=self.build_temp) From 59c60f2c94aefc4ff4deac0240f016dd703fa3f1 Mon Sep 17 00:00:00 2001 From: Zachary Houlton Date: Tue, 24 Jun 2025 16:03:39 -0500 Subject: [PATCH 04/10] Add cpp and performance checks --- .gitignore | 6 +++++ ci/check_build.rb | 48 +++++++++++++++++++++------------------ ci/check_coverity.rb | 0 ci/check_cppcheck.rb | 23 +++++++++++++++++++ ci/check_format.rb | 10 ++++---- ci/check_perf.rb | 54 ++++++++++++++++++++++++++++++++++++++++++++ ci/check_tidy.rb | 8 +++---- ci/perf_baseline.txt | 1 + ci/run.rb | 26 +++++++++++---------- 9 files changed, 133 insertions(+), 43 deletions(-) create mode 100644 ci/check_coverity.rb create mode 100644 ci/check_cppcheck.rb create mode 100644 ci/check_perf.rb create mode 100644 ci/perf_baseline.txt diff --git a/.gitignore b/.gitignore index 8fa1c4c..4a879ee 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,9 @@ Makefile compile_commands.json logs/ Miniforge3-*.sh +.env +coverity_tool.tgz +cov-analysis-* +venvs/ +test_venv/ +*.so diff --git a/ci/check_build.rb b/ci/check_build.rb index 9e033b8..c811016 100755 --- a/ci/check_build.rb +++ b/ci/check_build.rb @@ -2,21 +2,21 @@ require 'fileutils' -#setup log directory -commit_hash = `git rev-parse --short HEAD`.strip -log_dir = "logs/commit-#{commit_hash}" +log_dir = ENV["SANAFE_CI_LOG_DIR"] || "logs/commit-latest" +log_file = "#{log_dir}/build.log" FileUtils.mkdir_p(log_dir) + #build standalone sim def build_cpp(label:, build_dir:, compiler: nil, log_file:) puts "[#{label}] Building standalone simulator..." - cmake_cmd = "cmake -S . -B #{build_dir}" - cmake_cmd += " -DCMAKE_CXX_COMPILER=#{compiler}" if compiler - cmake_ok = system("#{cmake_cmd} > #{log_file} 2>&1") + cmake_cmd = "cmake -S . -B #{build_dir}" #construct cmake config command using source and build dirs + cmake_cmd += " -DCMAKE_CXX_COMPILER=#{compiler}" if compiler #add compiler option if provided + cmake_ok = system("#{cmake_cmd} > #{log_file} 2>&1") #run cmake, direct output to log file if cmake_ok - make_ok = system("cmake --build #{build_dir} >> #{log_file} 2>&1") + make_ok = system("cmake --build #{build_dir} --parallel 8 >> #{log_file} 2>&1") #if cmake works, build and append output to log end if cmake_ok && make_ok @@ -37,39 +37,43 @@ def build_python(label:, build_dir:, log_file:) FileUtils.mkdir_p(File.dirname(log_file)) full_log_path = File.expand_path(log_file) - install_ok = nil + + #create unique venv name using timestamp and commit hash + timestamp = Time.now.strftime('%Y%m%d-%H%M%S') + commit_hash = `git rev-parse --short HEAD`.strip + venv_path = File.expand_path("venvs/commit-#{timestamp}-#{commit_hash}") + venv_python = File.join(venv_path, "bin", "python") + + FileUtils.mkdir_p("venvs") + unless system("python3 -m venv #{venv_path}") + puts "[#{label}] Failed to create virtualenv at #{venv_path}" + return false + end + + install_ok = false import_ok = false File.open(full_log_path, "w") do |log| Dir.chdir(build_dir) do - IO.popen("pip install .. > /dev/null 2>&1") do |io| + IO.popen("#{venv_python} -m pip install .. > /dev/null 2>&1") do |io| io.each { |line| log.puts line } end install_ok = $?.success? end end - #find and copy .so into site packages + #copy into site packages built_so = Dir.glob("#{build_dir}/../**/sanafecpp*.so").find { |f| File.file?(f) } dest_dir = File.join(ENV["CONDA_PREFIX"], "lib", "python#{RUBY_VERSION[/\d+\.\d+/]}", "site-packages", "sanafe") - if built_so && File.exist?(built_so) FileUtils.mkdir_p(dest_dir) FileUtils.cp(built_so, dest_dir) end - #check that import works - import_output = `python3 -c 'import sanafe' 2>&1` - import_ok = $?.success? - File.open(log_file, "a") { |f| f.puts "\n[Import Test]\n#{import_output}" } + FileUtils.rm_rf(venv_path) - if install_ok && import_ok - puts "[#{label}] Python build: PASS" - true - else - puts "[#{label}] Python build: FAIL (see #{log_file})" - false - end +puts "[#{label}] Python build: #{install_ok ? 'PASS' : "FAIL (see #{log_file})"}" + install_ok end #set clang path diff --git a/ci/check_coverity.rb b/ci/check_coverity.rb new file mode 100644 index 0000000..e69de29 diff --git a/ci/check_cppcheck.rb b/ci/check_cppcheck.rb new file mode 100644 index 0000000..7784fd6 --- /dev/null +++ b/ci/check_cppcheck.rb @@ -0,0 +1,23 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +log_dir = ENV["SANAFE_CI_LOG_DIR"] || "logs/commit-latest" +log_file = "#{log_dir}/cppcheck.log" +FileUtils.mkdir_p(log_dir) + +cpp_files = Dir.glob("src/*.cpp") + Dir.glob("plugins/*.cpp") +failed = false + +File.open(log_file, "w") do |log| + log.puts "Running CPPCheck..." + cpp_files.each do |file| + log.puts "----- #{file} -----" + result = `cppcheck --enable=all --inconclusive --quiet --std=c++20 --language=c++ --suppress=missingIncludeSystem #{file} 2>&1` + log.puts result + failed ||= !result.strip.empty? + end +end + +puts failed ? "CPPCheck: FAIL (see #{log_file})" : "CPPCheck: PASS" +exit(failed ? 1 : 0) diff --git a/ci/check_format.rb b/ci/check_format.rb index 6be3c11..4cf5da0 100755 --- a/ci/check_format.rb +++ b/ci/check_format.rb @@ -1,13 +1,13 @@ #!/usr/bin/env ruby - +#TODO: break format require 'fileutils' -#paths -commit_hash = `git rev-parse --short HEAD`.strip -log_file = "logs/commit-#{commit_hash}/format.log" +log_dir = ENV["SANAFE_CI_LOG_DIR"] || "logs/commit-latest" +log_file = "#{log_dir}/format.log" +FileUtils.mkdir_p(log_dir) #find cpp files -cpp_files = Dir.glob("src/**/*.cpp") + Dir.glob("plugins/**/*.cpp") +cpp_files = Dir.glob("src/*.cpp") + Dir.glob("plugins/*.cpp") failed_files = [] diff --git a/ci/check_perf.rb b/ci/check_perf.rb new file mode 100644 index 0000000..401964d --- /dev/null +++ b/ci/check_perf.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +log_dir = ENV["SANAFE_CI_LOG_DIR"] || "logs/commit-latest" +log_file = "#{log_dir}/perf.log" +FileUtils.mkdir_p(log_dir) + +#test parameters +sim_path = "./sim" +arch_file = "arch/example.yaml" +snn_file = "snn/example.yaml" +steps = 100000 + +perf_output = `(/usr/bin/time -f "%e" #{sim_path} #{arch_file} #{snn_file} #{steps} > /dev/null) 2>&1` +runtime = perf_output.to_f + +baseline_file = "ci/perf_baseline.txt" +commit_perf_file = "#{log_dir}/perf_runtime.txt" + +#first time +if !File.exist?(baseline_file) + File.write(baseline_file, "#{runtime.round(3)}\n") + File.write(log_file, <<~LOG) + Baseline not found. Created a new baseline: + Baseline Runtime: #{runtime.round(3)} sec + + To reset baseline manually later: + cp #{commit_perf_file} #{baseline_file} + LOG + puts "Performance Check: Baseline Created (#{runtime.round(3)} sec)" + File.write(commit_perf_file, "#{runtime.round(3)}\n") + exit 0 +end + +#compare with baseline +baseline = File.read(baseline_file).to_f +delta = (runtime - baseline).abs / baseline +warning = delta > 0.10 + +#write runtime +File.write(commit_perf_file, "#{runtime.round(3)}\n") + +#write to log +File.open(log_file, "w") do |f| + f.puts "Baseline Runtime: #{baseline.round(3)} sec" + f.puts "Current Runtime: #{runtime.round(3)} sec" + f.puts "Delta: #{(delta * 100).round(2)}%" + f.puts warning ? "Performance Check: WARNING (runtime changed > 10%)" : "Performance Check: PASS" +end + +#results +puts warning ? "Performance Check: WARNING (see #{log_file})" : "Performance Check: PASS" +exit(warning ? 1 : 0) diff --git a/ci/check_tidy.rb b/ci/check_tidy.rb index 20bebbc..3a178dd 100644 --- a/ci/check_tidy.rb +++ b/ci/check_tidy.rb @@ -2,11 +2,11 @@ require 'fileutils' -commit_hash = `git rev-parse --short HEAD`.strip -log_file = "logs/commit-#{commit_hash}/tidy.log" -FileUtils.mkdir_p("logs/commit-#{commit_hash}") +log_dir = ENV["SANAFE_CI_LOG_DIR"] || "logs/commit-latest" +log_file = "#{log_dir}/tidy.log" +FileUtils.mkdir_p(log_dir) -cpp_files = Dir.glob("src/**/*.cpp") +cpp_files = Dir.glob("src/*.cpp") # failed_files = [] File.open(log_file, "w") do |log| diff --git a/ci/perf_baseline.txt b/ci/perf_baseline.txt new file mode 100644 index 0000000..53aa115 --- /dev/null +++ b/ci/perf_baseline.txt @@ -0,0 +1 @@ +106.41 diff --git a/ci/run.rb b/ci/run.rb index 7548a50..43bdc1d 100755 --- a/ci/run.rb +++ b/ci/run.rb @@ -2,20 +2,22 @@ require 'fileutils' -#paths -commit_hash = `git rev-parse --short HEAD`.strip -log_dir = "logs/commit-#{commit_hash}" -FileUtils.mkdir_p(log_dir) +timestamp = Time.now.strftime("%Y%m%d-%H%M%S") +commit_hash = `git rev-parse --short HEAD`.strip +unique_id = "#{timestamp}-#{commit_hash}" +log_dir = "logs/commit-#{unique_id}" +FileUtils.mkdir_p(log_dir) #create log directory if it doesn't exist + +ENV["SANAFE_CI_LOG_DIR"] = log_dir +ENV["SANAFE_CI_ID"] = unique_id puts "Running SANA-FE CI for commit #{commit_hash}" puts "Logs will be saved to #{log_dir}/" puts "-------------------------------" -#run build checker -build_status = system("ruby ci/check_build.rb") - -#run clang-format -format_status = system("ruby ci/check_format.rb") - -#run clang-tidy -tidy_status = system("ruby ci/check_tidy.rb") +build_status = system("ruby ci/check_build.rb") #run build checker +format_status = system("ruby ci/check_format.rb") #run clang-format +tidy_status = system("ruby ci/check_tidy.rb") #run clang-tidy +cppcheck_status = system("ruby ci/check_cppcheck.rb") +#TODO: Optional coverity upload +perf_status = system("ruby ci/check_perf.rb") From c2e4e6f0fee5175a6417ebe7245d3ef23e391c05 Mon Sep 17 00:00:00 2001 From: arsen iman Date: Thu, 26 Jun 2025 14:00:30 -0500 Subject: [PATCH 05/10] added dynamic test support to CI framework, docker image for building simulator, and docker image for building wheels --- CMakeLists.txt | 33 +++++++++++- ci/check_dynamic.rb | 78 +++++++++++++++++++++++++++ docker/sana-fe-python/Dockerfile | 28 ++++++++++ docker/sana-fe-python/build_wheels.sh | 19 +++++++ docker/sana-fe/Dockerfile | 15 ++++++ docker/sana-fe/script.sh | 13 +++++ pyproject.toml | 7 +++ tests/CMakeLists.txt | 26 +++++++++ tests/tests.cpp | 13 +++++ 9 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 ci/check_dynamic.rb create mode 100644 docker/sana-fe-python/Dockerfile create mode 100644 docker/sana-fe-python/build_wheels.sh create mode 100644 docker/sana-fe/Dockerfile create mode 100644 docker/sana-fe/script.sh create mode 100644 tests/CMakeLists.txt create mode 100644 tests/tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d82724..f7aaa92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,11 +30,16 @@ if (NOT DEFINED PYTHON_BUILD_ENABLED) set(PYTHON_BUILD_ENABLED ON) endif() + +if (NOT DEFINED ENABLE_TESTING) + set(ENABLE_TESTING OFF) +endif() + # Ensure that at least one build target is enabled -if(NOT STANDALONE_BUILD_ENABLED AND NOT PYTHON_BUILD_ENABLED) +if(NOT STANDALONE_BUILD_ENABLED AND NOT PYTHON_BUILD_ENABLED AND NOT ENABLE_TESTING) message( FATAL_ERROR - "No build target enabled: Either STANDALONE_BUILD_ENABLED or PYTHON_BUILD_ENABLED must be ON." + "No build target enabled: Either STANDALONE_BUILD_ENABLED or PYTHON_BUILD_ENABLED or ENABLE_TESTING must be ON." ) endif() @@ -170,6 +175,8 @@ FetchContent_MakeAvailable(ryml) set(RYML_DEFAULT_CALLBACK_USES_EXCEPTIONS ON) ##### END OF rapid-yaml +message(STATUS "got ryml") + ############## booksim2 project(booksim2-quickstart LANGUAGES CXX) set(BOOKSIM_VERSION "110ad1b80e493241f6e57587bc11354ac84f91f8") @@ -278,3 +285,25 @@ if(STANDALONE_BUILD_ENABLED) endif() endif() endif() + +if(ENABLE_TESTING) + list(FILTER SOURCE_FILES EXCLUDE REGEX "pymodule.cpp") + add_library(sanafe SHARED ${SOURCE_FILES} ${HEADER_FILES}) + target_link_libraries(sanafe PUBLIC ryml::ryml) + target_link_libraries(sanafe PUBLIC booksim) + target_link_libraries(sanafe PRIVATE ${CMAKE_DL_LIBS}) + if (OpenMP_CXX_FOUND AND ENABLE_OPENMP) + target_link_libraries(sanafe PRIVATE OpenMP::OpenMP_CXX) + target_compile_definitions(sanafe PRIVATE HAVE_OPENMP) + endif() + target_link_libraries(sanafe PRIVATE Threads::Threads) + target_include_directories(sanafe PUBLIC + ${CMAKE_SOURCE_DIR}/src + ) + message(STATUS "Testing enabled") + include(CTest) + add_subdirectory(tests) + + find_program(CTEST_MEMORYCHECK_COMMAND valgrind) + set(CTEST_MEMORYCHECK_COMMAND_OPTIONS "--leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=10") +endif() \ No newline at end of file diff --git a/ci/check_dynamic.rb b/ci/check_dynamic.rb new file mode 100644 index 0000000..fa335ef --- /dev/null +++ b/ci/check_dynamic.rb @@ -0,0 +1,78 @@ +#!/usr/bin/env ruby + +require 'fileutils' + +log_dir = ENV["SANAFE_CI_LOG_DIR"] || "logs/commit-latest" +build_log_file = "#{log_dir}/dynamic_build.log" +test_log_file = "#{log_dir}/dynamic_test.log" +FileUtils.mkdir_p(log_dir) + +# timeout after 30 seconds +timeout = 30 + +puts "Running dynamic tests..." + +puts "Building the project with testing enabled..." + +cmake = system("cmake -DENABLE_TESTING=ON -DPYTHON_BUILD_ENABLED=OFF -DSTANDALONE_BUILD_ENABLED=OFF -DCMAKE_BUILD_TYPE=Debug -S . -B build > #{build_log_file} 2>&1") + +if !cmake + puts "CMake configuration failed. See #{build_log_file} for details." + puts "Dynamic Tests: FAIL" + exit 3 +end + +build = system("cmake --build build -j 10 >> #{build_log_file} 2>&1") + +if !build + puts "Build failed. See #{build_log_file} for details." + puts "Dynamic Tests: FAIL" + exit 3 +end + +puts "Build successful. Running tests..." + +test = system("ctest --memcheck --test-dir build --output-on-failure --timeout #{timeout} > #{test_log_file} 2>&1") +exitcode = $?.exitstatus + +puts "Tests completed, now checking for memory leaks..." + +build_dir = "build" +mem_log_dir = "#{build_dir}/Testing/Temporary" +pattern = "MemoryChecker.*\\.log$" +summary_log = File.join(mem_log_dir, "LastMemCheck.log") + +unless Dir.exist?(mem_log_dir) + puts "Memory log directory #{mem_log_dir} does not exist. Something went wrong." + exit 2 +end + +log_files = Dir.entries(mem_log_dir).select { |f| f.match?(pattern) } +puts "Found #{log_files.size} Valgrind log(s)." + +leaks_found = false + +log_files.each do |filename| + path = File.join(mem_log_dir, filename) + contents = File.read(path) + + if contents.include?("definitely lost") + puts "Leak detected in #{filename}" + leaks_found = true + end +end + +if leaks_found + puts "Memory leaks detected in one or more Valgrind logs. See #{mem_log_dir} for details." + puts "Dynamic Tests: FAIL" + exit 2 +end +if exitcode != 0 + puts "One or more tests failed. See #{test_log_file} for details." + puts "Dynamic Tests: FAIL" + exit 1 +else + puts "All tests passed successfully. Hooray! Check build/Testing/Temporary for memory leak reports." + puts "Dynamic Tests: PASS" + exit 0 +end \ No newline at end of file diff --git a/docker/sana-fe-python/Dockerfile b/docker/sana-fe-python/Dockerfile new file mode 100644 index 0000000..0907b65 --- /dev/null +++ b/docker/sana-fe-python/Dockerfile @@ -0,0 +1,28 @@ +FROM ubuntu:latest + +# Add Docker's official GPG key (for docker-ce-cli installation): +RUN apt-get update && apt-get install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release && \ + mkdir -p /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && \ + chmod a+r /etc/apt/keyrings/docker.asc && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo ${UBUNTU_CODENAME:-focal}) stable" > /etc/apt/sources.list.d/docker.list && \ + apt-get update + +RUN apt update && apt install -y \ + python3 \ + python3-pip \ + python3-dev \ + docker-ce-cli + +WORKDIR /app +COPY . /app + +RUN pip3 install --break-system-packages cibuildwheel pybind11 + +# workaround for cibuildwheel issue with flex +CMD ["bash", "-c", "cibuildwheel --output-dir wheelhouse"] \ No newline at end of file diff --git a/docker/sana-fe-python/build_wheels.sh b/docker/sana-fe-python/build_wheels.sh new file mode 100644 index 0000000..38471e4 --- /dev/null +++ b/docker/sana-fe-python/build_wheels.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +SOCKET_PATH="/var/run/docker.sock" + +if [[ ! -S "$SOCKET_PATH" ]]; then + echo "Docker socket not found at $SOCKET_PATH. Make sure that Docker is running and accessible." + exit 1 +fi + +docker build -t sana-fe-python . + +if [[ $? -ne 0 ]]; then + echo "Docker build failed." + exit 1 +fi + +mkdir -p wheelhouse + +docker run --rm -v "$(pwd)/wheelhouse:/app/wheelhouse" -v "$SOCKET_PATH:$SOCKET_PATH" sana-fe-python \ No newline at end of file diff --git a/docker/sana-fe/Dockerfile b/docker/sana-fe/Dockerfile new file mode 100644 index 0000000..5f7ead7 --- /dev/null +++ b/docker/sana-fe/Dockerfile @@ -0,0 +1,15 @@ +FROM ubuntu:latest AS build + +WORKDIR /opt/app +COPY . . + +RUN apt-get update && apt-get install -y python3 python3-pip python3-dev make gcc g++ \ + git flex bison cmake pybind11-dev && \ + apt-get clean + +RUN mkdir build && \ + cd build && \ + cmake -DPYTHON_BUILD_ENABLED=OFF -DSTANDALONE_BUILD_ENABLED=ON .. && \ + make + +ENTRYPOINT [ "/opt/app/build/sim" ] \ No newline at end of file diff --git a/docker/sana-fe/script.sh b/docker/sana-fe/script.sh new file mode 100644 index 0000000..ba3eb61 --- /dev/null +++ b/docker/sana-fe/script.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +IMAGE_NAME="sana-fe" + +docker build -t $IMAGE_NAME . +if [ $? -eq 0 ]; then + echo "Docker image '$IMAGE_NAME' built successfully." +else + echo "Failed to build Docker image '$IMAGE_NAME'." + exit 1 +fi + +docker run --rm -v "$(pwd)/arch":/data/arch -v "$(pwd)/snn":/data/snn $IMAGE_NAME "/data/$1" "/data/$2" $3 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f5ba0e9..ccc0c78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,3 +6,10 @@ requires = [ "pybind11>=2.6.0" ] build-backend = "setuptools.build_meta" + +[tool.cibuildwheel.linux] +before-all = "yum install -y flex" + +[[tool.cibuildwheel.overrides]] +select = "*-musllinux*" +before-all = "apk add flex" \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..141c762 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,26 @@ +include(FetchContent) + +FetchContent_Declare( + googletest + URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip +) +set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +FetchContent_MakeAvailable(googletest) + +add_executable( + sanafe_tests + tests.cpp +) + +target_link_libraries( + sanafe_tests + GTest::gtest_main +) + +target_link_libraries( + sanafe_tests + sanafe +) + +include(GoogleTest) +gtest_discover_tests(sanafe_tests) \ No newline at end of file diff --git a/tests/tests.cpp b/tests/tests.cpp new file mode 100644 index 0000000..40f4af2 --- /dev/null +++ b/tests/tests.cpp @@ -0,0 +1,13 @@ +#include +#include "chip.hpp" + + +TEST(InitialTest, CheckTestFunctionality) { + // making sure that the test framework is working correctly + EXPECT_EQ(0, 0); +} + +TEST(InitialTest, CheckChipInitialization) { + char *a = new char[100]; + (void)a; +} \ No newline at end of file From 6640d4f5b3536a916a5144077f45d215dcc4841b Mon Sep 17 00:00:00 2001 From: arsen iman Date: Thu, 26 Jun 2025 20:58:50 -0500 Subject: [PATCH 06/10] fixed script for building wheels to work on rootless docker setups --- docker/sana-fe-python/Dockerfile | 2 ++ docker/sana-fe-python/build_wheels.sh | 13 +++++++++++-- docker/sana-fe/script.sh | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/docker/sana-fe-python/Dockerfile b/docker/sana-fe-python/Dockerfile index 0907b65..70cdd0a 100644 --- a/docker/sana-fe-python/Dockerfile +++ b/docker/sana-fe-python/Dockerfile @@ -22,6 +22,8 @@ RUN apt update && apt install -y \ WORKDIR /app COPY . /app +RUN rm -rf build + RUN pip3 install --break-system-packages cibuildwheel pybind11 # workaround for cibuildwheel issue with flex diff --git a/docker/sana-fe-python/build_wheels.sh b/docker/sana-fe-python/build_wheels.sh index 38471e4..ec505af 100644 --- a/docker/sana-fe-python/build_wheels.sh +++ b/docker/sana-fe-python/build_wheels.sh @@ -1,10 +1,19 @@ #!/bin/bash -SOCKET_PATH="/var/run/docker.sock" +ROOTLESS_OR_NOT=$(docker info | grep -i "rootless") +if [[ -z "$ROOTLESS_OR_NOT" ]]; then + SOCKET_PATH="/var/run/docker.sock" + echo "You are not running Docker in rootless mode. Using default socket path: $SOCKET_PATH" +else + SOCKET_PATH="$XDG_RUNTIME_DIR/docker.sock" + echo "You are running Docker in rootless mode. Using socket path: $SOCKET_PATH" +fi if [[ ! -S "$SOCKET_PATH" ]]; then echo "Docker socket not found at $SOCKET_PATH. Make sure that Docker is running and accessible." exit 1 +else + echo "Using Docker socket at $SOCKET_PATH." fi docker build -t sana-fe-python . @@ -16,4 +25,4 @@ fi mkdir -p wheelhouse -docker run --rm -v "$(pwd)/wheelhouse:/app/wheelhouse" -v "$SOCKET_PATH:$SOCKET_PATH" sana-fe-python \ No newline at end of file +docker run --rm -v "$(pwd)/wheelhouse:/app/wheelhouse" -v "$SOCKET_PATH:/var/run/docker.sock" sana-fe-python \ No newline at end of file diff --git a/docker/sana-fe/script.sh b/docker/sana-fe/script.sh index ba3eb61..f51f559 100644 --- a/docker/sana-fe/script.sh +++ b/docker/sana-fe/script.sh @@ -10,4 +10,4 @@ else exit 1 fi -docker run --rm -v "$(pwd)/arch":/data/arch -v "$(pwd)/snn":/data/snn $IMAGE_NAME "/data/$1" "/data/$2" $3 \ No newline at end of file +docker run --rm -v "$(pwd)/arch":/data/arch -v "$(pwd)/snn":/data/snn $IMAGE_NAME "$@" \ No newline at end of file From 61952bc1ad0e06ec204dfbd8902ab6ff12f3ea44 Mon Sep 17 00:00:00 2001 From: Zachary Houlton Date: Fri, 27 Jun 2025 22:06:01 -0500 Subject: [PATCH 07/10] Integrated dynamic file, cleaned up build check --- ci/check_build.rb | 17 +++-------------- ci/run.rb | 11 ++++++----- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/ci/check_build.rb b/ci/check_build.rb index c811016..2f60d1d 100755 --- a/ci/check_build.rb +++ b/ci/check_build.rb @@ -38,10 +38,9 @@ def build_python(label:, build_dir:, log_file:) full_log_path = File.expand_path(log_file) - #create unique venv name using timestamp and commit hash - timestamp = Time.now.strftime('%Y%m%d-%H%M%S') - commit_hash = `git rev-parse --short HEAD`.strip - venv_path = File.expand_path("venvs/commit-#{timestamp}-#{commit_hash}") + #create venv name + unique_id = ENV["SANAFE_CI_ID"] + venv_path = File.expand_path("#{File.dirname(log_file)}/venv") venv_python = File.join(venv_path, "bin", "python") FileUtils.mkdir_p("venvs") @@ -62,16 +61,6 @@ def build_python(label:, build_dir:, log_file:) end end - #copy into site packages - built_so = Dir.glob("#{build_dir}/../**/sanafecpp*.so").find { |f| File.file?(f) } - dest_dir = File.join(ENV["CONDA_PREFIX"], "lib", "python#{RUBY_VERSION[/\d+\.\d+/]}", "site-packages", "sanafe") - if built_so && File.exist?(built_so) - FileUtils.mkdir_p(dest_dir) - FileUtils.cp(built_so, dest_dir) - end - - FileUtils.rm_rf(venv_path) - puts "[#{label}] Python build: #{install_ok ? 'PASS' : "FAIL (see #{log_file})"}" install_ok end diff --git a/ci/run.rb b/ci/run.rb index 43bdc1d..a181aad 100755 --- a/ci/run.rb +++ b/ci/run.rb @@ -6,7 +6,7 @@ commit_hash = `git rev-parse --short HEAD`.strip unique_id = "#{timestamp}-#{commit_hash}" log_dir = "logs/commit-#{unique_id}" -FileUtils.mkdir_p(log_dir) #create log directory if it doesn't exist +FileUtils.mkdir_p(log_dir) ENV["SANAFE_CI_LOG_DIR"] = log_dir ENV["SANAFE_CI_ID"] = unique_id @@ -15,9 +15,10 @@ puts "Logs will be saved to #{log_dir}/" puts "-------------------------------" -build_status = system("ruby ci/check_build.rb") #run build checker -format_status = system("ruby ci/check_format.rb") #run clang-format -tidy_status = system("ruby ci/check_tidy.rb") #run clang-tidy +build_status = system("ruby ci/check_build.rb") +format_status = system("ruby ci/check_format.rb") +tidy_status = system("ruby ci/check_tidy.rb") cppcheck_status = system("ruby ci/check_cppcheck.rb") -#TODO: Optional coverity upload +#TODO: Optional coverity integration +dynamic_status = system("ruby ci/check_dynamic.rb") perf_status = system("ruby ci/check_perf.rb") From 53ab43ecf55c6ce5a252378c136226381537ea52 Mon Sep 17 00:00:00 2001 From: arsen iman Date: Mon, 30 Jun 2025 16:07:11 -0500 Subject: [PATCH 08/10] setting up github actions for building wheels --- .github/workflows/build_wheels.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/build_wheels.yml diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml new file mode 100644 index 0000000..7867c0a --- /dev/null +++ b/.github/workflows/build_wheels.yml @@ -0,0 +1,30 @@ +name: Build + +on: [push, pull_request] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, macos-13, macos-latest] + + steps: + - uses: actions/checkout@v4 + + - name: Build wheels + uses: pypa/cibuildwheel@v3.0.0 + # env: + # CIBW_SOME_OPTION: value + # ... + with: + # package-dir: . + # output-dir: wheelhouse + config-file: "{package}/pyproject.toml" + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} + path: ./wheelhouse/*.whl \ No newline at end of file From 19490f2f03191a577ef3481410b3d35ae139f2af Mon Sep 17 00:00:00 2001 From: a1rsman <93457227+a1rsman@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:17:17 -0500 Subject: [PATCH 09/10] updated build_wheels.yml for windows and mac --- .github/workflows/build_wheels.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_wheels.yml b/.github/workflows/build_wheels.yml index 7867c0a..53ca733 100644 --- a/.github/workflows/build_wheels.yml +++ b/.github/workflows/build_wheels.yml @@ -14,6 +14,17 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Install flex and bison (platform-specific) + shell: bash + run: | + if [[ "$RUNNER_OS" == "macOS" ]]; then + brew install bison flex + echo 'PATH="/opt/homebrew/opt/bison/bin:/opt/homebrew/opt/flex/bin:$PATH"' >> $GITHUB_ENV + elif [[ "$RUNNER_OS" == "Windows" ]]; then + choco install winflexbison3 + echo 'C:\ProgramData\chocolatey\bin' >> $GITHUB_PATH + fi + - name: Build wheels uses: pypa/cibuildwheel@v3.0.0 # env: @@ -27,4 +38,4 @@ jobs: - uses: actions/upload-artifact@v4 with: name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} - path: ./wheelhouse/*.whl \ No newline at end of file + path: ./wheelhouse/*.whl From 6becc2ce4222403d870b54c2d5a06cd89e4f783c Mon Sep 17 00:00:00 2001 From: Zachary Houlton Date: Wed, 2 Jul 2025 03:11:38 -0500 Subject: [PATCH 10/10] Minor fixes --- .gitignore | 6 ------ ci/check_build.rb | 6 ++++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 4a879ee..5fd2476 100644 --- a/.gitignore +++ b/.gitignore @@ -33,11 +33,5 @@ build/ CMakeFiles/ Makefile compile_commands.json -logs/ -Miniforge3-*.sh -.env -coverity_tool.tgz -cov-analysis-* -venvs/ test_venv/ *.so diff --git a/ci/check_build.rb b/ci/check_build.rb index 2f60d1d..57c724c 100755 --- a/ci/check_build.rb +++ b/ci/check_build.rb @@ -65,13 +65,15 @@ def build_python(label:, build_dir:, log_file:) install_ok end -#set clang path -clang_path = "/home/jab23579/useful/clang-20/bin/clang++" +#set clang and gcc path +clang_path = ENV["CLANG_PATH"] +gcc_path = ENV["GCC_PATH"] #gcc builds gcc_sim_ok = build_cpp( label: "GCC", build_dir: "build_gcc", + compiler: gcc_path, log_file: "#{log_dir}/build_gcc_sim.log" ) gcc_py_ok = build_python(