Dependency services: preserve pub.dartlang.org in lockfile (#3846)
diff --git a/lib/src/command/dependency_services.dart b/lib/src/command/dependency_services.dart index 00cbe2e..c56f413 100644 --- a/lib/src/command/dependency_services.dart +++ b/lib/src/command/dependency_services.dart
@@ -381,6 +381,7 @@ final lockFileYaml = lockFile == null ? null : loadYaml(lockFile); final lockFileEditor = lockFile == null ? null : YamlEditor(lockFile); final hasContentHashes = _lockFileHasContentHashes(lockFileYaml); + final usesPubDev = _lockFileUsesPubDev(lockFileYaml); for (final p in toApply) { final targetPackage = p.name; final targetVersion = p.version; @@ -513,7 +514,6 @@ for (var package in solveResult.packages) { if (package.isRoot) continue; final description = package.description; - // Handle content-hashes of hosted dependencies. if (description is ResolvedHostedDescription) { // Ensure we get content-hashes if the original lock-file had @@ -547,6 +547,23 @@ description.withSha256(null), ); } + // Keep using https://pub.dartlang.org if the original lockfile + // used it. This is to support lockfiles from old sdks. + if (!usesPubDev && + HostedSource.isPubDevUrl(description.description.url)) { + package = PackageId( + package.name, + package.version, + ResolvedHostedDescription( + HostedDescription.raw( + package.name, + HostedSource.pubDartlangUrl, + ), + sha256: (package.description as ResolvedHostedDescription) + .sha256, + ), + ); + } } updatedPackages.add(package); } @@ -695,3 +712,28 @@ } return false; } + +/// `true` iff any of the packages described by the [lockfile] uses +/// `https://pub.dev` as url. +/// +/// Undefined for invalid lock files, but mostly `true`. +bool _lockFileUsesPubDev(dynamic lockfile) { + if (lockfile is! Map) return true; + final packages = lockfile['packages']; + if (packages is! Map) return true; + + /// We consider an empty lockfile ready to get content-hashes. + if (packages.isEmpty) return true; + for (final package in packages.values) { + if (package is! Map) return true; + if (package['source'] != 'hosted') continue; + final descriptor = package['description']; + if (descriptor is! Map) return true; + final url = descriptor['url']; + if (url is! String) return true; + if (HostedSource.isPubDevUrl(url) && url != HostedSource.pubDartlangUrl) { + return true; + } + } + return false; +}
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart index 2086ed0..6f6632c 100644 --- a/lib/src/source/hosted.dart +++ b/lib/src/source/hosted.dart
@@ -128,7 +128,16 @@ static String pubDartlangUrl = 'https://pub.dartlang.org'; static bool isPubDevUrl(String url) { - final origin = Uri.parse(url).origin; + final parsedUrl = Uri.parse(url); + if (parsedUrl.scheme != 'http' && parsedUrl.scheme != 'https') { + // A non http(s) url is not pub.dev. + return false; + } + if (parsedUrl.host.isEmpty) { + // The empty host is not pub.dev. + return false; + } + final origin = parsedUrl.origin; // Allow the defaultHostedUrl to be overriden when running from tests if (runningFromTest && io.Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'] != null) { @@ -1380,6 +1389,11 @@ final String url; HostedDescription._(this.packageName, this.url); + + // This can be used to construct a description with any specific url. + factory HostedDescription.raw(String packageName, String url) => + HostedDescription._(packageName, url); + factory HostedDescription(String packageName, String url) => HostedDescription._( packageName, @@ -1437,16 +1451,10 @@ @override Object? serializeForLockfile({required String? containingDir}) { - late final String url; - try { - url = validateAndNormalizeHostedUrl(description.url).toString(); - } on FormatException catch (e) { - throw ArgumentError.value(url, 'url', 'url must be normalized: $e'); - } final hash = sha256; return { 'name': description.packageName, - 'url': url.toString(), + 'url': description.url, if (hash != null) 'sha256': hexEncode(hash), }; }
diff --git a/test/dependency_services/dependency_services_test.dart b/test/dependency_services/dependency_services_test.dart index cd77694..412be30 100644 --- a/test/dependency_services/dependency_services_test.dart +++ b/test/dependency_services/dependency_services_test.dart
@@ -52,11 +52,15 @@ final process = await Process.start( Platform.resolvedExecutable, [ + '--enable-asserts', snapshot, '--verbose', ...args, ], - environment: getPubTestEnvironment(), + environment: { + ...getPubTestEnvironment(), + '_PUB_TEST_DEFAULT_HOSTED_URL': globalServer.url, + }, workingDirectory: p.join(d.sandbox, appPath), ); if (stdin != null) { @@ -265,6 +269,45 @@ ]); }); + testWithGolden('Preserves pub.dartlang.org as hosted url', (context) async { + final server = (await servePackages()) + ..serve('foo', '1.2.3') + ..serve('bar', '1.2.3') + ..serveContentHashes = true; + + await d.dir(appPath, [ + d.pubspec({ + 'name': 'app', + 'dependencies': { + 'foo': '^1.0.0', + 'bar': '^1.0.0', + }, + }) + ]).create(); + await pubGet(); + final lockFile = File(path(p.join(appPath, 'pubspec.lock'))); + final lockFileYaml = YamlEditor( + lockFile.readAsStringSync(), + ); + for (final p in lockFileYaml.parseAt(['packages']).value.entries) { + lockFileYaml.update( + ['packages', p.key, 'description', 'url'], + 'https://pub.dartlang.org', + ); + } + lockFile.writeAsStringSync(lockFileYaml.toString()); + + server.serve('foo', '1.2.4'); + server.serve('boo', '1.2.4'); + + await _listReportApply( + context, + [ + _PackageVersion('foo', '1.2.4'), + ], + ); + }); + testWithGolden('Adding transitive', (context) async { final server = (await servePackages()) ..serve('foo', '1.2.3') @@ -383,6 +426,7 @@ }); testWithGolden('Can update a git package', (context) async { + await servePackages(); await d.git('foo.git', [d.libPubspec('foo', '1.0.0')]).create(); await d.git('bar.git', [d.libPubspec('bar', '1.0.0')]).create();
diff --git a/test/testdata/goldens/dependency_services/dependency_services_test/Preserves pub.dartlang.org as hosted url.txt b/test/testdata/goldens/dependency_services/dependency_services_test/Preserves pub.dartlang.org as hosted url.txt new file mode 100644 index 0000000..5a95570 --- /dev/null +++ b/test/testdata/goldens/dependency_services/dependency_services_test/Preserves pub.dartlang.org as hosted url.txt
@@ -0,0 +1,352 @@ +# GENERATED BY: test/dependency_services/dependency_services_test.dart + +$ cat pubspec.yaml +{"name":"app","dependencies":{"foo":"^1.0.0","bar":"^1.0.0"},"environment":{"sdk":"^3.0.2"}} +$ cat pubspec.lock +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + bar: + dependency: "direct main" + description: + name: bar + sha256: "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + url: https://pub.dartlang.org + source: hosted + version: "1.2.3" + foo: + dependency: "direct main" + description: + name: foo + sha256: "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + url: https://pub.dartlang.org + source: hosted + version: "1.2.3" +sdks: + dart: ">=3.0.2 <4.0.0" +-------------------------------- END OF OUTPUT --------------------------------- + +## Section list +$ dependency_services list +{ + "dependencies": [ + { + "name": "bar", + "version": "1.2.3", + "kind": "direct", + "constraint": "^1.0.0", + "source": { + "type": "hosted", + "description": { + "name": "bar", + "url": "https://pub.dev", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + } + }, + { + "name": "foo", + "version": "1.2.3", + "kind": "direct", + "constraint": "^1.0.0", + "source": { + "type": "hosted", + "description": { + "name": "foo", + "url": "https://pub.dev", + "sha256": "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + } + } + } + ] +} + +-------------------------------- END OF OUTPUT --------------------------------- + +## Section report +$ dependency_services report +{ + "dependencies": [ + { + "name": "bar", + "version": "1.2.3", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "bar", + "url": "https://pub.dev", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + }, + "latest": null, + "constraint": "^1.0.0", + "compatible": [ + { + "name": "bar", + "version": "1.2.3", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "bar", + "url": "http://localhost:$PORT", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + }, + "constraintBumped": "^1.0.0", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "bar", + "url": "https://pub.dev", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + } + }, + { + "name": "foo", + "version": "1.2.4", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "foo", + "url": "http://localhost:$PORT", + "sha256": "98405f2be818d57d0c01a1252e6bc50c1db9bb69b90557909740083019f214cd" + } + }, + "constraintBumped": "^1.0.0", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "foo", + "url": "https://pub.dev", + "sha256": "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + } + } + } + ], + "singleBreaking": [], + "multiBreaking": [ + { + "name": "bar", + "version": "1.2.3", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "bar", + "url": "http://localhost:$PORT", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + }, + "constraintBumped": "^1.2.3", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "bar", + "url": "https://pub.dev", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + } + }, + { + "name": "foo", + "version": "1.2.4", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "foo", + "url": "http://localhost:$PORT", + "sha256": "98405f2be818d57d0c01a1252e6bc50c1db9bb69b90557909740083019f214cd" + } + }, + "constraintBumped": "^1.2.4", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "foo", + "url": "https://pub.dev", + "sha256": "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + } + } + } + ] + }, + { + "name": "foo", + "version": "1.2.3", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "foo", + "url": "https://pub.dev", + "sha256": "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + } + }, + "latest": "0.0.1", + "constraint": "^1.0.0", + "compatible": [ + { + "name": "bar", + "version": "1.2.3", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "bar", + "url": "http://localhost:$PORT", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + }, + "constraintBumped": "^1.0.0", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "bar", + "url": "https://pub.dev", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + } + }, + { + "name": "foo", + "version": "1.2.4", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "foo", + "url": "http://localhost:$PORT", + "sha256": "98405f2be818d57d0c01a1252e6bc50c1db9bb69b90557909740083019f214cd" + } + }, + "constraintBumped": "^1.0.0", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "foo", + "url": "https://pub.dev", + "sha256": "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + } + } + } + ], + "singleBreaking": [], + "multiBreaking": [ + { + "name": "bar", + "version": "1.2.3", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "bar", + "url": "http://localhost:$PORT", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + }, + "constraintBumped": "^1.2.3", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "bar", + "url": "https://pub.dev", + "sha256": "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + } + } + }, + { + "name": "foo", + "version": "1.2.4", + "kind": "direct", + "source": { + "type": "hosted", + "description": { + "name": "foo", + "url": "http://localhost:$PORT", + "sha256": "98405f2be818d57d0c01a1252e6bc50c1db9bb69b90557909740083019f214cd" + } + }, + "constraintBumped": "^1.2.4", + "constraintWidened": "^1.0.0", + "constraintBumpedIfNeeded": "^1.0.0", + "previousVersion": "1.2.3", + "previousConstraint": "^1.0.0", + "previousSource": { + "type": "hosted", + "description": { + "name": "foo", + "url": "https://pub.dev", + "sha256": "0a2eec28c63c8ee616670da9bae0dbfe9294f5b04b264cc4a9be24876ea23d3b" + } + } + } + ] + } + ] +} + +-------------------------------- END OF OUTPUT --------------------------------- + +## Section apply +$ echo '{"dependencyChanges":[{"name":"foo","version":"1.2.4"}]}' | dependency_services apply +{"dependencies":[]} + +-------------------------------- END OF OUTPUT --------------------------------- + +$ cat pubspec.yaml +{"name":"app","dependencies":{"foo":"^1.0.0","bar":"^1.0.0"},"environment":{"sdk":"^3.0.2"}} +$ cat pubspec.lock +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + bar: + dependency: "direct main" + description: + name: bar + sha256: "0a2afc2a042366f06a89e07dfe845d3c77c07534fe489d5c4d8e8a8bdcbf0c22" + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.3" + foo: + dependency: "direct main" + description: + name: foo + sha256: "98405f2be818d57d0c01a1252e6bc50c1db9bb69b90557909740083019f214cd" + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.4" +sdks: + dart: ">=3.0.2 <4.0.0"