1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
use crate::{
    common::{
        error::{inv_arg, oe_err, Result},
        log::{tee_file::TeeFileConfiguration, Loglevel, LoglevelFilter},
        types::{ArbCmd, PluginType},
    },
    host::{
        configuration::{
            env_mod::EnvMod, plugin::log::PluginLogConfiguration,
            stream_capture_mode::StreamCaptureMode, timeout::Timeout, PluginConfiguration,
            ReproductionPathStyle,
        },
        plugin::{process::PluginProcess, Plugin},
        reproduction::PluginReproduction,
    },
};
use serde::{Deserialize, Serialize};
use std::{
    env::{current_exe, split_paths, var_os},
    ffi::OsString,
    path::PathBuf,
};

/// Plugin specification, consisting of the executable filename for the plugin
/// and an optional script filename for it to execute for when the executable
/// is an interpreter.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginProcessSpecification {
    /// The executable filename of the plugin.
    pub executable: PathBuf,

    /// If specified, the executable is expected to be an interpreter, which is
    /// to execute the specified script file. If not specified, the executable
    /// is a native plugin.
    pub script: Option<PathBuf>,

    /// Plugin type.
    pub typ: PluginType,
}

impl PluginProcessSpecification {
    /// Constructs a new plugin specification.
    pub fn new<T>(
        executable: impl Into<PathBuf>,
        script: Option<T>,
        typ: impl Into<PluginType>,
    ) -> PluginProcessSpecification
    where
        T: Into<PathBuf>,
    {
        PluginProcessSpecification {
            executable: executable.into(),
            script: script.map(std::convert::Into::into),
            typ: typ.into(),
        }
    }

    /// Constructs a plugin specification from a "sugared" specification.
    ///
    /// The specification can take the following forms:
    ///
    /// - a valid path to a plugin executable with no file extension;
    /// - the basename of a plugin executable with no file extension with
    ///   implicit "dqcsfe"/"dqcsop"/"dqcsbe" prefix, searched for in A) the
    ///   working directory, B) the binary directory, and C) the system $PATH;
    /// - a valid path to a script file with a file extension. In this case,
    ///   the above rule is run for a plugin named by the file extension of the
    ///   script file. For instance, if "test.py" is specified for the frontend,
    ///   this will look for an executable named "dqcsfepy".
    ///
    /// Failure to find the plugin executable or script file results in an
    /// error being returned.
    pub fn from_sugar(
        specification: impl Into<PathBuf>,
        typ: PluginType,
    ) -> Result<PluginProcessSpecification> {
        // Generate the default specification. This default assumes that the
        // specification is a valid path to an executable. We'll fix the
        // structure later if this assumption turns out to be incorrect.
        let specification = specification.into();
        let sugared = specification.clone();
        let mut specification = PluginProcessSpecification {
            executable: specification,
            script: None,
            typ,
        };

        // Handle the simple cases, where the specification is a path to an
        // existing file.
        if specification.executable.exists() {
            if specification.executable.extension().is_some() {
                // The file that we assumed to be the executable is actually a
                // script file. Set the executable to just the file extension;
                // we desugar that later.
                specification.script = Some(specification.executable);
                specification.executable = specification
                    .script
                    .as_ref()
                    .unwrap()
                    .extension()
                    .unwrap()
                    .into();
            } else {
                // Our assumptions appear to be correct.
                return Ok(specification);
            }
        } else {
            // The executable does not exist. If it doesn't contain slashes,
            // we interpret it as a sugared plugin name. If it does contain
            // slashes, the user probably tried to give us an existing path
            // but made a mistake, so we return an error.
            if specification
                .executable
                .as_os_str()
                .to_string_lossy()
                .contains('/')
            {
                return inv_arg(format!(
                    "the plugin specification '{}' appears to be a path, \
                     but the referenced file does not exist",
                    &specification.executable.to_string_lossy()
                ));
            }
        }

        // The executable does not exist (or is just a file extension and
        // should always be treated as sugar). Before we look for the plugin
        // elsewhere, add the appropriate prefix.
        let mut prefix: OsString = match typ {
            PluginType::Frontend => "dqcsfe",
            PluginType::Operator => "dqcsop",
            PluginType::Backend => "dqcsbe",
        }
        .into();
        prefix.push(specification.executable.as_os_str());
        specification.executable = prefix.into();

        // If the executable exists now, i.e. there is a file with the right
        // name in the working directory, we're done.
        if specification.executable.exists() {
            return Ok(specification);
        }

        // Look for the file in the directory where DQCsim resides.
        if let Ok(dqcsim_dir) = current_exe() {
            let mut exec = dqcsim_dir
                .parent()
                .ok_or_else(oe_err("Could not determine path to DQCsim binary."))?
                .to_path_buf();
            exec.push(&specification.executable);
            if exec.exists() {
                specification.executable = exec;
                return Ok(specification);
            }
        }

        // Okay, still not found. Try the system path then.
        if let Some(sys_path) = var_os("PATH") {
            for base in split_paths(&sys_path) {
                let mut exec = base.clone();
                exec.push(&specification.executable);
                if exec.exists() {
                    specification.executable = exec;
                    return Ok(specification);
                }
            }
        }

        inv_arg(format!(
            "could not find plugin executable '{}', needed for plugin \
             specification '{}'",
            specification.executable.to_string_lossy(),
            sugared.to_string_lossy(),
        ))
    }
}

