Skip to content

Commit

Permalink
feat(compile): generate compile flags for files
Browse files Browse the repository at this point in the history
  • Loading branch information
kkharji committed Apr 26, 2022
1 parent d444372 commit 8855851
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 33 deletions.
29 changes: 25 additions & 4 deletions src/compile.rs
@@ -1,17 +1,37 @@
mod command;
mod flags;
pub use command::CompileCommand;
pub use flags::CompileFlags;

use crate::util::regex::matches_compile_swift_sources;
use anyhow::{Context, Result};
use serde::Deserialize;
use std::path::PathBuf;
use std::{ops::Deref, path::Path};
use tap::Pipe;

// TODO: Support compiling commands for objective-c files
// TODO: Test multiple module command compile

#[derive(Debug, Deserialize)]
pub struct CompileCommands(Vec<CompileCommand>);
pub struct CompileCommands(pub Vec<CompileCommand>);

impl IntoIterator for CompileCommands {
type Item = CompileCommand;

type IntoIter = std::vec::IntoIter<Self::Item>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

impl Deref for CompileCommands {
type Target = Vec<CompileCommand>;

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

impl CompileCommands {
pub fn from_logs(lines: Vec<String>) -> Self {
Expand Down Expand Up @@ -39,15 +59,15 @@ impl CompileCommands {
Self(commands)
}

pub fn from_file(path: &PathBuf) -> Result<Self> {
pub fn from_file(path: &Path) -> Result<Self> {
std::fs::read_to_string(path)?
.pipe_ref(|s| serde_json::from_str(s))
.context("Deserialize .compile")
}

/// Generate and write compile commands from build logs to directory
#[cfg(feature = "async")]
pub async fn update(dir: &PathBuf, build_log: Vec<String>) -> Result<()> {
pub async fn update(dir: &std::path::PathBuf, build_log: Vec<String>) -> Result<()> {
tracing::info!("Updating .compile in {:?}", dir);
Self::from_logs(build_log)
.pipe(|cmd| serde_json::to_vec_pretty(&cmd.0))?
Expand All @@ -59,6 +79,7 @@ impl CompileCommands {

#[test]
fn test() {
#[cfg(feature = "async")]
tokio::runtime::Runtime::new().unwrap().block_on(async {
use tap::Pipe;
tokio::fs::read_to_string("/Users/tami5/repos/swift/wordle/build.log")
Expand Down
76 changes: 57 additions & 19 deletions src/compile/command.rs
@@ -1,23 +1,26 @@
// See https://clang.llvm.org/docs/JSONCompilationDatabase.html
// See https://github.com/apple/sourcekit-lsp/blob/main/Sources/SKCore/CompilationDatabase.swift

use crate::util::fs;
use crate::util::regex::matches_compile_swift_sources;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

use crate::util::regex::matches_compile_swift_sources;
use super::CompileFlags;

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct CompileCommand {
/// Module name
/// NOTE: not sure if this required
#[serde(
rename(serialize = "module_name"),
skip_serializing_if = "String::is_empty"
skip_serializing_if = "Option::is_none"
)]
pub name: String,
pub name: Option<String>,

/// The path of the main file for the compilation, which may be relative to `directory`.
#[serde(skip_serializing_if = "String::is_empty")]
pub file: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub file: Option<String>,

/// The wroking directory for the compilation
pub directory: String,
Expand All @@ -26,24 +29,23 @@ pub struct CompileCommand {
pub command: String,

/// Source code files.
#[serde(rename(serialize = "fileLists"), skip_serializing_if = "Vec::is_empty")]
pub files: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub files: Option<Vec<String>>,

/// For SwiftFileList
#[serde(rename(serialize = "fileLists"))]
pub file_lists: Vec<String>,

/// The name of the build output
#[serde(skip_serializing_if = "String::is_empty")]
pub output: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<String>,

/// Index store path. Kept for the caller to further process.
#[serde(skip)]
pub index_store_path: Option<String>,
}

impl CompileCommand {
pub fn can_parse(line: &String) -> bool {
pub fn can_parse(line: &str) -> bool {
matches_compile_swift_sources(line)
}

Expand Down Expand Up @@ -75,17 +77,17 @@ impl CompileCommand {
};

// NOTE: This is never changed
let file = String::default();
let output = String::default();
let mut name = String::default();
let file = Default::default();
let output = Default::default();
let mut name = Default::default();
let mut files = Vec::default();
let mut file_lists = Vec::default();
let mut index_store_path = None;

for i in 0..arguments.len() {
let val = &arguments[i];
if val == "-module-name" {
name = arguments[i + 1].to_owned();
name = Some(arguments[i + 1].to_owned());
} else if val == "-index-store-path" {
index_store_path = Some(arguments[i + 1].to_owned());
} else if val.ends_with(".swift") {
Expand All @@ -101,7 +103,7 @@ impl CompileCommand {
name,
file,
output,
files,
files: if files.is_empty() { None } else { Some(files) },
file_lists,
index_store_path,
};
Expand All @@ -110,4 +112,40 @@ impl CompileCommand {
tracing::trace!("{:#?}", command);
Some(command)
}

/// Get a HashMap of workspace files and compile flags
pub fn compile_flags<'a>(&'a self) -> Result<HashMap<PathBuf, CompileFlags>> {
let mut info: HashMap<PathBuf, CompileFlags, _> = HashMap::default();
let flags = CompileFlags::from_command(&self.command)?;

// Swift File Lists
self.file_lists.iter().for_each(|path| {
let path = &PathBuf::from(path.as_str());
match fs::get_files_list(path) {
Ok(flist) => {
flist.into_iter().for_each(|file_path: PathBuf| {
info.insert(file_path, flags.clone());
});
}
Err(e) => tracing::error!("Fail to get file lists {e}"),
};
});

// Swift Module Files
if let Some(ref files) = self.files {
for file in files {
let file_path = PathBuf::from(file);
info.insert(file_path, flags.clone());
}
};

// Single File Command
if let Some(ref file) = self.file {
let file_path = PathBuf::from(file);

info.insert(file_path, flags.clone());
}

Ok(info)
}
}
135 changes: 135 additions & 0 deletions src/compile/flags.rs
@@ -0,0 +1,135 @@
use super::CompileCommand;
use crate::{
compile::CompileCommands,
util::fs::{self, find_header_dirs, find_swift_files, find_swift_module_root},
};
use anyhow::Result;
use std::{
collections::HashMap,
fs::read_to_string,
path::{Path, PathBuf},
};
use tap::{Pipe, Tap};

const SDKPATH: &str = "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/";

#[derive(Debug, Clone)]
pub struct CompileFlags(Vec<String>);

impl CompileFlags {
/// Generate compile flags from command
#[tracing::instrument(ret, skip_all, level = "trace")]
pub fn from_command(command: &str) -> Result<Self> {
command
.pipe(shell_words::split)
.map_err(anyhow::Error::from)?
.tap_mut(|flags| {
flags.remove(0);
})
.pipe(|flags| filter_swift_args(flags))?
.pipe(Self)
.pipe(Result::Ok)
}

/// Generate compile flags from filepath
#[tracing::instrument(ret, skip_all, level = "trace")]
pub fn from_filepath(filepath: &Path) -> Result<Self> {
let (ref project_root, swiftflags_filepath, compile_filepath) =
find_swift_module_root(filepath);
let flags;

if let Some(ref compile_filepath) = compile_filepath {
flags = CompileCommands::from_file(compile_filepath)?
.iter()
.flat_map(CompileCommand::compile_flags)
.flatten()
.collect::<HashMap<_, _>>()
.get(filepath)
.ok_or_else(|| anyhow::anyhow!("No flags for {:?}", filepath))?
.clone();
} else if let Some(ref swiftflags_filepath) = swiftflags_filepath {
let mut flags_collect = Vec::default();
let (headers, frameworks) = find_header_dirs(project_root)?;

headers
.into_iter()
.flat_map(|header| vec!["-Xcc".into(), "-I".into(), header])
.collect::<Vec<String>>()
.pipe_ref_mut(|flags| flags_collect.append(flags));

frameworks
.into_iter()
.map(|framework| format!("-F{framework}"))
.collect::<Vec<String>>()
.pipe_ref_mut(|flags| flags_collect.append(flags));

find_swift_files(project_root)?.pipe_ref_mut(|flags| flags_collect.append(flags));

if let Some(ref mut additional_flags) = additional_flags(swiftflags_filepath) {
flags_collect.append(additional_flags)
}

flags = flags_collect.pipe(Self);
} else {
flags = filepath
.to_str()
.ok_or_else(|| {
anyhow::anyhow!("Couldn't convert filepath to string {:?}", filepath)
})?
.pipe(|f| vec![f.into(), "-sdk".into(), SDKPATH.into()])
.pipe(Self)
}

Ok(flags)
}
}

impl std::ops::Deref for CompileFlags {
type Target = Vec<String>;

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

/// Get Additional flags from an optional flags_path.
fn additional_flags(flags_path: &Path) -> Option<Vec<String>> {
read_to_string(flags_path)
.ok()?
.split("\n")
.filter(|line| line.starts_with("#"))
.map(|line| line.trim().to_string())
.collect::<Vec<_>>()
.into()
}

/// Filter swift arguments
fn filter_swift_args(flags: Vec<String>) -> Result<Vec<String>> {
let mut args = vec![];
let mut items = flags.into_iter();
while let Some(arg) = items.next() {
// sourcekit dont support filelist, unfold it
if arg == "-filelist" {
items
.next()
.unwrap()
.pipe(PathBuf::from)
.pipe(fs::get_files_list)?
.pipe_as_mut(|paths| args.append(paths));
}

// swift 5.1 filelist, unfold it
if arg.starts_with("@") {
arg.strip_prefix("@")
.unwrap()
.pipe(fs::get_files_list)?
.pipe_as_mut(|paths| args.append(paths));

continue;
}

args.push(arg)
}

Ok(args)
}
3 changes: 0 additions & 3 deletions src/lib.rs
Expand Up @@ -30,6 +30,3 @@ pub use util::watch;

#[cfg(feature = "server")]
pub mod server;

#[cfg(feature = "server")]
pub use server::*;
13 changes: 6 additions & 7 deletions src/state/workspace.rs
@@ -1,16 +1,12 @@
#[cfg(feature = "proc")]
use crate::util::proc;

#[cfg(feature = "xcode")]
use crate::xcode;

#[cfg(feature = "daemon")]
use anyhow::Result;

#[cfg(feature = "xcodegen")]
use crate::xcodegen;

use crate::compile::CompileCommands;
use crate::Project;
use std::path::PathBuf;

Expand Down Expand Up @@ -92,7 +88,7 @@ impl Workspace {
self.project.targets().get(target_name)
}
/// Regenerate compiled commands and xcodeGen if project.yml exists
#[cfg(all(feature = "xcode", feature = "watcher"))]
#[cfg(feature = "watcher")]
pub async fn on_directory_change(
&mut self,
path: PathBuf,
Expand All @@ -103,8 +99,11 @@ impl Workspace {
.await?;
}

xcode::ensure_server_config_file(&self.root).await?;
CompileCommands::update(&self.root, self.project.fresh_build().await?).await?;
#[cfg(feature = "xcode")]
crate::xcode::ensure_server_config_file(&self.root).await?;
#[cfg(feature = "compilation")]
crate::compile::CompileCommands::update(&self.root, self.project.fresh_build().await?)
.await?;

Ok(())
}
Expand Down

0 comments on commit 8855851

Please sign in to comment.