Skip to content

Commit

Permalink
Use src dir for Q# projects (#975)
Browse files Browse the repository at this point in the history
This PR updates `qsc_project` logic so Q# projects:
- Default to `/src/**/*.qs` (instead of all sibling files)
- Ignore all dot entries 
- Don't support exclude files and regexes



Aside: This PR nicely proves that our project logic is indeed
centralized. We only need to update `qsc_project`, and we get project
  • Loading branch information
sezna authored Jan 8, 2024
1 parent c1baaee commit caa3872
Show file tree
Hide file tree
Showing 52 changed files with 195 additions and 327 deletions.
21 changes: 0 additions & 21 deletions compiler/qsc_project/src/manifest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use std::{
fs::{self, DirEntry, FileType},
};

use regex_lite::Regex;
use serde::Deserialize;
use std::{path::PathBuf, sync::Arc};

Expand All @@ -20,14 +19,6 @@ pub const MANIFEST_FILE_NAME: &str = "qsharp.json";
pub struct Manifest {
pub author: Option<String>,
pub license: Option<String>,
#[serde(default = "default_exclude_regexes")]
pub exclude_regexes: Vec<String>,
#[serde(default)]
pub exclude_files: Vec<String>,
}

fn default_exclude_regexes() -> Vec<String> {
vec![".*node_modules.*".into(), ".*\\.git.*".into()]
}

/// Describes the contents and location of a Q# manifest file.
Expand All @@ -38,18 +29,6 @@ pub struct ManifestDescriptor {
}

