1use std::num::NonZeroUsize;
2use std::ops::Deref;
3use std::path::{Path, PathBuf};
4use std::time::Duration;
5use tracing::info_span;
6use uv_client::{DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT, DEFAULT_READ_TIMEOUT_UPLOAD};
7use uv_dirs::{system_config_file, user_config_dir};
8use uv_distribution_types::Origin;
9use uv_flags::EnvironmentFlags;
10use uv_fs::Simplified;
11use uv_static::{EnvVars, InvalidEnvironmentVariable, parse_boolish_environment_variable};
12use uv_warnings::warn_user;
13
14pub use crate::combine::*;
15pub use crate::settings::*;
16
17mod combine;
18mod settings;
19
20#[derive(Debug, Clone)]
22pub struct FilesystemOptions(Options);
23
24impl FilesystemOptions {
25 pub fn into_options(self) -> Options {
27 self.0
28 }
29
30 #[must_use]
32 pub fn with_origin(self, origin: Origin) -> Self {
33 Self(self.0.with_origin(origin))
34 }
35}
36
37impl Deref for FilesystemOptions {
38 type Target = Options;
39
40 fn deref(&self) -> &Self::Target {
41 &self.0
42 }
43}
44
45impl FilesystemOptions {
46 pub fn user() -> Result<Option<Self>, Error> {
48 let Some(dir) = user_config_dir() else {
49 return Ok(None);
50 };
51 let root = dir.join("uv");
52 let file = root.join("uv.toml");
53
54 tracing::debug!("Searching for user configuration in: `{}`", file.display());
55 match read_file(&file) {
56 Ok(options) => {
57 tracing::debug!("Found user configuration in: `{}`", file.display());
58 validate_uv_toml(&file, &options)?;
59 Ok(Some(Self(options.with_origin(Origin::User))))
60 }
61 Err(Error::Io(err))
62 if matches!(
63 err.kind(),
64 std::io::ErrorKind::NotFound
65 | std::io::ErrorKind::NotADirectory
66 | std::io::ErrorKind::PermissionDenied
67 ) =>
68 {
69 Ok(None)
70 }
71 Err(err) => Err(err),
72 }
73 }
74
75 pub fn system() -> Result<Option<Self>, Error> {
76 let Some(file) = system_config_file() else {
77 return Ok(None);
78 };
79
80 tracing::debug!("Found system configuration in: `{}`", file.display());
81 let options = read_file(&file)?;
82 validate_uv_toml(&file, &options)?;
83 Ok(Some(Self(options.with_origin(Origin::System))))
84 }
85
86 pub fn find(path: &Path) -> Result<Option<Self>, Error> {
91 for ancestor in path.ancestors() {
92 match Self::from_directory(ancestor) {
93 Ok(Some(options)) => {
94 return Ok(Some(options));
95 }
96 Ok(None) => {
97 }
99 Err(Error::PyprojectToml(path, err)) => {
100 warn_user!(
102 "Failed to parse `{}` during settings discovery:\n{}",
103 path.user_display().cyan(),
104 textwrap::indent(&err.to_string(), " ")
105 );
106 }
107 Err(err) => {
108 return Err(err);
110 }
111 }
112 }
113 Ok(None)
114 }
115
116 pub fn from_directory(dir: &Path) -> Result<Option<Self>, Error> {
119 let path = dir.join("uv.toml");
121 match fs_err::read_to_string(&path) {
122 Ok(content) => {
123 let options =
124 info_span!("toml::from_str filesystem options uv.toml", path = %path.display())
125 .in_scope(|| toml::from_str::<Options>(&content))
126 .map_err(|err| Error::UvToml(path.clone(), Box::new(err)))?
127 .relative_to(&std::path::absolute(dir)?)?;
128
129 let pyproject = dir.join("pyproject.toml");
132 if let Ok(content) = fs_err::read_to_string(&pyproject) {
133 let result = info_span!("toml::from_str filesystem options pyproject.toml", path = %pyproject.display())
134 .in_scope(|| toml::from_str::<PyProjectToml>(&content)).ok();
135 if let Some(options) =
136 result.and_then(|pyproject| pyproject.tool.and_then(|tool| tool.uv))
137 {
138 warn_uv_toml_masked_fields(&options);
139 }
140 }
141
142 tracing::debug!("Found workspace configuration at `{}`", path.display());
143 validate_uv_toml(&path, &options)?;
144 return Ok(Some(Self(options.with_origin(Origin::Project))));
145 }
146 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
147 Err(err) => return Err(err.into()),
148 }
149
150 let path = dir.join("pyproject.toml");
152 match fs_err::read_to_string(&path) {
153 Ok(content) => {
154 let pyproject =
156 info_span!("toml::from_str filesystem options pyproject.toml", path = %path.display())
157 .in_scope(|| toml::from_str::<PyProjectToml>(&content))
158 .map_err(|err| Error::PyprojectToml(path.clone(), Box::new(err)))?;
159 let Some(tool) = pyproject.tool else {
160 tracing::debug!(
161 "Skipping `pyproject.toml` in `{}` (no `[tool]` section)",
162 dir.display()
163 );
164 return Ok(None);
165 };
166 let Some(options) = tool.uv else {
167 tracing::debug!(
168 "Skipping `pyproject.toml` in `{}` (no `[tool.uv]` section)",
169 dir.display()
170 );
171 return Ok(None);
172 };
173
174 let options = options.relative_to(&std::path::absolute(dir)?)?;
175
176 tracing::debug!("Found workspace configuration at `{}`", path.display());
177 return Ok(Some(Self(options)));
178 }
179 Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
180 Err(err) => return Err(err.into()),
181 }
182
183 Ok(None)
184 }
185
186 pub fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
188 let path = path.as_ref();
189 tracing::debug!("Reading user configuration from: `{}`", path.display());
190
191 let options = read_file(path)?;
192 validate_uv_toml(path, &options)?;
193 Ok(Self(options))
194 }
195}
196
197impl From<Options> for FilesystemOptions {
198 fn from(options: Options) -> Self {
199 Self(options)
200 }
201}
202
203fn read_file(path: &Path) -> Result<Options, Error> {
205 let content = fs_err::read_to_string(path)?;
206 let options = info_span!("toml::from_str filesystem options uv.toml", path = %path.display())
207 .in_scope(|| toml::from_str::<Options>(&content))
208 .map_err(|err| Error::UvToml(path.to_path_buf(), Box::new(err)))?;
209 let options = if let Some(parent) = std::path::absolute(path)?.parent() {
210 options.relative_to(parent)?
211 } else {
212 options
213 };
214 Ok(options)
215}
216
217fn validate_uv_toml(path: &Path, options: &Options) -> Result<(), Error> {
219 let Options {
220 globals: _,
221 top_level: _,
222 install_mirrors: _,
223 publish: _,
224 add: _,
225 pip: _,
226 cache_keys: _,
227 override_dependencies: _,
228 exclude_dependencies: _,
229 constraint_dependencies: _,
230 build_constraint_dependencies: _,
231 environments,
232 required_environments,
233 conflicts,
234 workspace,
235 sources,
236 dev_dependencies,
237 default_groups,
238 dependency_groups,
239 managed,
240 package,
241 build_backend,
242 } = options;
243 if conflicts.is_some() {
247 return Err(Error::PyprojectOnlyField(path.to_path_buf(), "conflicts"));
248 }
249 if workspace.is_some() {
250 return Err(Error::PyprojectOnlyField(path.to_path_buf(), "workspace"));
251 }
252 if sources.is_some() {
253 return Err(Error::PyprojectOnlyField(path.to_path_buf(), "sources"));
254 }
255 if dev_dependencies.is_some() {
256 return Err(Error::PyprojectOnlyField(
257 path.to_path_buf(),
258 "dev-dependencies",
259 ));
260 }
261 if default_groups.is_some() {
262 return Err(Error::PyprojectOnlyField(
263 path.to_path_buf(),
264 "default-groups",
265 ));
266 }
267 if dependency_groups.is_some() {
268 return Err(Error::PyprojectOnlyField(
269 path.to_path_buf(),
270 "dependency-groups",
271 ));
272 }
273 if managed.is_some() {
274 return Err(Error::PyprojectOnlyField(path.to_path_buf(), "managed"));
275 }
276 if package.is_some() {
277 return Err(Error::PyprojectOnlyField(path.to_path_buf(), "package"));
278 }
279 if build_backend.is_some() {
280 return Err(Error::PyprojectOnlyField(
281 path.to_path_buf(),
282 "build-backend",
283 ));
284 }
285 if environments.is_some() {
286 return Err(Error::PyprojectOnlyField(
287 path.to_path_buf(),
288 "environments",
289 ));
290 }
291 if required_environments.is_some() {
292 return Err(Error::PyprojectOnlyField(
293 path.to_path_buf(),
294 "required-environments",
295 ));
296 }
297 Ok(())
298}
299
300fn warn_uv_toml_masked_fields(options: &Options) {
304 let Options {
305 globals:
306 GlobalOptions {
307 required_version,
308 native_tls,
309 offline,
310 no_cache,
311 cache_dir,
312 preview,
313 python_preference,
314 python_downloads,
315 concurrent_downloads,
316 concurrent_builds,
317 concurrent_installs,
318 allow_insecure_host,
319 http_proxy,
320 https_proxy,
321 no_proxy,
322 },
323 top_level:
324 ResolverInstallerSchema {
325 index,
326 index_url,
327 extra_index_url,
328 no_index,
329 find_links,
330 index_strategy,
331 keyring_provider,
332 resolution,
333 prerelease,
334 fork_strategy,
335 dependency_metadata,
336 config_settings,
337 config_settings_package,
338 no_build_isolation,
339 no_build_isolation_package,
340 extra_build_dependencies,
341 extra_build_variables,
342 exclude_newer,
343 exclude_newer_package,
344 link_mode,
345 compile_bytecode,
346 no_sources,
347 no_sources_package: _,
348 upgrade,
349 upgrade_package,
350 reinstall,
351 reinstall_package,
352 no_build,
353 no_build_package,
354 no_binary,
355 no_binary_package,
356 torch_backend,
357 },
358 install_mirrors:
359 PythonInstallMirrors {
360 python_install_mirror,
361 pypy_install_mirror,
362 python_downloads_json_url,
363 },
364 publish:
365 PublishOptions {
366 publish_url,
367 trusted_publishing,
368 check_url,
369 },
370 add: AddOptions { add_bounds },
371 pip,
372 cache_keys,
373 override_dependencies,
374 exclude_dependencies,
375 constraint_dependencies,
376 build_constraint_dependencies,
377 environments: _,
378 required_environments: _,
379 conflicts: _,
380 workspace: _,
381 sources: _,
382 dev_dependencies: _,
383 default_groups: _,
384 dependency_groups: _,
385 managed: _,
386 package: _,
387 build_backend: _,
388 } = options;
389
390 let mut masked_fields = vec![];
391
392 if required_version.is_some() {
393 masked_fields.push("required-version");
394 }
395 if native_tls.is_some() {
396 masked_fields.push("native-tls");
397 }
398 if offline.is_some() {
399 masked_fields.push("offline");
400 }
401 if no_cache.is_some() {
402 masked_fields.push("no-cache");
403 }
404 if cache_dir.is_some() {
405 masked_fields.push("cache-dir");
406 }
407 if preview.is_some() {
408 masked_fields.push("preview");
409 }
410 if python_preference.is_some() {
411 masked_fields.push("python-preference");
412 }
413 if python_downloads.is_some() {
414 masked_fields.push("python-downloads");
415 }
416 if concurrent_downloads.is_some() {
417 masked_fields.push("concurrent-downloads");
418 }
419 if concurrent_builds.is_some() {
420 masked_fields.push("concurrent-builds");
421 }
422 if concurrent_installs.is_some() {
423 masked_fields.push("concurrent-installs");
424 }
425 if allow_insecure_host.is_some() {
426 masked_fields.push("allow-insecure-host");
427 }
428 if http_proxy.is_some() {
429 masked_fields.push("http-proxy");
430 }
431 if https_proxy.is_some() {
432 masked_fields.push("https-proxy");
433 }
434 if no_proxy.is_some() {
435 masked_fields.push("no-proxy");
436 }
437 if index.is_some() {
438 masked_fields.push("index");
439 }
440 if index_url.is_some() {
441 masked_fields.push("index-url");
442 }
443 if extra_index_url.is_some() {
444 masked_fields.push("extra-index-url");
445 }
446 if no_index.is_some() {
447 masked_fields.push("no-index");
448 }
449 if find_links.is_some() {
450 masked_fields.push("find-links");
451 }
452 if index_strategy.is_some() {
453 masked_fields.push("index-strategy");
454 }
455 if keyring_provider.is_some() {
456 masked_fields.push("keyring-provider");
457 }
458 if resolution.is_some() {
459 masked_fields.push("resolution");
460 }
461 if prerelease.is_some() {
462 masked_fields.push("prerelease");
463 }
464 if fork_strategy.is_some() {
465 masked_fields.push("fork-strategy");
466 }
467 if dependency_metadata.is_some() {
468 masked_fields.push("dependency-metadata");
469 }
470 if config_settings.is_some() {
471 masked_fields.push("config-settings");
472 }
473 if config_settings_package.is_some() {
474 masked_fields.push("config-settings-package");
475 }
476 if no_build_isolation.is_some() {
477 masked_fields.push("no-build-isolation");
478 }
479 if no_build_isolation_package.is_some() {
480 masked_fields.push("no-build-isolation-package");
481 }
482 if extra_build_dependencies.is_some() {
483 masked_fields.push("extra-build-dependencies");
484 }
485 if extra_build_variables.is_some() {
486 masked_fields.push("extra-build-variables");
487 }
488 if exclude_newer.is_some() {
489 masked_fields.push("exclude-newer");
490 }
491 if exclude_newer_package.is_some() {
492 masked_fields.push("exclude-newer-package");
493 }
494 if link_mode.is_some() {
495 masked_fields.push("link-mode");
496 }
497 if compile_bytecode.is_some() {
498 masked_fields.push("compile-bytecode");
499 }
500 if no_sources.is_some() {
501 masked_fields.push("no-sources");
502 }
503 if upgrade.is_some() {
504 masked_fields.push("upgrade");
505 }
506 if upgrade_package.is_some() {
507 masked_fields.push("upgrade-package");
508 }
509 if reinstall.is_some() {
510 masked_fields.push("reinstall");
511 }
512 if reinstall_package.is_some() {
513 masked_fields.push("reinstall-package");
514 }
515 if no_build.is_some() {
516 masked_fields.push("no-build");
517 }
518 if no_build_package.is_some() {
519 masked_fields.push("no-build-package");
520 }
521 if no_binary.is_some() {
522 masked_fields.push("no-binary");
523 }
524 if no_binary_package.is_some() {
525 masked_fields.push("no-binary-package");
526 }
527 if torch_backend.is_some() {
528 masked_fields.push("torch-backend");
529 }
530 if python_install_mirror.is_some() {
531 masked_fields.push("python-install-mirror");
532 }
533 if pypy_install_mirror.is_some() {
534 masked_fields.push("pypy-install-mirror");
535 }
536 if python_downloads_json_url.is_some() {
537 masked_fields.push("python-downloads-json-url");
538 }
539 if publish_url.is_some() {
540 masked_fields.push("publish-url");
541 }
542 if trusted_publishing.is_some() {
543 masked_fields.push("trusted-publishing");
544 }
545 if check_url.is_some() {
546 masked_fields.push("check-url");
547 }
548 if add_bounds.is_some() {
549 masked_fields.push("add-bounds");
550 }
551 if pip.is_some() {
552 masked_fields.push("pip");
553 }
554 if cache_keys.is_some() {
555 masked_fields.push("cache_keys");
556 }
557 if override_dependencies.is_some() {
558 masked_fields.push("override-dependencies");
559 }
560 if exclude_dependencies.is_some() {
561 masked_fields.push("exclude-dependencies");
562 }
563 if constraint_dependencies.is_some() {
564 masked_fields.push("constraint-dependencies");
565 }
566 if build_constraint_dependencies.is_some() {
567 masked_fields.push("build-constraint-dependencies");
568 }
569 if !masked_fields.is_empty() {
570 let field_listing = masked_fields.join("\n- ");
571 warn_user!(
572 "Found both a `uv.toml` file and a `[tool.uv]` section in an adjacent `pyproject.toml`. The following fields from `[tool.uv]` will be ignored in favor of the `uv.toml` file:\n- {}",
573 field_listing,
574 );
575 }
576}
577
578#[derive(thiserror::Error, Debug)]
579pub enum Error {
580 #[error(transparent)]
581 Io(#[from] std::io::Error),
582
583 #[error(transparent)]
584 Index(#[from] uv_distribution_types::IndexUrlError),
585
586 #[error("Failed to parse: `{}`", _0.user_display())]
587 PyprojectToml(PathBuf, #[source] Box<toml::de::Error>),
588
589 #[error("Failed to parse: `{}`", _0.user_display())]
590 UvToml(PathBuf, #[source] Box<toml::de::Error>),
591
592 #[error("Failed to parse: `{}`. The `{}` field is not allowed in a `uv.toml` file. `{}` is only applicable in the context of a project, and should be placed in a `pyproject.toml` file instead.", _0.user_display(), _1, _1
593 )]
594 PyprojectOnlyField(PathBuf, &'static str),
595
596 #[error(transparent)]
597 InvalidEnvironmentVariable(#[from] InvalidEnvironmentVariable),
598}
599
600#[derive(Copy, Clone, Debug)]
601pub struct Concurrency {
602 pub downloads: Option<NonZeroUsize>,
603 pub builds: Option<NonZeroUsize>,
604 pub installs: Option<NonZeroUsize>,
605}
606
607#[derive(Debug, Clone, Copy)]
611pub struct EnvFlag {
612 pub value: Option<bool>,
613 pub env_var: &'static str,
614}
615
616impl EnvFlag {
617 pub fn new(env_var: &'static str) -> Result<Self, Error> {
619 Ok(Self {
620 value: parse_boolish_environment_variable(env_var)?,
621 env_var,
622 })
623 }
624}
625
626#[derive(Debug, Clone)]
631pub struct EnvironmentOptions {
632 pub skip_wheel_filename_check: Option<bool>,
633 pub hide_build_output: Option<bool>,
634 pub python_install_bin: Option<bool>,
635 pub python_install_registry: Option<bool>,
636 pub install_mirrors: PythonInstallMirrors,
637 pub log_context: Option<bool>,
638 pub lfs: Option<bool>,
639 pub http_connect_timeout: Duration,
640 pub http_read_timeout: Duration,
641 pub http_read_timeout_upload: Duration,
644 pub http_retries: u32,
645 pub concurrency: Concurrency,
646 #[cfg(feature = "tracing-durations-export")]
647 pub tracing_durations_file: Option<PathBuf>,
648 pub frozen: EnvFlag,
649 pub locked: EnvFlag,
650 pub offline: EnvFlag,
651 pub no_sync: EnvFlag,
652 pub managed_python: EnvFlag,
653 pub no_managed_python: EnvFlag,
654 pub native_tls: EnvFlag,
655 pub preview: EnvFlag,
656 pub isolated: EnvFlag,
657 pub no_progress: EnvFlag,
658 pub no_installer_metadata: EnvFlag,
659 pub dev: EnvFlag,
660 pub no_dev: EnvFlag,
661 pub show_resolution: EnvFlag,
662 pub no_editable: EnvFlag,
663 pub no_env_file: EnvFlag,
664 pub venv_seed: EnvFlag,
665 pub venv_clear: EnvFlag,
666 pub venv_relocatable: EnvFlag,
667 pub init_bare: EnvFlag,
668}
669
670impl EnvironmentOptions {
671 pub fn new() -> Result<Self, Error> {
673 let http_read_timeout = parse_integer_environment_variable(
676 EnvVars::UV_HTTP_TIMEOUT,
677 Some("value should be an integer number of seconds"),
678 )?
679 .or(parse_integer_environment_variable(
680 EnvVars::UV_REQUEST_TIMEOUT,
681 Some("value should be an integer number of seconds"),
682 )?)
683 .or(parse_integer_environment_variable(
684 EnvVars::HTTP_TIMEOUT,
685 Some("value should be an integer number of seconds"),
686 )?)
687 .map(Duration::from_secs);
688
689 Ok(Self {
690 skip_wheel_filename_check: parse_boolish_environment_variable(
691 EnvVars::UV_SKIP_WHEEL_FILENAME_CHECK,
692 )?,
693 hide_build_output: parse_boolish_environment_variable(EnvVars::UV_HIDE_BUILD_OUTPUT)?,
694 python_install_bin: parse_boolish_environment_variable(EnvVars::UV_PYTHON_INSTALL_BIN)?,
695 python_install_registry: parse_boolish_environment_variable(
696 EnvVars::UV_PYTHON_INSTALL_REGISTRY,
697 )?,
698 concurrency: Concurrency {
699 downloads: parse_integer_environment_variable(
700 EnvVars::UV_CONCURRENT_DOWNLOADS,
701 None,
702 )?,
703 builds: parse_integer_environment_variable(EnvVars::UV_CONCURRENT_BUILDS, None)?,
704 installs: parse_integer_environment_variable(
705 EnvVars::UV_CONCURRENT_INSTALLS,
706 None,
707 )?,
708 },
709 install_mirrors: PythonInstallMirrors {
710 python_install_mirror: parse_string_environment_variable(
711 EnvVars::UV_PYTHON_INSTALL_MIRROR,
712 )?,
713 pypy_install_mirror: parse_string_environment_variable(
714 EnvVars::UV_PYPY_INSTALL_MIRROR,
715 )?,
716 python_downloads_json_url: parse_string_environment_variable(
717 EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL,
718 )?,
719 },
720 log_context: parse_boolish_environment_variable(EnvVars::UV_LOG_CONTEXT)?,
721 lfs: parse_boolish_environment_variable(EnvVars::UV_GIT_LFS)?,
722 http_read_timeout_upload: parse_integer_environment_variable(
723 EnvVars::UV_UPLOAD_HTTP_TIMEOUT,
724 Some("value should be an integer number of seconds"),
725 )?
726 .map(Duration::from_secs)
727 .or(http_read_timeout)
728 .unwrap_or(DEFAULT_READ_TIMEOUT_UPLOAD),
729 http_read_timeout: http_read_timeout.unwrap_or(DEFAULT_READ_TIMEOUT),
730 http_connect_timeout: parse_integer_environment_variable(
731 EnvVars::UV_HTTP_CONNECT_TIMEOUT,
732 Some("value should be an integer number of seconds"),
733 )?
734 .map(Duration::from_secs)
735 .unwrap_or(DEFAULT_CONNECT_TIMEOUT),
736 http_retries: parse_integer_environment_variable(EnvVars::UV_HTTP_RETRIES, None)?
737 .unwrap_or(uv_client::DEFAULT_RETRIES),
738 #[cfg(feature = "tracing-durations-export")]
739 tracing_durations_file: parse_path_environment_variable(
740 EnvVars::TRACING_DURATIONS_FILE,
741 ),
742 frozen: EnvFlag::new(EnvVars::UV_FROZEN)?,
743 locked: EnvFlag::new(EnvVars::UV_LOCKED)?,
744 offline: EnvFlag::new(EnvVars::UV_OFFLINE)?,
745 no_sync: EnvFlag::new(EnvVars::UV_NO_SYNC)?,
746 managed_python: EnvFlag::new(EnvVars::UV_MANAGED_PYTHON)?,
747 no_managed_python: EnvFlag::new(EnvVars::UV_NO_MANAGED_PYTHON)?,
748 native_tls: EnvFlag::new(EnvVars::UV_NATIVE_TLS)?,
749 preview: EnvFlag::new(EnvVars::UV_PREVIEW)?,
750 isolated: EnvFlag::new(EnvVars::UV_ISOLATED)?,
751 no_progress: EnvFlag::new(EnvVars::UV_NO_PROGRESS)?,
752 no_installer_metadata: EnvFlag::new(EnvVars::UV_NO_INSTALLER_METADATA)?,
753 dev: EnvFlag::new(EnvVars::UV_DEV)?,
754 no_dev: EnvFlag::new(EnvVars::UV_NO_DEV)?,
755 show_resolution: EnvFlag::new(EnvVars::UV_SHOW_RESOLUTION)?,
756 no_editable: EnvFlag::new(EnvVars::UV_NO_EDITABLE)?,
757 no_env_file: EnvFlag::new(EnvVars::UV_NO_ENV_FILE)?,
758 venv_seed: EnvFlag::new(EnvVars::UV_VENV_SEED)?,
759 venv_clear: EnvFlag::new(EnvVars::UV_VENV_CLEAR)?,
760 venv_relocatable: EnvFlag::new(EnvVars::UV_VENV_RELOCATABLE)?,
761 init_bare: EnvFlag::new(EnvVars::UV_INIT_BARE)?,
762 })
763 }
764}
765
766fn parse_string_environment_variable(name: &'static str) -> Result<Option<String>, Error> {
768 match std::env::var(name) {
769 Ok(v) => {
770 if v.is_empty() {
771 Ok(None)
772 } else {
773 Ok(Some(v))
774 }
775 }
776 Err(e) => match e {
777 std::env::VarError::NotPresent => Ok(None),
778 std::env::VarError::NotUnicode(err) => Err(Error::InvalidEnvironmentVariable(
779 InvalidEnvironmentVariable {
780 name: name.to_string(),
781 value: err.to_string_lossy().to_string(),
782 err: "expected a valid UTF-8 string".to_string(),
783 },
784 )),
785 },
786 }
787}
788
789fn parse_integer_environment_variable<T>(
790 name: &'static str,
791 help: Option<&str>,
792) -> Result<Option<T>, Error>
793where
794 T: std::str::FromStr + Copy,
795 <T as std::str::FromStr>::Err: std::fmt::Display,
796{
797 let value = match std::env::var(name) {
798 Ok(v) => v,
799 Err(e) => {
800 return match e {
801 std::env::VarError::NotPresent => Ok(None),
802 std::env::VarError::NotUnicode(err) => Err(Error::InvalidEnvironmentVariable(
803 InvalidEnvironmentVariable {
804 name: name.to_string(),
805 value: err.to_string_lossy().to_string(),
806 err: "expected a valid UTF-8 string".to_string(),
807 },
808 )),
809 };
810 }
811 };
812 if value.is_empty() {
813 return Ok(None);
814 }
815
816 match value.parse::<T>() {
817 Ok(v) => Ok(Some(v)),
818 Err(err) => Err(Error::InvalidEnvironmentVariable(
819 InvalidEnvironmentVariable {
820 name: name.to_string(),
821 value,
822 err: if let Some(help) = help {
823 format!("{err}; {help}")
824 } else {
825 err.to_string()
826 },
827 },
828 )),
829 }
830}
831
832#[cfg(feature = "tracing-durations-export")]
833fn parse_path_environment_variable(name: &'static str) -> Option<PathBuf> {
835 let value = std::env::var_os(name)?;
836
837 if value.is_empty() {
838 return None;
839 }
840
841 Some(PathBuf::from(value))
842}
843
844impl From<&EnvironmentOptions> for EnvironmentFlags {
846 fn from(options: &EnvironmentOptions) -> Self {
847 let mut flags = Self::empty();
848 if options.skip_wheel_filename_check == Some(true) {
849 flags.insert(Self::SKIP_WHEEL_FILENAME_CHECK);
850 }
851 if options.hide_build_output == Some(true) {
852 flags.insert(Self::HIDE_BUILD_OUTPUT);
853 }
854 flags
855 }
856}