1use std::{io::Write, str::FromStr};
18
19use codespan_reporting::diagnostic::Diagnostic;
20
21use crate::{
22    dockerfile::{Dockerfile, Image, Instruction, ResolvedDockerfile, ResolvedParent, Run},
23    imagegen::{self, BuildPlan, MergeNode, NodeId},
24    logic::{self, Clause, IRTerm, Literal, Predicate},
25    modusfile::{self, Modusfile},
26    sld::{self, ClauseId, ResolutionError, SLDResult, Tree},
27};
28
29use crate::imagegen::BuildNode;
30
31pub fn render_tree<W: Write>(clauses: &Vec<Clause>, sld_result: SLDResult, output: &mut W) {
33    let g = sld_result.tree.to_graph(clauses);
35    dot::render(&g, output).unwrap()
36}
37
38pub fn transpile(
39    mf: Modusfile,
40    query: modusfile::Expression,
41) -> Result<Dockerfile<ResolvedParent>, Vec<Diagnostic<()>>> {
42    let build_plan = imagegen::plan_from_modusfile(mf, query)?;
43    Ok(plan_to_docker(&build_plan))
44}
45
46fn plan_to_docker(plan: &BuildPlan) -> ResolvedDockerfile {
47    let topological_order = plan.topological_order();
48
49    let mut instructions = topological_order
50        .into_iter()
51        .map(|node_id| {
52            use crate::dockerfile::*;
53            let node = &plan.nodes[node_id];
54            let str_id = format!("n_{}", node_id);
55            match node {
56                BuildNode::FromScratch { .. } => {
57                    vec![Instruction::From(From {
58                        parent: ResolvedParent::Image(Image::from_str("scratch").unwrap()),
59                        alias: Some(str_id),
60                    })]
61                }
62                BuildNode::From {
63                    image_ref,
64                    display_name: _,
65                } => vec![Instruction::From(From {
66                    parent: ResolvedParent::Image(Image::from_str(image_ref).unwrap()),
67                    alias: Some(str_id),
68                })],
69                BuildNode::Run {
70                    parent,
71                    command,
72                    cwd,
73                    additional_envs,
74                } => {
75                    let mut instructions = vec![Instruction::From(From {
76                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
77                        alias: Some(str_id),
78                    })];
79                    for (k, v) in additional_envs.iter() {
80                        instructions.push(Instruction::Env(Env(format!("{}={}", k, v))));
81                    }
82                    instructions.push(Instruction::Run(Run(if cwd.is_empty() {
83                        command.to_owned()
84                    } else {
85                        format!("cd {:?} || exit 1; {}", cwd, command)
86                    })));
87                    instructions
88                }
89                BuildNode::CopyFromImage {
90                    parent,
91                    src_image,
92                    src_path,
93                    dst_path,
94                } => vec![
95                    Instruction::From(From {
96                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
97                        alias: Some(str_id),
98                    }),
99                    Instruction::Copy(Copy(format!(
100                        "--from=n_{} {:?} {:?}", src_image, src_path, dst_path
102                    ))),
103                ],
104                BuildNode::CopyFromLocal {
105                    parent,
106                    src_path,
107                    dst_path,
108                } => vec![
109                    Instruction::From(From {
110                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
111                        alias: Some(str_id),
112                    }),
113                    Instruction::Copy(Copy(format!("{:?} {:?}", src_path, dst_path))),
114                ],
115                BuildNode::SetWorkdir {
116                    parent,
117                    new_workdir,
118                } => vec![
119                    Instruction::From(From {
120                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
121                        alias: Some(str_id),
122                    }),
123                    Instruction::Workdir(Workdir(new_workdir.to_string())),
124                ],
125                BuildNode::SetEntrypoint {
126                    parent,
127                    new_entrypoint,
128                } => vec![
129                    Instruction::From(From {
130                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
131                        alias: Some(str_id),
132                    }),
133                    Instruction::Entrypoint(format!("{:?}", new_entrypoint)),
134                ],
135                BuildNode::SetCmd { parent, new_cmd } => vec![
136                    Instruction::From(From {
137                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
138                        alias: Some(str_id),
139                    }),
140                    Instruction::Cmd(format!("{:?}", new_cmd)),
141                ],
142                BuildNode::SetLabel {
143                    parent,
144                    label,
145                    value,
146                } => vec![
147                    Instruction::From(From {
148                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
149                        alias: Some(str_id),
150                    }),
151                    Instruction::Label(label.to_owned(), value.to_owned()),
152                ],
153                BuildNode::Merge(MergeNode { parent, operations }) => {
154                    let mut insts = Vec::new();
155                    insts.push(Instruction::From(From {
156                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
157                        alias: Some(str_id),
158                    }));
159                    for op in operations {
160                        use imagegen::MergeOperation;
161                        match op {
162                            MergeOperation::Run {
163                                command,
164                                cwd,
165                                additional_envs,
166                            } => {
167                                for (k, v) in additional_envs.iter() {
168                                    insts.push(Instruction::Env(Env(format!("{}={}", k, v))));
169                                }
170                                insts.push(Instruction::Run(Run(if cwd.is_empty() {
171                                    command.to_owned()
172                                } else {
173                                    format!("cd {:?} || exit 1; {}", cwd, command)
174                                })));
175                            }
176                            MergeOperation::CopyFromLocal { src_path, dst_path } => {
177                                insts.push(Instruction::Copy(Copy(format!(
178                                    "{:?} {:?}",
179                                    src_path, dst_path
180                                ))));
181                            }
182                            MergeOperation::CopyFromImage {
183                                src_image,
184                                src_path,
185                                dst_path,
186                            } => {
187                                insts.push(Instruction::Copy(Copy(format!(
188                                    "--from=n_{} {:?} {:?}",
189                                    src_image, src_path, dst_path
190                                ))));
191                            }
192                        }
193                    }
194                    insts
195                }
196                BuildNode::SetEnv { parent, key, value } => vec![
197                    Instruction::From(From {
198                        parent: ResolvedParent::Stage(format!("n_{}", parent)),
199                        alias: Some(str_id),
200                    }),
201                    Instruction::Env(Env(format!("{}={}", key, value))),
202                ],
203                BuildNode::AppendEnvValue { parent, key, value } => {
204                    todo!()
205                }
206                BuildNode::SetUser { .. } => todo!(),
207            }
208        })
209        .flatten()
210        .collect::<Vec<_>>();
211
212    if plan.outputs.len() > 1 {
213        use crate::dockerfile::{From, Run};
214        instructions.push(Instruction::From(From {
215            parent: ResolvedParent::Stage("busybox".to_owned()),
216            alias: Some("force_multioutput".to_owned()),
217        }));
218
219        for o in plan.outputs.iter() {
220            let k = format!("n_{}", o.node);
221            instructions.push(Instruction::Run(Run(format!(
222                "--mount=type=bind,from={},source=/,target=/mnt true",
223                k,
224            ))));
225        }
226    }
227
228    Dockerfile(instructions)
229}