Skip to content

Commit 1fb35f1

Browse files
committed
Obey 1.0 / 1.5 library specification when finding C++ library source
1 parent 5b866eb commit 1fb35f1

28 files changed

+158
-55
lines changed

Diff for: CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
## [Unreleased]
99
### Added
1010
- `arduino_ci_remote.rb` CLI switch `--skip-examples-compilation`
11+
- `CppLibrary.header_files` to find header files
1112

1213
### Changed
1314
- Move repository from https://github.com/ianfixes/arduino_ci to https://github.com/Arduino-CI/arduino_ci
15+
- `CppLibrary` functions returning C++ header or code files now respect the 1.0/1.5 library specification
1416

1517
### Deprecated
1618
- `arduino_ci_remote.rb` CLI switch `--skip-compilation`

Diff for: SampleProjects/OnePointFiveDummy/NoBase.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/NoBase.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This project resembles a "1.5 spec" library: it has `library.properties` and a `src/` directory that will be scanned recursively. `utility/`, if present, will be ignored.

Diff for: SampleProjects/OnePointFiveDummy/library.properties

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/src/YesSrc.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/src/YesSrc.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/src/subdir/YesSubdir.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/utility/ImNotHere.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveDummy/utility/ImNotHere.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveMalformed/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This project lacks a `library.properties` and so should be treated as a "1.0 spec" library -- the base and `utility` directories will be scanned for code, non-recursively. `src/`, if present, will be ignored.

Diff for: SampleProjects/OnePointFiveMalformed/YesBase.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveMalformed/YesBase.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveMalformed/src/ImNotHere.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveMalformed/src/ImNotHere.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveMalformed/utility/YesUtil.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointFiveMalformed/utility/YesUtil.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointOhDummy/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
This project should resemble "1.0 spec" library -- the base and `utility` directories will be scanned for code, non-recursively. `src/`, if present, will be ignored.

Diff for: SampleProjects/OnePointOhDummy/YesBase.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointOhDummy/YesBase.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointOhDummy/src/ImNotHere.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointOhDummy/src/ImNotHere.h

Whitespace-only changes.

Diff for: SampleProjects/OnePointOhDummy/utility/YesUtil.cpp

Whitespace-only changes.

Diff for: SampleProjects/OnePointOhDummy/utility/YesUtil.h

Whitespace-only changes.

Diff for: SampleProjects/README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
Arduino Sample Projects
22
=======================
33

4-
This directory contains projects that are meant to be built with and tested by this gem. Although this directory is named `SampleProjects`, it is by no means optional. These project test the testing framework itself, but also provide examples of how you might write your own tests (which should be placed in your system's Arduino `libraries` directory).
4+
This directory contains projects that are intended solely for testing the various features of this gem -- to test the testing framework itself. The RSpec tests refer specifically to these projects.
55

6-
* "DoSomething" is a simple test of the testing framework (arduino_ci) itself to verfy that passes and failures are properly identified and reported.
7-
* "TestSomething" contains tests for all the mock features of arduino_ci.
6+
Because of this, these projects include some intentional quirks that differ from what a well-formed an Arduino project for testing with `arduino_ci` might contain. See other projects in the "Arduino-CI" GitHub organization for practical examples.
7+
8+
9+
* "TestSomething" contains a minimial library, but tests for all the C++ compilation feature-mocks of arduino_ci.
10+
* "DoSomething" is a simple test of the testing framework (arduino_ci) itself to verfy that passes and failures are properly identified and reported. Because of this, it includes test files that are expected to fail -- they are prefixed with "bad-".
11+
* "OnePointOhDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the "1.0" specification
12+
* "OnePointFiveMalformed" is a non-functional library meant to test file inclusion logic on libraries that attempt to conform to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/) but fail to include a `src` directory
13+
* "OnePointFiveDummy" is a non-functional library meant to test file inclusion logic on libraries conforming to the ["1.5" specfication](https://arduino.github.io/arduino-cli/latest/library-specification/)

Diff for: lib/arduino_ci/cpp_library.rb

+58-20
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ def initialize(base_dir, arduino_lib_dir, exclude_dirs)
5555
@vendor_bundle_cache = nil
5656
end
5757

58+
# Decide whether this is a 1.5-compatible library
59+
#
60+
# according to https://arduino.github.io/arduino-cli/latest/library-specification
61+
#
62+
# Should match logic from https://github.com/arduino/arduino-cli/blob/master/arduino/libraries/loader.go
63+
# @return [bool]
64+
def one_point_five?
65+
lib_props = (@base_dir + "library.properties")
66+
src_dir = (@base_dir + "src")
67+
[lib_props, src_dir].all?(&:exist?) && lib_props.file? && src_dir.directory?
68+
end
69+
5870
# Guess whether a file is part of the vendor bundle (indicating we should ignore it).
5971
#
6072
# A safe way to do this seems to be to check whether any of the installed gems
@@ -152,41 +164,76 @@ def libasan?(gcc_binary)
152164

153165
# Get a list of all CPP source files in a directory and its subdirectories
154166
# @param some_dir [Pathname] The directory in which to begin the search
167+
# @param extensions [Array<Sring>] The set of allowable file extensions
155168
# @return [Array<Pathname>] The paths of the found files
156-
def cpp_files_in(some_dir)
169+
def code_files_in(some_dir, extensions)
157170
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
158171
return [] unless some_dir.exist? && some_dir.directory?
159172

160173
real = some_dir.realpath
161-
files = Find.find(real).map { |p| Pathname.new(p) }.reject(&:directory?)
162-
cpp = files.select { |path| CPP_EXTENSIONS.include?(path.extname.downcase) }
174+
files = some_dir.realpath.children.reject(&:directory?)
175+
cpp = files.select { |path| extensions.include?(path.extname.downcase) }
163176
not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
164177
not_hidden.sort_by(&:to_s)
165178
end
166179

180+
# Get a list of all CPP source files in a directory and its subdirectories
181+
# @param some_dir [Pathname] The directory in which to begin the search
182+
# @param extensions [Array<Sring>] The set of allowable file extensions
183+
# @return [Array<Pathname>] The paths of the found files
184+
def code_files_in_recursive(some_dir, extensions)
185+
raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
186+
return [] unless some_dir.exist? && some_dir.directory?
187+
188+
real = some_dir.realpath
189+
Find.find(real).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten
190+
end
191+
192+
# Header files that are part of the project library under test
193+
# @return [Array<Pathname>]
194+
def header_files
195+
ret = if one_point_five?
196+
code_files_in_recursive(@base_dir + "src", HPP_EXTENSIONS)
197+
else
198+
[@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, HPP_EXTENSIONS) }.flatten
199+
end
200+
201+
# note to future troubleshooter: some of these tests may not be relevant, but at the moment at
202+
# least some of them are tied to existing features
203+
ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
204+
end
205+
167206
# CPP files that are part of the project library under test
168207
# @return [Array<Pathname>]
169208
def cpp_files
170-
cpp_files_in(@base_dir).reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
209+
ret = if one_point_five?
210+
code_files_in_recursive(@base_dir + "src", CPP_EXTENSIONS)
211+
else
212+
[@base_dir, @base_dir + "utility"].map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten
213+
end
214+
215+
# note to future troubleshooter: some of these tests may not be relevant, but at the moment at
216+
# least some of them are tied to existing features
217+
ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
171218
end
172219

173220
# CPP files that are part of the arduino mock library we're providing
174221
# @return [Array<Pathname>]
175222
def cpp_files_arduino
176-
cpp_files_in(ARDUINO_HEADER_DIR)
223+
code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
177224
end
178225

179226
# CPP files that are part of the unit test library we're providing
180227
# @return [Array<Pathname>]
181228
def cpp_files_unittest
182-
cpp_files_in(UNITTEST_HEADER_DIR)
229+
code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
183230
end
184231

