Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build Description File #523

Merged
merged 17 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions book/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
- [RuSTy](./intro_1.md)
- [Build and Install](./build_and_install.md)
- [Using RuSTy](./using_rusty.md)
- [Build descrpition File](using_rusty/build_description_file.md)
- [Writing ST Programs]()
- [Libraries](libraries.md)
- [External Functions](libraries/external_functions.md)
Expand Down
2 changes: 1 addition & 1 deletion book/src/using_rusty.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ written as [glob patterns](https://en.wikipedia.org/wiki/Glob_(programming)).
Note that you can only specify at most one output format. In the case that no output
format switch has been specified, the compiler will select `--static` by default.

Similarily, if you do not specify an output filename via the `-o` or `--output` options,
Similarly, if you do not specify an output filename via the `-o` or `--output` options,
the output filename will consist of the first input filename, but with an appropriate
file extension depending on the output file format. A minimal invocation looks like this:

Expand Down
122 changes: 122 additions & 0 deletions book/src/using_rusty/build_description_files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Build description File

In addition to the comprehensive help, `rustyc` offers a build description file, that simplifies the build process. Instead of having numerous inline arguments, using the build description file makes passing the arguments easier and neater. The build description file needs to be safed as a [json](https://en.wikipedia.org/wiki/JSON) format.

`rustyc build [Config]`

Note that if `rustyc` cannot find the `plc.json` file, it will throw an error and request the path. The default location for the build file is the current directory. The command for building with an additional path looks like this:

`rustyc build src/plc.json`


# Plc.json

For the build description file to work, the build description file must be the [json](https://en.wikipedia.org/wiki/JavaScript_Object_Notation) format. All the keys used in the build description file are described in the following sections.


## files

The key `files` is the equivalent to the `input` parameter, which adds all the `.st` files that needs to be compiled. The value of `files` is an array of strings, definied as followed:
```json
"files" : [
"examples/hello_world.st",
"examples/hw.st"
]
```


## optimization

`rustyc` offers 4 levels of optimization which correspond to the levels established by llvm respectively [clang](https://clang.llvm.org/docs/CommandGuide/clang.html#code-generation-options) (`none` to `agressive`).
To use an optimization, the key `optimization` is required:
- `"optimization" : "none"`
- `"optimization" : "less"`
- `"optimization" : "default"`
- `"optimization" : "aggressive"`

By default `rustyc` will use `default` which corresponds to clang's `-o2`.


## error_format

`rustyc` offers 2 levels of formatting errors. To specify which error format is wanted, the key `error_format` is required:
- `"error_format" : "Clang"` This is used to get fewer error messaged
- `"error_format" : "Rich"` This is used to get a verbose error description.


## libraries

To link several executables `rustyc` has the option to add libraries and automatically build and like them together. if no compile type has been selected `rustyc` will link the files on default.

```json
"libraries" : [
{
"name" : "iec61131std",
"path" : "path/to/lib/",
"include_path" : [
"examples/hw.st",
"examples/hello_world.st"
]
}
]
```

## output

Similarly to specifying an output file via the `-o` or `--output` option, in the build file we use `"output" : "output.so"` to define the output file. The default location is likewise to the location for the build file, namely the current directory.



## Optional Keys
### sysroot

`rustyc` is using the `sysroot` key for linking purposes. It is considered to be the root directory for the purpose of locating headers and libraries.


### target

To build and compile [structured text](https://en.wikipedia.org/wiki/Structured_text) for the rigth platform we need to specify the `target`. As `rustyc` is using [LLVM](https://en.wikipedia.org/wiki/LLVM) a target-tripple supported by LLVM needs to be selected. The default `target` is `x86_64-linux-gnu`.


### compile_type
There are six options for choosing the `compile_type`. The valid options are:
<!-- TODO we should probably describe what each of those options do -->
- `Static` bindings have to be done at compile time
- `PIC` Position Independent Code
- `Shared` (dynamic) binginds will be done dynamically
- `Relocatable` generates Relocatable
- `Bitcode` adds bitcode alongside machine code in executable file
- `IR` Intermediate Representation

To specify which of the above mentioned compile formats is wanted, it needs to be added to the build description file as followed: `"compile_type" : "Shared"`.

# Example
```json
{
"files" : [
"examples/hw.st",
"examples/hello_world.st",
"examples/ExternalFunctions.st"
],
"compile_type" : "Shared",
"optimization" : "Default",
"output" : "proj.so",
"error_format": "Rich",
"libraries" : [
{
"name" : "iec61131std",
"path" : "path/to/lib",
"include_path" : [
"examples/hw.st"
]
},
{
"name" : "other_lib",
"path" : "path/to/lib",
"include_path" : [
"examples/hello_world.st"
]
}
]
}
```
27 changes: 27 additions & 0 deletions examples/plc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"files" : [
"examples/hw.st",
"examples/hello_world.st",
"examples/ExternalFunctions.st"
],
"compile_type" : "Shared",
"optimization" : "Default",
"output" : "proj.so",
"error_format": "Rich",
"libraries" : [
{
"name" : "iec61131std",
"path" : "",
"include_path" : [
"examples/hw.st"
]
},
{
"name" : "other_iec61131std",
"path" : "examples/hw.st",
"include_path" : [
"examples/hello_world.st"
]
}
]
}
91 changes: 91 additions & 0 deletions src/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use crate::diagnostics::Diagnostic;
use crate::diagnostics::ErrNo;
use crate::ErrorFormat;
use crate::FilePath;
use crate::FormatOption;
use crate::OptimizationLevel;
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;

#[derive(Serialize, Deserialize)]
pub struct Libraries {
pub name: String,
pub path: String,
pub include_path: Vec<String>,
}

#[derive(Serialize, Deserialize)]
pub struct Proj {
pub files: Vec<String>,
pub compile_type: Option<FormatOption>,
pub optimization: Option<OptimizationLevel>,
pub output: String,
pub error_format: ErrorFormat,
pub libraries: Option<Vec<Libraries>>,
}

pub fn get_project_from_file(build_config: Option<String>) -> Result<Proj, Diagnostic> {
let filepath = build_config.unwrap_or_else(|| String::from("plc.json"));

//read from file
let content = fs::read_to_string(filepath);

let content = match content {
Ok(file_content) => file_content,
Err(e) => {
return Err(Diagnostic::GeneralError {
message: e.to_string(),
err_no: ErrNo::general__io_err,
})
}
};

//convert file to Object
let project = serde_json::from_str(&content);
let project: Proj = match project {
Ok(project) => project,
Err(_e) => {
return Err(Diagnostic::GeneralError {
message: String::from(r#"An error occured whilest parsing!"#),
err_no: ErrNo::general__io_err,
})
}
};

let project = get_path_when_empty(project)?;
Ok(project)
}

pub fn string_to_filepath(content: Vec<String>) -> Vec<FilePath> {
let mut filepath: Vec<FilePath> = vec![];
for item in content {
filepath.push(FilePath::from(item));
}
filepath
}

fn get_path_when_empty(p: Proj) -> Result<Proj, Diagnostic> {
if let Some(ref libraries) = p.libraries {
for library in libraries {
let path = if library.path.is_empty() {
None
} else {
Some(&library.path)
};
let path = path
.map_or(Path::new("."), Path::new)
.join(&format!("lib{}.so", library.name));
if !Path::new(&path).is_file() {
return Err(Diagnostic::GeneralError {
message: format!(
"The library could not be found at : {}",
path.to_string_lossy()
),
err_no: ErrNo::general__io_err,
});
}
}
}
Ok(p)
}
61 changes: 57 additions & 4 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2021 Ghaith Hachem and Mathias Rieder
use clap::{ArgGroup, Parser};
use clap::{ArgGroup, Parser, Subcommand};
use encoding_rs::Encoding;
use std::{ffi::OsStr, path::Path};

Expand All @@ -17,6 +17,8 @@ pub type ParameterError = clap::Error;
about = "IEC61131-3 Structured Text compiler powered by Rust & LLVM ",
version,
)]
#[clap(propagate_version = true)]
#[clap(subcommand_negates_reqs = true)]
pub struct CompileParameters {
#[clap(
short,
Expand Down Expand Up @@ -104,7 +106,7 @@ pub struct CompileParameters {
short = 'L',
help = "Search path for libraries, used for linking"
)]
pub library_pathes: Vec<String>,
pub library_paths: Vec<String>,

#[clap(name = "library", long, short = 'l', help = "Library name to link")]
pub libraries: Vec<String>,
Expand Down Expand Up @@ -148,6 +150,31 @@ pub struct CompileParameters {
default_value = "rich"
)]
pub error_format: ErrorFormat,

#[clap(subcommand)]
pub commands: Option<SubCommands>,
}

#[derive(Debug, Subcommand)]
pub enum SubCommands {
/// Uses build description file.
/// Supported format: json build <plc.json> --sysroot <sysroot> --target <target-triple>
Build {
#[clap(
parse(try_from_str = validate_config)
)]
build_config: Option<String>,

#[clap(long, name = "sysroot", help = "Path to system root, used for linking")]
sysroot: Option<String>,

#[clap(
long,
name = "target-triple",
help = "A target-tripple supported by LLVM"
)]
target: Option<String>,
},
}

