Compiling header files independently as a test.

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Compiling header files independently as a test.

Boost - Build mailing list
Boost.Build team,

I have found it useful to (and there are some that require) test
header file independence by including it by itself in a source file
and checking that it compiles.  For example, the Bloomberg  BDE
project states that the ".h file of each component should contain only
those #include directives (and forward class declarations) necessary
to ensure that the header file can compile in isolation."
(https://github.com/bloomberg/bde/wiki/physical-code-organization).

I have been exploring doing this with Boost.Build and while I've
figured out a way to get it done, it is not nearly as nice as I'd like
it to be.  Does anyone have a better approach?  if not, could we add
something to testing.jam to support this directly?

I've cobbled together some code that works for me but has several
limitations that I don't like and don't know how to fix.

1. I implement the tests in the library's Jamfile since that is where
the actual header file names are known.  I actually put all required
files (headers, sources, etc.) in the sources list of the alias or
lib.  Is that something others are doing?  This forces me to put the
source file names and header file names into separate variables so I
can iterate over them.  I also do this in order to specify the sources
for a doxygen target within Boost.Build.

  I have a separate Jamfile for tests to simplify using just the
library with other Boost.Build-based projects.  I'd prefer to have all
the tests together, but I don't want to repeat the headers nor use a
glob.

  I might consider using a glob, but that would again be in the
library definition, not the test file.

2. I'd really prefer to iterate through the sources of any target (at
least any alias or lib) to run the compile-header tests in the test
Jamfile without knowing what they are named.  This would actually make
it possible to have a separate Jamfile to generate doxygen as well.

3. The "compile-header" target might require more information if the
library has dependencies, which have to be specified in both the
library and the compile-header targets.  This seems to usually be
handled by depending on the library itself in the compile-header
target, so maybe it's not a big deal, but it is a bit strange to say
that compiling the header that is part of libx depends on libx.

I've included inline some code that reflects what I am currently
doing.  It is extremely preliminary.  I could see, at the minimum,
adding a "compile-header" and maybe "compile-header-fail" (not
implemented here) that generates a source file and attempts to compile
it to testing.jam.  However, this approach still leaves a lot of
boilerplate to the user and requires the tests are in the same Jamfile
as the alias / lib target definition.

I would not mind doing the work to get something in if others agree on
the concept and an approach to implement it.

Best regards,
Tom

1. I have a local testing-additions.jam file that adds a "compile-header" rule

```
# testing-additions.jam
import testing ;

# generate a source file and check for compile success
rule compile-header ( sources : requirements * : target-name ? )
{
  local s ;
  local t ;

  if ! $(target-name)
  {
    target-name = compile_test_$(sources:D=:S=) ;
  }

  s = $(target-name).cpp ;

  make $(s) : $(sources) : @testing-additions.generate-source-for-header ;

  compile $(s) : $(requirements) : $(target-name) ;
}

actions generate-source-for-header
{
  echo "" > $(<)
  echo "#include \"$(>)\"" >> $(<)
  echo "int main () { return 0; }" >> $(<)
  echo "" >> $(<)
}
```

2. Wherever I have header files in my projects (in an alias or a lib,
etc).  I do something like the following.

```
# a Jamfile for the header-only library libx
import testing-additions ;

# other header-only libraries that libx depends on
alias dep0 ;
alias dep1 ;

local headers =
  x0.hpp
  x1.hpp
  x2.h
  ;

alias libx
  : # sources
    $(headers)

    dep0
    dep1

  : # requirements
  : # default-build
  : # usage-requirements
    <include>src
  ;

for h in $(headers)
{
  compile-header $(h) : <dependency>libx ;
}
```
_______________________________________________
Unsubscribe & other changes: https://lists.boost.org/mailman/listinfo.cgi/boost-build
Reply | Threaded
Open this post in threaded view
|

Re: Compiling header files independently as a test.

Boost - Build mailing list
AMDG

On 03/16/2018 02:13 PM, Thomas Brown via Boost-build wrote:

