2019-11-18 15:12:34 -05:00
|
|
|
// TAME linker
|
|
|
|
//
|
2021-07-22 15:00:15 -04:00
|
|
|
// Copyright (C) 2014-2021 Ryan Specialty Group, LLC.
|
2020-03-06 11:05:18 -05:00
|
|
|
//
|
|
|
|
// This file is part of TAME.
|
2019-11-18 15:12:34 -05:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
//! This is the TAME linker, so named after the traditional `ld` Unix
|
|
|
|
//! utility. Its job is to take each of the compiled object files and
|
|
|
|
//! produce a final executable.
|
|
|
|
//!
|
2019-12-10 00:33:53 -05:00
|
|
|
//! For more information about the linker,
|
|
|
|
//! see the [`tamer::ld`] module.
|
2019-11-18 15:12:34 -05:00
|
|
|
|
2019-11-27 09:18:17 -05:00
|
|
|
extern crate tamer;
|
|
|
|
|
2020-03-04 15:31:20 -05:00
|
|
|
use getopts::{Fail, Options};
|
|
|
|
use std::env;
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
use tamer::{
|
|
|
|
diagnose::{Reporter, VisualReporter},
|
|
|
|
ld::poc::{self, TameldError},
|
|
|
|
};
|
2019-11-18 15:12:34 -05:00
|
|
|
|
2020-03-04 15:31:20 -05:00
|
|
|
/// Types of commands
|
|
|
|
enum Command {
|
2020-04-30 14:33:10 -04:00
|
|
|
Link(String, String, Emit),
|
2020-03-04 15:31:20 -05:00
|
|
|
Usage,
|
|
|
|
}
|
|
|
|
|
2020-04-30 14:33:10 -04:00
|
|
|
/// Ways to emit the linked objects
|
|
|
|
enum Emit {
|
|
|
|
/// The typical desired `Emit`
|
|
|
|
///
|
|
|
|
/// Outputs the linked object files in a format that can be used in an
|
|
|
|
/// application.
|
|
|
|
Xmle,
|
|
|
|
|
|
|
|
/// Used for exploring the linked graph
|
|
|
|
Graphml,
|
|
|
|
}
|
|
|
|
|
2020-03-04 15:31:20 -05:00
|
|
|
/// Entrypoint for the linker
|
tamer: tameld (TameldError): Error sum type
This aggregates all non-panic errors that can occur during link time, making
`Box<dyn Error>` unnecessary. I've been wanting to do this for a long time,
so it's nice seeing this come together. This is a powerful tool, in that we
know, at compile time, all errors that can occur, and properly report on
them and compose them. This method of error composition ensures that all
errors have a chance to be handled within their context, though it'll take
time to do so in a decent way.
This just maintains compatibility with the dynamic dispatch that was
previous occurring. This work is being done to introduce the initial
diagnostic system, which was really difficult/confusing to do without proper
errors types at the top level, considering the toplevel is responsible for
triggering the diagnostic reporting.
The cycle error is in particular going to be interesting once the system is
in place, especially once it provides spans in the future, since it will
guide the user through the code to understand how the cycle formed.
More to come.
DEV-10935
2022-04-11 15:15:04 -04:00
|
|
|
pub fn main() -> Result<(), TameldError> {
|
2020-03-04 15:31:20 -05:00
|
|
|
let args: Vec<String> = env::args().collect();
|
|
|
|
let program = &args[0];
|
|
|
|
let opts = get_opts();
|
2020-04-30 14:33:10 -04:00
|
|
|
let usage =
|
|
|
|
opts.usage(&format!("Usage: {} [OPTIONS] -o OUTPUT FILE", program));
|
2020-03-04 15:31:20 -05:00
|
|
|
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
let mut reporter = VisualReporter;
|
|
|
|
|
2020-03-04 15:31:20 -05:00
|
|
|
match parse_options(opts, args) {
|
2020-04-30 14:33:10 -04:00
|
|
|
Ok(Command::Link(input, output, emit)) => match emit {
|
|
|
|
Emit::Xmle => poc::xmle(&input, &output),
|
|
|
|
Emit::Graphml => poc::graphml(&input, &output),
|
|
|
|
}
|
|
|
|
.or_else(|e| {
|
tamer: diagnose: Introduction of diagnostic system
This is a working concept that will continue to evolve. I wanted to start
with some basic output before getting too carried away, since there's a lot
of potential here.
This is heavily influenced by Rust's helpful diagnostic messages, but will
take some time to realize a lot of the things that Rust does. The next step
will be to resolve line and column numbers, and then possibly include
snippets and underline spans, placing the labels alongside them. I need to
balance this work with everything else I have going on.
This is a large commit, but it converts the existing Error Display impls
into Diagnostic. This separation is a bit verbose, so I'll see how this
ends up evolving.
Diagnostics are tied to Error at the moment, but I imagine in the future
that any object would be able to describe itself, error or not, which would
be useful in the future both for the Summary Page and for query
functionality, to help developers understand the systems they are writing
using TAME.
Output is integrated into tameld only in this commit; I'll add tamec
next. Examples of what this outputs are available in the test cases in this
commit.
DEV-10935
2022-04-13 14:41:54 -04:00
|
|
|
// POC: Rendering to a string ensures buffering so that we don't
|
|
|
|
// interleave output between processes,
|
|
|
|
// but we ought to reuse a buffer when we support multiple
|
|
|
|
// errors.
|
|
|
|
let report = reporter.render_to_string(&e)?;
|
|
|
|
println!("{report}\nfatal: failed to link `{}`", output);
|
2020-03-25 14:27:10 -04:00
|
|
|
|
2020-04-30 14:33:10 -04:00
|
|
|
std::process::exit(1);
|
|
|
|
}),
|
2020-03-04 15:31:20 -05:00
|
|
|
Ok(Command::Usage) => {
|
|
|
|
println!("{}", usage);
|
|
|
|
std::process::exit(exitcode::OK);
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
eprintln!("{}", e);
|
|
|
|
println!("{}", usage);
|
|
|
|
std::process::exit(exitcode::USAGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get 'Options'
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// use getopts::Options;
|
|
|
|
///
|
|
|
|
/// let opts = get_opts();
|
|
|
|
/// ```
|
|
|
|
fn get_opts() -> Options {
|
|
|
|
let mut opts = Options::new();
|
|
|
|
opts.optopt("o", "output", "set output file name", "NAME");
|
|
|
|
opts.optflag("h", "help", "print this help menu");
|
2020-04-30 14:33:10 -04:00
|
|
|
opts.optopt(
|
|
|
|
"",
|
|
|
|
"emit",
|
|
|
|
"set the output to be emitted",
|
|
|
|
"--emit xmle|graphml",
|
|
|
|
);
|
2020-03-04 15:31:20 -05:00
|
|
|
|
|
|
|
opts
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Option parser
|
|
|
|
fn parse_options(opts: Options, args: Vec<String>) -> Result<Command, Fail> {
|
|
|
|
let matches = match opts.parse(&args[1..]) {
|
|
|
|
Ok(m) => m,
|
|
|
|
Err(f) => {
|
|
|
|
return Err(f);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if matches.opt_present("h") {
|
|
|
|
return Ok(Command::Usage);
|
|
|
|
}
|
|
|
|
|
|
|
|
let input = match matches.free.len() {
|
|
|
|
0 => return Err(Fail::OptionMissing(String::from("FILE"))),
|
|
|
|
1 => matches.free[0].clone(),
|
|
|
|
_ => return Err(Fail::UnrecognizedOption(matches.free[1].clone())),
|
|
|
|
};
|
|
|
|
|
|
|
|
let output = match matches.opt_str("o") {
|
|
|
|
Some(m) => m,
|
|
|
|
None => {
|
|
|
|
return Err(Fail::OptionMissing(String::from("-o OUTPUT")));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-04-30 14:33:10 -04:00
|
|
|
let emit = match matches.opt_str("emit") {
|
|
|
|
Some(m) => match &m[..] {
|
|
|
|
"xmle" => Emit::Xmle,
|
|
|
|
"graphml" => Emit::Graphml,
|
|
|
|
em => return Err(Fail::ArgumentMissing(format!("--emit {}", em))),
|
|
|
|
},
|
|
|
|
None => Emit::Xmle,
|
|
|
|
};
|
|
|
|
|
|
|
|
Ok(Command::Link(input, output, emit))
|
2020-03-04 15:31:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_help() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![String::from("program"), String::from("-h")],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok(Command::Usage) => {}
|
|
|
|
_ => panic!("Long help option did not parse"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_help_long() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![String::from("program"), String::from("--help")],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok(Command::Usage) => {}
|
|
|
|
_ => panic!("Help option did not parse"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_invalid() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![String::from("program"), String::from("-q")],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Err(Fail::UnrecognizedOption(_)) => {}
|
|
|
|
_ => panic!("Invalid option not caught"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_missing_input() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(opts, vec![String::from("program")]);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Err(Fail::OptionMissing(message)) => {
|
|
|
|
assert_eq!("FILE", message);
|
|
|
|
}
|
|
|
|
_ => panic!("Missing input not caught"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_missing_output() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![String::from("program"), String::from("foo")],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Err(Fail::OptionMissing(message)) => {
|
|
|
|
assert_eq!("-o OUTPUT", message);
|
|
|
|
}
|
|
|
|
_ => panic!("Missing output not caught"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_too_many_args() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![
|
|
|
|
String::from("program"),
|
|
|
|
String::from("foo"),
|
|
|
|
String::from("-o"),
|
|
|
|
String::from("bar"),
|
|
|
|
String::from("baz"),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Err(Fail::UnrecognizedOption(message)) => {
|
|
|
|
assert_eq!("baz", message);
|
|
|
|
}
|
|
|
|
_ => panic!("Extra option not caught"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-30 14:33:10 -04:00
|
|
|
#[test]
|
|
|
|
fn parse_options_valid_long_emit_invalid() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![
|
|
|
|
String::from("program"),
|
|
|
|
String::from("foo"),
|
|
|
|
String::from("--output"),
|
|
|
|
String::from("bar"),
|
|
|
|
String::from("--emit"),
|
|
|
|
String::from("foo"),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Err(Fail::ArgumentMissing(message)) => {
|
|
|
|
assert_eq!("--emit foo", message);
|
|
|
|
}
|
|
|
|
_ => panic!("Extra option not caught"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-04 15:31:20 -05:00
|
|
|
#[test]
|
|
|
|
fn parse_options_valid() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![
|
|
|
|
String::from("program"),
|
|
|
|
String::from("foo"),
|
|
|
|
String::from("-o"),
|
|
|
|
String::from("bar"),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
2020-04-30 14:33:10 -04:00
|
|
|
Ok(Command::Link(infile, outfile, Emit::Xmle)) => {
|
2020-03-04 15:31:20 -05:00
|
|
|
assert_eq!("foo", infile);
|
|
|
|
assert_eq!("bar", outfile);
|
|
|
|
}
|
|
|
|
_ => panic!("Unexpected result"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_valid_long() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![
|
|
|
|
String::from("program"),
|
|
|
|
String::from("foo"),
|
|
|
|
String::from("--output"),
|
|
|
|
String::from("bar"),
|
2020-04-30 14:33:10 -04:00
|
|
|
String::from("--emit"),
|
|
|
|
String::from("xmle"),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
|
|
|
Ok(Command::Link(infile, outfile, Emit::Xmle)) => {
|
|
|
|
assert_eq!("foo", infile);
|
|
|
|
assert_eq!("bar", outfile);
|
|
|
|
}
|
|
|
|
_ => panic!("Unexpected result"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn parse_options_valid_long_emit_graphml() {
|
|
|
|
let opts = get_opts();
|
|
|
|
let result = parse_options(
|
|
|
|
opts,
|
|
|
|
vec![
|
|
|
|
String::from("program"),
|
|
|
|
String::from("foo"),
|
|
|
|
String::from("--output"),
|
|
|
|
String::from("bar"),
|
|
|
|
String::from("--emit"),
|
|
|
|
String::from("graphml"),
|
2020-03-04 15:31:20 -05:00
|
|
|
],
|
|
|
|
);
|
|
|
|
|
|
|
|
match result {
|
2020-04-30 14:33:10 -04:00
|
|
|
Ok(Command::Link(infile, outfile, Emit::Graphml)) => {
|
2020-03-04 15:31:20 -05:00
|
|
|
assert_eq!("foo", infile);
|
|
|
|
assert_eq!("bar", outfile);
|
|
|
|
}
|
|
|
|
_ => panic!("Unexpected result"),
|
|
|
|
}
|
|
|
|
}
|
2019-11-18 15:12:34 -05:00
|
|
|
}
|