Moddable-OpenSource / moddable

Tools for developers to create truly open IoT products using standard JavaScript on low cost microcontrollers.
http://www.moddable.com
1.34k stars 237 forks source link

How to use static link library(*.a) with Moddable #487

Closed meganetaaan closed 3 years ago

meganetaaan commented 4 years ago

Hello!

I'm trying to use Moddable with AquesTalk ESP32, a tiny text-to-speech engine for ESP32. But the problem is that the library is distributed as a combination of static link library(libaquestalk.a) and header file(aquestalk.h).

What is the best way to start from?

I think I can modify the generated esp-idf projects in build/tmp directory but does not make sense since they are reset on every build. It would be nice if mcconfig has options to pass the build flag (-Laquestalk or something) and to copy *.a files into the project.

phoddie commented 4 years ago

I didn't realize anyone distributes binary only releases for ESP32. ;)

The build is described by the manifest. The manifest supports including C source code in a build. It could, in principle, also support archives.

Until that happens, maybe the easiest solution is add the archive to the make file -- for macOS and Linux or Windows. That will avoid the problem of the temporary generated make file being overwritten on each build.

meganetaaan commented 4 years ago

I'll try your solution. Thank you!

phoddie commented 4 years ago

If you find a solution that works, please share that here. I'd like to see. Maybe it can help to shape a manifest based solution. Thank you!

meganetaaan commented 3 years ago

Hi. I tried that solution but no luck. First I set LDFLAGS in make.esp32.mk that points to my .a file. But this did not affect to linking process of esp-idf build. According to the documentation, I seem to have to modify CMakeLists.txt to add an extra dependency for building xs_esp32.a. I'm confused how to do so. https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/build-system.html#no-longer-available-in-cmake

Here is my build log when I got link error. https://gist.github.com/meganetaaan/bafcf6dc3d47a3d045fb7ee5624aff2f

I keep trying to understand the build process.

phoddie commented 3 years ago

I'm not very good with builds, but I thought I'd give this a try. I got it to link, so maybe that is a helpful result.

To start, I downloaded the archive. Then I extracted the objects from the archive. For testing, I am using the helloworld example:

cd $MODDABLE/examples/helloworld
ar x [YOUR PATH]/aquestalk-esp32/src/esp32/libaquestalk.a 

That extracts all the AQUESTALK objects into the helloworld directory. It is ugly, but this is just an experiment. Then, I added the objects to make.esp32.mk as LIB_AQUESTALK.

LIB_AQUESTALK = \
    $(MODDABLE)/examples/helloworld/AKR168.o \
    $(MODDABLE)/examples/helloworld/AKR169.o \
    $(MODDABLE)/examples/helloworld/AKR170.o \
    $(MODDABLE)/examples/helloworld/AKR171.o \
    $(MODDABLE)/examples/helloworld/AKR172.o \
    $(MODDABLE)/examples/helloworld/AKR173.o \
    $(MODDABLE)/examples/helloworld/AKR174.o \
    $(MODDABLE)/examples/helloworld/AKR175.o \
    $(MODDABLE)/examples/helloworld/AKR176.o \
    $(MODDABLE)/examples/helloworld/AKR177.o \
    $(MODDABLE)/examples/helloworld/AKR178.o \
    $(MODDABLE)/examples/helloworld/AKR179.o \
    $(MODDABLE)/examples/helloworld/AKR180.o \
    $(MODDABLE)/examples/helloworld/AKR181.o \
    $(MODDABLE)/examples/helloworld/AKR182.o \
    $(MODDABLE)/examples/helloworld/AKR183.o \
    $(MODDABLE)/examples/helloworld/AKR184.o \
    $(MODDABLE)/examples/helloworld/AKR185.o \
    $(MODDABLE)/examples/helloworld/AKR186.o \
    $(MODDABLE)/examples/helloworld/AKR187.o \
    $(MODDABLE)/examples/helloworld/AQR004.o \
    $(MODDABLE)/examples/helloworld/ATP192.o \
    $(MODDABLE)/examples/helloworld/ATP193.o \
    $(MODDABLE)/examples/helloworld/ATP194.o \
    $(MODDABLE)/examples/helloworld/ATP195.o \
    $(MODDABLE)/examples/helloworld/ATP196.o \
    $(MODDABLE)/examples/helloworld/ATP197.o \
    $(MODDABLE)/examples/helloworld/ATP198.o \
    $(MODDABLE)/examples/helloworld/ATP199.o \
    $(MODDABLE)/examples/helloworld/ATP200.o \
    $(MODDABLE)/examples/helloworld/ATP201.o \
    $(MODDABLE)/examples/helloworld/ATP202.o \
    $(MODDABLE)/examples/helloworld/ATP203.o

To add the AQUESTALK objects to the xs archive, after this line....

    $(AR) $(AR_FLAGS) $(BIN_DIR)/xs_esp32.a $^ $(LIB_DIR)/buildinfo.c.o

...add these two lines:

    @echo "# archive libaquestalk.a"
    $(AR) rs $(BIN_DIR)/xs_esp32.a $(LIB_AQUESTALK)

That's the build. To test, add this to helloworld/main.js:

function CAqK2R() @ "xs_CAqK2R";

Then add a main.c file with this:

#include "xs.h"
#include <stdint.h>

extern int  CAqK2R_Create(uint8_t *workbuf, int sizeWorkBuf);

void xs_CAqK2R(xsMachine *the)
{
    CAqK2R_Create(NULL, 0);
}

size_t aqdic_open()
{
}

size_t aqdic_read(size_t pos, size_t size, void *buf)
{
}

I confirmed that links and CAqK2R_Create is in the output. I have no idea what to do next. The instructions are in Japanese. ;)

Anyway... I expect you will need to adjust this slightly for Windows. And this is clearly not a sustainable solution. But, maybe it can help guide us towards one.

Good luck.

meganetaaan commented 3 years ago

Your suggestion worked! Thank you! https://twitter.com/meganetaaan/status/1332001639549468674

Now I can use aquestalk functions with the settings below. https://github.com/meganetaaan/moddable/commit/f7ff906b33ce3805a356e1900e2856e4a2c4d73e The speech sound have some noise. But I think it related to my usage of AudioOut so off topic to this thread.

Still hoping if there is more handy linking feature, I'm satisfied to know a working solution. I think we can close this topic.

phoddie commented 3 years ago

Congratulations! Very nice.

What I described above is a kind of proof-of-concept. It shows how to make this work manually. That's a necessary step prior to automating it. The basic idea is simple -- merge the external archive -- aquestalk -- into the Moddable SDK archive. However, it seems necessary to expand the external archive to the constituent objects first, which makes the process a bit complex. I imagine someone with more skill than me in authoring make files could automate this pretty easily.

I'll close this out as you suggest.

meganetaaan commented 1 year ago

Hi. Just let you know that I began automating the merging process because I'm interested in using esp-dl(deep learning), which is distributed as archive files.

phoddie commented 1 year ago

Wow. That's nifty. Where do you expect ARCHIVES to be defined?

meganetaaan commented 1 year ago

Still thinking. Since this is project-specific, I'd prefer to define ARCHIVES somewhere under manifest.json. Do you have any suggestion?

phoddie commented 1 year ago

Since this is project-specific, I'd prefer to define ARCHIVES somewhere under manifest.json

Agreed.

There are two approaches that I can see.

The first is to use the module section of the manifest. We do that for .c and .h files already. That would specify one archive at a time.

    "modules": {
        "*": {
            "./archive1.a",
            "./archive2.a"
    }

That would require mcconfig being aware of archive files and a different approach in the makefile. That's a bit of work to set-up. And it would only be supported on ESP32.

Another approach would be to use the variables defined in the build section of the manifest. Since this is really an ESP32-only feature, that could be named with an ESP32_ prefix to make that clear.

    "build": {
        "ESP32_ARCHIVES": "$(MODDABLE)/archives/archive.a"
    },

Doing that causes ESP32_ARCHIVES to be defined at the top of the makefile, so your patch would work. But, build variables do not merge -- so if two manifests each define the variable, only one will be used.

The thing I'm not sure about is how you would want to specify the paths of the archive. If they are reachable with environment variables, then it works. You can now also use a relative specifier in the build section (e.g. "./archives/archive.a")... but that only supports a single archive.

phoddie commented 1 year ago

FYI – I think the only practical approach is the module route. The BUILD approach is a better fit for your makefile change, but won't scale well (different modules including different archives). You could just implement it for ESP32 to start, since that's the need.

You might model the archive support on the approach of the compileDataView support. There's an example of that at $MODDABLE/examples/js/views. The idea is that you can explicitly state the kind of transform to apply. It is slightly more verbose, but much more explicit and flexible. Here it is applying the "cdv" transform to exampleView.h.

    "modules": {
        "*": [
            "./main",
            "./accessView",
            {
                "source": "./exampleView",
                "transform": "cdv"
            }
        ]
    }

In your case, you want to expand the archive, so you might do:

    {
        "source": "./myarchive.a",
        "transform": "extract"
    }

(Note that mcmanifest.js was recently enhanced to allow extensions on file names, so it should work to put the ".a" extension on the filename)

It should be pretty straightforward to update mcmanifest to do this by tracing through the support for "cdv".

meganetaaan commented 1 year ago

I agree with your module approach. I didn't know that the modules can have an additional transform property but it seems what I wanted.


I'm trying to add archive files and headers, but unfortunately I'm struggling due to the large number of related source codes and headers, as well as the need for additional build flags. I would like to report in detail later. Related to the above, I noticed that in esp32, the add_prebuilt_library command is used to import components from esp-idf. Reference link

The module we are trying to work with is also an esp-idf component.

It seems that having a mechanism for users to include any esp-idf component would simplify the settings, especially when wanting to include large-scale components in the project. The clear drawback is that it is limited to ESP32. Has such a feature been considered before? I would like to know if there's any discrepancy in my understanding. I think this is a little off-topic so I should open a new issue or discussion.

phoddie commented 1 year ago

It looks like using add_prebuilt_library makes sense. It seems like the approach would be:

You could try a simple ad-hoc proof-of-concept by just doing the last two steps.

I think this is a little off-topic so I should open a new issue or discussion.

Probably a good idea.

beckerzito commented 9 months ago

hi folks. Any improvement on this feature? I have the interest to use prebuilt libraries as well on ESP32.