Use relative path to point to precompiled .a library

Hello, guys.
I am using platformIO, version 5.2.0a6 in Windows 10 environment. There is a pre-compiled static library (*.a) that I want to include in my project by specifying the relative path of the *.a file. When using absolute paths in platformio.ini file:

build_flags = 
-I"G:\pd_open_library"  ;Absolute path to the third part library
-L"G:\pd_open_library"  ;Absolute path to the third part library 
-llibPDlib.a

everything works fine. However, when I tried to replace the absolute path with the relative one, by using ${platformio.workspace_dir}, or $PROJECT_DIR, or ${platformio.lib_dir} variables, e.g:

build_flags = 
-I"${platformio.workspace_dir}\pd_open_library"  
-L"${platformio.workspace_dir}\pd_open_library"  
-llibPDlib.a

the library is not properly included in the project.
What I am missing here?
Thanks in advance for your time and efforts.

Sincerely,
Bojan

What si the absolute path of the project folder?

Hello, @maxgerhardt.
Why is the absolute path important? I would like to avoid absolute paths because we are sharing the code through the GitHub repository. I would like to place the *.a file within the project folder (e.g. inside lib/ folder or in a separate folder placed inside project folder) and to specify relative path to it.

Regards,
Bojan.

Because then I can tell whether you have constructed the absolute path correctly :smiley: (together with the information where the library is)

OK. :slight_smile:
Here it is:

g:\StrikerVR\FW\richtek_pd\
The library is located at:
g:\StrikerVR\FW\richtek_pd\pd_open_library

The library is already in the project directory? Are you sure you don’t want to just move it into the lib/, add a library.json with the -I, -L and -l build flags and call it done? That would be the canonical way of doing it – creating a proper PlatformIO libray for it. No need for build_flags hacking in the platformio.ini then, too.

There’s a details tutorial for that at How to generate and use pre-compiled objects - #2 by maxgerhardt.

Anyways, since all the paths in the -I and -L flags are already relative to the project directory, have you tried

build_flags = 
  -Ipd_open_library
  -Lpd_open_library
  -llibPDlib.a

?

Thanks for the suggestions, @maxgerhardt! I am new to platformIO environment, that’s why I am trying to re-invent the wheel! :slight_smile:

I thought that lib/ folder is only used for remote libraries (where we specify url in json) or for the libraries that should be compiled (we put source files of the library inside /lib folder). I did not know it is possible to put pre-compiled libraries (*.a files) inside lib/ folder.

Give me some time to try and analyze what you suggested.

Cheers, :beers:
Bojan

Hello, @maxgerhardt!

build_flags = 
  -Ipd_open_library
  -Lpd_open_library
  -llibPDlib.a

seems to work fine. Project compiles but what confuses me is the fact that compiler says 0 compatible libraries are found:

Reading CMake configuration…
Parsing system calls…
Generating syscall files
Generating KObject files…
Validating driver…
LDF: Library Dependency Finder → Library Dependency Finder (LDF) — PlatformIO latest documentation
LDF Modes: Finder ~ chain, Compatibility ~ soft
Found 0 compatible libraries
Scanning dependencies…
No dependencies

Should I care about that?

Including pre-compiled library (*.a file) in lib/ folder seems to be more sophisticated approach. I tried to do it together with adding library.json file so that the project structure looks like:

├── lib
│   ├── README
│   └── pd_open_library
│       ├── library.json
│       ├── libPDlib.a
├── platformio.ini
└── src
    └── 

Inside library.json file, I tried to include -I, -L, and -l build flags as you suggested:

{
	"build": {
		"flags": [
			"-I lib/pd_open_library",
			"-L lib/pd_open_library",
			"-l libPDlib.a"
		]
	}
}

build_flags are also removed from the platform.ini file. However, when I try to compile the project, I get the compilation errors because *.a library is not properly included. Do you have any idea what I am missing with putting the *.a library into lib/ folder?

Regards,
Bojan.

