Skip to content

Commit 857cb1b

Browse files
authored
Detect Swiftpm support (#1412)
1 parent 9527636 commit 857cb1b

10 files changed

+220
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
## 0.22.13
77

88
- Pass-through for `formatter/page_width` in `analysis_options.yaml`.
9+
- Detect support for Swift Package Manager for ios/macos plugins.
910
- Upgraded `lints` to `^5.0.0`
1011

1112
## 0.22.12

lib/src/package_context.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ class PackageContext {
272272
tagger.flutterPluginTags(tags, explanations);
273273
tagger.nullSafetyTags(tags, explanations);
274274
tagger.wasmReadyTag(tags, explanations);
275+
tagger.swiftPackageManagerPluginTag(tags, explanations);
275276
if (currentSdkVersion.major >= 3) {
276277
tags.add(PanaTags.isDart3Compatible);
277278
}

lib/src/report/multi_platform.dart

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,19 @@ Future<ReportSection> multiPlatform(PackageContext context) async {
118118
}
119119

120120
final wasmSubsection = await _createWasmSubsection(context);
121+
final swiftPackageManagerSubsection =
122+
await _createSwiftPackageManagerSubSection(context);
121123

122124
return makeSection(
123125
id: ReportSectionId.platform,
124126
title: 'Platform support',
125127
maxPoints: 20,
126128
basePath: context.packageDir,
127-
subsections: [subsection, wasmSubsection],
129+
subsections: [
130+
subsection,
131+
wasmSubsection,
132+
if (swiftPackageManagerSubsection != null) swiftPackageManagerSubsection
133+
],
128134
maxIssues: 20);
129135
}
130136

@@ -178,3 +184,47 @@ Future<Subsection> _createWasmSubsection(PackageContext context) async {
178184
);
179185
}
180186
}
187+
188+
/// Create a subsection for ios and macos plugins, to highlight supported
189+
/// for swift package manager (or lack there of).
190+
Future<Subsection?> _createSwiftPackageManagerSubSection(
191+
PackageContext context) async {
192+
final tr = await context.staticAnalysis;
193+
final description = 'Swift Package Manager support';
194+
195+
if (tr.tags.contains(PanaTags.isSwiftPmPlugin)) {
196+
return Subsection(
197+
description,
198+
[
199+
RawParagraph(
200+
'This iOS or macOS plugin supports the Swift Package Manager. '
201+
'It will be rewarded additional points in a future version of the scoring model.'),
202+
RawParagraph('See https://docs.flutter.dev/to/spm for details.'),
203+
],
204+
0,
205+
0,
206+
ReportStatus.passed,
207+
);
208+
}
209+
final explanation = tr.explanations
210+
.where((e) => e.tag == PanaTags.isSwiftPmPlugin)
211+
.firstOrNull;
212+
if (explanation != null) {
213+
return Subsection(
214+
description,
215+
[
216+
explanationToIssue(explanation),
217+
RawParagraph(
218+
'This package for iOS or macOS does not support the Swift Package Manager. '
219+
'It will not receive full points in a future version of the scoring model.',
220+
),
221+
RawParagraph('See https://docs.flutter.dev/to/spm for details.'),
222+
],
223+
0,
224+
0,
225+
ReportStatus.failed,
226+
);
227+
}
228+
// Don't complain if this is not an ios/macos plugin.
229+
return null;
230+
}

