0

I am attempting to link to a number of libraries in a very rudimentary manner (without the use of any Makefile or CMakeLists.txt), using the following clang++ command:

FILAMENT_LIBS="-lassimp -lbackend -lbluegl -lbluevk -lcamutils -lfilabridge -lfilaflat -lfilagui -lfilamat -lfilamat_combined -lfilamat_lite -lfilament -lfilameshio -lgeometry -lgltfio -lgltfio_core -lgltfio_pipeline -lgltfio_resources -libl -limage -limageio -limgui -lmatdbg -lmatdbg_resources -lmath -lmeshoptimizer -lrays -lshaders -lsmol-v -lutils"
clang++-7 -L../lib/x86_64/ ${FILAMENT_LIBS} -lpthread -lc++ -ldl -lSDL2 -I./ -I../include/ -I../app/ -I${RESOURCE_DIR} -Wno-extern-c-compat -std=c++14 -pthread ../suzanne.cpp ../app/Cube.cpp ../app/FilamentApp.cpp ../app/IBL.cpp ../app/IcoSphere.cpp ../app/Image.cpp ../app/NativeWindowHelperLinux.cpp ../app/Sphere.cpp -o suzanne

However, this gives the error FilamentApp.cpp:(.text+0x4d2): undefined reference to... followed by what must be every single one of the calls to functions in the aforementioned libraries.

To give some context, I am attempting to compile one of the sample apps provided by the rendering engine Filament. When this is compiled with the source code, everything works no issues, but when I attempt to link against the precompiled versions of the libraries, I get the above error.

Additionally, when I attempt to link against the same libraries with the tester main.cpp file and accompanying Makefile as described in the Filament documentation, everything links and compiles completely fine.

I have a feeling I'm doing something obvious wrong here, specifically with the clang command I am using, but I can't for the life of me see what it is. Any help would be much appreciated!

Edit - first few lines of errors:

