22require "arduino_ci/host"
33require 'pathname'
44require 'shellwords'
5+ require 'os'
56
67HPP_EXTENSIONS = [ ".hpp" , ".hh" , ".h" , ".hxx" , ".h++" ] . freeze
78CPP_EXTENSIONS = [ ".cpp" , ".cc" , ".c" , ".cxx" , ".c++" ] . freeze
89CI_CPP_DIR = Pathname . new ( __dir__ ) . parent . parent + "cpp"
910ARDUINO_HEADER_DIR = CI_CPP_DIR + "arduino"
1011UNITTEST_HEADER_DIR = CI_CPP_DIR + "unittest"
12+ LIBRARY_NAME = "arduino" . freeze
13+ BUILD_DIR = "#{ Dir . pwd } /.arduino_ci" . freeze # hide build artifacts
1114
1215module ArduinoCI
1316
@@ -464,16 +467,15 @@ def flag_args(ci_gcc_config)
464467 ci_gcc_config [ :flags ]
465468 end
466469
467- # All GCC command line args for building any unit test
470+ # All non-CPP GCC command line args for building any unit test.
471+ # We leave out the CPP files so they can be included or not
472+ # depending on whether we are building a shared library.
468473 # @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
469474 # @param ci_gcc_config [Hash] The GCC config object
470475 # @return [Array<String>] GCC command-line flags
471476 def test_args ( aux_libraries , ci_gcc_config )
472477 # TODO: something with libraries?
473478 ret = include_args ( aux_libraries )
474- ret += cpp_files_arduino . map ( &:to_s )
475- ret += cpp_files_unittest . map ( &:to_s )
476- ret += cpp_files . map ( &:to_s )
477479 unless ci_gcc_config . nil?
478480 cgc = ci_gcc_config
479481 ret = feature_args ( cgc ) + warning_args ( cgc ) + define_args ( cgc ) + flag_args ( cgc ) + ret
@@ -486,18 +488,52 @@ def test_args(aux_libraries, ci_gcc_config)
486488 # The dependent libraries configuration is appended with data from library.properties internal to the library under test
487489 #
488490 # @param test_file [Pathname] The path to the file containing the unit tests
491+ # @param gcc_binary [String] name of a compiler
492+ # @return [Pathname] path to the compiled test executable
493+ def build_for_test ( test_file , gcc_binary )
494+ executable = Pathname . new ( "#{ BUILD_DIR } /#{ test_file . basename } .bin" ) . expand_path
495+ File . delete ( executable ) if File . exist? ( executable )
496+ arg_sets = [ "-std=c++0x" , "-o" , executable . to_s , "-L#{ BUILD_DIR } " , "-DARDUINO=100" ]
497+ if libasan? ( gcc_binary )
498+ arg_sets << [ # Stuff to help with dynamic memory mishandling
499+ "-g" , "-O1" ,
500+ "-fno-omit-frame-pointer" ,
501+ "-fno-optimize-sibling-calls" ,
502+ "-fsanitize=address"
503+ ]
504+ end
505+ arg_sets << @test_args
506+ arg_sets << [ test_file . to_s , "-l#{ LIBRARY_NAME } " ]
507+ args = arg_sets . flatten ( 1 )
508+ return nil unless run_gcc ( gcc_binary , *args )
509+
510+ artifacts << executable
511+ executable
512+ end
513+
514+ # build a shared library to be used by each test
515+ #
516+ # The dependent libraries configuration is appended with data from library.properties internal to the library under test
517+ #
489518 # @param aux_libraries [Array<Pathname>] The external Arduino libraries required by this project
519+ # @param gcc_binary [String] name of a compiler
490520 # @param ci_gcc_config [Hash] The GCC config object
491521 # @return [Pathname] path to the compiled test executable
492- def build_for_test_with_configuration ( test_file , aux_libraries , gcc_binary , ci_gcc_config )
493- base = test_file . basename
494- # hide build artifacts
495- build_dir = '.arduino_ci'
496- Dir . mkdir build_dir unless File . exist? ( build_dir )
497- executable = Pathname . new ( "#{ build_dir } /unittest_#{ base } .bin" ) . expand_path
522+ def build_shared_library ( aux_libraries , gcc_binary , ci_gcc_config )
523+ Dir . mkdir BUILD_DIR unless File . exist? ( BUILD_DIR )
524+ if OS . windows?
525+ flag = ENV [ "PATH" ] . include? ";"
526+ ENV [ "PATH" ] = BUILD_DIR + ( flag ? ";" : ":" ) + ENV [ "PATH" ] unless ENV [ "PATH" ] . include? BUILD_DIR
527+ suffix = "dll"
528+ else
529+ ENV [ "LD_LIBRARY_PATH" ] = BUILD_DIR
530+ suffix = "so"
531+ end
532+ full_lib_name = "#{ BUILD_DIR } /lib#{ LIBRARY_NAME } .#{ suffix } "
533+ executable = Pathname . new ( full_lib_name ) . expand_path
498534 File . delete ( executable ) if File . exist? ( executable )
499- arg_sets = [ ]
500- arg_sets << [ "-std=c++0x" , "-o" , executable . to_s , "-DARDUINO=100" ]
535+ arg_sets = [ "-std=c++0x" , "-shared" , "-fPIC" , "-Wl,-undefined,dynamic_lookup" ,
536+ "-o" , executable . to_s , "-L #{ BUILD_DIR } " , "-DARDUINO=100" ]
501537 if libasan? ( gcc_binary )
502538 arg_sets << [ # Stuff to help with dynamic memory mishandling
503539 "-g" , "-O1" ,
@@ -509,10 +545,15 @@ def build_for_test_with_configuration(test_file, aux_libraries, gcc_binary, ci_g
509545
510546 # combine library.properties defs (if existing) with config file.
511547 # 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
512- full_dependencies = all_arduino_library_dependencies! ( aux_libraries )
513- arg_sets << test_args ( full_dependencies , ci_gcc_config )
514- arg_sets << cpp_files_libraries ( full_dependencies ) . map ( &:to_s )
515- arg_sets << [ test_file . to_s ]
548+ # the following two take some time, so are cached when we build the shared library
549+ @full_dependencies = all_arduino_library_dependencies! ( aux_libraries )
550+ @test_args = test_args ( @full_dependencies , ci_gcc_config ) # build full set of include directories to be cached for later
551+
552+ arg_sets << @test_args
553+ arg_sets << cpp_files_arduino . map ( &:to_s ) # Arduino.cpp, Godmode.cpp, and stdlib.cpp
554+ arg_sets << cpp_files_unittest . map ( &:to_s ) # ArduinoUnitTests.cpp
555+ arg_sets << cpp_files . map ( &:to_s ) # CPP files for the primary application library under test
556+ arg_sets << cpp_files_libraries ( @full_dependencies ) . map ( &:to_s ) # CPP files for all the libraries we depend on
516557 args = arg_sets . flatten ( 1 )
517558 return nil unless run_gcc ( gcc_binary , *args )
518559
0 commit comments