Skip to content

Commit

Permalink
Merge pull request #5 from Rust-GCC/compile-basic-hw
Browse files Browse the repository at this point in the history
Compile basic rust project
  • Loading branch information
CohenArthur committed Jun 14, 2021
2 parents 4c78657 + 98688f7 commit 2cfc3db
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 18 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/build_test_fmt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: cargo-gccrs CI

on:
pull_request:
push:
branches:
master

jobs:
build-and-test:
runs-on: ubuntu-latest
env:
RUSTFLAGS: "-D warnings"

steps:
- uses: actions/checkout@v2
- name: Check the coding style
run: |
cargo fmt -- --check
- name: Check for clippy warnings
run: cargo clippy

- name: Build
run: cargo build

- name: Run Unit Tests and Documentation examples
run: cargo test
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
/target/
target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "cargo-gccrs"
version = "0.0.1"
authors = ["Arthur Cohen <arthur.cohen@epita.fr"]
authors = ["Arthur Cohen <cohenarthur.dev@gmail.com"]
edition = "2018"
description = "Adding gccrs support to the cargo build system"
license = "MIT OR Apache-2.0"
Expand All @@ -10,3 +10,4 @@ repository = "https://github.com/Rust-GCC/cargo-gccrs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
getopts = "0.2"
156 changes: 156 additions & 0 deletions src/gccrs/args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
//! This module interprets arguments given to `rustc` and transforms them into valid
//! arguments for `gccrs`.

use std::path::PathBuf;

use getopts::{Matches, Options};

use super::{Error, Result};

/// Crate types supported by `gccrs`
#[derive(Clone, Copy)]
pub enum CrateType {
/// Binary application
Bin,
/// Dynamic library/Shared object
DyLib,
/// Statically linked library
StaticLib,
/// Remaining options, handled by `rustc` but not `gccrs`
Unknown,
}

impl CrateType {
/// Get the corresponding [`CrateType`] from a given option to the `--crate-type`
/// option
pub fn from_str(s: &str) -> CrateType {
match s {
"bin" => CrateType::Bin,
"dylib" => CrateType::DyLib,
"staticlib" => CrateType::StaticLib,
_ => CrateType::Unknown,
}
}
}

fn format_output_filename(
matches: &Matches,
crate_type: CrateType,
) -> Result<(PathBuf, CrateType)> {
// Return an [`Error::InvalidArg`] error if `--crate-name` or `out-dir` weren't
// given as arguments at this point of the translation
let crate_name = matches.opt_str("crate-name").ok_or(Error::InvalidArg)?;
let out_dir = matches.opt_str("out-dir").ok_or(Error::InvalidArg)?;
let c_options = matches.opt_strs("C");

let mut output_file = PathBuf::from(&out_dir);

// FIXME: Horrendous. We need to create a separate "C options" parser since we'll
// probably use more than just `extra-filename`. Issue #6 on Rust-GCC/cargo-gccrs
let extra_filename = c_options
.iter()
.filter_map(|c_opt| {
let mut split = c_opt.split('=');

if let Some("extra-filename") = split.next().as_deref() {
split.next()
} else {
None
}
})
.collect::<Vec<&str>>()[0];

match crate_type {
CrateType::Bin => output_file.push(&format!("{}{}", crate_name, extra_filename)),
CrateType::DyLib => output_file.push(&format!("lib{}.so{}", crate_name, extra_filename)),
CrateType::StaticLib => output_file.push(&format!("lib{}.a{}", crate_name, extra_filename)),
_ => unreachable!(
"gccrs cannot handle other crate types than bin, dylib or staticlib at the moment"
),
}

Ok((output_file, crate_type))
}

/// Structure used to represent arguments passed to `gccrs`. Convert them from `rustc`
/// arguments using [`GccrsArg::from_rustc_arg`]
pub struct GccrsArgs {
source_files: Vec<String>,
crate_type: CrateType,
output_file: PathBuf,
}

