diff --git a/doc/repository-spec-v2.md b/doc/repository-spec-v2.md index 55ccbb0..224aa2d 100644 --- a/doc/repository-spec-v2.md +++ b/doc/repository-spec-v2.md
@@ -78,7 +78,7 @@ coming from. Including a URL allowing operators to reach owners/authors of the client is good practice. - * `User-Agent: my-pub-bot/1.2.3 (+https://github.com/organization/<repository)` + * `User-Agent: my-pub-bot/1.2.3 (+https://github.com/organization/<repository>)` The `User-Agent` header also allows package repository to determine how many different clients would be affected by an API change.
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart index 45833d4..46c1b94 100644 --- a/lib/src/command/dependency_services.dart +++ b/lib/src/command/dependency_services.dart
@@ -8,6 +8,7 @@ import 'dart:io'; import 'package:collection/collection.dart'; +import 'package:path/path.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; import 'package:yaml_edit/yaml_edit.dart'; @@ -367,7 +368,11 @@ final updatedLockfile = lockFileEditor == null ? null - : LockFile.parse(lockFileEditor.toString(), cache.sources); + : LockFile.parse( + lockFileEditor.toString(), + cache.sources, + filePath: entrypoint.lockFilePath, + ); await log.warningsOnlyUnlessTerminal( () async { final updatedPubspec = pubspecEditor.toString(); @@ -381,7 +386,8 @@ final solveResult = await resolveVersions( SolveType.get, cache, - Package.inMemory(Pubspec.parse(updatedPubspec, cache.sources)), + Package.inMemory(Pubspec.parse(updatedPubspec, cache.sources, + location: toUri(entrypoint.pubspecPath))), lockFile: updatedLockfile, ); if (pubspecEditor.edits.isNotEmpty) {
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 42b3732..b4a01a6 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart
@@ -5,10 +5,12 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:math'; import 'package:collection/collection.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; +import 'package:pool/pool.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; @@ -396,10 +398,13 @@ } else { ensureDir(_snapshotPath); } - return waitAndPrintErrors(executables.map((executable) { - var dir = p.dirname(pathOfExecutable(executable)); - cleanDir(dir); - return _precompileExecutable(executable); + // Don't do more than `Platform.numberOfProcessors - 1` compilations + // concurrently. Though at least one. + final pool = Pool(max(Platform.numberOfProcessors - 1, 1)); + return waitAndPrintErrors(executables.map((executable) async { + await pool.withResource(() async { + return _precompileExecutable(executable); + }); })); }); }
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index cc0bfdf..82b1263 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart
@@ -92,7 +92,7 @@ String? path, String? ref, }) async { - var name = await cache.git.getPackageNameFromRepo(repo, cache); + var name = await cache.git.getPackageNameFromRepo(repo, ref, path, cache); // TODO(nweiz): Add some special handling for git repos that contain path // dependencies. Their executables shouldn't be cached, and there should
diff --git a/lib/src/lock_file.dart b/lib/src/lock_file.dart index 0efc3af..0950de1 100644 --- a/lib/src/lock_file.dart +++ b/lib/src/lock_file.dart
@@ -81,8 +81,12 @@ } /// Parses a lockfile whose text is [contents]. - factory LockFile.parse(String contents, SourceRegistry sources) { - return LockFile._parse(null, contents, sources); + /// + /// If [filePath] is given, path-dependencies will be interpreted relative to + /// that. + factory LockFile.parse(String contents, SourceRegistry sources, + {String? filePath}) { + return LockFile._parse(filePath, contents, sources); } /// Parses the lockfile whose text is [contents].
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart index 0d26283..3cfd32d 100644 --- a/lib/src/source/git.dart +++ b/lib/src/source/git.dart
@@ -196,11 +196,13 @@ /// Given a Git repo that contains a pub package, gets the name of the pub /// package. - Future<String> getPackageNameFromRepo(String repo, SystemCache cache) { + Future<String> getPackageNameFromRepo( + String repo, String? ref, String? path, SystemCache cache) { // Clone the repo to a temp directory. return withTempDir((tempDir) async { await _clone(repo, tempDir, shallow: true); - var pubspec = Pubspec.load(tempDir, cache.sources); + if (ref != null) await _checkOut(tempDir, ref); + var pubspec = Pubspec.load(p.join(tempDir, path), cache.sources); return pubspec.name; }); } @@ -641,6 +643,9 @@ /// If the url was relative in the pubspec.yaml it will be resolved relative /// to the pubspec location, and stored here as an absolute file url, and /// [relative] will be true. + /// + /// This will not always parse as a [Uri] due the fact that `Uri.parse` does not allow strings of + /// the form: 'git@github.com:dart-lang/pub.git'. final String url; /// `true` if [url] was parsed from a relative url. @@ -679,7 +684,7 @@ @override String format() { - var result = '${p.prettyUri(url)} at ' + var result = '${prettyUri(url)} at ' '$ref'; if (path != '.') result += ' in $path'; return result; @@ -715,6 +720,18 @@ @override int get hashCode => Object.hash(url, ref, path); + + // Similar in intend to [p.prettyUri] but does not fail if the input doesn't + // parse with [Uri.parse]. + static String prettyUri(String url) { + // HACK: Working around the fact that `Uri.parse` does not allow strings of + // the form: 'git@github.com:dart-lang/pub.git'. + final parsedAsUri = Uri.tryParse(url); + if (parsedAsUri == null) { + return url; + } + return p.prettyUri(url); + } } class GitResolvedDescription extends ResolvedDescription { @@ -727,7 +744,7 @@ @override String format() { - var result = '${p.prettyUri(description.url)} at ' + var result = '${GitDescription.prettyUri(description.url)} at ' '${resolvedRef.substring(0, 6)}'; if (description.path != '.') result += ' in ${description.path}'; return result; @@ -736,8 +753,8 @@ @override Object? serializeForLockfile({required String? containingDir}) { final url = description.relative && containingDir != null - ? p.url - .relative(description.url, from: Uri.file(containingDir).toString()) + ? p.url.relative(description.url, + from: Uri.file(p.absolute(containingDir)).toString()) : description.url; return { 'url': url,
diff --git a/lib/src/validator/strict_dependencies.dart b/lib/src/validator/strict_dependencies.dart index 84f7a09..ea481f5 100644 --- a/lib/src/validator/strict_dependencies.dart +++ b/lib/src/validator/strict_dependencies.dart
@@ -42,11 +42,9 @@ for (var directive in directives) { Uri? url; - try { - url = Uri.parse(directive.uri.stringValue!); - } on FormatException catch (_) { - // Ignore a format exception. [url] will be null, and we'll emit an - // "Invalid URL" warning below. + final uriString = directive.uri.stringValue; + if (uriString != null) { + url = Uri.tryParse(uriString); } // If the URL could not be parsed or it is a `package:` URL AND there
diff --git a/test/dependency_services/dependency_services_test.dart b/test/dependency_services/dependency_services_test.dart index ed21a19..3830e27 100644 --- a/test/dependency_services/dependency_services_test.dart +++ b/test/dependency_services/dependency_services_test.dart
@@ -250,6 +250,28 @@ ); }); }); + testWithGolden('Relative paths are allowed', (context) async { + // We cannot update path-dependencies, but they should be allowed. + final server = await servePackages(); + server.serve('foo', '1.0.0'); + await d.dir('bar', [d.libPubspec('bar', '1.0.0')]).create(); + + await d.appDir({ + 'foo': '^1.0.0', + 'bar': {'path': '../bar'} + }).create(); + await pubGet(); + server.serve('foo', '2.0.0'); + await listReportApply(context, [ + _PackageVersion('foo', Version.parse('2.0.0'), + constraint: VersionConstraint.parse('^2.0.0')), + ], reportAssertions: (report) { + expect( + findChangeVersion(report, 'multiBreaking', 'foo'), + '2.0.0', + ); + }); + }); } dynamic findChangeVersion(dynamic json, String updateType, String name) {
diff --git a/test/get/git/check_out_test.dart b/test/get/git/check_out_test.dart index f3caaa2..ada5461 100644 --- a/test/get/git/check_out_test.dart +++ b/test/get/git/check_out_test.dart
@@ -9,6 +9,7 @@ import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; @@ -26,6 +27,12 @@ await pubGet(); + final lockfile = loadYaml( + File(p.join(d.sandbox, appPath, 'pubspec.lock')).readAsStringSync()); + expect(lockfile['packages']['foo']['description']['url'], '../foo.git', + reason: + 'The relative path should be preserved, and be a url (forward slashes on all platforms)'); + await d.dir(cachePath, [ d.dir('git', [ d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
diff --git a/test/get/git/ssh_url_test.dart b/test/get/git/ssh_url_test.dart new file mode 100644 index 0000000..e38f895 --- /dev/null +++ b/test/get/git/ssh_url_test.dart
@@ -0,0 +1,57 @@ +// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:pub/src/language_version.dart'; + +import 'package:pub/src/source/git.dart'; + +import 'package:test/test.dart'; + +void main() { + // These tests are not integration tests because it seems to be hard to + // actually test this kind of urls locally. We would have to set up serving + // git over ssh. + // + // We could set up a local cache, and only test the '--offline' part of this. + // But for now we live with this. + test( + 'Git description uris can be of the form git@github.com:dart-lang/pub.git', + () { + final description = GitDescription( + url: 'git@github.com:dart-lang/pub.git', + ref: 'main', + path: 'abc/', + containingDir: null, + ); + expect(description.format(), + 'git@github.com:dart-lang/pub.git at main in abc/'); + expect( + description.serializeForPubspec( + containingDir: null, + languageVersion: LanguageVersion(2, 16), + ), + { + 'url': 'git@github.com:dart-lang/pub.git', + 'ref': 'main', + 'path': 'abc/', + }, + ); + final resolvedDescription = GitResolvedDescription( + description, '7d48f902b0326fc2ce0615c20f1aab6c811fe55b'); + + expect( + resolvedDescription.format(), + 'git@github.com:dart-lang/pub.git at 7d48f9 in abc/', + ); + expect( + resolvedDescription.serializeForLockfile(containingDir: null), + { + 'url': 'git@github.com:dart-lang/pub.git', + 'ref': 'main', + 'path': 'abc/', + 'resolved-ref': '7d48f902b0326fc2ce0615c20f1aab6c811fe55b', + }, + ); + }); +}
diff --git a/test/get/with_empty_environment_test.dart b/test/get/with_empty_environment_test.dart index 30768c4..5dab9b3 100644 --- a/test/get/with_empty_environment_test.dart +++ b/test/get/with_empty_environment_test.dart
@@ -2,8 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'dart:io'; - import 'package:test/test.dart'; import '../descriptor.dart' as d; @@ -18,10 +16,6 @@ await pubGet(environment: { '_PUB_TEST_CONFIG_DIR': null, - if (Platform.isWindows) ...{ - 'SYSTEMROOT': Platform.environment['SYSTEMROOT'], - 'TMP': Platform.environment['TMP'], - }, - }, includeParentEnvironment: false); + }, includeParentHomeAndPath: false); }); }
diff --git a/test/global/activate/git_package_test.dart b/test/global/activate/git_package_test.dart index 02d06db..ad3ff27 100644 --- a/test/global/activate/git_package_test.dart +++ b/test/global/activate/git_package_test.dart
@@ -38,7 +38,7 @@ 'sub', [ d.libPubspec('foo', '1.0.0'), - d.dir('bin', [d.file('foo.dart', "main() => print('1');")]) + d.dir('bin', [d.file('sub.dart', "main() => print('1');")]) ], ), ]).create(); @@ -46,8 +46,8 @@ d.dir( 'sub', [ - d.libPubspec('foo', '2.0.0'), - d.dir('bin', [d.file('foo.dart', "main() => print('2');")]) + d.libPubspec('sub', '2.0.0'), + d.dir('bin', [d.file('sub.dart', "main() => print('2');")]) ], ), ]).commit(); @@ -55,8 +55,8 @@ d.dir( 'sub', [ - d.libPubspec('foo', '3.0.0'), - d.dir('bin', [d.file('foo.dart', "main() => print('3');")]) + d.libPubspec('sub', '3.0.0'), + d.dir('bin', [d.file('sub.dart', "main() => print('3');")]) ], ), ]).commit(); @@ -72,19 +72,19 @@ ], output: allOf( startsWith('Resolving dependencies...\n' - '+ foo 2.0.0 from git ..${p.separator}foo.git at'), + '+ sub 2.0.0 from git ..${p.separator}foo.git at'), // Specific revision number goes here. contains('in sub'), endsWith('Building package executables...\n' - 'Built foo:foo.\n' - 'Activated foo 2.0.0 from Git repository "..${p.separator}foo.git".'), + 'Built sub:sub.\n' + 'Activated sub 2.0.0 from Git repository "..${p.separator}foo.git".'), ), ); await runPub( args: [ 'global', 'run', - 'foo', + 'sub', ], output: contains('2'), );
diff --git a/test/test_pub.dart b/test/test_pub.dart index e7e7bc7..3c2f102 100644 --- a/test/test_pub.dart +++ b/test/test_pub.dart
@@ -130,7 +130,7 @@ int? exitCode, Map<String, String?>? environment, String? workingDirectory, - includeParentEnvironment = true, + includeParentHomeAndPath = true, }) async { if (error != null && warning != null) { throw ArgumentError("Cannot pass both 'error' and 'warning'."); @@ -155,7 +155,7 @@ exitCode: exitCode, environment: environment, workingDirectory: workingDirectory, - includeParentEnvironment: includeParentEnvironment); + includeParentHomeAndPath: includeParentHomeAndPath); } Future<void> pubAdd({ @@ -186,7 +186,7 @@ int? exitCode, Map<String, String?>? environment, String? workingDirectory, - bool includeParentEnvironment = true, + bool includeParentHomeAndPath = true, }) async => await pubCommand( RunCommand.get, @@ -197,7 +197,7 @@ exitCode: exitCode, environment: environment, workingDirectory: workingDirectory, - includeParentEnvironment: includeParentEnvironment, + includeParentHomeAndPath: includeParentHomeAndPath, ); Future<void> pubUpgrade( @@ -322,7 +322,7 @@ String? workingDirectory, Map<String, String?>? environment, List<String>? input, - includeParentEnvironment = true}) async { + includeParentHomeAndPath = true}) async { exitCode ??= exit_codes.SUCCESS; // Cannot pass both output and outputJson. assert(output == null || outputJson == null); @@ -331,7 +331,7 @@ args: args, workingDirectory: workingDirectory, environment: environment, - includeParentEnvironment: includeParentEnvironment, + includeParentHomeAndPath: includeParentHomeAndPath, ); if (input != null) { @@ -446,7 +446,7 @@ String? workingDirectory, Map<String, String?>? environment, bool verbose = true, - includeParentEnvironment = true}) async { + includeParentHomeAndPath = true}) async { args ??= []; ensureDir(_pathInSandbox(appPath)); @@ -468,7 +468,21 @@ ..addAll([pubPath, if (!verbose) '--verbosity=normal']) ..addAll(args); - final mergedEnvironment = getPubTestEnvironment(tokenEndpoint); + final systemRoot = Platform.environment['SYSTEMROOT']; + final tmp = Platform.environment['TMP']; + + final mergedEnvironment = { + if (includeParentHomeAndPath) ...{ + 'HOME': Platform.environment['HOME'] ?? '', + 'PATH': Platform.environment['PATH'] ?? '', + }, + // These seem to be needed for networking to work. + if (Platform.isWindows) ...{ + if (systemRoot != null) 'SYSTEMROOT': systemRoot, + if (tmp != null) 'TMP': tmp, + }, + ...getPubTestEnvironment(tokenEndpoint) + }; for (final e in (environment ?? {}).entries) { var value = e.value; if (value == null) { @@ -482,7 +496,7 @@ environment: mergedEnvironment, workingDirectory: workingDirectory ?? _pathInSandbox(appPath), description: args.isEmpty ? 'pub' : 'pub ${args.first}', - includeParentEnvironment: includeParentEnvironment); + includeParentEnvironment: false); } /// A subclass of [TestProcess] that parses pub's verbose logging output and
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Relative paths are allowed.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Relative paths are allowed.txt new file mode 100644 index 0000000..bbe5052 --- /dev/null +++ b/test/testdata/goldens/dependency_services/dependency_services_test/Relative paths are allowed.txt
@@ -0,0 +1,126 @@ +# GENERATED BY: test/dependency_services/dependency_services_test.dart + +$ cat pubspec.yaml +{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":"^1.0.0","bar":{"path":"../bar"}}} +$ cat pubspec.lock +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + bar: + dependency: "direct main" + description: + path: "../bar" + relative: true + source: path + version: "1.0.0" + foo: + dependency: "direct main" + description: + name: foo + url: "http://localhost:$PORT" + source: hosted + version: "1.0.0" +sdks: + dart: ">=0.1.2 <1.0.0" +-------------------------------- END OF OUTPUT --------------------------------- + +## Section list +$ dependency_services list +{ + "dependencies": [ + { + "name": "bar", + "version": "1.0.0", + "kind": "direct", + "constraint": "any" + }, + { + "name": "foo", + "version": "1.0.0", + "kind": "direct", + "constraint": "^1.0.0" + } + ] +} + +-------------------------------- END OF OUTPUT --------------------------------- + +## Section report +$ dependency_services report +{ + "dependencies": [ + { + "name": "bar", + "version": "1.0.0", + "kind": "direct", + "latest": "1.0.0", + "constraint": "any", + "compatible": [], + "singleBreaking": [], + "multiBreaking": [] + }, + { + "name": "foo", + "version": "1.0.0", + "kind": "direct", + "latest": "2.0.0", + "constraint": "^1.0.0", + "compatible": [], + "singleBreaking": [ + { + "name": "foo", + "version": "2.0.0", + "kind": "direct", + "constraintBumped": "^2.0.0", + "constraintWidened": ">=1.0.0 <3.0.0", + "constraintBumpedIfNeeded": "^2.0.0", + "previousVersion": "1.0.0", + "previousConstraint": "^1.0.0" + } + ], + "multiBreaking": [ + { + "name": "foo", + "version": "2.0.0", + "kind": "direct", + "constraintBumped": "^2.0.0", + "constraintWidened": ">=1.0.0 <3.0.0", + "constraintBumpedIfNeeded": "^2.0.0", + "previousVersion": "1.0.0", + "previousConstraint": "^1.0.0" + } + ] + } + ] +} + +-------------------------------- END OF OUTPUT --------------------------------- + +## Section apply +$ echo '{"dependencyChanges":[{"name":"foo","version":"2.0.0","constraint":"^2.0.0"}]}' | dependency_services apply +{"dependencies":[]} + +-------------------------------- END OF OUTPUT --------------------------------- + +$ cat pubspec.yaml +{"name":"myapp","environment":{"sdk":">=0.1.2 <1.0.0"},"dependencies":{"foo":^2.0.0,"bar":{"path":"../bar"}}} +$ cat pubspec.lock +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + bar: + dependency: "direct main" + description: + path: "../bar" + relative: true + source: path + version: "1.0.0" + foo: + dependency: "direct main" + description: + name: foo + url: "http://localhost:$PORT" + source: hosted + version: "2.0.0" +sdks: + dart: ">=0.1.2 <1.0.0"
diff --git a/test/token/add_token_test.dart b/test/token/add_token_test.dart index 65f7d56..e8d0dc4 100644 --- a/test/token/add_token_test.dart +++ b/test/token/add_token_test.dart
@@ -147,7 +147,7 @@ error: contains('No config dir found.'), exitCode: exit_codes.DATA, environment: {'_PUB_TEST_CONFIG_DIR': null}, - includeParentEnvironment: false, + includeParentHomeAndPath: false, ); });
diff --git a/test/token/remove_token_test.dart b/test/token/remove_token_test.dart index bfe2428..df880a4 100644 --- a/test/token/remove_token_test.dart +++ b/test/token/remove_token_test.dart
@@ -66,7 +66,7 @@ error: contains('No config dir found.'), exitCode: exit_codes.DATA, environment: {'_PUB_TEST_CONFIG_DIR': null}, - includeParentEnvironment: false, + includeParentHomeAndPath: false, ); }); }
diff --git a/test/validator/strict_dependencies_test.dart b/test/validator/strict_dependencies_test.dart index a6b0833..18ee9c7 100644 --- a/test/validator/strict_dependencies_test.dart +++ b/test/validator/strict_dependencies_test.dart
@@ -183,6 +183,14 @@ group('should consider a package invalid if it', () { setUp(d.validPackage.create); + test('has an invalid String value', () async { + await d.file(path.join(appPath, 'lib', 'library.dart'), r''' + import 'package:$bad'; + ''').create(); + + await expectValidation(strictDeps, errors: [matches('Invalid URL.')]); + }); + test('does not declare an "import" as a dependency', () async { await d.file(path.join(appPath, 'lib', 'library.dart'), r''' import 'package:silly_monkey/silly_monkey.dart';
diff --git a/tool/test.dart b/tool/test.dart index 7d98156..3aabf88 100755 --- a/tool/test.dart +++ b/tool/test.dart
@@ -17,6 +17,11 @@ import 'package:pub/src/exceptions.dart'; Future<void> main(List<String> args) async { + if (Platform.environment['FLUTTER_ROOT'] != null) { + print( + 'WARNING: The tests will not run correctly with dart from a flutter checkout!', + ); + } Process? testProcess; final sub = ProcessSignal.sigint.watch().listen((signal) { testProcess?.kill(signal);