Search code examples
c++autotoolsautomake

How to handle library's function files (not header files) in autotools?


So recently I've been trying out autotools to build a C++ Library. Originally I was just working on pure custom classes, which is quite easy to just #include and then compile with g++. But when I want to write some custom functions for the library, I've learnt that it's a bad practice to write functions within header file, but rather I should declare it in header then wrote it in a separate .cpp file. Which later I've tried and successfully compiled with g++ ./lib/**/*.cpp src/main.cpp.

So basically my project structure is that codes are put under src/, headers are under include/ and functions are put under lib/. And following is my src/Makefile.am

AUTOMAKE_OPTIONS = subdir-objects foreign
bin_PROGRAMS = main
include_HEADERS = -I../include/**/*.hpp 
main_SOURCES = -I../lib/**/*.cpp main.cpp

And for the endpoints' directory (under lib/) I have something like following

main_SOURCES = stdout.cpp

But it gave me error that no program named main, so I figured maybe all those function files has to be compiled first, so I changed them into

noinst_PROGRAMS = stdout
stdout_SOURCES = stdout.cpp

But then they gave me the following error

/usr/sbin/ld: /usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/../../../../lib/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status

I know that the error meant that there's no main() written in the file, but since it's a library function file, it doesn't meant to have a main(), it's meant to be called by other file (like main.cpp). And to here I'm stuck.

I've tried to find documentation online, but it seems that most of them are targeted at C programs instead of C++, which I'm not sure if those steps are compatible. And I remember C++ libraries are compiled into .so or .o file while most tutorials seems to be using .la files.


MWE

src/main.cpp

#include"../include/conn/bash/stdout.hpp"
#include<string>
#include<iostream>

int main() {
        std::string  o=d::conn::bash::exec("ls");
        std::cout << o << std::endl;
        return 0;
}

include/conn/bash/stdout.hpp

#ifndef __CONN_BASH_O__
#define __CONN_BASH_O__
#include<string>
namespace d { namespace conn { namespace bash {
        std::string exec(const char*);
}}}
#endif

lib/conn/bash/stdout.cpp

#include"../../../include/conn/bash/stdout.hpp"
#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <array>

std::string d::conn::bash::exec(const char* cmd) {
    std::array<char, 128> buffer;
    std::string result;
    std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
    if (!pipe) {
        throw std::runtime_error("popen() failed!");
    }
    while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
        result += buffer.data();
    }
    return result;
}
// https://stackoverflow.com/a/478960/8460574

Compiled and tested with g++ ./lib/**/*.cpp src/main.cpp


Solution

  • I've tried to find documentation online, but it seems that most of them are targeted at C programs instead of C++,

    "Most of them" suggests that you are searching out tutorials. Documentation would be the Automake manual, which you would be well advised to read. It covers these topics and many more that you will want to know about as you continue with the Autotools, and with Automake in particular. At minimum, you should know how to find the manual so as to consult it at need. Tutorials and guides can be helpful, but they should be regarded as supplements to the manual, not primary sources.

    which I'm not sure if those steps are compatible.

    They mostly are. The main thing to be aware of is that command-line options for the C compiler are specified via a CFLAGS primary (see below), whereas those for the C++ compiler are specified via a CXXFLAGS primary.

    And I remember C++ libraries are compiled into .so or .o file while most tutorials seems to be using .la files.

    You seem to be confused. The formats associated with those extensions are not language-specific, and .o does not designate a library at all.

    .o is the conventional extension for the object file arising from compiling a single source file. These can be linked into an executable, or they can be gathered together into a library, but they are not libraries themselves.

    Conventional UNIX library extensions are .a for static libraries and .so for shared libraries. This applies to any language that can be used to build general-purpose link libraries, including C, C++, Fortran, and a variety of others.

    The .la extension is something else. It designates a libtool library. Libtool is another of the autotools, focused on abstracting system-specific details of building and using libraries. This is again language independent. Building a libtool library will cause either a corresponding static library, a corresponding shared library, or both to be built and installed, depending the options specified in the Autotooling and / or on the configure command line.

    You should use libtool if you are building shared libraries with the Autotools. You may also use it if you are building only static libraries (.a), but it is a bit simpler in that case to leave libtool out of the picture.


    As for the specific question posed, you write:

    And for the endpoints' directory (under lib/) I have something like following

    main_SOURCES = stdout.cpp
    

    But it gave me error that no program named main,

    Always present the actual text of the error message (as text), rather than a paraphrase. If you have to ask a question about it here, then there is a significant risk that your understanding of the message is incomplete or wrong, and therefore that your paraphrase is misleading.

    In this case, I am relatively confident that Automake's complaint was that there is no target named "main". The line you've given specifies a source list for such a target, but Automake does not find that target defined in that Makefile.am file. The difference between "program" and "target" is a bit subtle, but significant.

    Supposing that you are trying to build a convenience library -- that is, one that groups functions for compile-time use in building other targets, but is not intended to be installed as a standalone library* -- you could get it with something like:

    noinst_LIBRARIES = libendpoints.a
    

    That consists of thee main pieces:

    • LIBRARIES is the "primary", specifying what kind of definition is being given. In this case, it is the definition of a list of static library targets included in the project.
    • libendpoints.a specifies the name of a (static library) target. This is an external name: the build will generate a file by this name in the build tree. Do make a habit of using standard naming conventions for this, such as I demonstrate.
    • noinst specifies where the built target will be installed by make install. This particular value is a special one indicating that the target will not be installed at all. If you wanted it installed in the configured libdir then you would instead say lib.

    The properties of that target must be given by other definitions, based on a munged form of its name. For instance, its source list might look like this:

    libendpoints_a_SOURCES = stdout.cpp
    

    That again consists of three pieces:

    • SOURCES is again the primary. In this case, it says that the definition provides a source list for some target.
    • libendpoints_a identifies the target whose source list is being given. It is a munged form of the target name, with characters that cannot appear in variable names replaced by '_' characters.
    • stdout.cpp is the (single-element) source list.

    To use that, the main program's properties would also need to specify that the library should be linked. That would mean something along these lines in the Makefile.am in which the main program target is defined:

    main_LDADD = lib/endpoints/libendpoints.a
    

    That is again a three-part definition, whose interpretation I leave as an exercise.


    *And if you wanted a static library that does get installed to the system then you would swap out the "noinst" for a prefix that identifies the installation location, such as "lib". And there is a slightly different form for libtool-mediated libraries, including shared libaries.