../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::loop()':
Engine.cpp:(.text._ZN8filament7details7FEngine4loopEv+0x22): undefined reference to `filament::backend::DefaultPlatform::create(filament::backend::Backend*)'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::init()':
Engine.cpp:(.text._ZN8filament7details7FEngine4initEv+0x1f): undefined reference to `filament::backend::CommandStream::CommandStream(filament::backend::Driver&, filament::backend::CircularBuffer&)'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::FEngine(filament::backend::Backend, filament::backend::Platform*, void*)':
Engine.cpp:(.text._ZN8filament7details7FEngineC2ENS_7backend7BackendEPNS2_8PlatformEPv+0x267): undefined reference to `filament::backend::CommandBufferQueue::CommandBufferQueue(unsigned long, unsigned long)'
Engine.cpp:(.text._ZN8filament7details7FEngineC2ENS_7backend7BackendEPNS2_8PlatformEPv+0x332): undefined reference to `filaflat::ShaderBuilder::ShaderBuilder()'
Engine.cpp:(.text._ZN8filament7details7FEngineC2ENS_7backend7BackendEPNS2_8PlatformEPv+0x342): undefined reference to `filaflat::ShaderBuilder::ShaderBuilder()'
Engine.cpp:(.text._ZN8filament7details7FEngineC2ENS_7backend7BackendEPNS2_8PlatformEPv+0x462): undefined reference to `filaflat::ShaderBuilder::~ShaderBuilder()'
Engine.cpp:(.text._ZN8filament7details7FEngineC2ENS_7backend7BackendEPNS2_8PlatformEPv+0x471): undefined reference to `filaflat::ShaderBuilder::~ShaderBuilder()'
Engine.cpp:(.text._ZN8filament7details7FEngineC2ENS_7backend7BackendEPNS2_8PlatformEPv+0x4a2): undefined reference to `filament::backend::CommandBufferQueue::~CommandBufferQueue()'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::~FEngine()':
Engine.cpp:(.text._ZN8filament7details7FEngineD2Ev+0x3e): undefined reference to `filament::backend::DefaultPlatform::destroy(filament::backend::DefaultPlatform**)'
Engine.cpp:(.text._ZN8filament7details7FEngineD2Ev+0x10c): undefined reference to `filaflat::ShaderBuilder::~ShaderBuilder()'
Engine.cpp:(.text._ZN8filament7details7FEngineD2Ev+0x118): undefined reference to `filaflat::ShaderBuilder::~ShaderBuilder()'
Engine.cpp:(.text._ZN8filament7details7FEngineD2Ev+0x148): undefined reference to `filament::backend::CommandBufferQueue::~CommandBufferQueue()'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::shutdown()':
Engine.cpp:(.text._ZN8filament7details7FEngine8shutdownEv+0x207): undefined reference to `filament::backend::CommandBufferQueue::flush()'
Engine.cpp:(.text._ZN8filament7details7FEngine8shutdownEv+0x20f): undefined reference to `filament::backend::CommandBufferQueue::requestExit()'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::flushCommandBuffer(filament::backend::CommandBufferQueue&)':
Engine.cpp:(.text._ZN8filament7details7FEngine18flushCommandBufferERNS_7backend18CommandBufferQueueE+0x12): undefined reference to `filament::backend::CommandBufferQueue::flush()'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::flush()':
Engine.cpp:(.text._ZN8filament7details7FEngine5flushEv+0x19): undefined reference to `filament::backend::CommandBufferQueue::flush()'
../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::execute()':
Engine.cpp:(.text._ZN8filament7details7FEngine7executeEv+0x1b): undefined reference to `filament::backend::CommandBufferQueue::waitForCommands() const'
Engine.cpp:(.text._ZN8filament7details7FEngine7executeEv+0x4c): undefined reference to `filament::backend::CommandStream::execute(void*)'
Engine.cpp:(.text._ZN8filament7details7FEngine7executeEv+0x57): undefined reference to `filament::backend::CommandBufferQueue::releaseBuffer(filament::backend::CommandBufferQueue::Slice const&)'
/tmp/suzanne-ef5171.o: In function `main::$_0::operator()(filament::Engine*, filament::View*, filament::Scene*) const':
suzanne.cpp:(.text+0x1dc0): undefined reference to `TEXTURES_ALBEDO_S3TC_OFFSET'
suzanne.cpp:(.text+0x1dc7): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1dce): undefined reference to `TEXTURES_ALBEDO_S3TC_SIZE'
suzanne.cpp:(.text+0x1e10): undefined reference to `TEXTURES_AO_OFFSET'
suzanne.cpp:(.text+0x1e17): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1e1d): undefined reference to `TEXTURES_AO_SIZE'
suzanne.cpp:(.text+0x1e5e): undefined reference to `TEXTURES_METALLIC_OFFSET'
suzanne.cpp:(.text+0x1e65): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1e6b): undefined reference to `TEXTURES_METALLIC_SIZE'
suzanne.cpp:(.text+0x1eac): undefined reference to `TEXTURES_ROUGHNESS_OFFSET'
suzanne.cpp:(.text+0x1eb3): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1eb9): undefined reference to `TEXTURES_ROUGHNESS_SIZE'
suzanne.cpp:(.text+0x1f85): undefined reference to `TEXTURES_NORMAL_OFFSET'
suzanne.cpp:(.text+0x1f8c): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1f93): undefined reference to `TEXTURES_NORMAL_SIZE'
suzanne.cpp:(.text+0x1fdb): undefined reference to `RESOURCES_TEXTUREDLIT_OFFSET'
suzanne.cpp:(.text+0x1fe2): undefined reference to `RESOURCES_PACKAGE'
suzanne.cpp:(.text+0x1fe9): undefined reference to `RESOURCES_TEXTUREDLIT_SIZE'
suzanne.cpp:(.text+0x21ea): undefined reference to `RESOURCES_SUZANNE_OFFSET'
suzanne.cpp:(.text+0x21f0): undefined reference to `RESOURCES_PACKAGE'
/tmp/FilamentApp-94694a.o: In function `FilamentApp::run(Config const&, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*)>, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*)>, std::__1::function<void (filament::Engine*, filament::View*)>, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*, filament::Renderer*)>, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*, filament::Renderer*)>, unsigned long, unsigned long)':
FilamentApp.cpp:(.text+0xd8e): undefined reference to `RESOURCES_DEPTHVISUALIZER_OFFSET'
FilamentApp.cpp:(.text+0xd95): undefined reference to `RESOURCES_PACKAGE'
FilamentApp.cpp:(.text+0xd9c): undefined reference to `RESOURCES_DEPTHVISUALIZER_SIZE'
FilamentApp.cpp:(.text+0xe2c): undefined reference to `RESOURCES_AIDEFAULTMAT_OFFSET'
FilamentApp.cpp:(.text+0xe33): undefined reference to `RESOURCES_PACKAGE'
FilamentApp.cpp:(.text+0xe3a): undefined reference to `RESOURCES_AIDEFAULTMAT_SIZE'
FilamentApp.cpp:(.text+0xeaf): undefined reference to `RESOURCES_TRANSPARENTCOLOR_OFFSET'
FilamentApp.cpp:(.text+0xeb6): undefined reference to `RESOURCES_PACKAGE'
FilamentApp.cpp:(.text+0xebd): undefined reference to `RESOURCES_TRANSPARENTCOLOR_SIZE'
/tmp/FilamentApp-94694a.o: In function `FilamentApp::Window::configureCamerasForWindow()':
FilamentApp.cpp:(.text+0x6902): undefined reference to `filament::Camera::setLensProjection(double, double, double, double)'
FilamentApp.cpp:(.text+0x6a34): undefined reference to `filament::Camera::lookAt(filament::math::details::TVec3<float> const&, filament::math::details::TVec3<float> const&)'
../lib/x86_64//libfilament.a(VertexBuffer.cpp.o): In function `filament::VertexBuffer::Builder::attribute(filament::VertexAttribute, unsigned char, filament::backend::ElementType, unsigned int, unsigned char)':

