Skip to content

Commit 04aafb3

Browse files
committed
Implemented proper .d file parser
1 parent 3eecf20 commit 04aafb3

File tree

8 files changed

+1132
-78
lines changed

8 files changed

+1132
-78
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 [email protected].
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 [email protected].
15+
16+
package cpp
17+
18+
import (
19+
"errors"
20+
"runtime"
21+
"strings"
22+
23+
"github.com/arduino/go-paths-helper"
24+
"go.bug.st/f"
25+
)
26+
27+
// Dependencies represents the dependencies of a source file.
28+
type Dependencies struct {
29+
ObjectFile string
30+
Dependencies []string
31+
}
32+
33+
// ReadDepFile reads a dependency file and returns the dependencies.
34+
// It may return nil if the dependency file is empty.
35+
func ReadDepFile(depFilePath *paths.Path) (*Dependencies, error) {
36+
depFileData, err := depFilePath.ReadFile()
37+
if err != nil {
38+
return nil, err
39+
}
40+
41+
if runtime.GOOS == "windows" {
42+
// This is required because on Windows we don't know which encoding is used
43+
// by gcc to write the dep file (it could be UTF-8 or any of the Windows
44+
// ANSI mappings).
45+
if decoded, err := convertAnsiBytesToString(depFileData); err == nil {
46+
if res, err := readDepFile(decoded); err == nil && res != nil {
47+
return res, nil
48+
}
49+
}
50+
// Fallback to UTF-8...
51+
}
52+
53+
return readDepFile(string(depFileData))
54+
}
55+
56+
func readDepFile(depFile string) (*Dependencies, error) {
57+
rows, err := unescapeAndSplit(strings.ReplaceAll(depFile, "\r\n", "\n"))
58+
if err != nil {
59+
return nil, err
60+
}
61+
rows = f.Map(rows, strings.TrimSpace)
62+
rows = f.Filter(rows, f.NotEquals(""))
63+
if len(rows) == 0 {
64+
return &Dependencies{}, nil
65+
}
66+
67+
// The first line of the depfile contains the path to the object file to generate.
68+
// The second line of the depfile contains the path to the source file.
69+
// All subsequent lines contain the header files necessary to compile the object file.
70+
71+
if !strings.HasSuffix(rows[0], ":") {
72+
return nil, errors.New("no colon in first item of depfile")
73+
}
74+
res := &Dependencies{
75+
ObjectFile: strings.TrimSuffix(rows[0], ":"),
76+
Dependencies: rows[1:],
77+
}
78+
return res, nil
79+
}
80+
81+
func unescapeAndSplit(s string) ([]string, error) {
82+
var res []string
83+
backslash := false
84+
dollar := false
85+
current := strings.Builder{}
86+
for _, c := range s {
87+
if backslash {
88+
switch c {
89+
case ' ':
90+
current.WriteByte(' ')
91+
case '\t':
92+
current.WriteByte('\t')
93+
case '#':
94+
current.WriteByte('#')
95+
case '\\':
96+
current.WriteByte('\\')
97+
case '\n':
98+
// ignore
99+
default:
100+
return nil, errors.New("invalid escape sequence: \\" + string(c))
101+
}
102+
backslash = false
103+
continue
104+
}
105+
if dollar {
106+
if c != '$' {
107+
return nil, errors.New("invalid dollar sequence: $" + string(c))
108+
}
109+
current.WriteByte('$')
110+
dollar = false
111+
continue
112+
}
113+
114+
if c == '\\' {
115+
backslash = true
116+
continue
117+
}
118+
if c == '$' {
119+
dollar = true
120+
continue
121+
}
122+
123+
if c == ' ' {
124+
if current.Len() > 0 {
125+
res = append(res, current.String())
126+
current.Reset()
127+
}
128+
continue
129+
}
130+
current.WriteRune(c)
131+
}
132+
if backslash || dollar {
133+
return nil, errors.New("unclosed escape sequence at end of string")
134+
}
135+
if current.Len() > 0 {
136+
res = append(res, current.String())
137+
}
138+
return res, nil
139+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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 [email protected].
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+
}

0 commit comments

Comments
 (0)