Control for visibility of symbols of deprecated API and compiler warnings
Open, Needs TriagePublic

Description

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.

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):

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:

#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:

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):

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
)
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

Please pick yours to port to ECMGenerateExportHeader: add your name to it when picking (" - myname"), then replace with the Dxxx number once review request is done

attica - D24650
baloo - D24617
bluez-qt - D24707
karchive - D24665
kbookmarks - e3f6058884e6100f75b1f73b4657002e2bd5a64a
kcalendarcore - D24682
kcodecs - D24599
kcompletion - D24495
kconfig - D24496
kconfigwidgets - D24497
kcoreaddons - D23800
kdeclarative - D24673
kdesu - 27bb97de6b6a556c1f1049b44dc5b4ff0dc2e1b1
kdnssd - 9e471892039e640884756f60327484e36defba4e
kemoticons - D24710
kfilemetadata - D24611
kglobalaccel - D24643
ki18n - D24700
kiconthemes - D24608
kio - D24684
kitemmodels - D24600
kitemviews - D24610
knewstuff - D24681
kpackage - D24671
kparts - D24467
kservice - D23802
ktextwidgets - D24595
kwayland - D24663
kwidgetsaddons - D24468
kwindowsystem - D24465
kxmlgui - D24466
plasma-framework - D24678
solid - 280911f7a4e3444a6bdfe10f2282e8339bdbb0df
threadweaver - D24651

Deprecated modules

Need checking how they should signal by the compiler deprecation in a sensible way

kdelibs4support
kdewebkit
kjs
kjsembed
kmediaplayer
kross

No deprecated API yet

kactivities
kactivities-stats
kauth
kcmutils
kcontacts
kcrash
kdbusaddons
kded
kdoctools
kguiaddons
kholidays
khtml
kidletime
kimageformats
kinit
kirigami - only qml
kjobwidgets
knotifications
knotifyconfig
kpeople
kplotting
kpty
krunner
ktexteditor
kunitconversion
kwallet
kxmlrpcclient
modemmanager-qt
networkmanager-qt - only NM API deprecations
prison
purpose
sonnet
syndication
syntax-highlighting

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.

Related Objects

kossebau created this task.Sep 2 2019, 9:39 PM
kossebau updated the task description. (Show Details)Sep 2 2019, 10:17 PM

Prototype code up as D23789

kossebau updated the task description. (Show Details)Sep 25 2019, 2:28 PM
kossebau updated the task description. (Show Details)Oct 5 2019, 4:13 PM
kossebau updated the task description. (Show Details)Oct 5 2019, 4:56 PM
kossebau updated the task description. (Show Details)Oct 7 2019, 1:30 PM
vkrause moved this task from Backlog to In Progress on the KF6 board.
kossebau updated the task description. (Show Details)Oct 8 2019, 1:30 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 1:50 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 2:16 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 3:33 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 3:53 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 3:57 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 5:28 PM
kossebau updated the task description. (Show Details)Oct 12 2019, 9:01 PM
kossebau updated the task description. (Show Details)Oct 13 2019, 1:58 PM
kossebau updated the task description. (Show Details)Oct 13 2019, 5:38 PM
kossebau updated the task description. (Show Details)Oct 14 2019, 9:26 PM
kossebau updated the task description. (Show Details)Oct 15 2019, 1:17 AM
kossebau updated the task description. (Show Details)Oct 15 2019, 2:07 PM
kossebau updated the task description. (Show Details)Oct 15 2019, 3:10 PM
kossebau updated the task description. (Show Details)Oct 15 2019, 10:08 PM
kossebau updated the task description. (Show Details)Oct 15 2019, 11:18 PM
kossebau updated the task description. (Show Details)Oct 16 2019, 3:55 PM
kossebau updated the task description. (Show Details)Oct 16 2019, 8:08 PM

@kossebau Can this be considered 'done'?

kossebau closed this task as Resolved.Aug 13 2020, 4:09 PM

Most of "Future Todo" is yet to do (+ some things I have in mind), but it's not looking as if anyone (incl. me) is going to work on that ever. So indeed time to tick this task off, thanks for poking.

nicolasfella reopened this task as Open.Aug 13 2020, 4:10 PM

We agreed to not close tasks on the KF6 board and move them to done instead

nicolasfella moved this task from In Progress to Done on the KF6 board.Aug 13 2020, 4:11 PM

My bad, thanks for fixing, missed this is on that board