Skip to content

Commit

Permalink
Make plugin experimental according to SemVer if needed (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gustry authored Apr 27, 2021
1 parent 538a2be commit 53c3175
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 42 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ These scripts are written for and tested on GitHub, Travis-CI, github workflows
- pull and push translations
- all TS/QM files can be managed on the CI, the `i18n` folder can be omitted from the Git repository
- `changelog` section in the metadata.txt can be populated if the CHANGELOG.md is present
- set the `experimental` flag according to the tag if needed

:book: For further information, see [the documentation](https://opengisch.github.io/qgis-plugin-ci/).

QGIS-Plugin-CI is best served if you use these two conventions :

* [Semantic versioning](https://semver.org/)
* [Keep A Changelog](https://keepachangelog.com)

## Command line

```commandline
Expand Down Expand Up @@ -59,9 +65,14 @@ Both can be achieved in the same process.

## Pre-release and experimental

In the case of a pre-release (from GitHub), the plugin will be flagged as experimental.
In the case of a pre-release (either from the tag name according to [Semantic Versioning](https://semver.org/) or from the GitHub release), the plugin will be flagged as experimental.
If pushed to the QGIS plugin repository, the QGIS minimum version will be raised to QGIS 3.14 (only 3.14 and above support testing of experimental versions).

The tool will recognise any label use as a suffix to flag it as pre-release :

* `10.1.0-beta1`
* `3.4.0-rc.2`

## Debug

In any Python module, you can have a global variable as `DEBUG = True`, which will be changed to `False` when packaging the plugin.
7 changes: 7 additions & 0 deletions docs/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ Side note, the plugin path shouldn't have any dash character.
You can find a template `.qgis-plugin-ci` in this repository.
You can read the docstring of the [Parameters module](/_apidoc/qgispluginci.parameters) to know parameters which are available in the file.

## Conventions

QGIS-Plugin-CI is best served if you use these two conventions :

* [Semantic versioning](https://semver.org/)
* [Keep A Changelog](https://keepachangelog.com)

## Options

| Name | Required | Description | Example |
Expand Down
3 changes: 1 addition & 2 deletions docs/usage/cli_changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ optional arguments:

## Requirements

The `CHANGELOG.md` file must follow the convention [Keep A Changelog](https://keepachangelog.com/). For example, see this [repository changelog](https://github.com/opengisch/qgis-plugin-ci/blob/master/CHANGELOG.md).
If your format is different, you must use a different `changelog_regexp` expression to parse it in your settings.
The `CHANGELOG.md` file must follow the convention [Keep A Changelog](https://keepachangelog.com/). For example, see this [repository changelog](https://github.com/opengisch/qgis-plugin-ci/blob/master/CHANGELOG.md).

## Use cases

Expand Down
51 changes: 20 additions & 31 deletions qgispluginci/changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import logging
import re
from pathlib import Path
from typing import NamedTuple, Union
from typing import Union

from qgispluginci.version_note import VersionNote

# ############################################################################
# ########## Globals #############
Expand Down Expand Up @@ -113,8 +115,8 @@ def last_items(self, count: int) -> str:
output += "\n"
return output

def content(self, tag: str) -> str:
"""Get a version content to add in a release according to the version name."""
def _version_note(self, tag: str) -> Union[VersionNote, None]:
""" Get the tuple for a given version. """
changelog_content = self._parse()
if not len(changelog_content):
logger.error(
Expand All @@ -124,38 +126,25 @@ def content(self, tag: str) -> str:
return None

if tag == "latest":
latest_version = VersionNote(*changelog_content[0])
return latest_version.text.strip()
return VersionNote(*changelog_content[0])

for version in changelog_content:
version_note = VersionNote(*version)
if version_note.version == tag:
return version_note.text.strip()


class VersionNote(NamedTuple):
major: str = None
minor: str = None
patch: str = None
url: str = None
prerelease: str = None
separator: str = None
date: str = None
text: str = None

@property
def is_prerelease(self):
if self.prerelease and len(self.prerelease):
return True
else:
return False

@property
def version(self):
if self.prerelease:
return f"{self.major}.{self.minor}.{self.patch}-{self.prerelease}"
else:
return f"{self.major}.{self.minor}.{self.patch}"
return version_note

def latest_version(self) -> str:
""" Return the latest tag described in the changelog file. """
latest = self._version_note("latest")
return latest.version

def content(self, tag: str) -> Union[str, None]:
"""Get a version content to add in a release according to the version name."""
version_note = self._version_note(tag)
if not version_note:
return None

return version_note.text


# ############################################################################
Expand Down
24 changes: 17 additions & 7 deletions qgispluginci/release.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import re
import sys
import tarfile
import warnings
import xmlrpc.client
import zipfile
from glob import glob
Expand All @@ -31,7 +30,7 @@
)
from qgispluginci.parameters import Parameters
from qgispluginci.translation import Translation
from qgispluginci.utils import configure_file, replace_in_file
from qgispluginci.utils import configure_file, parse_tag, replace_in_file


def create_archive(
Expand Down Expand Up @@ -255,6 +254,15 @@ def release_is_prerelease(
release_tag: str,
github_token: str,
) -> bool:
""" Check the tag name or the GitHub release if the version must be experimental or not. """

if parse_tag(release_tag).is_prerelease:
# The tag itself is a pre-release according to https://semver.org/
return True

if not github_token:
return False

slug = "{}/{}".format(parameters.github_organization_slug, parameters.project_slug)
repo = Github(github_token).get_repo(slug)
try:
Expand Down Expand Up @@ -407,6 +415,10 @@ def release(
If omitted, a git submodule is updated. If specified, git submodules will not be updated/initialized before packaging.
"""

if release_version == "latest":
parser = ChangelogParser()
release_version = parser.latest_version()

if transifex_token is not None:
tr = Translation(
parameters, create_project=False, transifex_token=transifex_token
Expand All @@ -416,11 +428,9 @@ def release(

archive_name = parameters.archive_name(parameters.plugin_path, release_version)

is_prerelease = False
if github_token is not None:
is_prerelease = release_is_prerelease(
parameters, release_tag=release_version, github_token=github_token
)
is_prerelease = release_is_prerelease(
parameters, release_tag=release_version, github_token=github_token
)
print("*** is pre-release: {}".format("YES" if is_prerelease else "NO"))

create_archive(
Expand Down
19 changes: 19 additions & 0 deletions qgispluginci/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import os
import re
from typing import Union

from qgispluginci.version_note import VersionNote


def replace_in_file(file_path: str, pattern, new: str, encoding: str = "utf8"):
Expand Down Expand Up @@ -28,3 +31,19 @@ def touch_file(path, update_time: bool = False, create_dir: bool = True):
os.utime(path, None)
else:
pass


def parse_tag(version_tag: str) -> Union[VersionNote, None]:
""" Parse a tag and determine the semantic version. """
components = version_tag.split("-")
items = components[0].split(".")

try:
if len(components) == 2:
return VersionNote(
major=items[0], minor=items[1], patch=items[2], prerelease=components[1]
)
else:
return VersionNote(major=items[0], minor=items[1], patch=items[2])
except IndexError:
return VersionNote()
31 changes: 31 additions & 0 deletions qgispluginci/version_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import NamedTuple


class VersionNote(NamedTuple):
major: str = None
minor: str = None
patch: str = None
url: str = None
prerelease: str = None
separator: str = None
date: str = None
text_raw: str = None

@property
def text(self) -> str:
""" Remove many \n at the start and end of the string. """
return self.text_raw.strip()

@property
def is_prerelease(self) -> bool:
if self.prerelease and len(self.prerelease):
return True
else:
return False

@property
def version(self) -> str:
if self.prerelease:
return f"{self.major}.{self.minor}.{self.patch}-{self.prerelease}"
else:
return f"{self.major}.{self.minor}.{self.patch}"
35 changes: 34 additions & 1 deletion test/test_changelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
from pathlib import Path

# project
from qgispluginci.changelog import ChangelogParser, VersionNote
from qgispluginci.changelog import ChangelogParser
from qgispluginci.utils import parse_tag
from qgispluginci.version_note import VersionNote

# ############################################################################
# ########## Classes #############
Expand Down Expand Up @@ -101,6 +103,8 @@ def test_changelog_content_latest(self):
parser = ChangelogParser(parent_folder="test/fixtures")
self.assertEqual(expected_latest, parser.content("latest"))

self.assertEqual("10.1.0-beta1", parser.latest_version())

def test_changelog_content_ci_fake(self):
"""Test specific fake version used in tests."""
parser = ChangelogParser()
Expand Down Expand Up @@ -147,6 +151,35 @@ def test_changelog_version_note(self):
if len(version_note.prerelease):
self.assertEqual(version_note.is_prerelease, True)

def test_version_note_tuple(self):
""" Test the version note tuple. """
parser = ChangelogParser(parent_folder="test/fixtures")

version = parser._version_note("0.0.0")
self.assertIsNone(version)

version = parser._version_note("10.1.0-beta1")
self.assertEqual("10", version.major)
self.assertEqual("1", version.minor)
self.assertEqual("0", version.patch)
self.assertEqual("", version.url)
self.assertEqual("beta1", version.prerelease)
self.assertTrue(version.is_prerelease)
self.assertEqual("", version.separator) # Not sure what is the separator
self.assertEqual("2021/02/08", version.date)
self.assertEqual(
(
"- This is the latest documented version in this changelog\n"
"- The changelog module is tested against these lines\n"
"- Be careful modifying this file"
),
version.text,
)

version = parser._version_note("10.0.1")
self.assertEqual("", version.prerelease)
self.assertFalse(version.is_prerelease)


# ############################################################################
# ####### Stand-alone run ########
Expand Down
54 changes: 54 additions & 0 deletions test/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import unittest

from qgispluginci.utils import parse_tag
from qgispluginci.version_note import VersionNote


class TestUtils(unittest.TestCase):
def test_version_note_from_tag(self):
""" Test to parse a tag and check the version note. """

version = parse_tag("10.1.0-beta1")
self.assertIsInstance(version, VersionNote)
self.assertEqual("10", version.major)
self.assertEqual("1", version.minor)
self.assertEqual("0", version.patch)
self.assertEqual("beta1", version.prerelease)
self.assertTrue(version.is_prerelease)

version = parse_tag("3.4.0-rc.2")
self.assertIsInstance(version, VersionNote)
self.assertEqual("3", version.major)
self.assertEqual("4", version.minor)
self.assertEqual("0", version.patch)
self.assertEqual("rc.2", version.prerelease)
self.assertTrue(version.is_prerelease)

version = parse_tag("10.1.0")
self.assertIsInstance(version, VersionNote)
self.assertEqual("10", version.major)
self.assertEqual("1", version.minor)
self.assertEqual("0", version.patch)
self.assertIsNone(version.prerelease)
self.assertFalse(version.is_prerelease)

version = parse_tag("v10.1.0")
self.assertIsInstance(version, VersionNote)
self.assertEqual("v10", version.major)
self.assertEqual("1", version.minor)
self.assertEqual("0", version.patch)
self.assertIsNone(version.prerelease)
self.assertFalse(version.is_prerelease)

# Not following https://semver.org/, we can't guess
version = parse_tag("10.1")
self.assertIsInstance(version, VersionNote)
self.assertIsNone(version.major)
self.assertIsNone(version.minor)
self.assertIsNone(version.patch)
self.assertIsNone(version.prerelease)
self.assertFalse(version.is_prerelease)


if __name__ == "__main__":
unittest.main()

0 comments on commit 53c3175

Please sign in to comment.