Skip to content

Commit a0531a3

Browse files
committed
feat(run): run project from neovim
1 parent ef16dc4 commit a0531a3

File tree

9 files changed

+318
-35
lines changed

9 files changed

+318
-35
lines changed

lua/xbase/pickers.lua

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,15 @@ local get_selections = function(picker)
8686
end
8787
end
8888
end
89+
if picker == "Run" or picker == "Watch" then
90+
table.sort(results, function(a, b)
91+
if a.device and b.device then
92+
return a.device.is_on and not b.device.is_on
93+
else
94+
return
95+
end
96+
end)
97+
end
8998

9099
return results
91100
end

lua/xbase/util.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ local get_runners = function(platform)
1010
name = device.info.name,
1111
udid = device.info.udid,
1212
platform = platform,
13+
is_on = device.info.state ~= "Shutdown",
1314
})
1415
end
1516
end

src/daemon/requests/run.rs

Lines changed: 150 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,170 @@
11
use super::*;
2+
use crate::{
3+
nvim::BufferDirection,
4+
types::{BuildConfiguration, Platform},
5+
};
6+
7+
#[cfg(feature = "daemon")]
8+
use {crate::constants::DAEMON_STATE, crate::types::SimDevice, anyhow::anyhow as err};
9+
10+
#[derive(Debug, Serialize, Deserialize)]
11+
pub struct DeviceLookup {
12+
name: String,
13+
udid: String,
14+
platform: Platform,
15+
}
216

317
/// Run a project.
418
#[derive(Debug, Serialize, Deserialize)]
519
pub struct Run {
6-
simulator: bool,
7-
client: Client,
20+
pub client: Client,
21+
pub config: BuildConfiguration,
22+
pub device: Option<DeviceLookup>,
23+
pub direction: Option<BufferDirection>,
824
}
925

10-
#[cfg(feature = "lua")]
11-
impl<'a> Requester<'a, Run> for Run {}
12-
13-
// TODO: Implement run command
14-
//
15-
// Also, it might be important to pick which target/paltform to run under. This is currently just
16-
// either with a simulator or not assuming only the use case won't include
17-
// macos apps, which is wrong
1826
#[cfg(feature = "daemon")]
1927
#[async_trait::async_trait]
2028
impl Handler for Run {
2129
async fn handle(self) -> Result<()> {
22-
tracing::info!("Run command");
30+
tracing::info!("⚙️ Running command: {:#?}", self);
31+
32+
let Self {
33+
client,
34+
config,
35+
device,
36+
..
37+
} = self;
38+
let Client { pid, root } = client;
39+
40+
let direction = self.direction.clone();
41+
let state = DAEMON_STATE.clone().lock_owned().await;
42+
let platform = if let Some(d) = device.as_ref() {
43+
Some(d.platform.clone())
44+
} else {
45+
None
46+
};
47+
48+
let nvim = state
49+
.clients
50+
.get(&pid)
51+
.ok_or_else(|| err!("no client found with {}", pid))?;
52+
53+
let args = {
54+
let mut args = config.as_args();
55+
if let Some(platform) = platform {
56+
args.extend(platform.sdk_simulator_args())
57+
}
58+
args
59+
};
60+
61+
let build_settings = xcodebuild::runner::build_settings(&root, &args).await?;
62+
let ref app_id = build_settings.product_bundle_identifier;
63+
64+
// FIX(run): When running with release path_to_app is incorrect
65+
//
66+
// Err: application bundle was not found at the provided path.\nProvide a valid path to the desired application bundle.
67+
//
68+
// Path doesn't point to local directory build
69+
let ref path_to_app = build_settings.metal_library_output_dir;
70+
71+
tracing::debug!("{app_id}: {:?}", path_to_app);
72+
let (success, ref win) = nvim
73+
.new_logger("Build", &config.target, &direction)
74+
.log_build_stream(&root, &args, false, true)
75+
.await?;
76+
77+
if !success {
78+
let msg = format!("Failed: {} ", config.to_string());
79+
nvim.echo_err(&msg).await?;
80+
anyhow::bail!("{msg}");
81+
}
82+
83+
let ref mut logger = nvim.new_logger("Run", &config.target, &direction);
84+
85+
logger.set_running().await?;
86+
87+
if let Some(mut device) = get_device(&state, device) {
88+
device.try_boot(logger, win).await?;
89+
device.try_install(path_to_app, app_id, logger, win).await?;
90+
device.try_launch(app_id, logger, win).await?;
91+
92+
logger.set_status_end(true, true).await?;
93+
94+
tokio::spawn(async move {
95+
let mut state = DAEMON_STATE.clone().lock_owned().await;
96+
state.devices.insert(device);
97+
state.sync_client_state().await
98+
});
99+
} else {
100+
// TODO: check if macOS is the platform and run it
101+
}
102+
23103
Ok(())
24104
}
25105
}
26106

