Good Cross Platform Design?

In platformIO, what is a good way to organize your code so that it can support multiple platforms?

For example, a software application that can run on Windows, Arduino and Linux? What is the professional way of organizing your code so that you can best make a cross platform app?

My initial idea is: have basic/common logic in 1 folder, and then have several different folders for hardware specific software. Then your common code runs on top of the hardware specific software. The hardware specific software all share the same interface/contract so that the common software can easily use each different hardware-specific software depending on where it is deployed.

Also it would be valuable to run test cases too. How would you structure your test cases so that necessary test cases can be run on each of the platforms?

You can declare 3 different build environments in platformio.ini. The source code can be located in src folder where from each build environment you will control it via build_flags. Do you mean this?

[env:native]
platform = native
# build everything from src folder, exluding whole `hal` folder and after that inclue only `native` specific code
src_filter = +<*> -<hal> +<hal/native>

[env:embedded]
platform = native
# build everything from src folder, exluding whole `hal` folder and after that inclue only `native` specific code
src_filter = +<*> -<hal> +<hal/embedded>

See also an example with platformio-examples/unit-testing/calculator at develop · platformio/platformio-examples · GitHub

2 Likes

when running unit test cases, how is the source program built? Is it even built at all? Can I ignore certain files in src when testing?

When running the unit testing framework (pio test -e native) I notice that it tries to build every single file in lib before exeuting the test cases. So in lib if I have a folder called belt with arduino files in it, but I don’t have an arduino connected, the pio test -e yields:

In file included from lib/belt/ArdActuatorDriver.cpp:1:0:
lib/belt/ArdActuatorDriver.h:1:10: fatal error: Arduino.h: No such file or directory

even if I have not included any arduino files inside the test file in the test folder.

I have uploaded a very simple demonstration. If you extract it and put the necessary files inside a new pio project, then do pio test -e native, you will see the above error posted.

1 Like

I’m afraid I don’t get any error from that simple demo… the test ran just fine for me. I also don’t seem to be seeing any main code builds unless specifically doing one.

> Executing task in folder Mini_Framework: platformio test --environment native <

