diff --git a/index.md b/index.md index 9083c0e..4dfd9dd 100644 --- a/index.md +++ b/index.md @@ -42,6 +42,7 @@ The sponsor is responsible for managing the review stage and votes. | N/A | [Simplify Admin Views*][simplify-admin] | Elisa Foltyn | N/A | | N/A | [Simplify Admin Views*][simplify-admin2] | Astrid Günther | N/A | | N/A | [Composer support][composer] | Astrid Günther | N/A | +| N/A | [Release Versioning][release-versioning] | Niels Braczek | Harald Leithner | *) Two different proposals on the same target, need to be merged @@ -68,3 +69,4 @@ _**Legend:** A = Accepted | D = Draft | P = Pre-Draft | R = Review | X = Depreca [simplify-admin2]: https://github.com/joomla-x/joomla-standards/pull/7 [composer]: https://github.com/joomla-x/joomla-standards/pull/8 [rfc-procedure]: https://github.com/joomla-x/joomla-standards/blob/master/accepted/RFC-0-rfc-meta.md +[release-versioning]: https://github.com/joomla-x/joomla-Specifications/tree/master/proposed diff --git a/proposed/release-versioning-examples.md b/proposed/release-versioning-examples.md new file mode 100644 index 0000000..2ef9c2a --- /dev/null +++ b/proposed/release-versioning-examples.md @@ -0,0 +1,387 @@ +# Release Versioning Examples + +This document contains a collection of examples to illustrate the rules of the Release Versioning specification. + +* [Case: Utilising a New PHP Feature](#case-1) + * [Scenario 1 - Using the New PHP Feature to Replace Code](#case-1-1) + * [Scenario 2 - Using the New PHP Feature to Extend Functionality](#case-1-2) + +More cases may be added over time. + + + +## Case: Utilising a New PHP Feature + +The starting point is this class, which prepares a fresh Earl Grey tea: + +```php +// Old version +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} +``` + + + +### Scenario 1 - Using the New PHP Feature to Replace Code + +Let's assume that PHP 8.1 introduces a native function `php_make_caffeine_drink()` to make all kinds of caffeinated hot +drinks. This function does a lot of work for us, so we can greatly simplify `CupOfTea::makeTea()`. However, we still +need to support PHP versions before 8.1. This is where a so-called feature switch comes into play. + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + // Feature switch + if (version_compare(PHP_VERSION, '8.1', '>=')) { + $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} +``` + +Since this change does not affect existing code that uses the `CupOfTea::makeTea()` method in any way (nothing needs to +be adjusted there to continue working, after all), it can be released with the next patch release. As soon as official +support for PHP versions < 8.1 ceases, the old code can (must) be removed: + +```php +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + */ + public function makeTea($addSugar = false, $addMilk = false) + { + $tea = php_make_caffeine_drink(type: 'earl grey', amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } +} +``` + + + +## Scenario 2 - Using the New PHP Feature to Extend Functionality + +The PHP function `php_make_caffeine_drink()` in scenario 1 is a function for all caffeinated hot drinks, not just Earl +Grey tea. This opens up the chance for us to also offer other tea varieties such as Darjeeling. We can even go as far as +allowing coffee. + +Here we have to make a fundamental decision: Do we want to build a separate class for coffee? Or would we rather build a +more general version that brews caffeinated hot drinks? + +### a. Two Separate Classes + +In the first case, the class `CupOfTea` will be enabled to produce different types of tea. Unfortunately, we can't just +change the signature of `makeTea()` - there might be developers who have derived from this class because it wasn't +declared as `final`, and PHP throws an `E_WARNING` if the signatures of parent class and child class don't match. It can +make sense to accept these warnings, but this should be thoroughly considered, justified and documented. Here we choose +the way to replace the `makeTea()` function. + +```php +// PHP 8.1 version using native php_make_caffeine_drink() function +class CupOfTea +{ + /** + * Supported types of tea + * @api + */ + public const EARL_GREY = 'earl grey'; + public const DARJEELING = 'darjeeling'; + + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + * + * @deprecated 4.2 Will be removed in 6.0. Use CupOfTea::getTea() instead. + * Before (version < 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->makeTea($addSugar, $addMilk); + * After (version >= 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->getTea(CupOfTea::EARL_GREY, $addSugar, $addMilk); + */ + public function makeTea($addSugar = false, $addMilk = false) + { + trigger_error('Method ' . __METHOD__ . ' is deprecated', E_USER_DEPRECATED); + + return $this->getTea(self::EARL_GREY, $addSugar, $addMilk); + } + + /** + * Brew a fresh cup of tea + * + * @api + * @param string $type The type of tea, one of the CupOfTea::* constants. + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup A cup of tea + * @since 4.2 + */ + public function getTea($type, $addSugar = false, $addMilk = false) + { + if (version_compare(PHP_VERSION, '8.1', '>=')) { + $drink = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 100 : 0); + $cup = new Cup(); + $cup->addIngredient($drink); + return $cup; + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea($type); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} + +class CupOfCoffee +{ + /** + * Supported types of coffee + * @api + */ + public const ROBUSTA = 'robusta'; + public const ARABICA = 'arabica'; + + /** + * Constructor. + * + * This constructor can be removed, once the PHP minimum requirement is 8.1 or above. + * + * @api + * @since 4.2 + */ + public function __construct() + { + if (version_compare(PHP_VERSION, '8.1', '<')) { + throw new \RuntimeException('This class requires PHP >= 8.1'); + } + } + + /** + * Brew a fresh cup of coffee + * + * @api + * @param string $type The type of coffee, one of the CupOfCoffee::* constants. + * @param bool $addSugar Set to true, if you want sugar in the coffee. + * @param bool $addMilk Set to true, if you want milk in the coffee. + * + * @return Cup + * @since 4.2 + */ + public function getCoffee($type, $addSugar = false, $addMilk = false) + { + $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } +} +``` + +Because you can now make teas other than Earl Grey, and even coffee, the change is considered a feature and therefore +belongs in a minor release. + +### b. One General Class + +Since the methods `CupOfTea::getTea()` and `CupOfCoffee::getCoffee()` are very similar in end effect, it probably makes +sense in this case to provide a more general solution: a class that makes hot drinks. With the introduction of this new +class, `CupOfTea` becomes redundant and therefore deprecated. + +```php +class CupOfHotDrink +{ + /** + * Supported types of hot drinks + * @api + */ + public const EARL_GREY = 'earl grey'; + public const DARJEELING = 'darjeeling'; + public const ROBUSTA = 'robusta'; + public const ARABICA = 'arabica'; + + /** + * Brew a fresh hot drink + * + * @api + * @param string $type The type of hot drink, one of the CupOfHotDrink::* constants. + * @param bool $addSugar Set to true, if you want sugar in the hot drink. + * @param bool $addMilk Set to true, if you want milk in the hot drink. + * + * @return Cup + * @since 4.2 + */ + public function getHotDrink($type, $addSugar = false, $addMilk = false) + { + if (version_compare(PHP_VERSION, '8.1', '>=')) { + $tea = php_make_caffeine_drink(type: $type, amount: '0.5', sugar: $sugar ? 4 : 0, milk: $milk ? 50 : 0); + $cup = new Cup(); + $cup->addIngredient($tea); + return $cup; + } + + if ($type !== self::EARL_GREY) { + throw new \RuntimeException('To brew hot drinks other than Earl Gray tea you need PHP 8.1 or above.'); + } + + // Fall back to old code for PHP < 8.1 + $boiler = new Boiler(); + $boiler->addWater(0.5); + $boiler->cook(97.7778); // heat up to 97.7778 °C + + $tea = new Tea('earl grey'); + + $cup = new Cup(); + $cup->addIngredient($tea); + $cup->addIngredient($boiler->getWater()); + + if ($addSugar) { + $sugar = new Sugar(4); // get 4 gram sugar + $cup->addIngredient($sugar); + } + + if ($addMilk) { + $milk = new Milk(100); // get 100 ml milk + $cup->addIngredient($milk); + } + + return $cup; + } +} + +/** + * @deprecated 4.2 Will be removed in 6.0. Use CupOfHotDrink instead. + */ +class CupOfTea +{ + /** + * Brew a fresh cup of Earl Grey tea + * + * @api + * @param bool $addSugar Set to true, if you want sugar in the tea. + * @param bool $addMilk Set to true, if you want milk in the tea. + * + * @return Cup + * + * @deprecated 4.2 Will be removed in 6.0. Use CupOfHotDrink::getHotDrink() instead. + * Before (version < 4.2): + * $brewer = new CupOfTea(); + * $tea = $brewer->makeTea($addSugar, $addMilk); + * After (version >= 4.2): + * $brewer = new CupOfHotDrink(); + * $tea = $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); + */ + public function makeTea($addSugar = false, $addMilk = false) + { + trigger_error('Method ' . __METHOD__ . ' is deprecated. Use CupOfHotDrink::getHotDrink() instead.', E_USER_DEPRECATED); + + $brewer = new CupOfHotDrink(); + return $brewer->getHotDrink(CupOfHotDrink::EARL_GREY, $addSugar, $addMilk); + } +} +``` + +This solution will maintain the functionality `CupOfTea` until the next major version. From the next minor release that +includes this change, users in a modern environment will be able to use the new possibilities immediately. For those in +older environments, no functionality will be lost. diff --git a/proposed/release-versioning-meta.md b/proposed/release-versioning-meta.md new file mode 100644 index 0000000..4bd105d --- /dev/null +++ b/proposed/release-versioning-meta.md @@ -0,0 +1,113 @@ +# Release Versioning Meta Document + +## 1. Summary + +_This Specification describes how versions are numbered in Joomla projects._ + +This specification aims to help developers, code reviewers and release managers to determine, whether a new piece or a +change of code belongs to the next patch, minor or major release. It will also cover documentation requirements that are +related to versioning. + +## 2. Why Bother? + +Sometimes it is hard to tell bug fixes and features apart. The Release Leads want a comprehensive set of rules to be +able to make good and fast decisions about the target release of a contribution. + +## 3. Scope + +The specification applies to all Joomla projects, e.g., the CMS, the Framework, core extensions, ... + +## 4. Considerations + +### 4.1 Semantic Versioning + +Joomla started to follow [Semantic Versioning](Semver) with release 3.3 in April 2014. The rules were not always +followed strictly ([Issue#16874](16874), [Issue#17583](17583), [Issue#24728](24728) to name a few). This need to change, +because the Joomla ecosystem is heavily depending on reliable information about compatibility. This is done in section 1 +of the specification. + +[Semver]: https://github.com/semver/semver/blob/master/semver.md + +[24728]: https://issues.joomla.org/tracker/joomla-cms/24728 + +[17583]: https://github.com/joomla/joomla-cms/issues/17583 + +[16874]: https://github.com/joomla/joomla-cms/issues/16874 + +### 4.2 API + +The main problem with Semantic Versioning seems to be to categorise code contributions as bug fixes or features. This +should be easy enough, as the Semantic Versioning specification is quite clear in that regard. Strongly simplified, it +says: Changes that do not change signatures of functions or methods of the public API and do not add new ones can go +into a patch release. + +Unfortunately, Joomla has no official definition of "public API". Section 2 of the specification makes up for the +omission. It describes, how public and internal API are told apart using the `@api` and `@internal` annotations as +proposed by the draft [PSR19][]. It is recommended to add these annotations to existing code. For new code they are +required. + +[PSR19]: https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc-tags.md + +### 4.3 Deprecations + +The core contributors have not been very cautious about deprecations in the past. +`JObject`, for example, has +been [deprecated for Joomla 3.4](https://github.com/joomla/joomla-cms/issues/6125#issuecomment-75035212), but made it +into Joomla 4 (although renamed to CMSObject) with the deprecation annotation still in place. Section 3 describes how to +handle and document deprecations. Among other things, it stipulates that a replacement must be offered and used, if the +deprecation is not phasing out a feature. This requirement serves several purposes: + +- It proves that the new solution actually works where it will end up being used. +- It shows developers, who consider the code as authoritative documentation, how the new solution works and should be + used. +- It prevents the release managers of the following major version from being surprised by the "leftovers" and maybe + therefore not being able to meet the schedule. + +The documentation requirements make sure that legacy code can easily be adopted to the new implementation. +A comparison of old and new code shall make it easier for developers to adapt their code to the new conditions and +provide sample code for documentation. It is important that the representation of the old and new code is both human- +and machine-readable. + +### 4.4 User Interface + +From a code point of view, improvements of the user interface do not touch the API and thus are not features in the +sense of Semantic Versioning. Template files, however, work like functions, since they rely on injected variables to +produce the output. This interface between a View and a template is a contract and as such underlies the rules of +Semantic Versioning. Section 4 provides the rules for dealing with user interface changes. + +### 4.5 External Dependencies + +Section 5 deals with external dependencies, a.k.a. libraries. The implementation details of external dependencies are +not under the control of the Joomla project. Therefore, the code of these dependencies is basically internal and does +not fall under Semantic Versioning. As a result, these dependencies can be updated or even exchanged without breaking +the compatibility promise. As soon as an external API officially becomes part of the public API, as in the case of +Bootstrap, the rules of Semantic Versioning do apply. + +## 5. People + +### 5.1 Editor(s) + +* Niels Braczek + +### 5.2 Sponsors + +* Harald Leithner + +### 5.3 Contributors + +* N/A + +## 6. Votes + +* **Entrance Vote:** 2021-11-16 Production Team Leaders +* **Acceptance Vote:** _(not yet taken)_ + +## 7. Relevant Links + +_**Note:** Order descending chronologically._ + +* [Semantic Versioning Specification](Semver) + +## 8. Errata + +... diff --git a/proposed/release-versioning.md b/proposed/release-versioning.md new file mode 100644 index 0000000..80a5f88 --- /dev/null +++ b/proposed/release-versioning.md @@ -0,0 +1,117 @@ +# Release Versioning + +This specification describes how versions are numbered in Joomla projects. It also defines documentation requirements +related to versioning. + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", +"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119][]. + +[RFC 2119]: https://www.rfc-editor.org/rfc/rfc2119.html + +### Definitions + +* "Structural Element" is a collection of Programming Constructs which MAY be preceded by a DocBlock. The collection + contains the following constructs: + * class + * interface + * trait + * function (including methods) + * property + * constant + * variables, both local and global scope. + +### References + +- [RFC 2119][]: Key words for use in RFCs to Indicate Requirement Levels + +## Specification + +### 1 Semantic Versioning + +1. All releases in all Joomla projects MUST strictly follow the [Semantic Versioning](Semver) specification in its + current version. + +[Semver]: https://github.com/semver/semver/blob/master/semver.md + +### 2 API + +Because Semantic Versioning applies to public API only, a distinction between public and internal API is needed. + +#### 2.1 Public API + +"Public API" describes the sum of structural elements that MAY be used by extension developers. + +1. Changes to the public API are subject to the rules of semantic versioning. +2. In order to distinguish these elements from the internal elements, the following applies: + 1. Existing public structural elements SHOULD be annotated with `@api` in the corresponding DocBlock. + 2. Newly introduced public structural elements MUST be annotated with `@api` in the corresponding DocBlock. + 3. Public structural elements MUST be included in the official documentation. + +#### 2.2 Internal API + +The "Internal API" comprises the structural elements that are supportive for the core and SHOULD NOT be used by +extension developers. + +1. Changes to the internal API are not subject to the rules of semantic versioning. +2. In order to distinguish these elements from the public elements, the following applies: + 1. Existing internal structural elements SHOULD be annotated with `@internal` in the corresponding DocBlock. + 2. Newly introduced structural elements MUST be annotated with `@internal` in the corresponding DocBlock. + 3. Structural elements with the access modifier `private` and structural elements in final classes with the access + modifier `protected` MUST be treated as internal. + 4. Internal structural elements MUST NOT be included in the official documentation. All documentation needed by core + developers SHOULD be available in the corresponding DocBlock. + +### 3 Deprecations + +Deprecating existing functionality is a normal part of software development and is often required to make forward +progress. There are two reasons to deprecate code: (1) architectural changes or (2) removal of features. This section +applies to public API only. + +#### 3.1 Architectural Changes + +1. The official documentation MUST be updated to let users know about the change. +2. The DocBlock MUST be annotated to document the replacement for the deprecated element. The deprecation annotation + SHALL be supplemented by a usage example for the replacement. Example: + ```php + /** + * ... + * @deprecated X.Y Will be removed in X+2.0. Use {@see } instead. + * Before (version < X.Y): + * + * After (version >= X.Y): + * + */ + ``` + with X.Y being the minor version introducing the deprecation. +3. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code + anywhere. +4. The deprecated code MUST be removed in the next major release. + +#### 3.2 Phasing out Features + +1. The official documentation MUST be updated to let users know about the change. +2. The DocBlock MUST be annotated to document the future removal of the deprecated element. The deprecation annotation + SHALL be supplemented by a recommendation for an alternative. Example: + ```php + /** + * ... + * @deprecated X.Y Will be removed in X+2.0 without replacement. + * Please consider using or instead. + */ + ``` + with X.Y being the minor version introducing the deprecation. +3. A new minor release MUST be issued with the deprecation in place. That release MUST NOT use the deprecated code + anywhere. +4. The deprecated code MUST be removed in the next major release. + +### 4 User Interface + +1. Changes to the user interface are considered compatible if they do not affect the public API. +2. If variables passed to or required by template files change, it MUST be treated as a change of the public API. + +### 5 External Dependencies + +1. Updating external dependencies is considered compatible since it does not affect the public API. +2. The API of the dependencies is not part of the project's API, unless explicitly stated in the official documentation. +3. Extension developers MUST NOT rely on the presence of dependencies that are not covered in the official + documentation.