Skip to content

Commit

Permalink
pkgx --ensure make, uses the system make if poss
Browse files Browse the repository at this point in the history
  • Loading branch information
mxcl committed Feb 24, 2025
1 parent 118c17e commit bc9be97
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 50 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,6 @@ jobs:
GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }}
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}

- run: echo 'naughty-boy' > $FILENAME.asc
if: matrix.platform.build-id == 'linux+x86-64'

- name: attach product to release
run: ./pkgx gh release upload --clobber
v${{ github.event.inputs.version }}
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ jobs:
# testing we correctly handle +pkg syntax for pkgs with no env
- run: pkgx +curl.se/ca-certs

- run: pkgx --ensure make --version

# create a fork bomb, but since it’s via pkgx we prevent it
- run: |
echo '#!/bin/sh' > foo
Expand Down
11 changes: 8 additions & 3 deletions crates/cli/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct Flags {
pub silent: bool,
pub json: bool,
pub version_n_continue: bool,
pub ensure: bool,
}

pub struct Args {
Expand All @@ -25,9 +26,10 @@ pub fn parse() -> Args {
let mut mode = Mode::X;
let mut plus = Vec::new();
let mut args = Vec::new();
let mut silent: bool = false;
let mut quiet: bool = false;
let mut json: bool = false;
let mut silent = false;
let mut quiet = false;
let mut json = false;
let mut ensure = false;
let mut find_program = false;
let mut collecting_args = false;
let mut version_n_continue = false;
Expand Down Expand Up @@ -56,6 +58,7 @@ pub fn parse() -> Args {
"--help" => mode = Mode::Help,
"--version" => mode = Mode::Version,
"--quiet" => quiet = true,
"--ensure" => ensure = true,
"--shellcode" => {
if !silent {
eprintln!("{}", style("⨯ migration required").red());
Expand Down Expand Up @@ -88,6 +91,7 @@ pub fn parse() -> Args {
quiet = true
}
}
'e' => ensure = true,
'h' => mode = Mode::Help,
's' => silent = true,
'j' => json = true,
Expand All @@ -112,6 +116,7 @@ pub fn parse() -> Args {
json,
quiet,
version_n_continue,
ensure,
},
}
}
1 change: 1 addition & 0 deletions crates/cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ flags:
-q, --quiet # suppress brief informational messages
-qq, --silent # no chat. no errors. just execute.
-v, --version
-e, --ensure # if possible, delegate to the system‐installed program
more:
$ open https://docs.pkgx.sh
Expand Down
96 changes: 64 additions & 32 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ mod execve;
mod help;
#[cfg(test)]
mod tests;
mod utils;

use std::{collections::HashMap, error::Error, fmt::Write, sync::Arc, time::Duration};

use execve::execve;
use indicatif::{ProgressBar, ProgressState, ProgressStyle};
use libpkgx::{
config::Config, env, hydrate::hydrate, install_multi, pantry_db, resolve::resolve, sync,
types::PackageReq, utils,
types::PackageReq,
};
use regex::Regex;
use rusqlite::Connection;
Expand All @@ -23,23 +24,26 @@ async fn main() -> Result<(), Box<dyn Error>> {
mut args,
mode,
flags,
find_program,
mut find_program,
} = args::parse();

if flags.version_n_continue {
eprintln!("pkgx {}", env!("CARGO_PKG_VERSION"));
}

match mode {
args::Mode::Help => {
if flags.version_n_continue {
eprintln!("pkgx {}", env!("CARGO_PKG_VERSION"));
}
println!("{}", help::usage());
return Ok(());
}
args::Mode::Version => {
println!("pkgx {}", env!("CARGO_PKG_VERSION"));
return Ok(());
}
args::Mode::X => (),
args::Mode::X => {
if flags.version_n_continue {
eprintln!("pkgx {}", env!("CARGO_PKG_VERSION"));
}
}
}

let config = Config::new()?;
Expand Down Expand Up @@ -101,33 +105,57 @@ async fn main() -> Result<(), Box<dyn Error>> {
project: cmd,
} = PackageReq::parse(&args[0])?;

args[0] = cmd.clone(); // invoke eg. `node` rather than eg. `node@20`
let mut found = false;
if flags.ensure {
let paths = std::env::var("PATH")
.or_else(|_| Ok::<String, std::env::VarError>("".to_string()))
.unwrap()
.split(':')
.map(|x| x.to_string())
.collect::<Vec<String>>();
if let Some(cmd) = libpkgx::utils::find_program(&cmd, &paths).await {
#[cfg(target_os = "macos")]
let do_it = utils::good_on_macos(&cmd);
#[cfg(not(target_os = "macos"))]
let do_it = true;

if do_it {
args[0] = cmd;
found = true;
find_program = false;
}
}
}

let project = match which(&cmd, &conn, &pkgs).await {
Err(WhichError::CmdNotFound(cmd)) => {
if !did_sync {
if let Some(spinner) = &spinner {
let msg = format!("{} not found, syncing…", cmd);
spinner.set_message(msg);
}
// cmd not found ∴ sync in case it is new
sync::update(&config, &mut conn).await?;
if let Some(spinner) = &spinner {
spinner.set_message("resolving pkg graph…");
if !found {
args[0] = cmd.clone(); // invoke eg. `node` rather than eg. `node@20`

let project = match which(&cmd, &conn, &pkgs).await {
Err(WhichError::CmdNotFound(cmd)) => {
if !did_sync {
if let Some(spinner) = &spinner {
let msg = format!("{} not found, syncing…", cmd);
spinner.set_message(msg);
}
// cmd not found ∴ sync in case it is new
sync::update(&config, &mut conn).await?;
if let Some(spinner) = &spinner {
spinner.set_message("resolving pkg graph…");
}
which(&cmd, &conn, &pkgs).await
} else {
Err(WhichError::CmdNotFound(cmd))
}
which(&cmd, &conn, &pkgs).await
} else {
Err(WhichError::CmdNotFound(cmd))
}
}
Err(err) => Err(err),
Ok(project) => Ok(project),
}?;
Err(err) => Err(err),
Ok(project) => Ok(project),
}?;

pkgs.push(PackageReq {
project,
constraint,
});
pkgs.push(PackageReq {
project,
constraint,
});
}
}

let companions = pantry_db::companions_for_projects(
Expand Down Expand Up @@ -182,7 +210,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
}

let cmd = if find_program {
utils::find_program(&args.remove(0), &env["PATH"]).await?
libpkgx::utils::find_program(&args.remove(0), &env["PATH"])
.await
.unwrap()
} else if args[0].contains('/') {
// user specified a path to program which we should use
args.remove(0)
Expand All @@ -202,7 +232,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
.collect::<Vec<String>>(),
);
}
utils::find_program(&args.remove(0), &paths).await?
libpkgx::utils::find_program(&args.remove(0), &paths)
.await
.unwrap()
};
let env = env::mix(env);
let mut env = env::mix_runtime(&env, &installations, &conn)?;
Expand Down
26 changes: 26 additions & 0 deletions crates/cli/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#[cfg(target_os = "macos")]
use std::fs;

#[cfg(target_os = "macos")]
pub fn good_on_macos(cmd: &str) -> bool {
//TODO there’s a lot more in there
//NOTE tricky to know how to keep this updated or varied by CLT version etc.
//THO probs the tools we care about don’t vary or change
match cmd {
"/usr/bin/cc" => has_xcode_clt(),
"/usr/bin/c++" => has_xcode_clt(),
"/usr/bin/make" => has_xcode_clt(),
"/usr/bin/python3" => has_xcode_clt(),
"/usr/bin/pip3" => has_xcode_clt(),
"/usr/bin/strip" => has_xcode_clt(),
"/usr/bin/git" => has_xcode_clt(),
_ => true,
}
}

#[cfg(target_os = "macos")]
fn has_xcode_clt() -> bool {
fs::metadata("/Library/Developer/CommandLineTools/usr/bin")
.map(|m| m.is_dir())
.unwrap_or(false)
}
26 changes: 14 additions & 12 deletions crates/lib/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
#[cfg(not(windows))]
use std::os::unix::fs::PermissionsExt;
use std::{error::Error, path::Path};
use std::path::Path;

pub async fn find_program(arg: &str, paths: &Vec<String>) -> Result<String, Box<dyn Error>> {
pub async fn find_program(arg: &str, paths: &Vec<String>) -> Option<String> {
if arg.starts_with("/") {
return Ok(arg.to_string());
return Some(arg.to_string());
} else if arg.contains("/") {
return Ok(std::env::current_dir()
.unwrap()
.join(arg)
.to_str()
.unwrap()
.to_string());
return Some(
std::env::current_dir()
.unwrap()
.join(arg)
.to_str()
.unwrap()
.to_string(),
);
}
for path in paths {
let full_path = Path::new(&path).join(arg);
if full_path.is_file() {
#[cfg(unix)]
if let Ok(metadata) = full_path.metadata() {
if metadata.permissions().mode() & 0o111 != 0 {
return Ok(full_path.to_str().unwrap().to_string());
return Some(full_path.to_str().unwrap().to_string());
}
}
#[cfg(windows)]
if let Some(ext) = full_path.extension() {
match ext.to_str() {
Some("exe") | Some("bat") | Some("cmd") => {
return Ok(full_path.to_str().unwrap().to_string())
return Some(full_path.to_str().unwrap().to_string())
}
_ => {}
}
}
}
}
Err(format!("cmd not found: {}", arg).into())
None
}

0 comments on commit bc9be97

Please sign in to comment.