>
> I have found it useful to (and there are some that require) test
> header file independence by including it by itself in a source file
> and checking that it compiles.  For example, the Bloomberg  BDE
> project states that the ".h file of each component should contain only
> those #include directives (and forward class declarations) necessary
> to ensure that the header file can compile in isolation."
> (https://github.com/bloomberg/bde/wiki/physical-code-organization).
>
> I have been exploring doing this with Boost.Build and while I've
> figured out a way to get it done, it is not nearly as nice as I'd like
> it to be.  Does anyone have a better approach?  if not, could we add
> something to testing.jam to support this directly?
>
> I've cobbled together some code that works for me but has several
> limitations that I don't like and don't know how to fix.
>
> 1. I implement the tests in the library's Jamfile since that is where
> the actual header file names are known.  I actually put all required
> files (headers, sources, etc.) in the sources list of the alias or
> lib.  Is that something others are doing?  This forces me to put the
> source file names and header file names into separate variables so I
> can iterate over them.  I also do this in order to specify the sources
> for a doxygen target within Boost.Build.
>

  I've never seen that before.  Listing the headers
as sources for a library will just cause them to
be ignored.

>   I have a separate Jamfile for tests to simplify using just the
> library with other Boost.Build-based projects.  I'd prefer to have all
> the tests together, but I don't want to repeat the headers nor use a
> glob.
>
>   I might consider using a glob, but that would again be in the
> library definition, not the test file.
>
> 2. I'd really prefer to iterate through the sources of any target (at
> least any alias or lib) to run the compile-header tests in the test
> Jamfile without knowing what they are named.  This would actually make
> it possible to have a separate Jamfile to generate doxygen as well.
>

Iterating over the sources of a target is a bad idea.
Instead, use an alias and handle the processing at the
virtual target layer (via generate, a generator, or
a custom target class).  In particular, for checking
headers, you can use a composing generator to handle
all of the following:
- compile each header in a separate translation unit.
- include all headers in a single translation unit (useful
  for catching duplicate #include guards)
- link two translation units that #include all headers.  (catches
  functions that should be inlined)

> 3. The "compile-header" target might require more information if the
> library has dependencies, which have to be specified in both the
> library and the compile-header targets.  This seems to usually be
> handled by depending on the library itself in the compile-header
> target, so maybe it's not a big deal, but it is a bit strange to say
> that compiling the header that is part of libx depends on libx.
>

<use>libx will pick up the usage-requirements, but otherwise
ignore libx, which I think is the behavior you want.

> I've included inline some code that reflects what I am currently
> doing.  It is extremely preliminary.  I could see, at the minimum,
> adding a "compile-header" and maybe "compile-header-fail" (not
> implemented here)

I don't think that compile-header-fail is useful.

> that generates a source file and attempts to compile
> it to testing.jam.  However, this approach still leaves a lot of
> boilerplate to the user and requires the tests are in the same Jamfile
> as the alias / lib target definition.
>
> I would not mind doing the work to get something in if others agree on
> the concept and an approach to implement it.
>
> Best regards,
> Tom
>
> 1. I have a local testing-additions.jam file that adds a "compile-header" rule
>
> ```
> # testing-additions.jam
> import testing ;
>
> # generate a source file and check for compile success
> rule compile-header ( sources : requirements * : target-name ? )
> {
>   local s ;
>   local t ;
>
>   if ! $(target-name)
>   {
>     target-name = compile_test_$(sources:D=:S=) ;
>   }
>
>   s = $(target-name).cpp ;
>
>   make $(s) : $(sources) : @testing-additions.generate-source-for-header ;
>
>   compile $(s) : $(requirements) : $(target-name) ;
> }
>
> actions generate-source-for-header
> {
>   echo "" > $(<)
>   echo "#include \"$(>)\"" >> $(<)
>   echo "int main () { return 0; }" >> $(<)
>   echo "" >> $(<)
> }
> ```
>
> 2. Wherever I have header files in my projects (in an alias or a lib,
> etc).  I do something like the following.
>
> ```
> # a Jamfile for the header-only library libx
> import testing-additions ;
>
> # other header-only libraries that libx depends on
> alias dep0 ;
> alias dep1 ;
>
> local headers =
>   x0.hpp
>   x1.hpp
>   x2.h
>   ;
>
> alias libx
>   : # sources
>     $(headers)
>
>     dep0
>     dep1
>
>   : # requirements
>   : # default-build
>   : # usage-requirements
>     <include>src
>   ;
>
> for h in $(headers)
> {
>   compile-header $(h) : <dependency>libx ;
> }
> ```
>
_______________________________________________
Unsubscribe & other changes: https://lists.boost.org/mailman/listinfo.cgi/boost-build
Reply | Threaded
Open this post in threaded view
|

