Skip to content
This repository has been archived by the owner on Aug 24, 2021. It is now read-only.

Add cargo-spatial-codegen tool #49

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
2aaa589
Initial setup logic in project-example
randomPoison Jan 19, 2019
14dda35
Run the schema compiler on all schema files
randomPoison Jan 19, 2019
aae6322
Run protoc and remove temp dir
randomPoison Jan 19, 2019
aa8a226
Simplify arguments for the setup subcommand
randomPoison Jan 20, 2019
6b15455
Update README.md and remove setup.sh
randomPoison Jan 20, 2019
7810b5d
Cleanup logic in main() around setup vs worker
randomPoison Jan 20, 2019
b2b611b
Fix slashes in paths in README.md
randomPoison Jan 20, 2019
3245abb
Make setup its own binary in spatialos-sdk-tools
randomPoison Jan 21, 2019
e705498
Change globbing logic to match original bash script
randomPoison Jan 22, 2019
9ffccf3
Fixup error handling
randomPoison Jan 22, 2019
57bf2d0
Cleanup spacing and comments
randomPoison Jan 22, 2019
52f7a3d
Cleanup error handling in example
randomPoison Jan 22, 2019
e7d788e
Add clearer comments in example
randomPoison Jan 22, 2019
9041848
Rename schema_dir to output_dir
randomPoison Jan 22, 2019
f713abd
Normalize path separators
randomPoison Jan 23, 2019
a88d8c0
Run rustfmt
randomPoison Jan 23, 2019
4a9b376
Generate bundle.json as part of setup
randomPoison Jan 24, 2019
1a5171a
Allow user to specify more schema paths
randomPoison Jan 24, 2019
a105262
Fix #[structopt] attribute
randomPoison Jan 25, 2019
877acc2
Fix the --schema_path argument to schema_compiler
randomPoison Jan 25, 2019
b788212
Don't append bin to output_dir
randomPoison Jan 25, 2019
0c89058
Merge branch 'master' into example-setup-script
randomPoison Jan 29, 2019
0f20be4
Add logging to setup
randomPoison Jan 29, 2019
926f78f
Run code generation as part of setup
randomPoison Jan 29, 2019
d3d08b1
Delete codegen.sh
randomPoison Jan 29, 2019
dd1275a
Make the output dir a flag, update docs
randomPoison Jan 29, 2019
db68932
Remove leading ./ from paths because it trips up schema_compiler
randomPoison Jan 29, 2019
5ef7ca3
Update docs and comments for setup
randomPoison Jan 29, 2019
ad94734
Delete and ignore generated bindings
randomPoison Jan 29, 2019
661412d
Fix .gitignore for generated bindings
randomPoison Jan 29, 2019
1cc6817
Rename setup to cargo-spatial-codegen
randomPoison Jan 31, 2019
5baba10
Add add_schemas helper function
randomPoison Jan 31, 2019
73477f5
Remove invocation of protoc
randomPoison Jan 31, 2019
8f2a71e
Cleanup error handling for code generation
randomPoison Jan 31, 2019
3ec7231
Actually check the exit status of schema_compiler
randomPoison Jan 31, 2019
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
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,26 @@

This is still heavily WIP and should be treated as such. Progress can be seen in the `Projects` boards which define milestones and progress toward those milestones. When this reaches feature parity with the C API and has basic code generation in which all user facing APIs are safe, a crate will be published to `crates.io`.

## Setup
## Setup

1. Clone this repository.
2. Run `cargo run --bin download_sdk -- -d dependencies -s 13.5.1` to download the C API dependencies.
3. Set the `SPATIAL_LIB_DIR` environment variable to the location of the dependencies: `export SPATIAL_LIB_DIR=$(pwd)/dependencies`.
4. Run `cargo build`
4. Run `cargo build`

If these steps complete successfully, the `spatialos-sdk` crate has been built and linked successfully and can be used in user code.

## Running the Example Project

To run the example project, you will need to:

1. Run the code generator - `./spatialos-sdk/examples/project-example/codegen.sh`
2. Build a release version of the RustWorker - `cargo build --example project-example --release`.
3. In two terminals:
- Navigate to the `spatialos` directory and start spatial: `cd spatialos-sdk/examples/project-example/spatialos/ && spatial local launch`
- Run the example project worker - `cargo run --example project-example -- receptionist --worker_type RustWorker`
1. Install the setup tool: `cargo install --path ./spatialos-sdk-tools --bin cargo-spatial-codegen`
2. Navigate to `spatialos-sdk/examples/project-example/spatialos`
3. Run the setup process: `cargo spatial-codegen -s ./schema -c ../generated_code.rs -o ./schema/bin`
4. Build a release version of the RustWorker: `cargo build --example project-example --release`
5. In two terminals:
- Start spatial: `spatial local launch`
- Run the example project worker: `cargo run --example project-example -- receptionist --worker_type RustWorker`

## Testing the code generator

Expand Down
9 changes: 9 additions & 0 deletions spatialos-sdk-tools/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,17 @@ path = "src/download_sdk/main.rs"
name = "generate_bindings"
path = "src/generate_bindings/main.rs"

[[bin]]
name = "cargo-spatial-codegen"
path = "src/codegen/main.rs"

[dependencies]
zip = "0.5"
clap = "2.32.0"
glob = "0.2"
bindgen = "0.42"
structopt = "0.2.14"
tap = "0.3.0"
log = "0.4.1"
simplelog = "0.5.3"
spatialos-sdk-code-generator = { path = "../spatialos-sdk-code-generator" }
173 changes: 173 additions & 0 deletions spatialos-sdk-tools/src/codegen/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
use log::*;
use simplelog::*;
use spatialos_sdk_code_generator::{generator, schema_bundle};
use std::ffi::OsString;
use std::fs::{self, File};
use std::io::prelude::*;
use std::path::*;
use std::process::Command;
use structopt::StructOpt;
use tap::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let Opt {
spatial_lib_dir,
schema_paths,
codegen,
verbose,
output_dir,
} = Opt::from_args();

// Initialize the logger.
let verbosity = if verbose {
LevelFilter::Trace
} else {
LevelFilter::Warn
};
SimpleLogger::init(verbosity, Default::default()).expect("Failed to setup logger");

let output_dir = normalize(output_dir);

let spatial_lib_dir = spatial_lib_dir
.or_else(|| std::env::var("SPATIAL_LIB_DIR").map(Into::into).ok())
.ok_or("--spatial-lib-dir argument must be specified or the SPATIAL_LIB_DIR environment variable must be set")?;

// Determine the paths the the schema compiler and protoc relative the the lib
// dir path.
let schema_compiler_path = normalize(spatial_lib_dir.join("schema-compiler/schema_compiler"));
let std_lib_path = normalize(spatial_lib_dir.join("std-lib"));

// Calculate the various output directories relative to `output_dir`.
let bundle_json_path = output_dir.join("bundle.json");

// Create the output directories if they don't already exist.
fs::create_dir_all(&output_dir)
.map_err(|_| format!("Failed to create {}", output_dir.display()))?;

// Run the schema compiler for each of the schema files in std-lib/improbable.
let schema_path_arg = OsString::from("--schema_path=").tap(|arg| arg.push(&std_lib_path));
let bundle_json_arg =
OsString::from("--bundle_json_out=").tap(|arg| arg.push(&bundle_json_path));
let descriptor_out_arg = OsString::from("--descriptor_set_out=")
.tap(|arg| arg.push(normalize(output_dir.join("schema.descriptor"))));
let mut command = Command::new(&schema_compiler_path);
command
.arg(&schema_path_arg)
.arg(&bundle_json_arg)
.arg(&descriptor_out_arg)
.arg("--load_all_schema_on_schema_path");

for schema_path in &schema_paths {
let arg = OsString::from("--schema_path=").tap(|arg| arg.push(normalize(schema_path)));
command.arg(&arg);
}

// Add all schema files in the std lib.
add_schemas(&std_lib_path.join("improbable"), &mut command);

// Add all user-provided schemas.
for schema_path in &schema_paths {
add_schemas(schema_path, &mut command);
}

trace!("{:#?}", command);
let status = command
.status()
.map_err(|_| "Failed to compile schema files")?;

if !status.success() {
return Err("Failed to run schema compilation")?;
}