107+
// let target = project.get_target(&config.target, ,)?;
108+
#[cfg(feature = "daemon")]
109+
fn get_device<'a>(
110+
state: &'a tokio::sync::OwnedMutexGuard<crate::state::State>,
111+
device: Option<DeviceLookup>,
112+
) -> Option<SimDevice> {
113+
if let Some(device) = device {
114+
state
115+
.devices
116+
.iter()
117+
.find(|d| d.name == device.name && d.udid == device.udid)
118+
.cloned()
119+
} else {
120+
None
121+
}
122+
}
123+
27124
#[cfg(feature = "lua")]
28-
impl<'a> FromLua<'a> for Run {
29-
fn from_lua(lua_value: LuaValue<'a>, _lua: &'a Lua) -> LuaResult<Self> {
30-
if let LuaValue::Table(table) = lua_value {
31-
Ok(Self {
32-
simulator: table.get("simulator")?,
33-
client: table.get("client")?,
34-
})
125+
impl<'a> Requester<'a, Run> for Run {
126+
fn pre(lua: &Lua, msg: &Run) -> LuaResult<()> {
127+
lua.print(&msg.to_string());
128+
Ok(())
129+
}
130+
}
131+
132+
impl ToString for Run {
133+
fn to_string(&self) -> String {
134+
if let Some(ref device) = self.device {
135+
format!("run [{}] with {}", device.name, self.config.to_string())
35136
} else {
36-
Err(LuaError::external("Fail to deserialize Run"))
137+
format!("run with {}", self.config.to_string())
37138
}
38139
}
39140
}
141+
142+
#[cfg(feature = "lua")]
143+
impl<'a> FromLua<'a> for Run {
144+
fn from_lua(lua_value: LuaValue<'a>, _lua: &'a Lua) -> LuaResult<Self> {
145+
let table = match lua_value {
146+
LuaValue::Table(t) => Ok(t),
147+
_ => Err(LuaError::external("Fail to deserialize Run")),
148+
}?;
149+
150+
let device: Option<LuaTable> = table.get("device")?;
151+
152+
Ok(Self {
153+
client: table.get("client")?,
154+
config: table.get("config")?,
155+
direction: table.get("direction").ok(),
156+
device: device
157+
.map(|d| {
158+
let name = d.get("name").ok()?;
159+
let udid = d.get("udid").ok()?;
160+
let platform = d.get("platform").ok()?;
161+
Some(DeviceLookup {
162+
name,
163+
udid,
164+
platform,
165+
})
166+
})
167+
.flatten(),
168+
})
169+
}
170+
}

src/store/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ mod projects;
22

33
pub use projects::ProjectStore;
44

5-
#[cfg(any(feature = "daemon", feature = "lua"))]
6-
mod devices;
7-
#[cfg(any(feature = "daemon", feature = "lua"))]
8-
pub use devices::*;
5+
#[cfg(feature = "daemon")]
6+
mod simdevices;
7+
#[cfg(feature = "daemon")]
8+
pub use simdevices::*;
99

1010
#[cfg(feature = "daemon")]
1111
mod clients;
Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
1+
use crate::types::SimDevice;
12
use serde::{Deserialize, Serialize};
2-
use simctl::{Device, Simctl};
3+
use simctl::Simctl;
4+
use std::collections::HashSet;
5+
use std::ops::{Deref, DerefMut};
36

47
#[derive(Debug, Serialize, Deserialize)]
5-
pub struct SimDevices(Vec<Device>);
8+
pub struct SimDevices(HashSet<SimDevice>);
9+
10+
impl Deref for SimDevices {
11+
type Target = HashSet<SimDevice>;
12+
13+
fn deref(&self) -> &Self::Target {
14+
&self.0
15+
}
16+
}
17+
18+
impl DerefMut for SimDevices {
19+
fn deref_mut(&mut self) -> &mut Self::Target {
20+
&mut self.0
21+
}
22+
}
623

724
impl Default for SimDevices {
825
fn default() -> Self {
@@ -14,15 +31,8 @@ impl Default for SimDevices {
1431
.to_vec()
1532
.into_iter()
1633
.filter(|d| d.is_available)
34+
.map(SimDevice::new)
1735
.collect(),
1836
)
1937
}
2038
}
21-
22-
impl std::ops::Deref for SimDevices {
23-
type Target = Vec<Device>;
24-
25-
fn deref(&self) -> &Self::Target {
26-
&self.0
27-
}
28-
}

src/types.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
mod build;
22
mod client;
33
mod project;
4+
#[cfg(feature = "daemon")]
5+
mod simdevice;
46

57
pub use build::*;
68
pub use client::*;
79
pub use project::*;
810

11+
#[cfg(feature = "daemon")]
12+
pub use simdevice::*;
13+
914
pub type Root = std::path::PathBuf;

src/types/project/platform.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ pub enum Platform {
1414
}
1515

1616
impl Platform {
17+
pub fn sdk_simulator_args(&self) -> Vec<String> {
18+
match self {
19+
Platform::IOS => vec!["-sdk".into(), "iphonesimulator".into()],
20+
Platform::WatchOS => vec!["-sdk".into(), "watchsimulator".into()],
21+
Platform::TvOS => vec!["-sdk".into(), "appletvsimulator".into()],
22+
Platform::MacOS => vec!["-sdk".into(), "macosx".into()],
23+
Platform::None => vec![],
24+
}
25+
// -sdk driverkit -sdk iphoneos -sdk macosx -sdk appletvos -sdk watchos
26+
}
27+
1728
#[must_use]
1829
pub fn is_ios(&self) -> bool {
1930
matches!(self, Self::IOS)

0 commit comments

Comments
 (0)