Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f102aad

Browse files
authored
refactoring: implement a proper dependency (.d) file parser (#2972)
* Implemented proper .d file parser * Moved list cleanup into unescapeAndSplit * Nicer error handling * Ignore CR+LF in dep parser * Removed misleading comment
1 parent a86947b commit f102aad

File tree

13 files changed

+1487
-80
lines changed

13 files changed

+1487
-80
lines changed

internal/arduino/builder/internal/utils/ansi_others.go renamed to internal/arduino/builder/cpp/ansi_others.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
//go:build !windows
1717

18-
package utils
18+
package cpp
1919

2020
import (
2121
"errors"

internal/arduino/builder/internal/utils/ansi_windows.go renamed to internal/arduino/builder/cpp/ansi_windows.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// Arduino software without disclosing the source code of your own applications.
1414
// To purchase a commercial license, send an email to license@arduino.cc.
1515

16-
package utils
16+
package cpp
1717

1818
import (
1919
"golang.org/x/sys/windows"
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package cpp
17+
18+
import (
19+
"errors"
20+
"runtime"
21+
"strings"
22+
"unicode"
23+
24+
"github.com/arduino/go-paths-helper"
25+
"go.bug.st/f"
26+
)
27+
28+
// Dependencies represents the dependencies of a source file.
29+
type Dependencies struct {
30+
ObjectFile string
31+
Dependencies []string
32+
}
33+
34+
// ReadDepFile reads a dependency file and returns the dependencies.
35+
// It may return nil if the dependency file is empty.
36+
func ReadDepFile(depFilePath *paths.Path) (*Dependencies, error) {
37+
depFileData, err := depFilePath.ReadFile()
38+
if err != nil {
39+
return nil, err
40+
}
41+
42+
if runtime.GOOS == "windows" {
43+
// This is required because on Windows we don't know which encoding is used
44+
// by gcc to write the dep file (it could be UTF-8 or any of the Windows
45+
// ANSI mappings).
46+
if decoded, err := convertAnsiBytesToString(depFileData); err == nil {
47+
if res, err := readDepFile(decoded); err == nil && res != nil {
48+
return res, nil
49+
}
50+
}
51+
// Fallback to UTF-8...
52+
}
53+
54+
return readDepFile(string(depFileData))
55+
}
56+
57+
func readDepFile(depFile string) (*Dependencies, error) {
58+
rows, err := unescapeAndSplit(depFile)
59+
if err != nil {
60+
return nil, err
61+
}
62+
if len(rows) == 0 {
63+
return &Dependencies{}, nil
64+
}
65+
66+
if !strings.HasSuffix(rows[0], ":") {
67+
return nil, errors.New("no colon in first item of depfile")
68+
}
69+
res := &Dependencies{
70+
ObjectFile: strings.TrimSuffix(rows[0], ":"),
71+
Dependencies: rows[1:],
72+
}
73+
return res, nil
74+
}
75+
76+
func unescapeAndSplit(s string) ([]string, error) {
77+
var res []string
78+
backslash := false
79+
dollar := false
80+
current := strings.Builder{}
81+
for _, c := range s {
82+
if c == '\r' {
83+
// Ignore CR (Windows line ending style immediately followed by LF)
84+
continue
85+
}
86+
if backslash {
87+
switch c {
88+
case ' ':
89+
current.WriteRune(' ')
90+
case '#':
91+
current.WriteRune('#')
92+
case '\\':
93+
current.WriteRune('\\')
94+
case '\n':
95+
// ignore
96+
default:
97+
current.WriteRune('\\')
98+
current.WriteRune(c)
99+
}
100+
backslash = false
101+
continue
102+
}
103+
if dollar {
104+
if c != '$' {
105+
return nil, errors.New("invalid dollar sequence: $" + string(c))
106+
}
107+
current.WriteByte('$')
108+
dollar = false
109+
continue
110+
}
111+
112+
if c == '\\' {
113+
backslash = true
114+
continue
115+
}
116+
if c == '$' {
117+
dollar = true
118+
continue
119+
}
120+
121+
if unicode.IsSpace(c) {
122+
if current.Len() > 0 {
123+
res = append(res, current.String())
124+
current.Reset()
125+
}
126+
continue
127+
}
128+
current.WriteRune(c)
129+
}
130+
if dollar {
131+
return nil, errors.New("unclosed escape sequence at end of depfile")
132+
}
133+
if current.Len() > 0 {
134+
res = append(res, current.String())
135+
}
136+
res = f.Map(res, strings.TrimSpace)
137+
res = f.Filter(res, f.NotEquals(""))
138+
return res, nil
139+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to license@arduino.cc.
15+
16+
package cpp
17+
18+
import (
19+
"testing"
20+
21+
"github.com/arduino/go-paths-helper"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestDepFileReader(t *testing.T) {
26+
t.Run("0", func(t *testing.T) {
27+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.0.d"))
28+
require.NoError(t, err)
29+
require.NotNil(t, deps)
30+
require.Len(t, deps.Dependencies, 302)
31+
require.Equal(t, "sketch.ino.cpp.o", deps.ObjectFile)
32+
require.Equal(t, "/home/megabug/Arduino/sketch/build/sketch/sketch.ino.cpp.merged", deps.Dependencies[0])
33+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/variants/b_u585i_iot02a_stm32u585xx/llext-edk/include/zephyr/include/generated/zephyr/autoconf.h", deps.Dependencies[1])
34+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/variants/b_u585i_iot02a_stm32u585xx/llext-edk/include/zephyr/include/zephyr/toolchain/zephyr_stdint.h", deps.Dependencies[2])
35+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/libraries/Arduino_RPCLite/src/dispatcher.h", deps.Dependencies[301])
36+
})
37+
t.Run("1", func(t *testing.T) {
38+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.1.d"))
39+
require.NoError(t, err)
40+
require.NotNil(t, deps)
41+
require.Equal(t, "sketch.ino.o", deps.ObjectFile)
42+
require.Len(t, deps.Dependencies, 302)
43+
require.Equal(t, "/home/megabug/Arduino/sketch/build/sketch/sketch.ino.cpp", deps.Dependencies[0])
44+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/variants/b_u585i_iot02a_stm32u585xx/llext-edk/include/zephyr/include/generated/zephyr/autoconf.h", deps.Dependencies[1])
45+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/variants/b_u585i_iot02a_stm32u585xx/llext-edk/include/zephyr/include/zephyr/toolchain/zephyr_stdint.h", deps.Dependencies[2])
46+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/libraries/Arduino_RPCLite/src/dispatcher.h", deps.Dependencies[301])
47+
})
48+
t.Run("2", func(t *testing.T) {
49+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.2.d"))
50+
require.NoError(t, err)
51+
require.NotNil(t, deps)
52+
require.Equal(t, "ske tch.ino.cpp.o", deps.ObjectFile)
53+
require.Len(t, deps.Dependencies, 302)
54+
require.Equal(t, "/home/megabug/Arduino/ske tch/build/sketch/ske tch.ino.cpp.merged", deps.Dependencies[0])
55+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/variants/b_u585i_iot02a_stm32u585xx/llext-edk/include/zephyr/include/generated/zephyr/autoconf.h", deps.Dependencies[1])
56+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/variants/b_u585i_iot02a_stm32u585xx/llext-edk/include/zephyr/include/zephyr/toolchain/zephyr_stdint.h", deps.Dependencies[2])
57+
require.Equal(t, "/home/megabug/.arduino15/packages/arduino/hardware/zephyr/0.10.0-rc.10/libraries/Arduino_RPCLite/src/dispatcher.h", deps.Dependencies[301])
58+
})
59+
t.Run("3", func(t *testing.T) {
60+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.3.d"))
61+
require.NoError(t, err)
62+
require.NotNil(t, deps)
63+
require.Equal(t, "myfile.o", deps.ObjectFile)
64+
require.Len(t, deps.Dependencies, 3)
65+
require.Equal(t, "/some/path\\twith/backslash and spaces/file.cpp", deps.Dependencies[0])
66+
require.Equal(t, "/some/other$/path#/file.h", deps.Dependencies[1])
67+
require.Equal(t, "/yet/ano\\ther/path/file.h", deps.Dependencies[2])
68+
})
69+
t.Run("4", func(t *testing.T) {
70+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.4.d"))
71+
require.EqualError(t, err, "invalid dollar sequence: $a")
72+
require.Nil(t, deps)
73+
})
74+
t.Run("6", func(t *testing.T) {
75+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.6.d"))
76+
require.EqualError(t, err, "unclosed escape sequence at end of depfile")
77+
require.Nil(t, deps)
78+
})
79+
t.Run("7", func(t *testing.T) {
80+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.7.d"))
81+
require.EqualError(t, err, "no colon in first item of depfile")
82+
require.Nil(t, deps)
83+
})
84+
t.Run("8", func(t *testing.T) {
85+
deps, err := ReadDepFile(paths.New("testdata", "depcheck.8.d"))
86+
require.NoError(t, err)
87+
require.Nil(t, deps.Dependencies)
88+
require.Empty(t, deps.ObjectFile)
89+
})
90+
t.Run("9", func(t *testing.T) {
91+
deps, err := ReadDepFile(paths.New("testdata", "nonexistent.d"))
92+
require.Error(t, err)
93+
require.Nil(t, deps)
94+
})
95+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /