Class: ArduinoCI::CppLibrary

Inherits:
Object
  • Object
show all
Defined in:
lib/arduino_ci/cpp_library.rb

Overview

Information about an Arduino CPP library, specifically for compilation purposes

Constant Summary collapse

LIBRARY_PROPERTIES_FILE =

Returns The official library properties file name.

Returns:

  • (String)

    The official library properties file name

"library.properties".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(friendly_name, backend) ⇒ CppLibrary

Returns a new instance of CppLibrary.

Parameters:

  • friendly_name (String)

    The “official” name of the library, which can contain spaces

  • backend (ArduinoBackend)

    The support backend

Raises:

  • (ArgumentError)


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/arduino_ci/cpp_library.rb', line 50

def initialize(friendly_name, backend)
  raise ArgumentError, "friendly_name is not a String (got #{friendly_name.class})" unless friendly_name.is_a? String
  raise ArgumentError, 'backend is not a ArduinoBackend' unless backend.is_a? ArduinoBackend

  @name = friendly_name
  @backend = backend
  @info_cache = nil
  @artifacts = []
  @last_err = ""
  @last_out = ""
  @last_msg = ""
  @has_libasan_cache = {}
  @vendor_bundle_cache = nil
  @exclude_dirs = []
end

Instance Attribute Details

#artifactsArray<Pathname> (readonly)

Returns The set of artifacts created by this class (note: incomplete!).

Returns:

  • (Array<Pathname>)

    The set of artifacts created by this class (note: incomplete!)



31
32
33
# File 'lib/arduino_ci/cpp_library.rb', line 31

def artifacts
  @artifacts
end

#backendArduinoBackend (readonly)

Returns The backend support for this library.

Returns:



28
29
30
# File 'lib/arduino_ci/cpp_library.rb', line 28

def backend
  @backend
end

#exclude_dirsArray<Pathname>

Returns The set of directories that should be excluded from compilation.

Returns:

  • (Array<Pathname>)

    The set of directories that should be excluded from compilation



34
35
36
# File 'lib/arduino_ci/cpp_library.rb', line 34

def exclude_dirs
  @exclude_dirs
end

#last_cmdString (readonly)

Returns the last command.

Returns:

  • (String)

    the last command



43
44
45
# File 'lib/arduino_ci/cpp_library.rb', line 43

def last_cmd
  @last_cmd
end

#last_errString (readonly)

Returns STDERR from the last command.

Returns:

  • (String)

    STDERR from the last command



37
38
39
# File 'lib/arduino_ci/cpp_library.rb', line 37

def last_err
  @last_err
end

#last_outString (readonly)

Returns STDOUT from the last command.

Returns:

  • (String)

    STDOUT from the last command



40
41
42
# File 'lib/arduino_ci/cpp_library.rb', line 40

def last_out
  @last_out
end

#nameString (readonly)

Returns The “official” name of the library, which can include spaces (in a way that the lib dir won’t).

Returns:

  • (String)

    The “official” name of the library, which can include spaces (in a way that the lib dir won’t)



25
26
27
# File 'lib/arduino_ci/cpp_library.rb', line 25

def name
  @name
end

#vendor_bundle_cacheArray<Pathname> (readonly)

Returns Directories suspected of being vendor-bundle.

Returns:

  • (Array<Pathname>)

    Directories suspected of being vendor-bundle



46
47
48
# File 'lib/arduino_ci/cpp_library.rb', line 46

def vendor_bundle_cache
  @vendor_bundle_cache
end

Class Method Details

.library_directory_name(friendly_name) ⇒ String

Generate a guess as to the on-disk (coerced character) name of this library

@TODO: delegate this to the backend in some way? It uses “official” names for install, but dir names in lists :(

Parameters:

  • friendly_name (String)

    The library name as it might appear in library manager

Returns:

  • (String)

    How the path will be stored on disk – spaces are coerced to underscores



71
72
73
# File 'lib/arduino_ci/cpp_library.rb', line 71

def self.library_directory_name(friendly_name)
  friendly_name.tr(" ", "_")
end

Instance Method Details

#add_build_dirs_to_envObject

Add build dir to path



517
518
519
520
521
522
523
# File 'lib/arduino_ci/cpp_library.rb', line 517

def add_build_dirs_to_env
  ENV["LD_LIBRARY_PATH"] = BUILD_DIR unless OS.windows?
  paths = ENV["PATH"].split(File::PATH_SEPARATOR)
  return if paths.include?(BUILD_DIR)

  ENV["PATH"] = BUILD_DIR + File::PATH_SEPARATOR + ENV["PATH"] if OS.windows?
end

#all_arduino_library_dependencies!(additional_libraries = []) ⇒ Array<String>

Arduino library dependencies all the way down, installing if they are not present

Returns:

  • (Array<String>)

    The library names of the dependencies (not the paths)



407
408
409
410
411
412
413
414
415
416
# File 'lib/arduino_ci/cpp_library.rb', line 407

def all_arduino_library_dependencies!(additional_libraries = [])
  # Pull in all possible places that headers could live, according to the spec:
  # https://github.com/arduino/Arduino/wiki/Arduino-IDE-1.5:-Library-specification
  recursive = (additional_libraries + arduino_library_dependencies).map do |n|
    other_lib = self.class.new(n, @backend)
    other_lib.install unless other_lib.installed?
    other_lib.all_arduino_library_dependencies!
  end.flatten
  (additional_libraries + recursive).uniq
end

#arduino_library_dependenciesArray<String>

Get a list of all dependencies as defined in library.properties

Returns:

  • (Array<String>)

    The library names of the dependencies (not the paths)



398
399
400
401
402
403
# File 'lib/arduino_ci/cpp_library.rb', line 398

def arduino_library_dependencies
  return [] unless library_properties?
  return [] if library_properties.depends.nil?

  library_properties.depends
end

#arduino_library_src_dirs(aux_libraries) ⇒ Array<Pathname>

Arduino library directories containing sources – only those of the dependencies

Returns:

  • (Array<Pathname>)


420
421
422
# File 'lib/arduino_ci/cpp_library.rb', line 420

def arduino_library_src_dirs(aux_libraries)
  all_arduino_library_dependencies!(aux_libraries).map { |l| self.class.new(l, @backend).header_dirs }.flatten.uniq
end

#build_for_test(test_file, gcc_binary) ⇒ Pathname

build a file for running a test of the given unit test file

The dependent libraries configuration is appended with data from library.properties internal to the library under test

Parameters:

  • test_file (Pathname)

    The path to the file containing the unit tests

  • gcc_binary (String)

    name of a compiler

Returns:

  • (Pathname)

    path to the compiled test executable



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
# File 'lib/arduino_ci/cpp_library.rb', line 495

def build_for_test(test_file, gcc_binary)
  executable = Pathname.new("#{BUILD_DIR}/#{test_file.basename}.bin").expand_path
  File.delete(executable) if File.exist?(executable)
  arg_sets = ["-std=c++0x", "-o", executable.to_s, "-L#{BUILD_DIR}", "-DARDUINO=100"]
  if libasan?(gcc_binary)
    arg_sets << [ # Stuff to help with dynamic memory mishandling
      "-g", "-O1",
      "-fno-omit-frame-pointer",
      "-fno-optimize-sibling-calls",
      "-fsanitize=address"
    ]
  end
  arg_sets << @test_args
  arg_sets << [test_file.to_s, "-l#{LIBRARY_NAME}"]
  args = arg_sets.flatten(1)
  return nil unless run_gcc(gcc_binary, *args)

  artifacts << executable
  executable
end

#build_shared_library(aux_libraries, gcc_binary, ci_gcc_config) ⇒ Pathname

build a shared library to be used by each test

The dependent libraries configuration is appended with data from library.properties internal to the library under test

Parameters:

  • aux_libraries (Array<Pathname>)

    The external Arduino libraries required by this project

  • gcc_binary (String)

    name of a compiler

  • ci_gcc_config (Hash)

    The GCC config object

Returns:

  • (Pathname)

    path to the compiled test executable



533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/arduino_ci/cpp_library.rb', line 533

def build_shared_library(aux_libraries, gcc_binary, ci_gcc_config)
  FileUtils.mkdir_p(BUILD_DIR)
  add_build_dirs_to_env
  suffix = OS.windows? ? "dll" : "so"
  full_lib_name = "#{BUILD_DIR}/lib#{LIBRARY_NAME}.#{suffix}"
  executable = Pathname.new(full_lib_name).expand_path
  File.delete(executable) if File.exist?(executable)
  arg_sets = ["-std=c++0x", "-shared", "-fPIC", "-Wl,-undefined,dynamic_lookup",
              "-o", executable.to_s, "-L#{BUILD_DIR}", "-DARDUINO=100"]
  if libasan?(gcc_binary)
    arg_sets << [ # Stuff to help with dynamic memory mishandling
      "-g", "-O1",
      "-fno-omit-frame-pointer",
      "-fno-optimize-sibling-calls",
      "-fsanitize=address"
    ]
  end

  # combine library.properties defs (if existing) with config file.
  # TODO: as much as I'd like to rely only on the properties file(s), I think that would prevent testing 1.0-spec libs
  # the following two take some time, so are cached when we build the shared library
  @full_dependencies = all_arduino_library_dependencies!(aux_libraries)
  @test_args = test_args(@full_dependencies, ci_gcc_config) # build full set of include directories to be cached for later

  arg_sets << @test_args
  arg_sets << cpp_files_arduino.map(&:to_s)  # Arduino.cpp, Godmode.cpp, and stdlib.cpp
  arg_sets << cpp_files_unittest.map(&:to_s) # ArduinoUnitTests.cpp
  arg_sets << cpp_files.map(&:to_s) # CPP files for the primary application library under test
  arg_sets << cpp_files_libraries(@full_dependencies).map(&:to_s) # CPP files for all the libraries we depend on
  args = arg_sets.flatten(1)
  return nil unless run_gcc(gcc_binary, *args)

  artifacts << executable
  executable
end

#code_files_in(some_dir, extensions) ⇒ Array<Pathname>

Get a list of all CPP source files in a directory and its subdirectories

Parameters:

  • some_dir (Pathname)

    The directory in which to begin the search

  • extensions (Array<String>)

    The set of allowable file extensions

Returns:

  • (Array<Pathname>)

    The paths of the found files

Raises:

  • (ArgumentError)


281
282
283
284
285
286
287
288
289
290
291
# File 'lib/arduino_ci/cpp_library.rb', line 281

def code_files_in(some_dir, extensions)
  raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname

  full_dir = path + some_dir
  return [] unless full_dir.exist? && full_dir.directory?

  files = full_dir.children.reject(&:directory?)
  cpp = files.select { |path| extensions.include?(path.extname.downcase) }
  not_hidden = cpp.reject { |path| path.basename.to_s.start_with?(".") }
  not_hidden.sort_by(&:to_s)
end

#code_files_in_recursive(some_dir, extensions) ⇒ Array<Pathname>

Get a list of all CPP source files in a directory and its subdirectories

Parameters:

  • some_dir (Pathname)

    The directory in which to begin the search

  • extensions (Array<String>)

    The set of allowable file extensions

Returns:

  • (Array<Pathname>)

    The paths of the found files

Raises:

  • (ArgumentError)


297
298
299
300
301
302
# File 'lib/arduino_ci/cpp_library.rb', line 297

def code_files_in_recursive(some_dir, extensions)
  raise ArgumentError, 'some_dir is not a Pathname' unless some_dir.is_a? Pathname
  return [] unless some_dir.exist? && some_dir.directory?

  Find.find(some_dir).map { |p| Pathname.new(p) }.select(&:directory?).map { |d| code_files_in(d, extensions) }.flatten
end

#cpp_filesArray<Pathname>

CPP files that are part of the project library under test

Returns:

  • (Array<Pathname>)


328
329
330
# File 'lib/arduino_ci/cpp_library.rb', line 328

def cpp_files
  source_files(CPP_EXTENSIONS)
end

#cpp_files_arduinoArray<Pathname>

CPP files that are part of the arduino mock library we’re providing

Returns:

  • (Array<Pathname>)


334
335
336
# File 'lib/arduino_ci/cpp_library.rb', line 334

def cpp_files_arduino
  code_files_in(ARDUINO_HEADER_DIR, CPP_EXTENSIONS)
end

#cpp_files_libraries(aux_libraries) ⇒ Array<Pathname>

CPP files that are part of the 3rd-party libraries we’re including

Parameters:

  • aux_libraries (Array<String>)

Returns:

  • (Array<Pathname>)


347
348
349
# File 'lib/arduino_ci/cpp_library.rb', line 347

def cpp_files_libraries(aux_libraries)
  arduino_library_src_dirs(aux_libraries).map { |d| code_files_in(d, CPP_EXTENSIONS) }.flatten.uniq
end

#cpp_files_unittestArray<Pathname>

CPP files that are part of the unit test library we’re providing

Returns:

  • (Array<Pathname>)


340
341
342
# File 'lib/arduino_ci/cpp_library.rb', line 340

def cpp_files_unittest
  code_files_in(UNITTEST_HEADER_DIR, CPP_EXTENSIONS)
end

#define_args(ci_gcc_config) ⇒ Array<String>

GCC command line arguments for defines (e.g. -Dhave_something)

Parameters:

  • ci_gcc_config (Hash)

    The GCC config object

Returns:

  • (Array<String>)

    GCC command-line flags



457
458
459
460
461
# File 'lib/arduino_ci/cpp_library.rb', line 457

def define_args(ci_gcc_config)
  return [] if ci_gcc_config[:defines].nil?

  ci_gcc_config[:defines].map { |d| "-D#{d}" }
end

#example_sketchesArray<String>

Returns Example sketch files.

Parameters:

  • installed_library_path (String)

    The library to query

Returns:

  • (Array<String>)

    Example sketch files



138
139
140
141
142
# File 'lib/arduino_ci/cpp_library.rb', line 138

def example_sketches
  examples = info["library"].fetch("examples", [])
  reported_dirs = examples.map(&Pathname::method(:new))
  reported_dirs.map { |e| e + e.basename.sub_ext(".ino") }.select(&:exist?).sort_by(&:to_s)
end

#examples_dirString

Returns The parent directory of all examples.

Returns:

  • (String)

    The parent directory of all examples



90
91
92
# File 'lib/arduino_ci/cpp_library.rb', line 90

def examples_dir
  path + "examples"
end

#exclude_dirArray<Pathname>

Returns the Pathnames for all paths to exclude from testing and compilation

Returns:

  • (Array<Pathname>)


353
354
355
# File 'lib/arduino_ci/cpp_library.rb', line 353

def exclude_dir
  @exclude_dirs.map { |p| Pathname.new(path) + p }.select(&:exist?)
end

#feature_args(ci_gcc_config) ⇒ Array<String>

GCC command line arguments for features (e.g. -fno-weak)

Parameters:

  • ci_gcc_config (Hash)

    The GCC config object

Returns:

  • (Array<String>)

    GCC command-line flags



439
440
441
442
443
# File 'lib/arduino_ci/cpp_library.rb', line 439

def feature_args(ci_gcc_config)
  return [] if ci_gcc_config[:features].nil?

  ci_gcc_config[:features].map { |f| "-f#{f}" }
end

#flag_args(ci_gcc_config) ⇒ Array<String>

GCC command line arguments as-is

Parameters:

  • ci_gcc_config (Hash)

    The GCC config object

Returns:

  • (Array<String>)

    GCC command-line flags



466
467
468
469
470
# File 'lib/arduino_ci/cpp_library.rb', line 466

def flag_args(ci_gcc_config)
  return [] if ci_gcc_config[:flags].nil?

  ci_gcc_config[:flags]
end

#gcc_version(gcc_binary) ⇒ String

Return the GCC version

Returns:

  • (String)

    the version reported by ‘gcc -v`



390
391
392
393
394
# File 'lib/arduino_ci/cpp_library.rb', line 390

def gcc_version(gcc_binary)
  return nil unless run_gcc(gcc_binary, "-v")

  @last_err
end

#header_dirsArray<Pathname>

Find all directories in the project library that include C++ header files

Returns:

  • (Array<Pathname>)


371
372
373
374
375
376
# File 'lib/arduino_ci/cpp_library.rb', line 371

def header_dirs
  unbundled = header_files.reject { |path| vendor_bundle?(path) }
  unexcluded = unbundled.reject { |path| in_exclude_dir?(path) }
  files = unexcluded.select { |path| HPP_EXTENSIONS.include?(path.extname.downcase) }
  files.map(&:dirname).uniq
end

#header_filesArray<Pathname>

Header files that are part of the project library under test

Returns:

  • (Array<Pathname>)


322
323
324
# File 'lib/arduino_ci/cpp_library.rb', line 322

def header_files
  source_files(HPP_EXTENSIONS)
end

#in_exclude_dir?(sourcefile_path) ⇒ bool

Guess whether a file is part of any @excludes_dir dir (indicating library compilation should ignore it).

Parameters:

  • path (Pathname)

    The path to check

Returns:

  • (bool)


253
254
255
256
257
258
259
260
# File 'lib/arduino_ci/cpp_library.rb', line 253

def in_exclude_dir?(sourcefile_path)
  # we could do this but some rubies don't return an enumerator for ascend
  # path.ascend.any? { |part| tests_dir_aliases.include?(part) }
  sourcefile_path.ascend do |part|
    return true if exclude_dir.any? { |p| p.realpath == part.realpath }
  end
  false
end

#in_tests_dir?(sourcefile_path) ⇒ bool

Guess whether a file is part of the tests/ dir (indicating library compilation should ignore it).

Parameters:

  • path (Pathname)

    The path to check

Returns:

  • (bool)


237
238
239
240
241
242
243
244
245
246
247
# File 'lib/arduino_ci/cpp_library.rb', line 237

def in_tests_dir?(sourcefile_path)
  return false unless tests_dir.exist?

  tests_dir_aliases = [tests_dir, tests_dir.realpath]
  # we could do this but some rubies don't return an enumerator for ascend
  # path.ascend.any? { |part| tests_dir_aliases.include?(part) }
  sourcefile_path.ascend do |part|
    return true if tests_dir_aliases.include?(part)
  end
  false
end

#include_args(aux_libraries) ⇒ Array<String>

GCC command line arguments for including aux libraries

This function recursively collects the library directories of the dependencies

Parameters:

  • aux_libraries (Array<Pathname>)

    The external Arduino libraries required by this project

Returns:

  • (Array<String>)

    The GCC command-line flags necessary to include those libraries



430
431
432
433
434
# File 'lib/arduino_ci/cpp_library.rb', line 430

def include_args(aux_libraries)
  all_aux_include_dirs = arduino_library_src_dirs(aux_libraries)
  places = [ARDUINO_HEADER_DIR, UNITTEST_HEADER_DIR] + header_dirs + all_aux_include_dirs
  places.map { |d| "-I#{d}" }
end

#infoHash

information about the library as reported by the backend

Returns:

  • (Hash)

    the metadata object



122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/arduino_ci/cpp_library.rb', line 122

def info
  return nil unless installed?

  # note that if the library isn't found, we're going to do a lot of cache attempts...
  if @info_cache.nil?
    @info_cache = @backend.installed_libraries.find do |l|
      lib_info = l["library"]
      Pathname.new(lib_info["install_dir"]).realpath == path.realpath
    end
  end

  @info_cache
end

#install(version = nil, recursive = false) ⇒ bool

install a library by name

Parameters:

  • version (String) (defaults to: nil)

    the version to install

  • recursive (bool) (defaults to: false)

    whether to also install its dependencies

Returns:

  • (bool)

    whether the command succeeded



108
109
110
111
112
113
114
115
116
117
118
# File 'lib/arduino_ci/cpp_library.rb', line 108

def install(version = nil, recursive = false)
  return true if installed? && !recursive

  fqln = version.nil? ? @name : "#{@name}@#{version}"
  result = if recursive
    @backend.run_and_capture("lib", "install", fqln)
  else
    @backend.run_and_capture("lib", "install", "--no-deps", fqln)
  end
  result[:success]
end

#installed?bool

Determine whether a library is present in the lib dir

Note that ‘true` doesn’t guarantee that the library is valid/installed

and `false` doesn't guarantee that the library isn't built-in

Returns:

  • (bool)


100
101
102
# File 'lib/arduino_ci/cpp_library.rb', line 100

def installed?
  path.exist? || path.symlink?
end

#libasan?(gcc_binary) ⇒ Boolean

Check whether libasan (and by extension -fsanitizer=address) is supported

This requires compilation of a sample program, and will be cached

Parameters:

  • gcc_binary (String)

Returns:

  • (Boolean)


266
267
268
269
270
271
272
273
274
275
# File 'lib/arduino_ci/cpp_library.rb', line 266

def libasan?(gcc_binary)
  unless @has_libasan_cache.key?(gcc_binary)
    Tempfile.create(["arduino_ci_libasan_check", ".cpp"]) do |file|
      file.write "int main(){}"
      file.close
      @has_libasan_cache[gcc_binary] = run_gcc(gcc_binary, "-o", "/dev/null", "-fsanitize=address", file.path)
    end
  end
  @has_libasan_cache[gcc_binary]
end

#library_propertiesLibraryProperties

Library properties

Returns:



159
160
161
162
163
# File 'lib/arduino_ci/cpp_library.rb', line 159

def library_properties
  return nil unless library_properties?

  LibraryProperties.new(library_properties_path)
end

#library_properties?bool

Whether library.properties definitions for this library exist

Returns:

  • (bool)


152
153
154
155
# File 'lib/arduino_ci/cpp_library.rb', line 152

def library_properties?
  lib_props = library_properties_path
  lib_props.exist? && lib_props.file?
end

#library_properties_pathPathname

The expected path to the library.properties file (i.e. even if it does not exist)

Returns:

  • (Pathname)


146
147
148
# File 'lib/arduino_ci/cpp_library.rb', line 146

def library_properties_path
  path + LIBRARY_PROPERTIES_FILE
end

#name_on_diskString

Generate a guess as to the on-disk (coerced character) name of this library

@TODO: delegate this to the backend in some way? It uses “official” names for install, but dir names in lists :(

Returns:

  • (String)

    How the path will be stored on disk – spaces are coerced to underscores



79
80
81
# File 'lib/arduino_ci/cpp_library.rb', line 79

def name_on_disk
  self.class.library_directory_name(@name)
end

#one_point_five?bool

Decide whether this is a 1.5-compatible library

This should be according to arduino.github.io/arduino-cli/latest/library-specification but we rely on the cli to decide for us

Returns:

  • (bool)


176
177
178
179
180
181
# File 'lib/arduino_ci/cpp_library.rb', line 176

def one_point_five?
  return false unless library_properties?

  src_dir = path + "src"
  src_dir.exist? && src_dir.directory?
end

#pathPathname

Get the path to this library, whether or not it exists

Returns:

  • (Pathname)

    The fully qualified library path



85
86
87
# File 'lib/arduino_ci/cpp_library.rb', line 85

def path
  @backend.lib_dir + name_on_disk
end

print any found stack dumps

Parameters:

  • executable (Pathname)

    the path to the test file



571
572
573
574
575
576
577
578
579
# File 'lib/arduino_ci/cpp_library.rb', line 571

def print_stack_dump(executable)
  possible_dumpfiles = [
    executable.sub_ext("#{executable.extname}.stackdump")
  ]
  possible_dumpfiles.select(&:exist?).each do |dump|
    puts "========== Stack dump from #{dump}:"
    File.foreach(dump) { |line| print "    #{line}" }
  end
end

#run_gcc(gcc_binary, *args, **kwargs) ⇒ Object

wrapper for the GCC command



379
380
381
382
383
384
385
386
# File 'lib/arduino_ci/cpp_library.rb', line 379

def run_gcc(gcc_binary, *args, **kwargs)
  full_args = [gcc_binary] + args
  @last_cmd = " $ #{full_args.join(' ')}"
  ret = Host.run_and_capture(*full_args, **kwargs)
  @last_err = ret[:err]
  @last_out = ret[:out]
  ret[:success]
end

#run_test_file(executable) ⇒ bool

run a test file

Parameters:

  • executable (Pathname)

    the path to the test file

Returns:

  • (bool)

    whether all tests were successful



584
585
586
587
588
589
590
591
592
593
594
# File 'lib/arduino_ci/cpp_library.rb', line 584

def run_test_file(executable)
  @last_cmd = executable
  @last_out = ""
  @last_err = ""
  ret = Host.run_and_output(executable.to_s.shellescape)

  # print any stack traces found during a failure
  print_stack_dump(executable) unless ret

  ret
end

#source_files(extensions) ⇒ Array<Pathname>

Source files that are part of the library under test

Parameters:

  • extensions (Array<String>)

    the allowed extensions (or, the ones we’re looking for)

Returns:

  • (Array<Pathname>)


307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/arduino_ci/cpp_library.rb', line 307

def source_files(extensions)
  source_dir = Pathname.new(info["library"]["source_dir"])
  ret = if one_point_five?
    code_files_in_recursive(source_dir, extensions)
  else
    [source_dir, source_dir + "utility"].map { |d| code_files_in(d, extensions) }.flatten
  end

  # note to future troubleshooter: some of these tests may not be relevant, but at the moment at
  # least some of them are tied to existing features
  ret.reject { |p| vendor_bundle?(p) || in_tests_dir?(p) || in_exclude_dir?(p) }
end

#test_args(aux_libraries, ci_gcc_config) ⇒ Array<String>

All non-CPP GCC command line args for building any unit test. We leave out the CPP files so they can be included or not depending on whether we are building a shared library.

Parameters:

  • aux_libraries (Array<Pathname>)

    The external Arduino libraries required by this project

  • ci_gcc_config (Hash)

    The GCC config object

Returns:

  • (Array<String>)

    GCC command-line flags



478
479
480
481
482
483
484
485
486
# File 'lib/arduino_ci/cpp_library.rb', line 478

def test_args(aux_libraries, ci_gcc_config)
  # TODO: something with libraries?
  ret = include_args(aux_libraries)
  unless ci_gcc_config.nil?
    cgc = ci_gcc_config
    ret = feature_args(cgc) + warning_args(cgc) + define_args(cgc) + flag_args(cgc) + ret
  end
  ret
end

#test_filesArray<Pathname>

The files provided by the user that contain unit tests

Returns:

  • (Array<Pathname>)


365
366
367
# File 'lib/arduino_ci/cpp_library.rb', line 365

def test_files
  code_files_in(tests_dir, CPP_EXTENSIONS)
end

#tests_dirPathname

The directory where we expect to find unit test definitions provided by the user

Returns:

  • (Pathname)


359
360
361
# File 'lib/arduino_ci/cpp_library.rb', line 359

def tests_dir
  Pathname.new(path) + "test"
end

#vendor_bundle?(some_path) ⇒ bool

Guess whether a file is part of the vendor bundle (indicating we should ignore it).

A safe way to do this seems to be to check whether any of the installed gems

appear to be a subdirectory of (but not equal to) the working directory.
That gets us the vendor directory (or multiple directories). We can check
if the given path is contained by any of those.

Parameters:

  • some_path (Pathname)

    The path to check

Returns:

  • (bool)


192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/arduino_ci/cpp_library.rb', line 192

def vendor_bundle?(some_path)
  # Cache bundle information, as it is (1) time consuming to fetch and (2) not going to change while we run
  if @vendor_bundle_cache.nil?
    bundle_info = Host.run_and_capture("bundle show --paths")
    if !bundle_info[:success]
      # if the bundle show command fails, assume there isn't a bundle
      @vendor_bundle_cache = false
    else
      # Get all the places where gems are stored.  We combine a few things here:
      # by preemptively switching to the parent directory, we can both ensure that
      # we skip any gems that are equal to the working directory AND exploit some
      # commonality in the paths to cut down our search locations
      #
      # NOT CONFUSING THE WORKING DIRECTORY WITH VENDOR BUNDLE IS SUPER IMPORTANT
      # because if we do, we won't be able to run CI on this library itself.
      bundle_paths = bundle_info[:out].lines
                                      .map { |l| Pathname.new(l.chomp) }
                                      .select(&:exist?)
                                      .map(&:realpath)
                                      .map(&:parent)
                                      .uniq
      wd = Pathname.new(".").realpath
      @vendor_bundle_cache = bundle_paths.select do |gem_path|
        gem_path.ascend do |part|
          break true if wd == part
        end
      end
    end
  end

  # no bundle existed
  return false if @vendor_bundle_cache == false

  # With vendor bundles located, check this file against those
  @vendor_bundle_cache.any? do |gem_path|
    some_path.ascend do |part|
      break true if gem_path == part
    end
  end
end

#warning_args(ci_gcc_config) ⇒ Array<String>

GCC command line arguments for warning (e.g. -Wall)

Parameters:

  • ci_gcc_config (Hash)

    The GCC config object

Returns:

  • (Array<String>)

    GCC command-line flags



448
449
450
451
452
# File 'lib/arduino_ci/cpp_library.rb', line 448

def warning_args(ci_gcc_config)
  return [] if ci_gcc_config[:warnings].nil?

  ci_gcc_config[:warnings].map { |w| "-W#{w}" }
end