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}