Skip to content

Commit 1c7920f

Browse files
facchinmcmaglie
andauthored
Fix mixed code precompiled libraries (again!) (#611)
* Fix mixed code precompiled libraries This patch restores some functionalities broken by #512 . It introduces a BREAKING CHANGE to "precompiled" field If merged, "precompiled" means that the library ONLY contains a precompiled library and its headers. No file containing implementation will be compiled if the library is specified as such. Fixes arduino/arduino-builder#353 Tested with * #512 (comment) (Nano33BLE and generic MKR board) * https://github.com/BoschSensortec/BSEC-Arduino-library (works after removing precompiled=true directive in library.properties) * https://github.com/vidor-libraries/USBBlaster (MKRVidor, fully precompiled) * Added test-build for Arduino_TensorFlowLite * Added another test for Bosch Sensor lib * Fallback search for precompiled libraries in non-fpu specific directories * variable renamed * Moved fixLDFLAG inside compileLibraries * Inlined FixLDflags * Using more paths helpers to simplify code * Added support for "precompiled=full" in library.properties This flag allow the deployment of a pure precompiled library that ships also the source code of the precompiled part. This allows to possibly compile-from-source as a fallback if the library does not provide a precompiled build for the current architecture. The "precompiled=true" instead has been ported back to the old behaviour (for libraries that ships a precompiled binary and support source code that wraps/uses the precompiled part). * Updated tests * Fixed test compile-build on very long paths on Windows * Removed constants.BUILD_PROPERTIES_COMPILER_LIBRARIES_LDFLAGS * Add space before "-L" gcc flag to allow multiple precompiled libs to be included Co-authored-by: Cristian Maglie <[email protected]>
1 parent 6718cf4 commit 1c7920f

File tree

7 files changed

+130
-84
lines changed

7 files changed

+130
-84
lines changed

Diff for: arduino/libraries/libraries.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,21 @@ type Library struct {
5555

5656
Types []string `json:"types,omitempty"`
5757

58-
InstallDir *paths.Path
59-
SourceDir *paths.Path
60-
UtilityDir *paths.Path
61-
Location LibraryLocation
62-
ContainerPlatform *cores.PlatformRelease `json:""`
63-
Layout LibraryLayout
64-
RealName string
65-
DotALinkage bool
66-
Precompiled bool
67-
LDflags string
68-
IsLegacy bool
69-
Version *semver.Version
70-
License string
71-
Properties *properties.Map
58+
InstallDir *paths.Path
59+
SourceDir *paths.Path
60+
UtilityDir *paths.Path
61+
Location LibraryLocation
62+
ContainerPlatform *cores.PlatformRelease `json:""`
63+
Layout LibraryLayout
64+
RealName string
65+
DotALinkage bool
66+
Precompiled bool
67+
PrecompiledWithSources bool
68+
LDflags string
69+
IsLegacy bool
70+
Version *semver.Version
71+
License string
72+
Properties *properties.Map
7273
}
7374

7475
func (library *Library) String() string {

Diff for: arduino/libraries/loader.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ func makeNewLibrary(libraryDir *paths.Path, location LibraryLocation) (*Library,
103103
library.Website = strings.TrimSpace(libProperties.Get("url"))
104104
library.IsLegacy = false
105105
library.DotALinkage = libProperties.GetBoolean("dot_a_linkage")
106-
library.Precompiled = libProperties.GetBoolean("precompiled")
106+
library.PrecompiledWithSources = libProperties.Get("precompiled") == "full"
107+
library.Precompiled = libProperties.Get("precompiled") == "true" || library.PrecompiledWithSources
107108
library.LDflags = strings.TrimSpace(libProperties.Get("ldflags"))
108109
library.Properties = libProperties
109110

Diff for: legacy/builder/constants/constants.go

-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ const BUILD_PROPERTIES_BUILD_BOARD = "build.board"
2626
const BUILD_PROPERTIES_BUILD_MCU = "build.mcu"
2727
const BUILD_PROPERTIES_COMPILER_C_ELF_FLAGS = "compiler.c.elf.flags"
2828
const BUILD_PROPERTIES_COMPILER_LDFLAGS = "compiler.ldflags"
29-
const BUILD_PROPERTIES_COMPILER_LIBRARIES_LDFLAGS = "compiler.libraries.ldflags"
3029
const BUILD_PROPERTIES_COMPILER_CPP_FLAGS = "compiler.cpp.flags"
3130
const BUILD_PROPERTIES_COMPILER_WARNING_FLAGS = "compiler.warning_flags"
3231
const BUILD_PROPERTIES_FQBN = "build.fqbn"
@@ -95,7 +94,6 @@ const MSG_FIND_INCLUDES_FAILED = "Error while detecting libraries included by {0
9594
const MSG_INVALID_QUOTING = "Invalid quoting: no closing [{0}] char found."
9695
const MSG_LIB_LEGACY = "(legacy)"
9796
const MSG_LIBRARIES_MULTIPLE_LIBS_FOUND_FOR = "Multiple libraries were found for \"{0}\""
98-
const MSG_PRECOMPILED_LIBRARY_NOT_FOUND_FOR = "Library \"{0}\" declared precompiled but folder \"{1}\" does not exist"
9997
const MSG_LIBRARIES_NOT_USED = " Not used: {0}"
10098
const MSG_LIBRARIES_USED = " Used: {0}"
10199
const MSG_LIBRARY_CAN_USE_SRC_AND_UTILITY_FOLDERS = "Library can't use both 'src' and 'utility' folders. Double check {0}"

Diff for: legacy/builder/phases/libraries_builder.go

+49-66
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ package phases
1717

1818
import (
1919
"os"
20-
"path/filepath"
2120
"strings"
2221

2322
"github.com/arduino/arduino-cli/arduino/libraries"
@@ -30,8 +29,6 @@ import (
3029
"github.com/pkg/errors"
3130
)
3231

33-
var PRECOMPILED_LIBRARIES_VALID_EXTENSIONS_STATIC = map[string]bool{".a": true}
34-
var PRECOMPILED_LIBRARIES_VALID_EXTENSIONS_DYNAMIC = map[string]bool{".so": true}
3532
var FLOAT_ABI_CFLAG = "float-abi"
3633
var FPU_CFLAG = "fpu"
3734

@@ -53,10 +50,6 @@ func (s *LibrariesBuilder) Run(ctx *types.Context) error {
5350
}
5451

5552
ctx.LibrariesObjectFiles = objectFiles
56-
57-
// Search for precompiled libraries
58-
fixLDFLAGforPrecompiledLibraries(ctx, libs)
59-
6053
return nil
6154
}
6255

@@ -87,58 +80,25 @@ func findExpectedPrecompiledLibFolder(ctx *types.Context, library *libraries.Lib
8780
}
8881

8982
logger := ctx.GetLogger()
83+
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO, "Library {0} has been declared precompiled:", library.Name)
84+
85+
// Try directory with full fpuSpecs first, if available
9086
if len(fpuSpecs) > 0 {
9187
fpuSpecs = strings.TrimRight(fpuSpecs, "-")
92-
if library.SourceDir.Join(mcu).Join(fpuSpecs).Exist() {
93-
return library.SourceDir.Join(mcu).Join(fpuSpecs)
94-
} else {
95-
// we are unsure, compile from sources
96-
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO,
97-
constants.MSG_PRECOMPILED_LIBRARY_NOT_FOUND_FOR, library.Name, library.SourceDir.Join(mcu).Join(fpuSpecs))
98-
return nil
88+
fullPrecompDir := library.SourceDir.Join(mcu).Join(fpuSpecs)
89+
if fullPrecompDir.Exist() {
90+
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO, "Using precompiled library in {0}", fullPrecompDir)
91+
return fullPrecompDir
9992
}
93+
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO, "Precompiled library in \"{0}\" not found", fullPrecompDir)
10094
}
10195

