123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932 |
- #!/usr/bin/env python3
- #
- # Copyright 2018-2022 Elyra Authors
- #
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- #
- import argparse
- import elyra
- import elyra._version
- import git
- import io
- import os
- import re
- import shutil
- import subprocess
- import sys
- from datetime import datetime
- from pathlib import Path
- from types import SimpleNamespace
- config: SimpleNamespace
- VERSION_REG_EX = r"(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(\.(?P<pre_release>[a-z]+)(?P<build>\d+))?"
- DEFAULT_GIT_ORG = "elyra-ai"
- DEFAULT_GIT_BRANCH = "master"
- DEFAULT_BUILD_DIR = "build/release"
- class DependencyException(Exception):
- """Error if dependency is missing"""
- class MissingReleaseArtifactException(Exception):
- """Error if an artifact being released is not available"""
- class UpdateVersionException(Exception):
- """Error if the old version is invalid or cannot be found, or if there's a duplicate version"""
- def check_run(args, cwd=os.getcwd(), capture_output=True, env=None, shell=False) -> subprocess.CompletedProcess:
- try:
- return subprocess.run(args, cwd=cwd, capture_output=capture_output, check=True)
- except subprocess.CalledProcessError as ex:
- raise RuntimeError(f'Error executing process: {ex.stderr.decode("unicode_escape")}') from ex
- def check_output(args, cwd=os.getcwd(), env=None, shell=False) -> str:
- response = check_run(args, cwd, capture_output=True, env=env, shell=shell)
- return response.stdout.decode("utf-8").replace("\n", "")
- def dependency_exists(command) -> bool:
- """Returns true if a command exists on the system"""
- try:
- check_run(["which", command])
- except subprocess.CalledProcessError:
- return False
- return True
- def sed(file: str, pattern: str, replace: str) -> None:
- """Perform regex substitution on a given file"""
- try:
- check_run(["sed", "-i", "", "-e", f"s#{pattern}#{replace}#g", file], capture_output=False)
- except Exception as ex:
- raise RuntimeError(f"Error processing updated to file {file}: ") from ex
- def validate_dependencies() -> None:
- """Error if a dependency is missing or invalid"""
- if not dependency_exists("git"):
- raise DependencyException("Please install git https://git-scm.com/downloads")
- if not dependency_exists("node"):
- raise DependencyException("Please install node.js v16+ https://nodejs.org/")
- if not dependency_exists("yarn"):
- raise DependencyException("Please install yarn https://classic.yarnpkg.com/")
- if not dependency_exists("twine"):
- raise DependencyException("Please install twine https://twine.readthedocs.io/en/latest/#installation")
- def validate_environment() -> None:
- """Validate environment configurations are valid"""
- pass
- def update_version_to_release() -> None:
- global config
- old_version = config.old_version
- old_npm_version = config.old_npm_version
- new_version = config.new_version
- new_npm_version = config.new_npm_version
- try:
- # Update backend version
- sed(_source(".bumpversion.cfg"), rf"^current_version* =* {old_version}", f"current_version = {new_version}")
- sed(_source("elyra/_version.py"), rf'^__version__* =* "{old_version}"', f'__version__ = "{new_version}"'),
- sed(_source("README.md"), rf"elyra {old_version}", f"elyra {new_version}")
- sed(_source("docs/source/getting_started/installation.md"), rf"elyra {old_version}", f"elyra {new_version}")
- # Update docker related tags
- sed(_source("Makefile"), r"^TAG:=dev", f"TAG:={new_version}")
- sed(_source("README.md"), r"elyra:dev ", f"elyra:{new_version} ")
- if config.rc is None and config.beta is None:
- # Update the stable version Binder link
- sed(_source("README.md"), r"/v[0-9].[0-9].[0-9]?", f"/v{new_version}?")
- sed(_source("etc/docker/kubeflow/README.md"), r"kf-notebook:dev", f"kf-notebook:{new_version}")
- sed(_source("docs/source/getting_started/installation.md"), r"elyra:dev ", f"elyra:{new_version} ")
- sed(_source("docs/source/getting_started/installation.md"), r"/v[0-9].[0-9].[0-9]?", f"/v{new_version}?")
- sed(_source("docs/source/recipes/configure-airflow-as-a-runtime.md"), r"master", f"{config.tag}")
- sed(_source("docs/source/recipes/deploying-elyra-in-a-jupyterhub-environment.md"), r"dev", f"{new_version}")
- sed(_source("docs/source/recipes/using-elyra-with-kubeflow-notebook-server.md"), r"master", f"{new_version}")
- # Update UI component versions
- sed(_source("README.md"), rf"v{old_npm_version}", f"v{new_version}")
- sed(_source("docs/source/getting_started/installation.md"), rf"v{old_npm_version}", f"v{new_version}")
- sed(
- _source("packages/theme/src/index.ts"),
- r"https://elyra.readthedocs.io/en/latest/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- )
- sed(
- _source("elyra/cli/pipeline_app.py"),
- r"https://elyra.readthedocs.io/en/latest/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- )
- # Update documentation version for elyra-metadata cli help
- sed(
- _source("elyra/metadata/metadata_app_utils.py"),
- r"https://elyra.readthedocs.io/en/latest/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- )
- sed(
- _source("packages/pipeline-editor/src/EmptyPipelineContent.tsx"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("packages/pipeline-editor/src/PipelineEditorWidget.tsx"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- # update documentation references in schema definitions
- # located in elyra/metadata/schemas/
- sed(
- _source("elyra/metadata/schemas/url-catalog.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("elyra/metadata/schemas/local-directory-catalog.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("elyra/metadata/schemas/local-file-catalog.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("elyra/metadata/schemas/airflow.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("elyra/metadata/schemas/kfp.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("elyra/metadata/schemas/code-snippet.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- sed(
- _source("elyra/metadata/schemas/runtime-image.json"),
- r"https://elyra.readthedocs.io/en/latest/user_guide/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- )
- # Update documentation references in documentation
- sed(
- _source("docs/source/user_guide/jupyterlab-interface.md"),
- r"https://elyra.readthedocs.io/en/latest/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- )
- # Update GitHub references in documentation
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/master/etc/kfp/pip.conf",
- rf"elyra-ai/elyra/v{new_version}/etc/kfp/pip.conf/",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/master/elyra/kfp/bootstrapper.py",
- rf"elyra-ai/elyra/v{new_version}/elyra/kfp/bootstrapper.py",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/master/elyra/airflow/bootstrapper.py",
- rf"elyra-ai/elyra/v{new_version}/elyra/airflow/bootstrapper.py",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/master/etc/generic/requirements-elyra-py37.txt",
- rf"elyra-ai/elyra/v{new_version}/etc/generic/requirements-elyra-py37.txt",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/master/etc/generic/requirements-elyra.txt",
- rf"elyra-ai/elyra/v{new_version}/etc/generic/requirements-elyra.txt",
- )
- check_run(
- ["lerna", "version", new_npm_version, "--no-git-tag-version", "--no-push", "--yes", "--exact"],
- cwd=config.source_dir,
- )
- check_run(["yarn", "version", "--new-version", new_npm_version, "--no-git-tag-version"], cwd=config.source_dir)
- except Exception as ex:
- raise UpdateVersionException from ex
- def update_version_to_dev() -> None:
- global config
- new_version = config.new_version
- dev_version = config.dev_version
- dev_npm_version = config.dev_npm_version
- try:
- # Update backend version
- sed(_source(".bumpversion.cfg"), rf"^current_version* =* {new_version}", f"current_version = {dev_version}")
- sed(_source("elyra/_version.py"), rf'^__version__* =* "{new_version}"', f'__version__ = "{dev_version}"')
- sed(_source("README.md"), rf"elyra {new_version}", f"elyra {dev_version}")
- sed(_source("docs/source/getting_started/installation.md"), rf"elyra {new_version}", f"elyra {dev_version}")
- # Update docker related tags
- sed(_source("Makefile"), rf"^TAG:={new_version}", "TAG:=dev")
- sed(_source("README.md"), rf"elyra:{new_version} ", "elyra:dev ")
- sed(_source("etc/docker/kubeflow/README.md"), rf"kf-notebook:{new_version}", "kf-notebook:dev")
- # this does not goes back to dev
- # sed(source('README.md'), rf"/v[0-9].[0-9].[0-9]", "/v{dev_version}")
- sed(_source("docs/source/getting_started/installation.md"), rf"elyra:{new_version} ", "elyra:dev ")
- # this does not goes back to dev
- # sed(source('docs/source/getting_started/installation.md'), rf"/v[0-9].[0-9].[0-9]", "/v{dev_version}")
- sed(_source("docs/source/recipes/configure-airflow-as-a-runtime.md"), rf"{config.tag}", "master")
- sed(_source("docs/source/recipes/deploying-elyra-in-a-jupyterhub-environment.md"), rf"{new_version}", "dev")
- sed(_source("docs/source/recipes/using-elyra-with-kubeflow-notebook-server.md"), rf"{new_version}", "master")
- # Update UI component versions
- sed(_source("README.md"), rf"extension v{new_version}", f"extension v{dev_npm_version}")
- sed(
- _source("docs/source/getting_started/installation.md"),
- rf"extension v{new_version}",
- f"extension v{dev_npm_version}",
- )
- sed(
- _source("packages/theme/src/index.ts"),
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- rf"https://elyra.readthedocs.io/en/latest/",
- )
- # Update documentation references in documentation
- sed(
- _source("docs/source/user_guide/jupyterlab-interface.md"),
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- r"https://elyra.readthedocs.io/en/latest/",
- )
- sed(
- _source("elyra/cli/pipeline_app.py"),
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- rf"https://elyra.readthedocs.io/en/latest/",
- )
- # Update documentation version for elyra-metadata cli help
- sed(
- _source("elyra/metadata/metadata_app_utils.py"),
- rf"https://elyra.readthedocs.io/en/latest/",
- rf"https://elyra.readthedocs.io/en/v{new_version}/",
- )
- sed(
- _source("packages/pipeline-editor/src/EmptyPipelineContent.tsx"),
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- rf"https://elyra.readthedocs.io/en/latest/user_guide/",
- )
- sed(
- _source("packages/pipeline-editor/src/PipelineEditorWidget.tsx"),
- rf"https://elyra.readthedocs.io/en/v{new_version}/user_guide/",
- rf"https://elyra.readthedocs.io/en/latest/user_guide/",
- )
- # Update GitHub references in documentation
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/v{new_version}/etc/kfp/pip.conf",
- rf"elyra-ai/elyra/master/etc/kfp/pip.conf/",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/v{new_version}/elyra/kfp/bootstrapper.py",
- rf"elyra-ai/elyra/master/elyra/kfp/bootstrapper.py",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/v{new_version}/elyra/airflow/bootstrapper.py",
- rf"elyra-ai/elyra/master/elyra/airflow/bootstrapper.py",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/v{new_version}/etc/generic/requirements-elyra-py37.txt",
- rf"elyra-ai/elyra/master/etc/generic/requirements-elyra-py37.txt",
- )
- sed(
- _source("docs/source/recipes/running-elyra-in-air-gapped-environment.md"),
- r"elyra-ai/elyra/v{new_version}/etc/generic/requirements-elyra.txt",
- rf"elyra-ai/elyra/master/etc/generic/requirements-elyra.txt",
- )
- check_run(
- ["lerna", "version", dev_npm_version, "--no-git-tag-version", "--no-push", "--yes", "--exact"],
- cwd=config.source_dir,
- )
- check_run(["yarn", "version", "--new-version", dev_npm_version, "--no-git-tag-version"], cwd=config.source_dir)
- except Exception as ex:
- raise UpdateVersionException from ex
- def _source(file: str) -> str:
- global config
- return os.path.join(config.source_dir, file)
- def checkout_code() -> None:
- global config
- print("-----------------------------------------------------------------")
- print("-------------------- Retrieving source code ---------------------")
- print("-----------------------------------------------------------------")
- print(f"Cloning repository: {config.git_url}")
- if os.path.exists(config.work_dir):
- print(f"Removing working directory: {config.work_dir}")
- shutil.rmtree(config.work_dir)
- print(f"Creating working directory: {config.work_dir}")
- os.makedirs(config.work_dir)
- print(f"Cloning : {config.git_url} to {config.work_dir}")
- check_run(["git", "clone", config.git_url, "-b", config.git_branch], cwd=config.work_dir)
- check_run(["git", "config", "user.name", config.git_user_name], cwd=config.source_dir)
- check_run(["git", "config", "user.email", config.git_user_email], cwd=config.source_dir)
- print("")
- def build_release():
- global config
- print("-----------------------------------------------------------------")
- print("----------------------- Building Release ------------------------")
- print("-----------------------------------------------------------------")
- check_run(["make", "release"], cwd=config.source_dir, capture_output=False)
- print("")
- def build_server():
- global config
- print("-----------------------------------------------------------------")
- print("------------------------ Building Server ------------------------")
- print("-----------------------------------------------------------------")
- # update project name
- sed(_source("setup.py"), r'name="elyra"', 'name="elyra-server"')
- sed(
- _source("setup.py"),
- r'description="Elyra provides AI Centric extensions to JupyterLab"',
- 'description="The elyra-server package provides common core libraries and functions that are required by '
- "Elyra's individual extensions. Note: Installing this package alone will not enable the use of Elyra. "
- "Please install the 'elyra' package instead. e.g. pip install elyra[all]\"",
- )
- # build server wheel
- check_run(["make", "build-server"], cwd=config.source_dir, capture_output=False)
- # revert project name
- check_run(["git", "reset", "--hard"], cwd=config.source_dir, capture_output=False)
- print("")
- def show_release_artifacts():
- global config
- dist_dir = os.path.join(config.source_dir, "dist")
- print("-----------------------------------------------------------------")
- print("------------------------ Release Files --------------------------")
- print("-----------------------------------------------------------------")
- print("")
- print(f"Location \t {dist_dir}")
- print("")
- check_run(["ls", "-la", dist_dir], capture_output=False)
- print("")
- def copy_extension_dir(extension: str, work_dir: str) -> None:
- global config
- extension_package_source_dir = os.path.join(config.source_dir, "dist/labextensions/@elyra", extension)
- extension_package_dest_dir = os.path.join(work_dir, "dist/labextensions/@elyra", extension)
- os.makedirs(os.path.dirname(extension_package_dest_dir), exist_ok=True)
- shutil.copytree(extension_package_source_dir, extension_package_dest_dir)
- def generate_changelog() -> None:
- global config
- print("-----------------------------------------------------------------")
- print("--------------------- Preparing Changelog -----------------------")
- print("-----------------------------------------------------------------")
- changelog_path = os.path.join(config.source_dir, "docs/source/getting_started/changelog.md")
- changelog_backup_path = os.path.join(config.source_dir, "docs/source/getting_started/changelog.bak")
- if os.path.exists(changelog_backup_path):
- os.remove(changelog_backup_path)
- shutil.copy(changelog_path, changelog_backup_path)
- repo = git.Repo(config.source_dir)
- # define static header
- header_lines = [
- "# Changelog\n",
- "\n",
- "A summary of new feature highlights is located on the [GitHub release page](https://github.com/elyra-ai/elyra/releases).\n",
- "\n",
- ]
- # Start generating the release header on top of the changelog
- with io.open(changelog_path, "r+") as changelog:
- # add static header
- for line in header_lines:
- changelog.write(line)
- # add release section
- changelog.write(f'## Release {config.new_version} - {datetime.now().strftime("%m/%d/%Y")}\n')
- changelog.write("\n")
- start = 0
- page_size = 10
- continue_paginating = True
- while continue_paginating:
- # paginate the list of commits until it finds the begining of release changes
- # which is denominated by a commit titled 'Prepare for next development iteration'
- commits = list(repo.iter_commits(max_count=page_size, skip=start))
- start += page_size
- for commit in commits:
- # for each commit, get it's title and prepare a changelog
- # entry linking to the related pull request
- commit_title = commit.message.splitlines()[0]
- # commit_hash = commit.hexsha
- # print(f'>>> {commit_hash} - {commit_title}')
- if commit_title != "Prepare for next development iteration":
- pr_string = ""
- pr = re.findall("\(#(.*?)\)", commit_title)
- if pr:
- commit_title = re.sub("\(#(.*?)\)", "", commit_title).strip()
- pr_string = f" - [#{pr[0]}](https://github.com/elyra-ai/elyra/pull/{pr[0]})"
- changelog_entry = f"- {commit_title}{pr_string}\n"
- changelog.write(changelog_entry)
- else:
- # here it found the first commit of the release
- # changelog for the release is done
- # exit the loop
- continue_paginating = False
- break
- # copy the remaining changelog at the bottom of the new content
- with io.open(changelog_backup_path) as old_changelog:
- # ignore existing static header
- line = old_changelog.readline()
- while line and line.startswith("## Release") is False:
- line = old_changelog.readline()
- changelog.write("\n")
- while line:
- changelog.write(line)
- line = old_changelog.readline()
- def prepare_extensions_release() -> None:
- global config
- print("-----------------------------------------------------------------")
- print("--------------- Preparing Individual Extensions -----------------")
- print("-----------------------------------------------------------------")
- extensions = {
- "elyra-code-snippet-extension": SimpleNamespace(
- packages=["code-snippet-extension", "metadata-extension", "theme-extension"],
- description=f"The Code Snippet editor extension adds support for reusable code fragments, "
- f"making programming in JupyterLab more efficient by reducing repetitive work. "
- f"See https://elyra.readthedocs.io/en/{config.new_version}/user_guide/code-snippets.html",
- ),
- "elyra-code-viewer-extension": SimpleNamespace(
- packages=["code-viewer-extension"],
- description="The Code Viewer extension adds the ability to display a given chunk of code "
- "(string) in a transient read-only 'editor' without needing to create a file."
- "This extension will be available in JupyterLab core in a near future release and removed "
- "from Elyra as a standalone extension.",
- ),
- "elyra-pipeline-editor-extension": SimpleNamespace(
- packages=["code-viewer-extension", "pipeline-editor-extension", "metadata-extension", "theme-extension"],
- description=f"The Visual Editor Pipeline extension is used to build AI pipelines from notebooks, "
- f"Python scripts and R scripts, simplifying the conversion of multiple notebooks "
- f"or script files into batch jobs or workflows."
- f"See https://elyra.readthedocs.io/en/{config.new_version}/user_guide/pipelines.html",
- ),
- "elyra-python-editor-extension": SimpleNamespace(
- packages=["python-editor-extension", "metadata-extension", "theme-extension"],
- description=f"The Python Script editor extension contains support for Python files, "
- f"which can take advantage of the Hybrid Runtime Support enabling users to "
- f"locally edit .py scripts and execute them against local or cloud-based resources."
- f"See https://elyra.readthedocs.io/en/{config.new_version}/user_guide/enhanced-script-support.html",
- ),
- "elyra-r-editor-extension": SimpleNamespace(
- packages=["r-editor-extension", "metadata-extension", "theme-extension"],
- description=f"The R Script editor extension contains support for R files, which can take "
- f"advantage of the Hybrid Runtime Support enabling users to locally edit .R scripts "
- f"and execute them against local or cloud-based resources."
- f"See https://elyra.readthedocs.io/en/{config.new_version}/user_guide/enhanced-script-support.html",
- ),
- }
- for extension in extensions:
- extension_source_dir = os.path.join(config.work_dir, extension)
- print(f"Preparing extension : {extension} at {extension_source_dir}")
- # copy extension package template to working directory
- if os.path.exists(extension_source_dir):
- print(f"Removing working directory: {config.source_dir}")
- shutil.rmtree(extension_source_dir)
- check_run(["mkdir", "-p", extension_source_dir], cwd=config.work_dir)
- print(f'Copying : {_source("etc/templates/setup.py")} to {extension_source_dir}')
- check_run(["cp", _source("etc/templates/setup.py"), extension_source_dir], cwd=config.work_dir)
- # update template
- setup_file = os.path.join(extension_source_dir, "setup.py")
- sed(setup_file, "{{package-name}}", extension)
- sed(setup_file, "{{version}}", config.new_version)
- sed(setup_file, "{{data - files}}", re.escape("('share/jupyter/labextensions', 'dist/labextensions', '**')"))
- sed(setup_file, "{{install - requires}}", f"'elyra-server=={config.new_version}',")
- sed(setup_file, "{{description}}", f"'{extensions[extension].description}'")
- for dependency in extensions[extension].packages:
- copy_extension_dir(dependency, extension_source_dir)
- # build extension
- check_run(["python", "setup.py", "bdist_wheel", "sdist"], cwd=extension_source_dir)
- print("")
- def prepare_runtime_extensions_package_release() -> None:
- global config
- print("-----------------------------------------------------------------")
- print("---------------- Preparing Individual Packages ------------------")
- print("-----------------------------------------------------------------")
- packages = {"kfp-notebook": ["kfp>=1.6.3"], "airflow-notebook": ["pygithub", "black"]}
- packages_source = {"kfp-notebook": "kfp", "airflow-notebook": "airflow"}
- for package in packages:
- package_source_dir = os.path.join(config.work_dir, package)
- print(f"Preparing package : {package} at {package_source_dir}")
- # copy extension package template to working directory
- if os.path.exists(package_source_dir):
- print(f"Removing working directory: {config.source_dir}")
- shutil.rmtree(package_source_dir)
- check_run(["mkdir", "-p", package_source_dir], cwd=config.work_dir)
- print(f'Copying : {_source("etc/templates/setup.py")} to {package_source_dir}')
- check_run(["cp", _source("etc/templates/setup.py"), package_source_dir], cwd=config.work_dir)
- # update template
- setup_file = os.path.join(package_source_dir, "setup.py")
- sed(setup_file, "{{package-name}}", package)
- sed(setup_file, "{{version}}", config.new_version)
- # no data files
- sed(setup_file, "{{data - files}}", "")
- # prepare package specific dependencies
- requires = ""
- for dependency in packages[package]:
- requires += f"'{dependency}',"
- sed(setup_file, "{{install - requires}}", requires)
- # copy source files
- source_dir = os.path.join(config.source_dir, "elyra", packages_source[package])
- dest_dir = os.path.join(package_source_dir, "elyra", packages_source[package])
- print(f"Copying package source from {source_dir} to {dest_dir}")
- Path(os.path.join(package_source_dir, "elyra")).mkdir(parents=True, exist_ok=True)
- shutil.copytree(source_dir, dest_dir)
- # build extension
- check_run(["python", "setup.py", "bdist_wheel", "sdist"], cwd=package_source_dir)
- print("")
- def prepare_changelog() -> None:
- """
- Prepare a release changelog
- """
- global config
- print(f"Generating changelog for release {config.new_version}")
- print("")
- # clone repository
- checkout_code()
- # generate changelog with new release list of commits
- generate_changelog()
- # commit
- check_run(
- ["git", "commit", "-a", "-m", f"Update changelog for release {config.new_version}"], cwd=config.source_dir
- )
- def prepare_release() -> None:
- """
- Prepare a release
- """
- global config
- print(f"Processing release from {config.old_version} to {config.new_version} ")
- print("")
- # clone repository
- checkout_code()
- # generate changelog with new release list of commits
- prepare_changelog()
- # Update to new release version
- update_version_to_release()
- # commit and tag
- check_run(["git", "commit", "-a", "-m", f"Release v{config.new_version}"], cwd=config.source_dir)
- check_run(["git", "tag", config.tag], cwd=config.source_dir)
- # server-only wheel
- build_server()
- # build release wheel and npm artifacts
- build_release()
- # show built release artifacts
- show_release_artifacts()
- # back to development
- update_version_to_dev()
- # commit
- check_run(["git", "commit", "-a", "-m", f"Prepare for next development iteration"], cwd=config.source_dir)
- # prepare extensions
- prepare_extensions_release()
- # prepare runtime extsnsions
- prepare_runtime_extensions_package_release()
- def publish_release(working_dir) -> None:
- global config
- files_to_publish = [
- f"{config.source_dir}/dist/elyra-{config.new_version}-py3-none-any.whl",
- f"{config.source_dir}/dist/elyra-{config.new_version}.tar.gz",
- f"{config.source_dir}/dist/elyra_server-{config.new_version}-py3-none-any.whl",
- f"{config.source_dir}/dist/elyra-server-{config.new_version}.tar.gz",
- f"{config.work_dir}/airflow-notebook/dist/airflow_notebook-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/airflow-notebook/dist/airflow-notebook-{config.new_version}.tar.gz",
- f"{config.work_dir}/kfp-notebook/dist/kfp_notebook-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/kfp-notebook/dist/kfp-notebook-{config.new_version}.tar.gz",
- f"{config.work_dir}/elyra-code-snippet-extension/dist/elyra_code_snippet_extension-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/elyra-code-snippet-extension/dist/elyra-code-snippet-extension-{config.new_version}.tar.gz",
- f"{config.work_dir}/elyra-code-viewer-extension/dist/elyra_code_viewer_extension-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/elyra-code-viewer-extension/dist/elyra-code-viewer-extension-{config.new_version}.tar.gz",
- f"{config.work_dir}/elyra-pipeline-editor-extension/dist/elyra_pipeline_editor_extension-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/elyra-pipeline-editor-extension/dist/elyra-pipeline-editor-extension-{config.new_version}.tar.gz",
- f"{config.work_dir}/elyra-python-editor-extension/dist/elyra_python_editor_extension-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/elyra-python-editor-extension/dist/elyra-python-editor-extension-{config.new_version}.tar.gz",
- f"{config.work_dir}/elyra-r-editor-extension/dist/elyra_r_editor_extension-{config.new_version}-py3-none-any.whl",
- f"{config.work_dir}/elyra-r-editor-extension/dist/elyra-r-editor-extension-{config.new_version}.tar.gz",
- ]
- print("-----------------------------------------------------------------")
- print("---------------------- Publishing to PyPI -----------------------")
- print("-----------------------------------------------------------------")
- # Validate all artifacts to be published are available
- for file in files_to_publish:
- if not os.path.exists(file):
- raise MissingReleaseArtifactException(f"Missing release file: {file}")
- # push files to PyPI
- for file in files_to_publish:
- print(f"Publishing: {file}")
- check_run(["twine", "upload", "--sign", file], cwd=working_dir)
- print("-----------------------------------------------------------------")
- print("--------------- Pushing Release and Tag to git ------------------")
- print("-----------------------------------------------------------------")
- # push release and tags to git
- print()
- print("Pushing release to git")
- check_run(["git", "push"], cwd=config.source_dir)
- print("Pushing release tag to git")
- check_run(["git", "push", "--tags"], cwd=config.source_dir)
- print("-----------------------------------------------------------------")
- print("--------------- Preparing to push npm packages ------------------")
- print("-----------------------------------------------------------------")
- # checkout the tag
- print()
- print(f"Checking out release tag {config.tag}")
- check_run(["git", "checkout", config.tag], cwd=config.source_dir)
- check_run(["git", "status"], cwd=config.source_dir)
- print("-----------------------------------------------------------------")
- print("-------------------- Pushing npm packages -----------------------")
- print("-----------------------------------------------------------------")
- # publish npm packages
- print()
- print(f"publishing npm packages")
- check_run(
- ["lerna", "publish", "--yes", "from-package", "--no-git-tag-version", "--no-verify-access", "--no-push"],
- cwd=config.source_dir,
- )
- def initialize_config(args=None) -> SimpleNamespace:
- if not args:
- raise ValueError("Invalid command line arguments")
- v = re.search(VERSION_REG_EX, elyra._version.__version__)
- configuration = {
- "goal": args.goal,
- "git_url": f"git@github.com:{args.org or DEFAULT_GIT_ORG}/elyra.git",
- "git_branch": args.branch or DEFAULT_GIT_BRANCH,
- "git_hash": "HEAD",
- "git_user_name": check_output(["git", "config", "user.name"]),
- "git_user_email": check_output(["git", "config", "user.email"]),
- "base_dir": os.getcwd(),
- "work_dir": os.path.join(os.getcwd(), DEFAULT_BUILD_DIR),
- "source_dir": os.path.join(os.getcwd(), DEFAULT_BUILD_DIR, "elyra"),
- "old_version": elyra._version.__version__,
- "old_npm_version": f"{v['major']}.{v['minor']}.{v['patch']}-dev",
- "new_version": args.version
- if (not args.rc or not str.isdigit(args.rc)) and (not args.beta or not str.isdigit(args.beta))
- else f"{args.version}rc{args.rc}"
- if args.rc
- else f"{args.version}b{args.beta}",
- "new_npm_version": args.version
- if (not args.rc or not str.isdigit(args.rc)) and (not args.beta or not str.isdigit(args.beta))
- else f"{args.version}-rc.{args.rc}"
- if args.rc
- else f"{args.version}-beta.{args.beta}",
- "rc": args.rc,
- "beta": args.beta,
- "dev_version": f"{args.dev_version}.dev0",
- "dev_npm_version": f"{args.dev_version}-dev",
- "tag": f"v{args.version}"
- if (not args.rc or not str.isdigit(args.rc)) and (not args.beta or not str.isdigit(args.beta))
- else f"v{args.version}rc{args.rc}"
- if args.rc
- else f"v{args.version}b{args.beta}",
- }
- global config
- config = SimpleNamespace(**configuration)
- def print_config() -> None:
- global config
- print("")
- print("-----------------------------------------------------------------")
- print("--------------------- Release configuration ---------------------")
- print("-----------------------------------------------------------------")
- print(f"Goal \t\t\t -> {config.goal}")
- print(f"Git URL \t\t -> {config.git_url}")
- print(f"Git Branch \t\t -> {config.git_branch}")
- print(f"Git reference \t\t -> {config.git_hash}")
- print(f"Git user \t\t -> {config.git_user_name}")
- print(f"Git user email \t\t -> {config.git_user_email}")
- print(f"Work dir \t\t -> {config.work_dir}")
- print(f"Source dir \t\t -> {config.source_dir}")
- print(f"Old Version \t\t -> {config.old_version}")
- print(f"Old NPM Version \t -> {config.old_npm_version}")
- print(f"New Version \t\t -> {config.new_version}")
- print(f"New NPN Version \t -> {config.new_npm_version}")
- if config.rc is not None:
- print(f"RC number \t\t -> {config.rc}")
- if config.beta is not None:
- print(f"Beta number \t\t -> {config.beta}")
- print(f"Dev Version \t\t -> {config.dev_version}")
- print(f"Dev NPM Version \t -> {config.dev_npm_version}")
- print(f"Release Tag \t\t -> {config.tag}")
- print("-----------------------------------------------------------------")
- print("")
- def print_help() -> str:
- return """create-release.py [ prepare | publish ] --version VERSION
-
- DESCRIPTION
- Creates Elyra release based on git commit hash or from HEAD.
-
- create release prepare-changelog --version 1.3.0 [--beta 0] [--rc 0]
- This will prepare the release changelog and make it ready for review on the release workdir.
- create-release.py prepare --version 1.3.0 --dev-version 1.4.0 [--beta 0] [--rc 0]
- This will prepare a release candidate, build it locally and make it ready for review on the release workdir.
-
- Note: that one can either use a beta or rc modifier for the release, but not both.
- create-release.py publish --version 1.3.0 [--beta 0] [--rc 0]
- This will build a previously prepared release, and publish the artifacts to public repositories.
-
- Required software dependencies for building and publishing a release:
- - Git
- - Node
- - Twine
- - Yarn
-
- Required configurations for publishing a release:
- - GPG with signing key configured
-
-
- """
- def main(args=None):
- """Perform necessary tasks to create and/or publish a new release"""
- parser = argparse.ArgumentParser(usage=print_help())
- parser.add_argument(
- "goal",
- help="Supported goals: {prepare-changelog | prepare | publish}",
- type=str,
- choices={"prepare-changelog", "prepare", "publish"},
- )
- parser.add_argument("--version", help="the new release version", type=str, required=True)
- parser.add_argument("--dev-version", help="the new development version", type=str, required=False)
- parser.add_argument("--beta", help="the release beta number", type=str, required=False)
- parser.add_argument("--rc", help="the release candidate number", type=str, required=False)
- parser.add_argument("--org", help="the github org or username to use", type=str, required=False)
- parser.add_argument("--branch", help="the branch name to use", type=str, required=False)
- args = parser.parse_args()
- # can't use both rc and beta parameters
- if args.beta and args.rc:
- print_help()
- sys.exit(1)
- global config
- try:
- # Validate all pre-requisites are available
- validate_dependencies()
- validate_environment()
- # Generate release config based on the provided arguments
- initialize_config(args)
- print_config()
- if config.goal == "prepare-changelog":
- prepare_changelog()
- print("")
- print("")
- print(f"Changelog for release version: {config.new_version} is ready for review at {config.source_dir}")
- print("After you are done, push the reviewed changelog to github.")
- print("")
- print("")
- elif config.goal == "prepare":
- if not args.dev_version:
- print_help()
- sys.exit()
- prepare_release()
- print("")
- print("")
- print(f"Release version: {config.new_version} is ready for review")
- print("After you are done, run the script again to [publish] the release.")
- print("")
- print("")
- elif args.goal == "publish":
- publish_release(working_dir=os.getcwd())
- else:
- print_help()
- sys.exit()
- except Exception as ex:
- raise RuntimeError(f"Error performing release {args.version}") from ex
- if __name__ == "__main__":
- main()
|