Repository navigation
Spell paths once and place build files by source path (#1370) - #1378
Merged
Merged
Conversation
A scenario harness runs a project's paths through configuration, collection, build-path mapping, and the lookups inverting it, without compiling. Integration, unit, and system probes record each gap the path handling audit around #1370 found, as pending examples naming the gap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ProjectPaths gives a place one spelling and compares paths by directory. BuildSubpath encodes a path as a build subpath from the project root (`_up_`, `_abs_`, `_unc_`, and `_ceedling_` for the running installation), with an exact inverse. Test names use it. A `.` segment no longer stops a path matching, and a task name with a leading `..` names a test outside the project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PathMatcher.resolve_relative splits its anchor check and its per-segment step into their own methods, retiring the file's backlog entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PathMirror compared a file's directory with each root by text, so `..` contained any path climbing past it, and a mock or preprocessed header of a header far outside the project landed outside the build directory (#1370). Roots and directories now compare as places, however each is spelled, and a `-:` exclusion is never a root. A `./` or absolute root now mirrors as its plain spelling does, and finds a header's source beside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A path spelled through a symlink was collected as a second file, a `-:` exclusion dropped only the directory it named, and a root above the build root collected the build tree, vendored framework headers included. Each entry now resolves its base to the place it really is, exclusions drop every directory below them, configuration leaves the build root out, and `~` reads as the home directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mocks, a header's source by convention, and a Partial's module were each found below the raw :paths entries, so a glob in the middle of an entry (`components/*/include`) or a symlinked spelling found nothing. Configuration now computes the roots once, each canonical, a glob-middle entry rooting at each directory it names, and a Partial's directory drops its `.` segments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A TEST_INCLUDE_PATH() spelled with `./`, backslashes, or absolutely gave its headers a second spelling beside the collection's, and never ranked ahead of other search paths, so a same-named header elsewhere won. Each path is now recorded canonically, and headers rank by the search path that truly holds them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Objects, preprocessed files and dependency files sit below their build root at their source's own path from the project root, as test names already do. A path above the project encodes `..` as `_up_`, another drive as `_abs_` or `_unc_`, and a file of the running installation outside the project as `_ceedling_`. Two sources never share a build file, and none lands outside the build directory. Files below a test's generated roots (mocks, Partials) and below the build root (runners, vendored frameworks) keep their placement. Two sources that would still build to one file stop the build with an error naming both. A release build's link-only object names are placed apart from its sources, so CException's joins its compiled object. Lookups invert the placement exactly: an object's place decodes to its source's path, matched against the collection by absolute path before any tail match, and a test's name is the longest known name its object's directory begins with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No caller passes it, and none of these paths depends on a build context. Retires file_path_utils.rb from the Lint/UnusedMethodArgument backlog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A gcov or Bullseye untested source compiles outside any test, so its object and dependencies file now sit at the source's path below the context's output root, and the plugin makes those directories itself. Two same-named untested sources no longer share one object. FilePathUtils#form_test_object_filepath places an object exactly where a test's build does, so gcov's console summary searches the directory holding a source's coverage data rather than re-deriving it from the source roots. A dependencies file follows its object by containment, with or without a test. A dependency built by :build_lib places each object at its source's path below the dependency's source path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MockLocator#placement gives a mock's directory and the folder and root CMock writes it by. Mock generation, the planner's mock sources, the stand-ins and the mock search paths all use it, so a mock whose #include spells more path than its mirror is compiled, stood in for and searched where it is written. A folder that climbs stays below the test's mock root, and Generator#generate_mock takes CMock's root explicitly. The compiler resolves a test's #include of a mock as written, from the test file's own directory, where no generated mock can be, so a mock #include that climbs with `..` never compiled. It now stops the build with an error naming the test file and a spelling that works. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
:cmock ↳ :unity_helper_path names a helper's header, or its path without an extension. The test build compiled that path itself, a header beside the helper's source, which now collides with the source's own object. It compiles the source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker's -L arguments were built from :paths ↳ :libraries as written, so a decorated or globbed entry reached it verbatim. They now come from LibraryResolver#search_path_arguments: the directories those paths name, decorated and globbed as configured, with those a plugin adds, collected once at the first link. Test and release links share it, as library file resolution already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ConfiguratorPathsValidator takes the :paths and :files entry checks and the checks of paths named singly from ConfiguratorValidator, which keeps the checks of required entries, tools and matchers. Behavior is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clobber empties the build root's test and release trees, so a build root of `.` deleted the project's own tests. Configuration now refuses a build root that names no directory, is the project directory or a directory above it, or holds the base of a :paths or :files entry that adds to a build, naming the reason. A `-:` entry names what a build leaves out, so naming nothing is no longer a validation error. Below the build root, where nothing exists before a first build, it passes silently, and elsewhere with a warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bin/ handles some paths before the configuration pipeline runs: the project file, mixins, the log file and directory, plugin load paths and the WHICH_CEEDLING hand-off. PathValidator#standardize_paths now spells them through ProjectPaths.standardize, so `~` names the home directory and a trailing separator drops, as everywhere else. The log directory follows the build root after inline Ruby string expansion, and bin/ expands and standardizes :plugins ↳ :load_paths through the same RakefileComponentResolver helper the application uses, so both find plugins in the same places. PathValidator#validate, MixinResolvinator#extract_mixins and #validate_mixins, and RakefileComponentResolver#plugin_rake_files split into smaller steps, retiring their backlog entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Setupinator#do_setup becomes a short list of named steps, each holding what one numbered stage of setup did. The Configurator steps that fill out the configuration run from an ordered list, as configuration's own path collections and validations do. Behavior is unchanged, and setupinator.rb leaves the AbcSize and MethodLength backlog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each :cmock ↳ :unity_helper_path header is included in every mock by its filename, which was taken before paths were standardized, so a helper written with backslashes was included by its whole path. The filename is now taken after standardizing, as a setup step of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An absolute or `~` :release_build ↳ :output was joined below the release build root. It now names the release itself, with its map file beside it, and linking makes the release's directory. The Dependencies plugin reads its configured :paths as every configured path is read, keeps an artifact or include named by an absolute path as written, and writes each object's list file beside the object rather than in the working directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A project named by --project from another directory reads its relative paths against the working directory, not the project file's directory. A system spec now builds such a project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FileFinderHelper#find_best_path_in_collection, which matches a module generator path hint to a configured path, scores each path, picks the first highest score, and complains through small steps of their own. New specs pin its behavior, unchanged, and file_finder_helper.rb leaves the Metrics backlog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DestinationCollision finds the outputs more than one input maps to and stops the build naming them, for two tests sharing a build name and two sources sharing an object alike, with one message shape and fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FilenameExtension#pattern matches any one of a type's extensions, escaped, as one group. Release library sorting, the release compile and assemble rules, gcovr's exclusions and Bullseye's source filtering use it rather than each building the alternation by hand. sort_objects_and_libraries sheds its Metrics offenses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build compares the same few roots with every file, and spelling each absolutely on every comparison made a no-change build of temp_sensor measurably slower than master's. ProjectPaths.absolute now remembers each spelling per working directory and home directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A build root spelled through a symlink, such as macOS's /tmp for /private/tmp, is compared as the place it really is, so it cannot pass for a directory of its own when it is the project's. - A build root that expands to nothing stops bin/ with the refusal configuration gives, rather than placing logs at the filesystem root. - A bin/ path of only whitespace stays as written, so a destination never becomes the filesystem root. - A `-:` subdirectories glob of a directory that does not exist warns as any other `-:` entry naming nothing does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The refusal of a mock #include that climbs with `..` fired for one in a branch that preprocessing drops, and for a spelling the mock search paths reach, both of which build on master. It is gone; such an #include is anchored to the test file's directory as any other. MockLocator#placement now also gives the directory a mock's #include as written reaches it from, which joins the mock search paths whether or not CMock is given the folder, and compares the folder with the header's mirrored subdirectory alone, never the mock root that carries the test's name. The planner keys mocks by the directory they are written to, so two same-named mocks whose #include folders alone differ both build, and each stand-in includes its header by the folder its real mock will. A test without a mock root places no stand-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A test that #includes a module's source as well as its header must not link that source's object a second time. The object left out was formed from the #include as written, which no longer matched the object's place at the source's own path, so the link found two definitions. Each such #include now resolves to the file it names first. One file named by two spellings, such as a vendored CException source below a `./build` build root, or a `./`-prefixed Unity helper beside the support files that collect it, is one source, never a collision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A project directory named `_up_`, `_abs_`, `_unc_` or `_ceedling_` encoded exactly as the marker for a path outside the project, so its object read back as a different source, or none. Such a directory now gains a leading `_` when encoded, and loses it when decoded. A file of the running Ceedling installation on another drive also places below `_ceedling_`, and each object's whole path is checked against the platform's path length limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The core review found one place still reached by two spellings: - A symlinked directory below a collected root was listed by its link while a `-:` entry naming it was spelled through its target, so the exclusion missed it and two entries could collect it twice. Every collected directory is now spelled canonically, and containment is judged on both spellings. A build root written through a symlink is excluded the same way, and :files revisions compare each file through its real directory. - A `-:` entry now leaves out the directories below it unless an additive entry names one nearer, as `+:src/legacy/keep` beside `-:src/legacy` does. Exclusion is a walk up each directory, not a comparison with every excluded one, so a large tree collects in master's time again. - ProjectPaths reads a `~user` path as a directory of that name, keeps a UNC share's `..` below the share, treats a doubled leading `/` as the root outside Windows, reads `~` after a decorator and a space, and drops every trailing separator. Clean absolute paths skip respelling. - Mirror roots read a literal bracket beside a glob (#104) and root `src/**/**` at `src`; ReportGenerator's source directories collapse by containment; header ranking walks up each header's directories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A second review pass found that spelling every collected directory as it really is changed what a project sees: an #include through a symlinked subdirectory below a :paths base no longer resolved, and a symlinked test directory renamed its tests. Collection again spells each place through its entry's base as the base really is, as before, and compares and deduplicates places by their real spelling, now kept in PathSpellings. - Each directory is kept or left out by the entries that name it, so a middle-wildcard entry or an explicit directory no longer shields directories other entries name. - A directory named like a placement marker in any case is escaped, since macOS and Windows file systems fold case. - The shortcut for a clean absolute path spells `/a/` and padded paths as the full computation does, and on Windows leaves short `~` names and trailing dots and spaces to File.expand_path. - Each object's path length is checked once, and a `-:` glob matching nothing below a directory that exists warns. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
file_path_utils.rb no longer has AbcSize or CyclomaticComplexity offenses, nor release_build_executor.rb CyclomaticComplexity or PerceivedComplexity ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A :files revision such as `+:**/*.c` matched the build root's mocks, runners and vendored Unity and CMock sources. A release build then compiled them and failed on a missing cmock.h, even right after clobber. A :files revision now adds no file below the build root unless its own base lies there or it names the file exactly, as :paths collection already leaves the build root out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A build root holding a :paths or :files entry was refused, so generated code configured in build/gen could not build. Only an empty build root, the project directory, or a directory above it is refused now, since clobber would empty the project's own files. A :paths or :files entry within the build root collects with a warning instead. No entry collects from the directories Ceedling creates: the build root's artifacts, test, release, vendor and cache directories, the log and mock directories, and each enabled plugin's build directory. An entry based above the build root still collects nothing from it, and one based at the build root collects what the build root holds besides. PathExclusions holds the three kinds of exclusion collection applies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A :files glob such as `+:**/*.c` added test files to the source collection and the release build input, and a source root holding the test root added them to the release build input. The release then compiled them and stopped. Test files are now filtered out after :files revisions, and out of the release build input however they reach it. Only the source collection warns of the overlap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Changelog, Breaking Changes, Known Issues, and Release Notes cover the path handling fixes and the behavior they change. The configuration reference describes `~`, `-:` recursion, build root collection and its warning, and build files placed by source path. PATH_HANDLING.md explains the path modules, build subpath markers, and platform differences for developers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI summary · e392802 · ✅ 23/23 jobs passed · nearest baseline 4f6f1b6 (master)
Diff coverage: 98.92% (735/743 changed lines). Uncovered: All legs (examples / failures / pending)
Jobs
|
A project inside a Ceedling checkout, as the system specs' projects are, had its sibling sources placed below `_ceedling_`, since they lay below the installation root. The subpath carried the checkout's whole path to the project, and Windows refused gcov's paths past 260 characters. Only the installation's assets, bin, lib, plugins, and vendor directories place below `_ceedling_` now. Other files beside the project place by `_up_` as any sibling does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A project directory took the example's description, up to 60 characters, and its temp dir Dir.mktmpdir's 22. On Windows CI the deepest build file of a header_include_path_validation project reached 272 characters, past the 260 limit. A project is now named by its prefix and a digest of the full description, and its temp dir by 7 characters. That path drops to about 205. Failure reports print the project name beside the example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundler evaluates the gemspec on every `bundle exec`, and Dir['**/*'] walked specout/ before filtering it out. A retained system test project nested past Windows' path limit made the glob raise, and every later example failed. The walk now skips build/, site-web/, specout/ and tools/ at the top level. The packaged files are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1370.
Problem
A mocked header found below a
:pathsroot such as../**, beside headers far outside the project (C:/Keil_v5/…), had its mock written outsidebuild/. The build then stopped with "Found no file … in search paths". It is a 1.2.0 regression, and it reproduces on Linux and macOS too.The cause was wider than mocks. Ceedling held one place in three spellings: relative to the working directory, as written in
project.yml, and absolute. Code compared them as text, and asked whether a path's text started with a root's. So..appeared to contain../../../Keil_v5/…, and./src/**failed to matchsrc/**. An audit with probe specs confirmed 21 related gaps, one of them destructive::build_root: .letclobberdelete the project's owntest/.Changes
One spelling, containment by directory. New
ProjectPathsspells each configured path once: standardized,~expanded, symlinks resolved at the entry's base, relative to the working directory.contains?andsubdir_belowcompare whole directories.PathMirror, collection, lookups, and validation all use them.bin/shares the module.Build files placed by their source's path from the project root. New
BuildSubpathencodes a directory so it never climbs or goes absolute...becomes_up_, another Windows drive goes under_abs_, a UNC share under_unc_, and files of the running Ceedling installation outside the project under_ceedling_. A directory named like a marker is escaped, sodecodeinvertsencodeexactly.FilePathUtils#form_build_subpathis the one placement rule for objects, preprocessed files, dependencies files, and plugin objects.DestinationCollisionstops a build when two sources would build to one file, as it already did for test names.Lookups invert placement.
FileFinder#mirrored_querydecodes an object's place and matches the collection exactly, through an index per collection. Name matching remains only as the fallback.Mocks stay include-root-relative, now with true containment. A header outside every root mocks flat into the test's mock directory, which fixes #1370. A climbing
#includefolder places its mock below a_up_anchor inside the test's mock directory.Collection.
-:removes the directories below the one it names, unless an additive entry names one more closely. A root above the build root (../**,./**,+:**/*.c) collects nothing from it. The directories Ceedling and its enabled plugins create are never collected. Test files are kept out of the source collection after:filesrevisions, and out of the release build input. Each place is collected once however many entries or spellings reach it.Build root. An empty build root, the project directory, or a directory above it is refused. A
:pathsor:filesentry within the build root collects with a warning.Other gaps.
~expands everywhere a path is configured.TEST_INCLUDE_PATH()ranks by containment.-Lnames the directories:paths↳:librariescollects.test:tasks accept./and a leading... A-:entry naming nothing warns instead of failing validation. The Unity helper compiles its.cand is read after standardizing. An absolute:release_build↳:outputnames the artifact itself. The Dependencies plugin keeps absolute paths absolute and places objects by source path. The launcher's log path and plugin load paths read Ruby expansion and backslashes.lib/ceedling/PATH_HANDLING.mddocuments the design for developers and agents.Compatibility
These change behavior a project might rely on, and each has a Breaking Changes entry:
-:in:pathsremoves the directories below the one it names.:build_root: .,.., or an empty value is refused.-Larguments name collected directories, not entries as written.ConfiguratorValidator's path methods moved toConfiguratorPathsValidator, without shims.Docs
Changelog (with a Path handling subsection), Breaking Changes, Known Issues for 1.2.0 and 1.2.1, Release Notes, and the
paths,files,project,cmock,libraries,release-build, Which Ceedling, Dependencies, gcov, and conventions references.Verification
madsciencelab-plugins:1.2.1: 406 examples, 0 failures, 28 environment-gated pending.pending:removed, across all three tiers. New specs cover each gap, including Windows drive and UNC examples gated to Windows.test:alloftemp_sensormatches master (about 3.4 s).Windows CI is the first run of the drive-letter, case-folding, and UNC specs on Windows, so it deserves a close look.
🤖 Generated with Claude Code