This is an automated email from the ASF dual-hosted git repository. kasjer pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git
commit ebb4bb9702c65f2c0dc766d7e72b9adfd8d0f3f3 Author: Szymon Janc <[email protected]> AuthorDate: Thu Oct 22 16:45:06 2026 +0200 newt/toolchain: Add tests for dependency files generated by compiler Check with a fake compiler that the dependency file written during compilation is used to decide whether a later compile is required, that an object file without a dependency file is rebuilt, and that dependency options are left out of compile_commands.json. Assisted-by: Claude:claude-opus-5-5 --- newt/toolchain/deps_test.go | 160 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/newt/toolchain/deps_test.go b/newt/toolchain/deps_test.go index eb0008c8..e72447d2 100644 --- a/newt/toolchain/deps_test.go +++ b/newt/toolchain/deps_test.go @@ -23,7 +23,9 @@ import ( "fmt" "os" "path/filepath" + "runtime" "slices" + "strings" "sync" "testing" "time" @@ -79,6 +81,164 @@ func TestCompilerConcurrentUpdates(t *testing.T) { } } +// The dependency file must be generated by the compile itself and used to +// decide whether a later compile is required. +func TestCompileGeneratesDepsFile(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("uses a shell script as a fake compiler") + } + + dir := t.TempDir() + t.Chdir(dir) + + // Fake compiler writing the object and dependency files like gcc -MMD. + cc := filepath.Join(dir, "fake-cc") + script := "#!/bin/sh\n" + + "while [ $# -gt 0 ]; do\n" + + " case \"$1\" in\n" + + " -MF) dep=\"$2\"; shift;;\n" + + " -o) obj=\"$2\"; shift;;\n" + + " -*) ;;\n" + + " *) src=\"$1\";;\n" + + " esac\n" + + " shift\n" + + "done\n" + + "printf '%s: %s src/a.h\\n' \"$obj\" \"$src\" > \"$dep\"\n" + + ": > \"$obj\"\n" + if err := os.WriteFile(cc, []byte(script), 0755); err != nil { + t.Fatal(err) + } + + c := newTestCompiler() + c.ccPath = cc + c.baseDir = dir + c.dstDir = "bin" + src := dir + "/src/a.c" + hdr := dir + "/src/a.h" + + if err := os.MkdirAll(filepath.Dir(src), 0755); err != nil { + t.Fatal(err) + } + for _, f := range []string{src, hdr} { + if err := os.WriteFile(f, nil, 0644); err != nil { + t.Fatal(err) + } + } + old := time.Now().Add(-time.Hour) + for _, f := range []string{src, hdr} { + if err := os.Chtimes(f, old, old); err != nil { + t.Fatal(err) + } + } + + required, err := c.depTracker.CompileRequired(src, COMPILER_TYPE_C) + if err != nil { + t.Fatal(err) + } + if !required { + t.Fatalf("compile not required for new object file") + } + + if err := c.CompileFile(src, COMPILER_TYPE_C); err != nil { + t.Fatal(err) + } + + deps, err := ParseDepsFile(c.dstFilePath(src) + ".d") + if err != nil { + t.Fatal(err) + } + exp := []string{"src/a.c", "src/a.h"} + if !slices.Equal(deps, exp) { + t.Errorf("unexpected dependencies: expected %q, got %q", exp, deps) + } + + for _, cmd := range c.GetCompileCommands() { + if strings.Contains(cmd.Command, "-MMD") || + strings.Contains(cmd.Command, "-MF") { + + t.Errorf("dependency options in compile command: %s", + cmd.Command) + } + } + + required, err = c.depTracker.CompileRequired(src, COMPILER_TYPE_C) + if err != nil { + t.Fatal(err) + } + if required { + t.Errorf("compile required for up to date object file") + } + + now := time.Now().Add(time.Minute) + if err := os.Chtimes(hdr, now, now); err != nil { + t.Fatal(err) + } + + required, err = c.depTracker.CompileRequired(src, COMPILER_TYPE_C) + if err != nil { + t.Fatal(err) + } + if !required { + t.Errorf("compile not required after header was modified") + } +} + +// An object file without dependency file (e.g., built by an older newt) must +// be rebuilt, as it is not known which headers it depends on. +func TestCompileRequiredMissingDepsFile(t *testing.T) { + dir := t.TempDir() + c := newTestCompiler() + c.ccPath = "cc" + c.baseDir = dir + c.dstDir = dir + "/bin" + src := dir + "/src/a.c" + dst := c.dstFilePath(src) + + if err := os.MkdirAll(filepath.Dir(src), 0755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(src, nil, 0644); err != nil { + t.Fatal(err) + } + + cmd, err := c.CompileFileCmd(src, COMPILER_TYPE_C) + if err != nil { + t.Fatal(err) + } + if err := writeCommandFile(dst+".o", cmd); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(dst+".o", nil, 0644); err != nil { + t.Fatal(err) + } + + old := time.Now().Add(-time.Hour) + if err := os.Chtimes(src, old, old); err != nil { + t.Fatal(err) + } + + required, err := c.depTracker.CompileRequired(src, COMPILER_TYPE_C) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if !required { + t.Errorf("compile not required without dependency file") + } +} + +func TestStripDepsFlags(t *testing.T) { + cmd := []string{"cc", "-Wall", "-MMD", "-MF", "bin/a.d", "-c", "-o", + "bin/a.o", "a.c"} + exp := []string{"cc", "-Wall", "-c", "-o", "bin/a.o", "a.c"} + + if got := stripDepsFlags(cmd); !slices.Equal(got, exp) { + t.Errorf("expected %q, got %q", exp, got) + } +} + func TestParseDepsFileEscaping(t *testing.T) { dir := t.TempDir() obj := dir + "/my dir/my file.o"
