Skip to content

Commit c3e5091

Browse files
authored
Fix detection of root packages in PackageGraph.transitiveDependencies (#4620)
1 parent 15b9658 commit c3e5091

File tree

5 files changed

+131
-9
lines changed

5 files changed

+131
-9
lines changed

lib/src/command/deps.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ class DepsCommand extends PubCommand {
420420
.expand(
421421
(p) => graph.transitiveDependencies(
422422
p,
423-
followDevDependenciesFromRoot: false,
423+
followDevDependenciesFromPackage: false,
424424
),
425425
)
426426
.map((package) => package.name)

lib/src/command/upgrade.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class UpgradeCommand extends PubCommand {
124124
(package) => graph
125125
.transitiveDependencies(
126126
package,
127-
followDevDependenciesFromRoot: true,
127+
followDevDependenciesFromPackage: true,
128128
)
129129
.map((p) => p.name),
130130
)

lib/src/global_packages.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,10 @@ Follow progress in https://github.com/dart-lang/sdk/issues/60889.
212212
final activatedPackage = entrypoint.workPackage;
213213
final name = activatedPackage.name;
214214
for (final package in (await entrypoint.packageGraph)
215-
.transitiveDependencies(name, followDevDependenciesFromRoot: false)) {
215+
.transitiveDependencies(
216+
name,
217+
followDevDependenciesFromPackage: false,
218+
)) {
216219
_testForHooks(package, name);
217220
}
218221
_describeActive(name, cache);

lib/src/package_graph.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,11 @@ class PackageGraph {
4646

4747
/// Returns all transitive dependencies of [package].
4848
///
49-
/// For the entrypoint this returns all packages in [packages], which includes
50-
/// dev and override. For any other package, it ignores dev and override
51-
/// dependencies.
49+
/// If [package] is a root, this will explore the dev_dependencies of
50+
/// [package] if [followDevDependenciesFromPackage] is true.
5251
Set<Package> transitiveDependencies(
5352
String package, {
54-
required bool followDevDependenciesFromRoot,
53+
required bool followDevDependenciesFromPackage,
5554
}) {
5655
final result = <Package>{};
5756

@@ -63,7 +62,11 @@ class PackageGraph {
6362
final currentPackage = packages[current]!;
6463
result.add(currentPackage);
6564
stack.addAll(currentPackage.dependencies.keys);
66-
if (followDevDependenciesFromRoot && current == package) {
65+
if (followDevDependenciesFromPackage &&
66+
current == package &&
67+
entrypoint.workspaceRoot.transitiveWorkspace.any(
68+
(p) => p.name == current,
69+
)) {
6770
stack.addAll(currentPackage.devDependencies.keys);
6871
}
6972
}
@@ -89,7 +92,9 @@ class PackageGraph {
8992

9093
return transitiveDependencies(
9194
package,
92-
followDevDependenciesFromRoot: true,
95+
// If package is a root package it is not immutable itself, and we don't
96+
// need to consider its dev_dependencies.
97+
followDevDependenciesFromPackage: false,
9398
).any((dep) => !_isPackageFromImmutableSource(dep.name));
9499
}
95100
}

test/package_graph_test.dart

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:path/path.dart' as p;
6+
import 'package:pub/src/entrypoint.dart';
7+
import 'package:pub/src/system_cache.dart';
8+
import 'package:test/test.dart';
9+
10+
import 'descriptor.dart' as d;
11+
import 'test_pub.dart';
12+
13+
void main() {
14+
test('transitiveDependencies', () async {
15+
final server = await servePackages();
16+
server.serve(
17+
'foo',
18+
'1.0.0',
19+
deps: {
20+
'transitive': {'hosted': globalServer.url},
21+
},
22+
pubspec: {
23+
'dev_dependencies': {
24+
'transitive_dev_dep': {'hosted': globalServer.url},
25+
}, // This should **not** be included.
26+
},
27+
);
28+
server.serve(
29+
'dev_dep',
30+
'1.0.0',
31+
deps: {
32+
'dev_dep_transitive': {'hosted': globalServer.url},
33+
},
34+
pubspec: {
35+
'dev_dependencies': {
36+
'transitive_dev_dep': {
37+
'hosted': globalServer.url,
38+
}, // This should **not** be included.
39+
},
40+
},
41+
);
42+
server.serve('dev_dep_transitive', '1.0.0');
43+
server.serve('transitive', '1.0.0');
44+
server.serve('a_dev_dep', '1.0.0');
45+
await d.dir(appPath, [
46+
d.appPubspec(
47+
dependencies: {
48+
'a': null,
49+
'foo': {'hosted': globalServer.url},
50+
},
51+
extras: {
52+
'environment': {'sdk': '^3.5.0'},
53+
'workspace': ['a'],
54+
'dev_dependencies': {
55+
'dev_dep': {'hosted': globalServer.url},
56+
},
57+
},
58+
),
59+
d.dir('a', [
60+
d.libPubspec(
61+
'a',
62+
'1.0.0',
63+
resolutionWorkspace: true,
64+
devDeps: {
65+
'a_dev_dep': {'hosted': globalServer.url},
66+
},
67+
),
68+
]),
69+
]).create();
70+
await pubGet(environment: {'_PUB_TEST_SDK_VERSION': '3.5.0'});
71+
final entrypoint = Entrypoint(
72+
p.join(d.sandbox, appPath),
73+
SystemCache(rootDir: p.join(d.sandbox, cachePath)),
74+
);
75+
final graph = await entrypoint.packageGraph;
76+
77+
expect(
78+
graph
79+
.transitiveDependencies('foo', followDevDependenciesFromPackage: true)
80+
.map((p) => p.name),
81+
{'foo', 'transitive'},
82+
);
83+
84+
expect(
85+
graph
86+
.transitiveDependencies(
87+
'foo',
88+
followDevDependenciesFromPackage: false,
89+
)
90+
.map((p) => p.name),
91+
{'foo', 'transitive'},
92+
);
93+
94+
expect(
95+
graph
96+
.transitiveDependencies(
97+
'myapp',
98+
followDevDependenciesFromPackage: true,
99+
)
100+
.map((p) => p.name),
101+
{'myapp', 'foo', 'dev_dep', 'dev_dep_transitive', 'transitive', 'a'},
102+
);
103+
104+
expect(
105+
graph
106+
.transitiveDependencies(
107+
'myapp',
108+
followDevDependenciesFromPackage: false,
109+
)
110+
.map((p) => p.name),
111+
{'myapp', 'foo', 'transitive', 'a'},
112+
);
113+
});
114+
}

0 commit comments

Comments
 (0)