diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index c5a727ffdb..277248d2da 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -29,6 +29,21 @@ jobs: script: | const pr = context.payload.pull_request; const body = pr.body || ''; + + // List of users to skip description validation + // This should be automations where we don't care that much about the description format + const skipUsersPrefixes = [ + 'unity-renovate', + 'svc-' + ]; + + // If PR author is in the skip list, exit early + const author = pr.user.login; + console.log(`PR author: ${author}`); + if (skipUsersPrefixes.some(prefix => author.startsWith(prefix))) { + console.log(`Skipping PR description check for user: ${author}`); + return; + } // List of mandatory PR sections const requiredSections = [ diff --git a/.yamato/ngo-publish.yml b/.yamato/ngo-publish.yml index e3a73a87dc..81f9f57b3b 100644 --- a/.yamato/ngo-publish.yml +++ b/.yamato/ngo-publish.yml @@ -1,10 +1,10 @@ ngo_release_preparation: name: "NGO release preparation" agent: { type: Unity::VM, flavor: b1.small, image: package-ci/ubuntu-22.04:v4 } - #triggers: - # recurring: - # - branch: develop-2.0.0 # We make new releases from this branch - # frequency: "10 ? * 1" # Runs every Sunday at 10:00 AM + triggers: + recurring: + - branch: develop-2.0.0 # We make new releases from this branch + frequency: "10 ? * 1" # Runs every Sunday at 10:00 AM commands: - pip install PyGithub - pip install GitPython diff --git a/.yamato/project.metafile b/.yamato/project.metafile index f434d36106..2ddd060521 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -84,6 +84,7 @@ test_platforms: type: Unity::VM::osx image: package-ci/macos-13:v4 flavor: m1.mac + larger_flavor: m1.mac standalone: IOS base: mac architecture: arm64 diff --git a/Tools/CI/regenerate.bat b/Tools/CI/regenerate.bat deleted file mode 100644 index 33c9c9fb12..0000000000 --- a/Tools/CI/regenerate.bat +++ /dev/null @@ -1,2 +0,0 @@ -cd %~dp0../../ -dotnet run --project Tools\CI\NGO.Cookbook.csproj \ No newline at end of file diff --git a/Tools/CI/regenerate.sh b/Tools/CI/regenerate.sh deleted file mode 100644 index ef2be6b8d8..0000000000 --- a/Tools/CI/regenerate.sh +++ /dev/null @@ -1,2 +0,0 @@ -cd $(dirname "$0")/../../ -dotnet run --project Tools\CI\NGO.Cookbook.csproj \ No newline at end of file diff --git a/Tools/regenerate-ci.cmd b/Tools/regenerate-ci.cmd new file mode 100644 index 0000000000..f31f7122c1 --- /dev/null +++ b/Tools/regenerate-ci.cmd @@ -0,0 +1,5 @@ +@echo off + +cd /d "%~dp0.." +dotnet run --project "Tools\CI\NGO.Cookbook.csproj" %* +if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/Tools/regenerate-ci.sh b/Tools/regenerate-ci.sh new file mode 100644 index 0000000000..910d547122 --- /dev/null +++ b/Tools/regenerate-ci.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e # fail on first error + +SCRIPT_DIR=$(dirname "$0") +dotnet run --project $SCRIPT_DIR/CI/NGO.Cookbook.csproj "$@" \ No newline at end of file diff --git a/Tools/scripts/ReleaseAutomation/release_config.py b/Tools/scripts/ReleaseAutomation/release_config.py index b91f6201d4..0aab8d0940 100644 --- a/Tools/scripts/ReleaseAutomation/release_config.py +++ b/Tools/scripts/ReleaseAutomation/release_config.py @@ -27,6 +27,23 @@ def is_branch_present(self, branch_name): if ghe.status == 404: return False # Branch does not exist raise Exception(f"An error occurred with the GitHub API: {ghe.status}", data=ghe.data) + + def create_pull_request(self, title, body, head, base): + try: + return self.repo.create_pull(title=title, body=body, head=head, base=base) + + except GithubException as ghe: + raise Exception(f"Failed to create pull request: {ghe.status}", ghe.data) from ghe + + def request_reviews(self, pr, reviewers): + if not reviewers: + return + + try: + pr.create_review_request(reviewers=reviewers) + except GithubException as ghe: + raise Exception(f"Failed to request reviews: {ghe.status}", ghe.data) from ghe + class ReleaseConfig: """A simple class to hold all shared configuration.""" @@ -45,7 +62,17 @@ def __init__(self): self.package_version = get_package_version_from_manifest(self.manifest_path) self.release_branch_name = f"release/{self.package_version}" # Branch from which we want to release - self.commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" + + self.release_commit_message = f"Updated changelog and package version for Netcode in anticipation of v{self.package_version} release" + + self.pr_branch_name = f"netcode-update-after-{self.package_version}-release-branch-creation" # Branch from which we will create PR to default branch with relevant changes after release branch is created + self.pr_commit_message = f"chore: Updated aspects of Netcode package in anticipation of v{self.package_version} release" + self.pr_body = f"This PR was created in sync with branching of {self.release_branch_name}. It includes changes that should land on the default Netcode branch ({self.default_repo_branch}) to reflect the new state of the package after the v{self.package_version} release:\n" \ + f"1) Updated CHANGELOG.md by adding new [Unreleased] section template at the top and cleaning the Changelog for the current release.\n" \ + f"2) Updated package version in package.json by incrementing the patch version to signify the current state of the package.\n" \ + f"3) Updated package version in ValidationExceptions.json to match the new package version.\n\n" \ + f"Please review and merge this PR to keep the default branch up to date with the latest package state after the release. Those changes can land immediately OR after the release was finalized but make sure that the Changelog will be merged correctly as sometimes some discrepancies may be introduced due to new entries being introduced meantime\n" + self.pr_reviewers = ["michal-chrobot"] GITHUB_TOKEN_NAME = "NETCODE_GITHUB_TOKEN" YAMATO_API_KEY_NAME = "NETCODE_YAMATO_API_KEY" diff --git a/Tools/scripts/ReleaseAutomation/run_release_preparation.py b/Tools/scripts/ReleaseAutomation/run_release_preparation.py index 562680f8d3..b15e23d4d2 100644 --- a/Tools/scripts/ReleaseAutomation/run_release_preparation.py +++ b/Tools/scripts/ReleaseAutomation/run_release_preparation.py @@ -7,9 +7,9 @@ sys.path.insert(0, PARENT_DIR) from ReleaseAutomation.release_config import ReleaseConfig -from Utils.git_utils import create_branch_execute_commands_and_push +from Utils.git_utils import create_release_branch from Utils.verifyReleaseConditions import verifyReleaseConditions -from Utils.commitChangelogAndPackageVersionUpdates import commitChangelogAndPackageVersionUpdates +from Utils.createPrAfterRelease import createPrAfterRelease from Utils.triggerYamatoJobsForReleasePreparation import trigger_release_preparation_jobs def PrepareNetcodePackageForRelease(): @@ -27,13 +27,13 @@ def PrepareNetcodePackageForRelease(): try: print("\nStep 2: Creating release branch...") - create_branch_execute_commands_and_push(config) + create_release_branch(config) print("\nStep 3: Triggering Yamato validation jobs...") trigger_release_preparation_jobs(config) - print("\nStep 4: Committing changelog and version updates...") - commitChangelogAndPackageVersionUpdates(config) + print("\nStep 4: Creating PR with needed changes to default branch...") + createPrAfterRelease(config) except Exception as e: print("\n--- ERROR: Netcode release process failed ---", file=sys.stderr) diff --git a/Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py b/Tools/scripts/Utils/createPrAfterRelease.py similarity index 75% rename from Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py rename to Tools/scripts/Utils/createPrAfterRelease.py index f58d461216..7bf2cbf349 100644 --- a/Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py +++ b/Tools/scripts/Utils/createPrAfterRelease.py @@ -19,7 +19,7 @@ from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_package_version_by_patch, update_validation_exceptions from Utils.git_utils import get_local_repo -def commitChangelogAndPackageVersionUpdates(config: ReleaseConfig): +def createPrAfterRelease(config: ReleaseConfig): """ The function updates the changelog and package version of the package in anticipation of a new release. This means that it will @@ -33,13 +33,20 @@ def commitChangelogAndPackageVersionUpdates(config: ReleaseConfig): try: if not config.github_manager.is_branch_present(config.default_repo_branch): - print(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") - sys.exit(1) + raise Exception(f"Branch '{config.default_repo_branch}' does not exist. Exiting.") + + author = Actor(config.commiter_name, config.commiter_email) + committer = Actor(config.commiter_name, config.commiter_email) repo = get_local_repo() repo.git.fetch('--prune', '--prune-tags') repo.git.checkout(config.default_repo_branch) + repo.git.add('Tools/regenerate-ci.sh') + repo.index.commit(config.pr_commit_message, author=author, committer=committer, skip_hooks=True) repo.git.pull("origin", config.default_repo_branch) + + # Create a new branch for the release changes PR to default branch + repo.git.checkout('-b', config.pr_branch_name) # Update the changelog file with adding new [Unreleased] section update_changelog(config.changelog_path, config.package_version, add_unreleased_template=True) @@ -51,13 +58,14 @@ def commitChangelogAndPackageVersionUpdates(config: ReleaseConfig): repo.git.add(config.manifest_path) repo.git.add(config.validation_exceptions_path) - author = Actor(config.commiter_name, config.commiter_email) - committer = Actor(config.commiter_name, config.commiter_email) + repo.index.commit(config.pr_commit_message, author=author, committer=committer, skip_hooks=True) + repo.git.push("origin", config.pr_branch_name) - repo.index.commit(config.commit_message, author=author, committer=committer, skip_hooks=True) - repo.git.push("origin", config.default_repo_branch) + github = config.github_manager + pr = github.create_pull_request(title=config.pr_commit_message, body=config.pr_body, head=config.pr_branch_name, base=config.default_repo_branch) + github.request_reviews(pr, config.pr_reviewers) - print(f"Successfully updated and pushed the changelog on branch: {config.default_repo_branch}") + print(f"Successfully updated and created the PR targeting: {config.default_repo_branch}") except GithubException as e: print(f"An error occurred with the GitHub API: {e.status}", file=sys.stderr) diff --git a/Tools/scripts/Utils/general_utils.py b/Tools/scripts/Utils/general_utils.py index 19208ee1b6..0b822ab8d9 100644 --- a/Tools/scripts/Utils/general_utils.py +++ b/Tools/scripts/Utils/general_utils.py @@ -6,6 +6,7 @@ import datetime import platform import subprocess +import warnings UNRELEASED_CHANGELOG_SECTION_TEMPLATE = r""" ## [Unreleased] @@ -37,8 +38,7 @@ def get_package_version_from_manifest(package_manifest_path): """ if not os.path.exists(package_manifest_path): - print("get_manifest_json_version function couldn't find a specified manifest_path") - return None + raise FileNotFoundError(f"{package_manifest_path} couldn't be find") with open(package_manifest_path, 'rb') as f: json_text = f.read() @@ -75,42 +75,6 @@ def update_package_version_by_patch(package_manifest_path): json.dump(package_manifest, f, indent=4) return new_package_version - - -def regenerate_wrench(): - """ - It runs Tools/regenerate-ci.cmd OR Tools/regenerate-ci.sh script - to regenerate the CI files. (depending on the OS) - - This is needed because wrench scripts content is created dynamically depending on the available editors - """ - - # --- Regenerate the CI files --- - print("\nRegenerating CI files...") - script_path = "" - if platform.system() == "Windows": - script_path = os.path.join('Tools', 'CI', 'regenerate.bat') - else: # macOS and Linux - script_path = os.path.join('Tools', 'CI', 'regenerate.sh') - - if not os.path.exists(script_path): - print(f"Error: Regeneration script not found at '{script_path}'.") - return - - try: - # Execute the regeneration script - # On non-Windows systems, the script might need execute permissions. - if platform.system() != "Windows": - os.chmod(script_path, 0o755) - - print(f"Running '{script_path}'...") - subprocess.run([script_path], check=True, shell=True) - print("CI regeneration completed successfully.") - - except subprocess.CalledProcessError as e: - print(f"Error: The CI regeneration script failed with exit code {e.returncode}.") - except Exception as e: - print(f"An unexpected error occurred while running the regeneration script: {e}") def update_validation_exceptions(validation_file, package_version): @@ -140,13 +104,12 @@ def update_validation_exceptions(validation_file, package_version): # If no exceptions were updated, we do not need to write the file if not updated: - print(f"No validation exceptions were updated in {validation_file}.") + warnings.warn(f"No validation exceptions were updated in {validation_file}.") return with open(validation_file, 'w', encoding='UTF-8', newline='\n') as json_file: json.dump(data, json_file, ensure_ascii=False, indent=2) json_file.write("\n") # Add newline cause Py JSON does not - print(f"updated `{validation_file}`") @@ -178,7 +141,7 @@ def update_changelog(changelog_path, new_version, add_unreleased_template=False) cleaned_content = pattern.sub('', changelog_text) if version_header_to_find_if_exists in changelog_text: - print(f"A changelog entry for version '{new_version}' already exists. The script will just remove Unreleased section and its content.") + warnings.warn(f"A changelog entry for version '{new_version}' already exists. The script will just remove Unreleased section and its content.") changelog_text = re.sub(r'(?s)## \[Unreleased(.*?)(?=## \[)', '', changelog_text) else: # Replace the [Unreleased] section with the new version + cleaned subsections diff --git a/Tools/scripts/Utils/git_utils.py b/Tools/scripts/Utils/git_utils.py index 7ead099710..4776d83e6a 100644 --- a/Tools/scripts/Utils/git_utils.py +++ b/Tools/scripts/Utils/git_utils.py @@ -41,7 +41,7 @@ def get_latest_git_revision(branch_name): except subprocess.CalledProcessError as e: raise Exception(f"Failed to get the latest revision for branch '{branch_name}'.") from e -def create_branch_execute_commands_and_push(config: ReleaseConfig): +def create_release_branch(config: ReleaseConfig): """ Creates a new branch with the specified name, performs specified action, commits the current changes and pushes it to the repo. Note that command_to_run_on_release_branch (within the Config) should be a single command that will be executed using subprocess.run. For multiple commands consider using a Python script file. @@ -67,7 +67,7 @@ def create_branch_execute_commands_and_push(config: ReleaseConfig): author = Actor(config.commiter_name, config.commiter_email) committer = Actor(config.commiter_name, config.commiter_email) - repo.index.commit(config.commit_message, author=author, committer=committer, skip_hooks=True) + repo.index.commit(config.release_commit_message, author=author, committer=committer, skip_hooks=True) repo.git.push("origin", config.release_branch_name) print(f"Successfully created, updated and pushed new branch: {config.release_branch_name}") diff --git a/Tools/scripts/release.py b/Tools/scripts/release.py index c81082e563..4693d90bc9 100644 --- a/Tools/scripts/release.py +++ b/Tools/scripts/release.py @@ -13,7 +13,40 @@ import subprocess import platform -from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_validation_exceptions, regenerate_wrench # nopep8 +from Utils.general_utils import get_package_version_from_manifest, update_changelog, update_validation_exceptions # nopep8 + +def regenerate_wrench(): + """ + It runs Tools/regenerate-ci.cmd OR Tools/regenerate-ci.sh script + to regenerate the CI files. (depending on the OS) + + This is needed because wrench scripts content is created dynamically depending on the available editors + """ + + # --- Regenerate the CI files --- + print("\nRegenerating CI files...") + script_path = "" + if platform.system() == "Windows": + script_path = os.path.join('Tools', 'regenerate-ci.cmd') + else: # macOS and Linux + script_path = os.path.join('Tools', 'regenerate-ci.sh') + + if not os.path.exists(script_path): + raise FileNotFoundError(f"Error: Regeneration script not found at '{script_path}'.") + + try: + # Execute the regeneration script + # On non-Windows systems, the script might need execute permissions. + if platform.system() != "Windows": + os.chmod(script_path, 0o755) + + subprocess.run([script_path], check=True, shell=True) + + except subprocess.CalledProcessError as e: + raise Exception(f"Error: The CI regeneration script failed with exit code {e.returncode}.") + except Exception as e: + raise Exception(f"An unexpected error occurred while running the regeneration script: {e}") + def make_package_release_ready(manifest_path, changelog_path, validation_exceptions_path, package_version): @@ -24,20 +57,32 @@ def make_package_release_ready(manifest_path, changelog_path, validation_excepti if not os.path.exists(changelog_path): print(f" Path does not exist: {changelog_path}") sys.exit(1) + + if not os.path.exists(validation_exceptions_path): + print(f" Path does not exist: {validation_exceptions_path}") + sys.exit(1) if package_version is None: print(f"Package version not found at {manifest_path}") sys.exit(1) - # Update the ValidationExceptions.json file - # with the new package version OR remove it if not a release branch - update_validation_exceptions(validation_exceptions_path, package_version) - # Clean the CHANGELOG and add latest entry - # package version is already know as explained in - # https://github.cds.internal.unity3d.com/unity/dots/pull/14318 - update_changelog(changelog_path, package_version) - # Make sure that the wrench scripts are up to date - regenerate_wrench() + try: + # Update the ValidationExceptions.json file + # with the new package version OR remove it if not a release branch + update_validation_exceptions(validation_exceptions_path, package_version) + # Clean the CHANGELOG and add latest entry + # package version is already know as explained in + # https://github.cds.internal.unity3d.com/unity/dots/pull/14318 + update_changelog(changelog_path, package_version) + # Make sure that the wrench scripts are up to date + regenerate_wrench() + + except Exception as e: + print(f"An unexpected error occurred: {e}", file=sys.stderr) + sys.exit(1) + + + if __name__ == '__main__': diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 06109ea401..9dff48cd14 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- test addition ### Changed