"Multiple Definition" compile error when including header file via platformio.ini

I’ve been struggling for many hours trying to figure out why I’m getting a “Multiple Definition” compile error when I include a header file via the pio.ini file (eg. -include Single_ESP.h)

Note that the program compiles fine when I include the exact same file directly at the top of main.cpp

Here is the a snippet of error:

c:/users/achro/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/10.3.0/../../../../xtensa-lx106-elf/bin/ld.exe: .pio\build\singleTestESP8266\lib349\FastLED\bitswap.cpp.o:(.bss.densityEnable+0x0): multiple definition of `densityEnable'; .pio\build\singleTestESP8266\lib349\FastLED\FastLED.cpp.o:(.bss.densityEnable+0x0): first defined here
c:/users/achro/.platformio/packages/toolchain-xtensa/bin/../lib/gcc/xtensa-lx106-elf/10.3.0/../../../../xtensa-lx106-elf/bin/ld.exe: .pio\build\singleTestESP8266\lib349\FastLED\colorpalettes.cpp.o:(.bss.densityEnable+0x0): multiple definition of `densityEnable'; .pio\build\singleTestESP8266\lib349\FastLED\FastLED.cpp.o:(.bss.densityEnable+0x0): first defined here

The error gets repeated many more times for different files, but I’ve only showed part of it for brevity. For some reason, the complier things the boolean “densityEnable” is defined in multiple places, but I’ve confirmed (serached all files in project) that it is only defined once in the header file “Single_ESP.h”.

Also note, that the error does NOT occur if I include the header file directly in the top of main.cpp

Here is the header file Single_ESP.h:

// THIS IS A SINGLE ESP CONTROLLER SETUP
// - USED FOR TESTING
//

#include <Arduino.h>

/*##############################################################################################################################################*/
/* Need to change the following parameters before compiling for each controller to set their identity */
#ifndef ESP_ID
#define ESP_ID 0          // Number used to identify this particular LED controller in a multi-controller setup (MASTER = 0)
#endif                    //  - used to pass LED control to another controller in a multi-controller setup (coordinated with ESP_NOW)
                          //  - Controller '0' is the first controller, and ESP_NOW MASTER
                          //  - all other controllers are ESP_NOW clients
#define TOTAL_NUM_ESP 1   // Set to total number of ESP controllers in the multi-controller group
#define MASTER_DENSITY 60 // 30 LEDs/metre - MASTER Strip Density - used by CLIENTS to adjust various parameters to match speed and size of patterns


#if ESP_ID == 0
#define ESP_SN 1     // ESP Serial Number for this controller (SN marked with sharpie on each ESP eg. SN = 1 for ESP12-1 )
#define NUM_LEDS 600 // How many leds in your strip?
#define STRIP_DENSITY 60
bool densityEnable = 0;                    // flag to enable density adjutments to parameters (1 = adjustment allowed)
#define INV_REV 0                          // Used to invert chase_rev to align string orientation in multi-contrller setup
#define STASSID "1F-TP-Link_2.4GHz_9B52B8" // Network SSID


/* Parameters for the Firework pattern */
// MAX_HEIGHT is unique to a configuration, as it depends on how the strips are physically arranged
// eg. the Porch Lights have 600 LEDS, but they are arranged in a triangle, so Total height is 300 LEDS (middle of string), so want to set MAX_HEIGHT less than this to allow room for explosion

// this Config is just a single strip of 150 LEDS, so MAX_HEIGHT can be set based on NUM_LEDS
const uint16_t MAX_HEIGHT = NUM_LEDS;
const uint16_t MIN_HEIGHT = MAX_HEIGHT / 2;
#endif //ESP_ID ==

#define MASTER 1

/****************************************************************/
/* The following flags are used to:                             *
   - Select the board type (ESP vs Arduino)                     *
   - Enable Wifi code in the sketch                             *
*/
#define ESPboard 1 // set to 1 for ESP board
#define wifiGUI 1  // Set to 1 to include WiFi, 0 to exclude
/*##############################################################################################################################################*/

#ifdef ESP8266
#define FL_DATA_PIN D5 // do NOT use D0, as its connected to onboard LED on ESP12, and this drops the voltage too much for the signal to be recognized by WS2812 LEDs
#else
#define FL_DATA_PIN 19 // for ESP32
#endif

#define LED_TYPE WS2812B
#define COLOR_ORDER GRB // for strip LEDs


/* ==== SET PATTERN ORDER HERE ======================================================================================================================================================= */
/* The following is used to initialize array that is used to set the random order of the patterns when they are automatically cycled */
#define PATTERN_ORDER 32, 31, 30, 1, 3, 6, 8, 9, 10, 11, 12, 13, 14, 16, 19, 20, 22, 26 // 1st element is used as default pattern on boot up
/* =================================================================================================================================================================================== */

