rusty_ci/buildbot/
builder.rs

1use std::fmt::{Display, Error, Formatter};
2/// we cannot use result for from trait definitions.
3///
4/// This means we must handle the error some other way.
5/// For now, Im using exit. I might
6/// change this in the future, as needed.
7use std::process::exit;
8
9use crate::{unquote, unwrap, Step};
10use rusty_yaml::Yaml;
11use std::path::PathBuf;
12
13/// This represents the directory in which buildbot starts the
14/// instance of the builder running your script.
15/// All this constant is used for is prepending
16/// to the working dir for all paths.
17const START_DIR: &str = "./build";
18
19/// The Builder struct encapsulates all the operations involved in
20/// defining a builder in buildbot. A builder works by giving tasks
21/// called steps to workers.
22///
23/// A Builder object is composed of the name of the builder, the list
24/// of worker names that the builder will give the steps to, and the
25/// steps themselves.
26pub struct Builder {
27    name: String,
28    workernames: Vec<String>,
29    steps: Vec<Step>,
30}
31
32/// The implmentation of the Builder struct
33impl Builder {
34    /// Create a new builder from a name, a list of worker names, and a list of steps
35    fn new<S: Display>(name: S, workernames: Vec<S>, steps: Vec<Step>) -> Self {
36        Self {
37            name: name.to_string(),
38            workernames: workernames.iter().map(|s| s.to_string()).collect(),
39            steps,
40        }
41    }
42
43    /// This method returns the name of the builder
44    pub fn get_name(&self) -> String {
45        self.name.clone()
46    }
47}
48
49/// This impl converts a Builder into the Python code for buildbot that will
50/// give us the behaviour we want.
51impl Display for Builder {
52    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
53        write!(
54            f,
55            "
56temp_factory = util.BuildFactory()
57{steps}
58c['builders'].append(
59    util.BuilderConfig(name=\"{name}\",
60    workernames={:?},
61    factory=temp_factory))
62        ",
63            self.workernames,
64            name = self.name,
65            steps = self
66                .steps
67                .iter()
68                .map(|s| { format!("temp_factory.addStep({})", s) })
69                .collect::<Vec<String>>()
70                .join("\n"),
71        )
72    }
73}
74
75/// This impl takes a rust-yaml::Yaml object and converts it into a Builder object
76impl From<Yaml> for Builder {
77    fn from(yaml: Yaml) -> Builder {
78        // The name of the yaml section will be used as the name of the builder
79        let name = yaml.get_name();
80
81        // Verify that the yaml contains the `workers`, `script`, and `repo` sections
82        // If not, tell user, and exit with error code 1.
83        for section in ["workers", "script", "repo"].iter() {
84            if !yaml.has_section(section) {
85                error!(
86                    "There was an error creating a builder: '{}' section not specified for '{}'",
87                    section, name
88                );
89                exit(1);
90            }
91        }
92        // Now that we've verified the required sections exist, continue
93
94        let mut steps: Vec<Step> = vec![];
95
96        // Because of the way buildbot processes shell commands,
97        // you cannot call the change directory, or cd command as an instruction.
98        // Well, you can, but it wont change the directory.
99        //
100        // To fix this, we keep track of the current working directory using a PathBuf.
101        // When the script uses the `cd` command, it will modify this path.
102        let mut workdir = PathBuf::new();
103        // We want to start in the builders starting directory
104        workdir.push(START_DIR);
105
106        // Get the url for the repo from the yaml section
107        let url = unwrap(&yaml, "repo");
108
109        // Refresh your copy of the repository
110        steps.push(Step::git_clone(&url));
111        steps.push(Step::gitlab_clone(url));
112
113        // Run each instruction in the script section
114        for instruction in yaml.get_section("script").unwrap() {
115            // Here we turn the instruction into a slice of each word so we can match it
116            match instruction
117                .to_string()
118                .split_whitespace()
119                .collect::<Vec<&str>>()[..]
120            {
121                ["cd", path] => workdir.push(path),
122                _ => steps.push(Step::command(
123                    unquote(&instruction.to_string()),
124                    match workdir.to_str() {
125                        Some(s) => Some(s.to_string()),
126                        None => None,
127                    },
128                )),
129            };
130        }
131
132        // Get the workers from the yaml file
133        let mut workers: Vec<String> = vec![];
134        for worker in yaml.get_section("workers").unwrap() {
135            workers.push(worker.to_string());
136        }
137
138        // Return the new builder
139        Builder::new(name, workers, steps)
140    }
141}