Discussion:
Per-Object Flags for Autotool C++ library?
Jeffrey Walton
2017-11-02 22:04:14 UTC
Permalink
I'm working on adding Autotools to a C++ library and test program. My
Automake.am has:

lib_LTLIBRARIES = \
libcryptopp.la

libcryptopp_la_SOURCES = \
cryptolib.cpp \
cpu.cpp \
integer.cpp \
<remaining files in alphabetical order>
...

cpu.cpp needs additional flags to enable ISAs on IA-32, Aarch64 and
Power7/Power8. According to
https://www.gnu.org/software/automake/manual/html_node/Per_002dObject-Flags.html,
I need to add an additional library:

CPU_FLAG = -msse2 -msse3 -mssse3
libcpu_a_SOURCES = cpu.cpp
libcpu_a_CXXFLAGS = $(CXXFLAGS) $(CPU_FLAG)

Now that the objects are built we need to add libcpu.a back into
libcryptopp.la in the exact position it would have been in if I could
have specified per-object flags. The Automake manual gives an example
of linking a program with disjoint libraries, but not adding the
extraneous library back to the main (primary?) library at a particular
position.

The "in the exact position" is important. Because this is C++, we have
to contend with Static Initialization Order Fiasco
(https://isocpp.org/wiki/faq/ctors#static-init-order). We need the
initializers for cryptlib.o to run first, then the initializers for
cpu.o to run, then the initializers for integer.o to run. The
remaining ones are "don't care".

My question is, how do I add libcpu.a back into libcryptopp.la in that
exact position it would have been in if I could have specified
per-object flags?

Thanks in advance,

Jeff
Nick Bowler
2017-11-03 00:37:44 UTC
Permalink
Hi Jeffrey,
Post by Jeffrey Walton
I'm working on adding Autotools to a C++ library and test program. My
lib_LTLIBRARIES = \
libcryptopp.la
libcryptopp_la_SOURCES = \
cryptolib.cpp \
cpu.cpp \
integer.cpp \
<remaining files in alphabetical order>
...
cpu.cpp needs additional flags to enable ISAs on IA-32, Aarch64 and
Power7/Power8. According to
https://www.gnu.org/software/automake/manual/html_node/Per_002dObject-Flags.html,
CPU_FLAG = -msse2 -msse3 -mssse3
libcpu_a_SOURCES = cpu.cpp
libcpu_a_CXXFLAGS = $(CXXFLAGS) $(CPU_FLAG)
Note that you should not include $(CXXFLAGS) here. CXXFLAGS is always
included (so with this it will duplicated on the command line, which
might be undesired by the user).
Post by Jeffrey Walton
Now that the objects are built we need to add libcpu.a back into
libcryptopp.la in the exact position it would have been in if I could
have specified per-object flags. The Automake manual gives an example
of linking a program with disjoint libraries, but not adding the
extraneous library back to the main (primary?) library at a particular
position.
The "in the exact position" is important.
Not too familiar with C++ stuff but I would be a bit concerned that
it might not be possible at all to force a particular link order for
the objects in the static version of the library.

Nevertheless for the shared library case you can probably achieve this
using several dummy libraries. Something like this should work OK
(totally untested):

lib_LTLIBRARIES = libfoo.la
EXTRA_LTLIBRARIES = libdummy1.la libdummy2.la libdummy3.la

libdummy1_la_SOURCES = a.cpp b.cpp

libdummy2_la_SOURCES = c.cpp d.cpp
libdummy2_la_CXXFLAGS = -mstuff

libdummy3_la_SOURCES = e.cpp f.cpp

libfoo_la_SOURCES =
libfoo_la_LIBADD = $(libdummy1_la_OBJECTS) \
$(libdummy2_la_OBJECTS) \
$(libdummy3_la_OBJECTS)

and then the linking order should be a, b, c, d, e, f -- with c and d
compiled using your special flags.

Cheers,
Nick
Mathieu Lirzin
2017-11-03 09:54:16 UTC
Permalink
Hello,
Post by Nick Bowler
Post by Jeffrey Walton
CPU_FLAG = -msse2 -msse3 -mssse3
libcpu_a_SOURCES = cpu.cpp
libcpu_a_CXXFLAGS = $(CXXFLAGS) $(CPU_FLAG)
Note that you should not include $(CXXFLAGS) here. CXXFLAGS is always
included (so with this it will duplicated on the command line, which
might be undesired by the user).
I guess Jeffrey was intending to use AM_CXXFLAGS which is overridden by
libcpu_a_CXXFLAGS.
Post by Nick Bowler
Post by Jeffrey Walton
Now that the objects are built we need to add libcpu.a back into
libcryptopp.la in the exact position it would have been in if I could
have specified per-object flags. The Automake manual gives an example
of linking a program with disjoint libraries, but not adding the
extraneous library back to the main (primary?) library at a particular
position.
The "in the exact position" is important.
I don't fully understand the "static initialization fiasco", however the
next entry in the FAQ [1] seems to suggest that it can be avoided
programmatically using a function wrapper instead of relying on the
linking order.

If this kind of issue is common in C++, I think it would be good to
give a hint in the Automake manual on how to solve it.

Thanks.

[1] https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use
--
Mathieu Lirzin
GPG: F2A3 8D7E EB2B 6640 5761 070D 0ADE E100 9460 4D37
Jeffrey Walton
2017-11-03 12:57:59 UTC
Permalink
Post by Mathieu Lirzin
Hello,
Post by Nick Bowler
Post by Jeffrey Walton
CPU_FLAG = -msse2 -msse3 -mssse3
libcpu_a_SOURCES = cpu.cpp
libcpu_a_CXXFLAGS = $(CXXFLAGS) $(CPU_FLAG)
Note that you should not include $(CXXFLAGS) here. CXXFLAGS is always
included (so with this it will duplicated on the command line, which
might be undesired by the user).
I guess Jeffrey was intending to use AM_CXXFLAGS which is overridden by
libcpu_a_CXXFLAGS.
Post by Nick Bowler
Post by Jeffrey Walton
Now that the objects are built we need to add libcpu.a back into
libcryptopp.la in the exact position it would have been in if I could
have specified per-object flags. The Automake manual gives an example
of linking a program with disjoint libraries, but not adding the
extraneous library back to the main (primary?) library at a particular
position.
The "in the exact position" is important.
I don't fully understand the "static initialization fiasco", however the
next entry in the FAQ [1] seems to suggest that it can be avoided
programmatically using a function wrapper instead of relying on the
linking order.
Ah, thanks. That relies on Dynamic Initialization and Destruction with
Concurrency (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm).
It is C++11 and only partially available on most platforms. And it is
only available on Windows 10 and above for Microsoft platforms.

We found explicitly controlling the order of initialization produces
the best results on all the platforms we support. There are other
controls that produce even better results, like Linux init_priority
and Microsoft ini_seg, but they are platform specific. We still need
something for AIX, some BSDs, OS X, and Solaris. On AIX, Solaris and
friends we fall back to object file ordering.
Post by Mathieu Lirzin
If this kind of issue is common in C++, I think it would be good to
give a hint in the Automake manual on how to solve it.
With respect to how common it is, in the past I audited open source
C++ projects to get an idea of how widespread the problem could be.
Something like 80% of them had objects that could suffer the problem
(emphasis "could").

The fix is rather easy once you know the cause. Once the dependencies
have been determined among translation units, you just order the
object files accordingly. A well written program or library will have
few (or none) of them. In our case, we have 160 source files, 1400
classes, and only 3 object files matter. The rest of the object files
are "don't care", and we order them alphabetically for a deterministic
build.

Some of the of the things Automake could do to help with the problem
are (1) honor existing list orders (certainly do not reorder them),
and (2) make it easy to combine sublibraries (?) into the primary
library due to per-object flags. And maybe (3) is, add better support
for per-object flags so sublibraries do not need to be combined in the
first place.

For documentation updates, I would like to see an example of how to
combine libraries when sublibraries must be combined due to per-object
flags. I recommend it be places on the Per-Object Flags page where the
example for a program is:
https://www.gnu.org/software/automake/manual/html_node/Per_002dObject-Flags.html
.

I kind of like the idea of (3) because we have a lot of source files
that require architectural specific flags:
https://github.com/weidai11/cryptopp/blob/master/GNUmakefile#L983 . As
we add more ARM NEON and Aarch32/64 support the list is going to get
bigger. It would be nice if we did not need to build a separate
library for them.

Jeff
Jeffrey Walton
2017-11-03 13:58:08 UTC
Permalink
This post might be inappropriate. Click to display it.
Jeffrey Walton
2017-11-03 17:13:58 UTC
Permalink
Post by Jeffrey Walton
I'm working on adding Autotools to a C++ library and test program. My
<Nick and Mathieu's changes applied>
...
I believe I applied Nick and Mathieu correctly. The project is
available at https://github.com/noloader/cryptopp-autotools . It
includes the six Git commands to duplicate the issue.

The new issue is, the compile stops after about 4 files are compiled.
Here's the pastebin of `make V=1`: https://pastebin.com/nCYN2RHh. The
error is also shown below.

The linker is invoked for reasons unknown to me at the moment. Most of
the objects are missing. I even deleted the project's directory and
re-cloned to ensure they were not old artifacts hanging around.

I have no idea why a C compiler is being invoked in some places. I
took great care to ensure Autoconf knew this was a C++ project, and
not a C project. That's another problem I've been searching for an
answer for.

Any ideas what I might be doing wrong this time?

Thanks in advance,

Jeff

***********

In the error below, integer.cpp was not compiled.

libtool: error: 'libinteger_la-integer.lo' is not a valid libtool object
make[1]: *** [Makefile:1056: libcryptopp.la] Error 1
make[1]: Leaving directory '/home/jwalton/cryptopp'
make: *** [Makefile:952: all] Error 2
Nick Bowler
2017-11-03 17:51:12 UTC
Permalink
Post by Jeffrey Walton
Post by Jeffrey Walton
I'm working on adding Autotools to a C++ library and test program. My
<Nick and Mathieu's changes applied>
...
I believe I applied Nick and Mathieu correctly. The project is
available at https://github.com/noloader/cryptopp-autotools . It
includes the six Git commands to duplicate the issue.
The new issue is, the compile stops after about 4 files are compiled.
Here's the pastebin of `make V=1`: https://pastebin.com/nCYN2RHh. The
error is also shown below.
The linker is invoked for reasons unknown to me at the moment. Most of
the objects are missing. I even deleted the project's directory and
re-cloned to ensure they were not old artifacts hanging around.
For whatever reason it appears that the generated makefile has missing
prerequisites for libcryptopp.la. I would expect everything listed in
LIBADD to end up as a prerequisite of the library. This might require
some investigation to find out why that apparently did not happen in
your case.

Adding everything to EXTRA_libcryptopp_la_DEPENDENCIES might help as
a workaround, e.g.,

EXTRA_libcryptopp_la_DEPENDENCIES = $(libcryptopp_la_LIBADD)

But this (or equivalent) should have happened automatically.
Post by Jeffrey Walton
I have no idea why a C compiler is being invoked in some places. I
took great care to ensure Autoconf knew this was a C++ project, and
not a C project. That's another problem I've been searching for an
answer for.
It seems it decided to link the library using the C compiler because no
source files are specified for the library. There may be (or should be)
a way to force it one way or the other, but an obvious workaround is to
specify at least one C++ source file in libcryptopp_la_SOURCES (could be
one of the real files or just a stub). The _SOURCES objects will appear
earlier on the linker command line than any of the _LIBADD objects.

Cheers,
Nick
Jeffrey Walton
2017-11-03 18:10:14 UTC
Permalink
Post by Nick Bowler
Post by Jeffrey Walton
Post by Jeffrey Walton
I'm working on adding Autotools to a C++ library and test program. My
<Nick and Mathieu's changes applied>
...
...
For whatever reason it appears that the generated makefile has missing
prerequisites for libcryptopp.la. I would expect everything listed in
LIBADD to end up as a prerequisite of the library. This might require
some investigation to find out why that apparently did not happen in
your case.
Adding everything to EXTRA_libcryptopp_la_DEPENDENCIES might help as
a workaround, e.g.,
EXTRA_libcryptopp_la_DEPENDENCIES = $(libcryptopp_la_LIBADD)
But this (or equivalent) should have happened automatically.
Thanks again. I thought this might be the case, but I don't read
Automake makefiles well.

Thumbing through the generated makefile did reveal this problem:
https://github.com/noloader/cryptopp-autotools/commit/961792eaee39.
Post by Nick Bowler
Post by Jeffrey Walton
I have no idea why a C compiler is being invoked in some places. I
took great care to ensure Autoconf knew this was a C++ project, and
not a C project. That's another problem I've been searching for an
answer for.
It seems it decided to link the library using the C compiler because no
source files are specified for the library. There may be (or should be)
a way to force it one way or the other, but an obvious workaround is to
specify at least one C++ source file in libcryptopp_la_SOURCES (could be
one of the real files or just a stub). The _SOURCES objects will appear
earlier on the linker command line than any of the _LIBADD objects.
Ah, thanks again. Maybe this could trigger the behavior
(https://github.com/noloader/cryptopp-autotools/blob/master/Makefile.am#L39):

AM_DEFAULT_SOURCE_EXT = .cpp

Jeff
Jeffrey Walton
2017-11-03 19:07:16 UTC
Permalink
Post by Nick Bowler
Post by Jeffrey Walton
...
The new issue is, the compile stops after about 4 files are compiled.
Here's the pastebin of `make V=1`: https://pastebin.com/nCYN2RHh. The
error is also shown below.
...
For whatever reason it appears that the generated makefile has missing
prerequisites for libcryptopp.la. I would expect everything listed in
LIBADD to end up as a prerequisite of the library. This might require
some investigation to find out why that apparently did not happen in
your case.
Adding everything to EXTRA_libcryptopp_la_DEPENDENCIES might help as
a workaround, e.g.,
Perfect, thanks. That was it.


Jeff
Jeffrey Walton
2017-11-04 18:48:08 UTC
Permalink
Post by Jeffrey Walton
Post by Nick Bowler
Post by Jeffrey Walton
...
The new issue is, the compile stops after about 4 files are compiled.
Here's the pastebin of `make V=1`: https://pastebin.com/nCYN2RHh. The
error is also shown below.
...
For whatever reason it appears that the generated makefile has missing
prerequisites for libcryptopp.la. I would expect everything listed in
LIBADD to end up as a prerequisite of the library. This might require
some investigation to find out why that apparently did not happen in
your case.
Adding everything to EXTRA_libcryptopp_la_DEPENDENCIES might help as
a workaround, e.g.,
Perfect, thanks. That was it.
A quick follow up...

EXTRA_libcryptopp_la_DEPENDENCIES listing the objects worked for Linux
and OS X, but not Solaris. For Solaris I needed to drop the leading
`EXTRA`, and use just `libcryptopp_la_DEPENDENCIES`.

Jeff
Nick Bowler
2017-11-04 19:56:45 UTC
Permalink
Hello,
Post by Jeffrey Walton
EXTRA_libcryptopp_la_DEPENDENCIES listing the objects worked for Linux
and OS X, but not Solaris. For Solaris I needed to drop the leading
`EXTRA`, and use just `libcryptopp_la_DEPENDENCIES`.
Is that just because you happen to be running an antique version of
Automake on the Solaris machine?

Cheers,
Nick
Jeffrey Walton
2017-11-04 20:52:43 UTC
Permalink
Post by Mathieu Lirzin
Hello,
Post by Jeffrey Walton
EXTRA_libcryptopp_la_DEPENDENCIES listing the objects worked for Linux
and OS X, but not Solaris. For Solaris I needed to drop the leading
`EXTRA`, and use just `libcryptopp_la_DEPENDENCIES`.
Is that just because you happen to be running an antique version of
Automake on the Solaris machine?
Well, I'm not sure. Is this considered old:

$ automake --version
automake (GNU automake) 1.11.2

One of our driving principles is "things just work". We don't want
library users inconvenienced or installing extra software. They should
be able to sit down at their computer, run configure, and everything
should work as expected.

If something does not work as expected then it becomes our problem. We
are expected to find workarounds so library users are not
inconvenienced.

Jeff
Nick Bowler
2017-11-04 21:00:59 UTC
Permalink
Post by Jeffrey Walton
Post by Nick Bowler
Post by Jeffrey Walton
EXTRA_libcryptopp_la_DEPENDENCIES listing the objects worked for Linux
and OS X, but not Solaris. For Solaris I needed to drop the leading
`EXTRA`, and use just `libcryptopp_la_DEPENDENCIES`.
Is that just because you happen to be running an antique version of
Automake on the Solaris machine?
$ automake --version
automake (GNU automake) 1.11.2
Well, it's coming up on its 6th birthday :)
Post by Jeffrey Walton
One of our driving principles is "things just work". We don't want
library users inconvenienced or installing extra software. They should
be able to sit down at their computer, run configure, and everything
should work as expected.
If something does not work as expected then it becomes our problem.
We are expected to find workarounds so library users are not
inconvenienced.
Library users shouldn't be running Automake at all, because when you
distribute a package all of the generated files are included (and do
not depend on Automake).

e.g., if you create your package with the latest versions then it
should "just work" on Solaris.

Cheers,
Nick
Jeffrey Walton
2017-11-04 21:26:57 UTC
Permalink
Post by Nick Bowler
Post by Jeffrey Walton
...
One of our driving principles is "things just work". We don't want
library users inconvenienced or installing extra software. They should
be able to sit down at their computer, run configure, and everything
should work as expected.
If something does not work as expected then it becomes our problem.
We are expected to find workarounds so library users are not
inconvenienced.
Library users shouldn't be running Automake at all, because when you
distribute a package all of the generated files are included (and do
not depend on Automake).
e.g., if you create your package with the latest versions then it
should "just work" on Solaris.
Thanks. If you come across the docs on how to do it, then please pass it on :)

Here's the closest I have found:
https://www.gnu.org/software/automake/manual/html_node/Basics-of-Distribution.html.
But it lacks a list of actionable items, so I only have part of the
picture. I don't have the experience to make the leap to what actually
needs to be done. No one with the project has the experience, either.

I'm pretty sure the folks who jump up and down asking for Autotools
don't know that much about it. I've reached out to the folks who were
pressuring us for it but they have not responded or surfaced with
suggestions or patches. I call them fan boi's :)

Jeff
Mike Mestnik
2017-11-05 17:40:56 UTC
Permalink
Post by Jeffrey Walton
Thanks. If you come across the docs on how to do it, then please pass it on :)
https://www.gnu.org/software/automake/manual/html_node/Basics-of-Distribution.html.
But it lacks a list of actionable items, so I only have part of the
picture. I don't have the experience to make the leap to what actually
needs to be done. No one with the project has the experience, either.
I'm pretty sure the folks who jump up and down asking for Autotools
don't know that much about it. I've reached out to the folks who were
pressuring us for it but they have not responded or surfaced with
suggestions or patches. I call them fan boi's :)
Jeff
Also needing documentation and perhaps support from github is release tags. There might be a handful of solutions, like always including the autotool distribution files or having a branch specifically for release tags.
Mathieu Lirzin
2017-11-05 22:32:43 UTC
Permalink
Hello Jeffrey,
Post by Jeffrey Walton
Post by Nick Bowler
Post by Jeffrey Walton
...
One of our driving principles is "things just work". We don't want
library users inconvenienced or installing extra software. They should
be able to sit down at their computer, run configure, and everything
should work as expected.
If something does not work as expected then it becomes our problem.
We are expected to find workarounds so library users are not
inconvenienced.
Library users shouldn't be running Automake at all, because when you
distribute a package all of the generated files are included (and do
not depend on Automake).
e.g., if you create your package with the latest versions then it
should "just work" on Solaris.
Thanks. If you come across the docs on how to do it, then please pass it on :)
https://www.gnu.org/software/automake/manual/html_node/Basics-of-Distribution.html.
But it lacks a list of actionable items, so I only have part of the
picture. I don't have the experience to make the leap to what actually
needs to be done. No one with the project has the experience, either.
This is the correct pointer.

The idea behind the Autotools is to not add extra dependency to the
build system for users building from source. As a consequence
maintainers when doing a release generates a tarball with "make dist"
that already contains the configure, Makefile.in which are generated by
Autoconf and Automake.

What needs to be done is to check that everything that is needed by
the build system is included in the tarball generated by 'make dist'.
This can be done manually by trying to build from that tarball or
alternatively 'make distcheck' verifies that automatically among other
important things (out of tree builds, uninstall, ...).

The first actionable item it to check that the build from the tarball
works fine, and if not add the missing files to the EXTRA_DIST variable
(or alternatives) in the Makefile.am.

'make distcheck' error message can sometime be tricky to interpret, so
feel free to ask for help.

HTH.
--
Mathieu Lirzin
GPG: F2A3 8D7E EB2B 6640 5761 070D 0ADE E100 9460 4D37
Loading...