Edit 2 - error list after 2nd alteration:

/tmp/suzanne-e10eca.o: In function `main::$_0::operator()(filament::Engine*, filament::View*, filament::Scene*) const':
suzanne.cpp:(.text+0x1dc0): undefined reference to `TEXTURES_ALBEDO_S3TC_OFFSET'
suzanne.cpp:(.text+0x1dc7): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1dce): undefined reference to `TEXTURES_ALBEDO_S3TC_SIZE'
suzanne.cpp:(.text+0x1e10): undefined reference to `TEXTURES_AO_OFFSET'
suzanne.cpp:(.text+0x1e17): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1e1d): undefined reference to `TEXTURES_AO_SIZE'
suzanne.cpp:(.text+0x1e5e): undefined reference to `TEXTURES_METALLIC_OFFSET'
suzanne.cpp:(.text+0x1e65): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1e6b): undefined reference to `TEXTURES_METALLIC_SIZE'
suzanne.cpp:(.text+0x1eac): undefined reference to `TEXTURES_ROUGHNESS_OFFSET'
suzanne.cpp:(.text+0x1eb3): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1eb9): undefined reference to `TEXTURES_ROUGHNESS_SIZE'
suzanne.cpp:(.text+0x1f85): undefined reference to `TEXTURES_NORMAL_OFFSET'
suzanne.cpp:(.text+0x1f8c): undefined reference to `TEXTURES_PACKAGE'
suzanne.cpp:(.text+0x1f93): undefined reference to `TEXTURES_NORMAL_SIZE'
suzanne.cpp:(.text+0x1fdb): undefined reference to `RESOURCES_TEXTUREDLIT_OFFSET'
suzanne.cpp:(.text+0x1fe2): undefined reference to `RESOURCES_PACKAGE'
suzanne.cpp:(.text+0x1fe9): undefined reference to `RESOURCES_TEXTUREDLIT_SIZE'
suzanne.cpp:(.text+0x21ea): undefined reference to `RESOURCES_SUZANNE_OFFSET'
suzanne.cpp:(.text+0x21f0): undefined reference to `RESOURCES_PACKAGE'
/tmp/FilamentApp-0b5ad4.o: In function `FilamentApp::run(Config const&, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*)>, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*)>, std::__1::function<void (filament::Engine*, filament::View*)>, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*, filament::Renderer*)>, std::__1::function<void (filament::Engine*, filament::View*, filament::Scene*, filament::Renderer*)>, unsigned long, unsigned long)':
FilamentApp.cpp:(.text+0xd8e): undefined reference to `RESOURCES_DEPTHVISUALIZER_OFFSET'
FilamentApp.cpp:(.text+0xd95): undefined reference to `RESOURCES_PACKAGE'
FilamentApp.cpp:(.text+0xd9c): undefined reference to `RESOURCES_DEPTHVISUALIZER_SIZE'
FilamentApp.cpp:(.text+0xe2c): undefined reference to `RESOURCES_AIDEFAULTMAT_OFFSET'
FilamentApp.cpp:(.text+0xe33): undefined reference to `RESOURCES_PACKAGE'
FilamentApp.cpp:(.text+0xe3a): undefined reference to `RESOURCES_AIDEFAULTMAT_SIZE'
FilamentApp.cpp:(.text+0xeaf): undefined reference to `RESOURCES_TRANSPARENTCOLOR_OFFSET'
FilamentApp.cpp:(.text+0xeb6): undefined reference to `RESOURCES_PACKAGE'
FilamentApp.cpp:(.text+0xebd): undefined reference to `RESOURCES_TRANSPARENTCOLOR_SIZE'
/tmp/FilamentApp-0b5ad4.o: In function `FilamentApp::Window::configureCamerasForWindow()':
FilamentApp.cpp:(.text+0x6902): undefined reference to `filament::Camera::setLensProjection(double, double, double, double)'
FilamentApp.cpp:(.text+0x6a34): undefined reference to `filament::Camera::lookAt(filament::math::details::TVec3<float> const&, filament::math::details::TVec3<float> const&)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Edit 3

