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}