185232
# CPP files that are part of the 3rd-party libraries we're including
186233
# @param [Array<String>] aux_libraries
187234
# @return [Array<Pathname>]
188235
def cpp_files_libraries(aux_libraries)
189-
arduino_library_src_dirs(aux_libraries).map { |d| cpp_files_in(d) }.flatten.uniq
236+
arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq
190237
end
191238

192239
# Returns the Pathnames for all paths to exclude from testing and compilation
@@ -204,15 +251,13 @@ def tests_dir
204251
# The files provided by the user that contain unit tests
205252
# @return [Array<Pathname>]
206253
def test_files
207-
cpp_files_in(tests_dir)
254+
code_files_in(tests_dir, CPP_EXTENSIONS)
208255
end
209256

210257
# Find all directories in the project library that include C++ header files
211258
# @return [Array<Pathname>]
212259
def header_dirs
213-
real = @base_dir.realpath
214-
all_files = Find.find(real).map { |f| Pathname.new(f) }.reject(&:directory?)
215-
unbundled = all_files.reject { |path| vendor_bundle?(path) }
260+
unbundled = header_files.reject { |path| vendor_bundle?(path) }
216261
unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
217262
files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
218263
files.map(&:dirname).uniq
@@ -241,15 +286,8 @@ def gcc_version(gcc_binary)
241286
def arduino_library_src_dirs(aux_libraries)
242287
# Pull in all possible places that headers could live, according to the spec:
243288
# https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
244-
# TODO: be smart and implement library spec (library.properties, etc)?
245-
subdirs = ["", "src", "utility"]
246-
all_aux_include_dirs_nested = aux_libraries.map do |libdir|
247-
# library manager coerces spaces in package names to underscores
248-
# see https://github.com/Arduino-CI/arduino_ci/issues/132#issuecomment-518857059
249-
legal_libdir = libdir.tr(" ", "_")
250-
subdirs.map { |subdir| Pathname.new(@arduino_lib_dir) + legal_libdir + subdir }
251-
end
252-
all_aux_include_dirs_nested.flatten.select(&:exist?).select(&:directory?)
289+
290+
aux_libraries.map { |d| self.new(d, @arduino_lib_dir, @exclude_dirs).header_dirs }.flatten
253291
end
254292

255293
# GCC command line arguments for including aux libraries

Diff for: spec/cpp_library_spec.rb

+86-32
Original file line numberDiff line numberDiff line change
@@ -12,47 +12,101 @@ def get_relative_dir(sampleprojects_tests_dir)
1212

1313
RSpec.describe ArduinoCI::CppLibrary do
1414
next if skip_ruby_tests
15-
cpp_lib_path = sampleproj_path + "DoSomething"
16-
cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), [])
17-
context "cpp_files" do
18-
it "finds cpp files in directory" do
19-
dosomething_cpp_files = [Pathname.new("DoSomething") + "do-something.cpp"]
20-
relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) }
21-
expect(relative_paths).to match_array(dosomething_cpp_files)
22-
end
23-
end
24-
25-
context "header_dirs" do
26-
it "finds directories containing h files" do
27-
dosomething_header_dirs = [Pathname.new("DoSomething")]
28-
relative_paths = cpp_library.header_dirs.map { |f| get_relative_dir(f) }
29-
expect(relative_paths).to match_array(dosomething_header_dirs)
30-
end
31-
end
32-
33-
context "tests_dir" do
34-
it "locates the tests directory" do
35-
# since we don't know where the CI system will install this stuff,
36-
# we need to go looking for a relative path to the SampleProjects directory
37-
# just to get our "expected" value
38-
relative_path = get_relative_dir(cpp_library.tests_dir)
39-
expect(relative_path.to_s).to eq("DoSomething/test")
40-
end
41-
end
4215

