Custom arduino framework and board: Multiple ways to build the same target were specified

Oh – your framework is not a new framework, but a different Arduino core. Then that’s the wrong way to add support for it. Or well, at least there’s a different intended way for that.

The post you’ve linked to talks about adding an entirely new framework, say framework = simba vs framework = arduino or framework = sming.

The Arduino builder script and the platform-atmelavr platform script is written in a very generic way so that it detects the board manifest’s build.core value, derives from that that it needs a framework-arduino-avr-<core> package and uses that.

Please read through this to get an understanding of PlatformIO packages, board definition files, platform and builder scripts.

Also, carefully read through the arduino builder script to see where it gets it e.g. variant definition from, which must be a valid folder name in the new core’s variants folder)

What you can do is basically create a nearly self-contained project (only modification to platform-atmelavr is the package) or any PlatformIO-internal stuff necessary) that references that core and builds for that stuff. (Though you can also expand the board definitions in platform-atmelavr if you wish to contribute back changes).

The basic gist is:

  • create a board definition file (derived from leonardo in your case) and change the core value to the core name, here e.g. xinput.
    • shortcuts can be taken: you do not need to create new board definition files if you only need to change a few values in them, as that can be done dynamically in the platformio.ini
  • now PlatformIO will be looking for the package framework-arduino-avr-xinput. This is obviously not in the PlatformIO registry (https://api.registry.platformio.org/v3/packages/platformio/tool/framework-arduino-avr-xinput) and not listed in the package definitions of the platform.json in platform-atmelavr, so you’ll need add it. First, the source / content of the package should be a fork of the original core with an added package.json which declares it to be the package of the required name. I’ve done so e.g. here. Then, a new entry in the "packages" array must be added, e.g. at the bottom: (don’t forget the needed , in the now second-last package)
    "framework-arduino-avr-xinput": {
      "type": "framework",
      "optional": true,
      "owner": "platformio",
      "version": "https://github.com/maxgerhardt/ArduinoXInput_AVR.git"
    }
  • note how the package can also be directly given a “source” via version to bypass it being sourced from the PlatformIO registry where it does not yet exist. Alternatively, one could use platform_packages to inject the package.
  • the package.json file can either be modified locally (e.g. C:\Users\Max\.platformio\platforms\atmelavr\package.json), a cleaner way is to fork platform-atmelavr, do the changes there, then reference the forked platform via platform = <git link of fork> in the platformio.ini, as e.g. explained in here. Then this also works for someone else and one doesn’t haven’t to do local file modifications.
  • in this case, an additional modifications has to be done to the core: the builder script assumes that the core lives in a cores/x folder, where x = build.core. This is true for a lot of cores (example). The packages PlatformIO uses rename that folder cores/x on-demand to the expected one if that convention is broken – here we need to do the same, since the core uses cores/arduino but we’re now expecting cores/xinput. The fork has been adapted for that
  • mind that a different Arduino core can implement arbitrary stuff in the build process like extra macro definitions (-D.. compiler flags), other arbitrary compiler flags or extra build steps like dynamically generating a file. This is defines in the platform.txt file of the core. In your case there is nothing new in here that the original Arduino core doesn’t do, so it can be ignored

Putting it all together, to compile for the new core, all one needs to do is create a new standard Leonardo + Arduino project, then override the platformio.ini with

[env:leonardo]
platform = atmelavr
; build upon the leonard board file
board = leonardo
framework = arduino
; dynamically modify the build.core attribute
board_build.core = xinput
; no need to modify the variant, the new core
; also has variants/leonardo.

with a src\main.cpp of

#include <Arduino.h>
//xinput/USB_XInput_API.h is automatically included
//through Arduino.h

void setup(){
	Serial.begin(115200);
}
void loop(){
	if(XInputUSB::connected()) {
		Serial.println("XInputUSB connected");
	} else { 
		Serial.println("XInputUSB not connected");
	}
}

and we get

C:\Users\Max\temp\leonardo_custom_core>pio run
Processing leonardo (platform: atmelavr; board: leonardo; framework: arduino)
------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/leonardo.html
PLATFORM: Atmel AVR (3.3.0) > Arduino Leonardo
HARDWARE: ATMEGA32U4 16MHz, 2.50KB RAM, 28KB Flash
DEBUG: Current (simavr) On-board (simavr)
PACKAGES:
 - framework-arduino-avr-xinput 1.0.2+sha.8dbf7ed
 - toolchain-atmelavr 1.70300.191015 (7.3.0)
LDF: Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 6 compatible libraries
Scanning dependencies...
No dependencies
Building in release mode
Compiling .pio\build\leonardo\src\main.cpp.o
Archiving .pio\build\leonardo\libFrameworkArduinoVariant.a
Compiling .pio\build\leonardo\FrameworkArduino\CDC.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\HardwareSerial.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\HardwareSerial0.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\HardwareSerial1.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\HardwareSerial2.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\HardwareSerial3.cpp.o
Indexing .pio\build\leonardo\libFrameworkArduinoVariant.a
Compiling .pio\build\leonardo\FrameworkArduino\IPAddress.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\PluggableUSB.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\Print.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\Stream.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\Tone.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\USBCore.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\WInterrupts.c.o
Compiling .pio\build\leonardo\FrameworkArduino\WMath.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\WString.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\abi.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\hooks.c.o
Compiling .pio\build\leonardo\FrameworkArduino\main.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\new.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\wiring.c.o
Compiling .pio\build\leonardo\FrameworkArduino\wiring_analog.c.o
Compiling .pio\build\leonardo\FrameworkArduino\wiring_digital.c.o
Compiling .pio\build\leonardo\FrameworkArduino\wiring_pulse.S.o
Compiling .pio\build\leonardo\FrameworkArduino\wiring_pulse.c.o
Compiling .pio\build\leonardo\FrameworkArduino\wiring_shift.c.o
Compiling .pio\build\leonardo\FrameworkArduino\xinput\USB_XInput_API.cpp.o
Compiling .pio\build\leonardo\FrameworkArduino\xinput\USB_XInput_Descriptors.cpp.o
Archiving .pio\build\leonardo\libFrameworkArduino.a
Indexing .pio\build\leonardo\libFrameworkArduino.a
Linking .pio\build\leonardo\firmware.elf
Checking size .pio\build\leonardo\firmware.elf
Advanced Memory Usage is available via "PlatformIO Home > Project Inspect"
RAM:   [          ]   0.7% (used 18 bytes from 2560 bytes)
Flash: [=         ]   7.3% (used 2104 bytes from 28672 bytes)
Building .pio\build\leonardo\firmware.hex
============================================= [SUCCESS] Took 2.82 seconds =============================================