Re: Compiling header files independently as a test.

Boost - Build mailing list
Hi Steven,

On 3/17/18 7:58 PM, Steven Watanabe via Boost-build wrote:

> AMDG
>
> On 03/16/2018 02:13 PM, Thomas Brown via Boost-build wrote:
>>
>> 1. I implement the tests in the library's Jamfile since that is where
>> the actual header file names are known.  I actually put all required
>> files (headers, sources, etc.) in the sources list of the alias or
>> lib.  Is that something others are doing?  This forces me to put the
>> source file names and header file names into separate variables so I
>> can iterate over them.  I also do this in order to specify the sources
>> for a doxygen target within Boost.Build.
>>
>
>    I've never seen that before.  Listing the headers
> as sources for a library will just cause them to
> be ignored.

That is true.  However, a missing header file in a lib target will
trigger an error from Boost.Build, which I find useful to let me know
when I miss something.

I use it like this in order to get an error if a public header does not
exist.  The install target is usually not run during development, so the
error source happen later in the development process if that were the
only place it were checked.

# untested
import package ;

local x_headers = x.hpp ;
local x_sources = x.cpp ;
lib x : $(x_headers) $(x_sources) ;

explicit install ;
package.install install
   : # requirements
   : # binaries
   : # libraries
     x
   : # headers
     $(x_headers)
   ;

>> 2. I'd really prefer to iterate through the sources of any target (at
>> least any alias or lib) to run the compile-header tests in the test
>> Jamfile without knowing what they are named.  This would actually make
>> it possible to have a separate Jamfile to generate doxygen as well.
>>

> Iterating over the sources of a target is a bad idea. > Instead, use an alias and handle the processing at the
> virtual target layer (via generate, a generator, or
> a custom target class).  In particular, for checking
> headers, you can use a composing generator to handle
> all of the following:
> - compile each header in a separate translation unit.
> - include all headers in a single translation unit (useful
>    for catching duplicate #include guards)
> - link two translation units that #include all headers.  (catches
>    functions that should be inlined)

Thanks, I will give this a try.  I'm not sure I fully understand how I
would do this yet, but I'll see if I can figure it out.  I've written a
lot of Boost.Build, but still am not comfortable just whipping up a
custom target class.  Maybe this will be the project where I finally
understand it :)

I think I can satisfy my requirement of having the library definition
and tests defined separately by making the compile-headers target be
explicit in the library's Jamfile and request it from the test Jamfile.

>> 3. The "compile-header" target might require more information if the
>> library has dependencies, which have to be specified in both the
>> library and the compile-header targets.  This seems to usually be
>> handled by depending on the library itself in the compile-header
>> target, so maybe it's not a big deal, but it is a bit strange to say
>> that compiling the header that is part of libx depends on libx.
>>
>
> <use>libx will pick up the usage-requirements, but otherwise
> ignore libx, which I think is the behavior you want.

I'll give that a try as well.  I vaguely having subtle issues with <use>
in the past, but I think they were fixed several years ago.

>
>> I've included inline some code that reflects what I am currently
>> doing.  It is extremely preliminary.  I could see, at the minimum,
>> adding a "compile-header" and maybe "compile-header-fail" (not
>> implemented here)
>
> I don't think that compile-header-fail is useful.

I agree.  I wish I hadn't mentioned it :)

I'll post my attempt at an actual target to do this in the next few days
for critique.

Thanks!
Tom


_______________________________________________
Unsubscribe & other changes: https://lists.boost.org/mailman/listinfo.cgi/boost-build