Skip to content

Commit 8d7a704

Browse files
committed
feat(core): generate compile commands on dir change
1 parent 65a5654 commit 8d7a704

File tree

9 files changed

+434
-46
lines changed

9 files changed

+434
-46
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shared/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ anyhow = "^1.0.42"
99

1010
# Serialization
1111
serde = { version = "1.0", features = ["derive"]}
12+
serde_json = "1.0.79"
1213
serde_yaml = "0.8.23"
1314

1415
# Get processes, processors, disks, components and networks
@@ -28,6 +29,9 @@ tracing-appender = "0.2.1"
2829
notify = "5.0.0-pre.13"
2930
dirs = "4.0"
3031
wax = "0.4.0"
32+
regex = "1.5"
33+
lazy_static = "1.4.0"
34+
shell-words = "1.1.0"
3135

3236
# WARN: api totally changed on later versions.
3337
# It would make sense to create custom function

shared/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod state;
44

55
mod command;
66
mod project;
7+
mod xcode;
78
pub use command::*;
89
pub mod tracing;
910
pub mod watch;

shared/src/project.rs

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::collections::HashMap;
22

3-
use anyhow::Context;
3+
use crate::xcode;
4+
use anyhow::{Context, Result};
45
use serde::Deserialize;
56
use std::path::PathBuf;
67
use tokio::fs;
@@ -30,20 +31,24 @@ pub struct Project {
3031
/// XcodeBase local configuration
3132
#[serde(rename(deserialize = "XcodeBase"))]
3233
xcode_base: LocalConfig,
34+
/// Root directory
35+
#[serde(skip)]
36+
root: PathBuf,
3337
}
3438

