mirror of
https://github.com/pesde-pkg/pesde.git
synced 2025-04-08 12:50:55 +01:00
refactor: handle bin linkers in rust
Previously, binary linkers were handled by a Luau script. This was not cross-runtime portable, and forced us to do many "hacks" in order to be able to implement them. To solve these issues, they are now handled with Rust, which allows us to use our existing infrastructure.
This commit is contained in:
parent
41337ac96a
commit
9a75ebf637
4 changed files with 122 additions and 184 deletions
|
@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixed
|
||||
- Fix path dependencies using project's workspace dependencies by @daimond113
|
||||
|
||||
### Changed
|
||||
- Binary linkers are now done in Rust to simplify their implementation and cross-runtime portability by @daimond113
|
||||
|
||||
## [0.6.0] - 2025-02-22
|
||||
### Added
|
||||
- Improve installation experience by @lukadev-0
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
local process = require("@lune/process")
|
||||
local fs = require("@lune/fs")
|
||||
local stdio = require("@lune/stdio")
|
||||
local serde = require("@lune/serde")
|
||||
|
||||
local project_root = nil
|
||||
local path_components = string.split(string.gsub(process.cwd, "\\", "/"), "/")
|
||||
if path_components[#path_components] == "" then
|
||||
table.remove(path_components)
|
||||
end
|
||||
|
||||
local function in_lockfile(lockfile)
|
||||
if not lockfile.graph then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, versions in lockfile.graph do
|
||||
for _, node in versions do
|
||||
if node.direct and node.direct[1] == "{alias}" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
for i = #path_components, 1, -1 do
|
||||
local path = table.concat(path_components, "/", 1, i)
|
||||
if not fs.isFile(path .. "/{MANIFEST_FILE_NAME}") then
|
||||
continue
|
||||
end
|
||||
|
||||
if project_root == nil then
|
||||
project_root = path
|
||||
end
|
||||
|
||||
if project_root and fs.isFile(path .. "/{LOCKFILE_FILE_NAME}") then
|
||||
local lockfile = serde.decode("toml", fs.readFile(path .. "/{LOCKFILE_FILE_NAME}"))
|
||||
if not lockfile.workspace then
|
||||
continue
|
||||
end
|
||||
|
||||
local search_for = string.gsub(project_root, path, "")
|
||||
if string.sub(search_for, 1, 1) == "/" then
|
||||
search_for = string.sub(search_for, 2)
|
||||
end
|
||||
|
||||
if search_for == "" then
|
||||
if in_lockfile(lockfile) then
|
||||
break
|
||||
end
|
||||
|
||||
continue
|
||||
end
|
||||
|
||||
for _, targets in lockfile.workspace do
|
||||
for _, member_path in targets do
|
||||
local path_normalized = string.gsub(member_path, "\\", "/")
|
||||
if path_normalized == search_for and in_lockfile(lockfile) then
|
||||
project_root = path
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if project_root ~= nil then
|
||||
for _, packages_folder in {{ {all_folders} }} do
|
||||
local path = `{{project_root}}/{{packages_folder}}/{alias}.bin.luau`
|
||||
|
||||
if fs.isFile(path) then
|
||||
require(path)
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
stdio.ewrite(stdio.color("red") .. "binary `{alias}` not found. are you in the right directory?" .. stdio.color("reset") .. "\n")
|
||||
process.exit(1)
|
|
@ -14,7 +14,7 @@ use pesde::{
|
|||
engine::EngineKind,
|
||||
graph::{DependencyGraph, DependencyGraphWithTarget},
|
||||
lockfile::Lockfile,
|
||||
manifest::{target::TargetKind, Alias, DependencyType, Manifest},
|
||||
manifest::{DependencyType, Manifest},
|
||||
names::PackageNames,
|
||||
source::{
|
||||
pesde::PesdePackageSource,
|
||||
|
@ -22,7 +22,7 @@ use pesde::{
|
|||
traits::{PackageRef as _, RefreshOptions},
|
||||
PackageSources,
|
||||
},
|
||||
version_matches, Project, RefreshedSources, LOCKFILE_FILE_NAME, MANIFEST_FILE_NAME,
|
||||
version_matches, Project, RefreshedSources, MANIFEST_FILE_NAME,
|
||||
};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet, HashMap},
|
||||
|
@ -32,32 +32,6 @@ use std::{
|
|||
};
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
fn bin_link_file(alias: &Alias) -> 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(", ");
|
||||
|
||||
format!(
|
||||
include_str!("bin_link.luau"),
|
||||
alias = alias,
|
||||
all_folders = all_folders,
|
||||
MANIFEST_FILE_NAME = MANIFEST_FILE_NAME,
|
||||
LOCKFILE_FILE_NAME = LOCKFILE_FILE_NAME
|
||||
)
|
||||
}
|
||||
|
||||
pub struct InstallHooks {
|
||||
pub bin_folder: std::path::PathBuf,
|
||||
}
|
||||
|
@ -85,39 +59,30 @@ impl DownloadAndLinkHooks for InstallHooks {
|
|||
let bin_exec_file = bin_folder
|
||||
.join(alias.as_str())
|
||||
.with_extension(std::env::consts::EXE_EXTENSION);
|
||||
let curr_exe =
|
||||
std::env::current_exe().context("failed to get current executable path")?;
|
||||
|
||||
let impl_folder = bin_folder.join(".impl");
|
||||
fs::create_dir_all(&impl_folder)
|
||||
// TODO: remove this in a major release
|
||||
#[cfg(unix)]
|
||||
if fs::metadata(&bin_exec_file)
|
||||
.await
|
||||
.context("failed to create bin link folder")?;
|
||||
|
||||
let bin_file = impl_folder.join(alias.as_str()).with_extension("luau");
|
||||
fs::write(&bin_file, bin_link_file(&alias))
|
||||
.await
|
||||
.context("failed to write bin link file")?;
|
||||
|
||||
#[cfg(windows)]
|
||||
match fs::symlink_file(
|
||||
std::env::current_exe().context("failed to get current executable path")?,
|
||||
&bin_exec_file,
|
||||
)
|
||||
.await
|
||||
.is_ok_and(|m| !m.is_symlink())
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
|
||||
e => e.context("failed to copy bin link file")?,
|
||||
fs::remove_file(&bin_exec_file)
|
||||
.await
|
||||
.context("failed to remove outdated bin linker")?;
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fs::write(
|
||||
&bin_exec_file,
|
||||
format!(
|
||||
r#"#!/bin/sh
|
||||
exec lune run "$(dirname "$0")/.impl/{alias}.luau" -- "$@""#
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("failed to link bin link file")?;
|
||||
#[cfg(windows)]
|
||||
let res = fs::symlink_file(curr_exe, &bin_exec_file).await;
|
||||
#[cfg(unix)]
|
||||
let res = fs::symlink(curr_exe, &bin_exec_file).await;
|
||||
|
||||
match res {
|
||||
Ok(_) => {}
|
||||
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {}
|
||||
e => e.context("failed to symlink bin link file")?,
|
||||
}
|
||||
|
||||
make_executable(&bin_exec_file)
|
||||
.await
|
||||
|
|
145
src/main.rs
145
src/main.rs
|
@ -1,19 +1,24 @@
|
|||
#[cfg(feature = "version-management")]
|
||||
use crate::cli::version::{check_for_updates, current_version, get_or_download_engine};
|
||||
use crate::cli::{auth::get_tokens, display_err, home_dir, HOME_DIR};
|
||||
use crate::cli::{auth::get_tokens, display_err, home_dir, style::ERROR_STYLE, HOME_DIR};
|
||||
use anyhow::Context as _;
|
||||
use clap::{builder::styling::AnsiColor, Parser};
|
||||
use fs_err::tokio as fs;
|
||||
use indicatif::MultiProgress;
|
||||
use pesde::{engine::EngineKind, find_roots, version_matches, AuthConfig, Project};
|
||||
use pesde::{
|
||||
engine::EngineKind, find_roots, manifest::target::TargetKind, version_matches, AuthConfig,
|
||||
Project,
|
||||
};
|
||||
use semver::VersionReq;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr as _,
|
||||
sync::Mutex,
|
||||
};
|
||||
use tempfile::NamedTempFile;
|
||||
use tokio::task::JoinSet;
|
||||
use tracing::instrument;
|
||||
use tracing_subscriber::{
|
||||
filter::LevelFilter, fmt::MakeWriter, layer::SubscriberExt as _, util::SubscriberInitExt as _,
|
||||
|
@ -150,51 +155,6 @@ async fn run() -> anyhow::Result<()> {
|
|||
.expect("exe name is not valid utf-8");
|
||||
let exe_name_engine = EngineKind::from_str(exe_name);
|
||||
|
||||
#[cfg(windows)]
|
||||
'scripts: {
|
||||
// if we're an engine, we don't want to run any scripts
|
||||
if exe_name_engine.is_ok() {
|
||||
break 'scripts;
|
||||
}
|
||||
|
||||
if let Some(bin_folder) = current_exe.parent() {
|
||||
// we're not in {path}/bin/{exe}
|
||||
if bin_folder.file_name().is_some_and(|parent| parent != "bin") {
|
||||
break 'scripts;
|
||||
}
|
||||
|
||||
// we're not in {path}/.pesde/bin/{exe}
|
||||
if bin_folder
|
||||
.parent()
|
||||
.and_then(|home_folder| home_folder.file_name())
|
||||
.is_some_and(|home_folder| home_folder != HOME_DIR)
|
||||
{
|
||||
break 'scripts;
|
||||
}
|
||||
}
|
||||
|
||||
// the bin script will search for the project root itself, so we do that to ensure
|
||||
// consistency across platforms, since the script is executed using a shebang
|
||||
// on unix systems
|
||||
let status = std::process::Command::new("lune")
|
||||
.arg("run")
|
||||
.arg(
|
||||
current_exe
|
||||
.parent()
|
||||
.unwrap_or(¤t_exe)
|
||||
.join(".impl")
|
||||
.join(current_exe.file_name().unwrap())
|
||||
.with_extension("luau"),
|
||||
)
|
||||
.arg("--")
|
||||
.args(std::env::args_os().skip(1))
|
||||
.current_dir(cwd)
|
||||
.status()
|
||||
.expect("failed to run lune");
|
||||
|
||||
std::process::exit(status.code().unwrap_or(1i32));
|
||||
};
|
||||
|
||||
let tracing_env_filter = EnvFilter::builder()
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.from_env_lossy()
|
||||
|
@ -236,6 +196,97 @@ async fn run() -> anyhow::Result<()> {
|
|||
.map_or_else(|| "none".to_string(), |p| p.display().to_string())
|
||||
);
|
||||
|
||||
'scripts: {
|
||||
// if we're an engine, we don't want to run any scripts
|
||||
if exe_name_engine.is_ok() {
|
||||
break 'scripts;
|
||||
}
|
||||
|
||||
if let Some(bin_folder) = current_exe.parent() {
|
||||
// we're not in {path}/bin/{exe}
|
||||
if bin_folder.file_name().is_some_and(|parent| parent != "bin") {
|
||||
break 'scripts;
|
||||
}
|
||||
|
||||
// we're not in {path}/.pesde/bin/{exe}
|
||||
if bin_folder
|
||||
.parent()
|
||||
.and_then(|home_folder| home_folder.file_name())
|
||||
.is_some_and(|home_folder| home_folder != HOME_DIR)
|
||||
{
|
||||
break 'scripts;
|
||||
}
|
||||
}
|
||||
|
||||
let linker_file_name = format!("{exe_name}.bin.luau");
|
||||
|
||||
let path = 'finder: {
|
||||
let all_folders = TargetKind::VARIANTS
|
||||
.iter()
|
||||
.flat_map(|a| TargetKind::VARIANTS.iter().map(|b| a.packages_folder(*b)))
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let mut tasks = all_folders
|
||||
.into_iter()
|
||||
.map(|folder| {
|
||||
let package_path = project_root_dir.join(&folder).join(&linker_file_name);
|
||||
let workspace_path = project_workspace_dir
|
||||
.as_deref()
|
||||
.map(|path| path.join(&folder).join(&linker_file_name));
|
||||
|
||||
async move {
|
||||
if fs::metadata(&package_path).await.is_ok() {
|
||||
return Some((true, package_path));
|
||||
}
|
||||
|
||||
if let Some(workspace_path) = workspace_path {
|
||||
if fs::metadata(&workspace_path).await.is_ok() {
|
||||
return Some((false, workspace_path));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<JoinSet<_>>();
|
||||
|
||||
let mut workspace_path = None;
|
||||
|
||||
while let Some(res) = tasks.join_next().await {
|
||||
if let Some((primary, path)) = res.unwrap() {
|
||||
if primary {
|
||||
break 'finder path;
|
||||
}
|
||||
|
||||
workspace_path = Some(path);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(path) = workspace_path {
|
||||
break 'finder path;
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
ERROR_STYLE.apply_to(format!(
|
||||
"binary `{exe_name}` not found. are you in the right directory?"
|
||||
))
|
||||
);
|
||||
std::process::exit(1i32);
|
||||
};
|
||||
|
||||
let status = std::process::Command::new("lune")
|
||||
.arg("run")
|
||||
.arg(path)
|
||||
.arg("--")
|
||||
.args(std::env::args_os().skip(1))
|
||||
.current_dir(cwd)
|
||||
.status()
|
||||
.expect("failed to run lune");
|
||||
|
||||
std::process::exit(status.code().unwrap_or(1i32));
|
||||
};
|
||||
|
||||
let home_dir = home_dir()?;
|
||||
let data_dir = home_dir.join("data");
|
||||
fs::create_dir_all(&data_dir)
|
||||
|
|
Loading…
Add table
Reference in a new issue