Skip to content

Commit

Permalink
Added support for constraints.yaml
Browse files Browse the repository at this point in the history
  • Loading branch information
franv314 committed Dec 30, 2024
1 parent 2f0eab0 commit 19f9491
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 10 deletions.
19 changes: 19 additions & 0 deletions data/statements/constraints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# If no constraints.py or limiti.py file is present, but
# either constraints.yaml or limiti.yaml is, then this file
# is automatically provided for booklet compilation.
#
# The script stores the entries of the two YAML files
# as global variables

import yaml

for constraint_file in ("limiti.yaml", "constraints.yaml"):
global_variables = globals()

try:
with open(constraint_file, "r") as constraints:
constraints = yaml.safe_load(constraints)
global_variables |= constraints
break
except FileNotFoundError:
pass
20 changes: 19 additions & 1 deletion task-maker-format/src/ioi/dag/input_generator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::Arc;

use anyhow::{bail, Context, Error};
Expand Down Expand Up @@ -30,6 +30,7 @@ impl InputGenerator {
pub(crate) fn generate(
&self,
eval: &mut EvaluationData,
task_path: &Path,
description: String,
subtask_id: SubtaskId,
testcase_id: TestcaseId,
Expand All @@ -56,9 +57,24 @@ impl InputGenerator {
let mut exec = source_file
.execute(eval, description, args.clone())
.context("Failed to execute generator source file")?;

exec.limits_mut().allow_multiprocess();
exec.tag(Tag::Generation.into());
exec.priority(GENERATION_PRIORITY - testcase_id as Priority);

// Add limiti.yaml and constraints.yaml file to the sandbox of the generator
for filename in &["limiti.yaml", "constraints.yaml"] {
let path = task_path.join("gen").join(filename);

if !path.is_file() {
continue;
}

let file = File::new(format!("Constraints file at {}", path.display()));
exec.input(&file, filename, false);
eval.dag.provide_file(file, path)?;
}

let stdout = exec.stdout();
Ok((stdout.uuid, Some(exec)))
}
Expand All @@ -70,11 +86,13 @@ impl InputGenerator {
pub(crate) fn generate_and_bind(
&self,
eval: &mut EvaluationData,
task_path: &Path,
subtask_id: SubtaskId,
testcase_id: TestcaseId,
) -> Result<FileUuid, Error> {
let (input, gen) = self.generate(
eval,
task_path,
format!(
"Generation of input file of testcase {}, subtask {}",
testcase_id, subtask_id
Expand Down
21 changes: 20 additions & 1 deletion task-maker-format/src/ioi/dag/input_validator.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::path::Path;
use std::sync::Arc;

use anyhow::{Context, Error};
use serde::{Deserialize, Serialize};
use typescript_definitions::TypeScriptify;

use task_maker_dag::{Execution, FileUuid, Priority};
use task_maker_dag::{Execution, File, FileUuid, Priority};
use task_maker_diagnostics::Diagnostic;

use crate::ioi::{SubtaskId, TestcaseId, GENERATION_PRIORITY, STDERR_CONTENT_LENGTH};
Expand Down Expand Up @@ -32,9 +33,11 @@ impl InputValidator {
/// Build the execution for the validation of the input file. Return the handle to the standard
/// output of the validator, if any and the `Execution` if any. The execution does not send UI
/// messages yet and it's not added to the DAG.
#[allow(clippy::too_many_arguments)]
pub(crate) fn validate(
&self,
eval: &mut EvaluationData,
task_path: &Path,
description: String,
subtask_id: SubtaskId,
subtask_name: Option<&str>,
Expand All @@ -56,6 +59,20 @@ impl InputValidator {
exec.env("TM_SUBTASK_NAME", name);
}
exec.limits_mut().allow_multiprocess();

// Add limiti.yaml and constraints.yaml file to the sandbox of the validator
for filename in &["limiti.yaml", "constraints.yaml"] {
let path = task_path.join("gen").join(filename);

if !path.is_file() {
continue;
}

let file = File::new(format!("Constraints file at {}", path.display()));
exec.input(&file, filename, false);
eval.dag.provide_file(file, path)?;
}

let stdout = exec.stdout();

Ok((Some(stdout.uuid), Some(exec)))
Expand All @@ -69,13 +86,15 @@ impl InputValidator {
pub(crate) fn validate_and_bind(
&self,
eval: &mut EvaluationData,
task_path: &Path,
subtask_id: SubtaskId,
subtask_name: Option<&str>,
testcase_id: TestcaseId,
input: FileUuid,
) -> Result<Option<FileUuid>, Error> {
let (handle, val) = self.validate(
eval,
task_path,
format!(
"Validation of input file of testcase {}, subtask {}",
testcase_id, subtask_id
Expand Down
23 changes: 17 additions & 6 deletions task-maker-format/src/ioi/dag/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ mod tests {
std::fs::write(&path, "x").unwrap();
let generator = InputGenerator::StaticFile(path);
let (mut eval, _) = EvaluationData::new(tmpdir.path());
let out = generator.generate_and_bind(&mut eval, 0, 0).unwrap();
let out = generator
.generate_and_bind(&mut eval, &PathBuf::from("."), 0, 0)
.unwrap();
assert!(eval.dag.data.provided_files.contains_key(&out));
assert!(eval
.dag
Expand All @@ -182,7 +184,7 @@ mod tests {
let path = tmpdir.path().join("input.txt");
let generator = InputGenerator::StaticFile(path.clone());
let (mut eval, _) = EvaluationData::new(tmpdir.path());
let gen = generator.generate_and_bind(&mut eval, 0, 0);
let gen = generator.generate_and_bind(&mut eval, &PathBuf::from("."), 0, 0);
assert!(gen.is_err());
let err = gen.unwrap_err().to_string();
assert!(err.contains("COPY"));
Expand All @@ -197,7 +199,9 @@ mod tests {
let source = SourceFile::new(&path, "", "", None, None::<PathBuf>).unwrap();
let generator = InputGenerator::Custom(Arc::new(source), vec![]);
let (mut eval, _recv) = EvaluationData::new(tmpdir.path());
let out = generator.generate_and_bind(&mut eval, 0, 0).unwrap();
let out = generator
.generate_and_bind(&mut eval, &PathBuf::from("."), 0, 0)
.unwrap();
assert_eq!(eval.dag.data.provided_files.len(), 1);
assert_eq!(eval.dag.data.execution_groups.len(), 1);
let group = eval.dag.data.execution_groups.values().next().unwrap();
Expand All @@ -218,7 +222,7 @@ mod tests {
let file = File::new("input");
let (mut eval, _recv) = EvaluationData::new("");
let out = validator
.validate_and_bind(&mut eval, 0, None, 0, file.uuid)
.validate_and_bind(&mut eval, &PathBuf::from("."), 0, None, 0, file.uuid)
.unwrap();
assert_eq!(eval.dag.data.provided_files.len(), 0);
assert_eq!(eval.dag.data.execution_groups.len(), 0);
Expand All @@ -235,7 +239,7 @@ mod tests {
let file = File::new("input");
let (mut eval, _recv) = EvaluationData::new(tmpdir.path());
let out = validator
.validate_and_bind(&mut eval, 0, None, 0, file.uuid)
.validate_and_bind(&mut eval, &PathBuf::from("."), 0, None, 0, file.uuid)
.unwrap();
assert_eq!(eval.dag.data.provided_files.len(), 1);
assert_eq!(eval.dag.data.execution_groups.len(), 1);
Expand All @@ -259,7 +263,14 @@ mod tests {
let file = File::new("input");
let (mut eval, _recv) = EvaluationData::new(tmpdir.path());
let out = validator
.validate_and_bind(&mut eval, 0, Some("name"), 0, file.uuid)
.validate_and_bind(
&mut eval,
&PathBuf::from("."),
0,
Some("name"),
0,
file.uuid,
)
.unwrap();
assert_eq!(eval.dag.data.provided_files.len(), 1);
assert_eq!(eval.dag.data.execution_groups.len(), 1);
Expand Down
4 changes: 3 additions & 1 deletion task-maker-format/src/ioi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,13 @@ impl IOITask {
.expect("Testcase not found in the task");
let input = testcase
.input_generator
.generate_and_bind(eval, subtask.id, testcase.id)
.generate_and_bind(eval, &self.path, subtask.id, testcase.id)
.context("Failed to bind input generator")?;
let val_handle = subtask
.input_validator
.validate_and_bind(
eval,
&self.path,
subtask.id,
subtask.name.as_deref(),
testcase.id,
Expand Down Expand Up @@ -387,6 +388,7 @@ impl IOITask {
.input_validator
.validate_and_bind(
eval,
&self.path,
subtask.id,
subtask.name.as_deref(),
testcase.id,
Expand Down
1 change: 1 addition & 0 deletions task-maker-format/src/ioi/sanity_checks/att.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl SanityCheck for AttSampleFilesValid {
.generate(None)
.validate(
eval,
&task.path,
format!("Validation of sample case {}", input_name.display()),
0,
Some("att"),
Expand Down
23 changes: 22 additions & 1 deletion task-maker-format/src/ioi/statement/booklet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,30 @@ impl Booklet {
let deps = statement
.build_deps(eval, &booklet_name, &self.config)
.context("Failed to build booklet dependencies")?;
for (path, file) in deps {

for (path, file) in &deps {
exec.input(file, base_dir.join(path), false);
}

let has_constraints_py = deps.iter().any(|(path, _file)| {
path == Path::new("limiti.py") || path == Path::new("constraints.py")
});
let has_constraints_yaml = deps.iter().any(|(path, _files)| {
path == Path::new("limiti.yaml") || path == Path::new("constraints.yaml")
});

// if there is no constraints.py (or equivalent) but there is constraints.yaml
// (or equivalent) the default constraints.py is provided both as contraints.py
// and limiti.py
if !has_constraints_py && has_constraints_yaml {
let path = DATA_DIR.join("statements/constraints.py");

for constraints_file in &["limiti.py", "constraints.py"] {
let file = File::new(format!("Default {}", constraints_file));
exec.input(&file, base_dir.join(constraints_file), false);
eval.dag.provide_file(file, &path)?;
}
}
}

// copy all the files from the data/statements directory
Expand Down
2 changes: 2 additions & 0 deletions task-maker-format/src/ioi/statement/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ impl Statement {
for path in &[
Path::new("../gen/limiti.py"),
Path::new("../gen/constraints.py"),
Path::new("../gen/limiti.yaml"),
Path::new("../gen/constraints.yaml"),
Path::new("../gen/GEN"),
] {
let full_path = base_dir.join(path);
Expand Down

0 comments on commit 19f9491

Please sign in to comment.