This is showing the libraries in the PlatformIO sense. If you manually add a statically linked library via the build_flags, PlatformIO doesn’t recognize it. It would count it however if it were inside a proper library folder within lib/.

Wrong folder structure, you need to have `lib/pd_open_library/< the .a and the .json file>. See the reference tutorial I linked above which has

grafik

The paths in here must be relative to the library folder, not the root project folder. Doing a simpler

    "build": {
        "flags": [
            "-L.",
            "-llibPDlib.a"
        ]
    }

should suffice in this case. (The library is automatically added to the include path, so no -I is needed)

It was my typo. While I was editing the post fixing the project structure tree, you already replied. I did not expect such a fast reply! :slight_smile:

With

{
	"build": {
        "flags": [
            "-L.",
            "-llibPDlib.a"
        ]
    }
}	

I am still unable to compile the project. :thinking:

What’s the final linker command when you execute the project task “Advanced → Verbose Build”?

Have you tried -lPDlib here?

Hi, @maxgerhardt.

Building in release mode
arm-none-eabi-g++ -o .pio\build\nrf52840_dk\firmware-pre.elf -T G:\StrikerVR\FW\richtek_pd\.pio\build\nrf52840_dk\zephyr\linker.cmd -Wl,--build-id=none -Wl,--gc-sections -Wl,--orphan-handling=warn -Wl,--print-memory-usage -Wl,--sort-common=descending -Wl,--sort-section=alignment -Wl,-Map=G:/StrikerVR/FW/richtek_pd/.pio/build/nrf52840_dk/zephyr/zephyr_prebuilt.map -Wl,-N -Wl,-X -Wl,-u,_ConfigAbsSyms -Wl,-u,_OffsetAbsSyms -mabi=aapcs -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mthumb -no-pie -nostdlib -specs=nano.specs -static .pio\build\nrf52840_dk\zephyr_prebuilt\zephyr\misc\empty_file.c.o .pio\build\nrf52840_dk\src\Haptics\drv2625.o .pio\build\nrf52840_dk\src\Haptics\haptic_controller.o .pio\build\nrf52840_dk\src\Haptics\i2s_engine.o .pio\build\nrf52840_dk\src\Haptics\i2s_service.o .pio\build\nrf52840_dk\src\Haptics\primitive_sequence.o .pio\build\nrf52840_dk\src\PD_Controller\171x_control_function.o .pio\build\nrf52840_dk\src\PD_Controller\pd_user_data.o .pio\build\nrf52840_dk\src\PD_Controller\pd_user_dpm.o .pio\build\nrf52840_dk\src\Services\serial_controller.o .pio\build\nrf52840_dk\src\Services\uart_commands_transport.o .pio\build\nrf52840_dk\src\ble_gatt.o .pio\build\nrf52840_dk\src\ble_service.o .pio\build\nrf52840_dk\src\input_controller.o .pio\build\nrf52840_dk\src\main.o -L.pio\build\nrf52840_dk -LC:\Users\bojan.jovanovic\.platformio\packages\toolchain-gccarmnoneeabi\lib\gcc\arm-none-eabi\8.2.1\thumb\v7e-m+fp\hard -L.pio\build\nrf52840_dk\zephyr -LC:\Users\bojan.jovanovic\.platformio\packages\toolchain-gccarmnoneeabi\arm-none-eabi\lib\thumb\v7e-m+fp\hard -Wl,--start-group -Wl,--whole-archive .pio\build\nrf52840_dk\modules\framework-zephyr-hal-nordic\lib..__framework-zephyr-hal-nordic.a .pio\build\nrf52840_dk\zephyr\arch\arch\arm\core\aarch32\cortex_m\libarch__arm__core__aarch32__cortex_m.a .pio\build\nrf52840_dk\zephyr\arch\arch\arm\core\aarch32\cortex_m\mpu\libarch__arm__core__aarch32__cortex_m__mpu.a .pio\build\nrf52840_dk\zephyr\arch\arch\arm\core\aarch32\libarch__arm__core__aarch32.a .pio\build\nrf52840_dk\zephyr\arch\common\libarch__common.a .pio\build\nrf52840_dk\zephyr\drivers\entropy\libdrivers__entropy.a .pio\build\nrf52840_dk\zephyr\drivers\gpio\libdrivers__gpio.a .pio\build\nrf52840_dk\zephyr\drivers\i2c\libdrivers__i2c.a .pio\build\nrf52840_dk\zephyr\drivers\serial\libdrivers__serial.a .pio\build\nrf52840_dk\zephyr\drivers\spi\libdrivers__spi.a .pio\build\nrf52840_dk\zephyr\lib\libc\newlib\liblib__libc__newlib.a .pio\build\nrf52840_dk\zephyr\lib\posix\liblib__posix.a .pio\build\nrf52840_dk\zephyr\libzephyr.a .pio\build\nrf52840_dk\zephyr\soc\arm\common\cortex_m\libsoc__arm__common__cortex_m.a .pio\build\nrf52840_dk\zephyr\soc\arm\nordic_nrf\nrf52\libsoc__arm__nordic_nrf__nrf52.a .pio\build\nrf52840_dk\zephyr\subsys\bluetooth\common\libsubsys__bluetooth__common.a .pio\build\nrf52840_dk\zephyr\subsys\bluetooth\controller\libsubsys__bluetooth__controller.a .pio\build\nrf52840_dk\zephyr\subsys\bluetooth\host\libsubsys__bluetooth__host.a .pio\build\nrf52840_dk\zephyr\subsys\net\libsubsys__net.a .pio\build\nrf52840_dk\zephyr\subsys\random\libsubsys__random.a .pio\build\nrf52840_dk\zephyr\liboffsets.a -Wl,--no-whole-archive G:\StrikerVR\FW\richtek_pd\.pio\build\nrf52840_dk\zephyr\kernel\libkernel.a G:\StrikerVR\FW\richtek_pd\.pio\build\nrf52840_dk\zephyr\arch\common\libisr_tables.a -lgcc -lstdc++ -lm -lc -lgcc -Wl,--end-group

Is this ^^^ what you wanted to see?

I tried with -lPDlib as well but to no avail.

AAH. There’s a caveat. PlatformIO doesn’t recognize that you’re using the library at all, which it does by scanning your code for #include <file> where it searches for file within the available libraries in lib/, among other places.

If your library doesn’t have a header file that is included in your main code, PlatformIO won’t include it as a library. Hence why the linker command doesn’t even have a mention of the PDlib anywhere.

Try doing the following:

Add

lib_deps = pd_open_library

in the platformio.ini to force an inclusion, or, add a pd_open_library.h dummy file (empty) in lib/pd_open_library, then do a #include <pd_open_library.h> in your main code and recompile.

Hey, @maxgerhardt !

Adding lib_deps = pd_open_library in platformio.ini did the magic! I can now compile the project with *.a precompiled library placed in lib/ folder! Thank you very much for bearing with me and for your great support!

Cheers, :beers:
Bojan

I have very similar issue. I precompiled the official version of libopencm3 (its size is ~3MB); I tried to place it in /lib/libopencm3/*.a; with and without empty header and library.json. Eventually, I see the following commands in verbose build:
arm-none-eabi-gcc-ar rc .pio\build\bluepill_f103c8\libcda\libopencm3_stm32f1.a
followed by inclusion in the link command “.pio\build\bluepill_f103c8\libcda\libopencm3_stm32f1.a .pio\build\bluepill_f103c8\libe89\libinclude.a”.

However, the size of generated .pio\build\bluepill_f103c8\libcda\libopencm3_stm32f1.a is <1kB, and it only contains “!” inside (i.e. empty). As the result, the program is linked with empty library and symbols are not found.

Any idea how to force it to simply copy the file, not create a new one?

Please post your exact project setup in another new topic, not this one. Though I highly recommend looking at How to use independently loaded and installed ARM compiler in PlatformIO? - #2 by maxgerhardt first.

Thanks for responding. However, the question is not "why out of the box platformio tools are not sufficient to build an example project, it is "how to use precompiled library in archive format and let liker take it “as is” and link it with the other *.o modules compiled in the platformio enfironment. I spent couple of days in unsuccessful attempts to make it work, including your recommendations.

First I naively included the path to *.a file in lib_path.

Then I added

-L "C:\PlatformIO\libopencm3\lib\libopencm3_stm32f1"
 -llibopencm3_stm32f1.a

in build_flags. The path to the library appeared in the link command but “.a” was stripped off.

Then I found your recommendation, which is to place the library in /lib/libopencm3 folder, together with empty header file and a library.json file:

{  "build": {
        "flags": [
            "-L.",
            "-llibopencm3_stm32f1.a"
        ]
    }
}

As the result, an empty libopencm3_stm32f1.a library is generated in C:\PlatformIO\STM32_libopencm3.pio\build\bluepill_f103c8\libcda, and the latter appears in the linkage command line. Then, the linker cannot find symbols in this library (because it is empty!):

**arm-none-eabi-gcc-ar rc .pio\build\bluepill_f103c8\libcda\libopencm3_stm32f1.a**
arm-none-eabi-gcc-ar rc .pio\build\bluepill_f103c8\libe89\libinclude.a

Building in release mode
arm-none-eabi-gcc -o .pio\build\bluepill_f103c8\firmware.elf -Os -Wl,--gc-sections,--relax -mthumb -mcpu=cortex-m3 .pio\build\bluepill_f103c8\src\StrPrintf.o .pio\build\bluepill_f103c8\src\led.o .pio\build\bluepill_f103c8\src\systick.o .pio\build\bluepill_f103c8\src\uart.o .pio\build\bluepill_f103c8\src\usb-serial.o .pio\build\bluepill_f103c8\src\usb.o -Llib\libopencm3_stm32f1 -LC:\Users\Win7\.platformio\platforms\ststm32\ldscripts -L.pio\build\bluepill_f103c8 -Wl,--start-group -lopencm3_stm32f1 **.pio\build\bluepill_f103c8\libcda\libopencm3_stm32f1.a** .pio\build\bluepill_f103c8\libe89\libinclude.a -lc -lgcc -lm -lstdc++ -Wl,--end-group

c:/users/win7/.platformio/packages/toolchain-gccarmnoneeabi@1.70201.0/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/bin/ld.exe: c:/users/win7/.platformio/packages/toolchain-gccarmnoneeabi@1.70201.0/bin/../lib/gcc/arm-none-eabi/10.2.1/../../../../arm-none-eabi/lib/thumb/v7-m/nofp\libc.a(lib_a-exit.o): in function `exit':
**exit.c:(.text.exit+0x16): undefined reference to `_exit'**

collect2.exe: error: ld returned 1 exit status
*** [.pio\build\bluepill_f103c8\firmware.elf] Error 1

So the real question: how to specify an insertion into the linker command file to use an *.a file from any specific location? In other words, I want the “C:\PlatformIO\STM32_libopencm3\lib\libopencm3_stm32f1.libopencm3_stm32f1.a” , not “.pio\build\bluepill_f103c8\libcda\libopencm3_stm32f1.a” in the linker command line.

Do you know of any option to specify additional arguments “as is” into the linker command line?

This is still out of scope for this exact topic about relative paths. I think part of the problem you’re having is naming the library folder exactly the same way as the .a file you put it into-- that will overshow what PlatformIO will generate by itself based on the library folder’s sources, which, if empty, generates an empty .a. This still does not harm if your custom library is correctly linked in. Also, you will have to remove the framework = ... line from the platformio.ini if you substitute the entire framework away with this .a file (and the headers), to make it a baremetal project.

Actually you should prefer the -l<name> linker command where <name> is the library name without the lib prefix and without the .a suffix. So just -lopencm3_stm32f1. Again, mind the shadowing noted above.

Anyways, if you’re still having problems, please open a new topic in which you upload the exact project you’re having problems with, as it’s not strictly related to relative paths.