diff --git a/crates/prek/src/languages/python/python.rs b/crates/prek/src/languages/python/python.rs index 3c3ba6bc..295b427e 100644 --- a/crates/prek/src/languages/python/python.rs +++ b/crates/prek/src/languages/python/python.rs @@ -6,7 +6,7 @@ use std::sync::Arc; use anyhow::{Context, Result}; use prek_consts::env_vars::EnvVars; use serde::Deserialize; -use tracing::debug; +use tracing::{debug, trace}; use crate::cli::reporter::{HookInstallReporter, HookRunReporter}; use crate::hook::InstalledHook; @@ -91,25 +91,45 @@ impl LanguageImpl for Python { .context("Failed to create Python virtual environment")?; // Install dependencies - let deps = hook.install_dependencies(); - if deps.is_empty() { - debug!("No dependencies to install"); - } else { - uv.cmd("uv pip install", store) - .arg("pip") + let pip_install = || { + let mut cmd = uv.cmd("uv pip", store); + cmd.arg("pip") .arg("install") // Explicitly set project to root to avoid uv searching for project-level configs // `--project` has no other effect on `uv pip` subcommands. .args(["--project", "/"]) - .args(&*deps) .env(EnvVars::VIRTUAL_ENV, &info.env_path) // Make sure uv uses the venv's python .env_remove(EnvVars::UV_PYTHON) .env_remove(EnvVars::UV_MANAGED_PYTHON) .env_remove(EnvVars::UV_NO_MANAGED_PYTHON) - .check(true) + .check(true); + cmd + }; + + if let Some(repo_path) = hook.repo_path() { + trace!( + "Installing dependencies from repo path: {}", + repo_path.display() + ); + pip_install() + .arg("--directory") + .arg(repo_path) + .arg(".") + .args(&hook.additional_dependencies) .output() .await?; + } else if !hook.additional_dependencies.is_empty() { + trace!( + "Installing additional dependencies: {:?}", + hook.additional_dependencies + ); + pip_install() + .args(&hook.additional_dependencies) + .output() + .await?; + } else { + debug!("No dependencies to install"); } let python = python_exec(&info.env_path); diff --git a/crates/prek/tests/common/mod.rs b/crates/prek/tests/common/mod.rs index 9408f969..94a80d39 100644 --- a/crates/prek/tests/common/mod.rs +++ b/crates/prek/tests/common/mod.rs @@ -289,6 +289,18 @@ impl TestContext { .success(); } + /// Run `git tag`. + pub fn git_tag(&self, tag: &str) { + Command::new("git") + .arg("tag") + .arg(tag) + .arg("-m") + .arg(format!("Tag {tag}")) + .current_dir(&self.temp_dir) + .assert() + .success(); + } + /// Run `git reset`. pub fn git_reset(&self, target: &str) { Command::new("git") diff --git a/crates/prek/tests/languages/python.rs b/crates/prek/tests/languages/python.rs index c8b03003..1601fe1b 100644 --- a/crates/prek/tests/languages/python.rs +++ b/crates/prek/tests/languages/python.rs @@ -1,5 +1,6 @@ use assert_fs::assert::PathAssert; use assert_fs::fixture::{FileWriteStr, PathChild}; +use prek_consts::MANIFEST_FILE; use prek_consts::env_vars::EnvVars; use crate::common::{TestContext, cmd_snapshot}; @@ -255,6 +256,71 @@ fn additional_dependencies() { "); } +#[test] +fn additional_dependencies_in_remote_repo() -> anyhow::Result<()> { + // Create a remote repo with a python hook that has additional dependencies. + let repo = TestContext::new(); + repo.init_project(); + + let repo_path = repo.work_dir(); + repo_path.child(MANIFEST_FILE).write_str(indoc::indoc! {r#" + - id: hello + name: hello + language: python + entry: pyecho Greetings from hook + additional_dependencies: [".[cli]"] + "#})?; + repo_path.child("module.py").write_str(indoc::indoc! {r#" + def greet(): + print("Greetings from module") + "#})?; + repo_path.child("setup.py").write_str(indoc::indoc! {r#" + from setuptools import setup, find_packages + + setup( + name="remote-hooks", + version="0.1.0", + py_modules=["module"], + extras_require={ + "cli": ["pyecho-cli"] + } + ) + "#})?; + repo.git_add("."); + repo.configure_git_author(); + repo.disable_auto_crlf(); + repo.git_commit("Add manifest"); + repo.git_tag("v0.1.0"); + + let context = TestContext::new(); + context.init_project(); + context.write_pre_commit_config(&indoc::formatdoc! {r" + repos: + - repo: {} + rev: v0.1.0 + hooks: + - id: hello + name: hello + verbose: true + ", repo_path.display()}); + + context.git_add("."); + cmd_snapshot!(context.filters(), context.run(), @r" + success: true + exit_code: 0 + ----- stdout ----- + hello....................................................................Passed + - hook id: hello + - duration: [TIME] + + Greetings from hook .pre-commit-config.yaml + + ----- stderr ----- + "); + + Ok(()) +} + /// Ensure that stderr from hooks is captured and shown to the user. #[test] fn hook_stderr() -> anyhow::Result<()> {