diff --git a/CHANGELOG.md b/CHANGELOG.md index c49e835df..204a1d585 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.22.13 - Pass-through for `formatter/page_width` in `analysis_options.yaml`. +- Detect support for Swift Package Manager for ios/macos plugins. - Upgraded `lints` to `^5.0.0` ## 0.22.12 diff --git a/lib/src/package_context.dart b/lib/src/package_context.dart index ba0e6a9d1..3361c5d5d 100644 --- a/lib/src/package_context.dart +++ b/lib/src/package_context.dart @@ -272,6 +272,7 @@ class PackageContext { tagger.flutterPluginTags(tags, explanations); tagger.nullSafetyTags(tags, explanations); tagger.wasmReadyTag(tags, explanations); + tagger.swiftPackageManagerPluginTag(tags, explanations); if (currentSdkVersion.major >= 3) { tags.add(PanaTags.isDart3Compatible); } diff --git a/lib/src/report/multi_platform.dart b/lib/src/report/multi_platform.dart index 4a03465e5..9be9ad1f3 100644 --- a/lib/src/report/multi_platform.dart +++ b/lib/src/report/multi_platform.dart @@ -118,13 +118,19 @@ Future multiPlatform(PackageContext context) async { } final wasmSubsection = await _createWasmSubsection(context); + final swiftPackageManagerSubsection = + await _createSwiftPackageManagerSubSection(context); return makeSection( id: ReportSectionId.platform, title: 'Platform support', maxPoints: 20, basePath: context.packageDir, - subsections: [subsection, wasmSubsection], + subsections: [ + subsection, + wasmSubsection, + if (swiftPackageManagerSubsection != null) swiftPackageManagerSubsection + ], maxIssues: 20); } @@ -178,3 +184,47 @@ Future _createWasmSubsection(PackageContext context) async { ); } } + +/// Create a subsection for ios and macos plugins, to highlight supported +/// for swift package manager (or lack there of). +Future _createSwiftPackageManagerSubSection( + PackageContext context) async { + final tr = await context.staticAnalysis; + final description = 'Swift Package Manager support'; + + if (tr.tags.contains(PanaTags.isSwiftPmPlugin)) { + return Subsection( + description, + [ + RawParagraph( + 'This iOS or macOS plugin supports the Swift Package Manager. ' + 'It will be rewarded additional points in a future version of the scoring model.'), + RawParagraph('See https://docs.flutter.dev/to/spm for details.'), + ], + 0, + 0, + ReportStatus.passed, + ); + } + final explanation = tr.explanations + .where((e) => e.tag == PanaTags.isSwiftPmPlugin) + .firstOrNull; + if (explanation != null) { + return Subsection( + description, + [ + explanationToIssue(explanation), + RawParagraph( + '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.', + ), + RawParagraph('See https://docs.flutter.dev/to/spm for details.'), + ], + 0, + 0, + ReportStatus.failed, + ); + } + // Don't complain if this is not an ios/macos plugin. + return null; +} diff --git a/lib/src/tag/pana_tags.dart b/lib/src/tag/pana_tags.dart index 6dfbe9429..5c159bb12 100644 --- a/lib/src/tag/pana_tags.dart +++ b/lib/src/tag/pana_tags.dart @@ -38,6 +38,7 @@ abstract class PanaTags { static const hasScreenshot = 'has:screenshot'; static const isPlugin = 'is:plugin'; static const isNullSafe = 'is:null-safe'; + static const isSwiftPmPlugin = 'is:swiftpm-plugin'; /// Package version is compatible with Dart 3. static const isDart3Compatible = 'is:dart3-compatible'; diff --git a/lib/src/tag/tagger.dart b/lib/src/tag/tagger.dart index e1fd3fff4..6f2e6c299 100644 --- a/lib/src/tag/tagger.dart +++ b/lib/src/tag/tagger.dart @@ -94,6 +94,7 @@ export '_specs.dart' show Runtime; /// Calculates the tags for the package residing in a given directory. class Tagger { final String packageName; + final String packageDir; final AnalysisSession _session; final PubspecCache _pubspecCache; final bool _isBinaryOnly; @@ -115,14 +116,15 @@ class Tagger { final PackageGraph _packageGraph; Tagger._( - this.packageName, - this._session, - PubspecCache pubspecCache, - this._isBinaryOnly, - this._usesFlutter, - this._topLibraries, - this._publicLibraries, - ) : _pubspecCache = pubspecCache, + this.packageName, + this._session, + PubspecCache pubspecCache, + this._isBinaryOnly, + this._usesFlutter, + this._topLibraries, + this._publicLibraries, + this.packageDir) + : _pubspecCache = pubspecCache, _packageGraph = PackageGraph(pubspecCache); /// Assumes that `dart pub get` has been run. @@ -170,15 +172,8 @@ class Tagger { final publicLibraries = nonSrcDartFiles .map((s) => Uri.parse('package:${pubspec.name}/$s')) .toList(); - return Tagger._( - pubspec.name, - session, - pubspecCache, - isBinaryOnly, - pubspec.usesFlutter, - topLibraries, - publicLibraries, - ); + return Tagger._(pubspec.name, session, pubspecCache, isBinaryOnly, + pubspec.usesFlutter, topLibraries, publicLibraries, packageDir); } void sdkTags(List tags, List explanations) { @@ -355,6 +350,52 @@ class Tagger { } } + void swiftPackageManagerPluginTag( + List tags, List explanations) { + if (!_usesFlutter) return; + final pubspec = _pubspecCache.pubspecOfPackage(packageName); + + bool pathExists(dynamic m, List path) { + dynamic current = m; + for (final e in path) { + if (current is! Map) return false; + if (!current.containsKey(e)) return false; + current = current[e]; + } + return true; + } + + var isDarwinPlugin = false; + var swiftPmSupport = true; + + for (final darwinOs in ['macos', 'ios']) { + if (pathExists( + pubspec.originalYaml, ['flutter', 'plugin', 'platforms', darwinOs])) { + isDarwinPlugin = true; + final osDir = pubspec.originalYaml['flutter']?['plugin']?['platforms'] + ?[darwinOs]?['sharedDarwinSource'] == + true + ? 'darwin' + : darwinOs; + + final packageSwiftFile = path.join(osDir, packageName, 'Package.swift'); + if (!File(path.join(packageDir, packageSwiftFile)).existsSync()) { + swiftPmSupport = false; + final osName = {'macos': 'macOS', 'ios': 'iOS'}[darwinOs]; + explanations.add(Explanation( + 'Package does not support the Swift Package Manager on $osName', + ''' +It does not contain `$packageSwiftFile`. +''', + tag: PanaTags.isSwiftPmPlugin)); + } + } + } + if (isDarwinPlugin && swiftPmSupport) { + tags.add(PanaTags.isSwiftPmPlugin); + } + } + /// Adds tags for the Dart runtimes that this package supports to [tags]. /// /// Adds [Explanation]s to [explanations] for runtimes not supported. diff --git a/test/goldens/end2end/audio_service-0.18.10.json b/test/goldens/end2end/audio_service-0.18.10.json index d5bd2c79b..e9d6026f5 100644 --- a/test/goldens/end2end/audio_service-0.18.10.json +++ b/test/goldens/end2end/audio_service-0.18.10.json @@ -155,7 +155,7 @@ "grantedPoints": 20, "maxPoints": 20, "status": "failed", - "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
\n\nPackage does not support platform `Windows`.\n\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n
\n\n
\n\nPackage does not support platform `Linux`.\n\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n
\n\n\nThese issues are present but do not affect the score, because they may not originate in your package:\n\n
\n\nPackage does not support platform `Web`.\n\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
\n\n### [x] 0/0 points: WASM compatibility\n\n
\n\nPackage not compatible with runtime wasm\n\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
\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" + "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
\n\nPackage does not support platform `Windows`.\n\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n
\n\n
\n\nPackage does not support platform `Linux`.\n\n\nBecause:\n* `package:audio_service/audio_service.dart` that declares support for platforms: `Android`, `iOS`, `macOS`, `Web`.\n
\n\n\nThese issues are present but do not affect the score, because they may not originate in your package:\n\n
\n\nPackage does not support platform `Web`.\n\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
\n\n### [x] 0/0 points: WASM compatibility\n\n
\n\nPackage not compatible with runtime wasm\n\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
\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
\n\nPackage does not support the Swift Package Manager on macOS\n\n\nIt does not contain `macos/audio_service/Package.swift`.\n\n
\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" }, { "id": "analysis", diff --git a/test/goldens/end2end/audio_service-0.18.10.json_report.md b/test/goldens/end2end/audio_service-0.18.10.json_report.md index f479a09bf..cff71fc20 100644 --- a/test/goldens/end2end/audio_service-0.18.10.json_report.md +++ b/test/goldens/end2end/audio_service-0.18.10.json_report.md @@ -88,6 +88,21 @@ This package is not compatible with runtime `wasm`, and will not be rewarded ful See https://dart.dev/web/wasm for details. +### [x] 0/0 points: Swift Package Manager support + +
+ +Package does not support the Swift Package Manager on macOS + + +It does not contain `macos/audio_service/Package.swift`. + +
+ +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. + +See https://docs.flutter.dev/to/spm for details. + ## 50/50 Pass static analysis diff --git a/test/goldens/end2end/url_launcher-6.1.12.json b/test/goldens/end2end/url_launcher-6.1.12.json index fe039a500..461acfa35 100644 --- a/test/goldens/end2end/url_launcher-6.1.12.json +++ b/test/goldens/end2end/url_launcher-6.1.12.json @@ -133,8 +133,8 @@ "title": "Platform support", "grantedPoints": 20, "maxPoints": 20, - "status": "passed", - "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" + "status": "failed", + "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
\n\nPackage does not support the Swift Package Manager on macOS\n\n\nIt does not contain `macos/url_launcher/Package.swift`.\n\n
\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" }, { "id": "analysis", diff --git a/test/goldens/end2end/url_launcher-6.1.12.json_report.md b/test/goldens/end2end/url_launcher-6.1.12.json_report.md index 8e729700d..45f54ceda 100644 --- a/test/goldens/end2end/url_launcher-6.1.12.json_report.md +++ b/test/goldens/end2end/url_launcher-6.1.12.json_report.md @@ -52,6 +52,21 @@ This package is compatible with runtime `wasm`, and will be rewarded additional See https://dart.dev/web/wasm for details. +### [x] 0/0 points: Swift Package Manager support + +
+ +Package does not support the Swift Package Manager on macOS + + +It does not contain `macos/url_launcher/Package.swift`. + +
+ +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. + +See https://docs.flutter.dev/to/spm for details. + ## 50/50 Pass static analysis diff --git a/test/tag/tag_end2end_test.dart b/test/tag/tag_end2end_test.dart index 4ceb5c1e8..a23a157d6 100644 --- a/test/tag/tag_end2end_test.dart +++ b/test/tag/tag_end2end_test.dart @@ -578,6 +578,81 @@ name: my_package }); }); + group('swift package manager tag', () { + test('Works with ios//Package.swift', () async { + final descriptor = d.dir('cache', [ + packageWithPathDeps('my_package', pubspecExtras: { + 'flutter': { + 'plugin': { + 'platforms': {'ios': {}} + } + } + }, extraFiles: [ + d.dir('ios', [ + d.dir('my_package', [d.file('Package.swift')]) + ]), + ]) + ]); + await descriptor.create(); + final tagger = Tagger('${descriptor.io.path}/my_package'); + _expectTagging(tagger.swiftPackageManagerPluginTag, + tags: contains('is:swiftpm-plugin')); + }); + + test('Works with darwin/package_name/Package.swift', () async { + final descriptor = d.dir('cache', [ + packageWithPathDeps('my_package', pubspecExtras: { + 'flutter': { + 'plugin': { + 'platforms': { + 'ios': {'sharedDarwinSource': true}, + 'macos': {'sharedDarwinSource': true} + } + } + } + }, extraFiles: [ + d.dir('darwin', [ + d.dir('my_package', [d.file('Package.swift')]) + ]), + ]) + ]); + await descriptor.create(); + final tagger = Tagger('${descriptor.io.path}/my_package'); + _expectTagging(tagger.swiftPackageManagerPluginTag, + tags: contains('is:swiftpm-plugin')); + }); + + test('Fails with the wrong os/package_name/Package.swift', () async { + final descriptor = d.dir('cache', [ + packageWithPathDeps('my_package', pubspecExtras: { + 'flutter': { + 'plugin': { + 'platforms': {'macos': {}} + } + } + }, extraFiles: [ + d.dir('ios', [ + d.dir('my_package', [d.file('Package.swift')]) + ]), + ]) + ]); + await descriptor.create(); + final tagger = Tagger('${descriptor.io.path}/my_package'); + _expectTagging(tagger.swiftPackageManagerPluginTag, + tags: isEmpty, + explanations: contains(isA() + .having((e) => e.tag, 'tag', 'is:swiftpm-plugin'))); + }); + + test('Does not complain about non-plugins', () async { + final descriptor = d.dir('cache', [packageWithPathDeps('my_package')]); + await descriptor.create(); + final tagger = Tagger('${descriptor.io.path}/my_package'); + _expectTagging(tagger.swiftPackageManagerPluginTag, + tags: isEmpty, explanations: isEmpty); + }); + }); + group('wasm tag', () { test('Excluded with dart:js', () async { final descriptor = d.dir('cache', [