wiki:Guidelines/MaintenanceGuidelines

Version 11 (modified by viboes, 14 years ago) ( diff )

Separate sections for the three audiences

  1. Motivation
  2. Introduction
    1. User code breaking cases
    2. Versioning individual Boost libraries
    3. Deprecating features
    4. Cross version testing
  3. Developer Guidelines
    1. Announce new library features
    2. Make feature request for each feature
    3. Document
      1. Tag Boost library with specific library version
      2. Include the tracked tickets on the Release notes
      3. List the test cases associated to the trac system tickets
      4. List the dependency upon other Boost library
      5. Document behavior differences between release and debug variants
      6. Document behavior differences between toolsets
    4. Coding
      1. Be careful with the use of using namespace header files
      2. Don't overload functions that are used by the TR1
      3. Avoid include-all-features files at the /boost level
      4. Don't refine functions overloading without ensuring the same behavior
      5. Avoid the inclusion of symbols at the boost or boost::detail namespace
      6. Avoid different external behavior depending on the variant release or …
      7. Avoid to change interfaces
      8. Don't delete files prematurely
      9. Don't delete namespaces prematurely
      10. Don't delete classes/functions/variables prematurely
      11. Don't modify functions prototypes prematurely
      12. Remove the deprecated features on a given release
    5. Test
      1. Test headers
      2. Don't forget to test
        1. The implicitly generated member functions
        2. The removed default member functions when you declare a constructor
        3. The deleted (private) default member functions
        4. The explicit constructors
        5. The implicit constructors or conversions
        6. The const-ness of variables, function parameters and function return …
      3. Separate the functional test from the unit test (implementation test)
      4. Preserve the functional test from the preceding versions
  4. User guidelines
    1. Don't use using directives
    2. Avoid the use of include all features files at the /boost level
    3. Help tracking regression test on the Trac system tickets
  5. Boosters guidelines
    1. Inspect the code
    2. Check that every modification has been documented
    3. Check that every modification has been tested

MAINTENANCE GUIDELINES

WARNING: The contents of this page is not the result of a consensus on the Boost community.

Please be free to improve this page directly or post on the Boost mailing list boost-AT-lists.boost.org and boost-users-AT-lists.boost.org.


Motivation

To be added


Introduction

Nobody wants to completely disallow changes, we need them.

Everybody wants to avoid breaking changes, but sometimes these are also necessary.

This page describes guidelines that could help the Boost community to get stable libraries and as consequence to avoid user code breaking when the user upgrades to a more recent Boost release.

The difference between breaking changes and bugs concerns documentation. A documented modification breaking user code cannot be considered as a bug while the same undocumented modification could be a bug.


User code breaking cases

But why can user code break when the user upgrades to a more recent Boost release. While this do not pretends to be exhaustive, some examples are given.

  • Syntactical breaking: detected at compile time

It is evident that the removal of files, namespaces, classes, function, variables or macros could break user code. What it is less evident is that the addition of namespaces, classes, functions, variables at the namespace level or macros can also break user code. Note that modifications can be considered as deletion+addition.

The following code example

#include <boost/file.hpp>
using namespace boost;

class foo {
  // ...
};

void bar() {
  // ...
}

breaks when

  • we add the namespace foo in file.hpp
    // boost/file.hpp
    namespace boost {
      namespace foo {
        // ...
      }
    }
    
  • we add the class foo in file.hpp
    // boost/file.hpp
    namespace boost {
      class foo {
        // ...
      };
    }
    
  • we add the function bar in file.hpp
    // boost/file.hpp
    namespace boost {
      void bar() {
        // ...
      }
    }
    

Adding macros We shouldn't have user code breaking if we preserve the macro naming rule BOOST_LIBNAME_NAME.

  • Semantical breaking: detected at runtime

Some times the user code will compile with the upgraded Boost release, but the code will not behave as before. The kind of changes that could lead to this situation must be over documented because the compiler can not help the user to catch the breaking code.

The following code example

#include <boost/file.hpp>
using namespace boost;


void bar(int i) {
  // ...
}

breaks when

  • we add the function bar in one of the files included by the user program
    // boost/file.hpp
    namespace boost {
      void bar(char c) {
        // ...
      }
    }
    

These guidelines are not only addressed to the Boost library developers, but also to the users and the release manager team (RM). Every member of the Boost community has his role to play. Note that the author plays also the role of user of all libraries on which his library depends and on his library depends and on its own library when writing the library tests.