102-
if library.SourceDir.Join(mcu).Exist() {
103-
return library.SourceDir.Join(mcu)
104-
}
105-
106-
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO,
107-
constants.MSG_PRECOMPILED_LIBRARY_NOT_FOUND_FOR, library.Name, library.SourceDir.Join(mcu))
108-
109-
return nil
110-
}
111-
112-
func fixLDFLAGforPrecompiledLibraries(ctx *types.Context, libs libraries.List) error {
113-
114-
for _, library := range libs {
115-
if library.Precompiled {
116-
// add library src path to compiler.c.elf.extra_flags
117-
// use library.Name as lib name and srcPath/{mcpu} as location
118-
path := findExpectedPrecompiledLibFolder(ctx, library)
119-
if path == nil {
120-
break
121-
}
122-
// find all library names in the folder and prepend -l
123-
filePaths := []string{}
124-
libs_cmd := library.LDflags + " "
125-
extensions := func(ext string) bool {
126-
return PRECOMPILED_LIBRARIES_VALID_EXTENSIONS_DYNAMIC[ext] || PRECOMPILED_LIBRARIES_VALID_EXTENSIONS_STATIC[ext]
127-
}
128-
utils.FindFilesInFolder(&filePaths, path.String(), extensions, false)
129-
for _, lib := range filePaths {
130-
name := strings.TrimSuffix(filepath.Base(lib), filepath.Ext(lib))
131-
// strip "lib" first occurrence
132-
if strings.HasPrefix(name, "lib") {
133-
name = strings.Replace(name, "lib", "", 1)
134-
libs_cmd += "-l" + name + " "
135-
}
136-
}
137-
138-
currLDFlags := ctx.BuildProperties.Get(constants.BUILD_PROPERTIES_COMPILER_LIBRARIES_LDFLAGS)
139-
ctx.BuildProperties.Set(constants.BUILD_PROPERTIES_COMPILER_LIBRARIES_LDFLAGS, currLDFlags+"\"-L"+path.String()+"\" "+libs_cmd+" ")
140-
}
96+
precompDir := library.SourceDir.Join(mcu)
97+
if precompDir.Exist() {
98+
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO, "Using precompiled library in {0}", precompDir)
99+
return precompDir
141100
}
101+
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO, "Precompiled library in \"{0}\" not found", precompDir)
142102
return nil
143103
}
144104

