Skip to content

Commit

Permalink
feat: support binary scripts in PATH
Browse files Browse the repository at this point in the history
  • Loading branch information
daimond113 committed Jul 25, 2024
1 parent 986196a commit 0fcdded
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 78 deletions.
87 changes: 85 additions & 2 deletions src/cli/install.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
use crate::cli::{reqwest_client, IsUpToDate};
use crate::cli::{home_dir, reqwest_client, IsUpToDate};
use anyhow::Context;
use clap::Args;
use indicatif::MultiProgress;
use pesde::{lockfile::Lockfile, manifest::target::TargetKind, Project};
use std::{collections::HashSet, sync::Arc, time::Duration};
use std::{
collections::{BTreeSet, HashSet},
sync::Arc,
time::Duration,
};

#[derive(Debug, Args)]
pub struct InstallCommand {
Expand All @@ -12,6 +16,44 @@ pub struct InstallCommand {
threads: u64,
}

fn bin_link_file(alias: &str) -> String {
let mut all_combinations = BTreeSet::new();

for a in TargetKind::VARIANTS {
for b in TargetKind::VARIANTS {
all_combinations.insert((a, b));
}
}

let all_folders = all_combinations
.into_iter()
.map(|(a, b)| format!("{:?}", a.packages_folder(b)))
.collect::<BTreeSet<_>>()
.into_iter()
.collect::<Vec<_>>()
.join(", ");

#[cfg(windows)]
let prefix = String::new();
#[cfg(not(windows))]
let prefix = "#!/usr/bin/env -S lune run\n";

format!(
r#"{prefix}local process = require("@lune/process")
local fs = require("@lune/fs")
for _, packages_folder in {{ {all_folders} }} do
local path = `{{process.cwd}}/{{packages_folder}}/{alias}.bin.luau`
if fs.isFile(path) then
require(path)
break
end
end
"#,
)
}

impl InstallCommand {
pub fn run(self, project: Project, multi: MultiProgress) -> anyhow::Result<()> {
let mut refreshed_sources = HashSet::new();
Expand Down Expand Up @@ -120,6 +162,47 @@ impl InstallCommand {
.apply_patches(&downloaded_graph)
.context("failed to apply patches")?;

let bin_folder = home_dir()?.join("bin");

for versions in downloaded_graph.values() {
for node in versions.values() {
if node.target.bin_path().is_none() {
continue;
}

let Some((alias, _)) = &node.node.direct else {
continue;
};

let bin_file = bin_folder.join(format!("{alias}.luau"));
std::fs::write(&bin_file, bin_link_file(alias))
.context("failed to write bin link file")?;

// TODO: test if this actually works
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;

let mut perms = std::fs::metadata(&bin_file)
.context("failed to get bin link file metadata")?
.permissions();
perms.set_mode(perms.mode() | 0o111);
std::fs::set_permissions(&bin_file, perms)
.context("failed to set bin link file permissions")?;
}

#[cfg(windows)]
{
let bin_file = bin_file.with_extension(std::env::consts::EXE_EXTENSION);
std::fs::copy(
std::env::current_exe().context("failed to get current executable path")?,
&bin_file,
)
.context("failed to copy bin link file")?;
}
}
}

project
.write_lockfile(Lockfile {
name: manifest.name,
Expand Down
12 changes: 6 additions & 6 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,15 +149,15 @@ pub fn reqwest_client(data_dir: &Path) -> anyhow::Result<reqwest::blocking::Clie
.build()?)
}

pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> {
let home_dir = directories::UserDirs::new()
pub fn home_dir() -> anyhow::Result<std::path::PathBuf> {
Ok(directories::UserDirs::new()
.context("failed to get home directory")?
.home_dir()
.to_owned();
.join(concat!(".", env!("CARGO_PKG_NAME"))))
}

let scripts_dir = home_dir
.join(concat!(".", env!("CARGO_PKG_NAME")))
.join("scripts");
pub fn update_scripts_folder(project: &Project) -> anyhow::Result<()> {
let scripts_dir = home_dir()?.join("scripts");

if scripts_dir.exists() {
let repo = gix::open(&scripts_dir).context("failed to open scripts repository")?;
Expand Down
24 changes: 23 additions & 1 deletion src/cli/publish.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use anyhow::Context;
use clap::Args;
use colored::Colorize;
use pesde::{manifest::target::Target, Project, MANIFEST_FILE_NAME, MAX_ARCHIVE_SIZE};
use pesde::{
manifest::target::Target, scripts::ScriptName, Project, MANIFEST_FILE_NAME, MAX_ARCHIVE_SIZE,
};
use std::path::Component;

#[derive(Debug, Args)]
Expand Down Expand Up @@ -66,6 +68,26 @@ impl PublishCommand {
);
}

if !manifest.includes.iter().any(|f| {
matches!(
f.to_lowercase().as_str(),
"readme" | "readme.md" | "readme.txt"
)
}) {
println!(
"{}: no README file in includes, consider adding one",
"warn".yellow().bold()
);
}

if manifest.includes.remove("default.project.json") {
println!(
"{}: default.project.json was in includes, this should be generated by the {} script upon dependants installation",
"warn".yellow().bold(),
ScriptName::RobloxSyncConfigGenerator
);
}

for (name, path) in [("lib path", lib_path), ("bin path", bin_path)] {
let Some(export_path) = path else { continue };

Expand Down
8 changes: 7 additions & 1 deletion src/cli/self_install.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use crate::cli::update_scripts_folder;
use crate::cli::{home_dir, update_scripts_folder};
use anyhow::Context;
use clap::Args;
use pesde::Project;
use std::fs::create_dir_all;

#[derive(Debug, Args)]
pub struct SelfInstallCommand {}
Expand All @@ -9,6 +11,10 @@ impl SelfInstallCommand {
pub fn run(self, project: Project) -> anyhow::Result<()> {
update_scripts_folder(&project)?;

create_dir_all(home_dir()?.join("bin")).context("failed to create bin folder")?;

// TODO: add the bin folder to the PATH

Ok(())
}
}
73 changes: 40 additions & 33 deletions src/linking/generator.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::path::{Component, Path};

use crate::manifest::target::TargetKind;
use full_moon::{ast::luau::ExportedTypeDeclaration, visitors::Visitor};

use crate::manifest::target::Target;
use relative_path::RelativePathBuf;

struct TypeVisitor {
types: Vec<String>,
Expand Down Expand Up @@ -49,7 +49,7 @@ pub fn get_file_types(file: &str) -> Result<Vec<String>, Vec<full_moon::Error>>
Ok(visitor.types)
}

pub fn generate_linking_module<I: IntoIterator<Item = S>, S: AsRef<str>>(
pub fn generate_lib_linking_module<I: IntoIterator<Item = S>, S: AsRef<str>>(
path: &str,
types: I,
) -> String {
Expand All @@ -64,27 +64,40 @@ pub fn generate_linking_module<I: IntoIterator<Item = S>, S: AsRef<str>>(
output
}

pub fn get_require_path(
target: &Target,
fn luau_style_path(path: &Path) -> String {
path.components()
.enumerate()
.filter_map(|(i, ct)| match ct {
Component::ParentDir => Some(if i == 0 {
".".to_string()
} else {
"..".to_string()
}),
Component::Normal(part) => Some(format!("{}", part.to_string_lossy())),
_ => None,
})
.collect::<Vec<_>>()
.join("/")
}

pub fn get_lib_require_path(
target: &TargetKind,
base_dir: &Path,
lib_file: &RelativePathBuf,
destination_dir: &Path,
use_new_structure: bool,
) -> Result<String, errors::GetRequirePathError> {
let Some(lib_file) = target.lib_path() else {
return Err(errors::GetRequirePathError::NoLibPath);
};

) -> String {
let path = pathdiff::diff_paths(destination_dir, base_dir).unwrap();
let path = if !use_new_structure {
log::debug!("using old structure for require path");
let path = if use_new_structure {
log::debug!("using new structure for require path");
lib_file.to_path(path)
} else {
log::debug!("using new structure for require path");
log::debug!("using old structure for require path");
path
};

#[cfg(feature = "roblox")]
if matches!(target, Target::Roblox { .. }) {
if matches!(target, TargetKind::Roblox) {
let path = path
.components()
.filter_map(|component| match component {
Expand All @@ -102,29 +115,23 @@ pub fn get_require_path(
.collect::<Vec<_>>()
.join("");

return Ok(format!("script{path}"));
return format!("script{path}");
};

let path = path
.components()
.filter_map(|ct| match ct {
Component::ParentDir => Some("..".to_string()),
Component::Normal(part) => Some(format!("{}", part.to_string_lossy())),
_ => None,
})
.collect::<Vec<_>>()
.join("/");
format!("{:?}", luau_style_path(&path))
}

Ok(format!("./{path}"))
pub fn generate_bin_linking_module(path: &str) -> String {
format!("return require({path})")
}

pub mod errors {
use thiserror::Error;
pub fn get_bin_require_path(
base_dir: &Path,
bin_file: &RelativePathBuf,
destination_dir: &Path,
) -> String {
let path = pathdiff::diff_paths(destination_dir, base_dir).unwrap();
let path = bin_file.to_path(path);

#[derive(Debug, Error)]
#[non_exhaustive]
pub enum GetRequirePathError {
#[error("get require path called for target without a lib path")]
NoLibPath,
}
format!("{:?}", luau_style_path(&path))
}
Loading

0 comments on commit 0fcdded

Please sign in to comment.