Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
69c9a62
Added utils scripts
michalChrobot Aug 15, 2025
cd41f6c
Added script to verify if release conditions are met
michalChrobot Aug 15, 2025
16a494c
Added script to create proper release branch
michalChrobot Aug 15, 2025
dad46ec
Added script to trigger Yamato jobs for release
michalChrobot Aug 15, 2025
2abefae
Added script to update changelog and package version on default branch
michalChrobot Aug 15, 2025
21b625f
updated release.py script
michalChrobot Aug 15, 2025
8a2297b
Created a job to run release automation
michalChrobot Aug 15, 2025
01d989b
Specific changes for NGOv1.X
michalChrobot Aug 15, 2025
513eec0
import correction
michalChrobot Aug 15, 2025
4087817
corrected UpdateChangelog logic
michalChrobot Aug 15, 2025
327169a
release.py update
michalChrobot Sep 11, 2025
3a59133
verifyReleaseConditions update
michalChrobot Sep 11, 2025
48b6874
commit function update
michalChrobot Sep 11, 2025
52845bc
Updated config and other files
michalChrobot Sep 11, 2025
2fe7966
Corrected job commands
michalChrobot Sep 11, 2025
d8b4879
command typo
michalChrobot Sep 11, 2025
bee92ec
secret correction
michalChrobot Sep 11, 2025
8f6beba
specific corrections to 1.X
michalChrobot Sep 11, 2025
d7a075c
Merge branch 'develop' into automated-netcode-releases-1.x
michalChrobot Sep 11, 2025
a77ee9a
nitpicks
michalChrobot Sep 12, 2025
962d2f6
Added Validation exceptions file
michalChrobot Sep 12, 2025
4ed4b86
typo corrections
michalChrobot Sep 12, 2025
1adffec
corrected changelog header pos
michalChrobot Sep 12, 2025
8bbc1b3
Added flags
michalChrobot Sep 12, 2025
cbfe8b1
small typos
michalChrobot Sep 12, 2025
e3d335d
suggestion from comment
michalChrobot Sep 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .yamato/ngo-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
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 # We make new releases from this branch
frequency: weekly # Run at some point every Saturday. Note that it's restricted to every 4th Saturday inside the script
rerun: always
commands:
- pip install PyGithub
- pip install GitPython
- python Tools/scripts/ReleaseAutomation/run_release_preparation.py
154 changes: 154 additions & 0 deletions Tools/scripts/ReleaseAutomation/release_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
"""Netcode configuration for the release process automation."""

import datetime
import sys
import os
from github import Github
from github import GithubException

PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
sys.path.insert(0, PARENT_DIR)

from Utils.general_utils import get_package_version_from_manifest
from release import make_package_release_ready

class GithubUtils:
def __init__(self, access_token, repo):
self.github = Github(base_url="https://api.github.com",
login_or_token=access_token)
self.repo = self.github.get_repo(repo)

def is_branch_present(self, branch_name):
try:
self.repo.get_branch(branch_name)
return True # Branch exists

except GithubException as ghe:
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)

class ReleaseConfig:
"""A simple class to hold all shared configuration."""
def __init__(self):
self.manifest_path = 'com.unity.netcode.gameobjects/package.json'
self.changelog_path = 'com.unity.netcode.gameobjects/CHANGELOG.md'
self.validation_exceptions_path = './ValidationExceptions.json'
self.github_repo = 'Unity-Technologies/com.unity.netcode.gameobjects'
self.default_repo_branch = 'develop' # Changelog and package version change will be pushed to this branch
self.yamato_project_id = '1201'
self.command_to_run_on_release_branch = make_package_release_ready

self.release_weekday = 5 # Saturday
self.release_week_cycle = 4 # Release every 4 weeks
self.anchor_date = datetime.date(2025, 7, 19) # Anchor date for the release cycle (previous release Saturday)

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"

GITHUB_TOKEN_NAME = "NETCODE_GITHUB_TOKEN"
YAMATO_API_KEY_NAME = "NETCODE_YAMATO_API_KEY"
self.github_token = os.environ.get(GITHUB_TOKEN_NAME)
self.yamato_api_token = os.environ.get(YAMATO_API_KEY_NAME)
self.commiter_name = "netcode-automation"
self.commiter_email = "svc-netcode-sdk@unity3d.com"

