Skip to content

Commit

Permalink
Refactor ReleaseMetadata, PushContext
Browse files Browse the repository at this point in the history
Now they use some shared functionality to make it easier for getting rid
of ExecutionEnvironment::Fake.
  • Loading branch information
cole-h committed Sep 13, 2024
1 parent d90d45a commit 7b65ea3
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 215 deletions.
103 changes: 102 additions & 1 deletion src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
mod instrumentation;

use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::str::FromStr as _;

use color_eyre::eyre::{eyre, Context as _, Result};

use crate::git_context::GitContext;
use crate::push_context::ExecutionEnvironment;
use crate::{Visibility, DEFAULT_ROLLING_PREFIX};

#[derive(Debug, clap::Parser)]
#[clap(version)]
Expand Down Expand Up @@ -324,4 +331,98 @@ impl FlakeHubPushCli {
}
}
}

pub(crate) fn execution_environment(&self) -> ExecutionEnvironment {
if std::env::var("GITHUB_ACTION").ok().is_some() {
ExecutionEnvironment::GitHub
} else if std::env::var("GITLAB_CI").ok().is_some() {
ExecutionEnvironment::GitLab
} else if cli.dest_dir.0.is_some() {
ExecutionEnvironment::Fake
} else {
ExecutionEnvironment::LocalGitHub
}
}

pub(crate) fn visibility(&self) -> Result<Visibility> {
match (self.visibility_alt, self.visibility) {
(Some(v), _) => Ok(v),
(None, Some(v)) => Ok(v),
(None, None) => Err(color_eyre::eyre::eyre!(
"Could not determine the flake's desired visibility. Use `--visibility` to set this to one of the following: public, unlisted, private.",
)),
}
}

pub(crate) fn resolve_local_git_root(&self) -> Result<PathBuf> {
let maybe_git_root = match &self.git_root.0 {
Some(gr) => Ok(gr.to_owned()),
None => std::env::current_dir().map(PathBuf::from),
};

let local_git_root = maybe_git_root.wrap_err("Could not determine current `git_root`. Pass `--git-root` or set `FLAKEHUB_PUSH_GIT_ROOT`, or run `flakehub-push` with the git root as the current working directory")?;
let local_git_root = local_git_root
.canonicalize()
.wrap_err("Failed to canonicalize `--git-root` argument")?;

Ok(local_git_root)
}

pub(crate) fn subdir_from_git_root(&self, local_git_root: &Path) -> Result<PathBuf> {
let subdir =
if let Some(directory) = &self.directory.0 {
let absolute_directory = if directory.is_absolute() {
directory.clone()
} else {
local_git_root.join(directory)
};
let canonical_directory = absolute_directory
.canonicalize()
.wrap_err("Failed to canonicalize `--directory` argument")?;

PathBuf::from(canonical_directory.strip_prefix(local_git_root).wrap_err(
"Specified `--directory` was not a directory inside the `--git-root`",
)?)
} else {
PathBuf::new()
};

Ok(subdir)
}

pub(crate) fn release_version(&self, git_ctx: &GitContext) -> Result<String> {
let rolling_prefix_or_tag = match (self.rolling_minor.0.as_ref(), &self.tag.0) {
(Some(_), _) if !self.rolling => {
return Err(eyre!(
"You must enable `rolling` to upload a release with a specific `rolling-minor`."
));
}
(Some(minor), _) => format!("0.{minor}"),
(None, _) if self.rolling => DEFAULT_ROLLING_PREFIX.to_string(),
(None, Some(tag)) => {
let version_only = tag.strip_prefix('v').unwrap_or(tag);
// Ensure the version respects semver
semver::Version::from_str(version_only).wrap_err_with(|| eyre!("Failed to parse version `{tag}` as semver, see https://semver.org/ for specifications"))?;
tag.to_string()
}
(None, None) => {
return Err(eyre!("Could not determine tag or rolling minor version, `--tag`, `GITHUB_REF_NAME`, or `--rolling-minor` must be set"));
}
};

let Some(commit_count) = git_ctx.revision_info.commit_count else {
return Err(eyre!("Could not determine commit count, this is normally determined via the `--git-root` argument or via the GitHub API"));
};

let rolling_minor_with_postfix_or_tag = if self.rolling_minor.0.is_some() || self.rolling {
format!(
"{rolling_prefix_or_tag}.{}+rev-{}",
commit_count, git_ctx.revision_info.revision
)
} else {
rolling_prefix_or_tag.to_string() // This will always be the tag since `self.rolling_prefix` was empty.
};

Ok(rolling_minor_with_postfix_or_tag)
}
}
225 changes: 11 additions & 214 deletions src/push_context.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
use std::{
collections::HashSet,
path::{Path, PathBuf},
str::FromStr,
};

use color_eyre::eyre::{eyre, Context, Result};

