mirror of
https://github.com/pesde-pkg/pesde.git
synced 2024-12-12 11:00:36 +00:00
feat: support binary scripts in PATH
This commit is contained in:
parent
986196ac67
commit
0fcddedad6
7 changed files with 245 additions and 80 deletions
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
|
|
@ -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")?;
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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 };
|
||||
|
||||
|
|
|
@ -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 {}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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("/");
|
||||
|
||||
Ok(format!("./{path}"))
|
||||
format!("{:?}", luau_style_path(&path))
|
||||
}
|
||||
|
||||
pub mod errors {
|
||||
use thiserror::Error;
|
||||
pub fn generate_bin_linking_module(path: &str) -> String {
|
||||
format!("return require({path})")
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[non_exhaustive]
|
||||
pub enum GetRequirePathError {
|
||||
#[error("get require path called for target without a lib path")]
|
||||
NoLibPath,
|
||||
}
|
||||
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);
|
||||
|
||||
format!("{:?}", luau_style_path(&path))
|
||||
}
|
||||
|
|
|
@ -11,6 +11,14 @@ use std::{collections::BTreeMap, fs::create_dir_all};
|
|||
|
||||
pub mod generator;
|
||||
|
||||
fn create_and_canonicalize<P: AsRef<std::path::Path>>(
|
||||
path: P,
|
||||
) -> std::io::Result<std::path::PathBuf> {
|
||||
let p = path.as_ref();
|
||||
create_dir_all(p)?;
|
||||
p.canonicalize()
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub fn link_dependencies(&self, graph: &DownloadedGraph) -> Result<(), errors::LinkingError> {
|
||||
let manifest = self.deser_manifest()?;
|
||||
|
@ -89,12 +97,12 @@ impl Project {
|
|||
|
||||
for (name, versions) in graph {
|
||||
for (version_id, node) in versions {
|
||||
let base_folder = self.path().join(
|
||||
let base_folder = create_and_canonicalize(
|
||||
self.path().join(
|
||||
self.path()
|
||||
.join(node.node.base_folder(manifest.target.kind(), true)),
|
||||
);
|
||||
create_dir_all(&base_folder)?;
|
||||
let base_folder = base_folder.canonicalize()?;
|
||||
),
|
||||
)?;
|
||||
let packages_container_folder = base_folder.join(PACKAGES_CONTAINER_NAME);
|
||||
|
||||
let container_folder = node.node.container_folder(
|
||||
|
@ -108,17 +116,36 @@ impl Project {
|
|||
.and_then(|v| v.get(version_id))
|
||||
.and_then(|types| node.node.direct.as_ref().map(|(alias, _)| (alias, types)))
|
||||
{
|
||||
let module = generator::generate_linking_module(
|
||||
&generator::get_require_path(
|
||||
&node.target,
|
||||
&base_folder,
|
||||
if let Some(lib_file) = node.target.lib_path() {
|
||||
let linker_file = base_folder.join(format!("{alias}.luau"));
|
||||
|
||||
let module = generator::generate_lib_linking_module(
|
||||
&generator::get_lib_require_path(
|
||||
&node.target.kind(),
|
||||
&linker_file,
|
||||
lib_file,
|
||||
&container_folder,
|
||||
node.node.pkg_ref.use_new_structure(),
|
||||
)?,
|
||||
),
|
||||
types,
|
||||
);
|
||||
|
||||
std::fs::write(base_folder.join(format!("{alias}.luau")), module)?;
|
||||
std::fs::write(linker_file, module)?;
|
||||
};
|
||||
|
||||
if let Some(bin_file) = node.target.bin_path() {
|
||||
let linker_file = base_folder.join(format!("{alias}.bin.luau"));
|
||||
|
||||
let module = generator::generate_bin_linking_module(
|
||||
&generator::get_bin_require_path(
|
||||
&linker_file,
|
||||
bin_file,
|
||||
&container_folder,
|
||||
),
|
||||
);
|
||||
|
||||
std::fs::write(linker_file, module)?;
|
||||
}
|
||||
}
|
||||
|
||||
for (dependency_name, (dependency_version_id, dependency_alias)) in
|
||||
|
@ -134,26 +161,28 @@ impl Project {
|
|||
));
|
||||
};
|
||||
|
||||
let dependency_container_folder = dependency_node.node.container_folder(
|
||||
let Some(lib_file) = dependency_node.target.lib_path() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let linker_file = create_and_canonicalize(
|
||||
container_folder
|
||||
.join(dependency_node.node.base_folder(node.target.kind(), false)),
|
||||
)?
|
||||
.join(format!("{dependency_alias}.luau"));
|
||||
|
||||
let module = generator::generate_lib_linking_module(
|
||||
&generator::get_lib_require_path(
|
||||
&dependency_node.target.kind(),
|
||||
&linker_file,
|
||||
lib_file,
|
||||
&dependency_node.node.container_folder(
|
||||
&packages_container_folder,
|
||||
dependency_name,
|
||||
dependency_version_id.version(),
|
||||
);
|
||||
|
||||
let linker_folder = container_folder
|
||||
.join(dependency_node.node.base_folder(node.target.kind(), false));
|
||||
create_dir_all(&linker_folder)?;
|
||||
let linker_folder = linker_folder.canonicalize()?;
|
||||
|
||||
let linker_file = linker_folder.join(format!("{dependency_alias}.luau"));
|
||||
|
||||
let module = generator::generate_linking_module(
|
||||
&generator::get_require_path(
|
||||
&dependency_node.target,
|
||||
&linker_file,
|
||||
&dependency_container_folder,
|
||||
),
|
||||
node.node.pkg_ref.use_new_structure(),
|
||||
)?,
|
||||
),
|
||||
package_types
|
||||
.get(dependency_name)
|
||||
.and_then(|v| v.get(dependency_version_id))
|
||||
|
@ -190,9 +219,6 @@ pub mod errors {
|
|||
#[error("error parsing Luau script at {0}")]
|
||||
FullMoon(String, Vec<full_moon::Error>),
|
||||
|
||||
#[error("error generating require path")]
|
||||
GetRequirePath(#[from] crate::linking::generator::errors::GetRequirePathError),
|
||||
|
||||
#[cfg(feature = "roblox")]
|
||||
#[error("error generating roblox sync config for {0}")]
|
||||
GenerateRobloxSyncConfig(String, #[source] std::io::Error),
|
||||
|
|
21
src/main.rs
21
src/main.rs
|
@ -22,6 +22,27 @@ struct Cli {
|
|||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let exe = std::env::current_exe().expect("failed to get current executable path");
|
||||
let exe_name = exe.with_extension("");
|
||||
let exe_name = exe_name.file_name().unwrap();
|
||||
|
||||
if exe_name != env!("CARGO_BIN_NAME") {
|
||||
let args = std::env::args_os();
|
||||
|
||||
let status = std::process::Command::new("lune")
|
||||
.arg("run")
|
||||
.arg(exe.with_extension("luau"))
|
||||
.args(args.skip(1))
|
||||
.current_dir(std::env::current_dir().unwrap())
|
||||
.status()
|
||||
.expect("failed to run lune");
|
||||
|
||||
std::process::exit(status.code().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
let multi = {
|
||||
let logger = pretty_env_logger::formatted_builder()
|
||||
.parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info"))
|
||||
|
|
Loading…
Reference in a new issue