self.yamato_samples_to_build = [
{
"name": "BossRoom",
"jobDefinition": f".yamato%2Fproject-builders%2Fproject-builders.yml%23build_BossRoom_project",
}
]

self.yamato_build_automation_configs = [
{
"job_name": "Build Sample for Windows with minimal supported editor (2022.3), burst ON, IL2CPP",
"variables": [
{ "key": "BURST_ON_OFF", "value": "on" },
{ "key": "PLATFORM_WIN64_MAC_ANDROID", "value": "win64" },
{ "key": "SCRIPTING_BACKEND_IL2CPP_MONO", "value": "il2cpp" },
{ "key": "UNITY_VERSION", "value": "2022.3" } # Minimal supported editor
]
},
{
"job_name": "Build Sample for Windows with latest functional editor (6000.2), burst ON, IL2CPP",
"variables": [
{ "key": "BURST_ON_OFF", "value": "on" },
{ "key": "PLATFORM_WIN64_MAC_ANDROID", "value": "win64" },
{ "key": "SCRIPTING_BACKEND_IL2CPP_MONO", "value": "il2cpp" },
{ "key": "UNITY_VERSION", "value": "6000.2" } # Editor that most our users will use (not alpha). Sometimes when testing on trunk we have weird editor issues not caused by us so the preference will be to test on latest editor that our users will use.
]
},
{
"job_name": "Build Sample for MacOS with minimal supported editor (2022.3), burst OFF, Mono",
"variables": [
{ "key": "BURST_ON_OFF", "value": "off" },
{ "key": "PLATFORM_WIN64_MAC_ANDROID", "value": "mac" },
{ "key": "SCRIPTING_BACKEND_IL2CPP_MONO", "value": "mono" },
{ "key": "UNITY_VERSION", "value": "2022.3" } # Minimal supported editor
]
},
{
"job_name": "Build Sample for MacOS with latest functional editor (6000.2), burst OFF, Mono",
"variables": [
{ "key": "BURST_ON_OFF", "value": "off" },
{ "key": "PLATFORM_WIN64_MAC_ANDROID", "value": "mac" },
{ "key": "SCRIPTING_BACKEND_IL2CPP_MONO", "value": "mono" },
{ "key": "UNITY_VERSION", "value": "6000.2" } # Editor that most our users will use (not alpha). Sometimes when testing on trunk we have weird editor issues not caused by us so the preference will be to test on latest editor that our users will use.
]
},
{
"job_name": "Build Sample for Android with minimal supported editor (2022.3), burst ON, IL2CPP",
"variables": [
{ "key": "BURST_ON_OFF", "value": "on" },
{ "key": "PLATFORM_WIN64_MAC_ANDROID", "value": "android" },
{ "key": "SCRIPTING_BACKEND_IL2CPP_MONO", "value": "il2cpp" },
{ "key": "UNITY_VERSION", "value": "2022.3" } # Minimal supported editor
]
},
{
"job_name": "Build Sample for Android with latest functional editor (6000.2), burst ON, IL2CPP",
"variables": [
{ "key": "BURST_ON_OFF", "value": "on" },
{ "key": "PLATFORM_WIN64_MAC_ANDROID", "value": "android" },
{ "key": "SCRIPTING_BACKEND_IL2CPP_MONO", "value": "il2cpp" },
{ "key": "UNITY_VERSION", "value": "6000.2" } # Editor that most our users will use (not alpha). Sometimes when testing on trunk we have weird editor issues not caused by us so the preference will be to test on latest editor that our users will use.
]
}
]

error_messages = []
if not os.path.exists(self.manifest_path):
error_messages.append(f" Path does not exist: {self.manifest_path}")

if not os.path.exists(self.changelog_path):
error_messages.append(f" Path does not exist: {self.changelog_path}")

if not os.path.exists(self.validation_exceptions_path):
error_messages.append(f" Path does not exist: {self.validation_exceptions_path}")

if not callable(self.command_to_run_on_release_branch):
error_messages.append("command_to_run_on_release_branch is not a function! Actual value:", self.command_to_run_on_release_branch)