3539
impl Project {
36-
pub async fn new_from_project_yml(path: PathBuf) -> anyhow::Result<Self> {
40+
pub async fn new_from_project_yml(root: PathBuf, path: PathBuf) -> Result<Self> {
3741
let content = fs::read_to_string(path).await?;
38-
serde_yaml::from_str(&content).context("Unable to parse project.yaml")
42+
let mut project: Project =
43+
serde_yaml::from_str(&content).context("Unable to parse project.yaml")?;
44+
project.root = root;
45+
Ok(project)
3946
}
4047

4148
pub fn config(&self) -> &LocalConfig {
4249
&self.xcode_base
4350
}
44-
}
4551

46-
impl Project {
4752
/// Get a reference to the project's name.
4853
pub fn name(&self) -> &str {
4954
self.name.as_ref()
@@ -53,4 +58,34 @@ impl Project {
5358
pub fn targets(&self) -> &TargetMap {
5459
&self.targets
5560
}
61+
62+
/// Build project with clean and return build log
63+
pub async fn fresh_build(&self) -> Result<Vec<String>> {
64+
/*
65+
TODO: Find away to get commands ran without doing xcodebuild clean
66+
67+
Right now, in order to produce compiled commands and for `xcodebuild build` to spit out ran
68+
commands, we need to first run xcodebuild clean.
69+
70+
NOTE: This far from possilbe after some research
71+
*/
72+
xcode::clean(&self.root, &["-verbose"]).await?;
73+
74+
/*
75+
TODO: Support overriding xcodebuild arguments
76+
77+
Not sure how important is this, but ideally I'd like to be able to add extra arguments for
78+
when generating compiled commands, as well as doing actual builds and runs.
79+
80+
```yaml
81+
XcodeBase:
82+
buildArguments: [];
83+
compileArguments: [];
84+
runArguments: [];
85+
```
86+
*/
87+
xcode::build(&self.root, &["-verbose"]).await
88+
}
5689
}
90+
91+
impl Project {}

shared/src/watch.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,15 @@ fn new(state: SharedState, root: String) -> tokio::task::JoinHandle<anyhow::Resu
174174

175175
trace!("[NewEvent] {:#?}", &event);
176176

177-
let state = state.lock().await;
178-
let workspace = match state.workspaces.get(&root) {
179-
Some(w) => w,
177+
// let mut state = state.lock().await;
178+
179+
match state.lock().await.workspaces.get_mut(&root) {
180+
Some(w) => {
181+
w.on_dirctory_change(path, event.kind).await?;
182+
}
183+
// NOTE: should stop watch here
180184
None => continue,
181185
};
182-
183-
workspace.on_dirctory_change(path, event.kind).await?;
184186
}
185187
Ok(())
186188
})

shared/src/workspace.rs

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@ use crate::project::{Project, Target, TargetMap};
22
use anyhow::{bail, Ok, Result};
33
use std::path::PathBuf;
44

5+
use crate::xcode;
56
use libproc::libproc::proc_pid;
67
use notify::EventKind;
8+
use serde_json::json;
79
use std::process::Stdio;
10+
use tokio::fs;
11+
use tokio::io::AsyncWriteExt;
812
use tokio::process::Command;
913

1014
/// Managed Workspace
@@ -20,7 +24,7 @@ pub struct Workspace {
2024

2125
impl Workspace {
2226
/// Create new workspace from a path representing project root.
23-
/// TODO: Support setting up projects with .xproj as well as xcworkspace
27+
/// TODO: Support projects with .xproj as well as xcworkspace
2428
pub async fn new(root: &str) -> Result<Self> {
2529
let root = PathBuf::from(root);
2630

@@ -30,10 +34,10 @@ impl Workspace {
3034
bail!("project.yaml doesn't exist in '{:?}'", root)
3135
}
3236

33-
Project::new_from_project_yml(path).await?
37+
Project::new_from_project_yml(root.clone(), path).await?
3438
};
3539

36-
tracing::info!("[New::{}]: {:?}", project.name(), root);
40+
tracing::info!("Managing [{}] {:?}", project.name(), root);
3741

3842
Ok(Self {
3943
root,
@@ -52,7 +56,7 @@ impl Workspace {
5256
let name = self.project.name();
5357
self.clients.retain(|&pid| {
5458
if proc_pid::name(pid).is_err() {
55-
tracing::debug!("[Update::{}]: Remove Client: {pid}", name);
59+
tracing::debug!("[{}]: Remove Client: {pid}", name);
5660
false
5761
} else {
5862
true
@@ -65,7 +69,7 @@ impl Workspace {
6569
// Remove no longer active clients
6670
self.update_clients();
6771
// NOTE: Implicitly assuming that pid is indeed a valid pid
68-
tracing::debug!("[Update::{}] Add Client: {pid}", self.name());
72+
tracing::debug!("[{}] Add Client: {pid}", self.name());
6973
self.clients.push(pid)
7074
}
7175

@@ -86,45 +90,65 @@ impl Workspace {
8690
self.project.targets().get(target_name)
8791
}
8892

89-
/// Checks whether current workspace is xcodegen project.
90-
pub fn is_xcodegen_project(&self) -> bool {
91-
/*
92-
TODO: support otherways to identify xcodegen project
93-
Some would have xcodegen config as json file or
94-
have different location to where they store xcodegen project config.
95-
*/
96-
self.root.join("project.yml").exists()
97-
}
98-
9993
/// Regenerate compiled commands and xcodeGen if project.yml exists
100-
pub async fn on_dirctory_change(&self, _path: PathBuf, _event: EventKind) -> Result<()> {
94+
pub async fn on_dirctory_change(&mut self, path: PathBuf, _event: EventKind) -> Result<()> {
10195
if self.is_xcodegen_project() {
102-
/*
103-
FIXME: make xCodeGen binary path configurable.
104-
105-
Current implementation will not work unless the user has xcodeGen located in
106-
`~/.mint/bin/xcodegen`. Should either make it configurable as well as support a
107-
number of paths by default.
108-
*/
109-
let xcodegen_path = dirs::home_dir().unwrap().join(".mint/bin/xcodegen");
110-
let xcodegen = Command::new(xcodegen_path)
111-
.current_dir(self.root.clone())
112-
.stdout(Stdio::null())
113-
.arg("generate")
114-
.spawn()
115-
.expect("Failed to start xcodeGen.")
116-
.wait()
117-
.await
118-
.expect("Failed to run xcodeGen.");
119-
120-
if xcodegen.success() {
121-
tracing::info!("Generated {}.xcodeproj", self.name());
96+
let is_config_file = path.file_name().unwrap().eq("project");
97+
self.update_xcodeproj(is_config_file).await?;
98+
}
99+
100+
xcode::ensure_server_config_file(&self.root).await?;
101+
xcode::update_compiled_commands(&self.root, self.project.fresh_build().await?).await?;
102+
103+
Ok(())
104+
}
105+
106+
/// Update .compile commands
107+
pub async fn update_xcodeproj(&mut self, update_config: bool) -> Result<()> {
108+
/*
109+
FIXME: make xCodeGen binary path configurable.
110+
111+
Current implementation will not work unless the user has xcodeGen located in
112+
`~/.mint/bin/xcodegen`. Should either make it configurable as well as support a
113+
number of paths by default.
114+
*/
115+
let xcodegen_path = dirs::home_dir().unwrap().join(".mint/bin/xcodegen");
116+
let xcodegen = Command::new(xcodegen_path)
117+
.current_dir(self.root.clone())
118+
.stdout(Stdio::null())
119+
.arg("generate")
120+
.spawn()
121+
.expect("Failed to start xcodeGen.")
122+
.wait()
123+
.await
124+
.expect("Failed to run xcodeGen.");
125+
126+
if xcodegen.success() {
127+
tracing::info!("Updated {}.xcodeproj", self.name());
128+
if update_config {
129+
tracing::debug!("Updated internal state.{}.project", self.name());
130+
let path = self.xcodegen_config_path();
131+
self.project = Project::new_from_project_yml(self.root.clone(), path).await?;
122132
}
123133
}
124134

125135
Ok(())
126136
}
127137

138+
/// Checks whether current workspace is xcodegen project.
139+
pub fn is_xcodegen_project(&self) -> bool {
140+
self.xcodegen_config_path().exists()
141+
}
142+
143+
pub fn xcodegen_config_path(&self) -> PathBuf {
144+
/*
145+
TODO: support otherways to identify xcodegen project
146+
147+
Some would have xcodegen config as json file or
148+
have different location to where they store xcodegen project config.
149+
*/
150+
self.root.join("project.yml")
151+
}
128152
pub fn get_ignore_patterns(&self) -> Option<Vec<String>> {
129153
if self.is_xcodegen_project() {
130154
return Some(self.project.config().ignore.clone());

0 commit comments

Comments
 (0)