Skip to content

Commit

Permalink
feat: add flox envs command (#1264)
Browse files Browse the repository at this point in the history
This PR adds an environment registry and `flox envs` command.

Flox commands has so far been mostly stateless, i.e. the fact that an
environment exists at a given path is "forgotten" as soon as the command
completes.
`flox envs` is supposed to list both active and _available/inactive_
environments (that are represented by .flox directories).
To avoid _scanning_ for `.flox` directories, we use the env registry
introduced in #1312.

In addition, the `flox envs` supports filtering the output to only
active environments as well as formatting its output as JSON.
  • Loading branch information
ysndr committed Apr 23, 2024
1 parent 2e3102c commit 251c239
Show file tree
Hide file tree
Showing 17 changed files with 361 additions and 83 deletions.
39 changes: 39 additions & 0 deletions cli/flox-rust-sdk/src/data/canonical_path.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use std::path::{Path, PathBuf};

use serde::Serialize;
use thiserror::Error;

/// A path that is guaranteed to be canonicalized
///
/// [`ManagedEnvironment`] uses this to refer to the path of its `.flox` directory.
/// [`ManagedEnvironment::encode`] is used to uniquely identify the environment
/// by encoding the canonicalized path.
/// This encoding is used to create a unique branch name in the floxmeta repository.
/// Thus, rather than canonicalizing the path every time we need to encode it,
/// we store the path as a [`CanonicalPath`].
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, derive_more::Deref, derive_more::AsRef)]
#[deref(forward)]
#[as_ref(forward)]
pub struct CanonicalPath(PathBuf);

#[derive(Debug, Error)]
#[error("couldn't canonicalize path {path:?}: {err}")]
pub struct CanonicalizeError {
pub path: PathBuf,
#[source]
pub err: std::io::Error,
}

impl CanonicalPath {
pub fn new(path: impl AsRef<Path>) -> Result<Self, CanonicalizeError> {
let canonicalized = std::fs::canonicalize(&path).map_err(|e| CanonicalizeError {
path: path.as_ref().to_path_buf(),
err: e,
})?;
Ok(Self(canonicalized))
}

pub fn into_path_buf(self) -> PathBuf {
self.0
}
}
2 changes: 2 additions & 0 deletions cli/flox-rust-sdk/src/data/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
mod canonical_path;
mod version;

pub use canonical_path::{CanonicalPath, CanonicalizeError};
pub use version::Version;
pub type System = String;
2 changes: 1 addition & 1 deletion cli/flox-rust-sdk/src/data/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt::Debug;
use serde::{Deserialize, Serialize};
use thiserror::Error;

#[derive(Clone, PartialEq)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct Version<const V: u8>;

Expand Down
4 changes: 2 additions & 2 deletions cli/flox-rust-sdk/src/models/env_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use fslock::LockFile;
use serde::{Deserialize, Serialize};
use tracing::debug;

use super::environment::{path_hash, CanonicalPath, EnvironmentPointer};
use crate::data::Version;
use super::environment::{path_hash, EnvironmentPointer};
use crate::data::{CanonicalPath, Version};
use crate::flox::Flox;
use crate::utils::traceable_path;

