From 94a539ee3d59cc6df0dd0586534b9a928bfcde5f Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Thu, 25 Jan 2024 10:58:53 +1030 Subject: [PATCH] Makefile: add CLN_NEXT_VERSION, functions encoding deprecation schedule. Each feature has a name, and says when deprecation begins and ends. There's an API coming to allow you to re-enable on a per-feature basis even if it's ended (as long as it's not been removed from the code ofc!). Default end is 6 months after deprecation, i.e. we complain about it at that point, if we can detect its use. e.g, a standard deprecation in v24.05: v24.02: allowed v24.02 with mods: allowed master after v24.02: allowed unless deprecated APIs disabled. v24.05: allowed unless deprecated APIs disabled. v24.08: allowed unless deprecated APIs disabled. v24.11: allowed unless deprecated APIs disabled, but logs at BROKEN level. v25.02: allowed only if --i-promise-to-fix-broken-api-user=FEATURE. v25.05: code is actually removed. Signed-off-by: Rusty Russell --- Makefile | 5 +- common/Makefile | 1 + common/deprecation.c | 147 ++++++++++++++++ common/deprecation.h | 38 ++++ common/test/run-deprecation.c | 164 ++++++++++++++++++ .../release-checklist.md | 8 +- 6 files changed, 359 insertions(+), 4 deletions(-) create mode 100644 common/deprecation.c create mode 100644 common/deprecation.h create mode 100644 common/test/run-deprecation.c diff --git a/Makefile b/Makefile index 5a1ce0ec4..4ebd47892 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,9 @@ # Extract version from git, or if we're from a zipfile, use dirname VERSION=$(shell git describe --always --dirty=-modded --abbrev=7 2>/dev/null || pwd | sed -n 's|.*/c\{0,1\}lightning-v\{0,1\}\([0-9a-f.rc\-]*\)$$|\1|gp') +# Next release. +CLN_NEXT_VERSION := v24.02 + # --quiet / -s means quiet, dammit! ifeq ($(findstring s,$(word 1, $(MAKEFLAGS))),s) ECHO := : @@ -249,7 +252,7 @@ CPATH := /usr/local/include LIBRARY_PATH := /usr/local/lib endif -CPPFLAGS += -DBINTOPKGLIBEXECDIR="\"$(shell sh tools/rel.sh $(bindir) $(pkglibexecdir))\"" +CPPFLAGS += -DCLN_NEXT_VERSION="\"$(CLN_NEXT_VERSION)\"" -DBINTOPKGLIBEXECDIR="\"$(shell sh tools/rel.sh $(bindir) $(pkglibexecdir))\"" CFLAGS = $(CPPFLAGS) $(CWARNFLAGS) $(CDEBUGFLAGS) $(COPTFLAGS) -I $(CCANDIR) $(EXTERNAL_INCLUDE_FLAGS) -I . -I$(CPATH) $(SQLITE3_CFLAGS) $(POSTGRES_INCLUDE) $(FEATURES) $(COVFLAGS) $(DEV_CFLAGS) -DSHACHAIN_BITS=48 -DJSMN_PARENT_LINKS $(PIE_CFLAGS) $(COMPAT_CFLAGS) $(CSANFLAGS) # If CFLAGS is already set in the environment of make (to whatever value, it diff --git a/common/Makefile b/common/Makefile index d37acb4c9..159068750 100644 --- a/common/Makefile +++ b/common/Makefile @@ -29,6 +29,7 @@ COMMON_SRC_NOGEN := \ common/daemon.c \ common/daemon_conn.c \ common/decode_array.c \ + common/deprecation.c \ common/derive_basepoints.c \ common/descriptor_checksum.c \ common/dev_disconnect.c \ diff --git a/common/deprecation.c b/common/deprecation.c new file mode 100644 index 000000000..49012b1b0 --- /dev/null +++ b/common/deprecation.c @@ -0,0 +1,147 @@ +#include "config.h" +#include +#include +#include +#include +#include + +#define MONTH_VAL (10) +#define YEAR_VAL (12 * MONTH_VAL) + +/* Returns 1 + patchnumber + 10*minor + 120*major. 0 on malformed */ +u32 version_to_number(const char *version) +{ + char *yend, *mend; + long year, month, patchlevel; + + if (version[0] != 'v') + return 0; + + year = strtol(version + 1, ¥d, 10); + if (yend == version + 1 || *yend != '.') + return 0; + if (year > 99) + return 0; + + month = strtol(yend + 1, &mend, 10); + if (mend == yend + 1) + return 0; + + if (month < 1 || month > 12) + return 0; + + if (*mend == '.') { + char *endp; + patchlevel = strtol(mend + 1, &endp, 10); + if (endp == mend + 1) + return 0; + if (patchlevel >= MONTH_VAL) + return 0; + } else + patchlevel = 0; + + return 1 + year*YEAR_VAL + month*MONTH_VAL + patchlevel; +} + +enum deprecation_level { + /* Will be deprecated in future */ + DEPRECATED_SOON, + /* Deprecated, but still ok unless explicitly disabled */ + DEPRECATED, + /* Deprecated, and we whine about it */ + DEPRECATED_COMPLAIN, + /* Deprecated, and we only enable it if they begged */ + DEPRECATED_BEG, +}; + +static enum deprecation_level deprecation(const char *start, + const char *end) +{ + long cur; + long startnum; + long endnum; + + /* Versions are hard. Consider these: + * v23.05 + * -- A released version + * v23.05rc1 + * -- A release candidate + * v23.05rc4-11-g1e96146 + * -- Development off rc4, OR a user's local mods. + * v23.05-1-gf165dc0-modded + * -- Development off 23.05, OR a user's local mods. + */ + + /* If master has moved since release, we want to increment + * deprecations. If a user has made local mods, we don't! + * Fortunately, the Makefile sets "CLN_NEXT_VERSION", and + * we can simply use this. + */ + cur = version_to_number(CLN_NEXT_VERSION); + assert(cur); + startnum = version_to_number(start); + assert(startnum); + if (end) { + endnum = version_to_number(end); + assert(endnum); + assert(endnum >= startnum); + } else /* 6 months later */ + endnum = startnum + 6 * MONTH_VAL; + + if (cur < startnum) + return DEPRECATED_SOON; + if (cur < endnum) + return DEPRECATED; + if (cur == endnum) + return DEPRECATED_COMPLAIN; + return DEPRECATED_BEG; +} + +bool deprecated_ok_(bool deprecated_apis, + const char *feature, + const char *start, + const char *end, + const char **begs, + void (*complain)(const char *feature, bool allowing, void *cbarg), + void *cbarg) +{ + enum deprecation_level level; + bool allow; + + /* Not deprecated at all? */ + if (!start) + return true; + + level = deprecation(start, end); + switch (level) { + case DEPRECATED_SOON: + return false; + case DEPRECATED: + /* Complain if we're disallowing becuase it's deprecated */ + allow = deprecated_apis; + if (!allow) + goto complain; + goto no_complain; + case DEPRECATED_COMPLAIN: + allow = deprecated_apis; + /* Always complain about these! */ + goto complain; + case DEPRECATED_BEG: + allow = false; + for (size_t i = 0; i < tal_count(begs); i++) { + if (streq(feature, begs[i])) + allow = true; + } + /* Don't complain about begging: they've explicitly noted this! */ + if (allow) + goto no_complain; + goto complain; + } + abort(); + +complain: + if (complain) + complain(feature, allow, cbarg); +no_complain: + return allow; +} diff --git a/common/deprecation.h b/common/deprecation.h new file mode 100644 index 000000000..c49980620 --- /dev/null +++ b/common/deprecation.h @@ -0,0 +1,38 @@ +#ifndef LIGHTNING_COMMON_DEPRECATION_H +#define LIGHTNING_COMMON_DEPRECATION_H +#include "config.h" +#include +#include + +/** + * deprecated_ok - should we allow a feature? + * @deprecated_apis: are deprecated features blanket enabled? + * @feature: user-visible name for feature + * @start: (optoonal) first version to deprecate it in. + * @end: (optional) final version to allow it in (default: 6 months after start). + * @begs: (optional) tal_arr of strings features to allow after @end. + * @complain: (optional) loggin callback if they use a deprecated feature. + * + * @feature is the name the user will see in the logs, and have to use to manually + * re-enable it at the end of the deprecation period. + * @start and @end are of form "v23.08". + * @complain takes the @feature, and a flag to say if we're allowing it or not. + */ +#define deprecated_ok(deprecated_apis, feature, start, end, begs, complain, cbarg) \ + deprecated_ok_((deprecated_apis), (feature), (start), (end), (begs), \ + typesafe_cb_preargs(void, void *, (complain), (cbarg), \ + const char *, bool), \ + cbarg) + +bool NON_NULL_ARGS(2) deprecated_ok_(bool deprecated_apis, + const char *feature, + const char *start, + const char *end, + const char **begs, + void (*complain)(const char *feat, bool allowing, void *), + void *cbarg); + +/* Returns number corresponding to version, or 0 if it doesn't parse */ +u32 version_to_number(const char *version); + +#endif /* LIGHTNING_COMMON_DEPRECATION_H */ diff --git a/common/test/run-deprecation.c b/common/test/run-deprecation.c new file mode 100644 index 000000000..fe1a4cd05 --- /dev/null +++ b/common/test/run-deprecation.c @@ -0,0 +1,164 @@ +#include "config.h" +#undef CLN_NEXT_VERSION +#define CLN_NEXT_VERSION test_next_version + +static const char *test_next_version; + +#include "../deprecation.c" +#include +#include +#include +#include + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_feerate */ + bool amount_feerate(u32 *feerate UNNEEDED, struct amount_sat fee UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_feerate called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +int main(int argc, char *argv[]) +{ + common_setup(argv[0]); + + assert(version_to_number("v22.11") == 22*YEAR_VAL + 11*MONTH_VAL + 1); + + assert(version_to_number("v22.11rc1") == 22*YEAR_VAL + 11*MONTH_VAL + 1); + + assert(version_to_number("v22.11rc1-1-g123456") == 22*YEAR_VAL + 11*MONTH_VAL + 1); + + /* We insert a 0 so far, but let's check both! */ + assert(version_to_number("v25.1") == 25*YEAR_VAL + 1*MONTH_VAL + 1); + assert(version_to_number("v25.02") == 25*YEAR_VAL + 2*MONTH_VAL + 1); + assert(version_to_number("v25.12") == 25*YEAR_VAL + 12*MONTH_VAL + 1); + + assert(version_to_number("v25.12.9") == 25*YEAR_VAL + 12*MONTH_VAL + 9 + 1); + + /* Malformed */ + assert(version_to_number("v25.12.") == 0); + assert(version_to_number("v25.12.10") == 0); + assert(version_to_number("v25.0") == 0); + assert(version_to_number("v25.") == 0); + assert(version_to_number("v25..") == 0); + assert(version_to_number("v25.13") == 0); + assert(version_to_number("v100.12") == 0); + assert(version_to_number("v25.") == 0); + assert(version_to_number("v25") == 0); + assert(version_to_number("v") == 0); + assert(version_to_number("") == 0); + assert(version_to_number("25.12") == 0); + + test_next_version = "v23.08"; + /* Deprecated in future */ + assert(deprecation("v24.08", NULL) == DEPRECATED_SOON); + assert(deprecation("v23.09", NULL) == DEPRECATED_SOON); + /* Just deprecated for 6 months */ + assert(deprecation("v23.08", NULL) == DEPRECATED); + assert(deprecation("v23.07", NULL) == DEPRECATED); + assert(deprecation("v23.06", NULL) == DEPRECATED); + assert(deprecation("v23.05", NULL) == DEPRECATED); + assert(deprecation("v23.04", NULL) == DEPRECATED); + assert(deprecation("v23.03", NULL) == DEPRECATED); + /* At 6 months, it complains */ + assert(deprecation("v23.02", NULL) == DEPRECATED_COMPLAIN); + /* After that, you need to switch it on */ + assert(deprecation("v23.01", NULL) == DEPRECATED_BEG); + assert(deprecation("v22.12", NULL) == DEPRECATED_BEG); + assert(deprecation("v22.11", NULL) == DEPRECATED_BEG); + assert(deprecation("v22.01", NULL) == DEPRECATED_BEG); + + /* Non-default end works */ + assert(deprecation("v23.09", "v23.10") == DEPRECATED_SOON); + assert(deprecation("v23.08", "v23.09") == DEPRECATED); + assert(deprecation("v23.07", "v23.08") == DEPRECATED_COMPLAIN); + assert(deprecation("v23.06", "v23.07") == DEPRECATED_BEG); + + common_shutdown(); +} diff --git a/doc/contribute-to-core-lightning/release-checklist.md b/doc/contribute-to-core-lightning/release-checklist.md index c247e98ff..aa97c2fa4 100644 --- a/doc/contribute-to-core-lightning/release-checklist.md +++ b/doc/contribute-to-core-lightning/release-checklist.md @@ -93,6 +93,8 @@ Here's a checklist for the release process. ## Post-release -1. Look through PRs which were delayed for release and merge them. -2. Close out the Milestone for the now-shipped release. -3. Update this file with any missing or changed instructions. +1. Wait for a week to see if we need any point releases! +2. Create a PR to update Makefile's CLN_NEXT_VERSION. +3. Look through PRs which were delayed for release and merge them. +4. Close out the Milestone for the now-shipped release. +5. Update this file with any missing or changed instructions.