Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 10 additions & 9 deletions content/learn/contribute/project-information/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,17 @@ When making a release, the Maintainers follow these checklists:
2. Check appropriate milestone and close it.
3. Check GitHub Projects page for staleness.
4. Update change log.
5. Create migration guide.
6. Write blog post.
7. Update book.
8. Bump version number for all crates, using the "Release" workflow.
5. Run the [`generate-release`](https://github.com/bevyengine/bevy-website/tree/main/generate-release) tool.
1. Create migration guide.
2. Write blog post.
6. Update book.
7. Bump version number for all crates, using the "Release" workflow.
1. Change the commit message to be nicer.
9. Create tag on GitHub.
10. Edit GitHub Release. Add links to the `Release announcement` and `Migration Guide`.
11. Bump `latest` tag to most recent release.
12. Run the [`update-screenshots` workflow] to update screenshots. *This will block blog post releases (and take ~40 minutes) so do it early*.
13. Run the [`build-wasm-examples` workflow] to update Wasm examples.
8. Create tag on GitHub.
9. Edit GitHub Release. Add links to the `Release announcement` and `Migration Guide`.
10. Bump `latest` tag to most recent release.
11. Run the [`update-screenshots` workflow] to update screenshots. *This will block blog post releases (and take ~40 minutes) so do it early*.
12. Run the [`build-wasm-examples` workflow] to update Wasm examples.

#### Minor Release

Expand Down
1 change: 1 addition & 0 deletions generate-release/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dotenvy = "0.15.6"
serde_json = "1.0.91"
rayon = "1.10.0"
thiserror = "1.0.61"
toml = "0.8.19"

[lints]
workspace = true
26 changes: 19 additions & 7 deletions generate-release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ This CLI tool is used to generate all the skeleton files required to create a ne

For a bit more background see this issue: <https://github.com/bevyengine/bevy-website/issues/1163>

All commands assume they are ran in the `/generate-release` folder.
The commands can be run from anywhere inside the workspace folder.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's obvious, but this is true as long as the .env file is at the root. cargo run -p generate-release wasn't working in the project root, then I realised that I had my .env file in the /generate-release folder (from the previous release). 😓

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's obvious, but this is true as long as the .env file is at the root. cargo run -p generate-release wasn't working in the project root, then I realised that I had my .env file in the /generate-release folder (from the previous release). 😓

Should be a warning in the README now.


Each command will generate files in the `/release-content/{release-version}` folder. The `release-version` is an argument to all commands.

Each command have a `--from` and `--to` argument. You can pass it a Git branch, tag, or commit.

To create issues for the `release-notes` subcommand, you need to pass the `--create-issues` flag, otherwise it performs a dry-run that does not have lasting consequences. This should probably only be done for the initial run, after a regular dry-run has been done to confirm the tool is working as expected.

Before running the command, you'll need to generate a GitHub API token at <https://github.com/settings/tokens>. It's easier to use classic tokens.
The token must have `repo` permissions to be able to open issues (and PRs) on your behalf.

Expand All @@ -22,16 +24,18 @@ GITHUB_TOKEN=token_string_copied_from_github
Here's an example for the commands used to generate the `0.14` release:

```shell
cargo run -- --from v0.13.0 --to main --release-version 0.14 migration-guides
cargo run -- --from v0.13.0 --to main --release-version 0.14 release-notes
cargo run -- --from v0.13.0 --to main --release-version 0.14 changelog
cargo run -- --from v0.13.0 --to main --release-version 0.14 contributors
cargo run -p generate-release -- --from v0.13.0 --to main --release-version 0.14 migration-guides
cargo run -p generate-release -- --from v0.13.0 --to main --release-version 0.14 release-notes
cargo run -p generate-release -- --from v0.13.0 --to main --release-version 0.14 changelog
cargo run -p generate-release -- --from v0.13.0 --to main --release-version 0.14 contributors
```

## Generating a release

To generate a release from scratch, run all these commands then add the new migration guide and blog post to their respective `/content` folder. When doing so, it's important to use the `public_draft` feature to hide those pages until the day of the release. For the `public_draft` feature, you'll need to provide it a GitHub issue number, it's recommended to point it to an issue tracker for the current release being worked on. The issue needs to be on the `bevy-website` repo.

When you're merging, or editing, notes and guides, keep in mind that this tool will not regenerate notes or guides that still have a PR number in any note or guide's metadata, contained in the `_<release_notes|migration_guides>.toml`. This means to merge multiple PRs into one note or guide you simply remove one `[[release_notes]]` or `[[guides]]` entry, and move it's PR number to the merged entry that is the sum of all the merged PRs. For editing, this means the other metadata will also not be regenerated if the PR number still exists in the metadata.

The following sections go in more details on each parts of the process.

### Migration Guides
Expand Down Expand Up @@ -74,9 +78,14 @@ Once all those files are generated you'll need to create a new blog post in `/co

```markdown
+++
title = "Bevy 0.14"
date = 2024-05-17
# Let X be the major version, and y the minor version.
# Change the Bevy release versions below to match this one!
title = "Bevy X.y"
# Insert a date in the year, month, day format.
date = YYYY-MM-DD
[extra]
# GitHub issue number for tracking this release's
# news post.
public_draft = _release tracking issue number_
+++

Expand All @@ -96,3 +105,6 @@ public_draft = _release tracking issue number_
```

The most important part of this is the `release_notes`, `changelog`, and `contributors` shortcodes. `release_notes` will get the list of release notes from the `_release_notes.toml` file and combine all the separate file and add them to this file. `contributors()` will load the `contributors.toml` file and generate the necessary markup. `changelog()` will load the `changelog.toml` file and generate the necessary markup.

> [!NOTE]
> The `contributors` field in `_release_notes.toml` is for all non-PR-author contributors to the PR; they should be added to the `authors` field on a case-by-case basis depending on level of involvement.
80 changes: 72 additions & 8 deletions generate-release/src/migration_guides.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
use anyhow::Context;
use serde::Deserialize;

use crate::{
github_client::{GithubClient, GithubIssuesResponse},
helpers::{get_merged_prs, get_pr_area},
markdown::write_markdown_section,
};
use std::{collections::BTreeMap, io::Write as IoWrite, path::PathBuf};
use std::{
collections::BTreeMap,
fs::{self, OpenOptions},
io::Write as IoWrite,
path::PathBuf,
};

type PrsByAreaBTreeMap = BTreeMap<Vec<String>, Vec<(String, GithubIssuesResponse)>>;

#[derive(Deserialize, Clone)]
struct MigrationGuides {
guides: Vec<MigrationGuide>,
}

#[allow(dead_code)]
#[derive(Deserialize, Clone)]
struct MigrationGuide {
title: String,
prs: Vec<u64>,
areas: Vec<String>,
file_name: String,
}

pub fn generate_migration_guides(
from: &str,
to: &str,
Expand All @@ -25,6 +45,19 @@ pub fn generate_migration_guides(
// We'll write the file once at the end when all the metdaata is generated
let mut guides_metadata = Vec::new();

// If there is metadata that already exists,
// and would contain info such as which PR already
// has an entry, then get it and use it for that.
let preexisting_metadata_file = fs::read_to_string(path.join("_guides.toml")).ok();
let preexisting_metadata: Option<MigrationGuides> = match preexisting_metadata_file {
Some(file_data) => Some(toml::from_str(file_data.as_str())?),
None => None,
};

eprintln!("metadata exists? {}", preexisting_metadata.is_some());

let mut new_prs = false;

// Write all the separate migration guide files
for (area, prs) in areas {
let mut prs = prs;
Expand All @@ -34,6 +67,33 @@ pub fn generate_migration_guides(
prs.sort_by_key(|k| k.1.closed_at);

for (title, pr) in prs {
// If a PR is already included in the migration guides,
// then do not generate anything for this PR.
//
// If overwrite_existing is true, then ignore
// if the PRs may have already been generated.
if preexisting_metadata.is_some() && !overwrite_existing {
let preexisting_metadata = preexisting_metadata.clone().expect(
"that preexisting metadata exists at the _guides.toml for this release version",
);
let mut pr_already_generated = false;

for migration_guide in preexisting_metadata.guides {
if migration_guide.prs.contains(&pr.number) {
pr_already_generated = true;
}
}

if pr_already_generated {
eprintln!("PR #{} already exists", pr.number);
continue;
}
}

// If the code has reached this point then that means
// there is new PRs to be recorded.
new_prs = true;

// Slugify the title
let title_slug = title
.replace(' ', "_")
Expand All @@ -53,10 +113,7 @@ pub fn generate_migration_guides(
let metadata_block = generate_metadata_block(&title, &file_name, &area, pr.number);

let file_path = path.join(format!("{file_name}.md"));
if file_path.exists() && !overwrite_existing {
// Skip existing files because we don't want to overwrite changes when regenerating
continue;
}

if write_migration_file(
&file_path,
pr.body.as_ref().context("PR has no body")?,
Expand All @@ -67,8 +124,15 @@ pub fn generate_migration_guides(
}
}

if !new_prs {
return Ok(());
}

// Write the metadata file
let mut guides_toml = std::fs::File::create(path.join("_guides.toml"))
let mut guides_toml = OpenOptions::new()
.append(true)
.create(true)
.open(path.join("_guides.toml"))
.context("Failed to create _guides.toml")?;
for metadata in guides_metadata {
writeln!(&mut guides_toml, "{metadata}")?;
Expand Down Expand Up @@ -98,7 +162,7 @@ fn get_prs_by_areas(
let has_breaking_label = pr
.labels
.iter()
.any(|l| l.name.contains("C-Breaking-Change"));
.any(|l| l.name.contains("M-Needs-Migration-Guide"));

// We want to check for PRs with the breaking label but without the guide section
// to make it easier to track down missing guides
Expand Down Expand Up @@ -127,7 +191,7 @@ fn generate_metadata_block(
format!(
r#"[[guides]]
title = "{title}"
url = "https://github.com/bevyengine/bevy/pull/{pr_number}"
prs = [{pr_number},]
areas = [{areas}]
file_name = "{file_name}.md"
"#,
Expand Down
Loading