The variables referred to in the above errors are stored in the files ./generated/resources/resources.h and ./generated/resources/textures.h, in relation to the clang++ directory of execution. The header file includes for these files look like this:

#include "generated/resources/resources.h"
#include "generated/resources/textures.h"

and I have included -I./ in the clang command, so surely clang should be able to see these files?

Here is textures.h:

#ifndef TEXTURES_H_
#define TEXTURES_H_

#include <stdint.h>

extern "C" {
    extern const uint8_t TEXTURES_PACKAGE[];
    extern int TEXTURES_ALBEDO_S3TC_OFFSET;
    extern int TEXTURES_ALBEDO_S3TC_SIZE;
    extern int TEXTURES_ROUGHNESS_OFFSET;
    extern int TEXTURES_ROUGHNESS_SIZE;
    extern int TEXTURES_METALLIC_OFFSET;
    extern int TEXTURES_METALLIC_SIZE;
    extern int TEXTURES_AO_OFFSET;
    extern int TEXTURES_AO_SIZE;
    extern int TEXTURES_NORMAL_OFFSET;
    extern int TEXTURES_NORMAL_SIZE;
}
#define TEXTURES_ALBEDO_S3TC_DATA (TEXTURES_PACKAGE + TEXTURES_ALBEDO_S3TC_OFFSET)
#define TEXTURES_ROUGHNESS_DATA (TEXTURES_PACKAGE + TEXTURES_ROUGHNESS_OFFSET)
#define TEXTURES_METALLIC_DATA (TEXTURES_PACKAGE + TEXTURES_METALLIC_OFFSET)
#define TEXTURES_AO_DATA (TEXTURES_PACKAGE + TEXTURES_AO_OFFSET)
#define TEXTURES_NORMAL_DATA (TEXTURES_PACKAGE + TEXTURES_NORMAL_OFFSET)

