dev_prune/adapters/
dart.rs1use super::{
23 BloatDir, EnforcePolicy, PackageManager, dir_size, refuse_if_manifest_stale,
24 run_command_with_timeout,
25};
26use anyhow::{Result, anyhow};
27use std::fs;
28use std::path::Path;
29
30pub struct Dart;
32
33impl Dart {
34 fn is_flutter(path: &Path) -> bool {
40 fs::read_to_string(path.join("pubspec.yaml"))
41 .map(|manifest| manifest.contains("flutter:") || manifest.contains("sdk: flutter"))
42 .unwrap_or(false)
43 }
44}
45
46impl PackageManager for Dart {
47 fn name(&self) -> &'static str {
48 "dart"
49 }
50
51 fn detect(&self, path: &Path) -> bool {
52 path.join("pubspec.yaml").exists()
53 }
54
55 fn bloat_dirs(&self, path: &Path) -> Vec<BloatDir> {
56 let tool = path.join(".dart_tool");
57 if !tool.is_dir() {
58 return Vec::new();
59 }
60 vec![BloatDir {
61 name: ".dart_tool".to_string(),
62 path: tool.clone(),
63 size_bytes: dir_size(&tool),
64 shared_bytes: 0,
65 }]
66 }
67
68 fn enforce_lockfile(&self, path: &Path, _policy: EnforcePolicy) -> Result<()> {
69 let lock = path.join("pubspec.lock");
70 let content = fs::read_to_string(&lock).map_err(|e| {
71 anyhow!(
72 "`pubspec.lock` could not be read ({e}) — without it `pub get` resolves \
73 afresh instead of restoring the versions being deleted."
74 )
75 })?;
76 if !content.contains("packages:") {
80 return Err(anyhow!(
81 "`pubspec.lock` has no `packages:` section — it is not a complete pub \
82 lockfile, so `.dart_tool` cannot be proven rebuildable from it."
83 ));
84 }
85 refuse_if_manifest_stale(&path.join("pubspec.yaml"), &lock, "dart pub get")
89 }
90
91 fn restore(&self, path: &Path, timeout: std::time::Duration) -> Result<()> {
92 let program = if Self::is_flutter(path) {
93 "flutter"
94 } else {
95 "dart"
96 };
97 run_command_with_timeout(program, &["pub", "get"], path, timeout)
98 }
99
100 fn lockfiles(&self) -> &'static [&'static str] {
101 &["pubspec.lock"]
102 }
103
104 fn opt_in(&self) -> bool {
105 true
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use tempfile::tempdir;
113
114 #[test]
115 fn detects_on_the_pub_manifest() {
116 let dir = tempdir().unwrap();
117 assert!(!Dart.detect(dir.path()));
118 fs::write(dir.path().join("pubspec.yaml"), "name: example\n").unwrap();
119 assert!(Dart.detect(dir.path()));
120 }
121
122 #[test]
123 fn claims_the_tool_directory_and_never_the_output_one() {
124 let dir = tempdir().unwrap();
125 fs::create_dir(dir.path().join(".dart_tool")).unwrap();
126 fs::create_dir(dir.path().join("build")).unwrap();
127 let names: Vec<String> = Dart
128 .bloat_dirs(dir.path())
129 .into_iter()
130 .map(|b| b.name)
131 .collect();
132 assert_eq!(names, vec![".dart_tool"]);
133 }
134
135 #[test]
136 fn a_missing_or_malformed_lockfile_is_refused() {
137 let dir = tempdir().unwrap();
138 assert!(
139 Dart.enforce_lockfile(dir.path(), EnforcePolicy::default())
140 .is_err()
141 );
142 fs::write(dir.path().join("pubspec.lock"), "<<<<<<< HEAD\n").unwrap();
143 assert!(
144 Dart.enforce_lockfile(dir.path(), EnforcePolicy::default())
145 .is_err()
146 );
147 fs::write(
148 dir.path().join("pubspec.lock"),
149 "packages:\n http:\n version: \"1.2.0\"\n",
150 )
151 .unwrap();
152 assert!(
153 Dart.enforce_lockfile(dir.path(), EnforcePolicy::default())
154 .is_ok()
155 );
156 }
157
158 #[test]
159 fn a_flutter_app_is_told_apart_from_a_plain_dart_package() {
160 let dir = tempdir().unwrap();
161 fs::write(
162 dir.path().join("pubspec.yaml"),
163 "name: cli\ndependencies:\n",
164 )
165 .unwrap();
166 assert!(!Dart::is_flutter(dir.path()));
167 fs::write(
168 dir.path().join("pubspec.yaml"),
169 "name: app\ndependencies:\n flutter:\n sdk: flutter\n",
170 )
171 .unwrap();
172 assert!(Dart::is_flutter(dir.path()));
173 }
174
175 #[test]
176 fn is_opt_in_because_the_bulk_of_the_directory_is_compiler_output() {
177 assert!(Dart.opt_in());
178 }
179}