From 3de2d4ef7ccff954b65155958757771f7ffe48d6 Mon Sep 17 00:00:00 2001 From: Sigurd Meldgaard Date: Fri, 27 Sep 2024 11:44:03 +0000 Subject: [PATCH] Allow calling `findExtensions` with packageDir --- .../lib/extension_discovery.dart | 57 ++++++++++++++----- .../test/find_extensions_test.dart | 56 ++++++++++++++++++ .../test/test_descriptor.dart | 2 +- 3 files changed, 100 insertions(+), 15 deletions(-) diff --git a/pkgs/extension_discovery/lib/extension_discovery.dart b/pkgs/extension_discovery/lib/extension_discovery.dart index 22a5fedb6..1d9f28447 100644 --- a/pkgs/extension_discovery/lib/extension_discovery.dart +++ b/pkgs/extension_discovery/lib/extension_discovery.dart @@ -78,9 +78,14 @@ final class Extension { /// ## Locating `.dart_tool/package_config.json` /// /// This method requires the location of the [packageConfig], unless the current -/// isolate has been setup for package resolution. -/// Notably, Dart programs compiled for AOT cannot find their own -/// `package_config.json`. +/// isolate has been setup for package resolution. Notably, Dart programs +/// compiled for AOT cannot find their own `package_config.json`. +/// +/// Alternatively a [packageRootDir] can be provided. That is a file uri the the +/// directory containing the `pubspec.yaml` of [targetPackage]. In that case the +/// package_config will be searched by looking in all parent directories. +/// +/// It is an error to specify both [packageRootDir] and [packageConfig]. /// /// If operating on a project that isn't the current project, for example, if /// you are developing a tool that users are globally activating and then @@ -100,17 +105,15 @@ final class Extension { /// /// ### Caching results /// -/// When [useCache] is `true` then the detected extensions will be cached -/// in `.dart_tool/extension_discovery/.yaml`. -/// This function will compare modification timestamps of -/// `.dart_tool/package_config.json` with the cache file, before reusing cached -/// results. -/// This function will also treat relative path-dependencies as mutable -/// packages, and check such packages for extensions every time [findExtensions] -/// is called. Notably, it'll compare the modification time of the -/// `extension//config.yaml` file, to ensure that it's older than -/// the extension cache file. Otherwise, it'll reload the extension -/// configuration. +/// When [useCache] is `true` then the detected extensions will be cached in +/// `.dart_tool/extension_discovery/.yaml`. This function will +/// compare modification timestamps of `.dart_tool/package_config.json` with the +/// cache file, before reusing cached results. This function will also treat +/// relative path-dependencies as mutable packages, and check such packages for +/// extensions every time [findExtensions] is called. Notably, it'll compare the +/// modification time of the `extension//config.yaml` file, to +/// ensure that it's older than the extension cache file. Otherwise, it'll +/// reload the extension configuration. /// /// ## Exceptions /// @@ -128,8 +131,18 @@ Future> findExtensions( String targetPackage, { bool useCache = true, Uri? packageConfig, + Uri? packageRootDir, }) async { + if (packageRootDir != null) { + if (packageConfig != null) { + throw ArgumentError( + 'Specify only one of `packageConfig` and `packageRootDir`.', + ); + } + packageConfig = _findPackageConfig(packageRootDir); + } packageConfig ??= await Isolate.packageConfig; + if (packageConfig == null) { throw UnsupportedError( 'packageConfigUri must be provided, if not running in JIT mode', @@ -156,6 +169,22 @@ Future> findExtensions( ); } +Uri? _findPackageConfig(Uri packageDir) { + if (!packageDir.path.endsWith('/')) { + packageDir = packageDir.replace(path: '${packageDir.path}/'); + } + while (true) { + final packageConfigCandidate = + packageDir.resolve('.dart_tool/package_config.json'); + if (File.fromUri(packageConfigCandidate).existsSync()) { + return packageConfigCandidate; + } + final next = packageDir.resolve('..'); + if (next == packageDir) return null; + packageDir = next; + } +} + /// Find extensions with normalized arguments. Future> _findExtensions({ required String targetPackage, diff --git a/pkgs/extension_discovery/test/find_extensions_test.dart b/pkgs/extension_discovery/test/find_extensions_test.dart index 33e8bf7d6..2244e36d0 100644 --- a/pkgs/extension_discovery/test/find_extensions_test.dart +++ b/pkgs/extension_discovery/test/find_extensions_test.dart @@ -347,4 +347,60 @@ writtenAsYaml: true ); } }); + + test('finds extensions in a workspace with `packageDir`', () async { + final pkgLibDir = await Isolate.resolvePackageUri( + Uri.parse('package:extension_discovery/'), + ); + final pkgDir = pkgLibDir!.resolve('..'); + + await d.dir('workspace', [ + d.pubspec({ + 'name': '_', + 'environment': {'sdk': '^3.5.0'}, + 'workspace': ['myapp/'], + }), + d.dir('myapp', [ + d.pubspec({ + 'name': 'myapp', + 'dependencies': { + 'extension_discovery': {'path': pkgDir.toFilePath()}, + 'foo': {'path': '../../foo'}, + }, + 'environment': {'sdk': '^3.5.0'}, + 'resolution': 'workspace' + }) + ]) + ]).create(); + + await d.dir('foo', [ + d.pubspec({ + 'name': 'foo', + 'environment': {'sdk': '^3.0.0'}, + }), + // It has a config.yaml for myapp + d.dir('extension/myapp', [ + d.json('config.yaml', {'fromFoo': true}), + ]), + ]).create(); + + // Get dependencies + await d.dartPubGet(d.path('workspace')); + final [extension] = await findExtensions( + 'myapp', + packageRootDir: d.fileUri('workspace/myapp'), + ); + expect(extension.config, {'fromFoo': true}); + expect(extension.package, 'foo'); + expect(extension.rootUri, d.fileUri('foo/')); + expect(extension.packageUri, Uri.parse('lib/')); + + // Check that there is a myapp.json cache file + await d + .file( + 'workspace/.dart_tool/extension_discovery/myapp.json', + isNotEmpty, + ) + .validate(); + }); } diff --git a/pkgs/extension_discovery/test/test_descriptor.dart b/pkgs/extension_discovery/test/test_descriptor.dart index e0dc4a779..0bd4a63a7 100644 --- a/pkgs/extension_discovery/test/test_descriptor.dart +++ b/pkgs/extension_discovery/test/test_descriptor.dart @@ -53,4 +53,4 @@ Future dart([ } Future dartPubGet(String folder) async => - await dart('pub', 'get', '-C', d.path('myapp')); + await dart('pub', 'get', '-C', folder);