Skip to content

Commit

Permalink
Merge pull request #50 from lf-lang/librarify
Browse files Browse the repository at this point in the history
Librarify
  • Loading branch information
lhstrh authored Oct 16, 2024
2 parents 1a508b9 + a95182a commit a4a0f0f
Show file tree
Hide file tree
Showing 15 changed files with 620 additions and 242 deletions.
499 changes: 356 additions & 143 deletions Cargo.lock

Large diffs are not rendered by default.

19 changes: 16 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,37 @@ homepage = "https://lf-lang.org"
repository = "https://github.com/lf-lang/lingo"
license = "BSD-2-Clause"

[lib]
name = "liblingo"
path = "src/lib.rs"
crate-type = ["cdylib", "rlib"]

[[bin]]
name = "lingo"
publish = true
path = "./src/main.rs"
required-features = ["binary"]

[features]
default = ["binary"]
binary = ["which", "git2"]

[dependencies]

clap = { version = "4.1", features = ["derive"] }
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
which = "6.0"
regex = "1.8"
lazy_static = "1.4"
rayon = "1.7"
toml = {version = "0.8"}
toml = { version = "0.8" }
crossbeam = "0.8"
git2 = "0.18"
run_script = "0.11"
getrandom = {version="0.2", features = ["js"]}
which = { version = "6.0", optional = true }
git2 = { version = "0.19", optional = true, default-features=false, features = ["https"]}
print_logger = "0.2.0"
tempfile = "3.0"
url = { version = "2.5", features = ["serde"] }
anyhow = "1.0"
Expand Down
1 change: 1 addition & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::path::PathBuf;
#[value(rename_all = "lowercase")]
pub enum TargetLanguage {
C,
CCpp,
Cpp,
Rust,
TypeScript,
Expand Down
2 changes: 1 addition & 1 deletion src/backends/cmake_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ use std::process::Command;
use crate::backends::{
BatchBackend, BatchBuildResults, BuildCommandOptions, BuildProfile, BuildResult, CommandSpec,
};
use crate::package::App;
use crate::util::errors::LingoError;
use crate::util::execute_command_to_build_result;
use crate::App;

pub struct CmakeC;

Expand Down
2 changes: 1 addition & 1 deletion src/backends/cmake_cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::io::Write;

use std::process::Command;

use crate::package::App;
use crate::util::execute_command_to_build_result;
use crate::App;

use crate::backends::{
BatchBackend, BatchBuildResults, BuildCommandOptions, BuildProfile, BuildResult, CommandSpec,
Expand Down
2 changes: 1 addition & 1 deletion src/backends/lfc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl BatchBackend for LFC {

/// Formats LFC arguments to JSON.
#[derive(Serialize, Clone)]
struct LfcJsonArgs<'a> {
pub struct LfcJsonArgs<'a> {
/// Path to the LF source file containing the main reactor.
pub src: &'a Path,
/// Path to the directory into which build artifacts like
Expand Down
15 changes: 11 additions & 4 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::package::{
OUTPUT_DIRECTORY,
};
use crate::util::errors::{AnyError, BuildResult, LingoError};
use crate::{GitCloneAndCheckoutCap, WhichCapability};

pub mod cmake_c;
pub mod cmake_cpp;
Expand All @@ -19,7 +20,12 @@ pub mod npm;
pub mod pnpm;

#[allow(clippy::single_match)] // there more options will be added to this match block
pub fn execute_command<'a>(command: &CommandSpec, config: &'a mut Config) -> BatchBuildResults<'a> {
pub fn execute_command<'a>(
command: &CommandSpec,
config: &'a mut Config,
which: WhichCapability,
clone: GitCloneAndCheckoutCap,
) -> BatchBuildResults<'a> {
let mut result = BatchBuildResults::new();
let dependencies = Vec::from_iter(config.dependencies.clone());

Expand All @@ -28,6 +34,7 @@ pub fn execute_command<'a>(command: &CommandSpec, config: &'a mut Config) -> Bat
let manager = match DependencyManager::from_dependencies(
dependencies.clone(),
&PathBuf::from(OUTPUT_DIRECTORY),
&clone,
) {
Ok(value) => value,
Err(e) => {
Expand All @@ -54,7 +61,7 @@ pub fn execute_command<'a>(command: &CommandSpec, config: &'a mut Config) -> Bat
let mut by_build_system = HashMap::<(BuildSystem, TargetLanguage), Vec<&App>>::new();
for app in &config.apps {
by_build_system
.entry((app.build_system(), app.target))
.entry((app.build_system(&which), app.target))
.or_default()
.push(app);
}
Expand Down Expand Up @@ -168,10 +175,10 @@ impl<'a> BatchBuildResults<'a> {
for (app, b) in &self.results {
match b {
Ok(()) => {
println!("- {}: Success", &app.name);
log::info!("- {}: Success", &app.name);
}
Err(e) => {
println!("- {}: Error: {}", &app.name, e);
log::error!("- {}: Error: {}", &app.name, e);
}
}
}
Expand Down
63 changes: 63 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::package::tree::GitLock;
use std::io;

pub mod args;
pub mod backends;
pub mod package;
pub mod util;

#[derive(Debug)]
pub enum WhichError {
/// An executable binary with that name was not found
CannotFindBinaryPath,
/// There was nowhere to search and the provided name wasn't an absolute path
CannotGetCurrentDirAndPathListEmpty,
/// Failed to canonicalize the path found
CannotCanonicalize,
}
#[derive(Debug)]
pub struct GitCloneError(pub String); // TODO: create a more domain-specific error time like the actual git2::Error

impl std::fmt::Display for WhichError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WhichError::CannotFindBinaryPath => write!(f, "cannot find binary"),
WhichError::CannotGetCurrentDirAndPathListEmpty => {
write!(f, "cannot get current dir and path list empty")
}
WhichError::CannotCanonicalize => write!(f, "cannot canonicalize"),
}
}
}

