3
3
4
4
use crate :: {
5
5
manifest:: { GitHubRef , PackageType } ,
6
- Manifest , ManifestDescriptor , PackageRef ,
6
+ Manifest , PackageRef ,
7
7
} ;
8
8
use async_trait:: async_trait;
9
9
use futures:: FutureExt ;
@@ -129,6 +129,11 @@ pub enum Error {
129
129
#[ error( "Error fetching from GitHub: {0}" ) ]
130
130
#[ diagnostic( code( "Qsc.Project.GitHub" ) ) ]
131
131
GitHub ( String ) ,
132
+
133
+ #[ error( "File {0} is not listed in the `files` field of the manifest" ) ]
134
+ #[ help( "To avoid unexpected behavior, add this file to the `files` field in the `qsharp.json` manifest" ) ]
135
+ #[ diagnostic( code( "Qsc.Project.DocumentNotInProject" ) ) ]
136
+ DocumentNotInProject ( String ) ,
132
137
}
133
138
134
139
impl Error {
@@ -137,6 +142,7 @@ impl Error {
137
142
pub fn path ( & self ) -> Option < & String > {
138
143
match self {
139
144
Error :: GitHubManifestParse { path, .. }
145
+ | Error :: DocumentNotInProject ( path)
140
146
| Error :: NoSrcDir { path }
141
147
| Error :: ManifestParse { path, .. } => Some ( path) ,
142
148
// Note we don't return the path for `FileSystem` errors,
@@ -182,10 +188,7 @@ pub trait FileSystemAsync {
182
188
) -> miette:: Result < Arc < str > > ;
183
189
184
190
/// Given an initial path, fetch files matching <initial_path>/**/*.qs
185
- async fn collect_project_sources (
186
- & self ,
187
- initial_path : & Path ,
188
- ) -> ProjectResult < Vec < Self :: Entry > > {
191
+ async fn collect_project_sources ( & self , initial_path : & Path ) -> ProjectResult < Vec < PathBuf > > {
189
192
let listing = self
190
193
. list_directory ( initial_path)
191
194
. await
@@ -210,7 +213,7 @@ pub trait FileSystemAsync {
210
213
async fn collect_project_sources_inner (
211
214
& self ,
212
215
initial_path : & Path ,
213
- ) -> ProjectResult < Vec < Self :: Entry > > {
216
+ ) -> ProjectResult < Vec < PathBuf > > {
214
217
let listing = self
215
218
. list_directory ( initial_path)
216
219
. await
@@ -221,7 +224,7 @@ pub trait FileSystemAsync {
221
224
let mut files = vec ! [ ] ;
222
225
for item in filter_hidden_files ( listing. into_iter ( ) ) {
223
226
match item. entry_type ( ) {
224
- Ok ( EntryType :: File ) if item. entry_extension ( ) == "qs" => files. push ( item) ,
227
+ Ok ( EntryType :: File ) if item. entry_extension ( ) == "qs" => files. push ( item. path ( ) ) ,
225
228
Ok ( EntryType :: Folder ) => {
226
229
files. append ( & mut self . collect_project_sources_inner ( & item. path ( ) ) . await ?) ;
227
230
}
@@ -231,8 +234,64 @@ pub trait FileSystemAsync {
231
234
Ok ( files)
232
235
}
233
236
237
+ async fn collect_sources_from_files_field (
238
+ & self ,
239
+ project_path : & Path ,
240
+ manifest : & Manifest ,
241
+ ) -> ProjectResult < Vec < PathBuf > > {
242
+ let mut v = vec ! [ ] ;
243
+ for file in & manifest. files {
244
+ v. push (
245
+ self . resolve_path ( project_path, Path :: new ( & file) )
246
+ . await
247
+ . map_err ( |e| Error :: FileSystem {
248
+ about_path : project_path. to_string_lossy ( ) . to_string ( ) ,
249
+ error : e. to_string ( ) ,
250
+ } ) ?,
251
+ ) ;
252
+ }
253
+ Ok ( v)
254
+ }
255
+
256
+ fn validate (
257
+ & self ,
258
+ qs_files : & mut Vec < PathBuf > ,
259
+ listed_files : & mut Vec < PathBuf > ,
260
+ ) -> Result < ( ) , Vec < Error > > {
261
+ qs_files. sort ( ) ;
262
+ listed_files. sort ( ) ;
263
+
264
+ // If the `files` field exists in the manifest, validate it includes
265
+ // all the files in the `src` directory.
266
+ // how do I subtract one sorted vector from another
267
+ let mut difference = qs_files. clone ( ) ;
268
+ let mut iter2 = listed_files. iter ( ) . peekable ( ) ;
269
+
270
+ difference. retain ( |item| {
271
+ while let Some ( & next) = iter2. peek ( ) {
272
+ if next < item {
273
+ iter2. next ( ) ;
274
+ } else {
275
+ break ;
276
+ }
277
+ }
278
+ iter2. peek ( ) != Some ( & item)
279
+ } ) ;
280
+
281
+ if !difference. is_empty ( ) {
282
+ return Err ( difference
283
+ . iter ( )
284
+ . map ( |p| Error :: DocumentNotInProject ( p. to_string_lossy ( ) . to_string ( ) ) )
285
+ . collect ( ) ) ;
286
+ }
287
+ Ok ( ( ) )
288
+ }
289
+
234
290
/// Given a directory, loads the project sources
235
291
/// and the sources for all its dependencies.
292
+ ///
293
+ /// Any errors that didn't block project load are contained in the
294
+ /// `errors` field of the returned `Project`.
236
295
async fn load_project (
237
296
& self ,
238
297
directory : & Path ,
@@ -243,15 +302,15 @@ pub trait FileSystemAsync {
243
302
. await
244
303
. map_err ( |e| vec ! [ e] ) ?;
245
304
246
- let root = self
247
- . read_local_manifest_and_sources ( directory)
248
- . await
249
- . map_err ( |e| vec ! [ e] ) ?;
250
-
251
305
let mut errors = vec ! [ ] ;
252
306
let mut packages = FxHashMap :: default ( ) ;
253
307
let mut stack = vec ! [ ] ;
254
308
309
+ let root = self
310
+ . read_local_manifest_and_sources ( directory, & mut errors)
311
+ . await
312
+ . map_err ( |e| vec ! [ e] ) ?;
313
+
255
314
let root_path = directory. to_string_lossy ( ) . to_string ( ) ;
256
315
let root_ref = PackageRef :: Path { path : root_path } ;
257
316
@@ -321,41 +380,33 @@ pub trait FileSystemAsync {
321
380
322
381
/// Load the sources for a single package at the given directory. Also load its
323
382
/// dependency information but don't recurse into dependencies yet.
383
+ ///
384
+ /// Any errors that didn't block project load are accumulated into the `errors` vector.
324
385
async fn read_local_manifest_and_sources (
325
386
& self ,
326
- directory : & Path ,
387
+ manifest_dir : & Path ,
388
+ errors : & mut Vec < Error > ,
327
389
) -> ProjectResult < PackageInfo > {
328
- let manifest = self . parse_manifest_in_dir ( directory) . await ?;
329
-
330
- let manifest = ManifestDescriptor {
331
- manifest_dir : directory. to_path_buf ( ) ,
332
- manifest,
333
- } ;
334
-
335
- let project_path = manifest. manifest_dir . clone ( ) ;
336
-
337
- // If the `files` field exists in the manifest, prefer that.
338
- // Otherwise, collect all files in the project directory.
339
- let qs_files: Vec < PathBuf > = if manifest. manifest . files . is_empty ( ) {
340
- let qs_files = self . collect_project_sources ( & project_path) . await ?;
341
- qs_files. into_iter ( ) . map ( |file| file. path ( ) ) . collect ( )
342
- } else {
343
- let mut v = vec ! [ ] ;
344
- for file in manifest. manifest . files {
345
- v. push (
346
- self . resolve_path ( & project_path, Path :: new ( & file) )
347
- . await
348
- . map_err ( |e| Error :: FileSystem {
349
- about_path : project_path. to_string_lossy ( ) . to_string ( ) ,
350
- error : e. to_string ( ) ,
351
- } ) ?,
352
- ) ;
353
- }
354
- v
355
- } ;
390
+ let manifest = self . parse_manifest_in_dir ( manifest_dir) . await ?;
391
+
392
+ // All the *.qs files under src/
393
+ let mut all_qs_files = self . collect_project_sources ( manifest_dir) . await ?;
394
+
395
+ // Files explicitly listed in the `files` field of the manifest
396
+ let mut listed_files = self
397
+ . collect_sources_from_files_field ( manifest_dir, & manifest)
398
+ . await ?;
399
+
400
+ if !listed_files. is_empty ( ) {
401
+ errors. extend (
402
+ self . validate ( & mut all_qs_files, & mut listed_files)
403
+ . err ( )
404
+ . unwrap_or_default ( ) ,
405
+ ) ;
406
+ }
356
407
357
- let mut sources = Vec :: with_capacity ( qs_files . len ( ) ) ;
358
- for path in qs_files {
408
+ let mut sources = Vec :: with_capacity ( all_qs_files . len ( ) ) ;
409
+ for path in all_qs_files {
359
410
sources. push ( self . read_file ( & path) . await . map_err ( |e| Error :: FileSystem {
360
411
about_path : path. to_string_lossy ( ) . to_string ( ) ,
361
412
error : e. to_string ( ) ,
@@ -367,13 +418,13 @@ pub trait FileSystemAsync {
367
418
// For any local dependencies, convert relative paths to absolute,
368
419
// so that multiple references to the same package, from different packages,
369
420
// get merged correctly.
370
- for ( alias, mut dep) in manifest. manifest . dependencies {
421
+ for ( alias, mut dep) in manifest. dependencies {
371
422
if let PackageRef :: Path { path : dep_path } = & mut dep {
372
423
* dep_path = self
373
- . resolve_path ( & project_path , & PathBuf :: from ( dep_path. clone ( ) ) )
424
+ . resolve_path ( manifest_dir , & PathBuf :: from ( dep_path. clone ( ) ) )
374
425
. await
375
426
. map_err ( |e| Error :: FileSystem {
376
- about_path : project_path . to_string_lossy ( ) . to_string ( ) ,
427
+ about_path : manifest_dir . to_string_lossy ( ) . to_string ( ) ,
377
428
error : e. to_string ( ) ,
378
429
} ) ?
379
430
. to_string_lossy ( )
@@ -384,9 +435,9 @@ pub trait FileSystemAsync {
384
435
385
436
Ok ( PackageInfo {
386
437
sources,
387
- language_features : LanguageFeatures :: from_iter ( & manifest . manifest . language_features ) ,
438
+ language_features : LanguageFeatures :: from_iter ( manifest. language_features ) ,
388
439
dependencies,
389
- package_type : manifest. manifest . package_type ,
440
+ package_type : manifest. package_type ,
390
441
} )
391
442
}
392
443
@@ -477,6 +528,7 @@ pub trait FileSystemAsync {
477
528
global_cache : & RefCell < PackageCache > ,
478
529
key : PackageKey ,
479
530
this_pkg : & PackageRef ,
531
+ errors : & mut Vec < Error > ,
480
532
) -> ProjectResult < PackageInfo > {
481
533
match this_pkg {
482
534
PackageRef :: GitHub { github } => {
@@ -499,7 +551,7 @@ pub trait FileSystemAsync {
499
551
// editing experience as intuitive as possible. This may change if we start
500
552
// hitting perf issues, but careful consideration is needed into when to
501
553
// invalidate the cache.
502
- self . read_local_manifest_and_sources ( PathBuf :: from ( path. clone ( ) ) . as_path ( ) )
554
+ self . read_local_manifest_and_sources ( PathBuf :: from ( path. clone ( ) ) . as_path ( ) , errors )
503
555
. await
504
556
}
505
557
}
@@ -535,7 +587,7 @@ pub trait FileSystemAsync {
535
587
}
536
588
537
589
let dep_result = self
538
- . read_manifest_and_sources ( global_cache, dep_key. clone ( ) , & dependency)
590
+ . read_manifest_and_sources ( global_cache, dep_key. clone ( ) , & dependency, errors )
539
591
. await ;
540
592
541
593
match dep_result {
0 commit comments