impl ManifestDescriptor {
pub(crate) fn exclude_regexes(&self) -> Result<Vec<Regex>, crate::Error> {
self.manifest
.exclude_regexes
.iter()
.map(|x| Regex::new(x))
.collect::<Result<_, _>>()
.map_err(crate::Error::from)
}

pub(crate) fn exclude_files(&self) -> &[String] {
&self.manifest.exclude_files
}
/// Generate a canonical compilation URI for the project associated with this manifest
pub fn compilation_uri(&self) -> Arc<str> {
Arc::from(format!(
Expand Down
112 changes: 56 additions & 56 deletions compiler/qsc_project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

use crate::manifest::ManifestDescriptor;
use regex_lite::Regex;
use std::{
path::{Path, PathBuf},
sync::Arc,
Expand Down Expand Up @@ -61,49 +60,47 @@ pub trait FileSystemAsync {
/// Given a path, list its directory contents (if any).
/// This function should only return files that end in *.qs and folders.
async fn list_directory(&self, path: &Path) -> miette::Result<Vec<Self::Entry>>;
/// Given an initial path, fetch files matching <initial_path>/**/*.qs
async fn collect_project_sources(
&self,
initial_path: &Path,
) -> miette::Result<Vec<Self::Entry>> {
let listing = self.list_directory(initial_path).await?;
if let Some(src_dir) = listing.into_iter().find(|x| {
let Ok(entry_type) = x.entry_type() else {
return false;
};
entry_type == EntryType::Folder && x.entry_name() == "src"
}) {
self.collect_project_sources_inner(&src_dir.path()).await
} else {
Err(miette::ErrReport::msg(
"No `src` directory found for project.",
))
}
}

/// Given an initial path and some regexes to exclude, fetch files that don't match
/// those regexes.
async fn fetch_files_with_exclude_pattern(
async fn collect_project_sources_inner(
&self,
exclude_patterns: &[Regex],
exclude_files: &[String],
initial_path: &Path,
) -> miette::Result<Vec<Self::Entry>> {
let listing = self.list_directory(initial_path).await?;
let mut files = vec![];
for item in listing {
let file_name = item.entry_name();
let name = item.path().to_string_lossy().to_string();
if regex_matches(exclude_patterns, &name) || exclude_files.contains(&file_name) {
continue;
}
for item in filter_hidden_files(listing.into_iter()) {
match item.entry_type() {
Ok(EntryType::File) if item.entry_extension() == "qs" => files.push(item),
Ok(EntryType::Folder) => files.append(
&mut self
.fetch_files_with_exclude_pattern(
exclude_patterns,
exclude_files,
&item.path(),
)
.await?,
),
Ok(EntryType::Folder) => {
files.append(&mut self.collect_project_sources_inner(&item.path()).await?)
}
_ => (),
}
}
Ok(files)
}

/// Given a [ManifestDescriptor], load project sources.
async fn load_project(&self, manifest: &ManifestDescriptor) -> miette::Result<Project> {
let qs_files = self
.fetch_files_with_exclude_pattern(
&manifest.exclude_regexes()?,
manifest.exclude_files(),
&manifest.manifest_dir,
)
.await?;
let project_path = manifest.manifest_dir.clone();
let qs_files = self.collect_project_sources(&project_path).await?;

let qs_files = qs_files.into_iter().map(|file| file.path());

Expand All @@ -119,6 +116,13 @@ pub trait FileSystemAsync {
}
}

/// Filters out any hidden files (files that start with '.')
fn filter_hidden_files<Entry: DirEntry>(
listing: impl Iterator<Item = Entry>,
) -> impl Iterator<Item = Entry> {
listing.filter(|x| !x.entry_name().starts_with('.'))
}

/// This trait is used to abstract filesystem logic with regards to Q# projects.
/// A Q# project requires some multi-file structure, but that may not actually be
/// an OS filesystem. It could be a virtual filesystem on vscode.dev, or perhaps a
Expand All @@ -131,30 +135,35 @@ pub trait FileSystem {

/// Given a path, list its directory contents (if any).
fn list_directory(&self, path: &Path) -> miette::Result<Vec<Self::Entry>>;
/// Given an initial path, fetch files matching <initial_path>/**/*.qs
fn collect_project_sources(&self, initial_path: &Path) -> miette::Result<Vec<Self::Entry>> {
let listing = self.list_directory(initial_path)?;
if let Some(src_dir) = listing.into_iter().find(|x| {
let Ok(entry_type) = x.entry_type() else {
return false;
};
entry_type == EntryType::Folder && x.entry_name() == "src"
}) {
self.collect_project_sources_inner(&src_dir.path())
} else {
Err(miette::ErrReport::msg(
"No `src` directory found for project.",
))
}
}

/// Given an initial path and some regexes to exclude, fetch files that don't match
/// those regexes.
fn fetch_files_with_exclude_pattern(
fn collect_project_sources_inner(
&self,
exclude_patterns: &[Regex],
exclude_files: &[String],
initial_path: &Path,
) -> miette::Result<Vec<Self::Entry>> {
let listing = self.list_directory(initial_path)?;
let mut files = vec![];
for item in listing {
let file_name = item.entry_name();
let name = item.path().to_string_lossy().to_string();
if regex_matches(exclude_patterns, &name) || exclude_files.contains(&file_name) {
continue;
}
for item in filter_hidden_files(listing.into_iter()) {
match item.entry_type() {
Ok(EntryType::File) if item.entry_extension() == "qs" => files.push(item),
Ok(EntryType::Folder) => files.append(&mut self.fetch_files_with_exclude_pattern(
exclude_patterns,
exclude_files,
&item.path(),
)?),
Ok(EntryType::Folder) => {
files.append(&mut self.collect_project_sources_inner(&item.path())?)
}
_ => (),
}
}
Expand All @@ -163,11 +172,8 @@ pub trait FileSystem {

/// Given a [ManifestDescriptor], load project sources.
fn load_project(&self, manifest: &ManifestDescriptor) -> miette::Result<Project> {
let qs_files = self.fetch_files_with_exclude_pattern(
&manifest.exclude_regexes()?,
manifest.exclude_files(),
&manifest.manifest_dir,
)?;
let project_path = manifest.manifest_dir.clone();
let qs_files = self.collect_project_sources(&project_path)?;

let qs_files = qs_files.into_iter().map(|file| file.path());

Expand All @@ -180,9 +186,3 @@ pub trait FileSystem {
})
}
}

fn regex_matches(exclude_patterns: &[Regex], entry_name: &str) -> bool {
exclude_patterns
.iter()
.any(|pattern| matches!(pattern.find(entry_name), Some(item) if item.as_str().len() == entry_name.len()))
}
6 changes: 0 additions & 6 deletions compiler/qsc_project/tests/projects/exclude_blobs/Main.qs

This file was deleted.

3 changes: 0 additions & 3 deletions compiler/qsc_project/tests/projects/exclude_blobs/qsharp.json

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

6 changes: 0 additions & 6 deletions compiler/qsc_project/tests/projects/exclude_files/Main.qs

This file was deleted.

3 changes: 0 additions & 3 deletions compiler/qsc_project/tests/projects/exclude_files/qsharp.json

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

5 changes: 0 additions & 5 deletions compiler/qsc_project/tests/projects/exclude_list/Excluded.qs

This file was deleted.

5 changes: 0 additions & 5 deletions compiler/qsc_project/tests/projects/exclude_list/Included.qs

This file was deleted.

7 changes: 0 additions & 7 deletions compiler/qsc_project/tests/projects/exclude_list/Main.qs

This file was deleted.

3 changes: 0 additions & 3 deletions compiler/qsc_project/tests/projects/exclude_list/qsharp.json

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
namespace Foo {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Project {
@EntryPoint()
operation Entry() : String {
Strings.Concat("12", $"{(Math.Subtract(346, 1))}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Strings {
function Concat(a: String, b: String) : String {
a + b
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Math {
function Add(a: Int, b: Int) : Int {
a + b
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Math {
function Subtract(a: Int, b: Int) : Int {
a - b
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This file should not be included in the project.
1 change: 1 addition & 0 deletions compiler/qsc_project/tests/projects/peer_file/qsharp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
6 changes: 6 additions & 0 deletions compiler/qsc_project/tests/projects/peer_file/src/Project.qs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Project {
@EntryPoint()
operation Entry() : String {
Strings.Concat("12", $"{(Math.Subtract(346, 1))}")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Math {
function Add(a: Int, b: Int) : Int {
a + b
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Math {
function Subtract(a: Int, b: Int) : Int {
a - b
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Strings {
function Concat(a: String, b: String) : String {
a + b
}
}
Loading

0 comments on commit caa3872

Please sign in to comment.