@@ -18,6 +18,8 @@ import 'common/process_runner.dart';
1818import 'common/pub_version_finder.dart' ;
1919import 'common/repository_package.dart' ;
2020
21+ const int _exitMissingChangeDescriptionFile = 3 ;
22+
2123/// Categories of version change types.
2224enum NextVersionType {
2325 /// A breaking change.
@@ -108,13 +110,36 @@ class VersionCheckCommand extends PackageLoopingCommand {
108110 argParser.addFlag (
109111 _againstPubFlag,
110112 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.' ,
112115 defaultsTo: false ,
113116 negatable: true ,
114117 );
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 );
115132 }
116133
117134 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' ;
118143
119144 final PubVersionFinder _pubVersionFinder;
120145
@@ -292,16 +317,17 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
292317 return _CurrentVersionState .invalidChange;
293318 }
294319
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)) {
301322 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.' );
303328 return _CurrentVersionState .invalidChange;
304329 }
330+
305331 return _CurrentVersionState .validChange;
306332 }
307333
@@ -398,4 +424,45 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog.
398424 return null ;
399425 }
400426 }
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+ }
401468}
0 commit comments