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

I am attempting to add in a custom framework, Xinput, based on a forked version of Arduino to make my Leonardo-based board operate as a Xbox 360 USB device. I followed the directions here to put in a custom framework:

I created the builder file (xinput.py) by copying over the one from arduino (arduino.py), and changing the default framework path to point at the xinput framework package.

And also added a new board to based on boards.txt from the Xinput framework to ~/.platformio/platforms/atmelavr/boards/leonardo-xinput.json (I copied the one from leonardo, and changed a few parameters which I got from boards.txt in the xinput framework fork)

In VSCode everything seems to be accepted - I can select the custom board definition, and it autofills the custom framework (as it is the only one defined for that board). However, now I’m getting stuck behind an error like this

PIO Core Call Error: "The current working directory /home/dev_user/Documents/PlatformIO/Projects/xinput-test1 will be used for the project.

The next files/directories have been created in /home/dev_user/Documents/PlatformIO/Projects/xinput-test1
include - Put project header files here
lib - Put here project specific (private) libraries
src - Put project source files here
platformio.ini - Project Configuration File


Error: Processing leonardo-xinput (platform: atmelavr; board: leonardo-xinput; framework: xinput)
--------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/leonardo-xinput.html
PLATFORM: Atmel AVR (3.3.0) > Arduino Leonardo with Xinput
HARDWARE: ATMEGA32U4 16MHz, 2.50KB RAM, 28KB Flash
DEBUG: Current (simavr) On-board (simavr)
PACKAGES: 
 - framework-xinput-avr 1.0.2 
 - toolchain-atmelavr 1.70300.191015 (7.3.0)

*** Multiple ways to build the same target were specified for: 
/home/dev_user/Documents/PlatformIO/Projects/xinput-test1/.pio/build/leonardo-xinput/FrameworkArduino/wiring_pulse.o 

(from 
['/home/dev_user/.platformio/packages/framework-xinput-avr/cores/arduino/wiring_pulse.S'] 
and from 
['/home/dev_user/.platformio/packages/framework-xinput-avr/cores/arduino/wiring_pulse.c']
)

File \"/home/dev_user/.platformio/penv/lib/python3.8/site-packages/platformio/builder/tools/platformio.py\", line 350, in BuildLibrary
========================== [FAILED] Took 0.25 seconds =========================="

(I added some formatting to be able to see the problem more clearly)

The big question is what could I have missed when I set up the custom framework and board? The upstream, unaltered arduino framework that comes with platformio has these same two files (wiring_pulse.S and wiring_pulse.c), and doesn’t emit this error. Somehow it either ignores one, or creates separate build targets I’d guess.

Any help getting this sorted would be appreciated. I love having all the C++ features in VSCode with PlatformIO, so I’d really like to get the framework integrated with PlatformIO if at all possible.

Thanks,

jonbitzen

In the PlatformIO (which equals SCons) build system, both these files would produce a wiring_pulse.o object file. Hence they are conflicting.

You should be able to manually solve the conflict for now by renaming e.g. wiring_pulse.c into wiring_pulse_c_code.c.

Solving it systematically would require an adaption in either PlatformIO or at SCons. Open an issue at e.g. Issues · platformio/platformio-core · GitHub for that.

I wonder what object files the Arduino IDE produces. Does it detect that and adds some random suffix to the object file? It should have the same problem.

I think for now I’ve got this sorted. Your comment got me looking at the build files, and I found this bit here in ~/.platformio/penv/lib/python3.8/site-packages/platformio/builder/tools/platformio.py:

def BuildFrameworks(env, frameworks):
    if not frameworks:
        return

    if "BOARD" not in env:
        sys.stderr.write(
            "Please specify `board` in `platformio.ini` to use "
            "with '%s' framework\n" % ", ".join(frameworks)
        )
        env.Exit(1)

    board_frameworks = env.BoardConfig().get("frameworks", [])
    if frameworks == ["platformio"]:
        if board_frameworks:
            frameworks.insert(0, board_frameworks[0])
        else:
            sys.stderr.write("Error: Please specify `board` in `platformio.ini`\n")
            env.Exit(1)

    for f in frameworks:
        if f == "arduino":
            # Arduino IDE appends .o the end of filename
            Builder.match_splitext = scons_patched_match_splitext
            if "nobuild" not in COMMAND_LINE_TARGETS:
                env.ConvertInoToCpp()

        if f in board_frameworks:
            SConscript(env.GetFrameworkScript(f), exports="env")
        else:
            sys.stderr.write("Error: This board doesn't support %s framework!\n" % f)
            env.Exit(1)

Changing if if f == "arduino" line to if f == "arduino" or f == "xinput": lets PlatformIO in VSCode create the project normally, as near as I can tell.

I think there’s a holistic question here: Is there merit to opening a bug or feature request to allow a configurable framework feature to indicate a framework is derived from arduino and should be handled in build that way? It’s a framework that I bet a lot of other people tweak for “reasons”. Or add a framework flag that causes the branch to be taken so that any framework could take that branch in the build file if appropriate?

Thanks again for your input - it sort of got me out of looking for the problem in my manifests and configuration.

Jon

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 =============================================

And to come back to the original problem: Looks like I was wrong. When I use PlatformIO of the latest dev version (pio upgrade --dev in the CLI), I do not need to rename the wiring_pulse core files because the extension is included in the object file name. No need to change filenames.

Make sure you have a sufficiently new PIO core version and check if you get the same build result after compiling the project described above.

Thanks - I’ll go through that. I’ll probably retract the feature-request I added, if it isn’t rejected before I get a chance.

jonbitzen