impl GccrsArgs {
fn generate_parser() -> Options {
let mut options = Options::new();
options.optopt("", "crate-name", "Name of the crate to compile", "NAME");
options.optopt("", "edition", "Rust edition to use", "YEAR");
options.optopt("", "error-format", "Requested error format", "EXTENSION");
options.optopt(
"",
"out-dir",
"Directory in which to output generated files",
"DIR",
);
options.optopt("", "emit", "Requested output to emit", "KIND");
options.optopt("", "json", "JSON Rendering type", "RENDER");
options.optmulti("C", "", "Extra compiler options", "OPTION[=VALUE]");
options.optmulti(
"L",
"",
"Add a directory to the library's search path",
"KIND[=PATH]",
);
options.optmulti("", "crate-type", "Type of binary to output", "TYPE");

options
}

/// Get the corresponding `gccrs` argument from a given `rustc` argument
pub fn from_rustc_args(rustc_args: &[String]) -> Result<Vec<GccrsArgs>> {
let options = GccrsArgs::generate_parser();

// Parse arguments, skipping `cargo-gccrs` and `rustc` in the invocation
let matches = options.parse(&rustc_args[2..])?;

matches
.opt_strs("crate-type")
.iter()
.map(|type_str| CrateType::from_str(&type_str))
.map(|crate_type| format_output_filename(&matches, crate_type))
.map(|result_tuple| {
result_tuple.map(|(output_file, crate_type)| GccrsArgs {
source_files: matches.free.clone(),
crate_type,
output_file,
})
})
.collect()
}

/// Convert a `GccrsArgs` structure into arguments usable to spawn a process
pub fn into_args(self) -> Vec<String> {
let mut args = self.source_files;

// FIXME: How does gccrs behave with non-unicode filenames? Is gcc[rs] available
// on the OSes that support non-unicode filenames?
let output_file = self
.output_file
.to_str()
.expect("Cannot handle non-unicode filenames yet")
.to_owned();

match self.crate_type {
CrateType::Bin => args.append(&mut vec![String::from("-o"), output_file]),
CrateType::DyLib => args.append(&mut vec![
String::from("-shared"),
String::from("-o"),
output_file,
]),
CrateType::StaticLib => unreachable!("Cannot generate static libraries yet"),
_ => {}
}

args
}
}
9 changes: 5 additions & 4 deletions src/gccrs/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

pub struct GccrsConfig;

use super::Result;
use super::{Error, Result};

