Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 84 additions & 15 deletions crates/prek/src/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1053,12 +1053,15 @@ mod tests {
description: Some(
"desc",
),
language_request: Python(
MajorMinor(
3,
12,
language_request: LanguageRequest {
version: Python(
MajorMinor(
3,
12,
),
),
),
allows_download: true,
},
log_file: None,
require_serial: false,
stages: Stages(manual),
Expand Down Expand Up @@ -1223,7 +1226,7 @@ mod tests {

#[tokio::test]
async fn hook_builder_preserves_additional_dependency_order() -> Result<()> {
let (temp, project) = setup_python_hook_test()?;
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
let repo = Arc::new(Repo::Remote {
path: repo_path.clone(),
Expand Down Expand Up @@ -1350,7 +1353,7 @@ mod tests {

/// Set up a temporary directory with a minimal `.pre-commit-config.yaml`
/// and a `remote-repo` subdirectory.
fn setup_python_hook_test() -> Result<(tempfile::TempDir, Arc<Project>)> {
fn setup_hook_test() -> Result<(tempfile::TempDir, Arc<Project>)> {
let temp = tempfile::tempdir()?;
let config_path = temp.path().join(PRE_COMMIT_CONFIG_YAML);
fs_err::write(&config_path, "repos: []\n")?;
Expand All @@ -1367,9 +1370,11 @@ mod tests {
}

/// Build a hook from the given repo path and options via `HookBuilder`.
async fn build_python_hook(
async fn build_hook(
project: Arc<Project>,
repo_path: PathBuf,
language: Language,
entry: &str,
language_version: Option<&str>,
) -> Result<Hook> {
let repo = Arc::new(Repo::Remote {
Expand All @@ -1382,8 +1387,8 @@ mod tests {
let hook_spec = HookSpec {
id: "test-hook".to_string(),
name: "test-hook".to_string(),
entry: "./hook.py".to_string(),
language: Language::Python,
entry: entry.to_string(),
language,
priority: None,
groups: None,
options: HookOptions {
Expand All @@ -1397,6 +1402,21 @@ mod tests {
.await?)
}

async fn build_python_hook(
project: Arc<Project>,
repo_path: PathBuf,
language_version: Option<&str>,
) -> Result<Hook> {
build_hook(
project,
repo_path,
Language::Python,
"./hook.py",
language_version,
)
.await
}

static PEP723_SCRIPT: &str = indoc::indoc! {r#"
# /// script
# requires-python = ">=3.11"
Expand All @@ -1406,7 +1426,7 @@ mod tests {

#[tokio::test]
async fn hook_builder_python_pep723_overrides_user_and_pyproject() -> Result<()> {
let (temp, project) = setup_python_hook_test()?;
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(
repo_path.join("pyproject.toml"),
Expand All @@ -1425,7 +1445,7 @@ mod tests {

#[tokio::test]
async fn hook_builder_python_user_language_version_overrides_pyproject() -> Result<()> {
let (temp, project) = setup_python_hook_test()?;
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(
repo_path.join("pyproject.toml"),
Expand All @@ -1444,7 +1464,7 @@ mod tests {

#[tokio::test]
async fn hook_builder_python_pep723_overrides_pyproject_without_user_version() -> Result<()> {
let (temp, project) = setup_python_hook_test()?;
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(
repo_path.join("pyproject.toml"),
Expand All @@ -1461,9 +1481,31 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn hook_builder_python_metadata_refines_system_without_enabling_downloads() -> Result<()>
{
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(
repo_path.join("pyproject.toml"),
"[project]\nrequires-python = \">=3.8\"\n",
)?;
fs_err::write(repo_path.join("hook.py"), PEP723_SCRIPT)?;

let hook = build_python_hook(project, repo_path, Some("system")).await?;
let expected = LanguageRequest::parse(Language::Python, ">=3.11")?;

assert_eq!(
hook.language_request.version_request(),
expected.version_request()
);
assert!(!hook.language_request.allows_download());
Ok(())
}

#[tokio::test]
async fn hook_builder_python_defaults_to_any_without_version_sources() -> Result<()> {
let (temp, project) = setup_python_hook_test()?;
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(repo_path.join("hook.py"), "print(\"hello\")\n")?;

Expand All @@ -1475,7 +1517,7 @@ mod tests {

#[tokio::test]
async fn hook_builder_python_pyproject_provides_version_when_no_other_source() -> Result<()> {
let (temp, project) = setup_python_hook_test()?;
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(
repo_path.join("pyproject.toml"),
Expand All @@ -1491,4 +1533,31 @@ mod tests {
);
Ok(())
}

#[tokio::test]
async fn hook_builder_go_mod_refines_system_without_enabling_downloads() -> Result<()> {
let (temp, project) = setup_hook_test()?;
let repo_path = temp.path().join("remote-repo");
fs_err::write(
repo_path.join("go.mod"),
"module example.com/test-hook\n\ngo 1.22\n",
)?;

let hook = build_hook(
project,
repo_path,
Language::Golang,
"go test",
Some("system"),
)
.await?;
let expected = LanguageRequest::parse(Language::Golang, ">= 1.22.0")?;

assert_eq!(
hook.language_request.version_request(),
expected.version_request()
);
assert!(!hook.language_request.allows_download());
Ok(())
}
}
9 changes: 2 additions & 7 deletions crates/prek/src/languages/bun/bun.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::hook::{Hook, InstallInfo};
use crate::languages::LanguageBackend;
use crate::languages::bun::BunRequest;
use crate::languages::bun::installer::{BunInstaller, BunResult, bin_dir, lib_dir};
use crate::languages::version::LanguageRequest;
use crate::process::Cmd;
use crate::run::run_by_batch;
use crate::store::{Store, ToolBucket};
Expand Down Expand Up @@ -43,13 +42,9 @@ impl LanguageBackend for Bun {
let bun_dir = store.tools_path(ToolBucket::Bun);
let installer = BunInstaller::new(bun_dir);

let (bun_request, allows_download) = match &hook.language_request {
LanguageRequest::Any { system_only } => (&BunRequest::Any, !system_only),
LanguageRequest::Bun(bun_request) => (bun_request, true),
_ => unreachable!(),
};
let bun_request: &BunRequest = hook.language_request.version();
let bun = installer
.install(store, bun_request, allows_download)
.install(store, bun_request, hook.language_request.allows_download())
.await
.context("Failed to install bun")?;

Expand Down
9 changes: 2 additions & 7 deletions crates/prek/src/languages/deno/deno.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use crate::hook::{Hook, InstallInfo, InstalledHook};
use crate::languages::LanguageBackend;
use crate::languages::deno::DenoRequest;
use crate::languages::deno::installer::{DenoInstaller, DenoResult, bin_dir};
use crate::languages::version::LanguageRequest;
use crate::process::Cmd;
use crate::run::run_by_batch;
use crate::store::{CacheBucket, Store, ToolBucket};
Expand Down Expand Up @@ -70,13 +69,9 @@ impl LanguageBackend for Deno {
let deno_dir = store.tools_path(ToolBucket::Deno);
let installer = DenoInstaller::new(deno_dir);

let (deno_request, allows_download) = match &hook.language_request {
LanguageRequest::Any { system_only } => (&DenoRequest::Any, !system_only),
LanguageRequest::Deno(deno_request) => (deno_request, true),
_ => unreachable!(),
};
let deno_request: &DenoRequest = hook.language_request.version();
let deno = installer
.install(store, deno_request, allows_download)
.install(store, deno_request, hook.language_request.allows_download())
.await
.context("Failed to install deno")?;

Expand Down
9 changes: 2 additions & 7 deletions crates/prek/src/languages/dotnet/dotnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use crate::hook::{Hook, InstallInfo, InstalledHook};
use crate::languages::LanguageBackend;
use crate::languages::dotnet::DotnetRequest;
use crate::languages::dotnet::installer::{DotnetInstaller, DotnetResult};
use crate::languages::version::LanguageRequest;
use crate::process::Cmd;
use crate::run::run_by_batch;
use crate::store::{Store, ToolBucket};
Expand Down Expand Up @@ -49,13 +48,9 @@ impl LanguageBackend for Dotnet {
let progress = reporter.on_install_start(&hook);

let installer = DotnetInstaller::new(store.tools_path(ToolBucket::Dotnet));
let (request, allows_download) = match &hook.language_request {
LanguageRequest::Any { system_only } => (&DotnetRequest::Any, !system_only),
LanguageRequest::Dotnet(request) => (request, true),
_ => unreachable!(),
};
let request: &DotnetRequest = hook.language_request.version();
let dotnet = installer
.install(request, allows_download)
.install(request, hook.language_request.allows_download())
.await
.context("Failed to install dotnet SDK")?;

Expand Down
9 changes: 2 additions & 7 deletions crates/prek/src/languages/golang/golang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::hook::{Hook, InstallInfo, InstalledHook};
use crate::languages::LanguageBackend;
use crate::languages::golang::GoRequest;
use crate::languages::golang::installer::GoInstaller;
use crate::languages::version::LanguageRequest;
use crate::process::Cmd;
use crate::run::run_by_batch;
use crate::store::{CacheBucket, Store, ToolBucket};
Expand All @@ -36,13 +35,9 @@ impl LanguageBackend for Golang {
let go_dir = store.tools_path(ToolBucket::Go);
let installer = GoInstaller::new(go_dir);

let (version, allows_download) = match &hook.language_request {
LanguageRequest::Any { system_only } => (&GoRequest::Any, !system_only),
LanguageRequest::Golang(version) => (version, true),
_ => unreachable!(),
};
let version: &GoRequest = hook.language_request.version();
let go = installer
.install(store, version, allows_download)
.install(store, version, hook.language_request.allows_download())
.await
.context("Failed to install go")?;

Expand Down
9 changes: 5 additions & 4 deletions crates/prek/src/languages/golang/gomod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use tracing::trace;

use crate::config::Language;
use crate::hook::Hook;
use crate::languages::version::LanguageRequest;
use crate::languages::version::VersionRequest;

fn parse_go_mod_directives(contents: &str) -> (Option<String>, Option<String>) {
let mut go_version: Option<String> = None;
Expand Down Expand Up @@ -105,7 +105,8 @@ async fn extract_go_mod_language_request(repo_path: &Path) -> Result<Option<Stri
}

pub(crate) async fn extract_go_mod_metadata(hook: &mut Hook) -> Result<()> {
// Respect an explicitly configured `language_version`.
// Respect an explicitly configured concrete version request. `system` is still an
// unconstrained version request, so go.mod may refine it without enabling downloads.
if !hook.language_request.is_any() {
trace!(hook = %hook, "Skipping go.mod metadata extraction because language_version is already configured");
return Ok(());
Expand All @@ -120,7 +121,7 @@ pub(crate) async fn extract_go_mod_metadata(hook: &mut Hook) -> Result<()> {
return Ok(());
};

let req = match LanguageRequest::parse(Language::Golang, &req_str) {
let version = match VersionRequest::parse(Language::Golang, &req_str) {
Ok(req) => req,
Err(err) => {
trace!(%req_str, error = %err, "Ignoring invalid go.mod-derived language_version");
Expand All @@ -129,7 +130,7 @@ pub(crate) async fn extract_go_mod_metadata(hook: &mut Hook) -> Result<()> {
};

trace!(hook = %hook, version = %req_str, "Using go.mod-derived language_version");
hook.language_request = req;
hook.language_request.set_version(version);

Ok(())
}
Expand Down
9 changes: 2 additions & 7 deletions crates/prek/src/languages/node/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::languages::LanguageBackend;
use crate::languages::node::NodeRequest;
use crate::languages::node::installer::{NodeInstaller, bin_dir, lib_dir, query_node_version};
use crate::languages::node::version::EXTRA_KEY_LTS;
use crate::languages::version::LanguageRequest;
use crate::process::Cmd;
use crate::run::run_by_batch;
use crate::store::{CacheBucket, Store, ToolBucket};
Expand Down Expand Up @@ -61,13 +60,9 @@ impl LanguageBackend for Node {
let node_dir = store.tools_path(ToolBucket::Node);
let installer = NodeInstaller::new(node_dir);

let (node_request, allows_download) = match &hook.language_request {
LanguageRequest::Any { system_only } => (&NodeRequest::Any, !system_only),
LanguageRequest::Node(node_request) => (node_request, true),
_ => unreachable!(),
};
let node_request: &NodeRequest = hook.language_request.version();
let node = installer
.install(store, node_request, allows_download)
.install(store, node_request, hook.language_request.allows_download())
.await
.context("Failed to install node")?;

Expand Down
7 changes: 4 additions & 3 deletions crates/prek/src/languages/python/pep723.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use serde::Deserialize;
use tracing::trace;

use crate::hook::Hook;
use crate::languages::version::LanguageRequest;
use crate::languages::version::VersionRequest;

static FINDER: LazyLock<Finder> = LazyLock::new(|| Finder::new(b"# /// script"));

Expand Down Expand Up @@ -285,10 +285,11 @@ pub(crate) async fn extract_pep723_metadata(hook: &mut Hook) -> Result<()> {
if let Some(language_request) = script.metadata.requires_python {
if !hook.language_request.is_any() {
trace!(
"`language_version` is ignored because `requires_python` is specified in the PEP 723 metadata"
"The `language_version` constraint is replaced by `requires_python` from PEP 723 metadata"
);
}
hook.language_request = LanguageRequest::parse(hook.language, &language_request)?;
let version = VersionRequest::parse(hook.language, &language_request)?;
hook.language_request.set_version(version);
}

Ok(())
Expand Down
Loading
Loading