Skip to content

Commit 8feb2e7

Browse files
author
Chris Yang
authored
[tool] Improve check version ci so that it enforces the version in CHANGELOG and pubspec matches. (#3678)
1 parent 79dd06a commit 8feb2e7

File tree

7 files changed

+619
-81
lines changed

7 files changed

+619
-81
lines changed

.cirrus.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ root_task_template: &ROOT_TASK_TEMPLATE
66
env:
77
INTEGRATION_TEST_PATH: "./packages/integration_test"
88
CHANNEL: "master" # Default to master when not explicitly set by a task.
9+
PLUGIN_TOOLS: "dart run ./script/tool/lib/src/main.dart"
910
setup_script:
1011
- flutter channel $CHANNEL
1112
- flutter upgrade
1213
- git fetch origin master # To set FETCH_HEAD for "git merge-base" to work
14+
- cd script/tool
15+
- pub get
1316

1417

1518
# Light-workload Linux tasks.
@@ -24,10 +27,14 @@ task:
2427
- name: plugin_tools_tests
2528
script:
2629
- cd script/tool
27-
- pub get
2830
- CIRRUS_BUILD_ID=null pub run test
2931
- name: publishable
3032
script:
33+
- if [[ "$CIRRUS_BRANCH" == "master" ]]; then
34+
- $PLUGIN_TOOLS version-check
35+
- else
36+
- $PLUGIN_TOOLS version-check --run-on-changed-packages
37+
- fi
3138
- ./script/check_publish.sh
3239
- name: format
3340
format_script: ./script/incremental_build.sh format --fail-on-change

script/incremental_build.sh

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,5 @@ else
4949
else
5050
echo running "${ACTIONS[@]}"
5151
(cd "$REPO_DIR" && plugin_tools "${ACTIONS[@]}" --plugins="$CHANGED_PACKAGES" --exclude="$ALL_EXCLUDED" ${PLUGIN_SHARDING[@]})
52-
echo "Running version check for changed packages"
53-
# TODO(egarciad): Enable this check once in master.
54-
# (cd "$REPO_DIR" && $PUB global run flutter_plugin_tools version-check --base_sha="$(get_branch_base_sha)")
5552
fi
5653
fi

script/tool/lib/src/common.dart

Lines changed: 142 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@ import 'dart:io' as io;
77
import 'dart:math';
88

99
import 'package:args/command_runner.dart';
10+
import 'package:colorize/colorize.dart';
1011
import 'package:file/file.dart';
12+
import 'package:git/git.dart';
13+
import 'package:meta/meta.dart';
1114
import 'package:path/path.dart' as p;
15+
import 'package:pub_semver/pub_semver.dart';
1216
import 'package:yaml/yaml.dart';
1317

1418
typedef void Print(Object object);
@@ -140,6 +144,13 @@ bool isLinuxPlugin(FileSystemEntity entity, FileSystem fileSystem) {
140144
return pluginSupportsPlatform(kLinux, entity, fileSystem);
141145
}
142146

147+
/// Throws a [ToolExit] with `exitCode` and log the `errorMessage` in red.
148+
void printErrorAndExit({@required String errorMessage, int exitCode = 1}) {
149+
final Colorize redError = Colorize(errorMessage)..red();
150+
print(redError);
151+
throw ToolExit(exitCode);
152+
}
153+
143154
/// Error thrown when a command needs to exit with a non-zero exit code.
144155
class ToolExit extends Error {
145156
ToolExit(this.exitCode);
@@ -152,6 +163,7 @@ abstract class PluginCommand extends Command<Null> {
152163
this.packagesDir,
153164
this.fileSystem, {
154165
this.processRunner = const ProcessRunner(),
166+
this.gitDir,
155167
}) {
156168
argParser.addMultiOption(
157169
_pluginsArg,
@@ -179,12 +191,23 @@ abstract class PluginCommand extends Command<Null> {
179191
help: 'Exclude packages from this command.',
180192
defaultsTo: <String>[],
181193
);
194+
argParser.addFlag(_runOnChangedPackagesArg,
195+
help: 'Run the command on changed packages/plugins.\n'
196+
'If the $_pluginsArg is specified, this flag is ignored.\n'
197+
'The packages excluded with $_excludeArg is also excluded even if changed.\n'
198+
'See $_kBaseSha if a custom base is needed to determine the diff.');
199+
argParser.addOption(_kBaseSha,
200+
help: 'The base sha used to determine git diff. \n'
201+
'This is useful when $_runOnChangedPackagesArg is specified.\n'
202+
'If not specified, merge-base is used as base sha.');
182203
}
183204

184205
static const String _pluginsArg = 'plugins';
185206
static const String _shardIndexArg = 'shardIndex';
186207
static const String _shardCountArg = 'shardCount';
187208
static const String _excludeArg = 'exclude';
209+
static const String _runOnChangedPackagesArg = 'run-on-changed-packages';
210+
static const String _kBaseSha = 'base-sha';
188211

189212
/// The directory containing the plugin packages.
190213
final Directory packagesDir;
@@ -199,6 +222,11 @@ abstract class PluginCommand extends Command<Null> {
199222
/// This can be overridden for testing.
200223
final ProcessRunner processRunner;
201224

225+
/// The git directory to use. By default it uses the parent directory.
226+
///
227+
/// This can be mocked for testing.
228+
final GitDir gitDir;
229+
202230
int _shardIndex;
203231
int _shardCount;
204232

@@ -273,9 +301,13 @@ abstract class PluginCommand extends Command<Null> {
273301
/// "client library" package, which declares the API for the plugin, as
274302
/// well as one or more platform-specific implementations.
275303
Stream<Directory> _getAllPlugins() async* {
276-
final Set<String> plugins = Set<String>.from(argResults[_pluginsArg]);
304+
Set<String> plugins = Set<String>.from(argResults[_pluginsArg]);
277305
final Set<String> excludedPlugins =
278306
Set<String>.from(argResults[_excludeArg]);
307+
final bool runOnChangedPackages = argResults[_runOnChangedPackagesArg];
308+
if (plugins.isEmpty && runOnChangedPackages) {
309+
plugins = await _getChangedPackages();
310+
}
279311

280312
await for (FileSystemEntity entity
281313
in packagesDir.list(followLinks: false)) {
@@ -363,6 +395,50 @@ abstract class PluginCommand extends Command<Null> {
363395
(FileSystemEntity entity) => isFlutterPackage(entity, fileSystem))
364396
.cast<Directory>();
365397
}
398+
399+
/// Retrieve an instance of [GitVersionFinder] based on `_kBaseSha` and [gitDir].
400+
///
401+
/// Throws tool exit if [gitDir] nor root directory is a git directory.
402+
Future<GitVersionFinder> retrieveVersionFinder() async {
403+
final String rootDir = packagesDir.parent.absolute.path;
404+
String baseSha = argResults[_kBaseSha];
405+
406+
GitDir baseGitDir = gitDir;
407+
if (baseGitDir == null) {
408+
if (!await GitDir.isGitDir(rootDir)) {
409+
printErrorAndExit(
410+
errorMessage: '$rootDir is not a valid Git repository.',
411+
exitCode: 2);
412+
}
413+
baseGitDir = await GitDir.fromExisting(rootDir);
414+
}
415+
416+
final GitVersionFinder gitVersionFinder =
417+
GitVersionFinder(baseGitDir, baseSha);
418+
return gitVersionFinder;
419+
}
420+
421+
Future<Set<String>> _getChangedPackages() async {
422+
final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
423+
424+
final List<String> allChangedFiles =
425+
await gitVersionFinder.getChangedFiles();
426+
final Set<String> packages = <String>{};
427+
allChangedFiles.forEach((String path) {
428+
final List<String> pathComponents = path.split('/');
429+
final int packagesIndex =
430+
pathComponents.indexWhere((String element) => element == 'packages');
431+
if (packagesIndex != -1) {
432+
packages.add(pathComponents[packagesIndex + 1]);
433+
}
434+
});
435+
if (packages.isNotEmpty) {
436+
final String changedPackages = packages.join(',');
437+
print(changedPackages);
438+
}
439+
print('No changed packages.');
440+
return packages;
441+
}
366442
}
367443

368444
/// A class used to run processes.
@@ -466,3 +542,68 @@ class ProcessRunner {
466542
return 'ERROR: Unable to execute "$executable ${args.join(' ')}"$workdir.';
467543
}
468544
}
545+
546+
/// Finding diffs based on `baseGitDir` and `baseSha`.
547+
class GitVersionFinder {
548+
/// Constructor
549+
GitVersionFinder(this.baseGitDir, this.baseSha);
550+
551+
/// The top level directory of the git repo.
552+
///
553+
/// That is where the .git/ folder exists.
554+
final GitDir baseGitDir;
555+
556+
/// The base sha used to get diff.
557+
final String baseSha;
558+
559+
static bool _isPubspec(String file) {
560+
return file.trim().endsWith('pubspec.yaml');
561+
}
562+
563+
/// Get a list of all the pubspec.yaml file that is changed.
564+
Future<List<String>> getChangedPubSpecs() async {
565+
return (await getChangedFiles()).where(_isPubspec).toList();
566+
}
567+
568+
/// Get a list of all the changed files.
569+
Future<List<String>> getChangedFiles() async {
570+
final String baseSha = await _getBaseSha();
571+
final io.ProcessResult changedFilesCommand = await baseGitDir
572+
.runCommand(<String>['diff', '--name-only', '$baseSha', 'HEAD']);
573+
print('Determine diff with base sha: $baseSha');
574+
final String changedFilesStdout = changedFilesCommand.stdout.toString() ?? '';
575+
if (changedFilesStdout.isEmpty) {
576+
return <String>[];
577+
}
578+
final List<String> changedFiles = changedFilesStdout
579+
.split('\n')
580+
..removeWhere((element) => element.isEmpty);
581+
return changedFiles.toList();
582+
}
583+
584+
/// Get the package version specified in the pubspec file in `pubspecPath` and at the revision of `gitRef`.
585+
Future<Version> getPackageVersion(String pubspecPath, String gitRef) async {
586+
final io.ProcessResult gitShow =
587+
await baseGitDir.runCommand(<String>['show', '$gitRef:$pubspecPath']);
588+
final String fileContent = gitShow.stdout;
589+
final String versionString = loadYaml(fileContent)['version'];
590+
return versionString == null ? null : Version.parse(versionString);
591+
}
592+
593+
Future<String> _getBaseSha() async {
594+
if (baseSha != null && baseSha.isNotEmpty) {
595+
return baseSha;
596+
}
597+
598+
io.ProcessResult baseShaFromMergeBase = await baseGitDir.runCommand(
599+
<String>['merge-base', '--fork-point', 'FETCH_HEAD', 'HEAD'],
600+
throwOnError: false);
601+
if (baseShaFromMergeBase == null ||
602+
baseShaFromMergeBase.stderr != null ||
603+
baseShaFromMergeBase.stdout == null) {
604+
baseShaFromMergeBase = await baseGitDir
605+
.runCommand(<String>['merge-base', 'FETCH_HEAD', 'HEAD']);
606+
}
607+
return (baseShaFromMergeBase.stdout as String).trim();
608+
}
609+
}

0 commit comments

Comments
 (0)