/// Structure describing the functional configuration of a plugin, i.e. the
/// parameters that affect a plugin's behavior.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginProcessFunctionalConfiguration {
    /// ArbCmd objects passed to the plugin initialization RPC.
    pub init: Vec<ArbCmd>,

    /// Environment variable overrides for the plugin process.
    pub env: Vec<EnvMod>,

    /// The working directory for the plugin process.
    pub work: PathBuf,
}

impl Default for PluginProcessFunctionalConfiguration {
    fn default() -> PluginProcessFunctionalConfiguration {
        PluginProcessFunctionalConfiguration {
            init: vec![],
            env: vec![],
            work: ".".into(),
        }
    }
}

/// Structure describing the NONfunctional configuration of a plugin, i.e. the
/// parameters that only affect how the plugin represents its output.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginProcessNonfunctionalConfiguration {
    /// Specifies the verbosity of the messages sent to DQCsim.
    pub verbosity: LoglevelFilter,

    /// Specifies the tee file configuration for this plugin.
    pub tee_files: Vec<TeeFileConfiguration>,

    /// Specifies how the stdout stream of the plugin should be connected.
    pub stdout_mode: StreamCaptureMode,

    /// Specifies how the stderr stream of the plugin should be connected.
    pub stderr_mode: StreamCaptureMode,

    /// Specifies the timeout for connecting to the plugin after it has been
    /// spawned.
    pub accept_timeout: Timeout,

    /// Specifies the timeout duration to wait for the plugin to shutdown after
    /// sending the abort request.
    pub shutdown_timeout: Timeout,
}

impl Default for PluginProcessNonfunctionalConfiguration {
    fn default() -> PluginProcessNonfunctionalConfiguration {
        PluginProcessNonfunctionalConfiguration {
            verbosity: LoglevelFilter::Trace,
            tee_files: vec![],
            stdout_mode: StreamCaptureMode::Capture(Loglevel::Info),
            stderr_mode: StreamCaptureMode::Capture(Loglevel::Info),
            accept_timeout: Timeout::from_seconds(5),
            shutdown_timeout: Timeout::from_seconds(5),
        }
    }
}

/// Represents the complete configuration for a plugin running in an external
/// process.
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct PluginProcessConfiguration {
    /// Name of the plugin, used to refer to the plugin by the log system.
    pub name: String,

    /// Plugin specification, i.e. the plugin executable and optionally the
    /// script it should execute.
    pub specification: PluginProcessSpecification,

    /// The functional configuration of the plugin, i.e. the options
    /// configuring how the plugin behaves (besides the specification).
    pub functional: PluginProcessFunctionalConfiguration,

    /// The nonfunctional configuration of the plugin, i.e. any options that
    /// do not affect how the plugin behaves functionally, but only affect its
    /// output representation.
    pub nonfunctional: PluginProcessNonfunctionalConfiguration,
}

impl PluginProcessConfiguration {
    /// Creates a new plugin configuration.
    ///
    /// The default values are inserted for the configuration options.
    pub fn new(
        name: impl Into<String>,
        specification: PluginProcessSpecification,
    ) -> PluginProcessConfiguration {
        PluginProcessConfiguration {
            name: name.into(),
            specification,
            functional: PluginProcessFunctionalConfiguration::default(),
            nonfunctional: PluginProcessNonfunctionalConfiguration::default(),
        }
    }
}

impl Into<Box<dyn PluginConfiguration>> for PluginProcessConfiguration {
    fn into(self) -> Box<dyn PluginConfiguration> {
        Box::new(self) as Box<dyn PluginConfiguration>
    }
}

impl PluginConfiguration for PluginProcessConfiguration {
    fn instantiate(self: Box<Self>) -> Box<dyn Plugin> {
        Box::new(PluginProcess::new(*self))
    }

    fn get_log_configuration(&self) -> PluginLogConfiguration {
        self.into()
    }

    fn get_type(&self) -> PluginType {
        self.specification.typ
    }

    fn get_reproduction(&self, path_style: ReproductionPathStyle) -> Result<PluginReproduction> {
        Ok(PluginReproduction {
            name: self.name.clone(),
            executable: path_style.convert_path(&self.specification.executable)?,
            script: path_style.convert_path_option(&self.specification.script)?,
            functional: PluginProcessFunctionalConfiguration {
                init: self.functional.init.clone(),
                env: self.functional.env.clone(),
                work: path_style.convert_path(&self.functional.work)?,
            },
        })
    }

    fn limit_verbosity(&mut self, max_verbosity: LoglevelFilter) {
        if self.nonfunctional.verbosity > max_verbosity {
            self.nonfunctional.verbosity = max_verbosity;
        }
    }

    fn set_default_name(&mut self, default_name: String) {
        if self.name.is_empty() {
            self.name = default_name;
        }
    }
}