The key issue to get stable libraries are:

  • Versioning individual Boost.Libraries.
  • Using a deprecation period to give users notice that a breaking change is coming.
  • Documenting and tracking the changes.
  • Avoiding completely silent breakage by a deep inspection of code changes and running the regression tests (functional tests) from the _previous_ major release against the current release as a way to automatically detect and flag interface/behavioral changes.

These guidelines are related mainly to how to document changes, how breaking changes can be detected either by test or by inspections, and of course guidelines related to the code itself.


Versioning individual Boost libraries

This page don't contains any advice related to individual Boost libraries packaging. There is already the CMake project working on that. But tagging individual Boost libraries with a specific version MAJOR.MINOR.PATCH has already the informational advantage.

Versions are denoted using a standard triplet of integers: MAJOR.MINOR.PATCH. The basic intent is that MAJOR versions are incompatible, large-scale upgrades of the API. MINOR versions retain source and binary compatibility with older minor versions, and changes in the PATCH level are perfectly compatible, forwards and backwards.


Deprecating features

C++ don't have deprecated attributes, so the developer needs to emulate a warning when some deprecated feature is used. The #warning directive can be used to this purpose.

  #warning "name is deprecated, please use new_name instead"

The main problem is that this warning will appear independently of whether the user code uses or not the deprecated feature. In order to palliate to this the inclusion of the deprecated feature could be accessible conditionally and only when included the warning will be reported or not.

For compatibility purposes deprecated features should be included by default. So the library behaves as if the features were not been deprecated. If a user don't want to include a deprecated feature he/she can define one of the following macros:

  • BOOST_DONT_INCLUDE_DEPRECATED: don't include any deprecated feature.
  • BOOST_DONT_INCLUDE_DEPRECATED_ON_X_Y: don't include any deprecated feature to be removed on version X.Y.
  • BOOST_LIBX_DONT_INCLUDE_DEPRECATED: don't include any deprecated feature for library Boost.LIBX.
  • BOOST_LIBX_DONT_INCLUDE_DEPRECATED_ON_X_Y: don't include any deprecated feature for libarary Boost.LIBX to be removed on version X.Y.
  • BOOST_LIBX_DONT_INCLUDE_DEPRECATED_NAME: don't include the deprecated feature name.

Once one of this macros is defined the developer can define another macro to make easier the work.

  • BOOST_LIBX_NAME_DECLARED: states that the deprecated feature name has been declared

Deprecated warnings are not reported by default, so the library behaves as if the features were not been deprecated. If the user wants this deprecated warnings to appear he/she can define one of the macros:

  • BOOST_WARNS_DEPRECATED: warms on all deprecated features
  • BOOST_WARNS_DEPRECATED_ON_X_Y: warms on all deprecated features to be removed on version X.Y

See the examples below.


Cross version testing

Running the regression tests from the _previous_ major release against the current release is a way to automatically detect and flag interface/behavioral changes. Sure, there are many potential problems with such an approach:

  • Some library tests are likely to also exercise code at the implementation/detail level.
  • Already limited testing resources (on the other hand, interface changes are likely to be detected by running such tests for a single, reasonably compliant, compiler).
  • ...

Another approach consist in preserving the test of the older releases and run them on the current release. If the developer needs to change the tests while she/he evolves his library this is a symptom the interface has been changed and the same way the tests are broken, the user code can also be broken. If the developer forbids himself to modify the test, he will be able to identify breaking changes early on.


Developer Guidelines


Announce new library features

It is a good idea the developer announce to the Boost mailing lists the new features just before committing in the release branch. This will let the time to the Boost community to inspect the modifications introduced.


Make feature request for each feature

Each modification deprecated/suppressed/modified/new feature should be tracked with a ticket on the Trac system.


Document


Tag Boost library with specific library version

Tagging each library release with a version, is the more visible way to signal possible incompatibilities to the user.

For example the thread library could have:

  • 2.1.0 Changes since boost 1.35
  • 2.0.0 Changes since boost 1.34
  • 1.0.0 Initial release on 1.25

Include the tracked tickets on the Release notes

The release notes should include

  • the list of all the bug tickets that have been solved
  • the list of all deprecated/suppressed/modified/new features with their associated tickets.