43-
context "test_files" do
44-
it "finds cpp files in directory" do
45-
dosomething_test_files = [
16+
answers = {
17+
"DoSomething": {
18+
one_five: false,
19+
cpp_files: [Pathname.new("DoSomething") + "do-something.cpp"],
20+
header_dirs: [Pathname.new("DoSomething")],
21+
test_files: [
4622
"DoSomething/test/good-null.cpp",
4723
"DoSomething/test/good-library.cpp",
4824
"DoSomething/test/bad-null.cpp",
4925
].map { |f| Pathname.new(f) }
50-
relative_paths = cpp_library.test_files.map { |f| get_relative_dir(f) }
51-
expect(relative_paths).to match_array(dosomething_test_files)
26+
},
27+
"OnePointOhDummy": {
28+
one_five: false,
29+
cpp_files: [
30+
"OnePointOhDummy/YesBase.cpp",
31+
"OnePointOhDummy/utility/YesUtil.cpp",
32+
].map { |f| Pathname.new(f) },
33+
header_dirs: [
34+
"OnePointOhDummy",
35+
"OnePointOhDummy/utility"
36+
].map { |f| Pathname.new(f) },
37+
test_files: []
38+
},
39+
"OnePointFiveMalformed": {
40+
one_five: false,
41+
cpp_files: [
42+
"OnePointFiveMalformed/YesBase.cpp",
43+
"OnePointFiveMalformed/utility/YesUtil.cpp",
44+
].map { |f| Pathname.new(f) },
45+
header_dirs: [
46+
"OnePointFiveMalformed",
47+
"OnePointFiveMalformed/utility"
48+
].map { |f| Pathname.new(f) },
49+
test_files: []
50+
},
51+
"OnePointFiveDummy": {
52+
one_five: true,
53+
cpp_files: [
54+
"OnePointFiveDummy/src/YesSrc.cpp",
55+
"OnePointFiveDummy/src/subdir/YesSubdir.cpp",
56+
].map { |f| Pathname.new(f) },
57+
header_dirs: [
58+
"OnePointFiveDummy/src",
59+
"OnePointFiveDummy/src/subdir",
60+
].map { |f| Pathname.new(f) },
61+
test_files: []
62+
}
63+
}.freeze
64+
65+
answers.each do |sampleproject, expected|
66+
context "#{sampleproject}" do
67+
cpp_lib_path = sampleproj_path + sampleproject.to_s
68+
cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), [])
69+
70+
it "detects 1.5 format" do
71+
expect(cpp_library.one_point_five?).to eq(expected[:one_five])
72+
end
73+
74+
context "cpp_files" do
75+
it "finds cpp files in directory" do
76+
relative_paths = cpp_library.cpp_files.map { |f| get_relative_dir(f) }
77+
expect(relative_paths.map(&:to_s)).to match_array(expected[:cpp_files].map(&:to_s))
78+
end
79+
end
80+
81+
context "header_dirs" do
82+
it "finds directories containing h files" do
83+
relative_paths = cpp_library.header_dirs.map { |f| get_relative_dir(f) }
84+
expect(relative_paths.map(&:to_s)).to match_array(expected[:header_dirs].map(&:to_s))
85+
end
86+
end
87+
88+
context "tests_dir" do
89+
it "locates the tests directory" do
90+
# since we don't know where the CI system will install this stuff,
91+
# we need to go looking for a relative path to the SampleProjects directory
92+
# just to get our "expected" value
93+
relative_path = get_relative_dir(cpp_library.tests_dir)
94+
expect(relative_path.to_s).to eq("#{sampleproject}/test")
95+
end
96+
end
97+
98+
context "test_files" do
99+
it "finds cpp files in directory" do
100+
relative_paths = cpp_library.test_files.map { |f| get_relative_dir(f) }
101+
expect(relative_paths.map(&:to_s)).to match_array(expected[:test_files].map(&:to_s))
102+
end
103+
end
52104
end
53105
end
54106

55107
context "test" do
108+
cpp_lib_path = sampleproj_path + "DoSomething"
109+
cpp_library = ArduinoCI::CppLibrary.new(cpp_lib_path, Pathname.new("my_fake_arduino_lib_dir"), [])
56110
config = ArduinoCI::CIConfig.default
57111

58112
after(:each) do |example|

0 commit comments

Comments
 (0)