Skip to content

Commit 946e62d

Browse files
committed
Implemented proper .d file parser
1 parent 3eecf20 commit 946e62d

File tree

8 files changed

+1128
-79
lines changed

8 files changed

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