fn parse_encoding(encoding: &str) -> Result<&'static Encoding, String> {
Expand Down Expand Up @@ -248,7 +275,7 @@ impl CompileParameters {

#[cfg(test)]
mod cli_tests {
use super::CompileParameters;
use super::{CompileParameters, SubCommands};
use crate::{ConfigFormat, ErrorFormat, FormatOption, OptimizationLevel};
use clap::ErrorKind;
use pretty_assertions::assert_eq;
Expand Down Expand Up @@ -552,7 +579,7 @@ mod cli_tests {
"-L/tmp"
))
.unwrap();
assert_eq!(parameters.library_pathes, vec!["xxx", "test", ".", "/tmp"]);
assert_eq!(parameters.library_paths, vec!["xxx", "test", ".", "/tmp"]);
}

#[test]
Expand Down Expand Up @@ -585,6 +612,32 @@ mod cli_tests {
}
}

#[test]
fn build_subcommand() {
let parameters = CompileParameters::parse(vec_of_strings!(
"build",
"src/ProjectPlc.json",
"--sysroot",
"systest",
"--target",
"targettest"
))
.unwrap();
if let Some(commands) = parameters.commands {
match commands {
SubCommands::Build {
build_config,
sysroot,
target,
} => {
assert_eq!(build_config, Some("src/ProjectPlc.json".to_string()));
assert_eq!(sysroot, Some("systest".to_string()));
assert_eq!(target, Some("targettest".to_string()));
}
};
}
}

#[test]
fn sysroot_added() {
let parameters =
Expand Down
Loading