Skip to content

Commit 51b52d5

Browse files
committed
bundle maven
1 parent e4728f5 commit 51b52d5

File tree

6 files changed

+85
-18
lines changed

6 files changed

+85
-18
lines changed

tmc-langs-java/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ j4rs = "0.11.2" # specific version just in case it's necessary to match the jar
1515
tempfile = "3"
1616
lazy_static = "1"
1717
dirs = "2"
18+
tar = "0.4"
19+
flate2 = "1"
1820

1921
[dev-dependencies]
2022
env_logger = "0.7"
9.07 MB
Binary file not shown.

tmc-langs-java/src/ant.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl LanguagePlugin for AntPlugin {
112112
.stderr(stderr)
113113
.current_dir(path)
114114
.output()
115-
.map_err(|e| JavaError::FailedToRun("ant", e))?;
115+
.map_err(|e| JavaError::FailedToRun("ant".to_string(), e))?;
116116

117117
if output.status.success() {
118118
fs::remove_file(stdout_path)?;
@@ -179,7 +179,7 @@ impl JavaPlugin for AntPlugin {
179179
.arg("compile-test")
180180
.current_dir(project_root_path)
181181
.output()
182-
.map_err(|e| JavaError::FailedToRun("ant", e))?;
182+
.map_err(|e| JavaError::FailedToRun("ant".to_string(), e))?;
183183

184184
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
185185
log::debug!("stderr: {}", String::from_utf8_lossy(&output.stderr));
@@ -250,7 +250,7 @@ impl JavaPlugin for AntPlugin {
250250
.current_dir(path)
251251
.args(arguments.join(" ").split(' ').collect::<Vec<&str>>())
252252
.output()
253-
.map_err(|e| JavaError::FailedToRun("java", e))?;
253+
.map_err(|e| JavaError::FailedToRun("java".to_string(), e))?;
254254

255255
Ok(TestRun {
256256
test_results: result_file,

tmc-langs-java/src/error.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ pub enum JavaError {
1414
#[error("Invalid exercise")]
1515
InvalidExercise,
1616
#[error("Failed to run {0}: {1}")]
17-
FailedToRun(&'static str, std::io::Error),
17+
FailedToRun(String, std::io::Error),
1818
#[error("Command '{0}' exited with an error, stderr: {}", String::from_utf8_lossy(&.1))]
19-
FailedCommand(&'static str, Vec<u8>),
19+
FailedCommand(String, Vec<u8>),
2020
#[error("Failed to write temporary .jar files: {0}")]
2121
JarWrite(String),
2222
#[error("IO error with file {0}: {1}")]
@@ -29,6 +29,10 @@ pub enum JavaError {
2929
HomeDir,
3030
#[error("Failed to copy file from {0} to {1}: {2}")]
3131
FileCopy(PathBuf, PathBuf, std::io::Error),
32+
#[error("Failed to find cache directory")]
33+
CacheDir,
34+
#[error("Failed to unpack bundled mvn to {0}: {1}")]
35+
MvnUnpack(PathBuf, std::io::Error),
3236

3337
#[error(transparent)]
3438
Json(#[from] serde_json::Error),

tmc-langs-java/src/maven.rs

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,25 @@ pub mod policy;
44

55
use super::{error::JavaError, plugin::JavaPlugin, CompileResult, TestRun, SEPARATOR};
66

7+
use flate2::read::GzDecoder;
78
use j4rs::Jvm;
89
use policy::MavenStudentFilePolicy;
9-
use std::fs;
10-
use std::path::Path;
10+
use std::ffi::OsString;
11+
use std::fs::{self, File};
12+
use std::io::{Cursor, Write};
13+
use std::path::{Path, PathBuf};
1114
use std::process::Command;
1215
use std::time::Duration;
16+
use tar::Archive;
1317
use tmc_langs_framework::{
1418
domain::{ExerciseDesc, RunResult},
1519
plugin::{Language, LanguagePlugin, ValidationResult},
1620
policy::StudentFilePolicy,
1721
Error,
1822
};
1923

24+
const MVN_ARCHIVE: &[u8] = include_bytes!("../apache-maven-3.6.3-bin.tar.gz");
25+
2026
pub struct MavenPlugin {
2127
jvm: Jvm,
2228
}
@@ -26,6 +32,30 @@ impl MavenPlugin {
2632
let jvm = crate::instantiate_jvm()?;
2733
Ok(Self { jvm })
2834
}
35+
36+
// check if mvn is in PATH, if yes return mvn
37+
// if not, check if the bundled maven has been extracted already,
38+
// if not, extract
39+
// finally, return the path to the extracted executable
40+
fn get_mvn_command(&self) -> Result<OsString, JavaError> {
41+
// check if mvn is in PATH
42+
if let Ok(status) = Command::new("mvn").arg("--version").status() {
43+
if status.success() {
44+
return Ok(OsString::from("mvn"));
45+
}
46+
}
47+
log::debug!("could not execute mvn");
48+
let tmc_path = dirs::cache_dir().ok_or(JavaError::CacheDir)?.join("tmc");
49+
let mvn_exec = tmc_path.join("apache-maven-3.6.3").join("bin").join("mvn");
50+
if !mvn_exec.exists() {
51+
log::debug!("extracting bundled tar");
52+
let tar = GzDecoder::new(Cursor::new(MVN_ARCHIVE));
53+
let mut tar = Archive::new(tar);
54+
tar.unpack(&tmc_path)
55+
.map_err(|e| JavaError::MvnUnpack(tmc_path, e))?;
56+
}
57+
Ok(mvn_exec.as_os_str().to_os_string())
58+
}
2959
}
3060

3161
impl LanguagePlugin for MavenPlugin {
@@ -95,18 +125,24 @@ impl JavaPlugin for MavenPlugin {
95125
let class_path_file = temp.path().join("cp.txt");
96126

97127
let output_arg = format!("-Dmdep.outputFile={}", class_path_file.display());
98-
let output = Command::new("mvn")
128+
let mvn_path = self.get_mvn_command()?;
129+
let output = Command::new(&mvn_path)
99130
.current_dir(path)
100131
.arg("dependency:build-classpath")
101132
.arg(output_arg)
102133
.output()
103-
.map_err(|e| JavaError::FailedToRun("mvn", e))?;
134+
.map_err(|e| {
135+
JavaError::FailedToRun(mvn_path.as_os_str().to_string_lossy().to_string(), e)
136+
})?;
104137

105138
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
106139
log::debug!("stderr: {}", String::from_utf8_lossy(&output.stderr));
107140

108141
if !output.status.success() {
109-
return Err(JavaError::FailedCommand("mvn", output.stderr));
142+
return Err(JavaError::FailedCommand(
143+
mvn_path.as_os_str().to_string_lossy().to_string(),
144+
output.stderr,
145+
));
110146
}
111147

112148
let class_path = fs::read_to_string(&class_path_file)
@@ -129,19 +165,25 @@ impl JavaPlugin for MavenPlugin {
129165
fn build(&self, project_root_path: &Path) -> Result<CompileResult, JavaError> {
130166
log::info!("Building maven project at {}", project_root_path.display());
131167

132-
let output = Command::new("mvn")
168+
let mvn_path = self.get_mvn_command()?;
169+
let output = Command::new(&mvn_path)
133170
.current_dir(project_root_path)
134171
.arg("clean")
135172
.arg("compile")
136173
.arg("test-compile")
137174
.output()
138-
.map_err(|e| JavaError::FailedToRun("mvn", e))?;
175+
.map_err(|e| {
176+
JavaError::FailedToRun(mvn_path.as_os_str().to_string_lossy().to_string(), e)
177+
})?;
139178

140179
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
141180
log::debug!("stderr: {}", String::from_utf8_lossy(&output.stderr));
142181

143182
if !output.status.success() {
144-
return Err(JavaError::FailedCommand("mvn", output.stderr));
183+
return Err(JavaError::FailedCommand(
184+
mvn_path.as_os_str().to_string_lossy().to_string(),
185+
output.stderr,
186+
));
145187
}
146188

147189
Ok(CompileResult {
@@ -158,17 +200,23 @@ impl JavaPlugin for MavenPlugin {
158200
) -> Result<TestRun, JavaError> {
159201
log::info!("Running tests for maven project at {}", path.display());
160202

161-
let output = Command::new("mvn")
203+
let mvn_path = self.get_mvn_command()?;
204+
let output = Command::new(&mvn_path)
162205
.current_dir(path)
163206
.arg("fi.helsinki.cs.tmc:tmc-maven-plugin:1.12:test")
164207
.output()
165-
.map_err(|e| JavaError::FailedToRun("mvn", e))?;
208+
.map_err(|e| {
209+
JavaError::FailedToRun(mvn_path.as_os_str().to_string_lossy().to_string(), e)
210+
})?;
166211

167212
log::trace!("stdout: {}", String::from_utf8_lossy(&output.stdout));
168213
log::debug!("stderr: {}", String::from_utf8_lossy(&output.stderr));
169214

170215
if !output.status.success() {
171-
return Err(JavaError::FailedCommand("mvn", output.stderr));
216+
return Err(JavaError::FailedCommand(
217+
mvn_path.as_os_str().to_string_lossy().to_string(),
218+
output.stderr,
219+
));
172220
}
173221

174222
Ok(TestRun {
@@ -314,4 +362,17 @@ mod test {
314362
"com.puppycrawl.tools.checkstyle.checks.indentation.IndentationCheck"
315363
);
316364
}
365+
366+
// TODO: currently will extract maven to your cache directory
367+
#[test]
368+
fn unpack_bundled_mvn() {
369+
let plugin = MavenPlugin::new().unwrap();
370+
std::env::set_var("PATH", "");
371+
let cmd = plugin.get_mvn_command().unwrap();
372+
let expected = format!(
373+
"tmc{0}apache-maven-3.6.3{0}bin{0}mvn",
374+
std::path::MAIN_SEPARATOR
375+
);
376+
assert!(cmd.to_string_lossy().ends_with(&expected))
377+
}
317378
}

tmc-langs-java/src/plugin.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,10 @@ pub(crate) trait JavaPlugin: LanguagePlugin {
118118
.arg("-XshowSettings:properties")
119119
.arg("-version")
120120
.output()
121-
.map_err(|e| JavaError::FailedToRun("java", e))?;
121+
.map_err(|e| JavaError::FailedToRun("java".to_string(), e))?;
122122

123123
if !output.status.success() {
124-
return Err(JavaError::FailedCommand("java", output.stderr));
124+
return Err(JavaError::FailedCommand("java".to_string(), output.stderr));
125125
}
126126

127127
// information is printed to stderr

0 commit comments

Comments
 (0)