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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave it as lingua-franca because we still want to squat that name on crates.io ;D

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

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))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we now want to switch to a proper logging library, we probably need some non-standard schema.

.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