PIO Plus (https://pioplus.com) v2.4.0
Verbose mode can be enabled via `-v, --verbose` option
Collected 1 items

=================================================== [test/*] Building... (1/2) ===================================================
Please wait...

=================================================== [test/*] Testing... (2/2) ===================================================

-----------------------
0 Tests 0 Failures 0 Ignored
OK
========================================================= [TEST SUMMARY] =========================================================

test/* > uno    [IGNORED]
test/* > native [PASSED]
=================================================== [PASSED] Took 0.61 seconds ===================================================

One way to do platform specific could would be to start using #defines. i.e., the below is a main.cpp that would build for either native or Arduino … although in this case it’s basically two separate programs in one source file. I use something similar though for code that runes on both Arduino Uno or ESP8266 though, and just needs a few bits changed.

You could add extra defines for say Windows, Linux, Mac… if you wanted to detect the other OSs, based on your platformio.ini environment… i.e. build_flags= -D WINDOWS for a windows environment…

#ifdef ARDUINO
#include <Arduino.h>

void setup()
{
    Serial.begin(9600);
    Serial.println("Hi, I'm an Arduino! I'm cute and cuddly!");
}

void loop()
{
}
#endif

#ifndef ARDUINO
#include <iostream>
using namespace::std;

int main()
{
    cout << "This ain't no stinking Arduino!" << endl;
    return 0;
}
#endif

I’ve not really played with the unit testing, as I haven’t really worked out how it would fit in my current development workflow (or lack of one). After playing around with it for a minute now, it looks like when you do a test, instead of building what’s in your /src folder, it seems to be building whats in the /test folder? So if your test has an #include <Arduino.h> in it, but you’re testing against native… that is doomed to fail. So you’d either need to use the test_ignore parameter to ignore test code that is platform specific, or perhaps use defines to disable any platform specific bits of the code.

that’s strange, I literally deleted everything in my test folder, and reran pio test -e native, and it still show the arduino.h error. I also tried pio test and then the output to that showed it was compiling everything in lib.

(pio) tris@xps:~/Documents/PlatformIO/Projects/PRISM_ONBOARD$ pio test -v
PIO Plus (https://pioplus.com) v2.4.0
Verbose mode can be enabled via `-v, --verbose` option
Collected 1 items

====================================================================================================================== [test/*] Building... (1/3) ======================================================================================================================
Processing uno (platform: atmelavr; board: uno; framework: arduino)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
CONFIGURATION: https://docs.platformio.org/page/boards/atmelavr/uno.html
PLATFORM: Atmel AVR > Arduino Uno
HARDWARE: ATMEGA328P 16MHz 2KB RAM (31.50KB Flash)
Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF MODES: FINDER(chain) COMPATIBILITY(soft)
Collected 11 compatible libraries
Scanning dependencies...
Dependency Graph
|-- <belt>
Linking .pioenvs/uno/firmware.elf
/tmp/ccwq6x2E.ltrans0.ltrans.o: In function `main':
<artificial>:(.text.startup+0x86): undefined reference to `setup'
<artificial>:(.text.startup+0x8a): undefined reference to `loop'
collect2: error: ld returned 1 exit status
*** [.pioenvs/uno/firmware.elf] Error 1
======================================================================================================================= [ERROR] Took 3.19 seconds =======================================================================================================================

=============================================================================================================================== [SUMMARY] ===============================================================================================================================
Environment uno         [ERROR]
Environment native      [SKIP]

and for native:
PIO Plus (https://pioplus.com) v2.4.0
Verbose mode can be enabled via -v, --verbose option
Collected 1 items

====================================================================================================================== [test/*] Building... (1/2) ======================================================================================================================

Processing native (platform: native)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Verbose mode can be enabled via `-v, --verbose` option
Library Dependency Finder -> http://bit.ly/configure-pio-ldf
LDF MODES: FINDER(chain) COMPATIBILITY(soft)
Collected 6 compatible libraries
Scanning dependencies...
Dependency Graph
|-- <belt>
Compiling .pioenvs/native/lib756/belt/arduino_drivers/ArdActuatorDriver.o
In file included from lib/belt/arduino_drivers/ArdActuatorDriver.cpp:1:0:
lib/belt/arduino_drivers/ArdActuatorDriver.h:1:10: fatal error: Arduino.h: No such file or directory

*****************************************************************
* Looking for Arduino.h dependency? Check our library registry!
*
* CLI  > platformio lib search "header:Arduino.h"
* Web  > https://platformio.org/lib/search?query=header:Arduino.h
*
*****************************************************************

#include <Arduino.h>
^~~~~~~~~~~
compilation terminated.
*** [.pioenvs/native/lib756/belt/arduino_drivers/ArdActuatorDriver.o] Error 1

this is without anything inside the test folder.

I don’t know why the LDF keeps adding belt for no reason.

EDIT:

I deleted all of my #includes inside the main.cpp inside the src folder. Now its passing like what you showed. Funny, the unit testing framework is taking the main.cpp inside src into account even if I have not included main.cpp inside my testing framework at all. This is why the mini-framework worked for you @pfeerick

EDIT2: Ahhh now I get what’s going on. The pio test command builds the src folder first (as if you do pio run) and then runs your unit testing framework.

1 Like

Ok, that’s just weird… It shouldn’t be! :open_mouth:

PIO Unit Testing Engine does not build source code from src_dir folder by default.

https://docs.platformio.org/en/latest/plus/unit-testing.html#shared-code

Plus I’d added that main.cpp to the mini-framework to see if would change the test behaviour… and that was before I added the guard #ifdef ARDUINO code.

Now I’m just confused! :confused: :confounded:

You’d better not have set test_build_project_src = true in your projectio.ini just to confuse me! :laughing:

What is your PlatformIO Core version?

pio --version