lib/src/tag/pana_tags.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ abstract class PanaTags {
3838
static const hasScreenshot = 'has:screenshot';
3939
static const isPlugin = 'is:plugin';
4040
static const isNullSafe = 'is:null-safe';
41+
static const isSwiftPmPlugin = 'is:swiftpm-plugin';
4142

4243
/// Package version is compatible with Dart 3.
4344
static const isDart3Compatible = 'is:dart3-compatible';

lib/src/tag/tagger.dart

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ export '_specs.dart' show Runtime;
9494
/// Calculates the tags for the package residing in a given directory.
9595
class Tagger {
9696
final String packageName;
97+
final String packageDir;
9798
final AnalysisSession _session;
9899
final PubspecCache _pubspecCache;
99100
final bool _isBinaryOnly;
@@ -115,14 +116,15 @@ class Tagger {
115116
final PackageGraph _packageGraph;
116117

117118
Tagger._(
118-
this.packageName,
119-
this._session,
120-
PubspecCache pubspecCache,
121-
this._isBinaryOnly,
122-
this._usesFlutter,
123-
this._topLibraries,
124-
this._publicLibraries,
125-
) : _pubspecCache = pubspecCache,
119+
this.packageName,
120+
this._session,
121+
PubspecCache pubspecCache,
122+
this._isBinaryOnly,
123+
this._usesFlutter,
124+
this._topLibraries,
125+
this._publicLibraries,
126+
this.packageDir)
127+
: _pubspecCache = pubspecCache,
126128
_packageGraph = PackageGraph(pubspecCache);
127129

128130
/// Assumes that `dart pub get` has been run.
@@ -170,15 +172,8 @@ class Tagger {
170172
final publicLibraries = nonSrcDartFiles
171173
.map((s) => Uri.parse('package:${pubspec.name}/$s'))
172174
.toList();
173-
return Tagger._(
174-
pubspec.name,
175-
session,
176-
pubspecCache,
177-
isBinaryOnly,
178-
pubspec.usesFlutter,
179-
topLibraries,
180-
publicLibraries,
181-
);
175+
return Tagger._(pubspec.name, session, pubspecCache, isBinaryOnly,
176+
pubspec.usesFlutter, topLibraries, publicLibraries, packageDir);
182177
}
183178

184179
void sdkTags(List<String> tags, List<Explanation> explanations) {
@@ -355,6 +350,52 @@ class Tagger {
355350
}
356351
}
357352

353+
void swiftPackageManagerPluginTag(
354+
List<String> tags, List<Explanation> explanations) {
355+
if (!_usesFlutter) return;
356+
final pubspec = _pubspecCache.pubspecOfPackage(packageName);
357+
358+
bool pathExists(dynamic m, List<String> path) {
359+
dynamic current = m;
360+
for (final e in path) {
361+
if (current is! Map) return false;
362+
if (!current.containsKey(e)) return false;
363+
current = current[e];
364+
}
365+
return true;
366+
}
367+
368+
var isDarwinPlugin = false;
369+
var swiftPmSupport = true;
370+
371+
for (final darwinOs in ['macos', 'ios']) {
372+
if (pathExists(
373+
pubspec.originalYaml, ['flutter', 'plugin', 'platforms', darwinOs])) {
374+
isDarwinPlugin = true;
375+
final osDir = pubspec.originalYaml['flutter']?['plugin']?['platforms']
376+
?[darwinOs]?['sharedDarwinSource'] ==
377+
true
378+
? 'darwin'
379+
: darwinOs;
380+
381+
final packageSwiftFile = path.join(osDir, packageName, 'Package.swift');
382+
if (!File(path.join(packageDir, packageSwiftFile)).existsSync()) {
383+
swiftPmSupport = false;
384+
final osName = {'macos': 'macOS', 'ios': 'iOS'}[darwinOs];
385+
explanations.add(Explanation(
386+
'Package does not support the Swift Package Manager on $osName',
387+
'''
388+
It does not contain `$packageSwiftFile`.
389+
''',
390+
tag: PanaTags.isSwiftPmPlugin));
391+
}
392+
}
393+
}
394+
if (isDarwinPlugin && swiftPmSupport) {
395+
tags.add(PanaTags.isSwiftPmPlugin);
396+
}
397+
}
398+
358399
/// Adds tags for the Dart runtimes that this package supports to [tags].
359400
///
360401
/// Adds [Explanation]s to [explanations] for runtimes not supported.

test/goldens/end2end/audio_service-0.18.10.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@
155155
"grantedPoints": 20,
156156
"maxPoints": 20,
157157
"status": "failed",
158-
"summary": "### [*] 20/20 points: Supports 4 of 6 possible platforms (**iOS**, **Android**, **Web**, Windows, **macOS**, Linux)\n\n* ✓ Android\n\n* ✓ iOS\n\n* ✓ macOS\n\n* ✓ Web\n\n\nThese platforms are not supported:\n\n<details>\n<summary>\nPackage does not support platform `Windows`.\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n</details>\n\n<details>\n<summary>\nPackage does not support platform `Linux`.\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n</details>\n\n\nThese issues are present but do not affect the score, because they may not originate in your package:\n\n<details>\n<summary>\nPackage does not support platform `Web`.\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that imports:\n* `package:flutter_cache_manager/flutter_cache_manager.dart` that imports:\n* `package:flutter_cache_manager/src/storage/cache_info_repositories/cache_info_repositories.dart` that imports:\n* `package:flutter_cache_manager/src/storage/cache_info_repositories/json_cache_info_repository.dart` that imports:\n* `package:path_provider/path_provider.dart` that declares support for platforms: `Android`, `iOS`, `Windows`, `Linux`, `macOS`.\n</details>\n\n### [x] 0/0 points: WASM compatibility\n\n<details>\n<summary>\nPackage not compatible with runtime wasm\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that imports:\n* `package:flutter_cache_manager/flutter_cache_manager.dart` that imports:\n* `package:flutter_cache_manager/src/web/web_helper.dart` that imports:\n* `package:flutter_cache_manager/src/cache_store.dart` that imports:\n* `package:flutter_cache_manager/src/storage/file_system/file_system.dart` that imports:\n* `package:file/file.dart` that imports:\n* `package:file/src/interface.dart` that imports:\n* `package:file/src/io.dart` that imports:\n* `dart:io`\n</details>\n\nThis package is not compatible with runtime `wasm`, and will not be rewarded full points in a future version of the scoring model.\n\nSee https://dart.dev/web/wasm for details.\n"
158+
"summary": "### [*] 20/20 points: Supports 4 of 6 possible platforms (**iOS**, **Android**, **Web**, Windows, **macOS**, Linux)\n\n* ✓ Android\n\n* ✓ iOS\n\n* ✓ macOS\n\n* ✓ Web\n\n\nThese platforms are not supported:\n\n<details>\n<summary>\nPackage does not support platform `Windows`.\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n</details>\n\n<details>\n<summary>\nPackage does not support platform `Linux`.\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n</details>\n\n\nThese issues are present but do not affect the score, because they may not originate in your package:\n\n<details>\n<summary>\nPackage does not support platform `Web`.\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that imports:\n* `package:flutter_cache_manager/flutter_cache_manager.dart` that imports:\n* `package:flutter_cache_manager/src/storage/cache_info_repositories/cache_info_repositories.dart` that imports:\n* `package:flutter_cache_manager/src/storage/cache_info_repositories/json_cache_info_repository.dart` that imports:\n* `package:path_provider/path_provider.dart` that declares support for platforms: `Android`, `iOS`, `Windows`, `Linux`, `macOS`.\n</details>\n\n### [x] 0/0 points: WASM compatibility\n\n<details>\n<summary>\nPackage not compatible with runtime wasm\n</summary>\n\nBecause:\n* `package:audio_service/audio_service.dart` that imports:\n* `package:flutter_cache_manager/flutter_cache_manager.dart` that imports:\n* `package:flutter_cache_manager/src/web/web_helper.dart` that imports:\n* `package:flutter_cache_manager/src/cache_store.dart` that imports:\n* `package:flutter_cache_manager/src/storage/file_system/file_system.dart` that imports:\n* `package:file/file.dart` that imports:\n* `package:file/src/interface.dart` that imports:\n* `package:file/src/io.dart` that imports:\n* `dart:io`\n</details>\n\nThis package is not compatible with runtime `wasm`, and will not be rewarded full points in a future version of the scoring model.\n\nSee https://dart.dev/web/wasm for details.\n\n### [x] 0/0 points: Swift Package Manager support\n\n<details>\n<summary>\nPackage does not support the Swift Package Manager on macOS\n</summary>\n\nIt does not contain `macos/audio_service/Package.swift`.\n\n</details>\n\nThis package for iOS or macOS does not support the Swift Package Manager. It will not receive full points in a future version of the scoring model.\n\nSee https://docs.flutter.dev/to/spm for details.\n"
159159
},
160160
{
161161
"id": "analysis",

test/goldens/end2end/audio_service-0.18.10.json_report.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ This package is not compatible with runtime `wasm`, and will not be rewarded ful
8888

8989
See https://dart.dev/web/wasm for details.
9090

91+
### [x] 0/0 points: Swift Package Manager support
92+
93+
<details>
94+
<summary>
95+
Package does not support the Swift Package Manager on macOS
96+
</summary>
97+
98+
It does not contain `macos/audio_service/Package.swift`.
99+
100+
</details>
101+
102+
This package for iOS or macOS does not support the Swift Package Manager. It will not receive full points in a future version of the scoring model.
103+
104+
See https://docs.flutter.dev/to/spm for details.
105+
91106

92107
## 50/50 Pass static analysis
93108

test/goldens/end2end/url_launcher-6.1.12.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@
133133
"title": "Platform support",
134134
"grantedPoints": 20,
135135
"maxPoints": 20,
136-
"status": "passed",
137-
"summary": "### [*] 20/20 points: Supports 6 of 6 possible platforms (**iOS**, **Android**, **Web**, **Windows**, **macOS**, **Linux**)\n\n* ✓ Android\n\n* ✓ iOS\n\n* ✓ Windows\n\n* ✓ Linux\n\n* ✓ macOS\n\n* ✓ Web\n\n### [*] 0/0 points: WASM compatibility\n\nThis package is compatible with runtime `wasm`, and will be rewarded additional points in a future version of the scoring model.\n\nSee https://dart.dev/web/wasm for details.\n"
136+
"status": "failed",
137+
"summary": "### [*] 20/20 points: Supports 6 of 6 possible platforms (**iOS**, **Android**, **Web**, **Windows**, **macOS**, **Linux**)\n\n* ✓ Android\n\n* ✓ iOS\n\n* ✓ Windows\n\n* ✓ Linux\n\n* ✓ macOS\n\n* ✓ Web\n\n### [*] 0/0 points: WASM compatibility\n\nThis package is compatible with runtime `wasm`, and will be rewarded additional points in a future version of the scoring model.\n\nSee https://dart.dev/web/wasm for details.\n\n### [x] 0/0 points: Swift Package Manager support\n\n<details>\n<summary>\nPackage does not support the Swift Package Manager on macOS\n</summary>\n\nIt does not contain `macos/url_launcher/Package.swift`.\n\n</details>\n\nThis package for iOS or macOS does not support the Swift Package Manager. It will not receive full points in a future version of the scoring model.\n\nSee https://docs.flutter.dev/to/spm for details.\n"
138138
},
139139
{
140140
"id": "analysis",

test/goldens/end2end/url_launcher-6.1.12.json_report.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,21 @@ This package is compatible with runtime `wasm`, and will be rewarded additional
5252

5353
See https://dart.dev/web/wasm for details.
5454

55+
### [x] 0/0 points: Swift Package Manager support
56+
57+
<details>
58+
<summary>
59+
Package does not support the Swift Package Manager on macOS
60+
</summary>
61+
62+
It does not contain `macos/url_launcher/Package.swift`.
63+
64+
</details>
65+
66+
This package for iOS or macOS does not support the Swift Package Manager. It will not receive full points in a future version of the scoring model.
67+
68+
See https://docs.flutter.dev/to/spm for details.
69+
5570

5671
## 50/50 Pass static analysis
5772

test/tag/tag_end2end_test.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,81 @@ name: my_package
578578
});
579579
});
580580

