Skip to content

Commit

Permalink
feat(run): run project from neovim
Browse files Browse the repository at this point in the history
  • Loading branch information
kkharji committed May 17, 2022
1 parent ef16dc4 commit a0531a3
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 35 deletions.
9 changes: 9 additions & 0 deletions lua/xbase/pickers.lua
Expand Up @@ -86,6 +86,15 @@ local get_selections = function(picker)
end
end
end
if picker == "Run" or picker == "Watch" then
table.sort(results, function(a, b)
if a.device and b.device then
return a.device.is_on and not b.device.is_on
else
return
end
end)
end

return results
end
Expand Down
1 change: 1 addition & 0 deletions lua/xbase/util.lua
Expand Up @@ -10,6 +10,7 @@ local get_runners = function(platform)
name = device.info.name,
udid = device.info.udid,
platform = platform,
is_on = device.info.state ~= "Shutdown",
})
end
end
Expand Down
169 changes: 150 additions & 19 deletions src/daemon/requests/run.rs
@@ -1,39 +1,170 @@
use super::*;
use crate::{
nvim::BufferDirection,
types::{BuildConfiguration, Platform},
};

#[cfg(feature = "daemon")]
use {crate::constants::DAEMON_STATE, crate::types::SimDevice, anyhow::anyhow as err};

#[derive(Debug, Serialize, Deserialize)]
pub struct DeviceLookup {
name: String,
udid: String,
platform: Platform,
}

/// Run a project.
#[derive(Debug, Serialize, Deserialize)]
pub struct Run {
simulator: bool,
client: Client,
pub client: Client,
pub config: BuildConfiguration,
pub device: Option<DeviceLookup>,
pub direction: Option<BufferDirection>,
}

#[cfg(feature = "lua")]
impl<'a> Requester<'a, Run> for Run {}

// TODO: Implement run command
//
// Also, it might be important to pick which target/paltform to run under. This is currently just
// either with a simulator or not assuming only the use case won't include
// macos apps, which is wrong
#[cfg(feature = "daemon")]
#[async_trait::async_trait]
impl Handler for Run {
async fn handle(self) -> Result<()> {
tracing::info!("Run command");
tracing::info!("⚙️ Running command: {:#?}", self);

let Self {
client,
config,
device,
..
} = self;
let Client { pid, root } = client;

let direction = self.direction.clone();
let state = DAEMON_STATE.clone().lock_owned().await;
let platform = if let Some(d) = device.as_ref() {
Some(d.platform.clone())
} else {
None
};

let nvim = state
.clients
.get(&pid)
.ok_or_else(|| err!("no client found with {}", pid))?;

let args = {
let mut args = config.as_args();
if let Some(platform) = platform {
args.extend(platform.sdk_simulator_args())
}
args
};

let build_settings = xcodebuild::runner::build_settings(&root, &args).await?;
let ref app_id = build_settings.product_bundle_identifier;

// FIX(run): When running with release path_to_app is incorrect
//
// Err: application bundle was not found at the provided path.\nProvide a valid path to the desired application bundle.
//
// Path doesn't point to local directory build
let ref path_to_app = build_settings.metal_library_output_dir;

tracing::debug!("{app_id}: {:?}", path_to_app);
let (success, ref win) = nvim
.new_logger("Build", &config.target, &direction)
.log_build_stream(&root, &args, false, true)
.await?;

if !success {
let msg = format!("Failed: {} ", config.to_string());
nvim.echo_err(&msg).await?;
anyhow::bail!("{msg}");
}

let ref mut logger = nvim.new_logger("Run", &config.target, &direction);

logger.set_running().await?;

if let Some(mut device) = get_device(&state, device) {
device.try_boot(logger, win).await?;
device.try_install(path_to_app, app_id, logger, win).await?;
device.try_launch(app_id, logger, win).await?;

logger.set_status_end(true, true).await?;

tokio::spawn(async move {
let mut state = DAEMON_STATE.clone().lock_owned().await;
state.devices.insert(device);
state.sync_client_state().await
});
} else {
// TODO: check if macOS is the platform and run it
}

Ok(())
}
}

// let target = project.get_target(&config.target, ,)?;
#[cfg(feature = "daemon")]
fn get_device<'a>(
state: &'a tokio::sync::OwnedMutexGuard<crate::state::State>,
device: Option<DeviceLookup>,
) -> Option<SimDevice> {
if let Some(device) = device {
state
.devices
.iter()
.find(|d| d.name == device.name && d.udid == device.udid)
.cloned()
} else {
None
}
}

