@@ -18,6 +18,8 @@ import 'common/process_runner.dart';
18
18
import 'common/pub_version_finder.dart' ;
19
19
import 'common/repository_package.dart' ;
20
20
21
+ const int _exitMissingChangeDescriptionFile = 3 ;
22
+
21
23
/// Categories of version change types.
22
24
enum NextVersionType {
23
25
/// A breaking change.
@@ -108,13 +110,36 @@ class VersionCheckCommand extends PackageLoopingCommand {
108
110
argParser.addFlag (
109
111
_againstPubFlag,
110
112
help: 'Whether the version check should run against the version on pub.\n '
111
- 'Defaults to false, which means the version check only run against the previous version in code.' ,
113
+ 'Defaults to false, which means the version check only run against '
114
+ 'the previous version in code.' ,
112
115
defaultsTo: false ,
113
116
negatable: true ,
114
117
);
118
+ argParser.addOption (_changeDescriptionFile,
119
+ help: 'The path to a file containing the description of the change '
120
+ '(e.g., PR description or commit message).\n\n '
121
+ 'If supplied, this is used to allow overrides to some version '
122
+ 'checks.' );
123
+ argParser.addFlag (_ignorePlatformInterfaceBreaks,
124
+ help: 'Bypasses the check that platform interfaces do not contain '
125
+ 'breaking changes.\n\n '
126
+ 'This is only intended for use in post-submit CI checks, to '
127
+ 'prevent the possibility of post-submit breakage if a change '
128
+ 'description justification is not transferred into the commit '
129
+ 'message. Pre-submit checks should always use '
130
+ '--$_changeDescriptionFile instead.' ,
131
+ hide: true );
115
132
}
116
133
117
134
static const String _againstPubFlag = 'against-pub' ;
135
+ static const String _changeDescriptionFile = 'change-description-file' ;
136
+ static const String _ignorePlatformInterfaceBreaks =
137
+ 'ignore-platform-interface-breaks' ;
138
+
139
+ /// The string that must be in [_changeDescriptionFile] to allow a breaking
140
+ /// change to a platform interface.
141
+ static const String _breakingChangeJustificationMarker =
142
+ '## Breaking change justification' ;
118
143
119
144
final PubVersionFinder _pubVersionFinder;
120
145
@@ -292,16 +317,17 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
292
317
return _CurrentVersionState .invalidChange;
293
318
}
294
319
295
- final bool isPlatformInterface =
296
- pubspec.name.endsWith ('_platform_interface' );
297
- // TODO(stuartmorgan): Relax this check. See
298
- // https://github.com/flutter/flutter/issues/85391
299
- if (isPlatformInterface &&
300
- allowedNextVersions[currentVersion] == NextVersionType .BREAKING_MAJOR ) {
320
+ if (allowedNextVersions[currentVersion] == NextVersionType .BREAKING_MAJOR &&
321
+ ! _validateBreakingChange (package)) {
301
322
printError ('${indentation }Breaking change detected.\n '
302
- '${indentation }Breaking changes to platform interfaces are strongly discouraged.\n ' );
323
+ '${indentation }Breaking changes to platform interfaces are not '
324
+ 'allowed without explicit justification.\n '
325
+ '${indentation }See '
326
+ 'https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages '
327
+ 'for more information.' );
303
328
return _CurrentVersionState .invalidChange;
304
329
}
330
+
305
331
return _CurrentVersionState .validChange;
306
332
}
307
333
@@ -398,4 +424,45 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog.
398
424
return null ;
399
425
}
400
426
}
427
+
428
+ /// Checks whether the current breaking change to [package] should be allowed,
429
+ /// logging extra information for auditing when allowing unusual cases.
430
+ bool _validateBreakingChange (RepositoryPackage package) {
431
+ // Only platform interfaces have breaking change restrictions.
432
+ if (! package.isPlatformInterface) {
433
+ return true ;
434
+ }
435
+
436
+ if (getBoolArg (_ignorePlatformInterfaceBreaks)) {
437
+ logWarning (
438
+ '${indentation }Allowing breaking change to ${package .displayName } '
439
+ 'due to --$_ignorePlatformInterfaceBreaks ' );
440
+ return true ;
441
+ }
442
+
443
+ if (_getChangeDescription ().contains (_breakingChangeJustificationMarker)) {
444
+ logWarning (
445
+ '${indentation }Allowing breaking change to ${package .displayName } '
446
+ 'due to "$_breakingChangeJustificationMarker " in the change '
447
+ 'description.' );
448
+ return true ;
449
+ }
450
+
451
+ return false ;
452
+ }
453
+
454
+ /// Returns the contents of the file pointed to by [_changeDescriptionFile] ,
455
+ /// or an empty string if that flag is not provided.
456
+ String _getChangeDescription () {
457
+ final String path = getStringArg (_changeDescriptionFile);
458
+ if (path.isEmpty) {
459
+ return '' ;
460
+ }
461
+ final File file = packagesDir.fileSystem.file (path);
462
+ if (! file.existsSync ()) {
463
+ printError ('${indentation }No such file: $path ' );
464
+ throw ToolExit (_exitMissingChangeDescriptionFile);
465
+ }
466
+ return file.readAsStringSync ();
467
+ }
401
468
}
0 commit comments