/// Different kinds of options dumped by `gccrs -frust-dump-target_options`
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -34,8 +34,7 @@ impl DumpedOption {
/// let t_feature = DumpedOption::from_str("target_feature: \"sse\"");
/// ```
pub fn from_str(input: &str) -> Result<DumpedOption> {
use std::io::{Error, ErrorKind};
let invalid_input = Error::new(ErrorKind::InvalidInput, "invalid option dump");
let invalid_input = Error::InvalidCfgDump;

let splitted_input: Vec<&str> = input.split(':').collect();

Expand Down Expand Up @@ -98,7 +97,9 @@ impl GccrsConfig {
const CONFIG_FILENAME: &'static str = "gccrs.target-options.dump";

fn read_options() -> Result<String> {
std::fs::read_to_string(GccrsConfig::CONFIG_FILENAME)
let content = std::fs::read_to_string(GccrsConfig::CONFIG_FILENAME)?;

Ok(content)
}

fn parse(input: String) -> Result<Vec<DumpedOption>> {
Expand Down
33 changes: 33 additions & 0 deletions src/gccrs/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Error type of the `gccrs` abstraction

use std::io::Error as IoError;

use getopts::Fail;

/// Public enum of possible errors
#[derive(Debug)]
pub enum Error {
/// Invalid argument given to `gccrs`
InvalidArg,
/// Invalid config line dumped when executing `gccrs -frust-dump-*`
InvalidCfgDump,
/// Error when compiling a program using `gccrs`
CompileError,
/// IO Error when executing a `gccrs` command
CommandError(IoError),
}

// IO Error should be kept for better debugging
impl From<IoError> for Error {
fn from(e: IoError) -> Self {
Error::CommandError(e)
}
}

// If parsing the options using `getopts` fail, then it was because an unhandled argument
// was given to the translation unit
impl From<Fail> for Error {
fn from(_: Fail) -> Self {
Error::InvalidArg
}
}
53 changes: 49 additions & 4 deletions src/gccrs/mod.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
//! This module aims at abstracting the usage of `gccrs` via Rust code. This is a simple
//! wrapper around spawning a `gccrs` command with various arguments

mod args;
mod config;
mod error;

use args::GccrsArgs;
use config::GccrsConfig;
use error::Error;

use std::process::{Command, ExitStatus, Stdio};

pub struct Gccrs;

pub type Result<T = ()> = std::io::Result<T>;
pub type Result<T = ()> = std::result::Result<T, Error>;

/// Internal type to use when executing commands. The errors should be converted into
/// [`Error`]s using the `?` operator.
type CmdResult<T = ()> = std::io::Result<T>;

impl Gccrs {
fn install() -> Result {
// TODO: Remove this once `gccrs` gets stable releases or packages
unreachable!("cargo-gccrs cannot install gccrs yet")
}

Expand All @@ -29,7 +39,7 @@ impl Gccrs {
}
}

fn dump_config() -> Result<ExitStatus> {
fn dump_config() -> CmdResult<ExitStatus> {
Command::new("gccrs")
.arg("-x")
.arg("rs")
Expand Down Expand Up @@ -61,8 +71,9 @@ impl Gccrs {
}
}

/// Convert arguments given to `rustc` into valid arguments for `gccrs`
pub fn handle_rust_args() -> Result {
fn cfg_print() -> Result {
// FIXME: The output needs to be adapted based on the target triple. For example,
// produce a .dll on windows, etc etc
Gccrs::fake_output(r#"___"#);
Gccrs::fake_output(r#"lib___.rlib"#);
Gccrs::fake_output(r#"lib___.so"#);
Expand All @@ -73,4 +84,38 @@ impl Gccrs {
Gccrs::dump_config()?;
GccrsConfig::display()
}

fn spawn_with_args(args: &[String]) -> CmdResult<ExitStatus> {
Command::new("gccrs").args(args).status()
}

fn compile(args: &[String]) -> Result {
let gccrs_args = GccrsArgs::from_rustc_args(args)?;

gccrs_args
.into_iter()
.map(|arg_set| Gccrs::spawn_with_args(&arg_set.into_args()))
.try_for_each(|exit_status| {
if exit_status?.success() {
Ok(())
} else {
Err(Error::CompileError)
}
})
}

/// Convert arguments given to `rustc` into valid arguments for `gccrs`
pub fn handle_rust_args(args: &[String]) -> Result {
let first_rustc_arg = args.get(2);

match first_rustc_arg.map(|s| s.as_str()) {
// FIXME: Is that true? Should we use getopts and just parse it and convert
// it as well?
// If rustc is invoked with stdin as input, then it's simply to print the
// configuration option in our case, since we are compiling a rust project
// with files and crates
Some("-") => Gccrs::cfg_print(),
_ => Gccrs::compile(args),
}
}
}
14 changes: 6 additions & 8 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,16 @@ fn spawn_as_wrapper() {
}

fn main() {
#[cfg(not(release))]
dbg!(std::env::args());
let args: Vec<String> = std::env::args().collect();

Gccrs::maybe_install().expect("gccrs should be installed");

let first_arg = std::env::args().nth(1);
let first_arg = args.get(1).expect("Invalid arguments");

match first_arg.as_deref() {
Some("gccrs") => spawn_as_wrapper(),
Some("rustc") => {
Gccrs::handle_rust_args().expect("cannot translate rustc arguments into gccrs ones")
}
match first_arg.as_str() {
"gccrs" => spawn_as_wrapper(),
"rustc" => Gccrs::handle_rust_args(&args)
.expect("cannot translate rustc arguments into gccrs ones"),
_ => eprintln!(
"cargo-gccrs should not be invoked directly. Use the `cargo gccrs <...>` subcommand"
),
Expand Down
9 changes: 9 additions & 0 deletions tests/basic_rust_project/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[package]
name = "basic_rust_project"
version = "0.1.0"
authors = ["CohenArthur <arthur.cohen@epita.fr>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
2 changes: 2 additions & 0 deletions tests/basic_rust_project/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fn main() {
}

0 comments on commit 2cfc3db

Please sign in to comment.