impl std::fmt::Display for GitCloneError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

impl std::error::Error for WhichError {}

impl std::error::Error for GitCloneError {}

pub struct GitUrl<'a>(&'a str);

impl<'a> From<&'a str> for GitUrl<'a> {
fn from(value: &'a str) -> Self {
GitUrl(value)
}
}

impl<'a> From<GitUrl<'a>> for &'a str {
fn from(value: GitUrl<'a>) -> Self {
value.0
}
}

pub type WhichCapability<'a> = Box<dyn Fn(&str) -> Result<std::path::PathBuf, WhichError> + 'a>;
pub type GitCloneCapability<'a> =
Box<dyn Fn(GitUrl, &std::path::Path) -> Result<(), GitCloneError> + 'a>;
pub type FsReadCapability<'a> = Box<dyn Fn(&std::path::Path) -> io::Result<String> + 'a>;
pub type GitCloneAndCheckoutCap<'a> = Box<
dyn Fn(GitUrl, &std::path::Path, Option<GitLock>) -> Result<Option<String>, GitCloneError> + 'a,
>;
123 changes: 99 additions & 24 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,92 @@
use liblingo::args::TargetLanguage;
use std::io::ErrorKind;
use std::path::Path;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::{env, io};

use clap::Parser;
use git2::Repository;

use liblingo::args::InitArgs;
use liblingo::args::{BuildArgs, Command as ConsoleCommand, CommandLineArgs};
use liblingo::backends::{BatchBuildResults, BuildCommandOptions, CommandSpec};
use liblingo::package::tree::GitLock;
use liblingo::package::{Config, ConfigFile};
use liblingo::util::errors::{BuildResult, LingoError};
use liblingo::{GitCloneCapability, GitCloneError, GitUrl, WhichCapability, WhichError};

fn do_repo_clone(url: GitUrl, into: &std::path::Path) -> Result<(), GitCloneError> {
Repository::clone(url.into(), into).map_or_else(
|err: git2::Error| Err(GitCloneError(format!("{}", err))),
|_| Ok(()),
)
}

use crate::args::{InitArgs, TargetLanguage};
use args::{BuildArgs, Command as ConsoleCommand, CommandLineArgs};
use package::App;
fn do_which(cmd: &str) -> Result<PathBuf, WhichError> {
which::which(cmd).map_err(|err| match err {
which::Error::CannotFindBinaryPath => WhichError::CannotFindBinaryPath,
which::Error::CannotGetCurrentDirAndPathListEmpty => {
WhichError::CannotGetCurrentDirAndPathListEmpty
}
which::Error::CannotCanonicalize => WhichError::CannotCanonicalize,
})
}

use crate::backends::{BatchBuildResults, BuildCommandOptions, CommandSpec};
use crate::package::{Config, ConfigFile};
use crate::util::errors::{BuildResult, LingoError};
fn do_clone_and_checkout(
git_url: GitUrl,
outpath: &Path,
git_tag: Option<GitLock>,
) -> Result<Option<String>, GitCloneError> {
let repo = Repository::clone(<&str>::from(git_url), outpath)
.map_err(|_| GitCloneError("clone failed".to_string()))?;
let mut git_rev = None;

if let Some(git_lock) = git_tag {
let name = match git_lock {
GitLock::Tag(tag) => tag,
GitLock::Branch(branch) => branch,
GitLock::Rev(rev) => rev,
};

// TODO: this produces hard to debug output
let (object, reference) = repo
.revparse_ext(&name)
.map_err(|_| GitCloneError("cannot parse rev".to_string()))?;
repo.checkout_tree(&object, None)
.map_err(|_| GitCloneError("cannot checkout rev".to_string()))?;

match reference {
// gref is an actual reference like branches or tags
Some(gref) => {
git_rev = gref.target().map(|v| v.to_string());
repo.set_head(gref.name().unwrap())
}
// this is a commit, not a reference
None => repo.set_head_detached(object.id()),
}
.map_err(|_| GitCloneError("cannot checkout rev".to_string()))?;
}

pub mod args;
pub mod backends;
pub mod package;
pub(crate) mod util;
Ok(git_rev)
}

