Skip to content

Commit

Permalink
feature: compiling of projects instead of files
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Jun 14, 2020
1 parent 73d0839 commit 5d04ec7
Show file tree
Hide file tree
Showing 25 changed files with 856 additions and 171 deletions.
1 change: 1 addition & 0 deletions crates/mun/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mun_compiler = { version = "=0.2.0", path = "../mun_compiler" }
mun_compiler_daemon = { version = "=0.2.0", path = "../mun_compiler_daemon" }
mun_runtime = { version = "=0.2.0", path = "../mun_runtime" }
mun_language_server = { version = "=0.1.0", path = "../mun_language_server" }
mun_project = { version = "=0.1.0", path = "../mun_project" }

[dev-dependencies.cargo-husky]
version = "1"
Expand Down
105 changes: 75 additions & 30 deletions crates/mun/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use std::time::Duration;

use anyhow::anyhow;
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use mun_compiler::{Config, DisplayColor, PathOrInline, Target};
use mun_compiler::{Config, DisplayColor, Target};
use mun_project::MANIFEST_FILENAME;
use mun_runtime::{invoke_fn, ReturnTypeReflection, Runtime, RuntimeBuilder};
use std::path::{Path, PathBuf};

fn setup_logging() -> Result<(), anyhow::Error> {
pretty_env_logger::try_init()?;
Expand All @@ -23,15 +25,17 @@ fn main() -> Result<(), anyhow::Error> {
.subcommand(
SubCommand::with_name("build")
.arg(
Arg::with_name("INPUT")
.help("Sets the input file to use")
.required(true)
.index(1),
Arg::with_name("manifest-path")
.long("manifest-path")
.takes_value(true)
.help(&format!("Path to {}", MANIFEST_FILENAME))
)
.arg(
Arg::with_name("watch")
.long("watch")
.help("Run the compiler in watch mode.\
Watch input files and trigger recompilation on changes.",)
)
.arg(Arg::with_name("watch").long("watch").help(
"Run the compiler in watch mode.
Watch input files and trigger recompilation on changes.",
))
.arg(
Arg::with_name("opt-level")
.short("O")
Expand Down Expand Up @@ -80,23 +84,61 @@ fn main() -> Result<(), anyhow::Error> {
)
.get_matches();

match matches.subcommand() {
let success = match matches.subcommand() {
("build", Some(matches)) => build(matches)?,
("start", Some(matches)) => start(matches)?,
("language-server", Some(matches)) => language_server(matches)?,
("start", Some(matches)) => {
start(matches)?;
true
}
_ => unreachable!(),
}
};

Ok(())
std::process::exit(if success { 0 } else { 1 });
}

/// Build the source file specified
fn build(matches: &ArgMatches) -> Result<(), anyhow::Error> {
/// Find a Mun manifest file in the specified directory or one of its parents.
pub fn find_manifest(directory: &Path) -> Option<PathBuf> {
let mut current_dir = Some(directory);
while let Some(dir) = current_dir {
let manifest_path = dir.join(MANIFEST_FILENAME);
if manifest_path.exists() {
return Some(manifest_path);
}
current_dir = dir.parent();
}
None
}

/// This method is invoked when the executable is run with the `build` argument indicating that a
/// user requested us to build a project in the current directory or one of its parent directories.
///
/// The `bool` return type for this function indicates whether the process should exit with a
/// success or failure error code.
fn build(matches: &ArgMatches) -> Result<bool, anyhow::Error> {
let options = compiler_options(matches)?;

// Locate the manifest
let manifest_path = match matches.value_of("manifest-path") {
None => {
let current_dir =
std::env::current_dir().expect("could not determine currrent working directory");
find_manifest(&current_dir).ok_or_else(|| {
anyhow::anyhow!(
"could not find {} in '{}' or a parent directory",
MANIFEST_FILENAME,
current_dir.display()
)
})?
}
Some(path) => std::fs::canonicalize(Path::new(path))
.map_err(|_| anyhow::anyhow!("'{}' does not refer to a valid manifest path", path))?,
};

if matches.is_present("watch") {
mun_compiler_daemon::main(options)
mun_compiler_daemon::compile_and_watch_manifest(&manifest_path, options)
} else {
mun_compiler::main(options).map(|_| {})
mun_compiler::compile_manifest(&manifest_path, options)
}
}

Expand Down Expand Up @@ -142,7 +184,7 @@ fn start(matches: &ArgMatches) -> Result<(), anyhow::Error> {
}
}

fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOptions, anyhow::Error> {
fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::Config, anyhow::Error> {
let optimization_lvl = match matches.value_of("opt-level") {
Some("0") => mun_compiler::OptimizationLevel::None,
Some("1") => mun_compiler::OptimizationLevel::Less,
Expand All @@ -162,16 +204,13 @@ fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOption
})
.unwrap_or(DisplayColor::Auto);

Ok(mun_compiler::CompilerOptions {
input: PathOrInline::Path(matches.value_of("INPUT").unwrap().into()), // Safe because its a required arg
config: Config {
target: matches
.value_of("target")
.map_or_else(Target::host_target, Target::search)?,
optimization_lvl,
out_dir: None,
display_color,
},
Ok(Config {
target: matches
.value_of("target")
.map_or_else(Target::host_target, Target::search)?,
optimization_lvl,
out_dir: None,
display_color,
})
}

Expand All @@ -190,6 +229,12 @@ fn runtime(matches: &ArgMatches) -> Result<Rc<RefCell<Runtime>>, anyhow::Error>
builder.spawn()
}

fn language_server(_matches: &ArgMatches) -> Result<(), anyhow::Error> {
mun_language_server::run_server().map_err(|e| anyhow!("{}", e))
/// This function is invoked when the executable is invoked with the `language-server` argument. A
/// Mun language server is started ready to serve language information about one or more projects.
///
/// The `bool` return type for this function indicates whether the process should exit with a
/// success or failure error code.
fn language_server(_matches: &ArgMatches) -> Result<bool, anyhow::Error> {
mun_language_server::run_server().map_err(|e| anyhow::anyhow!("{}", e))?;
Ok(true)
}
46 changes: 46 additions & 0 deletions crates/mun_codegen/src/assembly.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use crate::{IrDatabase, ModuleBuilder};
use std::path::Path;
use std::sync::Arc;
use tempfile::NamedTempFile;

#[derive(Debug)]
pub struct Assembly {
file: NamedTempFile,
}

impl PartialEq for Assembly {
fn eq(&self, other: &Self) -> bool {
self.path().eq(other.path())
}
}

impl Eq for Assembly {}

impl Assembly {
pub const EXTENSION: &'static str = "munlib";

/// Returns the current location of the assembly
pub fn path(&self) -> &Path {
self.file.path()
}

/// Copies the assembly to the specified location
pub fn copy_to<P: AsRef<Path>>(&self, destination: P) -> Result<(), std::io::Error> {
std::fs::copy(self.path(), destination).map(|_| ())
}
}

/// Create a new temporary file that contains the linked object
pub fn assembly_query(db: &impl IrDatabase, file_id: hir::FileId) -> Arc<Assembly> {
let file = NamedTempFile::new().expect("could not create temp file for shared object");

let module_builder = ModuleBuilder::new(db, file_id).expect("could not create ModuleBuilder");
let obj_file = module_builder
.build()
.expect("unable to create object file");
obj_file
.into_shared_object(file.path())
.expect("could not link object file");

Arc::new(Assembly { file })
}
34 changes: 6 additions & 28 deletions crates/mun_codegen/src/code_gen.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::code_gen::linker::LinkerError;
use crate::value::{IrTypeContext, IrValueContext};
use crate::IrDatabase;
use hir::{FileId, RelativePathBuf};
use hir::FileId;
use inkwell::targets::TargetData;
use inkwell::{
module::Module,
Expand All @@ -11,10 +11,7 @@ use inkwell::{
};
use mun_target::spec;
use std::io::{self, Write};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use std::{path::Path, sync::Arc};
use tempfile::NamedTempFile;
use thiserror::Error;

Expand Down Expand Up @@ -45,15 +42,14 @@ impl From<LinkerError> for CodeGenerationError {

pub struct ObjectFile {
target: spec::Target,
src_path: RelativePathBuf,
obj_file: NamedTempFile,
}

impl ObjectFile {
/// Constructs a new object file from the specified `module` for `target`
pub fn new(
target: &spec::Target,
target_machine: &TargetMachine,
src_path: RelativePathBuf,
module: Arc<inkwell::module::Module>,
) -> Result<Self, anyhow::Error> {
let obj = target_machine
Expand All @@ -68,23 +64,21 @@ impl ObjectFile {

Ok(Self {
target: target.clone(),
src_path,
obj_file,
})
}

pub fn into_shared_object(self, out_dir: Option<&Path>) -> Result<PathBuf, anyhow::Error> {
/// Links the object file into a shared object.
pub fn into_shared_object(self, output_path: &Path) -> Result<(), anyhow::Error> {
// Construct a linker for the target
let mut linker = linker::create_with_target(&self.target);
linker.add_object(self.obj_file.path())?;

let output_path = assembly_output_path(&self.src_path, out_dir);

// Link the object
linker.build_shared_object(&output_path)?;
linker.finalize()?;

Ok(output_path)
Ok(())
}
}

Expand Down Expand Up @@ -182,27 +176,11 @@ impl<'a, D: IrDatabase> ModuleBuilder<'a, D> {
ObjectFile::new(
&self.db.target(),
&self.target_machine,
self.db.file_relative_path(self.file_id),
self.assembly_module,
)
}
}

/// Computes the output path for the assembly of the specified file.
fn assembly_output_path(src_path: &RelativePathBuf, out_dir: Option<&Path>) -> PathBuf {
let original_filename = Path::new(src_path.file_name().unwrap());

// Add the `munlib` suffix to the original filename
let output_file_name = original_filename.with_extension("munlib");

// If there is an out dir specified, prepend the output directory
if let Some(out_dir) = out_dir {
out_dir.join(output_file_name)
} else {
output_file_name
}
}

/// Optimizes the specified LLVM `Module` using the default passes for the given
/// `OptimizationLevel`.
fn optimize_module(module: &Module, optimization_lvl: OptimizationLevel) {
Expand Down
6 changes: 6 additions & 0 deletions crates/mun_codegen/src/db.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(clippy::type_repetition_in_bounds)]

use crate::{
assembly::Assembly,
ir::{file::FileIR, file_group::FileGroupIR},
type_info::TypeInfo,
CodeGenParams, Context,
Expand Down Expand Up @@ -42,6 +43,11 @@ pub trait IrDatabase: hir::HirDatabase {
#[salsa::invoke(crate::ir::file_group::ir_query)]
fn group_ir(&self, file: hir::FileId) -> Arc<FileGroupIR>;

/// Returns a fully linked shared object for the specified group of files.
/// TODO: Current, a group always consists of a single file. Need to add support for multiple.
#[salsa::invoke(crate::assembly::assembly_query)]
fn assembly(&self, file: hir::FileId) -> Arc<Assembly>;

/// Given a `hir::FileId` generate code for the module.
#[salsa::invoke(crate::ir::file::ir_query)]
fn file_ir(&self, file: hir::FileId) -> Arc<FileIR>;
Expand Down
2 changes: 2 additions & 0 deletions crates/mun_codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod code_gen;
mod db;
#[macro_use]
mod ir;
mod assembly;

#[cfg(test)]
mod mock;
Expand All @@ -17,6 +18,7 @@ pub(crate) mod type_info;
pub use inkwell::{builder::Builder, context::Context, module::Module, OptimizationLevel};

pub use crate::{
assembly::Assembly,
code_gen::ModuleBuilder,
db::{IrDatabase, IrDatabaseStorage},
};
Expand Down
2 changes: 1 addition & 1 deletion crates/mun_codegen/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ impl MockDatabase {
db.set_file_relative_path(file_id, rel_path.clone());
db.set_file_text(file_id, Arc::new(text.to_string()));
db.set_file_source_root(file_id, source_root_id);
source_root.insert_file(rel_path, file_id);
source_root.insert_file(file_id);

db.set_source_root(source_root_id, Arc::new(source_root));
db.set_optimization_lvl(OptimizationLevel::None);
Expand Down
2 changes: 2 additions & 0 deletions crates/mun_compiler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ mun_codegen = { version = "=0.2.0", path="../mun_codegen" }
mun_syntax = { version = "=0.2.0", path="../mun_syntax" }
mun_hir = { version = "=0.2.0", path="../mun_hir" }
mun_target = { version = "=0.2.0", path="../mun_target" }
mun_project = { version = "=0.1.0", path = "../mun_project" }
annotate-snippets = { version = "0.6.1", features = ["color"] }
unicode-segmentation = "1.6.0"
ansi_term = "0.12.1"
walkdir = "2.3"

[dev-dependencies]
insta = "0.13.1"
Loading

0 comments on commit 5d04ec7

Please sign in to comment.