For example the thread library could have (#XXXX should be replaced by the trac tickets):

  • 2.1.0 Changes since boost 1.35
    • #XXXX: New generic lock() and try_lock() functions for locking multiple mutexes at once.
    • #XXXX: Rvalue reference support for move semantics where the compilers supports it.
    • A few bugs fixed and missing functions added (including the serious win32 condition variable bug).
      • #XXXX: bug1
      • ...
      • #XXXX: bugn
    • #XXXX: scoped_try_lock types are now backwards-compatible with Boost 1.34.0 and previous releases.
    • #XXXX: Support for passing function arguments to the thread function by supplying additional arguments to the boost::thread constructor.
    • #XXXX: Backwards-compatibility overloads added for timed_lock and timed_wait functions to allow use of xtime for timeouts.

List the test cases associated to the trac system tickets

Each feature request or bug should have associated at least a test case. A table showing this association will help to check that each feature,bug is has the expected test coverage.

TicketSynopsisTest Case
#XXXXfoo foo foo bar bar bar test_foo_bar

List the dependency upon other Boost library

Each library lists the other libraries it depends upon and the minimum version # - as it does with compilers now

LibraryVersion #Build & LinkComment
Thread2.1.0linkgeneric lock()

Document behavior differences between release and debug variants

Document the differences in behavior that depends on the user defines, in particular the release and debug variants.


Document behavior differences between toolsets

Document the differences in behavior that depends on the toolsets.

Coding


Be careful with the use of using namespace header files

To import symbols from another namespace the developer can use the using directive using namespace but limited to the function level.


Don't overload functions that are used by the TR1

Overloading imported Boost.TR1 functions is equivalent to overload the std functions.


Avoid include-all-features files at the /boost level

Avoid include-all-file at the /boost level including all the functionalities of the library. Provide instead specific files at boost/lib/file.hpp.

If you provide such files ensure that your tests works when you include them directly or include the specific ones.


Don't refine functions overloading without ensuring the same behavior

If you need document them and warm the user.


Avoid the inclusion of symbols at the boost or boost::detail namespace

These symbols can collide with symbols of other libraries.

Announce them on the Boost mailing list when you think this is the best choice.


Avoid different external behavior depending on the variant release or debug without documenting it

Providing different external behavior depending on the variant release or debug could be a surprise for the user.

If you need them document it and warm the user.


Avoid to change interfaces

When you change the interface, you can see the changes that are needed at the Boost level, but you can not evaluate the cost induced by this change for the users. So before to change an interface deprecate it and let the user enough time to migrate to the new one.


Don't delete files prematurely

Before deleting a file, "file.hpp", deprecate it and let the user modify his code during some versions (e.g. until 1_40). Replace it by

// boost/old_header_file.hpp
// include whatever file is needed to preserve the old file contents
#if defined(BOOST_WARNS_DEPRECATED) || defined(BOOST_WARNS_DEPRECATED_ON_1_40)
#warning "boost/old_header_file.hpp is deprecated, please include boost/new_header_file.hpp instead
#endif

It will be up to the user to define the macros BOOST_WARNS_DEPRECATED and BOOST_WARNS_DEPRECATED_ON_1_40 when the user wants to be warmed for deprecated features on Boost or on the Boost version 1.40 respectively.


Don't delete namespaces prematurely

Use the using sentence to import from the new namespace to the old one.


Don't delete classes/functions/variables prematurely

Instead of deleting a class or a public/protected function/variable on the next version protect its declaration by any of the DONT_INCLUDE_DEPRECATED macros.

// boost/libx/header_file.hpp
namespace boost {
namespace libx {

#if defined(BOOST_DONT_INCLUDE_DEPRECATED) || defined(BOOST_DONT_INCLUDE_DEPRECATED_ON_1_41) \
 || defined(BOOST_LIBX_DONT_INCLUDE_DEPRECATED)  || defined(BOOST_LIBX_DONT_INCLUDE_DEPRECATED_ON_1_41) \
 || defined(BOOST_LIBX_DONT_INCLUDE_DEPRECATED_NAME)
#else
  #define BOOST_LIBX_NAME_DECLARED
  #if defined(BOOST_WARNS_DEPRECATED) || defined(BOOST_WARNS_DEPRECATED_ON_1_40)
    #warning "name is deprecated, please use new_name instead"
  #endif

  class name {
    name::name();
    // as before
  };
#endif

}
}

When the declaration should been included, the developer could define a BOOST_LIBX_NAME_DECLARED, which could be used on the class/function/variable definition. E.g.

// boost/libx/header_file.cpp
namespace boost {
namespace libx {

#if defined(BOOST_LIBX_NAME_DECLARED)
  name::name() {
    // as before
  }
  // as before
#endif

}
}

Don't modify functions prototypes prematurely

Modifying an existing function prototype, if you were deleting it and adding the new one. Instead add the new when possible.

As both prototypes should coexist for some releases, check if this overloading could cause user code breaks.


Remove the deprecated features on a given release

Once the deprecated period is expired the developer sould remove them.

Don't forget to change the major version number.


Test

Test headers

Steve Watanabe has written a Jamfile that make easier this task. See /boost/libs/units/test/test_header for an example.

Include each header files twice The inclusion of a header file twice ensure that the file is self contained, and that it is well protected

    #include <file.hpp>
    #include <file.hpp>
    int main() {return 0;}

Include all header files in both orders One more test could be useful, include all the Boost Headers files on both orders

    #include <file1.hpp>
    #include <file2.hpp>
    ...
    #include <filen.hpp>
    #include <filen.hpp>
    ...
    #include <file2.hpp>
    #include <file1.hpp>

Link all the header files twice This ought to catch non-inlined functions and other duplicate definitions


Don't forget to test


The implicitly generated member functions

The implicitly generated member functions are part of your interface.

Add some simple tests that prove its correct behavior.


The removed default member functions when you declare a constructor

When you declare a constructor the other default constructors are not generated, so


The deleted (private) default member functions

The default member functions =deleted (or declared private) are not part of the interface.

Add some simple tests that prove the compilation fails when you use them.


The explicit constructors


The implicit constructors or conversions


The const-ness of variables, function parameters and function return types and functions

When a variable, function parameter or function return type is non-const, test that you can modify them.

When a variable, function parameter or function return type is const, test that you try to modify them the compilation fails.


Separate the functional test from the unit test (implementation test)

Functional test should take in account only the documented behavior. They can be used as regression test for new re-factorings of the implementation.

Implementation test can be used to test details in the implementation but not as regression tests.


Preserve the functional test from the preceding versions

While doing modifications to the library the developer can find some regressions on some tests of the preceding versions. It seems natural to change them to make the test succeed. This is not a good idea. The failing test is a symptom that the new modifications could break user code.

If the modification don't pretended to be an interface break, the developer should modify the library until the preceding test cases pass.

If the break is intentional move the tests to the failing tests compile_fail or run_fail on the Jamfile. The developer can copy the test and adapt it to the new interface. It could be a good idea to state explicitly on the test file name on which version they were included.

In order to avoid erroneous modification of the the released tests files the developer could change the rights of the test files. If the developer has no permission to do that on the SVN repository as a last resort he could change the permission in its own SVN copy.


User guidelines


Don't use using directives

As seen before, using directives in user code is one of the sources of user code breaking, so it will be safer to use namespace synonyms instead and prefix each symbol with them.

Instead of doing

   using namespace boost::interprocess;

   shared_memory_object::remove("MySharedMemory");

do

    namespace bip = boost::interprocess;

    bip::shared_memory_object::remove("MySharedMemory");

Avoid the use of include all features files at the /boost level

Avoid the use of include all features files at the /boost level use instead specific files at boost/lib/file.hpp. Including less code reduce your user code breaking chances.


Help tracking regression test on the Trac system tickets

A bug ticket should be associated to one or several test cases, either the test cases exists already and we are in face of a regression for some toolset, or a new toolset is considered, or a new test case is needed to reproduce the bug and the modification solve the problem. State clearly which test cases reproduce the bug it they exists and propose a new test case reproducing the bug otherwise.


Boosters guidelines


Inspect the code

Inspect the code to check that the new modifications don't include user code breaks. Tools comparing the contents of the current release and the preceding one could help. Discuss the issue on the ML and do a ticket on the Trac system if there is something to improve.


Check that every modification has been documented

Inspect that every modification has been reported on the documentation. Tools comparing the contents of the current release and the preceding one could help. Discuss the issue on the ML and do a ticket on the Trac system if there is something to improve.


Check that every modification has been tested

Inspect that every modification has been tested. Tools comparing the contents of the current release and the preceding one could help. Discuss the issue on the ML and do a ticket on the Trac system if there is something to improve.

Note: See TracWiki for help on using the wiki.