/* ESP12 has two UARTS (second one only has Tx). The #define below allows you to select which UART you want the
   Wifi setup and error messages to go to */
// #define logger (&Serial1) // this is for second UART on ESP, use this for normal operation of the "Serial Bridge"
#define logger (&Serial) // This is the primary UART on ESP that is conected to USB.

/* wifiGUI is a flag used to select the Input/Output device for the sketch by setting serialSel as follows:
   - "Serial" - will use the usual serial monitor and keyboard for I/O
   - "serverClient" - will use the WiFi client (eg. Android device) */
#if wifiGUI == 0
#define serialSel Serial
#define debugLog Serial
#else
#define serialSel serverClient
#define debugLog Serial
#endif // wifiGUI

#define BAUD_SERIAL 115200 // 57600
#define BAUD_LOGGER 115200 // 57600
#define RXBUFFERSIZE 256

Here is the .ini file:

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html


[platformio]
; default_envs = testESP32
; default_envs = testESP32_C3_mini
default_envs = singleTestESP8266


; ==================
; Base configuration
;
; Options that are used (or extended) in all device sections (and hence environments) are defined here

[base]
upload_port     =
monitor_port    =
build_type      = release
upload_speed    = 921600
lib_extra_dirs  = ~/Documents/Arduino/libraries
lib_deps        = 	fastled/FastLED @3.4.0 ;3.9.5, 3.6.0, 3.4.0, 3.3.3
			              arduino-libraries/NTPClient@^3.2.1

; Can find ESP8266 DEBUG flags here: https://docs.platformio.org/en/latest/platforms/espressif8266.html#serial-debug

;build_flags     =   -DDEBUG_ESP_CORE -DDEBUG_ESP_WIFI -DDEBUG_ESP_PORT=Serial
build_flags      = 	 ; needs this here, otherwise I get error in the "dev_"	definitions


; ===============
; Device sections
;
; Sections starting with "dev_" contain general settings for a particular type of device.
; They extend the "base" config, both in general and in terms of build flags and dependencies,
; where applicable.

[dev_nodemcuv2]
extends         = base
platform        = espressif8266
board 			= nodemcuv2
monitor_speed   = 115200
upload_speed    = 921600

[dev_d1minipro]
extends         = base
platform        = espressif8266
board 			= d1_mini_pro
monitor_speed   = 115200
upload_speed    = 921600

[dev_lolinESP32]
extends         = base
platform        = espressif32 @ 6.9.0
board 			= lolin32
monitor_speed   = 115200
upload_speed    = 921600


[dev_lolinESP32_C3_mini]
extends         = base
platform        = espressif32 @ 6.9.0
board           = lolin_c3_mini
monitor_speed   = 115200
upload_speed    = 921600


; ================================================================
; Basic environment section, automatically inherited by all others
;

[env]
framework       = arduino
build_type      = release


; ====================
; Project environments
;
; These are the actual environments defined in this file. Each configures one project for one
; specific device. They extend the "dev_" config for the device in question, both in general
; and in terms of build flags and dependencies, where applicable.
;
; Note: when adding a new environment to the list, don't forget to explicitly add the device
; section's build_flags and lib_deps options (using ${dev_<device>.<option>}) if you define
; either or both in the environment you add. PlatformIO does not merge them automatically.
; In addition to the build_flags and lib_deps of the device, capability flags and/or dependencies
; can be included where appropriate.

;=========
                            

[env:singleTestESP8266]
extends         = dev_nodemcuv2
build_flags     = -DESP_ID=0
                  ; -v    ;enable verbose build

                  -Isrc ; note that you need forward-slash (/) if you need to add more folders to path
                        ; I noted that -I/src/ does NOT work, but -Isrc/ works
                  -include Single_ESP.h
                  ${dev_nodemcuv2.build_flags}  
; upload_protocol = espota
; upload_port     = 192.168.0.112		;deck LEDs  


[env:testESP32]
extends         = dev_lolinESP32
build_flags     = -DESP_ID=0
                  ${dev_lolinESP32.build_flags}


[env:testESP32_C3_mini]
extends         = dev_lolinESP32_C3_mini
build_flags     = -DESP_ID=0
                  -DFL_DATA_PIN=3
                  -DTREE_LED_PIN=4
                  -DledESP=8
                  ${dev_lolinESP32_C3_mini.build_flags}                  

The main.cpp is large - I tried to attach it, but would not let me…is there a way I can share the main.cpp?