#[cfg(feature = "lua")]
impl<'a> FromLua<'a> for Run {
fn from_lua(lua_value: LuaValue<'a>, _lua: &'a Lua) -> LuaResult<Self> {
if let LuaValue::Table(table) = lua_value {
Ok(Self {
simulator: table.get("simulator")?,
client: table.get("client")?,
})
impl<'a> Requester<'a, Run> for Run {
fn pre(lua: &Lua, msg: &Run) -> LuaResult<()> {
lua.print(&msg.to_string());
Ok(())
}
}

impl ToString for Run {
fn to_string(&self) -> String {
if let Some(ref device) = self.device {
format!("run [{}] with {}", device.name, self.config.to_string())
} else {
Err(LuaError::external("Fail to deserialize Run"))
format!("run with {}", self.config.to_string())
}
}
}

#[cfg(feature = "lua")]
impl<'a> FromLua<'a> for Run {
fn from_lua(lua_value: LuaValue<'a>, _lua: &'a Lua) -> LuaResult<Self> {
let table = match lua_value {
LuaValue::Table(t) => Ok(t),
_ => Err(LuaError::external("Fail to deserialize Run")),
}?;

let device: Option<LuaTable> = table.get("device")?;

Ok(Self {
client: table.get("client")?,
config: table.get("config")?,
direction: table.get("direction").ok(),
device: device
.map(|d| {
let name = d.get("name").ok()?;
let udid = d.get("udid").ok()?;
let platform = d.get("platform").ok()?;
Some(DeviceLookup {
name,
udid,
platform,
})
})
.flatten(),
})
}
}
8 changes: 4 additions & 4 deletions src/store/mod.rs
Expand Up @@ -2,10 +2,10 @@ mod projects;

pub use projects::ProjectStore;

#[cfg(any(feature = "daemon", feature = "lua"))]
mod devices;
#[cfg(any(feature = "daemon", feature = "lua"))]
pub use devices::*;
#[cfg(feature = "daemon")]
mod simdevices;
#[cfg(feature = "daemon")]
pub use simdevices::*;

#[cfg(feature = "daemon")]
mod clients;
Expand Down
30 changes: 20 additions & 10 deletions src/store/devices.rs → src/store/simdevices.rs
@@ -1,8 +1,25 @@
use crate::types::SimDevice;
use serde::{Deserialize, Serialize};
use simctl::{Device, Simctl};
use simctl::Simctl;
use std::collections::HashSet;
use std::ops::{Deref, DerefMut};

#[derive(Debug, Serialize, Deserialize)]
pub struct SimDevices(Vec<Device>);
pub struct SimDevices(HashSet<SimDevice>);

impl Deref for SimDevices {
type Target = HashSet<SimDevice>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for SimDevices {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl Default for SimDevices {
fn default() -> Self {
Expand All @@ -14,15 +31,8 @@ impl Default for SimDevices {
.to_vec()
.into_iter()
.filter(|d| d.is_available)
.map(SimDevice::new)
.collect(),
)
}
}

impl std::ops::Deref for SimDevices {
type Target = Vec<Device>;

fn deref(&self) -> &Self::Target {
&self.0
}
}
5 changes: 5 additions & 0 deletions src/types.rs
@@ -1,9 +1,14 @@
mod build;
mod client;
mod project;
#[cfg(feature = "daemon")]
mod simdevice;

pub use build::*;
pub use client::*;
pub use project::*;

#[cfg(feature = "daemon")]
pub use simdevice::*;

pub type Root = std::path::PathBuf;
11 changes: 11 additions & 0 deletions src/types/project/platform.rs
Expand Up @@ -14,6 +14,17 @@ pub enum Platform {
}

impl Platform {
pub fn sdk_simulator_args(&self) -> Vec<String> {
match self {
Platform::IOS => vec!["-sdk".into(), "iphonesimulator".into()],
Platform::WatchOS => vec!["-sdk".into(), "watchsimulator".into()],
Platform::TvOS => vec!["-sdk".into(), "appletvsimulator".into()],
Platform::MacOS => vec!["-sdk".into(), "macosx".into()],
Platform::None => vec![],
}
// -sdk driverkit -sdk iphoneos -sdk macosx -sdk appletvos -sdk watchos
}

#[must_use]
pub fn is_ios(&self) -> bool {
matches!(self, Self::IOS)
Expand Down

0 comments on commit a0531a3

Please sign in to comment.