Skip to content

Commit 59c0506

Browse files
committed
Simplified release note process
1 parent b9a4fa9 commit 59c0506

35 files changed

+560
-152
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
*Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.*
2+
3+
*List which issues are fixed by this PR. You must list at least one issue.*
4+
5+
<!-- Uncomment and modify the following section if your PR does not require changes to the release notes -->
6+
<!--
7+
RELEASE_NOTE_EXCEPTION=[REASON GOES HERE]
8+
-->
9+
10+
## Pre-launch Checklist
11+
12+
- [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
13+
- [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities.
14+
- [ ] I read the [Flutter Style Guide] _recently_, and have followed its advice.
15+
- [ ] I signed the [CLA].
16+
- [ ] I listed at least one issue that this PR fixes in the description above.
17+
- [ ] I updated/added relevant documentation (doc comments with `///`).
18+
- [ ] I added new tests to check the change I am making, or there is a reason for not adding tests.
19+
20+
21+
![build.yaml badge]
22+
23+
If you need help, consider asking for help on [Discord].
24+
25+
<!-- Links -->
26+
[Contributor Guide]: https://github.com/flutter/devtools/blob/master/CONTRIBUTING.md
27+
[Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene
28+
[Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo
29+
[CLA]: https://cla.developers.google.com/
30+
[Discord]: https://github.com/flutter/flutter/wiki/Chat
31+
[build.yaml badge]: https://github.com/flutter/devtools/actions/workflows/build.yaml/badge.svg

.github/workflows/release-notes.yaml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Release Notes
2+
3+
on:
4+
pull_request:
5+
types: [ assigned, opened, synchronize, reopened, edited ]
6+
env:
7+
CURRENT_RELEASE_JSON_FILE_PATH: tool/release_notes/NEXT_RELEASE_NOTES.md
8+
jobs:
9+
release-preparedness:
10+
runs-on: ubuntu-latest
11+
name: Verify PR Release Note Requirements
12+
steps:
13+
14+
- name: Get Pull Request Number
15+
id: get-pull-request-number
16+
run: |
17+
PULL_REQUEST_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH")
18+
echo "PULL_REQUEST_NUMBER=$PULL_REQUEST_NUMBER" >> $GITHUB_OUTPUT
19+
20+
- name: Check if we have modified release note file
21+
id: get-modified-files
22+
env:
23+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
PULL_NUMBER: ${{steps.get-pull-request-number.outputs.PULL_REQUEST_NUMBER}}
25+
run: |
26+
FILES_RESPONSE=$(gh api /repos/$GITHUB_REPOSITORY/pulls/$PULL_NUMBER/files)
27+
echo "FILES_RESPONSE: $FILES_RESPONSE"
28+
29+
HAS_CHANGED_RELEASE_NOTES=$(echo $FILES_RESPONSE | jq '.[].filename' | jq -s '. | any(. == env.CURRENT_RELEASE_JSON_FILE_PATH)')
30+
echo "HAS_CHANGED_RELEASE_NOTES=$HAS_CHANGED_RELEASE_NOTES" >> $GITHUB_OUTPUT
31+
32+
- name: Get PR Description
33+
id: check-release-note-exceptions
34+
env:
35+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
36+
PULL_NUMBER: ${{steps.get-pull-request-number.outputs.PULL_REQUEST_NUMBER}}
37+
run: |
38+
PULLS_RESPONSE=$(gh api /repos/$GITHUB_REPOSITORY/pulls/$PULL_NUMBER)
39+
DESCRIPTION_BODY=$(echo $PULLS_RESPONSE | jq '.body')
40+
echo $DESCRIPTION_BODY
41+
if $(echo $DESCRIPTION_BODY | grep -Eq "RELEASE_NOTE_EXCEPTION="); then
42+
HAS_RELEASE_NOTE_EXCEPTION_STRING=true
43+
else
44+
HAS_RELEASE_NOTE_EXCEPTION_STRING=false
45+
fi
46+
echo "HAS_RELEASE_NOTE_EXCEPTION_STRING=$HAS_RELEASE_NOTE_EXCEPTION_STRING" >> $GITHUB_OUTPUT
47+
48+
- name: Check Release Preparedness requirements
49+
env:
50+
HAS_CHANGED_RELEASE_NOTES: ${{steps.get-modified-files.outputs.HAS_CHANGED_RELEASE_NOTES}}
51+
HAS_RELEASE_NOTE_EXCEPTION_STRING: ${{steps.check-release-note-exceptions.outputs.HAS_RELEASE_NOTE_EXCEPTION_STRING}}
52+
run: |
53+
if [ "$HAS_CHANGED_RELEASE_NOTES" != "true" ] && [ "$HAS_RELEASE_NOTE_EXCEPTION_STRING" != "true" ] ; then
54+
echo "Release Preparedness check failed"
55+
echo "::error file=$CURRENT_RELEASE_JSON_FILE_PATH,line=0,col=0,endColumn=0,title='Release Notes Weren\'t Modified'::Please add a release note entry or a reason to your description using: \`RELEASE_NOTE_EXCEPTION=[reason goes here]\`"
56+
exit 1
57+
fi

packages/devtools_app/lib/src/app.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,17 +229,17 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
229229
return ValueListenableBuilder<bool>(
230230
valueListenable: preferences.vmDeveloperModeEnabled,
231231
builder: (_, __, child) {
232-
final tabs = _visibleScreens()
232+
final screens = _visibleScreens()
233233
.where((p) => embed && page != null ? p.screenId == page : true)
234234
.where((p) => !hide.contains(p.screenId))
235235
.toList();
236-
if (tabs.isEmpty) return child ?? const SizedBox.shrink();
236+
if (screens.isEmpty) return child ?? const SizedBox.shrink();
237237
return _providedControllers(
238238
child: DevToolsScaffold(
239239
embed: embed,
240240
ideTheme: ideTheme,
241241
page: page,
242-
tabs: tabs,
242+
screens: screens,
243243
actions: [
244244
// TODO(https://github.com/flutter/devtools/issues/1941)
245245
if (serviceManager.connectedApp!.isFlutterAppNow!) ...[

packages/devtools_app/lib/src/framework/about_dialog.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class DevToolsAboutDialog extends StatelessWidget {
5555
...dialogSubHeader(theme, 'Contributing'),
5656
Wrap(
5757
children: const [
58-
Text('Want to contribute to DevTools?'),
58+
Text('Want to contribute to DevTools? Please see our '),
5959
_ContributingLink(),
6060
Text(' guide, or '),
6161
],

packages/devtools_app/lib/src/framework/release_notes/README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
## Writing DevTools release notes
1+
## Generating Release notes
22
- Release notes for DevTools are hosted on the flutter website (see [archive](https://docs.flutter.dev/development/tools/devtools/release-notes)).
3-
- To add release notes for the latest release, create a PR with the appropriate changes for your release
4-
- The [release notes template](release-notes-template.md) can be used as a starting point
5-
- see example [PR](https://github.com/flutter/website/pull/6791).
6-
3+
- To add release notes for the latest release, create a PR with the appropriate changes for your release.
4+
- The [NEXT_RELEASE_NOTES.md](../../../../../../tool/release_notes/NEXT_RELEASE_NOTES.md) file contains the running release notes for the current version.
5+
- see example [PR](https://github.com/flutter/website/pull/6791) for an idea of how to add those to the Flutter website.
76
- Test these changes locally before creating the PR.
87
- See [README.md](https://github.com/flutter/website/blob/main/README.md)
98
for getting setup to run the Flutter website locally.

packages/devtools_app/lib/src/framework/scaffold.dart

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import 'status_line.dart';
3737
class DevToolsScaffold extends StatefulWidget {
3838
const DevToolsScaffold({
3939
Key? key,
40-
required this.tabs,
40+
required this.screens,
4141
this.page,
4242
this.actions,
4343
this.embed = false,
@@ -51,7 +51,7 @@ class DevToolsScaffold extends StatefulWidget {
5151
List<Widget>? actions,
5252
}) : this(
5353
key: key,
54-
tabs: [SimpleScreen(child)],
54+
screens: [SimpleScreen(child)],
5555
ideTheme: ideTheme,
5656
actions: actions,
5757
);
@@ -77,7 +77,7 @@ class DevToolsScaffold extends StatefulWidget {
7777
EdgeInsets.symmetric(horizontal: isEmbedded() ? 2.0 : 16.0);
7878

7979
/// All of the [Screen]s that it's possible to navigate to from this Scaffold.
80-
final List<Screen> tabs;
80+
final List<Screen> screens;
8181

8282
/// The page being rendered.
8383
final String? page;
@@ -134,22 +134,23 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
134134
void didUpdateWidget(DevToolsScaffold oldWidget) {
135135
super.didUpdateWidget(oldWidget);
136136

137-
if (widget.tabs.length != oldWidget.tabs.length) {
137+
if (widget.screens.length != oldWidget.screens.length) {
138138
var newIndex = 0;
139139
// Stay on the current tab if possible when the collection of tabs changes.
140140
if (_tabController != null &&
141-
widget.tabs.contains(oldWidget.tabs[_tabController!.index])) {
142-
newIndex = widget.tabs.indexOf(oldWidget.tabs[_tabController!.index]);
141+
widget.screens.contains(oldWidget.screens[_tabController!.index])) {
142+
newIndex =
143+
widget.screens.indexOf(oldWidget.screens[_tabController!.index]);
143144
}
144145
// Create a new tab controller to reflect the changed tabs.
145146
_setupTabController();
146147
_tabController!.index = newIndex;
147-
} else if (widget.tabs[_tabController!.index].screenId != widget.page) {
148+
} else if (widget.screens[_tabController!.index].screenId != widget.page) {
148149
// If the page changed (eg. the route was modified by pressing back in the
149150
// browser), animate to the new one.
150151
final newIndex = widget.page == null
151152
? 0 // When there's no supplied page, we show the first one.
152-
: widget.tabs.indexWhere((t) => t.screenId == widget.page);
153+
: widget.screens.indexWhere((t) => t.screenId == widget.page);
153154
_tabController!.animateTo(newIndex);
154155
}
155156
}
@@ -183,19 +184,19 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
183184

184185
void _setupTabController() {
185186
_tabController?.dispose();
186-
_tabController = TabController(length: widget.tabs.length, vsync: this);
187+
_tabController = TabController(length: widget.screens.length, vsync: this);
187188

188189
if (widget.page != null) {
189190
final initialIndex =
190-
widget.tabs.indexWhere((screen) => screen.screenId == widget.page);
191+
widget.screens.indexWhere((screen) => screen.screenId == widget.page);
191192
if (initialIndex != -1) {
192193
_tabController!.index = initialIndex;
193194
}
194195
}
195196

196-
_currentScreen = widget.tabs[_tabController!.index];
197+
_currentScreen = widget.screens[_tabController!.index];
197198
_tabController!.addListener(() {
198-
final screen = widget.tabs[_tabController!.index];
199+
final screen = widget.screens[_tabController!.index];
199200

200201
if (_currentScreen != screen) {
201202
setState(() {
@@ -246,7 +247,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
246247
final existingTabIndex = _tabController!.index;
247248

248249
final newIndex =
249-
widget.tabs.indexWhere((screen) => screen.screenId == pageId);
250+
widget.screens.indexWhere((screen) => screen.screenId == pageId);
250251

251252
if (newIndex != -1 && newIndex != existingTabIndex) {
252253
DevToolsRouterDelegate.of(context).navigateIfNotCurrent(pageId);
@@ -278,7 +279,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
278279
Widget build(BuildContext context) {
279280
// Build the screens for each tab and wrap them in the appropriate styling.
280281
final tabBodies = [
281-
for (var screen in widget.tabs)
282+
for (var screen in widget.screens)
282283
Container(
283284
// TODO(kenz): this padding creates a flash when dragging and dropping
284285
// into the app size screen because it creates space that is outside
@@ -380,17 +381,17 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
380381

381382
// Add a leading [BulletSpacer] to the actions if the screen is not narrow.
382383
final actions = List<Widget>.from(widget.actions ?? []);
383-
if (!isNarrow && actions.isNotEmpty && widget.tabs.length > 1) {
384+
if (!isNarrow && actions.isNotEmpty && widget.screens.length > 1) {
384385
actions.insert(0, const BulletSpacer(useAccentColor: true));
385386
}
386387

387-
final bool hasMultipleTabs = widget.tabs.length > 1;
388+
final bool hasMultipleTabs = widget.screens.length > 1;
388389

389390
if (hasMultipleTabs) {
390391
tabBar = TabBar(
391392
controller: _tabController,
392393
isScrollable: true,
393-
tabs: [for (var screen in widget.tabs) screen.buildTab(context)],
394+
tabs: [for (var screen in widget.screens) screen.buildTab(context)],
394395
);
395396
preferredSize = isNarrow
396397
? Size.fromHeight(
@@ -461,7 +462,7 @@ class DevToolsScaffoldState extends State<DevToolsScaffold>
461462
// Approximate size of the title. Add [defaultSpacing] to account for
462463
// title's leading padding.
463464
double wideWidth = painter.width + defaultSpacing;
464-
for (var tab in widget.tabs) {
465+
for (var tab in widget.screens) {
465466
wideWidth += tab.approximateWidth(textTheme);
466467
}
467468
final actionsLength = widget.actions?.length ?? 0;

packages/devtools_app/lib/src/screens/memory/panes/diff/controller/heap_diff.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,17 @@ class ObjectSetDiff {
166166
final object = before.objectsByCodes[code] ?? after.objectsByCodes[code]!;
167167

168168
if (inBefore) {
169-
deleted.countInstance(object);
170-
delta.uncountInstance(object);
169+
final excludeFromRetained =
170+
before.notCountedInRetained.contains(object.code);
171+
deleted.countInstance(object, excludeFromRetained: excludeFromRetained);
172+
delta.uncountInstance(object, excludeFromRetained: excludeFromRetained);
171173
continue;
172174
}
173175
if (inAfter) {
174-
created.countInstance(object);
175-
delta.countInstance(object);
176+
final excludeFromRetained =
177+
after.notCountedInRetained.contains(object.code);
178+
created.countInstance(object, excludeFromRetained: excludeFromRetained);
179+
delta.countInstance(object, excludeFromRetained: excludeFromRetained);
176180
continue;
177181
}
178182
assert(false);

packages/devtools_app/lib/src/screens/memory/panes/diff/widgets/class_filter_dialog.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,5 @@ const _helpText = 'Choose and customize the filter.\n'
135135
' MyClass\n'
136136
' package:myPackage/src/\n\n'
137137
'Specify:\n'
138-
' - ${ClassFilter.noPackageLibrariesAlias} for classes without package prefix\n'
138+
' - ${ClassFilter.dartInternalAlias} for dart internal objects, not assigned to any package\n'
139139
' - ${ClassFilter.dartAndFlutterLibrariesAlias} for most "dart:" and "package:" libraries published by Dart and Flutter orgs.';

packages/devtools_app/lib/src/screens/memory/shared/heap/class_filter.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@ class ClassFilter {
3636
ClassFilter.empty()
3737
: this(
3838
filterType: ClassFilterType.showAll,
39-
except: '$noPackageLibrariesAlias\n$dartAndFlutterLibrariesAlias',
39+
except: '$dartInternalAlias\n$dartAndFlutterLibrariesAlias',
4040
only: null,
4141
);
4242

4343
static String _trimByLine(String value) =>
4444
value.split('\n').map((e) => e.trim()).join('\n');
4545

4646
static const String dartAndFlutterLibrariesAlias = '\$dart-flutter-libraries';
47-
static const String noPackageLibrariesAlias = '\$no-package-libraries';
47+
static const String dartInternalAlias = '\$dart-internal-objects';
4848

4949
final ClassFilterType filterType;
5050
final String except;
@@ -140,8 +140,7 @@ class ClassFilter {
140140
if (className.fullName.contains(filter)) return true;
141141
if (filter == dartAndFlutterLibrariesAlias && className.isDartOrFlutter)
142142
return true;
143-
if (filter == noPackageLibrariesAlias && className.isPackageless)
144-
return true;
143+
if (filter == dartInternalAlias && className.isPackageless) return true;
145144
return false;
146145
}
147146
}

packages/devtools_app/lib/src/screens/memory/shared/heap/heap.dart

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,19 @@ class SingleClassStats extends ClassStats {
133133
assert(!isSealed);
134134
final object = data.objects[objectIndex];
135135
assert(object.heapClass.fullName == heapClass.fullName);
136-
objects.countInstance(object);
137136

138137
final path = data.retainingPath(objectIndex);
138+
objects.countInstance(
139+
object,
140+
excludeFromRetained: path?.isRetainedBySameClass ?? false,
141+
);
142+
139143
if (path == null) return;
140144
final objectsForPath = statsByPath.putIfAbsent(
141145
ClassOnlyHeapPath(path),
142146
() => ObjectSet(),
143147
);
144-
objectsForPath.countInstance(object);
148+
objectsForPath.countInstance(object, excludeFromRetained: false);
145149
}
146150

147151
bool get isZero => objects.isZero;
@@ -174,16 +178,22 @@ class ObjectSetStats with Sealable {
174178
bool get isZero =>
175179
shallowSize == 0 && retainedSize == 0 && instanceCount == 0;
176180

177-
void countInstance(AdaptedHeapObject object) {
181+
void countInstance(
182+
AdaptedHeapObject object, {
183+
required bool excludeFromRetained,
184+
}) {
178185
assert(!isSealed);
179-
retainedSize += object.retainedSize!;
186+
if (!excludeFromRetained) retainedSize += object.retainedSize!;
180187
shallowSize += object.shallowSize;
181188
instanceCount++;
182189
}
183190

184-
void uncountInstance(AdaptedHeapObject object) {
191+
void uncountInstance(
192+
AdaptedHeapObject object, {
193+
required bool excludeFromRetained,
194+
}) {
185195
assert(!isSealed);
186-
retainedSize -= object.retainedSize!;
196+
if (!excludeFromRetained) retainedSize -= object.retainedSize!;
187197
shallowSize -= object.shallowSize;
188198
instanceCount--;
189199
}
@@ -194,19 +204,27 @@ class ObjectSet extends ObjectSetStats {
194204
static ObjectSet empty = ObjectSet()..seal();
195205

196206
final objectsByCodes = <IdentityHashCode, AdaptedHeapObject>{};
207+
final notCountedInRetained = <IdentityHashCode>{};
197208

198209
@override
199210
bool get isZero => objectsByCodes.isEmpty;
200211

201212
@override
202-
void countInstance(AdaptedHeapObject object) {
213+
void countInstance(
214+
AdaptedHeapObject object, {
215+
required bool excludeFromRetained,
216+
}) {
203217
if (objectsByCodes.containsKey(object.code)) return;
204-
super.countInstance(object);
218+
super.countInstance(object, excludeFromRetained: excludeFromRetained);
205219
objectsByCodes[object.code] = object;
220+
if (excludeFromRetained) notCountedInRetained.add(object.code);
206221
}
207222

208223
@override
209-
void uncountInstance(AdaptedHeapObject object) {
224+
void uncountInstance(
225+
AdaptedHeapObject object, {
226+
required bool excludeFromRetained,
227+
}) {
210228
throw AssertionError('uncountInstance is not valid for $ObjectSet');
211229
}
212230
}

0 commit comments

Comments
 (0)