xref: /aosp_15_r20/external/bazelbuild-rules_rust/util/process_wrapper/options.rs (revision d4726bddaa87cc4778e7472feed243fa4b6c267f)
1*d4726bddSHONG Yifan use std::collections::HashMap;
2*d4726bddSHONG Yifan use std::env;
3*d4726bddSHONG Yifan use std::fmt;
4*d4726bddSHONG Yifan use std::fs::File;
5*d4726bddSHONG Yifan use std::io::{self, Write};
6*d4726bddSHONG Yifan use std::process::exit;
7*d4726bddSHONG Yifan 
8*d4726bddSHONG Yifan use crate::flags::{FlagParseError, Flags, ParseOutcome};
9*d4726bddSHONG Yifan use crate::rustc;
10*d4726bddSHONG Yifan use crate::util::*;
11*d4726bddSHONG Yifan 
12*d4726bddSHONG Yifan #[derive(Debug)]
13*d4726bddSHONG Yifan pub(crate) enum OptionError {
14*d4726bddSHONG Yifan     FlagError(FlagParseError),
15*d4726bddSHONG Yifan     Generic(String),
16*d4726bddSHONG Yifan }
17*d4726bddSHONG Yifan 
18*d4726bddSHONG Yifan impl fmt::Display for OptionError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result19*d4726bddSHONG Yifan     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20*d4726bddSHONG Yifan         match self {
21*d4726bddSHONG Yifan             Self::FlagError(e) => write!(f, "error parsing flags: {e}"),
22*d4726bddSHONG Yifan             Self::Generic(s) => write!(f, "{s}"),
23*d4726bddSHONG Yifan         }
24*d4726bddSHONG Yifan     }
25*d4726bddSHONG Yifan }
26*d4726bddSHONG Yifan 
27*d4726bddSHONG Yifan #[derive(Debug)]
28*d4726bddSHONG Yifan pub(crate) struct Options {
29*d4726bddSHONG Yifan     // Contains the path to the child executable
30*d4726bddSHONG Yifan     pub(crate) executable: String,
31*d4726bddSHONG Yifan     // Contains arguments for the child process fetched from files.
32*d4726bddSHONG Yifan     pub(crate) child_arguments: Vec<String>,
33*d4726bddSHONG Yifan     // Contains environment variables for the child process fetched from files.
34*d4726bddSHONG Yifan     pub(crate) child_environment: HashMap<String, String>,
35*d4726bddSHONG Yifan     // If set, create the specified file after the child process successfully
36*d4726bddSHONG Yifan     // terminated its execution.
37*d4726bddSHONG Yifan     pub(crate) touch_file: Option<String>,
38*d4726bddSHONG Yifan     // If set to (source, dest) copies the source file to dest.
39*d4726bddSHONG Yifan     pub(crate) copy_output: Option<(String, String)>,
40*d4726bddSHONG Yifan     // If set, redirects the child process stdout to this file.
41*d4726bddSHONG Yifan     pub(crate) stdout_file: Option<String>,
42*d4726bddSHONG Yifan     // If set, redirects the child process stderr to this file.
43*d4726bddSHONG Yifan     pub(crate) stderr_file: Option<String>,
44*d4726bddSHONG Yifan     // If set, also logs all unprocessed output from the rustc output to this file.
45*d4726bddSHONG Yifan     // Meant to be used to get json output out of rustc for tooling usage.
46*d4726bddSHONG Yifan     pub(crate) output_file: Option<String>,
47*d4726bddSHONG Yifan     // If set, it configures rustc to emit an rmeta file and then
48*d4726bddSHONG Yifan     // quit.
49*d4726bddSHONG Yifan     pub(crate) rustc_quit_on_rmeta: bool,
50*d4726bddSHONG Yifan     // This controls the output format of rustc messages.
51*d4726bddSHONG Yifan     pub(crate) rustc_output_format: Option<rustc::ErrorFormat>,
52*d4726bddSHONG Yifan }
53*d4726bddSHONG Yifan 
options() -> Result<Options, OptionError>54*d4726bddSHONG Yifan pub(crate) fn options() -> Result<Options, OptionError> {
55*d4726bddSHONG Yifan     // Process argument list until -- is encountered.
56*d4726bddSHONG Yifan     // Everything after is sent to the child process.
57*d4726bddSHONG Yifan     let mut subst_mapping_raw = None;
58*d4726bddSHONG Yifan     let mut stable_status_file_raw = None;
59*d4726bddSHONG Yifan     let mut volatile_status_file_raw = None;
60*d4726bddSHONG Yifan     let mut env_file_raw = None;
61*d4726bddSHONG Yifan     let mut arg_file_raw = None;
62*d4726bddSHONG Yifan     let mut touch_file = None;
63*d4726bddSHONG Yifan     let mut copy_output_raw = None;
64*d4726bddSHONG Yifan     let mut stdout_file = None;
65*d4726bddSHONG Yifan     let mut stderr_file = None;
66*d4726bddSHONG Yifan     let mut output_file = None;
67*d4726bddSHONG Yifan     let mut rustc_quit_on_rmeta_raw = None;
68*d4726bddSHONG Yifan     let mut rustc_output_format_raw = None;
69*d4726bddSHONG Yifan     let mut flags = Flags::new();
70*d4726bddSHONG Yifan     flags.define_repeated_flag("--subst", "", &mut subst_mapping_raw);
71*d4726bddSHONG Yifan     flags.define_flag("--stable-status-file", "", &mut stable_status_file_raw);
72*d4726bddSHONG Yifan     flags.define_flag("--volatile-status-file", "", &mut volatile_status_file_raw);
73*d4726bddSHONG Yifan     flags.define_repeated_flag(
74*d4726bddSHONG Yifan         "--env-file",
75*d4726bddSHONG Yifan         "File(s) containing environment variables to pass to the child process.",
76*d4726bddSHONG Yifan         &mut env_file_raw,
77*d4726bddSHONG Yifan     );
78*d4726bddSHONG Yifan     flags.define_repeated_flag(
79*d4726bddSHONG Yifan         "--arg-file",
80*d4726bddSHONG Yifan         "File(s) containing command line arguments to pass to the child process.",
81*d4726bddSHONG Yifan         &mut arg_file_raw,
82*d4726bddSHONG Yifan     );
83*d4726bddSHONG Yifan     flags.define_flag(
84*d4726bddSHONG Yifan         "--touch-file",
85*d4726bddSHONG Yifan         "Create this file after the child process runs successfully.",
86*d4726bddSHONG Yifan         &mut touch_file,
87*d4726bddSHONG Yifan     );
88*d4726bddSHONG Yifan     flags.define_repeated_flag("--copy-output", "", &mut copy_output_raw);
89*d4726bddSHONG Yifan     flags.define_flag(
90*d4726bddSHONG Yifan         "--stdout-file",
91*d4726bddSHONG Yifan         "Redirect subprocess stdout in this file.",
92*d4726bddSHONG Yifan         &mut stdout_file,
93*d4726bddSHONG Yifan     );
94*d4726bddSHONG Yifan     flags.define_flag(
95*d4726bddSHONG Yifan         "--stderr-file",
96*d4726bddSHONG Yifan         "Redirect subprocess stderr in this file.",
97*d4726bddSHONG Yifan         &mut stderr_file,
98*d4726bddSHONG Yifan     );
99*d4726bddSHONG Yifan     flags.define_flag(
100*d4726bddSHONG Yifan         "--output-file",
101*d4726bddSHONG Yifan         "Log all unprocessed subprocess stderr in this file.",
102*d4726bddSHONG Yifan         &mut output_file,
103*d4726bddSHONG Yifan     );
104*d4726bddSHONG Yifan     flags.define_flag(
105*d4726bddSHONG Yifan         "--rustc-quit-on-rmeta",
106*d4726bddSHONG Yifan         "If enabled, this wrapper will terminate rustc after rmeta has been emitted.",
107*d4726bddSHONG Yifan         &mut rustc_quit_on_rmeta_raw,
108*d4726bddSHONG Yifan     );
109*d4726bddSHONG Yifan     flags.define_flag(
110*d4726bddSHONG Yifan         "--rustc-output-format",
111*d4726bddSHONG Yifan         "Controls the rustc output format if --rustc-quit-on-rmeta is set.\n\
112*d4726bddSHONG Yifan         'json' will cause the json output to be output, \
113*d4726bddSHONG Yifan         'rendered' will extract the rendered message and print that.\n\
114*d4726bddSHONG Yifan         Default: `rendered`",
115*d4726bddSHONG Yifan         &mut rustc_output_format_raw,
116*d4726bddSHONG Yifan     );
117*d4726bddSHONG Yifan 
118*d4726bddSHONG Yifan     let mut child_args = match flags
119*d4726bddSHONG Yifan         .parse(env::args().collect())
120*d4726bddSHONG Yifan         .map_err(OptionError::FlagError)?
121*d4726bddSHONG Yifan     {
122*d4726bddSHONG Yifan         ParseOutcome::Help(help) => {
123*d4726bddSHONG Yifan             eprintln!("{help}");
124*d4726bddSHONG Yifan             exit(0);
125*d4726bddSHONG Yifan         }
126*d4726bddSHONG Yifan         ParseOutcome::Parsed(p) => p,
127*d4726bddSHONG Yifan     };
128*d4726bddSHONG Yifan     let current_dir = std::env::current_dir()
129*d4726bddSHONG Yifan         .map_err(|e| OptionError::Generic(format!("failed to get current directory: {e}")))?
130*d4726bddSHONG Yifan         .to_str()
131*d4726bddSHONG Yifan         .ok_or_else(|| OptionError::Generic("current directory not utf-8".to_owned()))?
132*d4726bddSHONG Yifan         .to_owned();
133*d4726bddSHONG Yifan     let subst_mappings = subst_mapping_raw
134*d4726bddSHONG Yifan         .unwrap_or_default()
135*d4726bddSHONG Yifan         .into_iter()
136*d4726bddSHONG Yifan         .map(|arg| {
137*d4726bddSHONG Yifan             let (key, val) = arg.split_once('=').ok_or_else(|| {
138*d4726bddSHONG Yifan                 OptionError::Generic(format!("empty key for substitution '{arg}'"))
139*d4726bddSHONG Yifan             })?;
140*d4726bddSHONG Yifan             let v = if val == "${pwd}" {
141*d4726bddSHONG Yifan                 current_dir.as_str()
142*d4726bddSHONG Yifan             } else {
143*d4726bddSHONG Yifan                 val
144*d4726bddSHONG Yifan             }
145*d4726bddSHONG Yifan             .to_owned();
146*d4726bddSHONG Yifan             Ok((key.to_owned(), v))
147*d4726bddSHONG Yifan         })
148*d4726bddSHONG Yifan         .collect::<Result<Vec<(String, String)>, OptionError>>()?;
149*d4726bddSHONG Yifan     let stable_stamp_mappings =
150*d4726bddSHONG Yifan         stable_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
151*d4726bddSHONG Yifan     let volatile_stamp_mappings =
152*d4726bddSHONG Yifan         volatile_status_file_raw.map_or_else(Vec::new, |s| read_stamp_status_to_array(s).unwrap());
153*d4726bddSHONG Yifan     let environment_file_block = env_from_files(env_file_raw.unwrap_or_default())?;
154*d4726bddSHONG Yifan     let mut file_arguments = args_from_file(arg_file_raw.unwrap_or_default())?;
155*d4726bddSHONG Yifan     // Process --copy-output
156*d4726bddSHONG Yifan     let copy_output = copy_output_raw
157*d4726bddSHONG Yifan         .map(|co| {
158*d4726bddSHONG Yifan             if co.len() != 2 {
159*d4726bddSHONG Yifan                 return Err(OptionError::Generic(format!(
160*d4726bddSHONG Yifan                     "\"--copy-output\" needs exactly 2 parameters, {} provided",
161*d4726bddSHONG Yifan                     co.len()
162*d4726bddSHONG Yifan                 )));
163*d4726bddSHONG Yifan             }
164*d4726bddSHONG Yifan             let copy_source = &co[0];
165*d4726bddSHONG Yifan             let copy_dest = &co[1];
166*d4726bddSHONG Yifan             if copy_source == copy_dest {
167*d4726bddSHONG Yifan                 return Err(OptionError::Generic(format!(
168*d4726bddSHONG Yifan                     "\"--copy-output\" source ({copy_source}) and dest ({copy_dest}) need to be different.",
169*d4726bddSHONG Yifan                 )));
170*d4726bddSHONG Yifan             }
171*d4726bddSHONG Yifan             Ok((copy_source.to_owned(), copy_dest.to_owned()))
172*d4726bddSHONG Yifan         })
173*d4726bddSHONG Yifan         .transpose()?;
174*d4726bddSHONG Yifan 
175*d4726bddSHONG Yifan     let rustc_quit_on_rmeta = rustc_quit_on_rmeta_raw.map_or(false, |s| s == "true");
176*d4726bddSHONG Yifan     let rustc_output_format = rustc_output_format_raw
177*d4726bddSHONG Yifan         .map(|v| match v.as_str() {
178*d4726bddSHONG Yifan             "json" => Ok(rustc::ErrorFormat::Json),
179*d4726bddSHONG Yifan             "rendered" => Ok(rustc::ErrorFormat::Rendered),
180*d4726bddSHONG Yifan             _ => Err(OptionError::Generic(format!(
181*d4726bddSHONG Yifan                 "invalid --rustc-output-format '{v}'",
182*d4726bddSHONG Yifan             ))),
183*d4726bddSHONG Yifan         })
184*d4726bddSHONG Yifan         .transpose()?;
185*d4726bddSHONG Yifan 
186*d4726bddSHONG Yifan     // Prepare the environment variables, unifying those read from files with the ones
187*d4726bddSHONG Yifan     // of the current process.
188*d4726bddSHONG Yifan     let vars = environment_block(
189*d4726bddSHONG Yifan         environment_file_block,
190*d4726bddSHONG Yifan         &stable_stamp_mappings,
191*d4726bddSHONG Yifan         &volatile_stamp_mappings,
192*d4726bddSHONG Yifan         &subst_mappings,
193*d4726bddSHONG Yifan     );
194*d4726bddSHONG Yifan     // Append all the arguments fetched from files to those provided via command line.
195*d4726bddSHONG Yifan     child_args.append(&mut file_arguments);
196*d4726bddSHONG Yifan     let child_args = prepare_args(child_args, &subst_mappings)?;
197*d4726bddSHONG Yifan     // Split the executable path from the rest of the arguments.
198*d4726bddSHONG Yifan     let (exec_path, args) = child_args.split_first().ok_or_else(|| {
199*d4726bddSHONG Yifan         OptionError::Generic(
200*d4726bddSHONG Yifan             "at least one argument after -- is required (the child process path)".to_owned(),
201*d4726bddSHONG Yifan         )
202*d4726bddSHONG Yifan     })?;
203*d4726bddSHONG Yifan 
204*d4726bddSHONG Yifan     Ok(Options {
205*d4726bddSHONG Yifan         executable: exec_path.to_owned(),
206*d4726bddSHONG Yifan         child_arguments: args.to_vec(),
207*d4726bddSHONG Yifan         child_environment: vars,
208*d4726bddSHONG Yifan         touch_file,
209*d4726bddSHONG Yifan         copy_output,
210*d4726bddSHONG Yifan         stdout_file,
211*d4726bddSHONG Yifan         stderr_file,
212*d4726bddSHONG Yifan         output_file,
213*d4726bddSHONG Yifan         rustc_quit_on_rmeta,
214*d4726bddSHONG Yifan         rustc_output_format,
215*d4726bddSHONG Yifan     })
216*d4726bddSHONG Yifan }
217*d4726bddSHONG Yifan 
args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError>218*d4726bddSHONG Yifan fn args_from_file(paths: Vec<String>) -> Result<Vec<String>, OptionError> {
219*d4726bddSHONG Yifan     let mut args = vec![];
220*d4726bddSHONG Yifan     for path in paths.iter() {
221*d4726bddSHONG Yifan         let mut lines = read_file_to_array(path).map_err(|err| {
222*d4726bddSHONG Yifan             OptionError::Generic(format!(
223*d4726bddSHONG Yifan                 "{} while processing args from file paths: {:?}",
224*d4726bddSHONG Yifan                 err, &paths
225*d4726bddSHONG Yifan             ))
226*d4726bddSHONG Yifan         })?;
227*d4726bddSHONG Yifan         args.append(&mut lines);
228*d4726bddSHONG Yifan     }
229*d4726bddSHONG Yifan     Ok(args)
230*d4726bddSHONG Yifan }
231*d4726bddSHONG Yifan 
env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError>232*d4726bddSHONG Yifan fn env_from_files(paths: Vec<String>) -> Result<HashMap<String, String>, OptionError> {
233*d4726bddSHONG Yifan     let mut env_vars = HashMap::new();
234*d4726bddSHONG Yifan     for path in paths.into_iter() {
235*d4726bddSHONG Yifan         let lines = read_file_to_array(&path).map_err(OptionError::Generic)?;
236*d4726bddSHONG Yifan         for line in lines.into_iter() {
237*d4726bddSHONG Yifan             let (k, v) = line
238*d4726bddSHONG Yifan                 .split_once('=')
239*d4726bddSHONG Yifan                 .ok_or_else(|| OptionError::Generic("environment file invalid".to_owned()))?;
240*d4726bddSHONG Yifan             env_vars.insert(k.to_owned(), v.to_owned());
241*d4726bddSHONG Yifan         }
242*d4726bddSHONG Yifan     }
243*d4726bddSHONG Yifan     Ok(env_vars)
244*d4726bddSHONG Yifan }
245*d4726bddSHONG Yifan 
prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String246*d4726bddSHONG Yifan fn prepare_arg(mut arg: String, subst_mappings: &[(String, String)]) -> String {
247*d4726bddSHONG Yifan     for (f, replace_with) in subst_mappings {
248*d4726bddSHONG Yifan         let from = format!("${{{f}}}");
249*d4726bddSHONG Yifan         arg = arg.replace(&from, replace_with);
250*d4726bddSHONG Yifan     }
251*d4726bddSHONG Yifan     arg
252*d4726bddSHONG Yifan }
253*d4726bddSHONG Yifan 
254*d4726bddSHONG Yifan /// Apply substitutions to the given param file. Returns the new filename.
prepare_param_file( filename: &str, subst_mappings: &[(String, String)], ) -> Result<String, OptionError>255*d4726bddSHONG Yifan fn prepare_param_file(
256*d4726bddSHONG Yifan     filename: &str,
257*d4726bddSHONG Yifan     subst_mappings: &[(String, String)],
258*d4726bddSHONG Yifan ) -> Result<String, OptionError> {
259*d4726bddSHONG Yifan     let expanded_file = format!("{filename}.expanded");
260*d4726bddSHONG Yifan     let format_err = |err: io::Error| {
261*d4726bddSHONG Yifan         OptionError::Generic(format!(
262*d4726bddSHONG Yifan             "{} writing path: {:?}, current directory: {:?}",
263*d4726bddSHONG Yifan             err,
264*d4726bddSHONG Yifan             expanded_file,
265*d4726bddSHONG Yifan             std::env::current_dir()
266*d4726bddSHONG Yifan         ))
267*d4726bddSHONG Yifan     };
268*d4726bddSHONG Yifan     let mut out = io::BufWriter::new(File::create(&expanded_file).map_err(format_err)?);
269*d4726bddSHONG Yifan     fn process_file(
270*d4726bddSHONG Yifan         filename: &str,
271*d4726bddSHONG Yifan         out: &mut io::BufWriter<File>,
272*d4726bddSHONG Yifan         subst_mappings: &[(String, String)],
273*d4726bddSHONG Yifan         format_err: &impl Fn(io::Error) -> OptionError,
274*d4726bddSHONG Yifan     ) -> Result<(), OptionError> {
275*d4726bddSHONG Yifan         for arg in read_file_to_array(filename).map_err(OptionError::Generic)? {
276*d4726bddSHONG Yifan             let arg = prepare_arg(arg, subst_mappings);
277*d4726bddSHONG Yifan             if let Some(arg_file) = arg.strip_prefix('@') {
278*d4726bddSHONG Yifan                 process_file(arg_file, out, subst_mappings, format_err)?;
279*d4726bddSHONG Yifan             } else {
280*d4726bddSHONG Yifan                 writeln!(out, "{arg}").map_err(format_err)?;
281*d4726bddSHONG Yifan             }
282*d4726bddSHONG Yifan         }
283*d4726bddSHONG Yifan         Ok(())
284*d4726bddSHONG Yifan     }
285*d4726bddSHONG Yifan     process_file(filename, &mut out, subst_mappings, &format_err)?;
286*d4726bddSHONG Yifan     Ok(expanded_file)
287*d4726bddSHONG Yifan }
288*d4726bddSHONG Yifan 
289*d4726bddSHONG Yifan /// Apply substitutions to the provided arguments, recursing into param files.
prepare_args( args: Vec<String>, subst_mappings: &[(String, String)], ) -> Result<Vec<String>, OptionError>290*d4726bddSHONG Yifan fn prepare_args(
291*d4726bddSHONG Yifan     args: Vec<String>,
292*d4726bddSHONG Yifan     subst_mappings: &[(String, String)],
293*d4726bddSHONG Yifan ) -> Result<Vec<String>, OptionError> {
294*d4726bddSHONG Yifan     args.into_iter()
295*d4726bddSHONG Yifan         .map(|arg| {
296*d4726bddSHONG Yifan             let arg = prepare_arg(arg, subst_mappings);
297*d4726bddSHONG Yifan             if let Some(param_file) = arg.strip_prefix('@') {
298*d4726bddSHONG Yifan                 // Note that substitutions may also apply to the param file path!
299*d4726bddSHONG Yifan                 prepare_param_file(param_file, subst_mappings)
300*d4726bddSHONG Yifan                     .map(|filename| format!("@{filename}"))
301*d4726bddSHONG Yifan             } else {
302*d4726bddSHONG Yifan                 Ok(arg)
303*d4726bddSHONG Yifan             }
304*d4726bddSHONG Yifan         })
305*d4726bddSHONG Yifan         .collect()
306*d4726bddSHONG Yifan }
307*d4726bddSHONG Yifan 
environment_block( environment_file_block: HashMap<String, String>, stable_stamp_mappings: &[(String, String)], volatile_stamp_mappings: &[(String, String)], subst_mappings: &[(String, String)], ) -> HashMap<String, String>308*d4726bddSHONG Yifan fn environment_block(
309*d4726bddSHONG Yifan     environment_file_block: HashMap<String, String>,
310*d4726bddSHONG Yifan     stable_stamp_mappings: &[(String, String)],
311*d4726bddSHONG Yifan     volatile_stamp_mappings: &[(String, String)],
312*d4726bddSHONG Yifan     subst_mappings: &[(String, String)],
313*d4726bddSHONG Yifan ) -> HashMap<String, String> {
314*d4726bddSHONG Yifan     // Taking all environment variables from the current process
315*d4726bddSHONG Yifan     // and sending them down to the child process
316*d4726bddSHONG Yifan     let mut environment_variables: HashMap<String, String> = std::env::vars().collect();
317*d4726bddSHONG Yifan     // Have the last values added take precedence over the first.
318*d4726bddSHONG Yifan     // This is simpler than needing to track duplicates and explicitly override
319*d4726bddSHONG Yifan     // them.
320*d4726bddSHONG Yifan     environment_variables.extend(environment_file_block);
321*d4726bddSHONG Yifan     for (f, replace_with) in &[stable_stamp_mappings, volatile_stamp_mappings].concat() {
322*d4726bddSHONG Yifan         for value in environment_variables.values_mut() {
323*d4726bddSHONG Yifan             let from = format!("{{{f}}}");
324*d4726bddSHONG Yifan             let new = value.replace(from.as_str(), replace_with);
325*d4726bddSHONG Yifan             *value = new;
326*d4726bddSHONG Yifan         }
327*d4726bddSHONG Yifan     }
328*d4726bddSHONG Yifan     for (f, replace_with) in subst_mappings {
329*d4726bddSHONG Yifan         for value in environment_variables.values_mut() {
330*d4726bddSHONG Yifan             let from = format!("${{{f}}}");
331*d4726bddSHONG Yifan             let new = value.replace(from.as_str(), replace_with);
332*d4726bddSHONG Yifan             *value = new;
333*d4726bddSHONG Yifan         }
334*d4726bddSHONG Yifan     }
335*d4726bddSHONG Yifan     environment_variables
336*d4726bddSHONG Yifan }
337