if self.package_version is None:
error_messages.append(f"Package version not found at {self.manifest_path}")

if not self.github_token:
error_messages.append(f"Error: {GITHUB_TOKEN_NAME} environment variable not set.")

if not self.yamato_api_token:
error_messages.append(f"Error: {YAMATO_API_KEY_NAME} environment variable not set.")

# Initialize PyGithub and get the repository object
self.github_manager = GithubUtils(self.github_token, self.github_repo)

if not self.github_manager.is_branch_present(self.default_repo_branch):
error_messages.append(f"Branch '{self.default_repo_branch}' does not exist.")

if self.github_manager.is_branch_present(self.release_branch_name):
error_messages.append(f"Branch '{self.release_branch_name}' is already present in the repo.")

if error_messages:
summary = "Failed to initialize NetcodeReleaseConfig due to invalid setup:\n" + "\n".join(f"- {msg}" for msg in error_messages)
raise ValueError(summary)
37 changes: 37 additions & 0 deletions Tools/scripts/ReleaseAutomation/run_release_preparation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Automation for package release process."""

import sys
import os

PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
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.verifyReleaseConditions import verifyReleaseConditions
from Utils.commitChangelogAndPackageVersionUpdates import commitChangelogAndPackageVersionUpdates
from Utils.triggerYamatoJobsForReleasePreparation import trigger_release_preparation_jobs

def PrepareNetcodePackageForRelease():
try:
config = ReleaseConfig()

print("\nStep 1: Verifying release conditions...")
verifyReleaseConditions(config)

print("\nStep 2: Creating release branch...")
create_branch_execute_commands_and_push(config)

print("\nStep 3: Triggering Yamato validation jobs...")
trigger_release_preparation_jobs(config)

print("\nStep 4: Committing changelog and version updates...")
commitChangelogAndPackageVersionUpdates(config)

except Exception as e:
print("\n--- ERROR: Netcode release process failed ---", file=sys.stderr)
print(f"Reason: {e}", file=sys.stderr)
sys.exit(1)

if __name__ == "__main__":
PrepareNetcodePackageForRelease()
68 changes: 68 additions & 0 deletions Tools/scripts/Utils/commitChangelogAndPackageVersionUpdates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""
Creates a direct commit to specified branch (in the config) to update the changelog, package version and validation exceptions for a new release using the GitHub API.
Quite often the changelog gets distorted between the time we branch for the release and the time we will branch back.
To mitigate this we want to create changelog update PR straight away and merge it fast while proceeding with the release.

This will also allow us to skip any PRs after releasing, unless, we made some changes on this branch.

"""
#!/usr/bin/env python3
import os
import sys
from github import GithubException
from git import Actor

PARENT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '../'))
sys.path.insert(0, PARENT_DIR)

from ReleaseAutomation.release_config import ReleaseConfig
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):
"""
The function updates the changelog and package version of the package in anticipation of a new release.
This means that it will
1) Clean and update the changelog for the current package version.
2) Add new Unreleased section template at the top.
3) Update the package version in the package.json file by incrementing the patch version to signify the current state of the package.
4) Update package version in the validation exceptions to match the new package version.

This assumes that at the same time you already branched off for the release. Otherwise it may be confusing
"""

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)

repo = get_local_repo()
repo.git.fetch('--prune', '--prune-tags')
repo.git.checkout(config.default_repo_branch)
repo.git.pull("origin", config.default_repo_branch)

# Update the changelog file with adding new [Unreleased] section
update_changelog(config.changelog_path, config.package_version, add_unreleased_template=True)
# Update the package version by patch to represent the "current package state" after release
updated_package_version = update_package_version_by_patch(config.manifest_path)
update_validation_exceptions(config.validation_exceptions_path, updated_package_version)

repo.git.add(config.changelog_path)
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.commit_message, author=author, committer=committer, skip_hooks=True)
repo.git.push("origin", config.default_repo_branch)

print(f"Successfully updated and pushed the changelog on branch: {config.default_repo_branch}")

except GithubException as e:
print(f"An error occurred with the GitHub API: {e.status}", file=sys.stderr)
print(f"Error details: {e.data}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"An unexpected error occurred: {e}", file=sys.stderr)
sys.exit(1)
Loading