// If the user specified the `--codegen` flag, run code generation with the bundle file
// that we just generated.
if let Some(codegen_out_path) = codegen {
// Load bundle.json, which describes the schema definitions for all components.
let mut input_file =
File::open(&bundle_json_path).map_err(|_| "Failed to open bundle.json")?;
let mut contents = String::new();
input_file
.read_to_string(&mut contents)
.map_err(|_| "Failed to read contents of bundle.json")?;

// Run code generation.
let bundle = schema_bundle::load_bundle(&contents)
.map_err(|_| "Failed to parse contents of bundle.json")?;
let generated_file = generator::generate_code(bundle);

// Write the generated code to the output file.
File::create(codegen_out_path)
.map_err(|_| "Unable to create codegen output file")?
.write_all(generated_file.as_bytes())
.map_err(|_| "Failed to write generated code to file")?;
}

Ok(())
}

#[derive(Debug, StructOpt)]
#[structopt(
name = "cargo-spatial-codegen",
rename_all = "kebab-case",
about = "Perform schema compilation and code generation for a Rust SpatialOS project."
)]
struct Opt {
/// The path to your local installation of the SpatialOS SDK
///
/// If not specified, uses the SPATIAL_OS_DIR environment variable instead. Will fail
/// with an error if neither is set.
#[structopt(long, short = "l", parse(from_os_str))]
spatial_lib_dir: Option<PathBuf>,

/// A directory to search for schema files
///
/// The directory will be searched recursively for all .schema files. Any schema
/// files found will be included in compilation. Can be specified multiple times,
/// e.g. `setup -s foo/schemas -s bar/schemas -o schemas/bin`.
#[structopt(long = "schema-path", short = "s", parse(from_os_str))]
schema_paths: Vec<PathBuf>,

/// Display detailed log output
#[structopt(long, short)]
verbose: bool,

/// Perform code generation and put the output in the specified file
///
/// If not specified, will not perform code generation.
#[structopt(long, short = "c", parse(from_os_str))]
codegen: Option<PathBuf>,

/// The path the output directory for the project
#[structopt(long, short, parse(from_os_str))]
output_dir: PathBuf,
}

/// HACK: Normalizes the separators in `path`.
///
/// This is necessary in order to be robust on Windows, as well as work around
/// some idiosyncrasies with schema_compiler and protoc. Currently,
/// schema_compiler and protoc get tripped up if you have paths with mixed path
/// separators (i.e. mixing '\' and '/'). This function normalizes paths to use
/// the same separators everywhere, ensuring that we can be robust regardless of
/// how the user specifies their paths. It also removes any current dir segments
/// ("./"), as they can trip up schema_compiler and protoc as well.
///
/// Improbable has noted that they are aware of these issues and will fix them
/// at some point in the future.
fn normalize<P: AsRef<std::path::Path>>(path: P) -> PathBuf {
path.as_ref()
.components()
.filter(|&comp| comp != Component::CurDir)
.collect()
}

/// Recursively searches `path` for `.schema` files and adds them to `command`.
fn add_schemas(path: &PathBuf, command: &mut Command) {
let schema_glob = path.join("**/*.schema");
for entry in glob::glob(schema_glob.to_str().unwrap())
.unwrap()
.filter_map(Result::ok)
{
command.arg(&entry);
}
}
5 changes: 4 additions & 1 deletion spatialos-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ path = "./examples/project-example/main.rs"

[dev-dependencies]
uuid = { version = "0.7", features = ["v4"] }
clap = "2.32.0"
clap = "2.32.0"
glob = "0.2.11"
fs_extra = "1.1.0"
tap = "0.3.0"
2 changes: 2 additions & 0 deletions spatialos-sdk/examples/project-example/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generated bindings to SpatialOS components, shouldn't be committed.
generated_code.rs
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an argument that while codegen is under heavy development, having some example code checked in makes it a lot easier to review rather than t4 templates

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that's a fair point, though I think it would make more sense to have small, controlled examples as part of the spatialos-sdk-code-generator crate, rather than using the example project for that purpose. That said, I'm happy to restore generated_code.rs if you'd prefer 🙂

25 changes: 0 additions & 25 deletions spatialos-sdk/examples/project-example/codegen.sh

This file was deleted.

Loading