use crate::plan::CleanupTargetDeletionStyle;
use super::ProjectArtifactDefinition;
use super::definitions::{CACHEDIR_TAG_DEFINITION, ProjectArtifactContext};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProjectArtifactPolicy {
pub artifact: &'static str,
pub aliases: &'static [&'static str],
pub definition: ProjectArtifactDefinition,
pub(crate) context: ProjectArtifactContext,
pub default_min_age_days: u64,
pub trim_eligible: bool,
pub deletion_style: CleanupTargetDeletionStyle,
pub ranking: ProjectArtifactRanking,
}
impl ProjectArtifactPolicy {
pub fn deletion_style_label(self) -> &'static str {
match self.deletion_style {
CleanupTargetDeletionStyle::PreserveRootContents => "preserve-root-contents",
CleanupTargetDeletionStyle::DeleteWholePath => "delete-whole-path",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ProjectArtifactRanking {
HeavyDependencyTree,
BuildOutput,
ToolCache,
TestOutput,
TaggedCache,
}
impl ProjectArtifactRanking {
pub fn label(self) -> &'static str {
match self {
Self::HeavyDependencyTree => "heavy-dependency-tree",
Self::BuildOutput => "build-output",
Self::ToolCache => "tool-cache",
Self::TestOutput => "test-output",
Self::TaggedCache => "tagged-cache",
}
}
pub fn priority(self) -> u8 {
match self {
Self::HeavyDependencyTree => 0,
Self::BuildOutput => 1,
Self::ToolCache => 2,
Self::TestOutput => 3,
Self::TaggedCache => 4,
}
}
}
const DEFAULT_MIN_AGE_DAYS: u64 = crate::model::DEFAULT_PROJECT_ARTIFACT_MIN_AGE_DAYS;
const fn policy(
artifact: &'static str,
aliases: &'static [&'static str],
directory_name: &'static str,
rule_id: &'static str,
restore_hint: &'static str,
context: ProjectArtifactContext,
ranking: ProjectArtifactRanking,
) -> ProjectArtifactPolicy {
ProjectArtifactPolicy {
artifact,
aliases,
definition: ProjectArtifactDefinition {
directory_name,
rule_id,
restore_hint,
},
context,
default_min_age_days: DEFAULT_MIN_AGE_DAYS,
trim_eligible: true,
deletion_style: CleanupTargetDeletionStyle::DeleteWholePath,
ranking,
}
}
pub const PROJECT_ARTIFACT_POLICIES: &[ProjectArtifactPolicy] = &[
policy(
"node_modules",
&["node-modules"],
"node_modules",
"portable.project-artifact-node-modules",
"Dependencies can be restored with the project's package manager.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::HeavyDependencyTree,
),
policy(
"target",
&["rust-target", "maven-target"],
"target",
"portable.project-artifact-target",
"Build outputs can be regenerated by the project build tool.",
ProjectArtifactContext::TargetProject,
ProjectArtifactRanking::BuildOutput,
),
policy(
"build",
&[],
"build",
"portable.project-artifact-build",
"Build outputs can be regenerated by the project build tool.",
ProjectArtifactContext::GenericProjectOutput,
ProjectArtifactRanking::BuildOutput,
),
policy(
"dist",
&[],
"dist",
"portable.project-artifact-dist",
"Distribution outputs can be regenerated by the project build tool.",
ProjectArtifactContext::GenericProjectOutput,
ProjectArtifactRanking::BuildOutput,
),
policy(
"venv",
&["python-venv"],
"venv",
"portable.project-artifact-python-venv",
"Python virtual environments can be recreated from project dependencies.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::HeavyDependencyTree,
),
policy(
".venv",
&["python-venv"],
".venv",
"portable.project-artifact-python-venv",
"Python virtual environments can be recreated from project dependencies.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::HeavyDependencyTree,
),
policy(
".pytest_cache",
&["pytest-cache"],
".pytest_cache",
"portable.project-artifact-pytest-cache",
"Pytest cache data is rebuildable.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::TestOutput,
),
policy(
".mypy_cache",
&["mypy-cache"],
".mypy_cache",
"portable.project-artifact-mypy-cache",
"Mypy cache data is rebuildable.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".tox",
&["tox-cache"],
".tox",
"portable.project-artifact-tox-cache",
"Tox environments can be recreated from project dependencies.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".nox",
&["nox-cache"],
".nox",
"portable.project-artifact-nox-cache",
"Nox environments can be recreated from project dependencies.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".ruff_cache",
&["ruff-cache"],
".ruff_cache",
"portable.project-artifact-ruff-cache",
"Ruff cache data is rebuildable.",
ProjectArtifactContext::PythonProject,
ProjectArtifactRanking::ToolCache,
),
policy(
"__pycache__",
&["python-bytecode"],
"__pycache__",
"portable.project-artifact-python-bytecode",
"Python bytecode cache data is rebuildable.",
ProjectArtifactContext::PythonCache,
ProjectArtifactRanking::ToolCache,
),
policy(
".gradle",
&["gradle-cache"],
".gradle",
"portable.project-artifact-gradle-cache",
"Gradle project cache data is rebuildable.",
ProjectArtifactContext::GradleProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".next",
&["next-cache"],
".next",
"portable.project-artifact-next-cache",
"Next.js build output can be regenerated.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::BuildOutput,
),
policy(
".nuxt",
&["nuxt-cache"],
".nuxt",
"portable.project-artifact-nuxt-cache",
"Nuxt build output can be regenerated.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::BuildOutput,
),
policy(
".output",
&["nuxt-output"],
".output",
"portable.project-artifact-nuxt-output",
"Nuxt output can be regenerated.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::BuildOutput,
),
policy(
".turbo",
&["turbo-cache"],
".turbo",
"portable.project-artifact-turbo-cache",
"Turborepo cache data is rebuildable.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".parcel-cache",
&["parcel-cache"],
".parcel-cache",
"portable.project-artifact-parcel-cache",
"Parcel cache data is rebuildable.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".dart_tool",
&["dart-tool"],
".dart_tool",
"portable.project-artifact-dart-tool",
"Dart and Flutter tool data can be regenerated.",
ProjectArtifactContext::DartProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".zig-cache",
&["zig-cache"],
".zig-cache",
"portable.project-artifact-zig-cache",
"Zig cache data is rebuildable.",
ProjectArtifactContext::ZigProject,
ProjectArtifactRanking::ToolCache,
),
policy(
"zig-out",
&["zig-output"],
"zig-out",
"portable.project-artifact-zig-out",
"Zig output can be regenerated.",
ProjectArtifactContext::ZigProject,
ProjectArtifactRanking::BuildOutput,
),
policy(
".angular",
&["angular-cache"],
".angular",
"portable.project-artifact-angular-cache",
"Angular cache data is rebuildable.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::ToolCache,
),
policy(
".svelte-kit",
&["svelte-kit"],
".svelte-kit",
"portable.project-artifact-svelte-kit",
"SvelteKit build data can be regenerated.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::BuildOutput,
),
policy(
".astro",
&["astro-cache"],
".astro",
"portable.project-artifact-astro-cache",
"Astro build data is rebuildable.",
ProjectArtifactContext::NodeProject,
ProjectArtifactRanking::ToolCache,
),
policy(
"coverage",
&["test-coverage"],
"coverage",
"portable.project-artifact-coverage",
"Coverage reports can be regenerated by test tooling.",
ProjectArtifactContext::GenericProjectOutput,
ProjectArtifactRanking::TestOutput,
),
policy(
"dotnet-bin",
&["bin"],
"bin",
"portable.project-artifact-dotnet-bin",
".NET bin outputs can be regenerated by the project build tool.",
ProjectArtifactContext::DotnetBin,
ProjectArtifactRanking::BuildOutput,
),
policy(
"dotnet-obj",
&["obj"],
"obj",
"portable.project-artifact-dotnet-obj",
".NET object output can be regenerated by the build tool.",
ProjectArtifactContext::DotnetObj,
ProjectArtifactRanking::BuildOutput,
),
policy(
"composer-vendor",
&["vendor"],
"vendor",
"portable.project-artifact-composer-vendor",
"Composer vendor dependencies can be restored with composer install.",
ProjectArtifactContext::ComposerVendor,
ProjectArtifactRanking::HeavyDependencyTree,
),
policy(
"cocoapods",
&["pods"],
"Pods",
"portable.project-artifact-cocoapods",
"CocoaPods dependencies can be restored with pod install.",
ProjectArtifactContext::CocoapodsProject,
ProjectArtifactRanking::HeavyDependencyTree,
),
policy(
"cxx-cache",
&["cxx"],
".cxx",
"portable.project-artifact-cxx-cache",
"C/C++ build cache data is rebuildable.",
ProjectArtifactContext::CxxProject,
ProjectArtifactRanking::ToolCache,
),
policy(
"expo-cache",
&["expo"],
".expo",
"portable.project-artifact-expo-cache",
"Expo cache data is rebuildable.",
ProjectArtifactContext::ExpoProject,
ProjectArtifactRanking::ToolCache,
),
policy(
"swift-build",
&["swift-package-build"],
".build",
"portable.project-artifact-swift-build",
"Swift build output can be regenerated.",
ProjectArtifactContext::SwiftPackage,
ProjectArtifactRanking::BuildOutput,
),
];
pub const CACHEDIR_TAG_POLICY: ProjectArtifactPolicy = ProjectArtifactPolicy {
artifact: "CACHEDIR.TAG",
aliases: &["cachedir-tag"],
definition: CACHEDIR_TAG_DEFINITION,
context: ProjectArtifactContext::CachedirTag,
default_min_age_days: DEFAULT_MIN_AGE_DAYS,
trim_eligible: true,
deletion_style: CleanupTargetDeletionStyle::DeleteWholePath,
ranking: ProjectArtifactRanking::TaggedCache,
};
pub fn all_project_artifact_policies() -> impl Iterator<Item = &'static ProjectArtifactPolicy> {
PROJECT_ARTIFACT_POLICIES
.iter()
.chain([&CACHEDIR_TAG_POLICY])
}
pub fn policy_for_definition(
definition: ProjectArtifactDefinition,
) -> Option<&'static ProjectArtifactPolicy> {
all_project_artifact_policies().find(|policy| policy.definition == definition)
}
pub fn policy_for_rule_id(rule_id: &str) -> Option<&'static ProjectArtifactPolicy> {
all_project_artifact_policies()
.find(|policy| policy.definition.rule_id.eq_ignore_ascii_case(rule_id))
}
pub fn policy_for_dir_name(name: &str) -> Option<&'static ProjectArtifactPolicy> {
PROJECT_ARTIFACT_POLICIES
.iter()
.find(|policy| policy.definition.directory_name.eq_ignore_ascii_case(name))
}
pub fn policy_matches_selector(policy: &ProjectArtifactPolicy, selector: &str) -> bool {
let selector = selector.trim();
if selector.is_empty() {
return false;
}
if selector.eq_ignore_ascii_case(policy.definition.rule_id)
|| selector.eq_ignore_ascii_case(policy.definition.directory_name)
|| selector.eq_ignore_ascii_case(policy.artifact)
|| policy
.aliases
.iter()
.any(|alias| selector.eq_ignore_ascii_case(alias))
{
return true;
}
let rule_suffix = policy
.definition
.rule_id
.strip_prefix("portable.project-artifact-")
.unwrap_or(policy.definition.rule_id);
selector.eq_ignore_ascii_case(rule_suffix)
|| selector_alias(selector) == selector_alias(policy.definition.directory_name)
|| selector_alias(selector) == selector_alias(policy.artifact)
|| policy
.aliases
.iter()
.any(|alias| selector_alias(selector) == selector_alias(alias))
}
fn selector_alias(value: &str) -> String {
value.trim().to_ascii_lowercase().replace(['_', '.'], "-")
}