pesde/src/main.rs

282 lines
8.6 KiB
Rust
Raw Normal View History

2024-10-20 17:13:08 +01:00
#[cfg(feature = "version-management")]
use crate::cli::version::{
check_for_updates, current_version, get_or_download_version, max_installed_version,
};
2024-11-05 19:44:24 +00:00
use crate::cli::{auth::get_tokens, display_err, home_dir, HOME_DIR};
use anyhow::Context;
use clap::Parser;
2024-11-05 19:44:24 +00:00
use fs_err::tokio as fs;
use indicatif::MultiProgress;
use indicatif_log_bridge::LogWrapper;
use pesde::{AuthConfig, Project, MANIFEST_FILE_NAME};
use std::{
collections::HashSet,
path::{Path, PathBuf},
};
use tempfile::NamedTempFile;
2024-07-12 23:09:37 +01:00
mod cli;
2024-07-22 21:00:09 +01:00
pub mod util;
2024-03-04 20:18:49 +00:00
2024-07-12 23:09:37 +01:00
#[derive(Parser, Debug)]
2024-09-28 23:04:35 +01:00
#[clap(
version,
about = "A package manager for the Luau programming language, supporting multiple runtimes including Roblox and Lune"
)]
2024-07-12 23:09:37 +01:00
#[command(disable_version_flag = true)]
struct Cli {
/// Print version
#[arg(short = 'v', short_alias = 'V', long, action = clap::builder::ArgAction::Version)]
version: (),
2024-03-04 20:18:49 +00:00
2024-07-12 23:09:37 +01:00
#[command(subcommand)]
2024-07-26 17:47:53 +01:00
subcommand: cli::commands::Subcommand,
2024-07-12 23:09:37 +01:00
}
2024-11-05 19:44:24 +00:00
async fn get_linkable_dir(path: &Path) -> PathBuf {
let mut curr_path = PathBuf::new();
let file_to_try = NamedTempFile::new_in(path).expect("failed to create temporary file");
let temp_file_name = file_to_try.path().file_name().unwrap();
2024-07-28 17:19:54 +01:00
for component in path.components() {
curr_path.push(component);
2024-07-28 17:19:54 +01:00
let try_path = curr_path.join(temp_file_name);
2024-11-05 19:44:24 +00:00
if fs::hard_link(file_to_try.path(), &try_path).await.is_ok() {
if let Err(err) = fs::remove_file(&try_path).await {
log::warn!(
"failed to remove temporary file at {}: {err}",
try_path.display()
);
}
return curr_path;
2024-07-28 17:19:54 +01:00
}
}
panic!(
"couldn't find a linkable directory for any point in {}",
curr_path.display()
);
2024-07-28 17:19:54 +01:00
}
2024-11-05 19:44:24 +00:00
async fn run() -> anyhow::Result<()> {
let cwd = std::env::current_dir().expect("failed to get current working directory");
2024-07-25 15:32:48 +01:00
#[cfg(windows)]
2024-07-26 17:47:53 +01:00
'scripts: {
2024-07-25 15:32:48 +01:00
let exe = std::env::current_exe().expect("failed to get current executable path");
2024-07-26 17:47:53 +01:00
if exe.parent().is_some_and(|parent| {
parent.file_name().is_some_and(|parent| parent != "bin")
2024-07-26 17:47:53 +01:00
|| parent
.parent()
.and_then(|parent| parent.file_name())
.is_some_and(|parent| parent != HOME_DIR)
2024-07-26 17:47:53 +01:00
}) {
break 'scripts;
}
let exe_name = exe.file_name().unwrap().to_string_lossy();
let exe_name = exe_name
.strip_suffix(std::env::consts::EXE_SUFFIX)
.unwrap_or(&exe_name);
2024-07-25 15:32:48 +01:00
2024-07-26 17:47:53 +01:00
if exe_name == env!("CARGO_BIN_NAME") {
break 'scripts;
}
2024-07-25 15:32:48 +01:00
// 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
2024-07-26 17:47:53 +01:00
let status = std::process::Command::new("lune")
.arg("run")
2024-08-13 19:15:22 +01:00
.arg(exe.with_extension(""))
.arg("--")
2024-07-26 17:47:53 +01:00
.args(std::env::args_os().skip(1))
.current_dir(cwd)
2024-07-26 17:47:53 +01:00
.status()
.expect("failed to run lune");
2024-07-25 15:32:48 +01:00
2024-07-26 17:47:53 +01:00
std::process::exit(status.code().unwrap());
2024-07-25 15:32:48 +01:00
}
let (project_root_dir, project_workspace_dir) = 'finder: {
let mut current_path = Some(cwd.clone());
let mut project_root = None::<PathBuf>;
let mut workspace_dir = None::<PathBuf>;
2024-11-05 19:44:24 +00:00
async fn get_workspace_members(path: &Path) -> anyhow::Result<HashSet<PathBuf>> {
let manifest = fs::read_to_string(path.join(MANIFEST_FILE_NAME))
.await
.context("failed to read manifest")?;
let manifest: pesde::manifest::Manifest =
toml::from_str(&manifest).context("failed to parse manifest")?;
if manifest.workspace_members.is_empty() {
return Ok(HashSet::new());
}
manifest
.workspace_members
.iter()
.map(|member| path.join(member))
.map(|p| glob::glob(&p.to_string_lossy()))
.collect::<Result<Vec<_>, _>>()
.context("invalid glob patterns")?
.into_iter()
.flat_map(|paths| paths.into_iter())
.collect::<Result<HashSet<_>, _>>()
.context("failed to expand glob patterns")
}
while let Some(path) = current_path {
current_path = path.parent().map(|p| p.to_path_buf());
if !path.join(MANIFEST_FILE_NAME).exists() {
continue;
}
match (project_root.as_ref(), workspace_dir.as_ref()) {
(Some(project_root), Some(workspace_dir)) => {
break 'finder (project_root.clone(), Some(workspace_dir.clone()));
}
(Some(project_root), None) => {
2024-11-05 19:44:24 +00:00
if get_workspace_members(&path).await?.contains(project_root) {
workspace_dir = Some(path);
}
}
(None, None) => {
2024-11-05 19:44:24 +00:00
if get_workspace_members(&path).await?.contains(&cwd) {
// initializing a new member of a workspace
break 'finder (cwd, Some(path));
} else {
project_root = Some(path);
}
}
(None, Some(_)) => unreachable!(),
}
}
// we mustn't expect the project root to be found, as that would
// disable the ability to run pesde in a non-project directory (for example to init it)
(project_root.unwrap_or_else(|| cwd.clone()), workspace_dir)
};
let multi = {
let logger = pretty_env_logger::formatted_builder()
.parse_env(pretty_env_logger::env_logger::Env::default().default_filter_or("info"))
.build();
let multi = MultiProgress::new();
LogWrapper::new(multi.clone(), logger).try_init().unwrap();
multi
};
let home_dir = home_dir()?;
let data_dir = home_dir.join("data");
2024-11-05 19:44:24 +00:00
fs::create_dir_all(&data_dir)
.await
.expect("failed to create data directory");
2024-07-26 17:47:53 +01:00
2024-11-05 19:44:24 +00:00
let cas_dir = get_linkable_dir(&project_root_dir).await.join(HOME_DIR);
let cas_dir = if cas_dir == home_dir {
&data_dir
2024-07-28 17:19:54 +01:00
} else {
&cas_dir
}
.join("cas");
log::debug!("using cas dir in {}", cas_dir.display());
2024-07-26 17:47:53 +01:00
let project = Project::new(
project_root_dir,
project_workspace_dir,
2024-07-28 17:19:54 +01:00
data_dir,
cas_dir,
2024-11-05 19:44:24 +00:00
AuthConfig::new().with_tokens(get_tokens().await?.0),
2024-07-26 17:47:53 +01:00
);
let reqwest = {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::ACCEPT,
"application/json"
.parse()
.context("failed to create accept header")?,
);
2024-11-05 19:44:24 +00:00
reqwest::Client::builder()
2024-07-26 17:47:53 +01:00
.user_agent(concat!(
env!("CARGO_PKG_NAME"),
"/",
env!("CARGO_PKG_VERSION")
))
.default_headers(headers)
.build()?
};
2024-09-28 23:37:38 +01:00
#[cfg(feature = "version-management")]
{
let target_version = project
.deser_manifest()
2024-11-05 19:44:24 +00:00
.await
2024-09-28 23:37:38 +01:00
.ok()
.and_then(|manifest| manifest.pesde_version);
// store the current version in case it needs to be used later
2024-11-05 19:44:24 +00:00
get_or_download_version(&reqwest, &current_version()).await?;
2024-09-28 23:37:38 +01:00
let exe_path = if let Some(version) = target_version {
2024-11-05 19:44:24 +00:00
Some(get_or_download_version(&reqwest, &version).await?)
2024-09-28 23:37:38 +01:00
} else {
None
};
let exe_path = if let Some(exe_path) = exe_path {
exe_path
} else {
2024-11-05 19:44:24 +00:00
get_or_download_version(&reqwest, &max_installed_version().await?).await?
2024-09-28 23:37:38 +01:00
};
if let Some(exe_path) = exe_path {
let status = std::process::Command::new(exe_path)
.args(std::env::args_os().skip(1))
.status()
.expect("failed to run new version");
std::process::exit(status.code().unwrap());
}
2024-07-26 17:47:53 +01:00
2024-10-20 17:13:08 +01:00
display_err(
2024-11-05 19:44:24 +00:00
check_for_updates(&reqwest).await,
" while checking for updates",
2024-10-20 17:13:08 +01:00
);
2024-11-05 19:44:24 +00:00
}
2024-10-20 17:13:08 +01:00
let cli = match Cli::try_parse() {
Ok(cli) => cli,
Err(err) => {
let _ = err.print();
std::process::exit(err.exit_code());
}
};
2024-07-26 17:47:53 +01:00
2024-11-05 19:44:24 +00:00
cli.subcommand.run(project, multi, reqwest).await
2024-09-03 22:08:08 +01:00
}
2024-07-17 18:38:01 +01:00
2024-11-05 19:44:24 +00:00
#[tokio::main]
async fn main() {
let result = run().await;
2024-09-03 22:08:08 +01:00
let is_err = result.is_err();
display_err(result, "");
if is_err {
2024-07-12 23:09:37 +01:00
std::process::exit(1);
2024-03-04 20:18:49 +00:00
}
}