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
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
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,
tanneberger marked this conversation as resolved.
Show resolved Hide resolved
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))
tanneberger marked this conversation as resolved.
Show resolved Hide resolved
.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
Loading