Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion tool/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ moved on:
pub.dev with `upstream_versions.yaml`, and fails on a new major: check the
package against it, then move the table and the README snippets together.
It also resolves a scratch app with the latest upstream and the package,
once from this repository and once at its latest version on pub.dev.
once from this repository and once at its latest version on pub.dev. A
package without a row in `published.yaml` is staged and not on pub.dev
yet, so it resolves from this repository only.
- `bin/pana_scores.dart` reads one `pana --json` report per package. Every
section must be full, except "Platform support" for a package that
declares only watchOS, which pana does not score (`AUTHORING.md`
Expand Down
9 changes: 8 additions & 1 deletion tool/bin/upstream_compat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
///
/// Usage, from `tool/`: `dart run bin/upstream_compat.dart
/// [--root <repository>] [--table <upstream_versions.yaml>]
/// [--flutter <flutter executable>]`.
/// [--published <published.yaml>] [--flutter <flutter executable>]`.
library;

import 'dart:io';
Expand All @@ -17,11 +17,13 @@ import 'package:path/path.dart' as p;
import 'package:plugins_tool/src/forbidden_words.dart';
import 'package:plugins_tool/src/repository.dart';
import 'package:plugins_tool/src/upstream_compat.dart';
import 'package:plugins_tool/src/versions.dart';

Future<void> main(List<String> arguments) async {
final Map<String, String> options = parseOptions(arguments, <String>{
'root',
'table',
'published',
'flutter',
});
final String tool = toolDirectory();
Expand All @@ -30,6 +32,11 @@ Future<void> main(List<String> arguments) async {
exitCode = await runUpstreamCompatibility(
options['root'] ?? repositoryRootOf(tool),
options['table'] ?? p.join(tool, 'upstream_versions.yaml'),
parsePublished(
File(
options['published'] ?? p.join(tool, 'published.yaml'),
).readAsStringSync(),
),
pubDevFetcher(client),
flutterPubGet(flutter: options['flutter'] ?? 'flutter'),
ForbiddenWords.load(p.join(tool, 'words', 'words.txt')),
Expand Down
29 changes: 22 additions & 7 deletions tool/lib/src/upstream_compat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
/// - resolves a scratch app with the upstream at its latest version and the
/// package from this repository, and another with the package's latest
/// version on pub.dev, as a user would; either failing to resolve fails.
///
/// A package without a row in `tool/published.yaml` is staged
/// (`AUTHORING.md` section 4) and not on pub.dev yet, so only the package
/// from this repository is resolved. A package with a row that pub.dev does
/// not know fails.
library;

import 'dart:convert';
Expand All @@ -28,6 +33,7 @@ import 'package:yaml/yaml.dart';
import 'forbidden_words.dart';
import 'public_log.dart';
import 'readme_snippets.dart';
import 'versions.dart';

/// Returns a package's latest version on pub.dev, or null when pub.dev does
/// not know the package.
Expand Down Expand Up @@ -212,10 +218,12 @@ List<String> resolutionReason(String output, {int limit = 8}) {
}

/// Runs the check on the repository at [root] with the table in
/// [tableFile], writes the report to [out] and returns the exit code.
/// [tableFile] and the rows of `tool/published.yaml` in [published], writes
/// the report to [out] and returns the exit code.
Future<int> runUpstreamCompatibility(
String root,
String tableFile,
Map<String, PublishedRow> published,
LatestVersionFetcher latestVersion,
Resolver resolve,
ForbiddenWords words,
Expand Down Expand Up @@ -287,9 +295,7 @@ Future<int> runUpstreamCompatibility(
packagePath: p.absolute(root, 'packages', package),
),
);
if (packageLatest == null) {
report.problems.add('pub.dev has no version of $package.');
} else {
if (packageLatest != null) {
tryResolve(
'published',
'$packageLatest from pub.dev',
Expand All @@ -300,11 +306,20 @@ Future<int> runUpstreamCompatibility(
packageVersion: packageLatest,
),
);
} else if (published.containsKey(package)) {
report.problems.add('pub.dev has no version of $package.');
} else {
report.notes.add(
'Not published yet (no row in tool/published.yaml), so only the '
'package from this repository was resolved.',
);
}
}
report.summary =
'$upstream $upstreamLatest resolves with the package from this '
'repository and with $packageLatest from pub.dev';
report.summary = packageLatest == null
? '$upstream $upstreamLatest resolves with the package from this '
'repository'
: '$upstream $upstreamLatest resolves with the package from this '
'repository and with $packageLatest from pub.dev';
if (report.writeTo(out, words)) {
failures++;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# The package has a row, as if it were published, but pub.dev does not
# know it. This check reads only the package names.
packages:
fixture_battery_plus_watchos:
version: 0.1.0
commit: '0000000000000000000000000000000000000000'
4 changes: 4 additions & 0 deletions tool/test/fixtures/upstream_compat/unpublished_package/expect
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
0
ok fixture_sqflite_watchos: sqflite 2.4.5 resolves with the package from this repository
Not published yet (no row in tool/published.yaml), so only the package from this repository was resolved.
Upstream compatibility: all 1 packages resolve with their latest upstream.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# latest.version on pub.dev, per package.
sqflite: 2.4.5
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: fixture_sqflite_watchos
version: 0.0.1
flutter:
plugin:
implements: sqflite
platforms:
watchos:
ffiPlugin: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# What pub get prints when it fails, per package and source. The package
# has no row in published.yaml and pub.dev does not know it, so the check
# must not resolve it from pub.dev.
fixture_sqflite_watchos published: |
Resolving dependencies...
Because upstream_compatibility depends on fixture_sqflite_watchos any which doesn't exist (could not find package fixture_sqflite_watchos at https://pub.dev), version solving failed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
upstreams:
sqflite: 2.4.5
exceptions: {}
37 changes: 36 additions & 1 deletion tool/test/upstream_compat_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

// The weekly upstream compatibility check on its fixtures, in
// tool/test/fixtures/upstream_compat/. Each fixture's pub_dev.yaml stands
// in for the pub.dev API, and its resolve.yaml for `flutter pub get`: a
// in for the pub.dev API, its published.yaml (when it has one) for
// tool/published.yaml, and its resolve.yaml for `flutter pub get`: a
// package and source listed there fails to resolve with that output. The
// games_services output is the one plugins.md P6 recorded on 2026-09-29.

Expand All @@ -14,6 +15,7 @@ import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:plugins_tool/src/forbidden_words.dart';
import 'package:plugins_tool/src/upstream_compat.dart';
import 'package:plugins_tool/src/versions.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
import 'package:yaml/yaml.dart';
Expand All @@ -40,6 +42,14 @@ LatestVersionFetcher fixtureFetcher(String fixture) {
});
}

/// The rows of the fixture's published.yaml; none when it has none.
Map<String, PublishedRow> fixturePublished(String fixture) {
final File published = File(p.join(fixture, 'published.yaml'));
return published.existsSync()
? parsePublished(published.readAsStringSync())
: <String, PublishedRow>{};
}

/// A resolver that fails for the package and source the fixture's
/// resolve.yaml lists, and checks each scratch pubspec it is given.
Resolver fixtureResolver(String fixture, String root) {
Expand Down Expand Up @@ -100,6 +110,7 @@ void main() {
final int code = await runUpstreamCompatibility(
root,
p.join(fixture.directory, 'upstream_versions.yaml'),
fixturePublished(fixture.directory),
fixtureFetcher(fixture.directory),
fixtureResolver(fixture.directory, root),
words,
Expand All @@ -124,6 +135,7 @@ void main() {
final int code = await runUpstreamCompatibility(
root,
p.join(fixture, 'upstream_versions.yaml'),
fixturePublished(fixture),
mapFetcher(<String, String>{
'battery_plus': '7.1.1',
'fixture_battery_plus_watchos': '0.1.0-${words.words.first}.1',
Expand All @@ -149,6 +161,7 @@ void main() {
final int code = await runUpstreamCompatibility(
root,
p.join(fixture, 'upstream_versions.yaml'),
fixturePublished(fixture),
(String package) async =>
throw const HttpException('pub.dev answered 503.'),
fixtureResolver(fixture, root),
Expand All @@ -159,6 +172,28 @@ void main() {
expect('$out', contains('pub.dev could not be read'));
});

test('an unpublished package does not claim a version on pub.dev', () async {
final String fixture = p.join(
'test',
'fixtures',
'upstream_compat',
'unpublished_package',
);
final String root = p.join(fixture, 'repo');
final StringBuffer out = StringBuffer();
final int code = await runUpstreamCompatibility(
root,
p.join(fixture, 'upstream_versions.yaml'),
fixturePublished(fixture),
fixtureFetcher(fixture),
fixtureResolver(fixture, root),
words,
out,
);
expect(code, 0, reason: '$out');
expect('$out', isNot(contains('from pub.dev')));
});

test('compareWithTable', () {
final Version table = Version.parse('7.1.1');
expect(compareWithTable('b', table, Version.parse('7.1.1')), (
Expand Down
Loading