581+
group('swift package manager tag', () {
582+
test('Works with ios/<package_name>/Package.swift', () async {
583+
final descriptor = d.dir('cache', [
584+
packageWithPathDeps('my_package', pubspecExtras: {
585+
'flutter': {
586+
'plugin': {
587+
'platforms': {'ios': <String, dynamic>{}}
588+
}
589+
}
590+
}, extraFiles: [
591+
d.dir('ios', [
592+
d.dir('my_package', [d.file('Package.swift')])
593+
]),
594+
])
595+
]);
596+
await descriptor.create();
597+
final tagger = Tagger('${descriptor.io.path}/my_package');
598+
_expectTagging(tagger.swiftPackageManagerPluginTag,
599+
tags: contains('is:swiftpm-plugin'));
600+
});
601+
602+
test('Works with darwin/package_name/Package.swift', () async {
603+
final descriptor = d.dir('cache', [
604+
packageWithPathDeps('my_package', pubspecExtras: {
605+
'flutter': {
606+
'plugin': {
607+
'platforms': {
608+
'ios': <String, dynamic>{'sharedDarwinSource': true},
609+
'macos': <String, dynamic>{'sharedDarwinSource': true}
610+
}
611+
}
612+
}
613+
}, extraFiles: [
614+
d.dir('darwin', [
615+
d.dir('my_package', [d.file('Package.swift')])
616+
]),
617+
])
618+
]);
619+
await descriptor.create();
620+
final tagger = Tagger('${descriptor.io.path}/my_package');
621+
_expectTagging(tagger.swiftPackageManagerPluginTag,
622+
tags: contains('is:swiftpm-plugin'));
623+
});
624+
625+
test('Fails with the wrong os/package_name/Package.swift', () async {
626+
final descriptor = d.dir('cache', [
627+
packageWithPathDeps('my_package', pubspecExtras: {
628+
'flutter': {
629+
'plugin': {
630+
'platforms': {'macos': <String, dynamic>{}}
631+
}
632+
}
633+
}, extraFiles: [
634+
d.dir('ios', [
635+
d.dir('my_package', [d.file('Package.swift')])
636+
]),
637+
])
638+
]);
639+
await descriptor.create();
640+
final tagger = Tagger('${descriptor.io.path}/my_package');
641+
_expectTagging(tagger.swiftPackageManagerPluginTag,
642+
tags: isEmpty,
643+
explanations: contains(isA<Explanation>()
644+
.having((e) => e.tag, 'tag', 'is:swiftpm-plugin')));
645+
});
646+
647+
test('Does not complain about non-plugins', () async {
648+
final descriptor = d.dir('cache', [packageWithPathDeps('my_package')]);
649+
await descriptor.create();
650+
final tagger = Tagger('${descriptor.io.path}/my_package');
651+
_expectTagging(tagger.swiftPackageManagerPluginTag,
652+
tags: isEmpty, explanations: isEmpty);
653+
});
654+
});
655+
581656
group('wasm tag', () {
582657
test('Excluded with dart:js', () async {
583658
final descriptor = d.dir('cache', [

0 commit comments

Comments
 (0)