Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Librarify #50

Merged
merged 16 commits into from
Oct 16, 2024
Merged
20 changes: 15 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "lingua-franca"
name = "lingo"
lhstrh marked this conversation as resolved.
Show resolved Hide resolved
version = "0.2.0"
edition = "2021"

Expand All @@ -8,10 +8,19 @@ homepage = "https://lf-lang.org"
repository = "https://github.com/lf-lang/lingo"
license = "BSD-2-Clause"

[lib]
name = "liblingo"
path = "src/lib.rs"

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

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

[dependencies]

Expand All @@ -20,13 +29,14 @@ os-version = "0.2"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
which = "5.0"
regex = "1.8"
lazy_static = "1.4"
rayon = "1.7"
toml = {version = "0.8"}
toml = { version = "0.8" }
crossbeam = "0.8"
termion = "2.0"
git2 = "0.18"
run_script = "0.10"
tempfile = "3.0"
which = { version = "5.0", optional = true }
git2 = { version = "0.18", optional = true }
log = "0.4.21"
print_logger = "0.2.0"
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;
#[clap(rename_all = "lowercase")]
pub enum TargetLanguage {
C,
CCpp,
tanneberger marked this conversation as resolved.
Show resolved Hide resolved
Cpp,
Rust,
TypeScript,
Expand Down
2 changes: 1 addition & 1 deletion src/backends/cmake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::fs;

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
13 changes: 9 additions & 4 deletions src/backends/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,23 @@ use rayon::prelude::*;
use crate::args::{BuildSystem, Platform};
use crate::package::App;
use crate::util::errors::{AnyError, BuildResult, LingoError};
use crate::WhichCapability;

pub mod cmake;
pub mod lfc;
pub mod npm;
pub mod pnpm;

pub fn execute_command<'a>(command: &CommandSpec, apps: &[&'a App]) -> BatchBuildResults<'a> {
pub fn execute_command<'a>(
command: &CommandSpec,
apps: &[&'a App],
which: WhichCapability,
) -> BatchBuildResults<'a> {
// Group apps by build system
let mut by_build_system = HashMap::<BuildSystem, Vec<&App>>::new();
for &app in apps {
by_build_system
.entry(app.build_system())
.entry(app.build_system(&which))
.or_default()
.push(app);
}
Expand Down Expand Up @@ -121,10 +126,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
59 changes: 59 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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>;
59 changes: 39 additions & 20 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,53 @@
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 crate::args::InitArgs;
use args::{BuildArgs, Command as ConsoleCommand, CommandLineArgs};
use package::App;
use git2::Repository;
use liblingo::args::InitArgs;
use liblingo::args::{BuildArgs, Command as ConsoleCommand, CommandLineArgs};

use crate::backends::{BatchBuildResults, BuildCommandOptions, CommandSpec};
use crate::package::{Config, ConfigFile};
use crate::util::errors::{BuildResult, LingoError};
use liblingo::backends::{BatchBuildResults, BuildCommandOptions, CommandSpec};
use liblingo::package::{Config, ConfigFile};
use liblingo::util::errors::{BuildResult, LingoError};
use liblingo::{GitCloneError, GitUrl, WhichError};

pub mod args;
pub mod backends;
pub mod package;
pub(crate) mod util;
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(()),
)
}

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,
})
}

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))
tanneberger marked this conversation as resolved.
Show resolved Hide resolved
.ok()
.map(|cf| cf.to_config(path.parent().unwrap()))
});
Expand All @@ -51,7 +69,7 @@ fn print_res(result: BuildResult) {
match result {
Ok(_) => {}
Err(errs) => {
println!("{}", errs);
log::error!("{}", errs);
}
}
}
Expand Down Expand Up @@ -87,14 +105,14 @@ fn execute_command(config: Option<&Config>, command: ConsoleCommand) -> CommandR
"Error: Missing Lingo.toml file",
)))),
(Some(config), ConsoleCommand::Build(build_command_args)) => {
println!("Building ...");
log::info!("Building ...");
CommandResult::Batch(build(&build_command_args, config))
}
(Some(config), ConsoleCommand::Run(build_command_args)) => {
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 @@ -109,15 +127,16 @@ fn execute_command(config: Option<&Config>, command: ConsoleCommand) -> CommandR
fn do_init(init_config: InitArgs) -> BuildResult {
let initial_config = ConfigFile::new_for_init_task(init_config)?;
initial_config.write(Path::new("./Lingo.toml"))?;
initial_config.setup_example()
initial_config.setup_example(Box::new(do_repo_clone))
}

fn build<'a>(args: &BuildArgs, config: &'a Config) -> BatchBuildResults<'a> {
run_command(
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 @@ -128,7 +147,7 @@ fn build<'a>(args: &BuildArgs, config: &'a Config) -> BatchBuildResults<'a> {

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