#endif

Edit 4:

Error list after finding resource libraries:

/tmp/FilamentApp-11fd11.o: In function `FilamentApp::Window::configureCamerasForWindow()':
FilamentApp.cpp:(.text+0x6902): undefined reference to `filament::Camera::setLensProjection(double, double, double, double)'
FilamentApp.cpp:(.text+0x6a34): undefined reference to `filament::Camera::lookAt(filament::math::details::TVec3<float> const&, filament::math::details::TVec3<float> const&)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Edit 5:

Some more errors...

../lib/x86_64//libfilament.a(Engine.cpp.o): In function `filament::details::FEngine::loop()':
/home/joel/Documents/KiwiTest/filament-source/out/cmake-debug/../../filament/src/Engine.cpp:448: undefined reference to `filament::matdbg::DebugServer::DebugServer(filament::backend::Backend, int)'
/home/joel/Documents/KiwiTest/filament-source/out/cmake-debug/../../filament/src/Engine.cpp:453: undefined reference to `filament::matdbg::DebugServer::~DebugServer()'
../lib/x86_64//libfilament.a(Material.cpp.o): In function `filament::Material::Builder::build(filament::Engine&)':
/home/joel/Documents/KiwiTest/filament-source/out/cmake-debug/../../filament/src/Material.cpp:110: undefined reference to `filament::matdbg::DebugServer::addMaterial(utils::CString const&, void const*, unsigned long, void*)'
clang: error: linker command failed with exit code 1 (use -v to see invocation)

1 Answers1

1

Put all libraries last on the line when compiling:

clang++-7 -stdlib=libc++ -L../lib/x86_64/ -I./ -I../include/ -I../app/ -I${RESOURCE_DIR} -Wno-extern-c-compat -std=c++14 ../suzanne.cpp ../app/Cube.cpp ../app/FilamentApp.cpp ../app/IBL.cpp ../app/IcoSphere.cpp ../app/Image.cpp ../app/NativeWindowHelperLinux.cpp ../app/Sphere.cpp -o suzanne ${FILAMENT_LIBS} -lSDL2 -ldl -pthread

Also, use only -pthread (remove -lpthread). There's no need for both.

You may also want to remove -lc++ and replace it with -stdlib=libc++ (which does not need to be towards the end of the line).

What happens when linking your program is that when the linker encounters a symbol (function) that has not been encountered before, it puts it on a list of unresolved symbols. For every library it examines after that, the unresolved symbols are checked. If it encounters symbols that exists in libraries already examined, linking will fail.

The ordering of the filament libraries is also important so try:

FILAMENT_LIBS=-lfilament -lbackend -lbluegl -lbluevk -lfilabridge -lfilaflat -lutils -lgeometry -lsmol-v -libl

as stated in the documentation. Note that I removed the non-filament libraries from FILAMENT_LIBS. Put those libraries in a separate variable and place that before the FILAMENT_LIBS variable when compiling.

If SDL2 uses filament, you should put -lSDL2 before the filament libraries, otherwise, leave it where it is.

If it's still failing, you can, as a last resort, wrap the filament libraries in a library group to make it resolve the internal filament dependencies:

-Wl,--start-group $(FILAMENT_LIBS) -Wl,--end-group

This is however not what you want since it's slow, but, it's an option.

An even more desperate option would be to wrap all libraries inside the -Wl,--start-group ... -Wl,--end-group pair just to verify that it compiles and that you have all the libraries and object files needed.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • Thanks very much for this answer! Changing the command to the one you gave halved the number of "undefined reference" errors raised, but there are still about 200 of them still occurring. Is there anything else obvious that may be causing these? – Joel McAllister Mar 11 '20 at 14:21
  • @JoelMcAllister You are welcome. Copy the first 10-20 of the lines of errors into the question and I'll see if I can figure it out. – Ted Lyngmo Mar 11 '20 at 14:48
  • @JoelMcAllister Updated. – Ted Lyngmo Mar 11 '20 at 15:35
  • Excellent, the number of errors has come down to just 35 lines now! Grouping the filament dependencies with `clang++-7 [...] -o suzanne -Wl,--start-group ${DEPENDENCIES} ${FILAMENT_LIBS} -Wl,--end-group -lSDL2 [...]` gives the same result. – Joel McAllister Mar 11 '20 at 16:13
  • @JoelMcAllister Ok, it can find `resources.h` alright, but not the `extern` variables it declares, so there should be a library or object file (`.o`) to go with that `resources.h`. Is there a `resources.o` or `libresources.a` somewhere? If you can find it, it looks like that should be _after_ the filament libraries when compiling. Edit: It's probably in one of the `gl` libraries I told you to put before the filament libraries. Move them one by one to the end - or wrap **all** libs inside the `start-group` ... `end-group` thingy to see that you've got all you need. Updated the answer. – Ted Lyngmo Mar 11 '20 at 16:38
  • Yes, there is! There are `generated/resources/textures.S` and `generated/resources/resources.S` files, which I have included. The error count has now come down to two more undefined references, but wrapping all the libraries with `-Wl` doesn't change things. I'll try moving them one by one to the end. (updated the question) – Joel McAllister Mar 11 '20 at 16:57
  • The merged pull request [#2192](https://github.com/google/filament/pull/2192) - "_Remove broken setLensProjection API_" suggests there's been some recent changes in that area. Though the error you get suggests that you actually have the new version of the headers (taking four arguments - the old took three). Perhaps the actual libraries include the old API? Where did you get the precompiled version from? Is it updated daily? If so, download and unpack a fresh version. If not, clone the filament repo from github and rebuild it from source. – Ted Lyngmo Mar 11 '20 at 17:12
  • 1
    Replacing my `libfilament.a` file with one on a debug build type produced errors about `undefined reference to DebugServer`, so I'm going to try compiling from source with release build type instead. – Joel McAllister Mar 11 '20 at 17:33
  • Okay, so here's where I'm at: compiling with the library from the release build works!... but the program crashes straight away with `Segmentation fault (core dumped)`. Attempting to compile with the library from the debug build gives me the error I have put in Edit 5 of my question. – Joel McAllister Mar 11 '20 at 18:09
  • Ok, great that the actual question with the linking error has been resolved at least. Does it crash even if you create an "empty" program (`int main() {}`) and just link with the directories? – Ted Lyngmo Mar 11 '20 at 18:13
  • Using the **release** library, the main() function from [here](https://github.com/google/filament/tree/master/filament) runs fine and then exits as expected. Using the **debug** library obviously still raises the linker errors of Edit 5. – Joel McAllister Mar 11 '20 at 18:30
  • Yeah, the missing symbols in the debug lib is a different matter - could be some compilation option you need to turn on _or_ the `master` branch is broken. Perhaps the repo has release tags? If so, you could try checking out one of those and rebuild. When it comes to release: Well, it's hard to say. I guess you can add function by function until it break and take it from there - or run `valgrind` or similar on the program you have to see where it breaks. You can also add sanatizer libs and flags to the build to make that easier. – Ted Lyngmo Mar 11 '20 at 18:42
  • @JoelMcAllister I usually add `-fsanitize=undefined -fsanitize=address -fsanitize=leak -fstack-check -lasan -lubsan` before the libs (`asan` and `ubsan` needs to come before the libs) - but I don't think that'll work with `-stdlib=libc++`. – Ted Lyngmo Mar 11 '20 at 18:44
  • I'll have a go with `valgrind` and let you know if I manage to get it to work. Thanks so much for your help, it's been invaluable! – Joel McAllister Mar 11 '20 at 18:57