about summary refs log tree commit diff
path: root/nixpkgs/pkgs/test/nixpkgs-check-by-name/src/ratchet.rs
blob: 8136d641c351592b6d1cdf4e3d337c46fea22e5e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! This module implements the ratchet checks, see ../README.md#ratchet-checks
//!
//! Each type has a `compare` method that validates the ratchet checks for that item.

use crate::nix_file::CallPackageArgumentInfo;
use crate::nixpkgs_problem::NixpkgsProblem;
use crate::validation::{self, Validation, Validation::Success};
use relative_path::RelativePathBuf;
use std::collections::HashMap;

/// The ratchet value for the entirety of Nixpkgs.
#[derive(Default)]
pub struct Nixpkgs {
    /// Sorted list of packages in package_map
    pub package_names: Vec<String>,
    /// The ratchet values for all packages
    pub package_map: HashMap<String, Package>,
}

impl Nixpkgs {
    /// Validates the ratchet checks for Nixpkgs
    pub fn compare(from: Self, to: Self) -> Validation<()> {
        validation::sequence_(
            // We only loop over the current attributes,
            // we don't need to check ones that were removed
            to.package_names.into_iter().map(|name| {
                Package::compare(&name, from.package_map.get(&name), &to.package_map[&name])
            }),
        )
    }
}

/// The ratchet value for a top-level package
pub struct Package {
    /// The ratchet value for the check for non-auto-called empty arguments
    pub manual_definition: RatchetState<ManualDefinition>,

    /// The ratchet value for the check for new packages using pkgs/by-name
    pub uses_by_name: RatchetState<UsesByName>,
}

impl Package {
    /// Validates the ratchet checks for a top-level package
    pub fn compare(name: &str, optional_from: Option<&Self>, to: &Self) -> Validation<()> {
        validation::sequence_([
            RatchetState::<ManualDefinition>::compare(
                name,
                optional_from.map(|x| &x.manual_definition),
                &to.manual_definition,
            ),
            RatchetState::<UsesByName>::compare(
                name,
                optional_from.map(|x| &x.uses_by_name),
                &to.uses_by_name,
            ),
        ])
    }
}

/// The ratchet state of a generic ratchet check.
pub enum RatchetState<Ratchet: ToNixpkgsProblem> {
    /// The ratchet is loose, it can be tightened more.
    /// In other words, this is the legacy state we're trying to move away from.
    /// Introducing new instances is not allowed but previous instances will continue to be allowed.
    /// The `Context` is context for error messages in case a new instance of this state is
    /// introduced
    Loose(Ratchet::ToContext),
    /// The ratchet is tight, it can't be tightened any further.
    /// This is either because we already use the latest state, or because the ratchet isn't
    /// relevant.
    Tight,
    /// This ratchet can't be applied.
    /// State transitions from/to NonApplicable are always allowed
    NonApplicable,
}

/// A trait that can convert an attribute-specific error context into a NixpkgsProblem
pub trait ToNixpkgsProblem {
    /// Context relating to the Nixpkgs that is being transitioned _to_
    type ToContext;

    /// How to convert an attribute-specific error context into a NixpkgsProblem
    fn to_nixpkgs_problem(
        name: &str,
        optional_from: Option<()>,
        to: &Self::ToContext,
    ) -> NixpkgsProblem;
}

impl<Context: ToNixpkgsProblem> RatchetState<Context> {
    /// Compare the previous ratchet state of an attribute to the new state.
    /// The previous state may be `None` in case the attribute is new.
    fn compare(name: &str, optional_from: Option<&Self>, to: &Self) -> Validation<()> {
        match (optional_from, to) {
            // Loosening a ratchet is now allowed
            (Some(RatchetState::Tight), RatchetState::Loose(loose_context)) => {
                Context::to_nixpkgs_problem(name, Some(()), loose_context).into()
            }

            // Introducing a loose ratchet is also not allowed
            (None, RatchetState::Loose(loose_context)) => {
                Context::to_nixpkgs_problem(name, None, loose_context).into()
            }

            // Everything else is allowed, including:
            // - Loose -> Loose (grandfathering policy for a loose ratchet)
            // - -> Tight (always okay to keep or make the ratchet tight)
            // - Anything involving NotApplicable, where we can't really make any good calls
            _ => Success(()),
        }
    }
}

/// The ratchet to check whether a top-level attribute has/needs
/// a manual definition, e.g. in all-packages.nix.
///
/// This ratchet is only tight for attributes that:
/// - Are not defined in `pkgs/by-name`, and rely on a manual definition
/// - Are defined in `pkgs/by-name` without any manual definition,
///   (no custom argument overrides)
/// - Are defined with `pkgs/by-name` with a manual definition that can't be removed
///   because it provides custom argument overrides
///
/// In comparison, this ratchet is loose for attributes that:
/// - Are defined in `pkgs/by-name` with a manual definition
///   that doesn't have any custom argument overrides
pub enum ManualDefinition {}

impl ToNixpkgsProblem for ManualDefinition {
    type ToContext = NixpkgsProblem;

    fn to_nixpkgs_problem(
        _name: &str,
        _optional_from: Option<()>,
        to: &Self::ToContext,
    ) -> NixpkgsProblem {
        (*to).clone()
    }
}

/// The ratchet value of an attribute
/// for the check that new packages use pkgs/by-name
///
/// This checks that all new package defined using callPackage must be defined via pkgs/by-name
/// It also checks that once a package uses pkgs/by-name, it can't switch back to all-packages.nix
pub enum UsesByName {}

impl ToNixpkgsProblem for UsesByName {
    type ToContext = (CallPackageArgumentInfo, RelativePathBuf);

    fn to_nixpkgs_problem(
        name: &str,
        optional_from: Option<()>,
        (to, file): &Self::ToContext,
    ) -> NixpkgsProblem {
        if let Some(()) = optional_from {
            if to.empty_arg {
                NixpkgsProblem::MovedOutOfByNameEmptyArg {
                    package_name: name.to_owned(),
                    call_package_path: to.relative_path.clone(),
                    file: file.to_owned(),
                }
            } else {
                NixpkgsProblem::MovedOutOfByNameNonEmptyArg {
                    package_name: name.to_owned(),
                    call_package_path: to.relative_path.clone(),
                    file: file.to_owned(),
                }
            }
        } else if to.empty_arg {
            NixpkgsProblem::NewPackageNotUsingByNameEmptyArg {
                package_name: name.to_owned(),
                call_package_path: to.relative_path.clone(),
                file: file.to_owned(),
            }
        } else {
            NixpkgsProblem::NewPackageNotUsingByNameNonEmptyArg {
                package_name: name.to_owned(),
                call_package_path: to.relative_path.clone(),
                file: file.to_owned(),
            }
        }
    }
}