ceedling 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/GIT_COMMIT_SHA +1 -1
- data/README.md +57 -58
- data/Rakefile +5 -3
- data/assets/features/ceedling +1 -1
- data/assets/fixtures/test_source_file_duplicate_basenames/test_calc_typo.c +14 -0
- data/assets/fixtures/tests_with_duplicate_test_names/test_foo.c +21 -0
- data/bin/actionator.rb +6 -12
- data/bin/app_cfg.rb +35 -1
- data/bin/ceedling +13 -3
- data/bin/cli.rb +25 -6
- data/bin/cli_handler.rb +119 -73
- data/bin/cli_helper.rb +56 -78
- data/bin/installation_selector.rb +81 -0
- data/bin/launchinator.rb +69 -0
- data/bin/mixin_resolvinator.rb +26 -36
- data/bin/objects.yml +13 -0
- data/bin/path_validator.rb +25 -29
- data/bin/versionator.rb +47 -48
- data/ceedling.gemspec +19 -15
- data/docs/BreakingChanges.md +70 -1
- data/docs/Changelog.md +84 -3
- data/docs/KnownIssues.md +90 -4
- data/docs/ReleaseNotes.md +13 -0
- data/docs/overrides/main.html +11 -8
- data/lib/ceedling/build_log_writer.rb +33 -0
- data/lib/ceedling/config/build_root_policy.rb +70 -0
- data/lib/ceedling/config/configurator.rb +19 -18
- data/lib/ceedling/config/configurator_builder.rb +58 -28
- data/lib/ceedling/config/configurator_paths_validator.rb +179 -0
- data/lib/ceedling/config/configurator_plugins.rb +1 -1
- data/lib/ceedling/config/configurator_setup.rb +39 -41
- data/lib/ceedling/config/configurator_validator.rb +1 -110
- data/lib/ceedling/defaults.rb +4 -4
- data/lib/ceedling/dependencies/README.md +18 -16
- data/lib/ceedling/dependencies/dependency_cache_store.rb +20 -3
- data/lib/ceedling/dependencies/dependency_hasher.rb +35 -1
- data/lib/ceedling/filename_extension.rb +7 -1
- data/lib/ceedling/filesystem/FILE_TREE.md +253 -0
- data/lib/ceedling/filesystem/PATH_HANDLING.md +304 -0
- data/lib/ceedling/filesystem/README.md +41 -0
- data/lib/ceedling/filesystem/build_subpath.rb +102 -0
- data/lib/ceedling/filesystem/destination_collision.rb +25 -0
- data/lib/ceedling/{file_finder.rb → filesystem/file_finder.rb} +159 -104
- data/lib/ceedling/{file_finder_helper.rb → filesystem/file_finder_helper.rb} +36 -31
- data/lib/ceedling/filesystem/file_path_collection_utils.rb +183 -0
- data/lib/ceedling/{file_path_utils.rb → filesystem/file_path_utils.rb} +108 -97
- data/lib/ceedling/{file_system_wrapper.rb → filesystem/file_system_wrapper.rb} +6 -5
- data/lib/ceedling/filesystem/file_tree.rb +241 -0
- data/lib/ceedling/{file_wrapper.rb → filesystem/file_wrapper.rb} +65 -28
- data/lib/ceedling/filesystem/path_exclusions.rb +30 -0
- data/lib/ceedling/{path_matcher.rb → filesystem/path_matcher.rb} +27 -23
- data/lib/ceedling/{path_mirror.rb → filesystem/path_mirror.rb} +19 -18
- data/lib/ceedling/filesystem/path_spellings.rb +55 -0
- data/lib/ceedling/filesystem/project_paths.rb +184 -0
- data/lib/ceedling/filesystem/working_dir.rb +40 -0
- data/lib/ceedling/generators/generator.rb +5 -6
- data/lib/ceedling/generators/generator_partials.rb +1 -1
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +7 -7
- data/lib/ceedling/handoff.rb +121 -0
- data/lib/ceedling/includes/include_factory.rb +3 -3
- data/lib/ceedling/includes/include_pathinator.rb +67 -10
- data/lib/ceedling/includes/includes.rb +1 -1
- data/lib/ceedling/installation.rb +92 -0
- data/lib/ceedling/library_resolver.rb +99 -0
- data/lib/ceedling/objects.yml +23 -2
- data/lib/ceedling/partials/partializer.rb +4 -4
- data/lib/ceedling/partials/partializer_config.rb +3 -1
- data/lib/ceedling/plugins/report_log_writer_plugin.rb +3 -5
- data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +1 -1
- data/lib/ceedling/preprocess/preprocessinator_includes_handler.rb +1 -1
- data/lib/ceedling/preprocess/preprocessinator_line_marker_includes_extractor.rb +1 -1
- data/lib/ceedling/rake_app/rakefile_component_resolver.rb +10 -17
- data/lib/ceedling/rake_app/test_task_name.rb +29 -0
- data/lib/ceedling/rakefile.rb +16 -1
- data/lib/ceedling/rakefiles/base/tasks_base.rake +1 -1
- data/lib/ceedling/rakefiles/release/rules_release.rake +4 -7
- data/lib/ceedling/rakefiles/tests/rules_tests.rake +3 -20
- data/lib/ceedling/rakefiles/tests/rules_tests_generate.rake +4 -30
- data/lib/ceedling/release_invoker/release_build_executor.rb +33 -44
- data/lib/ceedling/release_invoker/release_build_planner.rb +14 -6
- data/lib/ceedling/reportinator.rb +3 -4
- data/lib/ceedling/setupinator.rb +95 -87
- data/lib/ceedling/test_context_extractor.rb +5 -1
- data/lib/ceedling/test_invoker/PIPELINE.md +9 -7
- data/lib/ceedling/test_invoker/mock_locator.rb +60 -2
- data/lib/ceedling/test_invoker/partials_manager.rb +12 -0
- data/lib/ceedling/test_invoker/standin_headerinator.rb +22 -13
- data/lib/ceedling/test_invoker/test_build_executor.rb +42 -41
- data/lib/ceedling/test_invoker/test_build_planner.rb +37 -18
- data/lib/ceedling/test_invoker/test_build_setup.rb +17 -12
- data/lib/ceedling/test_invoker/test_build_validations.rb +16 -31
- data/lib/ceedling/test_invoker/test_file_preprocessinator.rb +1 -1
- data/lib/ceedling/test_invoker/test_invoker.rb +6 -1
- data/lib/ceedling/test_invoker/test_pipeline_helpers.rb +1 -1
- data/lib/ceedling/test_invoker/test_source_file_directive_resolver.rb +40 -20
- data/lib/ceedling/tool_executor.rb +9 -3
- data/lib/ceedling/yaml_wrapper.rb +3 -2
- data/lib/version.rb +1 -1
- data/plugins/bullseye/bullseye.rake +6 -9
- data/plugins/bullseye/config/defaults_bullseye.rb +1 -1
- data/plugins/bullseye/lib/bullseye.rb +9 -7
- data/plugins/cppcheck/config/defaults_cppcheck.rb +1 -1
- data/plugins/dependencies/config/defaults.yml +6 -6
- data/plugins/dependencies/dependencies.rake +4 -1
- data/plugins/dependencies/lib/dependencies.rb +18 -8
- data/plugins/dependencies/lib/dependency_spec.rb +17 -5
- data/plugins/fff/lib/fff.rb +45 -39
- data/plugins/fff/spec/fff_cmock_wrapper_spec.rb +128 -0
- data/plugins/gcov/config/defaults_gcov.rb +1 -1
- data/plugins/gcov/gcov.rake +6 -9
- data/plugins/gcov/lib/console_reportinator.rb +4 -18
- data/plugins/gcov/lib/gcov.rb +9 -2
- data/plugins/gcov/lib/gcovr_reportinator.rb +8 -40
- data/plugins/gcov/lib/gcovr_thresholds.rb +74 -0
- data/plugins/gcov/lib/reportgenerator_reportinator.rb +1 -1
- data/plugins/module_generator/lib/module_generator.rb +3 -3
- data/plugins/report_tests_teamcity_stdout/lib/report_tests_teamcity_stdout.rb +53 -80
- data/plugins/sanitize/lib/sanitize_records.rb +3 -6
- data/plugins/sanitize/sanitize.rake +6 -6
- data/plugins/valgrind/lib/valgrind.rb +14 -7
- data/plugins/valgrind/valgrind.rake +6 -6
- data/site-local/404.html +6 -20
- data/site-local/assets/javascripts/{bundle.d7400e89.min.js → bundle.c21346df.min.js} +3 -3
- data/site-local/assets/javascripts/{bundle.d7400e89.min.js.map → bundle.c21346df.min.js.map} +2 -2
- data/site-local/assets/javascripts/workers/{search.2c215733.min.js → search.a7633da7.min.js} +2 -2
- data/site-local/assets/javascripts/workers/{search.2c215733.min.js.map → search.a7633da7.min.js.map} +1 -1
- data/site-local/assets/stylesheets/main.57a0d79d.min.css +6 -0
- data/site-local/assets/stylesheets/main.57a0d79d.min.css.map +1 -0
- data/site-local/assets/stylesheets/palette.fb2fffe2.min.css +4 -0
- data/site-local/assets/stylesheets/palette.fb2fffe2.min.css.map +1 -0
- data/site-local/configuration/environment-vars.html +6 -20
- data/site-local/configuration/global-collections.html +6 -20
- data/site-local/configuration/index.html +6 -20
- data/site-local/configuration/loading.html +6 -20
- data/site-local/configuration/mixins.html +10 -24
- data/site-local/configuration/parallel-builds.html +6 -20
- data/site-local/configuration/project-file.html +8 -22
- data/site-local/configuration/reference/cexception.html +6 -20
- data/site-local/configuration/reference/cmock.html +8 -21
- data/site-local/configuration/reference/defines.html +16 -30
- data/site-local/configuration/reference/environment.html +10 -24
- data/site-local/configuration/reference/extension.html +6 -20
- data/site-local/configuration/reference/files.html +22 -30
- data/site-local/configuration/reference/flags.html +13 -27
- data/site-local/configuration/reference/index.html +6 -20
- data/site-local/configuration/reference/libraries.html +27 -23
- data/site-local/configuration/reference/mixins.html +6 -20
- data/site-local/configuration/reference/partials.html +6 -20
- data/site-local/configuration/reference/paths.html +38 -29
- data/site-local/configuration/reference/plugins.html +6 -20
- data/site-local/configuration/reference/project.html +32 -36
- data/site-local/configuration/reference/release-build.html +8 -20
- data/site-local/configuration/reference/test-build.html +6 -20
- data/site-local/configuration/reference/test-runner.html +6 -20
- data/site-local/configuration/reference/tools.html +27 -32
- data/site-local/configuration/reference/unity.html +6 -20
- data/site-local/configuration/which-ceedling.html +383 -37
- data/site-local/development/index.html +6 -20
- data/site-local/development/plugins/configuration.html +6 -20
- data/site-local/development/plugins/index.html +6 -20
- data/site-local/development/plugins/plugin-subclass.html +54 -68
- data/site-local/development/plugins/rake-tasks.html +8 -22
- data/site-local/development/releases.html +84 -29
- data/site-local/development/workflow.html +62 -71
- data/site-local/getting-started/builds.html +8 -20
- data/site-local/getting-started/command-line.html +62 -24
- data/site-local/getting-started/example-projects/cipher-quest.html +6 -20
- data/site-local/getting-started/example-projects/index.html +6 -20
- data/site-local/getting-started/example-projects/temp-sensor.html +6 -20
- data/site-local/getting-started/example-projects/wondrous-forest.html +6 -20
- data/site-local/getting-started/index.html +6 -20
- data/site-local/getting-started/installation.html +6 -20
- data/site-local/getting-started/quick-start.html +6 -20
- data/site-local/help.html +6 -20
- data/site-local/index.html +11 -24
- data/site-local/overview/build-system.html +6 -20
- data/site-local/overview/index.html +6 -20
- data/site-local/overview/test-environments.html +6 -20
- data/site-local/overview/tools-and-frameworks.html +6 -20
- data/site-local/plugins/beep.html +6 -20
- data/site-local/plugins/bullseye/index.html +13 -24
- data/site-local/plugins/bullseye/licensing.html +14 -28
- data/site-local/plugins/command-hooks.html +8 -22
- data/site-local/plugins/compile-commands-json-db.html +6 -20
- data/site-local/plugins/cppcheck.html +8 -22
- data/site-local/plugins/dependencies.html +16 -27
- data/site-local/plugins/fff.html +49 -32
- data/site-local/plugins/gcov/examples.html +7 -21
- data/site-local/plugins/gcov/gcovr.html +22 -33
- data/site-local/plugins/gcov/index.html +6 -20
- data/site-local/plugins/gcov/overview.html +9 -20
- data/site-local/plugins/gcov/reportgenerator.html +9 -23
- data/site-local/plugins/gcov/reporting.html +6 -20
- data/site-local/plugins/gcov/setup.html +8 -21
- data/site-local/plugins/gcov/tool-versions.html +6 -20
- data/site-local/plugins/gcov/troubleshooting.html +10 -24
- data/site-local/plugins/index.html +6 -20
- data/site-local/plugins/module-generator.html +21 -35
- data/site-local/plugins/report-build-warnings-log.html +53 -67
- data/site-local/plugins/report-tests-gtestlike-stdout.html +6 -20
- data/site-local/plugins/report-tests-ide-stdout.html +43 -57
- data/site-local/plugins/report-tests-log-factory.html +50 -64
- data/site-local/plugins/report-tests-pretty-stdout.html +7 -21
- data/site-local/plugins/report-tests-raw-output-log.html +9 -21
- data/site-local/plugins/report-tests-teamcity-stdout.html +20 -31
- data/site-local/plugins/sanitize.html +56 -65
- data/site-local/plugins/valgrind.html +49 -58
- data/site-local/project/index.html +6 -20
- data/site-local/project/release-history.html +6 -20
- data/site-local/project/sbom.html +6 -20
- data/site-local/project/upgrade.html +9 -20
- data/site-local/reference/build-directives.html +6 -20
- data/site-local/reference/command-line.html +15 -22
- data/site-local/reference/environment-vars.html +6 -20
- data/site-local/reference/gcov-plugin.html +6 -20
- data/site-local/reference/global-collections.html +6 -20
- data/site-local/reference/index.html +6 -20
- data/site-local/reference/partials-macros.html +6 -20
- data/site-local/reference/project-configuration.html +6 -20
- data/site-local/sitemap.xml.gz +0 -0
- data/site-local/snapshot/examples/cipher_quest/index.html +16 -30
- data/site-local/snapshot/examples/temp_sensor/index.html +8 -22
- data/site-local/snapshot/examples/wondrous_forest/index.html +6 -20
- data/site-local/snapshot/plugins/bullseye/config/defaults_bullseye.rb +1 -1
- data/site-local/snapshot/plugins/gcov/config/defaults_gcov.rb +1 -1
- data/site-local/testing-guide/build-directives.html +14 -28
- data/site-local/testing-guide/conventions.html +66 -27
- data/site-local/testing-guide/frameworks.html +11 -25
- data/site-local/testing-guide/index.html +6 -20
- data/site-local/testing-guide/partials/configuration.html +8 -22
- data/site-local/testing-guide/partials/conventions.html +8 -22
- data/site-local/testing-guide/partials/directives.html +16 -30
- data/site-local/testing-guide/partials/example.html +14 -28
- data/site-local/testing-guide/partials/index.html +6 -20
- data/site-local/testing-guide/partials/overview.html +6 -20
- data/site-local/testing-guide/partials/variables.html +11 -25
- data/site-local/testing-guide/test-cases.html +13 -27
- data/site-local/testing-guide/test-sample.html +12 -26
- data/site-local/testing-guide/test-suite-anatomy.html +6 -20
- data/spec/integration/actionator_file_operations_spec.rb +91 -1
- data/spec/integration/compile_commands_json_db_spec.rb +1 -1
- data/spec/integration/configurator_pipeline_spec.rb +10 -0
- data/spec/integration/configurator_validation_spec.rb +1 -1
- data/spec/integration/dependencies/dependency_tracker_cache_hardening_spec.rb +26 -0
- data/spec/integration/dependencies/dependency_tracker_core_spec.rb +62 -0
- data/spec/integration/launcher_handoff_spec.rb +297 -0
- data/spec/integration/mixin_loading_spec.rb +1 -1
- data/spec/integration/module_generator_spec.rb +3 -3
- data/spec/integration/nested_test_artifacts_spec.rb +99 -0
- data/spec/integration/partials_module_resolution_spec.rb +7 -4
- data/spec/integration/path_intake_scenarios_spec.rb +101 -0
- data/spec/integration/path_scenarios_spec.rb +322 -0
- data/spec/integration/quote_include_isolator_spec.rb +1 -1
- data/spec/integration/rake_task_registry_plugins_spec.rb +95 -0
- data/spec/integration/report_build_warnings_log_spec.rb +1 -1
- data/spec/integration/report_tests_log_factory_spec.rb +1 -1
- data/spec/integration/test_executable_manifest_spec.rb +1 -1
- data/spec/integration/test_includes_extractinator_spec.rb +2 -1
- data/spec/integration/test_naming_spec.rb +198 -0
- data/spec/integration/test_source_file_checks_spec.rb +166 -0
- data/spec/integration/tool_validator_spec.rb +1 -1
- data/spec/integration/upgrade_docs_spec.rb +84 -0
- data/spec/integration/vendor_copy_transient_io_retry_spec.rb +192 -75
- data/spec/support/file_tree_helper.rb +88 -0
- data/spec/support/integration/configurator_integration_helper.rb +10 -7
- data/spec/support/integration/dependency_tracker_integration_helper.rb +1 -1
- data/spec/support/integration/partials_generation_integration_helper.rb +2 -2
- data/spec/support/integration/path_scenario_harness.rb +173 -0
- data/spec/support/integration/spec_integration_helper.rb +1 -1
- data/spec/support/integration/test_includes_harness.rb +13 -2
- data/spec/support/spec_helper.rb +2 -0
- data/spec/support/system/spec_system_helper.rb +7 -6
- data/spec/support/system/system_context.rb +21 -3
- data/spec/support/working_dir_tracking.rb +49 -0
- data/spec/system/build_output_mirroring_spec.rb +2 -2
- data/spec/system/conditional_mock_includes_spec.rb +1 -1
- data/spec/system/delta_builds_spec.rb +137 -11
- data/spec/system/gcov_deployment_spec.rb +1 -0
- data/spec/system/header_include_path_validation_spec.rb +4 -4
- data/spec/system/launcher_handoff_spec.rb +82 -0
- data/spec/system/mock_include_folders_spec.rb +91 -0
- data/spec/system/parent_directory_reference_spec.rb +5 -3
- data/spec/system/path_handling_probes_spec.rb +192 -0
- data/spec/system/preprocessing_parameterized_tests_spec.rb +1 -1
- data/spec/system/rake_release_task_path_disambiguation_spec.rb +6 -6
- data/spec/system/rake_test_task_path_disambiguation_spec.rb +85 -0
- data/spec/system/special_path_characters_spec.rb +86 -0
- data/spec/system/support/common_test_cases.rb +23 -21
- data/spec/system/support/gcov_common_test_cases.rb +37 -9
- data/spec/system/support/sanitize_common_test_cases.rb +2 -2
- data/spec/system/support/valgrind_common_test_cases.rb +12 -12
- data/spec/system/test_pipeline_stop_points_spec.rb +4 -4
- data/spec/system/test_runner_system_include_spec.rb +1 -1
- data/spec/system/test_source_file_directive_spec.rb +34 -5
- data/spec/units/bin/actionator_spec.rb +14 -17
- data/spec/units/bin/app_cfg_spec.rb +47 -0
- data/spec/units/bin/cli_handler_spec.rb +284 -7
- data/spec/units/bin/cli_helper_spec.rb +126 -45
- data/spec/units/bin/installation_selector_spec.rb +114 -0
- data/spec/units/bin/launchinator_spec.rb +171 -0
- data/spec/units/bin/path_validator_spec.rb +10 -0
- data/spec/units/bin/versionator_spec.rb +38 -6
- data/spec/units/build_log_writer_spec.rb +57 -0
- data/spec/units/config/build_root_policy_spec.rb +61 -0
- data/spec/units/config/configurator_builder_spec.rb +37 -6
- data/spec/units/config/configurator_paths_validator_spec.rb +292 -0
- data/spec/units/config/configurator_setup_spec.rb +65 -103
- data/spec/units/config/configurator_spec.rb +37 -6
- data/spec/units/config/configurator_validator_spec.rb +1 -172
- data/spec/units/default_tools_quoting_spec.rb +83 -0
- data/spec/units/dependencies/dependency_cache_store_spec.rb +33 -2
- data/spec/units/dependencies/dependency_debug_tree_spec.rb +1 -1
- data/spec/units/dependencies/dependency_hasher_spec.rb +42 -1
- data/spec/units/dependencies/dependency_path_normalizer_spec.rb +1 -1
- data/spec/units/dependencies/dependency_tracker_spec.rb +3 -1
- data/spec/units/filename_extension_spec.rb +11 -0
- data/spec/units/filesystem/build_subpath_spec.rb +122 -0
- data/spec/units/filesystem/destination_collision_spec.rb +30 -0
- data/spec/units/{file_finder_helper_spec.rb → filesystem/file_finder_helper_spec.rb} +46 -1
- data/spec/units/{file_finder_spec.rb → filesystem/file_finder_spec.rb} +188 -22
- data/spec/units/{file_path_collection_utils_spec.rb → filesystem/file_path_collection_utils_spec.rb} +181 -1
- data/spec/units/{file_path_utils_spec.rb → filesystem/file_path_utils_spec.rb} +204 -87
- data/spec/units/filesystem/file_system_wrapper_spec.rb +33 -0
- data/spec/units/filesystem/file_tree_spec.rb +511 -0
- data/spec/units/filesystem/file_tree_windows_spec.rb +78 -0
- data/spec/units/{file_wrapper_spec.rb → filesystem/file_wrapper_spec.rb} +154 -32
- data/spec/units/{path_matcher_spec.rb → filesystem/path_matcher_spec.rb} +12 -1
- data/spec/units/{path_mirror_spec.rb → filesystem/path_mirror_spec.rb} +32 -4
- data/spec/units/filesystem/project_paths_spec.rb +147 -0
- data/spec/units/filesystem/working_dir_spec.rb +71 -0
- data/spec/units/generators/generator_spec.rb +12 -6
- data/spec/units/generators/generator_test_results_backtrace_spec.rb +50 -46
- data/spec/units/generators/generator_test_results_spec.rb +1 -1
- data/spec/units/handoff_spec.rb +205 -0
- data/spec/units/includes/include_factory_spec.rb +3 -3
- data/spec/units/includes/include_pathinator_spec.rb +46 -0
- data/spec/units/installation_spec.rb +228 -0
- data/spec/units/library_resolver_spec.rb +157 -0
- data/spec/units/mock_locator_spec.rb +76 -1
- data/spec/units/partials/partializer_config_spec.rb +7 -0
- data/spec/units/partials/partializer_spec.rb +1 -0
- data/spec/units/partials_manager_spec.rb +17 -0
- data/spec/units/plugins/bullseye_spec.rb +15 -2
- data/spec/units/plugins/console_reportinator_spec.rb +18 -20
- data/spec/units/plugins/dependencies_spec.rb +33 -7
- data/spec/units/plugins/dependency_spec_spec.rb +17 -0
- data/spec/units/plugins/gcov_spec.rb +28 -3
- data/spec/units/plugins/gcovr_reportinator_spec.rb +19 -47
- data/spec/units/plugins/gcovr_thresholds_spec.rb +95 -0
- data/spec/units/plugins/module_generator_spec.rb +5 -5
- data/spec/units/plugins/report_build_warnings_log_spec.rb +3 -3
- data/spec/units/plugins/report_log_writer_plugin_spec.rb +10 -1
- data/spec/units/plugins/report_tests_raw_output_log_spec.rb +13 -2
- data/spec/units/plugins/report_tests_teamcity_stdout_spec.rb +77 -3
- data/spec/units/plugins/reportgenerator_reportinator_spec.rb +1 -1
- data/spec/units/plugins/sanitize_records_spec.rb +3 -1
- data/spec/units/plugins/sanitize_spec.rb +1 -1
- data/spec/units/plugins/valgrind_spec.rb +23 -8
- data/spec/units/preprocess/preprocessinator_comment_stripper_spec.rb +1 -1
- data/spec/units/rake_app/rakefile_component_resolver_spec.rb +21 -0
- data/spec/units/rake_app/test_task_name_spec.rb +47 -0
- data/spec/units/release_build_executor_spec.rb +53 -14
- data/spec/units/release_build_planner_spec.rb +15 -3
- data/spec/units/reportinator_spec.rb +5 -5
- data/spec/units/standin_headerinator_spec.rb +31 -1
- data/spec/units/test_build_executor_spec.rb +107 -5
- data/spec/units/test_build_planner_spec.rb +104 -2
- data/spec/units/test_build_setup_spec.rb +96 -12
- data/spec/units/test_context_extractor_spec.rb +25 -0
- data/spec/units/test_file_preprocessinator_spec.rb +17 -1
- data/spec/units/test_includes_extractinator_spec.rb +5 -1
- data/spec/units/test_invoker/objects_wiring_spec.rb +6 -3
- data/spec/units/test_invoker_spec.rb +14 -1
- data/spec/units/test_source_file_directive_resolver_spec.rb +38 -7
- data/spec/units/tool_executor_spec.rb +26 -0
- data/spec/units/yaml_wrapper_spec.rb +11 -0
- metadata +122 -33
- data/lib/ceedling/file_path_collection_utils.rb +0 -164
- data/site-local/assets/stylesheets/main.ec1eaa64.min.css +0 -1
- data/site-local/assets/stylesheets/main.ec1eaa64.min.css.map +0 -1
- data/site-local/assets/stylesheets/palette.ab4e12ef.min.css +0 -1
- data/site-local/assets/stylesheets/palette.ab4e12ef.min.css.map +0 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# =========================================================================
|
|
2
|
+
# Ceedling - Test-Centered Build System for C
|
|
3
|
+
# ThrowTheSwitch.org
|
|
4
|
+
# Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
|
|
5
|
+
# SPDX-License-Identifier: MIT
|
|
6
|
+
# =========================================================================
|
|
7
|
+
|
|
8
|
+
require 'fileutils'
|
|
9
|
+
require 'ceedling/constants'
|
|
10
|
+
require 'ceedling/exceptions'
|
|
11
|
+
|
|
12
|
+
# Walks and copies trees of regular files without `lstat`. On some filesystems `lstat`
|
|
13
|
+
# reports a regular file as a directory, and any copy that classifies by it puts a directory
|
|
14
|
+
# where the file belongs. FileTree lists a directory with `Dir.children`, which reads names
|
|
15
|
+
# without glob syntax, and classifies each entry with `File.stat`, which follows links.
|
|
16
|
+
#
|
|
17
|
+
# Links in a source tree are followed, and a copy holds only regular files and directories.
|
|
18
|
+
# A link loop or a dangling link raises rather than copying part of a tree.
|
|
19
|
+
#
|
|
20
|
+
# This is best effort. It removes the dependence on `lstat`, and trusts `stat` in its place.
|
|
21
|
+
module FileTree
|
|
22
|
+
|
|
23
|
+
# Errors that clear on their own, such as a scanner or a sync client holding a file open.
|
|
24
|
+
# Any other error fails a copy at once.
|
|
25
|
+
TRANSIENT_ERRORS = %i[EACCES EBUSY EPERM EINTR].filter_map { |name| Errno.const_get( name ) if Errno.const_defined?( name ) }.freeze
|
|
26
|
+
|
|
27
|
+
Result = Struct.new( :copied, :unchanged, :healed, keyword_init: true )
|
|
28
|
+
|
|
29
|
+
# Yields each regular file below `root` as its path relative to `root` and its full path,
|
|
30
|
+
# sorted within each directory. Returns an Enumerator without a block. An entry whose name
|
|
31
|
+
# `skip` accepts is neither yielded nor entered.
|
|
32
|
+
def self.each_file(root, skip: nil)
|
|
33
|
+
return enum_for( :each_file, root, skip: skip ) unless block_given?
|
|
34
|
+
|
|
35
|
+
each_entry( root, skip: skip ) { |relative, path, directory| yield( relative, path ) unless directory }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Yields each directory and regular file below `root`, a directory ahead of its contents,
|
|
39
|
+
# as its relative path, its full path, and whether it is a directory
|
|
40
|
+
def self.each_entry(root, skip: nil, &block)
|
|
41
|
+
raise CeedlingException, "'#{root}' is not a directory" unless File.directory?( root )
|
|
42
|
+
|
|
43
|
+
walk( root, nil, [real_path( root )], skip, &block )
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Brings every file below `source` up to date below `destination` and returns a Result. A
|
|
47
|
+
# file that matches its source is left alone. One that differs or is missing is written by
|
|
48
|
+
# `publish`, a callable given the source and destination paths. A directory where a file
|
|
49
|
+
# belongs, or a file where a directory belongs, is removed first. Files `destination` has
|
|
50
|
+
# beyond `source` are left alone.
|
|
51
|
+
#
|
|
52
|
+
# `publish` is retried after each of `retry_delays` on an error in TRANSIENT_ERRORS.
|
|
53
|
+
# `log`, when given, receives a message, a Verbosity, and a LogLabels value.
|
|
54
|
+
def self.sync(source, destination, publish:, retry_delays: [], sleep: Kernel.method( :sleep ), log: nil)
|
|
55
|
+
return Sync.new( publish: publish, retry_delays: retry_delays, sleep: sleep, log: log ).tree( source, destination )
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# As .sync, for one regular file copied into `destination_dir`
|
|
59
|
+
def self.sync_file(source, destination_dir, publish:, retry_delays: [], sleep: Kernel.method( :sleep ), log: nil)
|
|
60
|
+
return Sync.new( publish: publish, retry_delays: retry_delays, sleep: sleep, log: log ).file( source, destination_dir )
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def self.walk(dir, relative, ancestors, skip, &block)
|
|
64
|
+
names_in( dir ).each do |name|
|
|
65
|
+
next if skip&.call( name )
|
|
66
|
+
|
|
67
|
+
path = File.join( dir, name )
|
|
68
|
+
entry = relative ? File.join( relative, name ) : name
|
|
69
|
+
next yield( entry, path, false ) unless directory_entry?( path )
|
|
70
|
+
|
|
71
|
+
yield( entry, path, true )
|
|
72
|
+
walk( path, entry, ancestors + [real_directory( path, ancestors )], skip, &block )
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def self.names_in(dir)
|
|
77
|
+
return Dir.children( dir ).sort
|
|
78
|
+
rescue SystemCallError => e
|
|
79
|
+
raise CeedlingException, "'#{dir}' could not be listed ⏩️ #{e.message}"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Whether a listed entry is a directory. Anything but a directory or a regular file raises.
|
|
83
|
+
def self.directory_entry?(path)
|
|
84
|
+
status = stat_of( path )
|
|
85
|
+
return true if status.directory?
|
|
86
|
+
return false if status.file?
|
|
87
|
+
|
|
88
|
+
raise CeedlingException, "'#{path}' is a #{status.ftype}, not a file or directory"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def self.stat_of(path)
|
|
92
|
+
return File.stat( path )
|
|
93
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
|
94
|
+
raise CeedlingException, "'#{path}' is a dangling or unresolvable link"
|
|
95
|
+
rescue SystemCallError => e
|
|
96
|
+
raise CeedlingException, "'#{path}' could not be read ⏩️ #{e.message}"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.real_directory(path, ancestors)
|
|
100
|
+
real = real_path( path )
|
|
101
|
+
raise CeedlingException, "Link loop: '#{path}' resolves to its own ancestor '#{real}'" if ancestors.include?( real )
|
|
102
|
+
|
|
103
|
+
return real
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# A directory's real path, for finding loops. A path that cannot be resolved stands for
|
|
107
|
+
# itself, so the walk goes on without that safeguard rather than stopping.
|
|
108
|
+
def self.real_path(path)
|
|
109
|
+
return File.realpath( path )
|
|
110
|
+
rescue SystemCallError
|
|
111
|
+
return File.expand_path( path )
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private_class_method :walk, :names_in, :directory_entry?, :stat_of, :real_directory, :real_path
|
|
115
|
+
|
|
116
|
+
# One copy's settings and tallies
|
|
117
|
+
class Sync
|
|
118
|
+
def initialize(publish:, retry_delays:, sleep:, log:)
|
|
119
|
+
@publish = publish
|
|
120
|
+
@retry_delays = retry_delays
|
|
121
|
+
@sleep = sleep
|
|
122
|
+
@log = log
|
|
123
|
+
@result = Result.new( copied: 0, unchanged: 0, healed: 0 )
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def tree(source, destination)
|
|
127
|
+
raise CeedlingException, "'#{source}' is not a directory" unless File.directory?( source )
|
|
128
|
+
|
|
129
|
+
directory( destination )
|
|
130
|
+
FileTree.each_entry( source ) do |relative, path, is_directory|
|
|
131
|
+
target = File.join( destination, relative )
|
|
132
|
+
is_directory ? directory( target ) : publish_file( path, target )
|
|
133
|
+
end
|
|
134
|
+
return @result
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def file(source, destination_dir)
|
|
138
|
+
raise CeedlingException, "'#{source}' is not a regular file" unless File.file?( source )
|
|
139
|
+
|
|
140
|
+
directory( destination_dir )
|
|
141
|
+
publish_file( source, File.join( destination_dir, File.basename( source ) ) )
|
|
142
|
+
return @result
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Destination checks follow links, as `stat` does, so they share the source side's view.
|
|
148
|
+
# A dangling link counts as a file.
|
|
149
|
+
def directory(path)
|
|
150
|
+
return if File.directory?( path )
|
|
151
|
+
|
|
152
|
+
with_retries( path ) do
|
|
153
|
+
heal( "Replaced a file where the directory '#{path}' belongs" ) { remove_file( path ) } if File.exist?( path ) || File.symlink?( path )
|
|
154
|
+
FileUtils.mkdir_p( path )
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Every step is retried, since a scanner or sync client can hold the destination too
|
|
159
|
+
def publish_file(source, destination)
|
|
160
|
+
with_retries( destination ) do
|
|
161
|
+
if File.directory?( destination )
|
|
162
|
+
heal( "Replaced a directory where the file '#{destination}' belongs" ) { remove_directory( destination ) }
|
|
163
|
+
elsif identical?( source, destination )
|
|
164
|
+
next @result.unchanged += 1
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
@publish.call( source, destination )
|
|
168
|
+
@result.copied += 1
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Counts and logs a heal the block reports it made
|
|
173
|
+
def heal(message)
|
|
174
|
+
return unless yield
|
|
175
|
+
|
|
176
|
+
@result.healed += 1
|
|
177
|
+
log( message, Verbosity::COMPLAIN, LogLabels::NOTICE )
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Whether this removed the file or link. An error removing it reaches the retries.
|
|
181
|
+
def remove_file(path)
|
|
182
|
+
File.unlink( path )
|
|
183
|
+
return true
|
|
184
|
+
rescue Errno::ENOENT
|
|
185
|
+
return false
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# Whether this removed the directory, or the link to one. Another run healing the same
|
|
189
|
+
# entry may have removed it already, or published a file in its place, which this run
|
|
190
|
+
# then publishes over. An error removing it reaches the retries.
|
|
191
|
+
def remove_directory(path)
|
|
192
|
+
return remove_file( path ) if File.symlink?( path )
|
|
193
|
+
|
|
194
|
+
Dir.rmdir( path )
|
|
195
|
+
return true
|
|
196
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
|
197
|
+
return false
|
|
198
|
+
rescue Errno::ENOTEMPTY, Errno::EEXIST
|
|
199
|
+
return remove_tree( path )
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# FileUtils.rm_r classifies by `lstat`, so it is reached only for a directory with
|
|
203
|
+
# contents. It works on the destination, and never follows a link out of it.
|
|
204
|
+
def remove_tree(path)
|
|
205
|
+
FileUtils.rm_r( path )
|
|
206
|
+
return true
|
|
207
|
+
rescue Errno::ENOENT
|
|
208
|
+
return false
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def identical?(source, destination)
|
|
212
|
+
return File.file?( destination ) && File.size( source ) == File.size( destination ) && FileUtils.compare_file( source, destination )
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def with_retries(destination)
|
|
216
|
+
delays = @retry_delays.dup
|
|
217
|
+
begin
|
|
218
|
+
yield
|
|
219
|
+
rescue *TRANSIENT_ERRORS => e
|
|
220
|
+
raise_exhausted( destination, e ) if delays.empty?
|
|
221
|
+
|
|
222
|
+
log( "Retrying copy to '#{destination}' after a transient error: #{e.message}", Verbosity::DEBUG, LogLabels::AUTO )
|
|
223
|
+
@sleep.call( delays.shift )
|
|
224
|
+
retry
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def raise_exhausted(destination, error)
|
|
229
|
+
message = "Failed to copy to '#{destination}' after #{@retry_delays.size} retries ⏩️ #{error.message}\n" \
|
|
230
|
+
"Try deleting this project's build/ directory and rebuilding -- or, if the error names a " \
|
|
231
|
+
"path under your Ruby gem install, reinstall the ceedling gem."
|
|
232
|
+
log( message, Verbosity::ERRORS, LogLabels::AUTO )
|
|
233
|
+
raise CeedlingException, message
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def log(message, verbosity, label)
|
|
237
|
+
@log&.call( message, verbosity, label )
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
end
|
|
@@ -17,6 +17,7 @@ require 'digest/sha2'
|
|
|
17
17
|
require 'ceedling/constants'
|
|
18
18
|
require 'ceedling/system_wrapper'
|
|
19
19
|
require 'ceedling/exceptions'
|
|
20
|
+
require 'ceedling/filesystem/file_tree'
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class FileWrapper
|
|
@@ -88,6 +89,13 @@ class FileWrapper
|
|
|
88
89
|
return File.basename(path)
|
|
89
90
|
end
|
|
90
91
|
|
|
92
|
+
# A file's status (size, modification time, ...), or nil when it can't be read
|
|
93
|
+
def stat(filepath)
|
|
94
|
+
return File.stat( filepath )
|
|
95
|
+
rescue SystemCallError
|
|
96
|
+
return nil
|
|
97
|
+
end
|
|
98
|
+
|
|
91
99
|
def exist?(filepath)
|
|
92
100
|
return true if (filepath == NULL_FILE_PATH)
|
|
93
101
|
return File.exist?(filepath)
|
|
@@ -118,6 +126,13 @@ class FileWrapper
|
|
|
118
126
|
return Dir.glob(glob, File::FNM_PATHNAME).sort()
|
|
119
127
|
end
|
|
120
128
|
|
|
129
|
+
# Every regular file below dir as a full path, sorted. Unlike a glob, the walk classifies
|
|
130
|
+
# entries by `stat`, follows links, and reads names without pattern syntax (see FileTree).
|
|
131
|
+
# An entry whose name `skip` accepts is neither listed nor entered.
|
|
132
|
+
def tree_files(dir, skip: nil)
|
|
133
|
+
return FileTree.each_file( dir, skip: skip ).map { |_relative, path| path }.sort
|
|
134
|
+
end
|
|
135
|
+
|
|
121
136
|
# Entry names within a single directory, `.` and `..` excluded.
|
|
122
137
|
#
|
|
123
138
|
# Complements `directory_listing()` for the case a glob cannot serve: matching a
|
|
@@ -238,34 +253,35 @@ class FileWrapper
|
|
|
238
253
|
false
|
|
239
254
|
end
|
|
240
255
|
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
256
|
+
# Brings every file beneath source_dir up to date beneath destination_dir (see FileTree.sync).
|
|
257
|
+
# A file that already matches its source is not written, so a reader elsewhere never sees it
|
|
258
|
+
# change. One that differs or is missing is replaced whole (see #replace_file). An entry of
|
|
259
|
+
# the wrong type is replaced. Files the destination has beyond the source are left alone.
|
|
260
|
+
#
|
|
261
|
+
# A write is retried on an error that clears on its own, such as another process holding a
|
|
262
|
+
# file open on Windows. One that never clears surfaces as a CeedlingException naming the file.
|
|
263
|
+
def sync_tree(source_dir, destination_dir)
|
|
264
|
+
FileTree.sync( source_dir, destination_dir, **sync_options )
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# As #sync_tree, for a single file copied into destination_dir
|
|
268
|
+
def sync_file(source, destination_dir)
|
|
269
|
+
FileTree.sync_file( source, destination_dir, **sync_options )
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Copies source to a temporary name beside destination, then renames it over destination.
|
|
273
|
+
# A concurrent reader sees the old file or the new one, never a partial one. The copy is
|
|
274
|
+
# owner-writable, and so is a destination file it replaces, since Windows refuses to rename
|
|
275
|
+
# over a read-only file. A destination link is replaced, and the file it names left alone.
|
|
276
|
+
def replace_file(source, destination)
|
|
277
|
+
temp = File.join( File.dirname( destination ), ".#{File.basename( destination )}.#{Process.pid}.#{Thread.current.object_id}.tmp" )
|
|
278
|
+
check_path_length( temp, origin: 'FileWrapper#replace_file' )
|
|
279
|
+
FileUtils.cp( source, temp )
|
|
280
|
+
owner_writable( temp )
|
|
281
|
+
owner_writable( destination ) if File.file?( destination ) && !File.symlink?( destination )
|
|
282
|
+
File.rename( temp, destination )
|
|
283
|
+
ensure
|
|
284
|
+
FileUtils.rm_f( temp ) if temp
|
|
269
285
|
end
|
|
270
286
|
|
|
271
287
|
def read(filepath, length=nil)
|
|
@@ -330,6 +346,13 @@ class FileWrapper
|
|
|
330
346
|
return FileUtils.mkdir_p(folder)
|
|
331
347
|
end
|
|
332
348
|
|
|
349
|
+
# Makes the directory a file is about to be written in and returns the filepath. Safe
|
|
350
|
+
# when build threads make the same directory at once.
|
|
351
|
+
def mkdir_for(filepath)
|
|
352
|
+
mkdir( File.dirname( filepath ) )
|
|
353
|
+
return filepath
|
|
354
|
+
end
|
|
355
|
+
|
|
333
356
|
# Creates a uniquely-named, empty directory nested inside `parent` and returns its path.
|
|
334
357
|
# `parent` must already exist. Collision-free even across concurrent callers targeting
|
|
335
358
|
# the same `parent` -- Dir.mktmpdir retries internally on name clash.
|
|
@@ -371,6 +394,20 @@ class FileWrapper
|
|
|
371
394
|
|
|
372
395
|
private
|
|
373
396
|
|
|
397
|
+
def sync_options
|
|
398
|
+
return {
|
|
399
|
+
publish: method( :replace_file ),
|
|
400
|
+
retry_delays: TRANSIENT_IO_RETRY_DELAYS,
|
|
401
|
+
sleep: ->( delay ) { sleep( delay ) },
|
|
402
|
+
log: ->( message, verbosity, label ) { @loginator.log( message, verbosity, label ) }
|
|
403
|
+
}
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
def owner_writable(path)
|
|
407
|
+
mode = File.stat( path ).mode & 0o7777
|
|
408
|
+
File.chmod( mode | 0o200, path ) if (mode & 0o200).zero?
|
|
409
|
+
end
|
|
410
|
+
|
|
374
411
|
def path_length_limit
|
|
375
412
|
return PATH_LENGTH_LIMITS[:windows] if SystemWrapper.windows?
|
|
376
413
|
return PATH_LENGTH_LIMITS[:macos] if SystemWrapper.macos?
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# =========================================================================
|
|
2
|
+
# Ceedling - Test-Centered Build System for C
|
|
3
|
+
# ThrowTheSwitch.org
|
|
4
|
+
# Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
|
|
5
|
+
# SPDX-License-Identifier: MIT
|
|
6
|
+
# =========================================================================
|
|
7
|
+
|
|
8
|
+
require 'set'
|
|
9
|
+
require 'ceedling/filesystem/path_spellings'
|
|
10
|
+
|
|
11
|
+
# What collection leaves out, each a set of spellings: `-:` entries' places, outweighed by
|
|
12
|
+
# an entry based nearer; excluded places, outweighed by an entry based no farther away; and
|
|
13
|
+
# reserved places, never collected
|
|
14
|
+
PathExclusions = Struct.new( :minus, :excluded, :reserved ) do
|
|
15
|
+
def self.of(minus, excluded, reserved)
|
|
16
|
+
return new( *[minus, excluded, reserved].map { |paths| PathSpellings.of( paths ) } )
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def none?
|
|
20
|
+
return [minus, excluded, reserved].all?( &:empty? )
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Whether a place, by `spellings` of it and `distance` below the base of the entry naming
|
|
24
|
+
# it, is collected
|
|
25
|
+
def keeps?(spellings, distance)
|
|
26
|
+
return distance < PathSpellings.nearer( spellings, minus ) &&
|
|
27
|
+
distance <= PathSpellings.nearer( spellings, excluded ) &&
|
|
28
|
+
PathSpellings.nearer( spellings, reserved ) == Float::INFINITY
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -88,37 +88,40 @@ class PathMatcher
|
|
|
88
88
|
query_segments = segments(query)
|
|
89
89
|
return query unless query_segments.include?('..')
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
raise CeedlingException.new(
|
|
93
|
-
"Relative path reference '..' in '#{query}' has no file context to resolve against here."
|
|
94
|
-
)
|
|
95
|
-
end
|
|
91
|
+
require_anchor!(query, anchor)
|
|
96
92
|
|
|
97
93
|
# segments() strips the empty string a leading "/" splits off, so a Unix
|
|
98
94
|
# absolute anchor's own "/"-ness has to be remembered separately here and
|
|
99
95
|
# reapplied below -- otherwise the result quietly turns into a relative-looking
|
|
100
96
|
# path carrying the same text minus its leading "/".
|
|
101
|
-
|
|
97
|
+
absolute = anchor.start_with?('/')
|
|
102
98
|
resolved = segments(anchor)
|
|
103
|
-
|
|
104
|
-
# A `..` beyond a relative anchor's own segments names a directory above it, as a
|
|
105
|
-
# project's paths outside its own root do (`../shared`), so it stays in the result.
|
|
106
|
-
# Nothing lies above an absolute anchor's root.
|
|
107
|
-
query_segments.each do |segment|
|
|
108
|
-
if segment != '..'
|
|
109
|
-
resolved << segment
|
|
110
|
-
elsif !resolved.empty? and resolved.last != '..'
|
|
111
|
-
resolved.pop
|
|
112
|
-
elsif anchor_was_absolute
|
|
113
|
-
raise CeedlingException.new("Relative path reference '..' in '#{query}' climbs above the filesystem root.")
|
|
114
|
-
else
|
|
115
|
-
resolved << '..'
|
|
116
|
-
end
|
|
117
|
-
end
|
|
99
|
+
query_segments.each { |segment| step(resolved, segment, query, absolute) }
|
|
118
100
|
|
|
119
101
|
joined = resolved.join('/')
|
|
120
|
-
|
|
102
|
+
absolute ? "/#{joined}" : joined
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.require_anchor!(query, anchor)
|
|
106
|
+
return unless anchor.nil?
|
|
107
|
+
|
|
108
|
+
raise CeedlingException.new( "Relative path reference '..' in '#{query}' has no file context to resolve against here." )
|
|
109
|
+
end
|
|
110
|
+
private_class_method :require_anchor!
|
|
111
|
+
|
|
112
|
+
# Applies one query segment to the segments resolved so far. A `..` beyond a relative
|
|
113
|
+
# anchor's own segments names a directory above it, as a project's paths outside its own
|
|
114
|
+
# root do (`../shared`), so it stays in the result. Nothing lies above an absolute
|
|
115
|
+
# anchor's root.
|
|
116
|
+
def self.step(resolved, segment, query, absolute)
|
|
117
|
+
return resolved << segment if segment != '..'
|
|
118
|
+
return resolved.pop if !resolved.empty? and resolved.last != '..'
|
|
119
|
+
|
|
120
|
+
raise CeedlingException.new("Relative path reference '..' in '#{query}' climbs above the filesystem root.") if absolute
|
|
121
|
+
|
|
122
|
+
resolved << '..'
|
|
121
123
|
end
|
|
124
|
+
private_class_method :step
|
|
122
125
|
|
|
123
126
|
# Two filepaths correspond if the shorter one's path segments equal the longer
|
|
124
127
|
# one's own trailing segments, exactly, in order -- checked without regard for
|
|
@@ -138,8 +141,9 @@ class PathMatcher
|
|
|
138
141
|
|
|
139
142
|
### Private ###
|
|
140
143
|
|
|
144
|
+
# A `.` segment names nothing, so it is dropped with the empty ones
|
|
141
145
|
def self.segments(path)
|
|
142
|
-
path.split(/[\\\/]/).reject
|
|
146
|
+
path.split(/[\\\/]/).reject { |segment| segment.empty? || segment == '.' }
|
|
143
147
|
end
|
|
144
148
|
private_class_method :segments
|
|
145
149
|
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT
|
|
6
6
|
# =========================================================================
|
|
7
7
|
|
|
8
|
-
require 'ceedling/file_path_utils'
|
|
8
|
+
require 'ceedling/filesystem/file_path_utils'
|
|
9
|
+
require 'ceedling/filesystem/project_paths'
|
|
9
10
|
|
|
10
11
|
# Answers the question build output asks when it wants to avoid same-basename
|
|
11
12
|
# collisions: given a resolved file and the raw (still possibly glob/decorator-laden)
|
|
@@ -23,12 +24,13 @@ class PathMirror
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
# Strips decorators and glob specifiers from each entry in `roots`, dropping any that
|
|
26
|
-
# end up empty. A caller mirroring many files against the same `roots` list -- a whole
|
|
27
|
+
# end up empty and every `-:` exclusion, which names no place a file lives below. A caller mirroring many files against the same `roots` list -- a whole
|
|
27
28
|
# build's worth of sources against one project's configured paths, say -- can compute
|
|
28
29
|
# this once and feed the result to `relative_subdir_from_clean_roots` for every file,
|
|
29
30
|
# rather than every file's own call re-parsing the same still-decorated list from scratch.
|
|
30
31
|
def self.clean_roots(roots)
|
|
31
32
|
roots
|
|
33
|
+
.select { |root| FilePathUtils.add_path?(root) }
|
|
32
34
|
.map { |root| FilePathUtils.no_decorators(root) }
|
|
33
35
|
.reject(&:empty?)
|
|
34
36
|
end
|
|
@@ -38,12 +40,13 @@ class PathMirror
|
|
|
38
40
|
# fallback for placing build output but hides the difference from a caller deciding
|
|
39
41
|
# whether a path belongs to a configured tree at all.
|
|
40
42
|
def self.relative_subdir_if_rooted(filepath, roots)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return nil unless clean.any? { |root| file_dir == root || file_dir.start_with?( root + '/' ) }
|
|
43
|
+
return relative_subdir_if_rooted_in_clean_roots( filepath, clean_roots( roots ) )
|
|
44
|
+
end
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
# As `relative_subdir_if_rooted`, but takes roots already reduced by `clean_roots`, or
|
|
47
|
+
# canonical roots such as configuration's mirror roots
|
|
48
|
+
def self.relative_subdir_if_rooted_in_clean_roots(filepath, clean_roots)
|
|
49
|
+
return containing_subdir( File.dirname( filepath ), clean_roots )
|
|
47
50
|
end
|
|
48
51
|
|
|
49
52
|
# A path as lowercased segments, split on either separator. One spelling for every caller
|
|
@@ -56,18 +59,16 @@ class PathMirror
|
|
|
56
59
|
# As `relative_subdir`, but takes roots already reduced by `clean_roots` instead of
|
|
57
60
|
# raw, possibly glob/decorator-laden `:paths` entries.
|
|
58
61
|
def self.relative_subdir_from_clean_roots(filepath, clean_roots)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
matching_roots = clean_roots.select do |root|
|
|
62
|
-
file_dir == root || file_dir.start_with?(root + '/')
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
return '' if matching_roots.empty?
|
|
66
|
-
|
|
67
|
-
best_root = matching_roots.max_by(&:length)
|
|
68
|
-
return '' if file_dir == best_root
|
|
62
|
+
return containing_subdir( File.dirname( filepath ), clean_roots ) || ''
|
|
63
|
+
end
|
|
69
64
|
|
|
70
|
-
|
|
65
|
+
# The directories from the most specific root truly containing `dir` down to it, or nil
|
|
66
|
+
# when no root does. Roots and `dir` are compared as places, however each is spelled, so
|
|
67
|
+
# the result never climbs out of its root.
|
|
68
|
+
def self.containing_subdir(dir, clean_roots)
|
|
69
|
+
subdirs = clean_roots.map { |root| ProjectPaths.subdir_below( root, dir ) }.compact
|
|
70
|
+
return subdirs.min_by( &:length )
|
|
71
71
|
end
|
|
72
|
+
private_class_method :containing_subdir
|
|
72
73
|
|
|
73
74
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# =========================================================================
|
|
2
|
+
# Ceedling - Test-Centered Build System for C
|
|
3
|
+
# ThrowTheSwitch.org
|
|
4
|
+
# Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
|
|
5
|
+
# SPDX-License-Identifier: MIT
|
|
6
|
+
# =========================================================================
|
|
7
|
+
|
|
8
|
+
require 'set'
|
|
9
|
+
require 'ceedling/filesystem/project_paths'
|
|
10
|
+
|
|
11
|
+
# A place reached through a symlink has two spellings: as listed and as it really is.
|
|
12
|
+
# Collection compares places by both, and measures how far one lies below another.
|
|
13
|
+
module PathSpellings
|
|
14
|
+
|
|
15
|
+
# A path as compared: Windows compares paths without regard to case
|
|
16
|
+
def self.key(path)
|
|
17
|
+
return ProjectPaths::WINDOWS ? path.downcase : path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Both spellings of each of `paths`, as compared, so either reaches it
|
|
21
|
+
def self.of(paths)
|
|
22
|
+
return Set.new( paths.flat_map { |path| [ProjectPaths.absolute( path ), ProjectPaths.real_absolute( path )] }.map { |path| key( path ) } )
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# How many directories above a place, by any of `spellings` of it, the nearest one in
|
|
26
|
+
# `spelled` lies: 0 for the place itself, or infinity for none
|
|
27
|
+
def self.nearer(spellings, spelled)
|
|
28
|
+
return Float::INFINITY if spelled.empty?
|
|
29
|
+
|
|
30
|
+
return spellings.map { |path| distance( path, spelled ) }.min
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# `paths`, found below `base`, respelled below the base's real path
|
|
34
|
+
def self.through_real(paths, base)
|
|
35
|
+
real = ProjectPaths.real_absolute( base )
|
|
36
|
+
return paths if base == real
|
|
37
|
+
|
|
38
|
+
return paths.map { |path| ProjectPaths.same?( path, base ) || ProjectPaths.starts_with?( path, "#{base}/" ) ? real + path[base.length..] : path }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.distance(path, spelled)
|
|
42
|
+
steps = 0
|
|
43
|
+
loop do
|
|
44
|
+
return steps if spelled.include?( key( path ) )
|
|
45
|
+
|
|
46
|
+
parent = File.dirname( path )
|
|
47
|
+
return Float::INFINITY if parent == path
|
|
48
|
+
|
|
49
|
+
path = parent
|
|
50
|
+
steps += 1
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
private_class_method :distance
|
|
54
|
+
|
|
55
|
+
end
|