use anodizer_core::config::{Config, ForceTokenKind};
use anodizer_core::context::Context;
use anyhow::Context as _;
pub(super) fn set_env_var_single_threaded(key: &str, value: &str) {
unsafe { std::env::set_var(key, value) };
}
pub(super) fn resolve_force_token_with_env<E: anodizer_core::env_source::EnvSource + ?Sized>(
config: &Config,
env: &E,
) -> Option<ForceTokenKind> {
config.force_token.as_ref().cloned().or_else(|| {
let env_val = env
.var("ANODIZER_FORCE_TOKEN")
.or_else(|| env.var("GORELEASER_FORCE_TOKEN"))?;
match env_val.to_lowercase().as_str() {
"github" => Some(ForceTokenKind::GitHub),
"gitlab" => Some(ForceTokenKind::GitLab),
"gitea" => Some(ForceTokenKind::Gitea),
_ => None,
}
})
}
fn resolve_force_token(config: &Config) -> Option<ForceTokenKind> {
resolve_force_token_with_env(config, &anodizer_core::env_source::ProcessEnvSource)
}
pub(super) fn merge_env_with_defaults(
defaults_env: Option<&Vec<String>>,
config_env: Option<&Vec<String>>,
) -> Option<Vec<String>> {
match (defaults_env, config_env) {
(None, None) => None,
(Some(d), None) => Some(d.clone()),
(None, Some(c)) => Some(c.clone()),
(Some(d), Some(c)) => {
let mut v = Vec::with_capacity(d.len() + c.len());
v.extend(d.iter().cloned());
v.extend(c.iter().cloned());
Some(v)
}
}
}
pub(super) fn referenced_var_keys(template: &str) -> Vec<&str> {
let mut out = Vec::new();
let bytes = template.as_bytes();
for (idx, _) in template.match_indices("Var.") {
let start = idx + "Var.".len();
let key_len = bytes[start..]
.iter()
.take_while(|b| b.is_ascii_alphanumeric() || **b == b'_')
.count();
if key_len > 0 {
out.push(&template[start..start + key_len]);
}
}
out
}
pub fn setup_env(
ctx: &mut Context,
config: &Config,
log: &anodizer_core::log::StageLogger,
) -> anyhow::Result<()> {
for (key, value) in ctx.env_source().vars() {
ctx.template_vars_mut().set_env(&key, &value);
}
if let Some(ref env_files_config) = config.env_files {
match env_files_config {
anodizer_core::config::EnvFilesConfig::List(files) => {
let env_vars = anodizer_core::config::load_env_files(files, log, ctx.is_strict())
.map_err(anyhow::Error::msg)?;
for (key, value) in &env_vars {
ctx.template_vars_mut().set_config_env(key, value);
}
}
anodizer_core::config::EnvFilesConfig::TokenFiles(token_config) => {
let token_vars = anodizer_core::config::load_token_files(token_config, log)
.map_err(anyhow::Error::msg)?;
for (key, value) in &token_vars {
ctx.template_vars_mut().set_config_env(key, value);
set_env_var_single_threaded(key, value);
}
}
}
} else {
let default_config = anodizer_core::config::EnvFilesTokenConfig::default();
let token_vars = anodizer_core::config::load_token_files(&default_config, log)
.map_err(anyhow::Error::msg)?;
for (key, value) in &token_vars {
ctx.template_vars_mut().set_config_env(key, value);
set_env_var_single_threaded(key, value);
}
}
let merged_env = merge_env_with_defaults(
config.defaults.as_ref().and_then(|d| d.env.as_ref()),
config.env.as_ref(),
);
if let Some(ref env_list) = merged_env {
let rendered_pairs =
anodizer_core::config::render_env_entries(env_list, |v| ctx.render_template(v))
.with_context(|| "config.env: parse and render entries")?;
for (key, rendered) in rendered_pairs {
ctx.template_vars_mut().set_config_env(&key, &rendered);
set_env_var_single_threaded(&key, &rendered);
}
}
if let Some(ref vars_map) = config.variables {
let mut defined: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
for (key, value) in vars_map {
for referenced in referenced_var_keys(value) {
if vars_map.contains_key(referenced) && !defined.contains(referenced) {
log.warn(&format!(
"variables.{key} references variable '{referenced}' that is \
defined later (variables resolve in alphabetical key order, so \
'{referenced}' is still unset here and renders empty). Rename so \
'{key}' sorts after '{referenced}'."
));
}
}
let rendered = ctx
.render_template(value)
.with_context(|| format!("variables.{key}: failed to render template '{value}'"))?;
ctx.template_vars_mut().set_custom_var(key, &rendered);
defined.insert(key.as_str());
}
}
let resolved_force = resolve_force_token(config);
if let Some(ref forced) = resolved_force {
let keep_github = matches!(forced, ForceTokenKind::GitHub);
let keep_gitlab = matches!(forced, ForceTokenKind::GitLab);
let keep_gitea = matches!(forced, ForceTokenKind::Gitea);
if !keep_github {
unsafe {
std::env::remove_var("GITHUB_TOKEN");
std::env::remove_var("ANODIZER_GITHUB_TOKEN");
}
}
if !keep_gitlab {
unsafe { std::env::remove_var("GITLAB_TOKEN") };
}
if !keep_gitea {
unsafe { std::env::remove_var("GITEA_TOKEN") };
}
}
if resolved_force.is_none() {
let has_github =
anodizer_core::git::resolve_github_token_with_env(None, &|key| ctx.env_var(key))
.is_some();
let has_gitlab = ctx.env_var("GITLAB_TOKEN").is_some_and(|v| !v.is_empty());
let has_gitea = ctx.env_var("GITEA_TOKEN").is_some_and(|v| !v.is_empty());
let count = [has_github, has_gitlab, has_gitea]
.iter()
.filter(|&&b| b)
.count();
if count > 1 {
anyhow::bail!(
"multiple SCM tokens set simultaneously ({}). Set force_token in config \
or ANODIZER_FORCE_TOKEN env var to specify which to use.",
[
if has_github {
Some("GITHUB_TOKEN")
} else {
None
},
if has_gitlab {
Some("GITLAB_TOKEN")
} else {
None
},
if has_gitea { Some("GITEA_TOKEN") } else { None },
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
.join(", ")
);
}
}
if ctx.options.token.is_none()
&& !ctx.is_snapshot()
&& !ctx.is_dry_run()
&& !ctx.options.publish_only
&& !ctx.options.preflight_secrets
{
let universe = config.crate_universe();
let release_skipped = match universe
.first()
.and_then(|c| c.release.as_ref()?.skip.as_ref())
{
Some(d) => d
.try_evaluates_to_true(|t| ctx.render_template(t))
.with_context(|| "release: render skip template")?,
None => false,
};
let needs_token = universe.iter().any(|c| c.release.is_some())
&& !ctx.should_skip("release")
&& !release_skipped;
if needs_token {
let hint = match ctx.token_type {
anodizer_core::scm::ScmTokenType::GitLab => {
"no GitLab token found. Set GITLAB_TOKEN.".to_string()
}
anodizer_core::scm::ScmTokenType::Gitea => {
"no Gitea token found. Set GITEA_TOKEN.".to_string()
}
anodizer_core::scm::ScmTokenType::GitHub => {
format!(
"no GitHub token found: {}.",
anodizer_core::git::github_token_hint()
)
}
};
anyhow::bail!("{}", hint);
}
}
Ok(())
}