===PROBLEM===
During the lifetime of a library supporting a given stable ABI, some parts of its API can become deprecated in favour of newer methods.
Compilers support tagging of such API as outdated by warning when such deprecated methods are used in own code. So developers are aware there is newer API they shoud see to use to improve their product or to prepare for the removal of that API.
Now developers would like to
* enforce avoiding of deprecated API
* not see deprecated warnings for a range of versions they support
* build variants of the library with the code for deprecated API not included in the build
===WANTED===
Required:
* option to disable visibility for API consumers of deprecated API up to a certain version
* option to disable deprecation warnings above a certain version
* support for flags to control a group of librariess with default settings, while still allowing fine-tuning per library
Optional:
* allows to disable the generation of code for the deprecated API, for smaller libs for dedicated systems/bundles
* API documentation generation matches the API part of the build
* similar to Qt concepts, so knowledge and usage pattern can be transferred
===PROPOSED SOLUTION===
With libraries of the KDE Frameworks there is no global header file which all files include and which could be used to globally configure C++ preprocessor macros. Though there is usually one file per library included by all headers which declare public API, the export header file, which defines the macros for the symbol visibility, including the deprecation tags. Extending this file to add C++ preprocessor logic for our needs allows us to inject things at the right place, without needing to add additional files or include more headers.
Such a file would be generated via a CMake macro (prototype WIP currently).
It would give API consumers these generated variables to control visibility:
* <FOO>_NO_DEPRECATED (flag)
* <FOO>_NO_DEPRECATED_WARNINGS (flag)
* <FOO>_DISABLE_DEPRECATED_BEFORE=hexvalue
* <FOO>_DEPRECATED_WARNINGS_SINCE=hexvalue
And give library developer these generated variables and functions for use in the API declaration & sources:
* <FOO>_ENABLE_DEPRECATED_SINCE(major, minor)
* <FOO>_DEPRECATED_VERSION(major, minor)
* <FOO>_EXPORT
* <FOO>_NO_EXPORT
* <FOO>_DEPRECATED
* <FOO>_DEPRECATED_EXPORT
* <FOO>_DEPRECATED_NO_EXPORT
A generated header would look like this (here an early prototype with code added to normal output of cmake's generate_export_header):
```lang=cpp, name=kparts_export.h
#ifndef KPARTS_EXPORT_H
#define KPARTS_EXPORT_H
#ifdef KPARTS_STATIC_DEFINE
# define KPARTS_EXPORT
# define KPARTS_NO_EXPORT
#else
# ifndef KPARTS_EXPORT
# ifdef KF5Parts_EXPORTS
/* We are building this library */
# define KPARTS_EXPORT __attribute__((visibility("default")))
# else
/* We are using this library */
# define KPARTS_EXPORT __attribute__((visibility("default")))
# endif
# endif
# ifndef KPARTS_NO_EXPORT
# define KPARTS_NO_EXPORT __attribute__((visibility("hidden")))
# endif
#endif
#ifndef KPARTS_DECL_DEPRECATED
# define KPARTS_DECL_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef KPARTS_DECL_DEPRECATED_EXPORT
# define KPARTS_DECL_DEPRECATED_EXPORT KPARTS_EXPORT KPARTS_DECL_DEPRECATED
#endif
#ifndef KPARTS_DECL_DEPRECATED_NO_EXPORT
# define KPARTS_DECL_DEPRECATED_NO_EXPORT KPARTS_NO_EXPORT KPARTS_DECL_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef KPARTS_NO_DEPRECATED
# define KPARTS_NO_DEPRECATED
# endif
#endif
#endif /* KPARTS_EXPORT_H */
// DO NOT EDIT! Generated from ecm_generate_export_header()
#ifndef ECM_GENERATEEXPORTHEADER_KPARTS_EXPORT_H
#define ECM_GENERATEEXPORTHEADER_KPARTS_EXPORT_H
#ifndef ECM_GENERATEEXPORTHEADER_VERSION_VALUE
# define ECM_GENERATEEXPORTHEADER_VERSION_VALUE(major, minor, patch) ((major<<16)|(minor<<8)|(patch))
#endif
// Take any defaults from group settings
#if !defined(KPARTS_NO_DEPRECATED) && defined(KF_NO_DEPRECATED)
# define KPARTS_NO_DEPRECATED
#endif
#if !defined(KPARTS_NO_DEPRECATED_WARNINGS) && defined(KF_NO_DEPRECATED_WARNINGS)
# define KPARTS_NO_DEPRECATED_WARNINGS
#endif
#if !defined(KPARTS_DISABLE_DEPRECATED_BEFORE) && defined(KF_DISABLE_DEPRECATED_BEFORE)
# define KPARTS_DISABLE_DEPRECATED_BEFORE KF_DISABLE_DEPRECATED_BEFORE
#endif
#if !defined(KPARTS_DEPRECATED_WARNINGS_SINCE) && defined(KF_DEPRECATED_WARNINGS_SINCE)
# define KPARTS_DEPRECATED_WARNINGS_SINCE KF_DEPRECATED_WARNINGS_SINCE
#endif
#if defined(KPARTS_NO_DEPRECATED)
# undef KPARTS_DEPRECATED
# undef KPARTS_DEPRECATED_EXPORT
# undef KPARTS_DEPRECATED_NO_EXPORT
#elif defined(KPARTS_NO_DEPRECATED_WARNINGS)
# define KPARTS_DEPRECATED
# define KPARTS_DEPRECATED_EXPORT KPARTS_EXPORT
# define KPARTS_DEPRECATED_NO_EXPORT KPARTS_NO_EXPORT
#else
# define KPARTS_DEPRECATED KPARTS_DECL_DEPRECATED
# define KPARTS_DEPRECATED_EXPORT KPARTS_DECL_DEPRECATED_EXPORT
# define KPARTS_DEPRECATED_NO_EXPORT KPARTS_DECL_DEPRECATED_NO_EXPORT
#endif
#ifndef KPARTS_DEPRECATED_WARNINGS_SINCE
# ifdef KPARTS_DISABLE_DEPRECATED_BEFORE
# define KPARTS_DEPRECATED_WARNINGS_SINCE KPARTS_DISABLE_DEPRECATED_BEFORE
# else
# define KPARTS_DEPRECATED_WARNINGS_SINCE ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 62, 0)
# endif
#endif
#ifndef KPARTS_DISABLE_DEPRECATED_BEFORE
# define KPARTS_DISABLE_DEPRECATED_BEFORE ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 0, 0)
#endif
#ifdef KPARTS_DEPRECATED
# define KPARTS_ENABLE_DEPRECATED_SINCE(major, minor) (ECM_GENERATEEXPORTHEADER_VERSION_VALUE(major, minor, 0) >= KPARTS_DISABLE_DEPRECATED_BEFORE)
#else
# define KPARTS_ENABLE_DEPRECATED_SINCE(major, minor) 0
#endif
#if KPARTS_DEPRECATED_WARNINGS_SINCE >= ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 0, 0)
# define KPARTS_DEPRECATED_VERSION_5_0 KPARTS_DEPRECATED
#else
# define KPARTS_DEPRECATED_VERSION_5_0
#endif
#define KPARTS_DEPRECATED_VERSION_5(minor) KPARTS_DEPRECATED_VERSION_5_##minor
#define KPARTS_DEPRECATED_VERSION(major, minor) KPARTS_DEPRECATED_VERSION_##major(minor)
#endif
```
It would be used in KParts code like this:
```lang=cpp
#if KPARTS_ENABLE_DEPRECATED_SINCE(5, 0)
/**
* <snip>
*/
KPARTS_DEPRECATED_VERSION(5, 0)
static AskSaveResult askEmbedOrSave(const QUrl &url, const QString &mimeType, const QString &suggestedFileName = QString(), int flags = 0);
#endif
```
And projects linking against KParts could simply add these definitions to control which API is visible:
```lang=cmake
add_definitions(
-DKF_DISABLE_DEPRECATED_BEFORE=0x052400
-DKPARTS_DISABLE_DEPRECATED_BEFORE=0x050000 # if we want lower req here, we can :)
)
```
The prototype cmake macro might be used like this (current API draft):
```lang=cmake, name=CMakeLists.txt
include(ECMGenerateExportHeader)
# option to allow library builder to cut away deprecated code, using -DEXCLUDE_DEPRECATED_BEFORE=[0/x,y,z/CURRENT]
set(EXCLUDE_DEPRECATED_BEFORE 0 CACHE STRING "Control how much of deprecated API is build [default=0(everything)].")
ecm_setup_export_header_version(${EXCLUDE_DEPRECATED_BEFORE}
CURRENT_VERSION ${KParts_VERSION}
STRING_VAR KParts_EXCLUDE_DEPRECATED_BEFORE
HEXNUMBER_VAR KParts_EXCLUDE_DEPRECATED_BEFORE_HEXNUMBER
)
add_definitions(
# limit which API of dependant KF libs we use in KParts
-DKF_DISABLE_DEPRECATED_BEFORE=0x050200
# control which API we see ourselves when building ourselves
-DKPARTS_DISABLE_DEPRECATED_BEFORE=${KParts_EXCLUDE_DEPRECATED_BEFORE_HEXNUMBER}
)
```
```lang=cmake, name=src/CMakeLists.txt
ecm_generate_export_header(KF5Parts
EXPORT_FILE_NAME ${KParts_BINARY_DIR}/kparts/kparts_export.h
BASE_NAME KParts
GROUP_BASE_NAME KF
VERSION ${KF5_VERSION}
DEPRECATION_VERSIONS 5.0
EXCLUDE_DEPRECATED_BEFORE ${KParts_EXCLUDE_DEPRECATED_BEFORE}Such a file would be generated via a CMake macro.
It would give API consumers these generated variables to control visibility:
* <FOO>_DISABLE_DEPRECATED_BEFORE_AND_AT=hexvalue
* <FOO>_DEPRECATED_WARNINGS_SINCE=hexvalue
* <FOO>_NO_DEPRECATED (flag, short for:<FOO>_DISABLE_DEPRECATED_BEFORE_AND_AT=latest )
* <FOO>_NO_DEPRECATED_WARNINGS (flag, short for: <FOO>_DEPRECATED_WARNINGS_SINCE=0)
And give library developer these generated variables and functions for use in the API declaration & sources:
* <FOO>_ENABLE_DEPRECATED_SINCE(major, minor)
* <FOO>_BUILD_DEPRECATED_SINCE(major, minor) (for library-build time exclude checks)
* <FOO>_DEPRECATED_VERSION(major, minor, text)
* <FOO>_EXPORT
* <FOO>_NO_EXPORT
* <FOO>_DEPRECATED
* <FOO>_DEPRECATED_EXPORT
* <FOO>_DEPRECATED_NO_EXPORT
A generated header would look like this (here an early prototype with code added to normal output of cmake's generate_export_header):
```lang=cpp, name=kparts_export.h
#ifndef KPARTS_EXPORT_H
#define KPARTS_EXPORT_H
#ifdef KPARTS_STATIC_DEFINE
# define KPARTS_EXPORT
# define KPARTS_NO_EXPORT
#else
# ifndef KPARTS_EXPORT
# ifdef KF5Parts_EXPORTS
/* We are building this library */
# define KPARTS_EXPORT __attribute__((visibility("default")))
# else
/* We are using this library */
# define KPARTS_EXPORT __attribute__((visibility("default")))
# endif
# endif
# ifndef KPARTS_NO_EXPORT
# define KPARTS_NO_EXPORT __attribute__((visibility("hidden")))
# endif
#endif
#ifndef KPARTS_DECL_DEPRECATED
# define KPARTS_DECL_DEPRECATED __attribute__ ((__deprecated__))
#endif
#ifndef KPARTS_DECL_DEPRECATED_EXPORT
# define KPARTS_DECL_DEPRECATED_EXPORT KPARTS_EXPORT KPARTS_DECL_DEPRECATED
#endif
#ifndef KPARTS_DECL_DEPRECATED_NO_EXPORT
# define KPARTS_DECL_DEPRECATED_NO_EXPORT KPARTS_NO_EXPORT KPARTS_DECL_DEPRECATED
#endif
#if 0 /* DEFINE_NO_DEPRECATED */
# ifndef KPARTS_NO_DEPRECATED
# define KPARTS_NO_DEPRECATED
# endif
#endif
#if defined(__GNUC__)
# if (__GNUC__ * 100 + __GNUC_MINOR__) >= 405
# define KPARTS_DECL_DEPRECATED_TEXT(text) __attribute__ ((__deprecated__(text)))
# endif
#endif
#ifndef KPARTS_DECL_DEPRECATED_TEXT
# define KPARTS_DECL_DEPRECATED_TEXT(text) KPARTS_DECL_DEPRECATED
#endif
#define ECM_GENERATEEXPORTHEADER_VERSION_VALUE(major, minor, patch) ((major<<16)|(minor<<8)|(patch))
// Take any defaults from group settings
#if !defined(KPARTS_NO_DEPRECATED) && !defined(KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT)
# ifdef KF_NO_DEPRECATED
# define KPARTS_NO_DEPRECATED
# elif defined(KF_DISABLE_DEPRECATED_BEFORE_AND_AT)
# define KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT KF_DISABLE_DEPRECATED_BEFORE_AND_AT
# endif
#endif
#if !defined(KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT) && defined(KF_DISABLE_DEPRECATED_BEFORE_AND_AT)
# define KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT KF_DISABLE_DEPRECATED_BEFORE_AND_AT
#endif
#if !defined(KPARTS_DEPRECATED_WARNINGS_SINCE) && defined(KF_DEPRECATED_WARNINGS_SINCE)
# define KPARTS_DEPRECATED_WARNINGS_SINCE KF_DEPRECATED_WARNINGS_SINCE
#endif
#if defined(KPARTS_NO_DEPRECATED)
# undef KPARTS_DEPRECATED
# define KPARTS_DEPRECATED_EXPORT KPARTS_EXPORT
# define KPARTS_DEPRECATED_NO_EXPORT KPARTS_NO_EXPORT
#elif defined(KPARTS_NO_DEPRECATED_WARNINGS)
# define KPARTS_DEPRECATED
# define KPARTS_DEPRECATED_EXPORT KPARTS_EXPORT
# define KPARTS_DEPRECATED_NO_EXPORT KPARTS_NO_EXPORT
#else
# define KPARTS_DEPRECATED KPARTS_DECL_DEPRECATED
# define KPARTS_DEPRECATED_EXPORT KPARTS_DECL_DEPRECATED_EXPORT
# define KPARTS_DEPRECATED_NO_EXPORT KPARTS_DECL_DEPRECATED_NO_EXPORT
#endif
// No deprecated API had been removed from build
#define KPARTS_EXCLUDE_DEPRECATED_BEFORE_AND_AT 0
#define KPARTS_BUILD_DEPRECATED_SINCE(major, minor) 1
#ifdef KPARTS_NO_DEPRECATED
# define KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 63, 0)
#endif
#ifndef KPARTS_DEPRECATED_WARNINGS_SINCE
# ifdef KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT
# define KPARTS_DEPRECATED_WARNINGS_SINCE KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT
# else
# define KPARTS_DEPRECATED_WARNINGS_SINCE ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 63, 0)
# endif
#endif
#ifndef KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT
# define KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 0, 0)
#endif
#ifdef KPARTS_DEPRECATED
# define KPARTS_ENABLE_DEPRECATED_SINCE(major, minor) (ECM_GENERATEEXPORTHEADER_VERSION_VALUE(major, minor, 0) > KPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT)
#else
# define KPARTS_ENABLE_DEPRECATED_SINCE(major, minor) 0
#endif
#if KPARTS_DEPRECATED_WARNINGS_SINCE >= ECM_GENERATEEXPORTHEADER_VERSION_VALUE(5, 0, 0)
# define KPARTS_DEPRECATED_VERSION_5_0(text) KPARTS_DECL_DEPRECATED_TEXT(text)
#else
# define KPARTS_DEPRECATED_VERSION_5_0(text)
#endif
#define KPARTS_DEPRECATED_VERSION_5(minor, text) KPARTS_DEPRECATED_VERSION_5_##minor(text)
#define KPARTS_DEPRECATED_VERSION(major, minor, text) KPARTS_DEPRECATED_VERSION_##major(minor, "Since "#major"."#minor". "#text)
#endif /* KPARTS_EXPORT_H */
```
It would be used in KParts code like this:
```lang=cpp
#if KPARTS_ENABLE_DEPRECATED_SINCE(5, 0)
/**
* <snip>
* @deprecated Since 5.0. Use KParts::BrowserOpenOrSaveQuestion
*/
KPARTS_DEPRECATED_VERSION(5, 0, "Use KParts::BrowserOpenOrSaveQuestion")
static AskSaveResult askEmbedOrSave(const QUrl &url, const QString &mimeType, const QString &suggestedFileName = QString(), int flags = 0);
#endif
```
And projects linking against KParts could simply add these definitions to control which API is visible:
```lang=cmake
add_definitions(
-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x052400
-DKPARTS_DISABLE_DEPRECATED_BEFORE_AND_AT=0x050000 # if we want lower req here, we can :)
)
```
The prototype cmake macro might be used like this (current API draft):
```lang=cmake, name=CMakeLists.txt
include(ECMGenerateExportHeader)
# option to allow library builder to cut away deprecated code, using -DEXCLUDE_DEPRECATED_BEFORE=[0/x,y,z/CURRENT]
set(EXCLUDE_DEPRECATED_BEFORE_AND_AT 0 CACHE STRING "Control how much of deprecated API is built [default=0(everything)].")
add_definitions(
# limit which API of dependant KF libs we use in KParts
-DKF_DISABLE_DEPRECATED_BEFORE_AND_AT=0x050200
)
```
```lang=cmake, name=src/CMakeLists.txt
ecm_generate_export_header(KF5Parts
EXPORT_FILE_NAME ${KParts_BINARY_DIR}/kparts/kparts_export.h
BASE_NAME KParts
GROUP_BASE_NAME KF
VERSION ${KF5_VERSION}
DEPRECATION_VERSIONS 5.0
EXCLUDE_DEPRECATED_BEFORE_AND_AT ${EXCLUDE_DEPRECATED_BEFORE_AND_AT}
)
```
=== KF5 MODULE PATCHES ===
attica
baloo
bluez-qt
kactivities
kactivities-stats
karchive
kauth
kbookmarks
kcalendarcore
kcmutils
kcodecs
kcompletion
kconfig - kossebau
kconfigwidgets
kcontacts
kcoreaddons - D23800
kcrash
kdbusaddons
kdeclarative
kded
kdelibs4support
kdesu
kdewebkit
kdnssd
kdoctools
kemoticons
kfilemetadata
kglobalaccel
kguiaddons - kossebau
kholidays
khtml
ki18n
kiconthemes
kidletime
kimageformats
kinit
kio
kirigami
kitemmodels
kitemviews
kjobwidgets
kjs
kjsembed
kmediaplayer
knewstuff
knotifications
knotifyconfig
kpackage
kparts - kossebau
kpeople
kplotting
kpty
kross
krunner
kservice - D23802
ktexteditor
ktextwidgets
kunitconversion
kwallet
kwayland
kwidgetsaddons - kossebau
kwindowsystem - kossebau
kxmlgui - kossebau
kxmlrpcclient
modemmanager-qt
networkmanager-qt
plasma-framework
prison
purpose
qqc2-desktop-style
solid
sonnet
syndication
syntax-highlighting
threadweaver
===FUTURE TODO===
Decide best practices how to deal with deprecation warnings inside code of the library itself, including tests & example code.
Generate macros to silence warnings (e.g. for inline methods calling other deprecated methods).
Document best practices for adding compiler messages to the deprecation statements.
Document best practices for adding the macros to headers (e.g. how to wrap API documentation).
Document best practices how to document deprecation in the API dox, in cooperation with deprecation macros.