I do have a work around by I really want to understand why this is a problem.
(The workaround is to declare densityEnable in the main.cpp, and using #define in header file to initialize it.)

I hope you can help me solve this mystery

You cannot define a global function in a header file and have it be included by multiple compilation units (C/C++ files) without causing a multiple definition error. This is a C/C++ thing, not a PlatformIO thing.

The function must either be static or just be a declaration, with the definition being in only one C/C++ file.

Thanks Max, makes sense - thanks for the education - much appreciated, as it was driving me crazy not knowing what the problem was!

I’ve now moved the declaration into the main.cpp and define it with a macro that is defined in the header file.

There is still one thing that puzzles me… why does this work when I included the header file directly at the top of the main.cpp?

So it seems that including the file via pio.ini is somehow different than including directly in main.cpp???

With -include Single_ESP.h you make the compiler include this file in every compilation unit (C/C++ file). It’s like forcing #include <Single_ESP.h> at the top of each source file. So that will absolutely blow up with more than two compilation units.

OK, I did not realize that … thanks for clarifying.

Is it possible to use pio.ini to include file for only the main.cpp?

Yes. Delete -include Single_ESP.h from your platformio.ini and write #include <Single_ESP.h> at the top of your src/main.cpp file.

Funny - that is what I originally did!

The change I made is that I moved the the “include” from the main.cpp to the .ini as I wanted to automatically change the included header file based on different builds defined in the .ini

was: #include Single_ESP.h (in main.cpp)
now: -include Single_ESP.h (in .ini)

Some background: The project is used to control LED strips around my house.
I have multiple controllers in different parts of the house that run their own version of the SW.

The header file (eg. Single_ESP.h) is used to configure each controller, as each location has different size LED strips, different WiFi connection, runs different patterns, etc.

I use the “default_envs” in the .ini to specify the build for a specific controller in a specific location of my house. So the reason I moved the header file into the .ini is so that it automatically selects the appropriate header file when I build for a specific house location.

I used to do this my uncommenting a line in the main.cpp - see snippet below from old version of main.cpp

/* The "include" files below contain the settings for differnent configurations to be complied
      - this is done to keep the code more organized and neat
      - uncomment one and only one of the include files for each compile session            */
// #include "House_Multi.h" // ******* Need to compile this 3 TIMES with ESP_ID = 0, then 1, then 2
// #include "Christmas_tree.h" // this file sets up a single controller setup
#include "Single_ESP.h" // this file sets up a single controller setup
// #include "Deck_LEDS.h" // this file sets up a single controller for Deck strip
// #include "Test_Multi.h" // Temporary Test File for DEBUG
// #include "Lab_House_Multi.h"

Note that the project copied in the original post was a streamlined version of the main.cpp and .ini, and does not show all these configurations and environments. (I wanted to keep it simple, as I only needed something to demonstrate the “multiple definition” problem.)

So, my question remains - can the .ini be used to include a file for only the main.cpp??

I hope this makes sense… thanks again for helping!

You can write an extra_script which modifes the compiler settings (CCFLAGS) for only one specific file, e.g., src/main.cpp, but this is unusual to see in the wild.

You might find a different technique usefull: In each of your environments, add only a build_flags that defines an identifying macro for that environment. For example,

[env] ; inherited by all envs
framework = arduino

[env:singleTestESP8266]
platform = espressif8266
board = nodemcuv2
build_flags = -D SINGLE_ESP8266

[env:testESP32]
platform = espressif32
board = lolin32
build_flags     = -D CHRISTMAS_TREE

And then make a compile-time decision in src/main.cpp

/* include right header file for the environmet */
#if SINGLE_ESP8266
#include "Single_ESP.h"
#elif defined(CHRISTMAS_TREE)
#include "Christmas_tree.h"
/* other cases.. */
#else
# error "unknown device type"
#endif

Thank you for the proposed solution - that’s exactly what I was thinking of doing if I could not get the -include in the .ini to exclude adding the header to all compile units.

I was trying to avoid this, as it’s (a little) more work and to me, it seems logical to do it in the .ini

I’m not a very experienced coder (as you can tell :smiley: ), so I’m probably missing something, but is seems to me that it would be a useful thing for platformIO to allow you to specify in which files to insert the include-file.

Is there are reason that it has to be included in every file?

BTW, using the -include in the .ini works fine for me now (since I fixed the "multiple definition problem), but it seems like a waste to include the header in multiple files.

Also, the following is a snippet from the PIO docs:

-include file

Process file as if #include "file" appeared as the first line of the primary source file.

I find it surprising that the include file (via .ini) is attached to ALL compilation units when the documentation states "Process file as if #include "file" appeared as the first line of the primary source file.

I emphasized “primary” as, to me, this implies it is ONLY added to primary source file, which I think is the main.cpp, as that’s the main file that I’m building which then calls all the library files.

It appears I am interpreting this incorrectly, so I wonder if the documentation should be changed to make it clear that the file is included in ALL compilation units?

Anyway, thanks very much for all your help in educating me!