diff --git a/changelog.d/107.feature.md b/changelog.d/107.feature.md new file mode 100644 index 0000000..54b1b7e --- /dev/null +++ b/changelog.d/107.feature.md @@ -0,0 +1 @@ +Add a `link_to_project` flag that will update the package project_urls in the setup.cfg with a link to the source code for the package. \ No newline at end of file diff --git a/docs/Versioning.md b/docs/Versioning.md index 3651b8f..ba4f53e 100644 --- a/docs/Versioning.md +++ b/docs/Versioning.md @@ -46,13 +46,14 @@ The following settings are supported: The setup.cfg go under the `screwdrivercd.verison` setting. -| Setting | Default Value | Description | -| ------------- | ------------------ | ----------------------------------------------------------------------- | -| version_type | git_revision_count | The versioning format to generate, choices: | -| | | git_revision_count - Update the last digit the number of git revisions | -| | | sdv4_sd_build - Update the last digit with the value of the SD_BUILD environment variable | -| | | utc_date - Generate a version based on the date, format: year.monthday.hourminutesecond | -| | | sdv4_date - Generate a version based on the date and SD_BUILD environment variable, format: year.month.SD_BUILD | +| Setting | Default Value | Description | +|-----------------|--------------------|-----------------------------------------------------------------------------------------------------------------| +| version_type | git_revision_count | The versioning format to generate, choices: | +| | | git_revision_count - Update the last digit the number of git revisions | +| | | sdv4_sd_build - Update the last digit with the value of the SD_BUILD environment variable | +| | | utc_date - Generate a version based on the date, format: year.monthday.hourminutesecond | +| | | sdv4_date - Generate a version based on the date and SD_BUILD environment variable, format: year.month.SD_BUILD | + | link_to_project | False | If set to True, a link to the source repo will be added to [metadata]project_urls in setup.cfg | ### Example diff --git a/screwdriver.yaml b/screwdriver.yaml index 46921b3..6caa005 100644 --- a/screwdriver.yaml +++ b/screwdriver.yaml @@ -21,10 +21,6 @@ jobs: - postinstall_dependencies: $BASE_PYTHON -m pip install -U . requires: [~commit, ~pr] - validate_dependencies: - template: python/validate_dependencies - requires: [~commit, ~pr] - validate_lint: template: python/validate_lint requires: [~commit, ~pr] @@ -51,8 +47,7 @@ jobs: version: template: python/generate_version requires: [ - validate_test, validate_lint, validate_codestyle, validate_dependencies, validate_security, - # validate_type + validate_test, validate_lint, validate_codestyle, validate_security ] publish_test_pypi: diff --git a/setup.cfg b/setup.cfg index cc79116..665343e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] author = Verizon Media Python Platform Team -author_email = python@verizonmedia.com +author_email = python@yahooinc.com classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers diff --git a/src/screwdrivercd/documentation/cli.py b/src/screwdrivercd/documentation/cli.py index 48a652e..24fd75b 100644 --- a/src/screwdrivercd/documentation/cli.py +++ b/src/screwdrivercd/documentation/cli.py @@ -56,5 +56,5 @@ def main(): # pragma: no cover rc = 1 if is_pull_request(): status = 'SUCCESS' if rc == 0 else 'FAILURE' - update_job_status(status=status, message=f'{operation} {", ".join(documentation_formats)} documentation') + # update_job_status(status=status, message=f'{operation} {", ".join(documentation_formats)} documentation') return rc diff --git a/src/screwdrivercd/version/arguments.py b/src/screwdrivercd/version/arguments.py index 456d85a..395234f 100644 --- a/src/screwdrivercd/version/arguments.py +++ b/src/screwdrivercd/version/arguments.py @@ -36,6 +36,14 @@ def get_config_default(key, default=None, setup_cfg_filename='setup.cfg'): return default +def get_bool_equivalent(key) -> bool: + """ Get the equivalent bool value for a string key in the config file. """ + + if isinstance(key, str) and key.lower() in ['false', '0', 'off']: + return False + return True + + def parse_arguments(): """ Parse the command line arguments @@ -47,10 +55,10 @@ def parse_arguments(): """ version_type = get_config_default('version_type', default='default') update_meta = get_config_default('update_screwdriver_meta', default='false') - if isinstance(update_meta, str) and update_meta.lower() in ['false', '0', 'off']: - update_meta = False - else: - update_meta = True + update_meta = get_bool_equivalent(update_meta) + link_to_project = get_config_default('link_to_project', default='false') + link_to_project = get_bool_equivalent(link_to_project) + version_choices = list(versioners.keys()) if version_type not in version_choices: raise VersionError(f'The version_type in the [screwdrivercd.version] section of setup.cfg has an invalid version type of {version_type!r}') @@ -60,5 +68,6 @@ def parse_arguments(): parser.add_argument('--version_type', default=version_type, choices=version_choices, help='Type of version number to generate') parser.add_argument('--ignore_meta', default=False, action='store_true', help='Ignore the screwdriver v4 metadata') parser.add_argument('--update_meta', default=update_meta, action='store_true', help='Update the screwdriver v4 metadata with the new version') + parser.add_argument('--link_to_project', default=link_to_project, action='store_true', help='Add/update link to source project tree for current package version') result = parser.parse_args() return result diff --git a/src/screwdrivercd/version/cli.py b/src/screwdrivercd/version/cli.py index cc87b5a..79a9ca5 100644 --- a/src/screwdrivercd/version/cli.py +++ b/src/screwdrivercd/version/cli.py @@ -24,7 +24,7 @@ def main(): if args.force_update or setupcfg_has_metadata(): versioner = versioners[args.version_type] print(f'Updating version using the {versioner.name} version plugin', flush=True) - version = versioner(ignore_meta_version=args.ignore_meta, update_sdv4_meta=args.update_meta) + version = versioner(ignore_meta_version=args.ignore_meta, update_sdv4_meta=args.update_meta, link_to_project=args.link_to_project) version.update_setup_cfg_metadata() if args.update_meta: diff --git a/src/screwdrivercd/version/version_types.py b/src/screwdrivercd/version/version_types.py index 5a9112d..470f0a5 100644 --- a/src/screwdrivercd/version/version_types.py +++ b/src/screwdrivercd/version/version_types.py @@ -13,7 +13,7 @@ LOG = logging.getLogger(__name__) -class Version(): +class Version: """ Base Screwdriver Versioning class """ @@ -22,12 +22,13 @@ class Version(): setup_cfg_filename: str = 'setup.cfg' _meta_version: str = '' - def __init__(self, setup_cfg_filename=None, ignore_meta_version: bool=False, update_sdv4_meta: bool=True, meta_command: str='meta'): + def __init__(self, setup_cfg_filename=None, ignore_meta_version: bool = False, update_sdv4_meta: bool = True, meta_command: str = 'meta', link_to_project: bool = False): if setup_cfg_filename: # pragma: no cover self.setup_cfg_filename = setup_cfg_filename self.meta_command = meta_command self.ignore_meta_version = ignore_meta_version self.update_sdv4_meta = update_sdv4_meta + self.link_to_project = link_to_project def __repr__(self): return repr(self.version) @@ -67,18 +68,53 @@ def generate(self): """ return self.read_setup_version() + def get_link_to_project_using_hash(self): + """ + Generate and return link to build-triggering commit using its SHA hash + """ + scm_url = os.environ.get('SCM_URL', '') + sha = os.environ.get('SD_BUILD_SHA', '') + if self.link_to_project and scm_url and sha: + if scm_url.startswith('git@'): + hostname = scm_url.split(':')[0].split('@')[1] + path = ':'.join(scm_url.split(':')[1:]) + return f'https://{hostname}/{path}/tree/{sha}' + else: + return f'{scm_url}/tree/{sha}' + return '' + def update_setup_cfg_metadata(self): """ Update the version value in the setup.cfg file """ if not self.version: # pragma: no cover return + link_to_project = self.get_link_to_project_using_hash() config = configparser.ConfigParser() config.read(self.setup_cfg_filename) if 'metadata' not in config.sections(): config['metadata'] = {} config['metadata']['version'] = self.version + if link_to_project: + project_urls_str = config['metadata'].get('project_urls', '') + project_urls_dict = {} + if project_urls_str: + for entry in project_urls_str.split(os.linesep): + entry = entry.strip() + if '=' in entry: + key, value = entry.split('=') + key = key.strip() + value = value.strip() + project_urls_dict[key] = value + + project_urls_dict['Source'] = link_to_project + + project_urls_str = '\n' + for key, value in project_urls_dict.items(): + project_urls_str += f'{key} = {value}\n' + + config['metadata']['project_urls'] = project_urls_str.rstrip('\n') with open(self.setup_cfg_filename, 'w') as config_file_handle: config.write(config_file_handle) @@ -187,7 +223,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def revision_value(self): # pragma: no cover - "Method to return a newly generatated revision value" + """Method to return a newly generated revision value""" return self.default_revision_value def generate(self): # pragma: no cover @@ -245,6 +281,7 @@ class VersionSDV4Build(VersionUpdateRevision): Each new screwdriver job run will increment the revision number. """ name = 'sdv4_SD_BUILD' + def revision_value(self): revision = os.environ.get('SD_BUILD', None) if not revision: @@ -305,6 +342,6 @@ def generate(self): } # Make sure the versioners are listed all lowercase to make identifying them easier -for key, value in list(versioners.items()): +for key, value in list(versioners.items()): if key.lower() not in versioners.keys(): versioners[key.lower()] = value diff --git a/tests/test_validators.py b/tests/test_validators.py index a03ab27..6640b89 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -5,7 +5,6 @@ import sys from json import dumps -from unittest import skip from screwdrivercd.packaging.build_python import build_sdist_package, build_wheel_packages from screwdrivercd.utility.environment import standard_directories diff --git a/tests/test_versioners.py b/tests/test_versioners.py index 3821b5d..65e054d 100644 --- a/tests/test_versioners.py +++ b/tests/test_versioners.py @@ -11,16 +11,90 @@ from screwdrivercd.version.version_types import versioners, Version, VersionGitRevisionCount, VersionSDV4Build, VersionDateSDV4Build, VersionUTCDate, VersionManualUpdate +existing_project_url_config = { + 'setup.cfg': b""" +[metadata] +author = Yahoo Python Platform Team +author_email = python@verizonmedia.com +name=mypyvalidator +project_urls = + Documentation = https://yahoo.github.io/mypyvalidator/ + Change Log = https://yahoo.github.io/python-screwdrivercd/changelog/ + CI Pipeline = https://cd.screwdriver.cd/pipelines/1 + Source = https://github.com/yahoo/mypyvalidator/tree/a5c3785ed8d6a35868bc169f07e40e889087fd2e +version=0.0.0 + +[options] +packages = + mypyvalidator + +package_dir = + =src +""" +} + + class TestVersioners(ScrewdriverTestCase): environ_keys = { 'BASE_PYTHON', 'PACKAGE_DIR', 'PACKAGE_DIRECTORY', 'SD_ARTIFACTS_DIR', 'SD_BUILD', 'SD_BUILD_ID', - 'SD_PULL_REQUEST', + 'SD_PULL_REQUEST', 'SCM_URL', 'SD_BUILD_SHA', } def test__version__read_setup_version__no_version(self): version = Version(ignore_meta_version=True).read_setup_version() self.assertEqual(version, Version.default_version) + def test__version__get_link_to_project_using_hash__unset_env_variables(self): + link = Version(ignore_meta_version=True, link_to_project=True).get_link_to_project_using_hash() + self.assertEqual(link, '') + + def test__version__get_link_to_project_using_hash__set_and_unset_env_variables(self): + os.environ['SD_BUILD_SHA'] = 'a5c3785ed8d6a35868bc169f07e40e889087fd2e' + link = Version(ignore_meta_version=True, link_to_project=True).get_link_to_project_using_hash() + self.assertEqual(link, '') + + def test__version__get_link_to_project_using_hash__set_env_variables__https_git(self): + os.environ['SCM_URL'] = 'https://github.com/org/project' + os.environ['SD_BUILD_SHA'] = 'a5c3785ed8d6a35868bc169f07e40e889087fd2e' + ver = Version(ignore_meta_version=True, link_to_project=True) + link = ver.get_link_to_project_using_hash() + self.assertEqual(link, 'https://github.com/org/project/tree/a5c3785ed8d6a35868bc169f07e40e889087fd2e') + + ver.update_setup_cfg_metadata() + self.assertTrue(os.path.exists('setup.cfg')) + with open('setup.cfg') as fh: + setup_cfg = fh.read() + self.assertIn(link, setup_cfg) + + def test__version__get_link_to_project_using_hash__set_env_variables__https_git__existing_project_urls(self): + os.environ['SCM_URL'] = 'https://github.com/org/project' + os.environ['SD_BUILD_SHA'] = 'a5c3785ed8d6a35868bc169f07e40e889087fd2e' + self.write_config_files(existing_project_url_config) + + ver = Version(ignore_meta_version=True, link_to_project=True) + link = ver.get_link_to_project_using_hash() + self.assertEqual(link, 'https://github.com/org/project/tree/a5c3785ed8d6a35868bc169f07e40e889087fd2e') + + ver.update_setup_cfg_metadata() + self.assertTrue(os.path.exists('setup.cfg')) + with open('setup.cfg') as fh: + setup_cfg = fh.read() + self.assertIn(link, setup_cfg) + self.assertIn('Documentation = https://yahoo.github.io/mypyvalidator/', setup_cfg) + + def test__version__get_link_to_project_using_hash__set_env_variables__ssh_git(self): + os.environ['SCM_URL'] = 'git@github.com:org/project' + os.environ['SD_BUILD_SHA'] = 'a5c3785ed8d6a35868bc169f07e40e889087fd2e' + ver = Version(ignore_meta_version=True, link_to_project=True) + link = ver.get_link_to_project_using_hash() + self.assertEqual(link, 'https://github.com/org/project/tree/a5c3785ed8d6a35868bc169f07e40e889087fd2e') + + ver.update_setup_cfg_metadata() + self.assertTrue(os.path.exists('setup.cfg')) + with open('setup.cfg') as fh: + setup_cfg = fh.read() + self.assertIn(link, setup_cfg) + def test__manual_version_update(self): with NamedTemporaryFile('w') as file: setup_cfg_content = '[metadata]\nversion = 1.0.5' @@ -77,6 +151,7 @@ def test__sdv4_SD_BUILD__set(self): def test__sdv4_SD_BUILD__PR__unset(self): self.delkeys(['SD_BUILD', 'SD_BUILD_ID', 'SD_PULL_REQUEST']) + os.environ['SD_PULL_REQUEST'] = '1' with self.assertRaises(VersionError): version = str(VersionSDV4Build(ignore_meta_version=True, log_errors=False)) diff --git a/tox.ini b/tox.ini index 3e425d7..ddf0e18 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,7 @@ package_dir = src/screwdrivercd package_name = screwdrivercd [tox] -envlist = py36,py37,py38,py39 +envlist = py37,py38,py39,py310 skip_missing_interpreters = true [testenv]