fn do_read_to_string(p: &Path) -> io::Result<String> {
std::fs::read_to_string(p)
}

fn main() {
print_logger::new().init().unwrap();
// parses command line arguments
let args = CommandLineArgs::parse();

// Finds Lingo.toml recursively inside the parent directories.
// If it exists the returned path is absolute.
let lingo_path = util::find_toml(&env::current_dir().unwrap());
let lingo_path = liblingo::util::find_toml(&env::current_dir().unwrap());

// tries to read Lingo.toml
let mut wrapped_config = lingo_path.as_ref().and_then(|path| {
ConfigFile::from(path)
.map_err(|err| println!("Error while reading Lingo.toml: {}", err))
ConfigFile::from(path, Box::new(do_read_to_string))
.map_err(|err| log::error!("Error while reading Lingo.toml: {}", err))
.ok()
.map(|cf| cf.to_config(path.parent().unwrap()))
});
Expand All @@ -39,7 +96,12 @@ fn main() {
print_res(result)
}

let result = execute_command(&mut wrapped_config, args.command);
let result = execute_command(
&mut wrapped_config,
args.command,
Box::new(do_which),
Box::new(do_repo_clone),
);

match result {
CommandResult::Batch(res) => res.print_results(),
Expand All @@ -51,7 +113,7 @@ fn print_res(result: BuildResult) {
match result {
Ok(_) => {}
Err(errs) => {
println!("{}", errs);
log::error!("{}", errs);
}
}
}
Expand Down Expand Up @@ -79,9 +141,16 @@ fn validate(config: &mut Option<Config>, command: &ConsoleCommand) -> BuildResul
}
}

fn execute_command(config: &mut Option<Config>, command: ConsoleCommand) -> CommandResult {
fn execute_command<'a>(
config: &'a mut Option<Config>,
command: ConsoleCommand,
_which_capability: WhichCapability,
git_clone_capability: GitCloneCapability,
) -> CommandResult<'a> {
match (config, command) {
(_, ConsoleCommand::Init(init_config)) => CommandResult::Single(do_init(init_config)),
(_, ConsoleCommand::Init(init_config)) => {
CommandResult::Single(do_init(init_config, &git_clone_capability))
}
(None, _) => CommandResult::Single(Err(Box::new(io::Error::new(
ErrorKind::NotFound,
"Error: Missing Lingo.toml file",
Expand All @@ -93,7 +162,7 @@ fn execute_command(config: &mut Option<Config>, command: ConsoleCommand) -> Comm
let mut res = build(&build_command_args, config);
res.map(|app| {
let mut command = Command::new(app.executable_path());
util::run_and_capture(&mut command)?;
liblingo::util::run_and_capture(&mut command)?;
Ok(())
});
CommandResult::Batch(res)
Expand All @@ -105,12 +174,13 @@ fn execute_command(config: &mut Option<Config>, command: ConsoleCommand) -> Comm
}
}

fn do_init(init_config: InitArgs) -> BuildResult {
fn do_init(init_config: InitArgs, git_clone_capability: &GitCloneCapability) -> BuildResult {
let initial_config = ConfigFile::new_for_init_task(&init_config)?;
initial_config.write(Path::new("./Lingo.toml"))?;
initial_config.setup_example(
init_config.platform,
init_config.language.unwrap_or(TargetLanguage::Cpp),
git_clone_capability,
)
}

Expand All @@ -119,7 +189,8 @@ fn build<'a>(args: &BuildArgs, config: &'a mut Config) -> BatchBuildResults<'a>
CommandSpec::Build(BuildCommandOptions {
profile: args.build_profile(),
compile_target_code: !args.no_compile,
lfc_exec_path: util::find_lfc_exec(args).expect("TODO replace me"),
lfc_exec_path: liblingo::util::find_lfc_exec(args, Box::new(do_which))
.expect("TODO replace me"),
max_threads: args.threads,
keep_going: args.keep_going,
}),
Expand All @@ -129,9 +200,13 @@ fn build<'a>(args: &BuildArgs, config: &'a mut Config) -> BatchBuildResults<'a>
}

fn run_command(task: CommandSpec, config: &mut Config, _fail_at_end: bool) -> BatchBuildResults {
//let apps = config.apps.iter().collect::<Vec<_>>();
//let dependencies = Vec::from_iter(config.dependencies.into_iter());
backends::execute_command(&task, config)
let _apps = config.apps.iter().collect::<Vec<_>>();
liblingo::backends::execute_command(
&task,
config,
Box::new(do_which),
Box::new(do_clone_and_checkout),
)
}

enum CommandResult<'a> {
Expand Down
Loading

0 comments on commit a4a0f0f

Please sign in to comment.