Skip to content

Commit 20ed200

Browse files
bors[bot]MikailBagsleirsgoevy
authored
Merge #123
123: Refactor configure-toolchains r=MikailBag a=MikailBag Co-authored-by: Mikail Bagishov <[email protected]> Co-authored-by: Sergey Lisov <[email protected]>
2 parents 4ae06ac + 0a2046c commit 20ed200

File tree

17 files changed

+258
-65
lines changed

17 files changed

+258
-65
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/setup/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn copy_or_symlink_config(conf_params: &ConfigParams, params: &SetupParams) -> a
6868
} else {
6969
add(data_dir, "etc")?;
7070
add(data_dir, "etc/toolchains")?;
71-
let cfg_dir_items = vec!["jjs.toml", "toolchains", "contest.toml"]
71+
let cfg_dir_items = vec!["jjs.toml", "contest.toml"]
7272
.iter()
7373
.map(|x| cfg_dir.join(x))
7474
.collect();
@@ -158,7 +158,7 @@ fn setup_toolchains(params: &SetupParams) -> anyhow::Result<()> {
158158
None => return Ok(()),
159159
};
160160
cmd.arg(toolchain_spec_db_dir);
161-
cmd.arg(target_dir.join("opt"));
161+
cmd.arg(target_dir);
162162
cmd.arg("--trace")
163163
.arg(target_dir.join("configure-toolchains-log.txt"));
164164
let st = cmd.status()?.success();

src/soft/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ util = {path = "../util"}
1313
toml = "0.5.5"
1414
copy-ln = { git = "https://github.com/mikailbag/copy-ln", branch = "master" }
1515
tempfile = "3.1.0"
16+
tera = "0.11.20"
17+
error-chain = "0.12.1" # used inderectly to render tera error into string

src/soft/src/config.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use serde::Deserialize;
22

3-
#[derive(Debug, Deserialize)]
3+
#[derive(Debug, Deserialize, Clone)]
44
#[serde(rename_all = "kebab-case")]
55
pub(crate) enum SetupKind {
66
Trace,
77
}
88

9-
#[derive(Debug, Deserialize)]
10-
pub(crate) struct Config {
9+
#[derive(Debug, Deserialize, Clone)]
10+
pub(crate) struct ToolchainConfig {
1111
kind: SetupKind,
12+
#[serde(default)]
13+
pub(crate) depends: Vec<String>,
14+
#[serde(default)]
15+
pub(crate) auto: bool,
1216
}
13-
14-
impl Config {}

src/soft/src/main.rs

Lines changed: 217 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ mod config;
44
mod dep_collector;
55

66
use crate::dep_collector::DepCollector;
7-
use anyhow::Context;
7+
use anyhow::{bail, Context};
8+
use error_chain::ChainedError;
89
use std::{
910
path::{Path, PathBuf},
1011
process::{Command, ExitCode},
@@ -13,8 +14,8 @@ use structopt::StructOpt;
1314

1415
#[derive(StructOpt)]
1516
struct Options {
16-
/// Spec files dir
17-
spec: PathBuf,
17+
/// Template files dir
18+
tpls_dir: PathBuf,
1819
/// Out dir
1920
out: PathBuf,
2021
/// Trace log,
@@ -41,14 +42,18 @@ struct Options {
4142
print: Option<PathBuf>,
4243
}
4344

44-
fn run_under_trace(script_path: &Path, data_path: &Path) -> anyhow::Result<Vec<u8>> {
45+
fn run_under_trace(
46+
script_path: &Path,
47+
data_path: &Path,
48+
detect_out: &DetectScriptOutput,
49+
) -> anyhow::Result<Vec<u8>> {
4550
let current_dir = tempfile::TempDir::new().context("failed to create temp dir")?;
4651
println!("running in {}", current_dir.path().display());
4752
let log_out_file = current_dir.path().join("__jjs_trace.json");
4853
let data_path = data_path.canonicalize().context("data dir not exists")?;
4954
println!("script will use data from {}", data_path.display());
50-
let status = Command::new("lxtrace")
51-
.current_dir(current_dir.path())
55+
let mut cmd = Command::new("lxtrace");
56+
cmd.current_dir(current_dir.path())
5257
// machine-readable
5358
.arg("--json")
5459
// redirect to file, so it will not mix with script output
@@ -58,35 +63,214 @@ fn run_under_trace(script_path: &Path, data_path: &Path) -> anyhow::Result<Vec<u
5863
.arg("--")
5964
.arg("bash")
6065
.arg(script_path.canonicalize().context("script not exists")?)
61-
.env("DATA", data_path)
62-
.status()
63-
.context("failed to start ktrace")?;
66+
.env("DATA", data_path);
67+
for (k, v) in &detect_out.env {
68+
cmd.env(k, v);
69+
}
70+
let status = cmd.status().context("failed to start ktrace")?;
6471
if !status.success() {
6572
anyhow::bail!("ktrace returned error");
6673
}
6774
Ok(std::fs::read(&log_out_file).context("failed to read trace log")?)
6875
}
6976

70-
fn process_toolchain(
71-
dir: &Path,
77+
#[derive(Clone)]
78+
struct TemplateInfo {
79+
dir: PathBuf,
80+
name: String,
81+
cfg: config::ToolchainConfig,
82+
}
83+
mod tpl_info_impls {
84+
use super::*;
85+
use std::{cmp::*, hash::*};
86+
87+
impl Hash for TemplateInfo {
88+
fn hash<H: Hasher>(&self, hasher: &mut H) {
89+
self.name.hash(hasher);
90+
}
91+
}
92+
93+
impl PartialEq for TemplateInfo {
94+
fn eq(&self, that: &TemplateInfo) -> bool {
95+
self.name == that.name
96+
}
97+
}
98+
99+
impl Eq for TemplateInfo {}
100+
}
101+
102+
fn list_templates(dir: &Path) -> anyhow::Result<Vec<TemplateInfo>> {
103+
let content = std::fs::read_dir(dir).context("failed to read toolchain templates dir")?;
104+
let mut out = Vec::new();
105+
for item in content {
106+
let item = item.context("failed to stat toolchain template dir")?;
107+
let cfg = item.path().join("config.toml");
108+
let cfg = std::fs::read_to_string(cfg).context("failed to open manifest")?;
109+
let cfg = toml::from_str(&cfg).context("failed to parse manifest")?;
110+
let name = item
111+
.path()
112+
.file_name()
113+
.unwrap()
114+
.to_str()
115+
.context("toolchain name is not utf8")?
116+
.to_string();
117+
out.push(TemplateInfo {
118+
dir: item.path(),
119+
name,
120+
cfg,
121+
});
122+
}
123+
Ok(out)
124+
}
125+
126+
fn select_templates(
127+
tpls: impl Iterator<Item = TemplateInfo>,
128+
opt: &Options,
129+
) -> anyhow::Result<impl Iterator<Item = TemplateInfo>> {
130+
let filter: Box<dyn FnMut(&TemplateInfo) -> bool> = if !opt.only.is_empty() {
131+
Box::new(|tpl| opt.only.contains(&tpl.name) || tpl.cfg.auto)
132+
} else if !opt.skip.is_empty() {
133+
Box::new(|tpl| !opt.skip.contains(&tpl.name))
134+
} else {
135+
Box::new(|_tpl| true)
136+
};
137+
let tpls: Vec<_> = tpls.collect();
138+
let roots: Vec<_> = tpls.clone().into_iter().filter(filter).collect();
139+
let mut q = std::collections::HashSet::new();
140+
let mut used = std::collections::HashSet::new();
141+
q.extend(roots.into_iter());
142+
143+
while let Some(head) = q.iter().next() {
144+
let tpl = head.clone();
145+
q.remove(&tpl);
146+
used.insert(tpl.clone());
147+
for dep_name in &tpl.cfg.depends {
148+
let dep = tpls
149+
.iter()
150+
.find(|d| d.name.as_str() == dep_name)
151+
.context("dependency not found")?
152+
.clone();
153+
if !used.contains(&dep) {
154+
q.insert(dep);
155+
}
156+
}
157+
}
158+
Ok(used.into_iter())
159+
}
160+
161+
struct DetectScriptOutput {
162+
env: std::collections::HashMap<String, String>,
163+
}
164+
165+
impl std::str::FromStr for DetectScriptOutput {
166+
type Err = anyhow::Error;
167+
168+
fn from_str(s: &str) -> anyhow::Result<Self> {
169+
let mut this = Self {
170+
env: std::collections::HashMap::new(),
171+
};
172+
for line in s.lines() {
173+
if line.starts_with("set-env:") {
174+
let cmd = line.trim_start_matches("set-env:");
175+
let parts: Vec<_> = cmd.splitn(2, '=').collect();
176+
if parts.len() != 2 {
177+
bail!("set-env command does not look like var_name=var_value");
178+
}
179+
this.env.insert(parts[0].to_string(), parts[1].to_string());
180+
} else {
181+
bail!("unknown command: {}", line);
182+
}
183+
}
184+
Ok(this)
185+
}
186+
}
187+
188+
fn run_detect_script(tpl: &TemplateInfo) -> anyhow::Result<Option<DetectScriptOutput>> {
189+
let detect_script_path = tpl.dir.join("detect.sh");
190+
if !detect_script_path.exists() {
191+
bail!("detect.sh script missing");
192+
}
193+
let out_file_path = tempfile::NamedTempFile::new().context("failed to allocate temp file")?;
194+
195+
let status = std::process::Command::new("bash")
196+
.arg(&detect_script_path)
197+
.arg(out_file_path.path())
198+
.status()
199+
.context("failed to execute detect.sh script")?;
200+
let script_out =
201+
std::fs::read_to_string(out_file_path.path()).context("failed to read detect.sh output")?;
202+
println!("--- script control output ---");
203+
print!("{}", &script_out);
204+
println!("--- end script control output ---");
205+
let script_out = script_out
206+
.parse()
207+
.context("failed to parse detect.sh output")?;
208+
let script_out = if status.success() {
209+
Some(script_out)
210+
} else {
211+
None
212+
};
213+
Ok(script_out)
214+
}
215+
216+
fn process_toolchain_invoke_conf(
217+
tpl: &TemplateInfo,
218+
out_dir: &Path,
219+
detect_out: &DetectScriptOutput,
220+
) -> anyhow::Result<()> {
221+
let out_file = out_dir
222+
.join("etc/toolchains")
223+
.join(format!("{}.toml", &tpl.name));
224+
let in_file = tpl.dir.join("invoke-conf.toml");
225+
if !in_file.exists() {
226+
return Ok(());
227+
}
228+
let in_file = std::fs::read_to_string(&in_file).context("failed to read invoke-conf.toml")?;
229+
230+
let mut render_ctx = std::collections::HashMap::new();
231+
for (k, v) in &detect_out.env {
232+
let k = format!("env_{}", k);
233+
render_ctx.insert(k, v.to_string());
234+
}
235+
let output = tera::Tera::one_off(&in_file, &render_ctx, false)
236+
.map_err(|tera_err| {
237+
// unfortunately, tera uses error_chain for error handling, which does not have `Sync` bound on its errors
238+
// to workaround it, we render error to string
239+
anyhow::anyhow!("{:#}", tera_err.display_chain())
240+
})
241+
.context("failed to render invoke config file")?;
242+
243+
std::fs::create_dir_all(out_file.parent().unwrap()).ok();
244+
245+
std::fs::write(&out_file, &output).context("failed to create config file")?;
246+
247+
Ok(())
248+
}
249+
250+
fn process_toolchain_template(
251+
tpl: TemplateInfo,
72252
collector: &mut DepCollector,
73253
mut event_log: Option<&mut dyn std::io::Write>,
254+
out_dir: &Path,
74255
) -> anyhow::Result<()> {
75-
let manifest_path = dir.join("config.toml");
76-
let manifest =
77-
std::fs::read_to_string(manifest_path).context("config.toml not found or not readable")?;
78-
let _manifest: config::Config = toml::from_str(&manifest).context("failed to parse config")?;
79-
// TODO: look at config
80-
let scripts: anyhow::Result<Vec<_>> = dir
81-
.join("scripts")
256+
let detect_out = match run_detect_script(&tpl)? {
257+
Some(dso) => dso,
258+
None => {
259+
println!("Skipping toolchain {}: not available", &tpl.name);
260+
return Ok(());
261+
}
262+
};
263+
let scripts: anyhow::Result<Vec<_>> = tpl
264+
.dir
265+
.join("use")
82266
.read_dir()?
83267
.map(|item| item.map_err(|err| anyhow::Error::new(err).context("failed to read script")))
84268
.collect();
85-
let current_dir = dir.join("data");
269+
let current_dir = tpl.dir.join("data");
86270
for script in scripts? {
87271
println!("running {}", script.path().display());
88-
let out =
89-
run_under_trace(&script.path(), &current_dir).context("failed to collect trace")?;
272+
let out = run_under_trace(&script.path(), &current_dir, &detect_out)
273+
.context("failed to collect trace")?;
90274
let scanner = serde_json::Deserializer::from_slice(&out).into_iter();
91275
let mut cnt = 0;
92276
let mut cnt_items = 0;
@@ -124,6 +308,7 @@ fn process_toolchain(
124308
cnt_items, cnt, cnt_errors
125309
);
126310
}
311+
process_toolchain_invoke_conf(&tpl, &out_dir, &detect_out)?;
127312
Ok(())
128313
}
129314

@@ -138,32 +323,16 @@ fn main_inner() -> anyhow::Result<()> {
138323
}
139324
None => None,
140325
};
141-
for item in std::fs::read_dir(&opt.spec).context("failed read spec dir")? {
142-
let item = item.context("failed read spec dir")?;
143-
let title = item
144-
.path()
145-
.file_name()
146-
.unwrap()
147-
.to_str()
148-
.context("toolchain name is not utf8")?
149-
.to_string();
326+
let templates = list_templates(&opt.tpls_dir)?;
327+
let templates = select_templates(templates.into_iter(), &opt)?;
328+
for tpl in templates {
329+
println!("------ processing {} ------", &tpl.name);
150330

151-
let mut ok = true;
152-
if !opt.only.is_empty() {
153-
ok = opt.only.contains(&title);
154-
} else if opt.skip.contains(&title) {
155-
ok = false;
156-
}
157-
if ok {
158-
println!("processing {}", &title);
159-
} else {
160-
println!("skipping {}", &title);
161-
continue;
162-
}
163-
if let Err(e) = process_toolchain(
164-
&item.path(),
331+
if let Err(e) = process_toolchain_template(
332+
tpl,
165333
&mut collector,
166334
log_file.as_mut().map(|x| x as _),
335+
&opt.out,
167336
) {
168337
util::print_error(&*e);
169338
}
@@ -172,6 +341,7 @@ fn main_inner() -> anyhow::Result<()> {
172341
"all toolchains processed: {} files found",
173342
collector.count()
174343
);
344+
let toolchain_files_output_dir = opt.out.join("opt");
175345
let mut process_file: Box<dyn FnMut(&str)> = if let Some(path) = &opt.print {
176346
let out_file = std::fs::File::create(&path).context("failed to open output file")?;
177347
let mut out_file = std::io::BufWriter::new(out_file);
@@ -185,7 +355,9 @@ fn main_inner() -> anyhow::Result<()> {
185355
if file.is_dir() && !opt.copy_dirs {
186356
return;
187357
}
188-
if let Err(e) = copy_ln::copy(&opt.out, file, true, !opt.copy_symlinks) {
358+
if let Err(e) =
359+
copy_ln::copy(&toolchain_files_output_dir, file, true, !opt.copy_symlinks)
360+
{
189361
eprintln!("{:?}", e);
190362
}
191363
})

0 commit comments

Comments
 (0)