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
300#[allow(deprecated)]
304fn warn_uv_toml_masked_fields(options: &Options) {
305 let Options {
306 globals:
307 GlobalOptions {
308 required_version,
309 system_certs,
310 native_tls,
311 offline,
312 no_cache,
313 cache_dir,
314 preview,
315 python_preference,
316 python_downloads,
317 concurrent_downloads,
318 concurrent_builds,
319 concurrent_installs,
320 allow_insecure_host,
321 http_proxy,
322 https_proxy,
323 no_proxy,
324 },
325 top_level:
326 ResolverInstallerSchema {
327 index,
328 index_url,
329 extra_index_url,
330 no_index,
331 find_links,
332 index_strategy,
333 keyring_provider,
334 resolution,
335 prerelease,
336 fork_strategy,
337 dependency_metadata,
338 config_settings,
339 config_settings_package,
340 no_build_isolation,
341 no_build_isolation_package,
342 extra_build_dependencies,
343 extra_build_variables,
344 exclude_newer,
345 exclude_newer_package,
346 link_mode,
347 compile_bytecode,
348 no_sources,
349 no_sources_package: _,
350 upgrade,
351 upgrade_package,
352 reinstall,
353 reinstall_package,
354 no_build,
355 no_build_package,
356 no_binary,
357 no_binary_package,
358 torch_backend,
359 },
360 install_mirrors:
361 PythonInstallMirrors {
362 python_install_mirror,
363 pypy_install_mirror,
364 python_downloads_json_url,
365 },
366 publish:
367 PublishOptions {
368 publish_url,
369 trusted_publishing,
370 check_url,
371 },
372 add: AddOptions { add_bounds },
373 pip,
374 cache_keys,
375 override_dependencies,
376 exclude_dependencies,
377 constraint_dependencies,
378 build_constraint_dependencies,
379 environments: _,
380 required_environments: _,
381 conflicts: _,
382 workspace: _,
383 sources: _,
384 dev_dependencies: _,
385 default_groups: _,
386 dependency_groups: _,
387 managed: _,
388 package: _,
389 build_backend: _,
390 } = options;
391
392 let mut masked_fields = vec![];
393
394 if required_version.is_some() {
395 masked_fields.push("required-version");
396 }
397 if system_certs.is_some() {
398 masked_fields.push("system-certs");
399 }
400 if native_tls.is_some() {
401 masked_fields.push("native-tls");
402 }
403 if offline.is_some() {
404 masked_fields.push("offline");
405 }
406 if no_cache.is_some() {
407 masked_fields.push("no-cache");
408 }
409 if cache_dir.is_some() {
410 masked_fields.push("cache-dir");
411 }
412 if preview.is_some() {
413 masked_fields.push("preview");
414 }
415 if python_preference.is_some() {
416 masked_fields.push("python-preference");
417 }
418 if python_downloads.is_some() {
419 masked_fields.push("python-downloads");
420 }
421 if concurrent_downloads.is_some() {
422 masked_fields.push("concurrent-downloads");
423 }
424 if concurrent_builds.is_some() {
425 masked_fields.push("concurrent-builds");
426 }
427 if concurrent_installs.is_some() {
428 masked_fields.push("concurrent-installs");
429 }
430 if allow_insecure_host.is_some() {
431 masked_fields.push("allow-insecure-host");
432 }
433 if http_proxy.is_some() {
434 masked_fields.push("http-proxy");
435 }
436 if https_proxy.is_some() {
437 masked_fields.push("https-proxy");
438 }
439 if no_proxy.is_some() {
440 masked_fields.push("no-proxy");
441 }
442 if index.is_some() {
443 masked_fields.push("index");
444 }
445 if index_url.is_some() {
446 masked_fields.push("index-url");
447 }
448 if extra_index_url.is_some() {
449 masked_fields.push("extra-index-url");
450 }
451 if no_index.is_some() {
452 masked_fields.push("no-index");
453 }
454 if find_links.is_some() {
455 masked_fields.push("find-links");
456 }
457 if index_strategy.is_some() {
458 masked_fields.push("index-strategy");
459 }
460 if keyring_provider.is_some() {
461 masked_fields.push("keyring-provider");
462 }
463 if resolution.is_some() {
464 masked_fields.push("resolution");
465 }
466 if prerelease.is_some() {
467 masked_fields.push("prerelease");
468 }
469 if fork_strategy.is_some() {
470 masked_fields.push("fork-strategy");
471 }
472 if dependency_metadata.is_some() {
473 masked_fields.push("dependency-metadata");
474 }
475 if config_settings.is_some() {
476 masked_fields.push("config-settings");
477 }
478 if config_settings_package.is_some() {
479 masked_fields.push("config-settings-package");
480 }
481 if no_build_isolation.is_some() {
482 masked_fields.push("no-build-isolation");
483 }
484 if no_build_isolation_package.is_some() {
485 masked_fields.push("no-build-isolation-package");
486 }
487 if extra_build_dependencies.is_some() {
488 masked_fields.push("extra-build-dependencies");
489 }
490 if extra_build_variables.is_some() {
491 masked_fields.push("extra-build-variables");
492 }
493 if exclude_newer.is_some() {
494 masked_fields.push("exclude-newer");
495 }
496 if exclude_newer_package.is_some() {
497 masked_fields.push("exclude-newer-package");
498 }
499 if link_mode.is_some() {
500 masked_fields.push("link-mode");
501 }
502 if compile_bytecode.is_some() {
503 masked_fields.push("compile-bytecode");
504 }
505 if no_sources.is_some() {
506 masked_fields.push("no-sources");
507 }
508 if upgrade.is_some() {
509 masked_fields.push("upgrade");
510 }
511 if upgrade_package.is_some() {
512 masked_fields.push("upgrade-package");
513 }
514 if reinstall.is_some() {
515 masked_fields.push("reinstall");
516 }
517 if reinstall_package.is_some() {
518 masked_fields.push("reinstall-package");
519 }
520 if no_build.is_some() {
521 masked_fields.push("no-build");
522 }
523 if no_build_package.is_some() {
524 masked_fields.push("no-build-package");
525 }
526 if no_binary.is_some() {
527 masked_fields.push("no-binary");
528 }
529 if no_binary_package.is_some() {
530 masked_fields.push("no-binary-package");
531 }
532 if torch_backend.is_some() {
533 masked_fields.push("torch-backend");
534 }
535 if python_install_mirror.is_some() {
536 masked_fields.push("python-install-mirror");
537 }
538 if pypy_install_mirror.is_some() {
539 masked_fields.push("pypy-install-mirror");
540 }
541 if python_downloads_json_url.is_some() {
542 masked_fields.push("python-downloads-json-url");
543 }
544 if publish_url.is_some() {
545 masked_fields.push("publish-url");
546 }
547 if trusted_publishing.is_some() {
548 masked_fields.push("trusted-publishing");
549 }
550 if check_url.is_some() {
551 masked_fields.push("check-url");
552 }
553 if add_bounds.is_some() {
554 masked_fields.push("add-bounds");
555 }
556 if pip.is_some() {
557 masked_fields.push("pip");
558 }
559 if cache_keys.is_some() {
560 masked_fields.push("cache_keys");
561 }
562 if override_dependencies.is_some() {
563 masked_fields.push("override-dependencies");
564 }
565 if exclude_dependencies.is_some() {
566 masked_fields.push("exclude-dependencies");
567 }
568 if constraint_dependencies.is_some() {
569 masked_fields.push("constraint-dependencies");
570 }
571 if build_constraint_dependencies.is_some() {
572 masked_fields.push("build-constraint-dependencies");
573 }
574 if !masked_fields.is_empty() {
575 let field_listing = masked_fields.join("\n- ");
576 warn_user!(
577 "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- {}",
578 field_listing,
579 );
580 }
581}
582
583#[derive(thiserror::Error, Debug)]
584pub enum Error {
585 #[error(transparent)]
586 Io(#[from] std::io::Error),
587
588 #[error(transparent)]
589 Index(#[from] uv_distribution_types::IndexUrlError),
590
591 #[error("Failed to parse: `{}`", _0.user_display())]
592 PyprojectToml(PathBuf, #[source] Box<toml::de::Error>),
593
594 #[error("Failed to parse: `{}`", _0.user_display())]
595 UvToml(PathBuf, #[source] Box<toml::de::Error>),
596
597 #[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
598 )]
599 PyprojectOnlyField(PathBuf, &'static str),
600
601 #[error(transparent)]
602 InvalidEnvironmentVariable(#[from] InvalidEnvironmentVariable),
603}
604
605#[derive(Copy, Clone, Debug)]
606pub struct Concurrency {
607 pub downloads: Option<NonZeroUsize>,
608 pub builds: Option<NonZeroUsize>,
609 pub installs: Option<NonZeroUsize>,
610}
611
612#[derive(Debug, Clone, Copy)]
616pub struct EnvFlag {
617 pub value: Option<bool>,
618 pub env_var: &'static str,
619}
620
621impl EnvFlag {
622 pub fn new(env_var: &'static str) -> Result<Self, Error> {
624 Ok(Self {
625 value: parse_boolish_environment_variable(env_var)?,
626 env_var,
627 })
628 }
629}
630
631#[derive(Debug, Clone)]
636pub struct EnvironmentOptions {
637 pub skip_wheel_filename_check: Option<bool>,
638 pub hide_build_output: Option<bool>,
639 pub python_install_bin: Option<bool>,
640 pub python_install_registry: Option<bool>,
641 pub install_mirrors: PythonInstallMirrors,
642 pub log_context: Option<bool>,
643 pub lfs: Option<bool>,
644 pub http_connect_timeout: Duration,
645 pub http_read_timeout: Duration,
646 pub http_read_timeout_upload: Duration,
649 pub http_retries: u32,
650 pub concurrency: Concurrency,
651 #[cfg(feature = "tracing-durations-export")]
652 pub tracing_durations_file: Option<PathBuf>,
653 pub frozen: EnvFlag,
654 pub locked: EnvFlag,
655 pub offline: EnvFlag,
656 pub no_sync: EnvFlag,
657 pub managed_python: EnvFlag,
658 pub no_managed_python: EnvFlag,
659 pub native_tls: EnvFlag,
660 pub system_certs: EnvFlag,
661 pub preview: EnvFlag,
662 pub isolated: EnvFlag,
663 pub no_progress: EnvFlag,
664 pub no_installer_metadata: EnvFlag,
665 pub dev: EnvFlag,
666 pub no_dev: EnvFlag,
667 pub show_resolution: EnvFlag,
668 pub no_editable: EnvFlag,
669 pub no_env_file: EnvFlag,
670 pub venv_seed: EnvFlag,
671 pub venv_clear: EnvFlag,
672 pub venv_relocatable: EnvFlag,
673 pub init_bare: EnvFlag,
674}
675
676impl EnvironmentOptions {
677 pub fn new() -> Result<Self, Error> {
679 let http_read_timeout = parse_integer_environment_variable(
682 EnvVars::UV_HTTP_TIMEOUT,
683 Some("value should be an integer number of seconds"),
684 )?
685 .or(parse_integer_environment_variable(
686 EnvVars::UV_REQUEST_TIMEOUT,
687 Some("value should be an integer number of seconds"),
688 )?)
689 .or(parse_integer_environment_variable(
690 EnvVars::HTTP_TIMEOUT,
691 Some("value should be an integer number of seconds"),
692 )?)
693 .map(Duration::from_secs);
694
695 Ok(Self {
696 skip_wheel_filename_check: parse_boolish_environment_variable(
697 EnvVars::UV_SKIP_WHEEL_FILENAME_CHECK,
698 )?,
699 hide_build_output: parse_boolish_environment_variable(EnvVars::UV_HIDE_BUILD_OUTPUT)?,
700 python_install_bin: parse_boolish_environment_variable(EnvVars::UV_PYTHON_INSTALL_BIN)?,
701 python_install_registry: parse_boolish_environment_variable(
702 EnvVars::UV_PYTHON_INSTALL_REGISTRY,
703 )?,
704 concurrency: Concurrency {
705 downloads: parse_integer_environment_variable(
706 EnvVars::UV_CONCURRENT_DOWNLOADS,
707 None,
708 )?,
709 builds: parse_integer_environment_variable(EnvVars::UV_CONCURRENT_BUILDS, None)?,
710 installs: parse_integer_environment_variable(
711 EnvVars::UV_CONCURRENT_INSTALLS,
712 None,
713 )?,
714 },
715 install_mirrors: PythonInstallMirrors {
716 python_install_mirror: parse_string_environment_variable(
717 EnvVars::UV_PYTHON_INSTALL_MIRROR,
718 )?,
719 pypy_install_mirror: parse_string_environment_variable(
720 EnvVars::UV_PYPY_INSTALL_MIRROR,
721 )?,
722 python_downloads_json_url: parse_string_environment_variable(
723 EnvVars::UV_PYTHON_DOWNLOADS_JSON_URL,
724 )?,
725 },
726 log_context: parse_boolish_environment_variable(EnvVars::UV_LOG_CONTEXT)?,
727 lfs: parse_boolish_environment_variable(EnvVars::UV_GIT_LFS)?,
728 http_read_timeout_upload: parse_integer_environment_variable(
729 EnvVars::UV_UPLOAD_HTTP_TIMEOUT,
730 Some("value should be an integer number of seconds"),
731 )?
732 .map(Duration::from_secs)
733 .or(http_read_timeout)
734 .unwrap_or(DEFAULT_READ_TIMEOUT_UPLOAD),
735 http_read_timeout: http_read_timeout.unwrap_or(DEFAULT_READ_TIMEOUT),
736 http_connect_timeout: parse_integer_environment_variable(
737 EnvVars::UV_HTTP_CONNECT_TIMEOUT,
738 Some("value should be an integer number of seconds"),
739 )?
740 .map(Duration::from_secs)
741 .unwrap_or(DEFAULT_CONNECT_TIMEOUT),
742 http_retries: parse_integer_environment_variable(EnvVars::UV_HTTP_RETRIES, None)?
743 .unwrap_or(uv_client::DEFAULT_RETRIES),
744 #[cfg(feature = "tracing-durations-export")]
745 tracing_durations_file: parse_path_environment_variable(
746 EnvVars::TRACING_DURATIONS_FILE,
747 ),
748 frozen: EnvFlag::new(EnvVars::UV_FROZEN)?,
749 locked: EnvFlag::new(EnvVars::UV_LOCKED)?,
750 offline: EnvFlag::new(EnvVars::UV_OFFLINE)?,
751 no_sync: EnvFlag::new(EnvVars::UV_NO_SYNC)?,
752 managed_python: EnvFlag::new(EnvVars::UV_MANAGED_PYTHON)?,
753 no_managed_python: EnvFlag::new(EnvVars::UV_NO_MANAGED_PYTHON)?,
754 native_tls: EnvFlag::new(EnvVars::UV_NATIVE_TLS)?,
755 system_certs: EnvFlag::new(EnvVars::UV_SYSTEM_CERTS)?,
756 preview: EnvFlag::new(EnvVars::UV_PREVIEW)?,
757 isolated: EnvFlag::new(EnvVars::UV_ISOLATED)?,
758 no_progress: EnvFlag::new(EnvVars::UV_NO_PROGRESS)?,
759 no_installer_metadata: EnvFlag::new(EnvVars::UV_NO_INSTALLER_METADATA)?,
760 dev: EnvFlag::new(EnvVars::UV_DEV)?,
761 no_dev: EnvFlag::new(EnvVars::UV_NO_DEV)?,
762 show_resolution: EnvFlag::new(EnvVars::UV_SHOW_RESOLUTION)?,
763 no_editable: EnvFlag::new(EnvVars::UV_NO_EDITABLE)?,
764 no_env_file: EnvFlag::new(EnvVars::UV_NO_ENV_FILE)?,
765 venv_seed: EnvFlag::new(EnvVars::UV_VENV_SEED)?,
766 venv_clear: EnvFlag::new(EnvVars::UV_VENV_CLEAR)?,
767 venv_relocatable: EnvFlag::new(EnvVars::UV_VENV_RELOCATABLE)?,
768 init_bare: EnvFlag::new(EnvVars::UV_INIT_BARE)?,
769 })
770 }
771}
772
773fn parse_string_environment_variable(name: &'static str) -> Result<Option<String>, Error> {
775 match std::env::var(name) {
776 Ok(v) => {
777 if v.is_empty() {
778 Ok(None)
779 } else {
780 Ok(Some(v))
781 }
782 }
783 Err(e) => match e {
784 std::env::VarError::NotPresent => Ok(None),
785 std::env::VarError::NotUnicode(err) => Err(Error::InvalidEnvironmentVariable(
786 InvalidEnvironmentVariable {
787 name: name.to_string(),
788 value: err.to_string_lossy().to_string(),
789 err: "expected a valid UTF-8 string".to_string(),
790 },
791 )),
792 },
793 }
794}
795
796fn parse_integer_environment_variable<T>(
797 name: &'static str,
798 help: Option<&str>,
799) -> Result<Option<T>, Error>
800where
801 T: std::str::FromStr + Copy,
802 <T as std::str::FromStr>::Err: std::fmt::Display,
803{
804 let value = match std::env::var(name) {
805 Ok(v) => v,
806 Err(e) => {
807 return match e {
808 std::env::VarError::NotPresent => Ok(None),
809 std::env::VarError::NotUnicode(err) => Err(Error::InvalidEnvironmentVariable(
810 InvalidEnvironmentVariable {
811 name: name.to_string(),
812 value: err.to_string_lossy().to_string(),
813 err: "expected a valid UTF-8 string".to_string(),
814 },
815 )),
816 };
817 }
818 };
819 if value.is_empty() {
820 return Ok(None);
821 }
822
823 match value.parse::<T>() {
824 Ok(v) => Ok(Some(v)),
825 Err(err) => Err(Error::InvalidEnvironmentVariable(
826 InvalidEnvironmentVariable {
827 name: name.to_string(),
828 value,
829 err: if let Some(help) = help {
830 format!("{err}; {help}")
831 } else {
832 err.to_string()
833 },
834 },
835 )),
836 }
837}
838
839#[cfg(feature = "tracing-durations-export")]
840fn parse_path_environment_variable(name: &'static str) -> Option<PathBuf> {
842 let value = std::env::var_os(name)?;
843
844 if value.is_empty() {
845 return None;
846 }
847
848 Some(PathBuf::from(value))
849}
850
851impl From<&EnvironmentOptions> for EnvironmentFlags {
853 fn from(options: &EnvironmentOptions) -> Self {
854 let mut flags = Self::empty();
855 if options.skip_wheel_filename_check == Some(true) {
856 flags.insert(Self::SKIP_WHEEL_FILENAME_CHECK);
857 }
858 if options.hide_build_output == Some(true) {
859 flags.insert(Self::HIDE_BUILD_OUTPUT);
860 }
861 flags
862 }
863}