Expand Down
3 changes: 2 additions & 1 deletion cli/flox-rust-sdk/src/models/environment/core_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ use super::{
LOCKFILE_FILENAME,
MANIFEST_FILENAME,
};
use crate::data::CanonicalPath;
use crate::flox::Flox;
use crate::models::container_builder::ContainerBuilder;
use crate::models::environment::{call_pkgdb, global_manifest_path, CanonicalPath};
use crate::models::environment::{call_pkgdb, global_manifest_path};
use crate::models::lockfile::{LockedManifest, LockedManifestError};
use crate::models::manifest::{
insert_packages,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use super::path_environment::PathEnvironment;
use super::{
gcroots_dir,
path_hash,
CanonicalPath,
CanonicalizeError,
CoreEnvironmentError,
EditResult,
Expand All @@ -26,7 +25,7 @@ use super::{
ENVIRONMENT_POINTER_FILENAME,
N_HASH_CHARS,
};
use crate::data::Version;
use crate::data::{CanonicalPath, Version};
use crate::flox::{EnvironmentRef, Flox};
use crate::models::container_builder::ContainerBuilder;
use crate::models::env_registry::{
Expand Down Expand Up @@ -1875,7 +1874,7 @@ mod test {
floxmeta,
&flox,
test_pointer,
CanonicalPath(dot_flox_path),
CanonicalPath::new(dot_flox_path).unwrap(),
flox.temp_dir.join("out_link"),
)
.unwrap();
Expand Down Expand Up @@ -1915,7 +1914,7 @@ mod test {
floxmeta,
&flox,
test_pointer,
CanonicalPath(dot_flox_path),
CanonicalPath::new(dot_flox_path).unwrap(),
flox.temp_dir.join("out_link"),
)
.unwrap();
Expand Down
58 changes: 9 additions & 49 deletions cli/flox-rust-sdk/src/models/environment/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::environment_ref::{EnvironmentName, EnvironmentOwner};
use super::lockfile::LockedManifest;
use super::manifest::PackageToInstall;
use super::pkgdb::UpgradeResult;
use crate::data::Version;
use crate::data::{CanonicalPath, CanonicalizeError, Version};
use crate::flox::{Flox, Floxhub};
use crate::models::pkgdb::call_pkgdb;
use crate::providers::git::{
Expand Down Expand Up @@ -74,45 +74,6 @@ pub struct UpdateResult {
pub store_path: Option<PathBuf>,
}

/// A path that is guaranteed to be canonicalized
///
/// [`ManagedEnvironment`] uses this to refer to the path of its `.flox` directory.
/// [`ManagedEnvironment::encode`] is used to uniquely identify the environment
/// by encoding the canonicalized path.
/// This encoding is used to create a unique branch name in the floxmeta repository.
/// Thus, rather than canonicalizing the path every time we need to encode it,
/// we store the path as a [`CanonicalPath`].
#[derive(Debug, Clone, derive_more::Deref, derive_more::AsRef)]
#[deref(forward)]
#[as_ref(forward)]
pub struct CanonicalPath(PathBuf);

#[derive(Debug, Error)]
#[error("couldn't canonicalize path {path:?}: {err}")]
pub struct CanonicalizeError {
pub path: PathBuf,
#[source]
pub err: std::io::Error,
}

impl CanonicalPath {
pub fn new(path: impl AsRef<Path>) -> Result<Self, CanonicalizeError> {
let canonicalized = std::fs::canonicalize(&path).map_err(|e| CanonicalizeError {
path: path.as_ref().to_path_buf(),
err: e,
})?;
Ok(Self(canonicalized))
}

pub fn path(&self) -> PathBuf {
self.0.clone()
}

pub fn into_path_buf(self) -> PathBuf {
self.0
}
}

/// The result of an installation attempt that contains the new manifest contents
/// along with whether each package was already installed
#[derive(Debug)]
Expand Down Expand Up @@ -237,7 +198,9 @@ pub trait Environment: Send {
/// A pointer to an environment, either managed or path.
/// This is used to determine the type of an environment at a given path.
/// See [EnvironmentPointer::open].
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, derive_more::From)]
#[derive(
Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, derive_more::From,
)]
#[serde(untagged)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub enum EnvironmentPointer {
Expand All @@ -250,7 +213,7 @@ pub enum EnvironmentPointer {
/// The identifier for a project environment.
///
/// This is serialized to `env.json` inside the `.flox` directory
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct PathPointer {
pub name: EnvironmentName,
Expand All @@ -271,7 +234,7 @@ impl PathPointer {
/// points to an environment owner and the name of the environment.
///
/// This is serialized to an `env.json` inside the `.flox` directory.
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
#[derive(Debug, Serialize, Clone, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
pub struct ManagedPointer {
pub owner: EnvironmentOwner,
Expand Down Expand Up @@ -358,7 +321,7 @@ impl EnvironmentPointer {
/// However, this type does not perform any validation of the referenced environment.
/// Opening the environment with [ManagedEnvironment::open] or
/// [PathEnvironment::open], could still fail.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
pub struct DotFlox {
pub path: PathBuf,
pub pointer: EnvironmentPointer,
Expand Down Expand Up @@ -474,11 +437,8 @@ pub enum EnvironmentError {
#[error("could not get current directory")]
GetCurrentDir(#[source] std::io::Error),

#[error("failed to get canonical path for '.flox' directory: {path}")]
CanonicalDotFlox {
err: CanonicalizeError,
path: PathBuf,
},
#[error("failed to get canonical path for '.flox' directory")]
CanonicalDotFlox(#[source] CanonicalizeError),

#[error("failed to access the environment registry")]
Registry(#[from] EnvRegistryError),
Expand Down
9 changes: 3 additions & 6 deletions cli/flox-rust-sdk/src/models/environment/path_environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use log::debug;

use super::core_environment::CoreEnvironment;
use super::{
CanonicalPath,
CanonicalizeError,
EditResult,
Environment,
Expand All @@ -39,7 +38,7 @@ use super::{
GCROOTS_DIR_NAME,
LOCKFILE_FILENAME,
};
use crate::data::System;
use crate::data::{CanonicalPath, System};
use crate::flox::Flox;
use crate::models::container_builder::ContainerBuilder;
use crate::models::env_registry::{deregister, ensure_registered};
Expand Down Expand Up @@ -365,10 +364,8 @@ impl PathEnvironment {
}

let canonical_dot_flox =
CanonicalPath::new(&dot_flox_path).map_err(|e| EnvironmentError::CanonicalDotFlox {
err: e,
path: dot_flox_path.as_ref().to_path_buf(),
})?;
CanonicalPath::new(&dot_flox_path).map_err(EnvironmentError::CanonicalDotFlox)?;

ensure_registered(
flox,
&canonical_dot_flox,
Expand Down
25 changes: 23 additions & 2 deletions cli/flox-rust-sdk/src/models/environment_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@ pub static DEFAULT_NAME: &str = "default";
pub static DEFAULT_OWNER: &str = "local";

#[derive(
Debug, Clone, PartialEq, Eq, Hash, AsRef, Deref, Display, DeserializeFromStr, SerializeDisplay,
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Deref,
Display,
DeserializeFromStr,
SerializeDisplay,
)]
pub struct EnvironmentOwner(String);

Expand All @@ -28,7 +39,17 @@ impl FromStr for EnvironmentOwner {
}

#[derive(
Debug, Clone, PartialEq, Eq, Hash, AsRef, Display, DeserializeFromStr, SerializeDisplay,
Debug,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
AsRef,
Display,
DeserializeFromStr,
SerializeDisplay,
)]
pub struct EnvironmentName(String);

Expand Down
10 changes: 3 additions & 7 deletions cli/flox-rust-sdk/src/models/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@ use log::debug;
use thiserror::Error;

use super::container_builder::ContainerBuilder;
use super::environment::{CanonicalizeError, UpdateResult};
use super::environment::UpdateResult;
use super::pkgdb::CallPkgDbError;
use crate::data::{System, Version};
use crate::data::{CanonicalPath, CanonicalizeError, System, Version};
use crate::flox::Flox;
use crate::models::environment::{
global_manifest_lockfile_path,
global_manifest_path,
CanonicalPath,
};
use crate::models::environment::{global_manifest_lockfile_path, global_manifest_path};
use crate::models::pkgdb::{call_pkgdb, BuildEnvResult, PKGDB_BIN};
use crate::utils::CommandExt;

Expand Down
51 changes: 51 additions & 0 deletions cli/flox/doc/flox-envs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: FLOX-ENVS
section: 1
header: "Flox User Manuals"
...

# NAME

flox-envs - show active and available environments

# SYNOPSIS

```
flox [<general options>] envs
[--active]
[--json]
```

# DESCRIPTION

This command can be used to list available environments on the local machine.
When one or more environments are active,
the last activated environment will be listed first and printed in **bold**.

Whenever an environment is used with any `flox` command
it is registered to a user specific global registry.
`flox envs` will list all environments known to it through the registry.
Environments that are present on the local system may not show up
until they are used the first time.
Similarly, if an environment is changed
(e.g. deleted and replaced by an environment with different metadata),
the change may not show until the new environment is used.

# OPTIONS

## Edit Options

`--active`
: Show only active environments

`--json`
: Format the output as JSON

```{.include}
./include/general-options.md
```

# SEE ALSO
[`flox-init(1)`](./flox-init.md),
[`flox-pull(1)`](./flox-pull.md),
[`flox-activate(1)`](./flox-activate.md)
Loading

0 comments on commit 251c239

Please sign in to comment.