enum CommandResult<'a> {
Expand Down
38 changes: 21 additions & 17 deletions src/package/mod.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
use crate::args::{BuildSystem, InitArgs, Platform, TargetLanguage};
use crate::util::{analyzer, copy_recursively};
use crate::{FsReadCapability, GitCloneCapability, GitUrl, WhichCapability};

use serde_derive::{Deserialize, Serialize};

use std::collections::HashMap;

use std::fs::{read_to_string, remove_dir_all, remove_file, write};
use std::fs::{remove_dir_all, remove_file, write};
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use std::{env, io};

use crate::args::BuildSystem::{CMake, LFC};
use crate::util::errors::{BuildResult, LingoError};
use git2::Repository;
use tempfile::tempdir;
use which::which;

fn is_valid_location_for_project(path: &std::path::Path) -> bool {
!path.join("src").exists() && !path.join(".git").exists() && !path.join("application").exists()
Expand Down Expand Up @@ -91,7 +90,7 @@ pub struct App {
}

impl App {
pub fn build_system(&self) -> BuildSystem {
pub fn build_system(&self, which: &WhichCapability) -> BuildSystem {
match self.target {
TargetLanguage::C => LFC,
TargetLanguage::Cpp => CMake,
Expand Down Expand Up @@ -211,10 +210,15 @@ impl ConfigFile {
write(path, toml_string)
}

pub fn from(path: &Path) -> io::Result<ConfigFile> {
read_to_string(path).and_then(|contents| {
toml::from_str(&contents)
.map_err(|e| io::Error::new(ErrorKind::InvalidData, format!("{}", e)))
pub fn from(path: &Path, fsr: FsReadCapability) -> io::Result<ConfigFile> {
let contents = fsr(path);
contents.and_then(|contents| {
toml::from_str(&contents).map_err(|e| {
io::Error::new(
ErrorKind::InvalidData,
format!("failed to convert string to toml: {}", e),
)
})
})
}

Expand All @@ -233,10 +237,10 @@ impl ConfigFile {
Ok(())
}

fn setup_template_repo(&self, url: &str) -> BuildResult {
fn setup_template_repo(&self, url: &str, clone: GitCloneCapability) -> BuildResult {
let dir = tempdir()?;
let tmp_path = dir.path();
Repository::clone(url, tmp_path)?;
clone(GitUrl::from(url), tmp_path)?;
// Copy the cloned template repo into the project directory
copy_recursively(tmp_path, Path::new("."))?;
// Remove temporary folder
Expand All @@ -245,29 +249,29 @@ impl ConfigFile {
}

// Sets up a LF project with Zephyr as the target platform.
fn setup_zephyr(&self) -> BuildResult {
fn setup_zephyr(&self, clone: GitCloneCapability) -> BuildResult {
let url = "https://github.com/lf-lang/lf-west-template";
self.setup_template_repo(url)?;
self.setup_template_repo(url, clone)?;
remove_file(".gitignore")?;
remove_dir_all(Path::new(".git"))?;
Ok(())
}

// Sets up a LF project with RP2040 MCU as the target platform.
// Initializes a repo using the lf-pico-template
fn setup_rp2040(&self) -> BuildResult {
fn setup_rp2040(&self, clone: GitCloneCapability) -> BuildResult {
let url = "https://github.com/lf-lang/lf-pico-template";
// leave git artifacts
self.setup_template_repo(url)?;
self.setup_template_repo(url, clone)?;
Ok(())
}

pub fn setup_example(&self) -> BuildResult {
pub fn setup_example(&self, clone: GitCloneCapability) -> BuildResult {
if is_valid_location_for_project(Path::new(".")) {
match self.apps[0].platform {
Some(Platform::Native) => self.setup_native(),
Some(Platform::Zephyr) => self.setup_zephyr(),
Some(Platform::RP2040) => self.setup_rp2040(),
Some(Platform::Zephyr) => self.setup_zephyr(clone),
Some(Platform::RP2040) => self.setup_rp2040(clone),
_ => Ok(()),
}
} else {
Expand Down
Loading
Loading