use crate::{
build_http_client,
cli::FlakeHubPushCli,
flake_info, flakehub_auth_fake,
flakehub_client::Tarball,
git_context::GitContext,
github::graphql::{GithubGraphqlDataQuery, MAX_LABEL_LENGTH, MAX_NUM_TOTAL_LABELS},
release_metadata::ReleaseMetadata,
revision_info::RevisionInfo,
DEFAULT_ROLLING_PREFIX,
build_http_client, cli::FlakeHubPushCli, flakehub_auth_fake, flakehub_client::Tarball,
git_context::GitContext, github::graphql::GithubGraphqlDataQuery,
release_metadata::ReleaseMetadata, revision_info::RevisionInfo,
};

#[derive(Clone)]
Expand Down Expand Up @@ -49,15 +37,7 @@ impl PushContext {

let client = build_http_client().build()?;

let exec_env = if std::env::var("GITHUB_ACTION").ok().is_some() {
ExecutionEnvironment::GitHub
} else if std::env::var("GITLAB_CI").ok().is_some() {
ExecutionEnvironment::GitLab
} else if cli.dest_dir.0.is_some() {
ExecutionEnvironment::Fake
} else {
ExecutionEnvironment::LocalGitHub
};
let exec_env = cli.execution_environment();

match exec_env.clone() {
ExecutionEnvironment::GitHub => {
Expand All @@ -69,14 +49,6 @@ impl PushContext {
_ => {}
};

let visibility = match (cli.visibility_alt, cli.visibility) {
(Some(v), _) => v,
(None, Some(v)) => v,
(None, None) => return Err(eyre!(
"Could not determine the flake's desired visibility. Use `--visibility` to set this to one of the following: public, unlisted, private.",
)),
};

// STEP: determine and check 'repository' and 'upload_name'
// If the upload name is supplied by the user, ensure that it contains exactly
// one slash and no whitespace. Default to the repository name.
Expand All @@ -92,20 +64,12 @@ impl PushContext {
let (upload_name, project_owner, project_name) =
determine_names(&cli.name.0, repository, cli.disable_rename_subgroups)?;

let maybe_git_root = match &cli.git_root.0 {
Some(gr) => Ok(gr.to_owned()),
None => std::env::current_dir().map(PathBuf::from),
};
let local_git_root = maybe_git_root.wrap_err("Could not determine current `git_root`. Pass `--git-root` or set `FLAKEHUB_PUSH_GIT_ROOT`, or run `flakehub-push` with the git root as the current working directory")?;

let local_git_root = local_git_root
.canonicalize()
.wrap_err("Failed to canonicalize `--git-root` argument")?;
let local_git_root = cli.resolve_local_git_root()?;
let local_rev_info = RevisionInfo::from_git_root(&local_git_root)?;

// "cli" and "git_ctx" are the user/env supplied info, augmented with data we might have fetched from github/gitlab apis

let (auth_token, git_ctx) = match (exec_env.clone(), &cli.jwt_issuer_uri) {
let (auth_token, git_ctx) = match (&exec_env, &cli.jwt_issuer_uri) {
(ExecutionEnvironment::GitHub, None) => {
// GITHUB CI
let github_token = cli
Expand Down Expand Up @@ -190,184 +154,17 @@ impl PushContext {
}
};

// STEP: resolve/canonicalize "subdir"
let subdir = if let Some(directory) = &cli.directory.0 {
let absolute_directory = if directory.is_absolute() {
directory.clone()
} else {
local_git_root.join(directory)
};
let canonical_directory = absolute_directory
.canonicalize()
.wrap_err("Failed to canonicalize `--directory` argument")?;

Path::new(
canonical_directory
.strip_prefix(local_git_root.clone())
.wrap_err(
"Specified `--directory` was not a directory inside the `--git-root`",
)?,
)
.into()
} else {
PathBuf::new()
};

let rolling_prefix_or_tag = match (cli.rolling_minor.0.as_ref(), &cli.tag.0) {
(Some(_), _) if !cli.rolling => {
return Err(eyre!(
"You must enable `rolling` to upload a release with a specific `rolling-minor`."
));
}
(Some(minor), _) => format!("0.{minor}"),
(None, _) if cli.rolling => DEFAULT_ROLLING_PREFIX.to_string(),
(None, Some(tag)) => {
let version_only = tag.strip_prefix('v').unwrap_or(tag);
// Ensure the version respects semver
semver::Version::from_str(version_only).wrap_err_with(|| eyre!("Failed to parse version `{tag}` as semver, see https://semver.org/ for specifications"))?;
tag.to_string()
}
(None, None) => {
return Err(eyre!("Could not determine tag or rolling minor version, `--tag`, `GITHUB_REF_NAME`, or `--rolling-minor` must be set"));
}
};

// TODO(future): (FH-282): change this so commit_count is only set authoritatively, is an explicit error if not set, when rolling, for gitlab
let Some(commit_count) = git_ctx.revision_info.commit_count else {
return Err(eyre!("Could not determine commit count, this is normally determined via the `--git-root` argument or via the GitHub API"));
};

let rolling_minor_with_postfix_or_tag = if cli.rolling_minor.0.is_some() || cli.rolling {
format!(
"{rolling_prefix_or_tag}.{}+rev-{}",
commit_count, git_ctx.revision_info.revision
)
} else {
rolling_prefix_or_tag.to_string() // This will always be the tag since `self.rolling_prefix` was empty.
};

// STEP: calculate labels
let merged_labels = {
let mut labels: HashSet<_> = cli
.extra_labels
.clone()
.into_iter()
.filter(|v| !v.is_empty())
.collect();
let extra_tags: HashSet<_> = cli
.extra_tags
.clone()
.into_iter()
.filter(|v| !v.is_empty())
.collect();

if !extra_tags.is_empty() {
let message = "`extra-tags` is deprecated and will be removed in the future. Please use `extra-labels` instead.";
tracing::warn!("{message}");

if matches!(&exec_env, ExecutionEnvironment::GitHub) {
println!("::warning::{message}");
}

if labels.is_empty() {
labels = extra_tags;
} else {
let message =
"Both `extra-tags` and `extra-labels` were set; `extra-tags` will be ignored.";
tracing::warn!("{message}");

if matches!(exec_env, ExecutionEnvironment::GitHub) {
println!("::warning::{message}");
}
}
}

// Get the "topic" labels from git_ctx, extend local mut labels
let topics = git_ctx.repo_topics;
labels = labels
.into_iter()
.chain(topics.iter().cloned())
.collect::<HashSet<String>>();

// Here we merge explicitly user-supplied labels and the labels ("topics")
// associated with the repo. Duplicates are excluded and all
// are converted to lower case.
let merged_labels: Vec<String> = labels
.into_iter()
.take(MAX_NUM_TOTAL_LABELS)
.map(|s| s.trim().to_lowercase())
.filter(|t: &String| {
!t.is_empty()
&& t.len() <= MAX_LABEL_LENGTH
&& t.chars().all(|c| c.is_alphanumeric() || c == '-')
})
.collect();

merged_labels
};

// flake_dir is an absolute path of flake_root(aka git_root)/subdir
let flake_dir = local_git_root.join(&subdir);

// FIXME: bail out if flake_metadata denotes a dirty tree.
let flake_metadata =
flake_info::FlakeMetadata::from_dir(&flake_dir, cli.my_flake_is_too_big)
.await
.wrap_err("Getting flake metadata")?;
tracing::debug!("Got flake metadata: {:?}", flake_metadata);

// sanity checks
flake_metadata
.check_evaluates()
.await
.wrap_err("failed to evaluate all system attrs of the flake")?;
flake_metadata
.check_lock_if_exists()
.await
.wrap_err("failed to evaluate all system attrs of the flake")?;

let flake_outputs = flake_metadata.outputs(cli.include_output_paths).await?;
tracing::debug!("Got flake outputs: {:?}", flake_outputs);

let description = flake_metadata
.metadata_json
.get("description")
.and_then(serde_json::Value::as_str)
.map(|s| s.to_string());

let readme = flake_metadata.get_readme_contents().await?;

let release_metadata = ReleaseMetadata {
commit_count,
description,
outputs: flake_outputs.0,
raw_flake_metadata: flake_metadata.metadata_json.clone(),
readme,
// TODO(colemickens): remove this confusing, redundant field (FH-267)
repo: upload_name.to_string(),
revision: git_ctx.revision_info.revision,
visibility,
mirrored: cli.mirror,
source_subdirectory: Some(
subdir
.to_str()
.map(|d| d.to_string())
.ok_or(eyre!("Directory {:?} is not a valid UTF-8 string", subdir))?,
),
spdx_identifier: git_ctx.spdx_expression,
labels: merged_labels,
};
let release_version = cli.release_version(&git_ctx)?;

let flake_tarball = flake_metadata
.flake_tarball()
.wrap_err("Making release tarball")?;
let (release_metadata, flake_tarball) =
ReleaseMetadata::new(cli, &git_ctx, Some(&exec_env)).await?;

let ctx = Self {
flakehub_host: cli.host.clone(),
auth_token,

upload_name,
release_version: rolling_minor_with_postfix_or_tag,
release_version,

error_if_release_conflicts: cli.error_on_conflict,

Expand All @@ -379,7 +176,7 @@ impl PushContext {
}
}

fn determine_names(
pub(crate) fn determine_names(
explicitly_provided_name: &Option<String>,
repository: &str,
subgroup_renaming_explicitly_disabled: bool,
Expand Down
Loading

0 comments on commit 7b65ea3

Please sign in to comment.