@@ -175,25 +135,48 @@ func compileLibrary(ctx *types.Context, library *libraries.Library, buildPath *p
175135
objectFiles := paths.NewPathList()
176136

177137
if library.Precompiled {
178-
// search for files with PRECOMPILED_LIBRARIES_VALID_EXTENSIONS
179-
extensions := func(ext string) bool { return PRECOMPILED_LIBRARIES_VALID_EXTENSIONS_STATIC[ext] }
180-
181-
filePaths := []string{}
138+
coreSupportPrecompiled := ctx.BuildProperties.ContainsKey("compiler.libraries.ldflags")
182139
precompiledPath := findExpectedPrecompiledLibFolder(ctx, library)
183-
if precompiledPath != nil {
184-
// TODO: This codepath is just taken for .a with unusual names that would
185-
// be ignored by -L / -l methods.
186-
// Should we force precompiled libraries to start with "lib" ?
187-
err := utils.FindFilesInFolder(&filePaths, precompiledPath.String(), extensions, false)
140+
141+
if !coreSupportPrecompiled {
142+
logger := ctx.GetLogger()
143+
logger.Fprintln(os.Stdout, constants.LOG_LEVEL_INFO, "The plaform does not support 'compiler.libraries.ldflags' for precompiled libraries.")
144+
145+
} else if precompiledPath != nil {
146+
// Find all libraries in precompiledPath
147+
libs, err := precompiledPath.ReadDir()
188148
if err != nil {
189149
return nil, errors.WithStack(err)
190150
}
191-
for _, path := range filePaths {
192-
if !strings.HasPrefix(filepath.Base(path), "lib") {
193-
objectFiles.Add(paths.New(path))
151+
152+
// Add required LD flags
153+
libsCmd := library.LDflags + " "
154+
dynAndStaticLibs := libs.Clone()
155+
dynAndStaticLibs.FilterSuffix(".a", ".so")
156+
for _, lib := range dynAndStaticLibs {
157+
name := strings.TrimSuffix(lib.Base(), lib.Ext())
158+
if strings.HasPrefix(name, "lib") {
159+
libsCmd += "-l" + name[3:] + " "
160+
}
161+
}
162+
163+
currLDFlags := ctx.BuildProperties.Get("compiler.libraries.ldflags")
164+
ctx.BuildProperties.Set("compiler.libraries.ldflags", currLDFlags+" \"-L"+precompiledPath.String()+"\" "+libsCmd+" ")
165+
166+
// TODO: This codepath is just taken for .a with unusual names that would
167+
// be ignored by -L / -l methods.
168+
// Should we force precompiled libraries to start with "lib" ?
169+
staticLibs := libs.Clone()
170+
staticLibs.FilterSuffix(".a")
171+
for _, lib := range staticLibs {
172+
if !strings.HasPrefix(lib.Base(), "lib") {
173+
objectFiles.Add(lib)
194174
}
195175
}
196-
return objectFiles, nil
176+
177+
if library.PrecompiledWithSources {
178+
return objectFiles, nil
179+
}
197180
}
198181
}
199182

Diff for: test/conftest.py

+23-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import simplejson as json
2121
from invoke import Local
2222
from invoke.context import Context
23+
import tempfile
2324

2425
from .common import Board
2526

@@ -31,7 +32,28 @@ def data_dir(tmpdir_factory):
3132
each test and deleted at the end, this way all the
3233
tests work in isolation.
3334
"""
34-
return str(tmpdir_factory.mktemp("ArduinoTest"))
35+
36+
# it seems that paths generated by pytest's tmpdir_factory are too
37+
# long and may lead to arduino-cli compile failures due to the
38+
# combination of (some or all of) the following reasons:
39+
# 1) Windows not liking path >260 chars in len
40+
# 2) arm-gcc not fully optimizing long paths
41+
# 3) libraries requiring headers deep down the include path
42+
# for example:
43+
#
44+
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/mbed/rtos/Thread.h:29,
45+
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/mbed/rtos/rtos.h:28,
46+
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/mbed/mbed.h:23,
47+
# from C:\Users\runneradmin\AppData\Local\Temp\pytest-of-runneradmin\pytest-0\A7\packages\arduino\hardware\mbed\1.1.4\cores\arduino/Arduino.h:32,
48+
# from C:\Users\RUNNER~1\AppData\Local\Temp\arduino-sketch-739B2B6DD21EB014317DA2A46062811B\sketch\magic_wand.ino.cpp:1:
49+
##[error]c:\users\runneradmin\appdata\local\temp\pytest-of-runneradmin\pytest-0\a7\packages\arduino\tools\arm-none-eabi-gcc\7-2017q4\arm-none-eabi\include\c++\7.2.1\new:39:10: fatal error: bits/c++config.h: No such file or directory
50+
#
51+
# due to the above on Windows we cut the tmp path straight to /tmp/xxxxxxxx
52+
if platform.system() == "Windows":
53+
with tempfile.TemporaryDirectory() as tmp:
54+
yield tmp
55+
else:
56+
yield str(tmpdir_factory.mktemp("ArduinoTest"))
3557

3658

3759
@pytest.fixture(scope="session")

Diff for: test/test_compile.py

+41
Original file line numberDiff line numberDiff line change
@@ -281,3 +281,44 @@ def test_compile_blacklisted_sketchname(run_command, data_dir):
281281
"compile -b {fqbn} {sketch_path}".format(fqbn=fqbn, sketch_path=sketch_path)
282282
)
283283
assert result.ok
284+
285+
286+
def test_compile_without_precompiled_libraries(run_command, data_dir):
287+
# Init the environment explicitly
288+
url = "https://adafruit.github.io/arduino-board-index/package_adafruit_index.json"
289+
result = run_command("core update-index --additional-urls={}".format(url))
290+
assert result.ok
291+
result = run_command("core install arduino:mbed --additional-urls={}".format(url))
292+
assert result.ok
293+
result = run_command("core install arduino:samd --additional-urls={}".format(url))
294+
assert result.ok
295+
result = run_command("core install adafruit:samd --additional-urls={}".format(url))
296+
assert result.ok
297+
298+
# Install pre-release version of Arduino_TensorFlowLite (will be officially released
299+
# via lib manager after https://github.com/arduino/arduino-builder/issues/353 is in)
300+
import zipfile
301+
with zipfile.ZipFile("test/testdata/Arduino_TensorFlowLite.zip", 'r') as zip_ref:
302+
zip_ref.extractall("{}/libraries/".format(data_dir))
303+
result = run_command("lib install [email protected]")
304+
assert result.ok
305+
result = run_command("compile -b arduino:mbed:nano33ble {}/libraries/Arduino_TensorFlowLite/examples/magic_wand/".format(data_dir))
306+
assert result.ok
307+
result = run_command("compile -b adafruit:samd:adafruit_feather_m4 {}/libraries/Arduino_TensorFlowLite/examples/magic_wand/".format(data_dir))
308+
assert result.ok
309+
310+
# Non-precompiled version of Arduino_TensorflowLite
311+
result = run_command("lib install [email protected]")
312+
assert result.ok
313+
result = run_command("compile -b arduino:mbed:nano33ble {}/libraries/Arduino_TensorFlowLite/examples/magic_wand/".format(data_dir))
314+
assert result.ok
315+
result = run_command("compile -b adafruit:samd:adafruit_feather_m4 {}/libraries/Arduino_TensorFlowLite/examples/magic_wand/".format(data_dir))
316+
assert result.ok
317+
318+
# Bosch sensor library
319+
result = run_command("lib install \"BSEC Software [email protected]\"")
320+
assert result.ok
321+
result = run_command("compile -b arduino:samd:mkr1000 {}/libraries/BSEC_Software_Library/examples/basic/".format(data_dir))
322+
assert result.ok
323+
result = run_command("compile -b arduino:mbed:nano33ble {}/libraries/BSEC_Software_Library/examples/basic/".format(data_dir))
324+
assert result.ok

Diff for: test/testdata/Arduino_TensorFlowLite.zip

3.71 MB
Binary file not shown.

0 commit comments

Comments
 (0)