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
use std::fmt::{Display, Error, Formatter};
/// we cannot use result for from trait definitions.
///
/// This means we must handle the error some other way.
/// For now, Im using exit. I might
/// change this in the future, as needed.
use std::process::exit;

use crate::{unquote, unwrap, Step};
use rusty_yaml::Yaml;
use std::path::PathBuf;

/// This represents the directory in which buildbot starts the
/// instance of the builder running your script.
/// All this constant is used for is prepending
/// to the working dir for all paths.
const START_DIR: &str = "./build";

/// The Builder struct encapsulates all the operations involved in
/// defining a builder in buildbot. A builder works by giving tasks
/// called steps to workers.
///
/// A Builder object is composed of the name of the builder, the list
/// of worker names that the builder will give the steps to, and the
/// steps themselves.
pub struct Builder {
    name: String,
    workernames: Vec<String>,
    steps: Vec<Step>,
}

/// The implmentation of the Builder struct
impl Builder {
    /// Create a new builder from a name, a list of worker names, and a list of steps
    fn new<S: Display>(name: S, workernames: Vec<S>, steps: Vec<Step>) -> Self {
        Self {
            name: name.to_string(),
            workernames: workernames.iter().map(|s| s.to_string()).collect(),
            steps,
        }
    }

    /// This method returns the name of the builder
    pub fn get_name(&self) -> String {
        self.name.clone()
    }
}

/// This impl converts a Builder into the Python code for buildbot that will
/// give us the behaviour we want.
impl Display for Builder {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        write!(
            f,
            "
temp_factory = util.BuildFactory()
{steps}
c['builders'].append(
    util.BuilderConfig(name=\"{name}\",
    workernames={:?},
    factory=temp_factory))
        ",
            self.workernames,
            name = self.name,
            steps = self
                .steps
                .iter()
                .map(|s| { format!("temp_factory.addStep({})", s) })
                .collect::<Vec<String>>()
                .join("\n"),
        )
    }
}

/// This impl takes a rust-yaml::Yaml object and converts it into a Builder object
impl From<Yaml> for Builder {
    fn from(yaml: Yaml) -> Builder {
        // The name of the yaml section will be used as the name of the builder
        let name = yaml.get_name();

        // Verify that the yaml contains the `workers`, `script`, and `repo` sections
        // If not, tell user, and exit with error code 1.
        for section in ["workers", "script", "repo"].iter() {
            if !yaml.has_section(section) {
                error!(
                    "There was an error creating a builder: '{}' section not specified for '{}'",
                    section, name
                );
                exit(1);
            }
        }
        // Now that we've verified the required sections exist, continue

        let mut steps: Vec<Step> = vec![];

        // Because of the way buildbot processes shell commands,
        // you cannot call the change directory, or cd command as an instruction.
        // Well, you can, but it wont change the directory.
        //
        // To fix this, we keep track of the current working directory using a PathBuf.
        // When the script uses the `cd` command, it will modify this path.
        let mut workdir = PathBuf::new();
        // We want to start in the builders starting directory
        workdir.push(START_DIR);

        // Get the url for the repo from the yaml section
        let url = unwrap(&yaml, "repo");

        // Refresh your copy of the repository
        steps.push(Step::git_clone(&url));
        steps.push(Step::gitlab_clone(url));

        // Run each instruction in the script section
        for instruction in yaml.get_section("script").unwrap() {
            // Here we turn the instruction into a slice of each word so we can match it
            match instruction
                .to_string()
                .split_whitespace()
                .collect::<Vec<&str>>()[..]
            {
                ["cd", path] => workdir.push(path),
                _ => steps.push(Step::command(
                    unquote(&instruction.to_string()),
                    match workdir.to_str() {
                        Some(s) => Some(s.to_string()),
                        None => None,
                    },
                )),
            };
        }

        // Get the workers from the yaml file
        let mut workers: Vec<String> = vec![];
        for worker in yaml.get_section("workers").unwrap() {
            workers.push(worker.to_string());
        }

        // Return the new builder
        Builder::new(name, workers, steps)
    }
}