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

Support workspaces at library level #197

Merged
merged 17 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ jobs:
- uses: actions-rs/[email protected]
with:
command: fmt
args: --all -- --check
args: --all -- --check
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,20 @@ create-rust-app create <project_name>

- **Tasks Plugin**
- For running background jobs, currently only supports actix-web and postgresql
- Uses [`fang`](https://github.com/ayrat555/fang) under the hood and all it's features are exposed.
- Uses [`fang`](https://github.com/ayrat555/fang) under the hood and all it's features are exposed.
- Add a task to the queue with `create_rust_app::tasks::queue()`
- Run the queue with `cargo run --bin tasks`

- **Workspace Support Plugin** (not supported in the CLI yet)
- allows you to organize your rust app in workspaces, and changes the defaults for the environment variables that specify paths to various important places.
- to organize you project as a workspace:
- enable this feature
- refactor your codebase into workspaces (see [#194](https://github.com/Wulf/create-rust-app/issues/194))
- Optional: set the following environment variables (paths are relative to the directory you call cargo fullstack/backend/run from)
- `CRA_MANIFEST_PATH`: default `./frontend/dist/manifest.json` when called from workspace root, `../frontend/dist/manifest.json` otherwise.
- `CRA_FRONTEND_DIR`: default `./frontend` when called from workspace root, `../frontend` otherwise.
- `CRA_VIEWS_GLOB`: default `backend/views/\*\*/\*.html` when called from workspace root, `views/\*\*/\*.html` otherwise.

### 2. Code-gen to reduce boilerplate

````sh
Expand Down
1 change: 1 addition & 0 deletions create-rust-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ plugin_storage = [
plugin_graphql = []
plugin_utoipa = ["utoipa", "backend_actix-web"]
plugin_tasks = ["fang"]
plugin_workspace_support = []
backend_poem = ["poem", "anyhow", "mime_guess", "tokio"]
backend_actix-web = [
"actix-web",
Expand Down
8 changes: 8 additions & 0 deletions create-rust-app/src/dev/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,15 @@ fn get_features(project_dir: &'static str) -> Vec<String> {
let cargo_toml = Manifest::from_path(PathBuf::from_iter([project_dir, "Cargo.toml"]))
.unwrap_or_else(|_| panic!("Could not find \"{}\"", project_dir));
// .expect(&format!("Could not find \"{project_dir}\""));
#[cfg(not(feature = "plugin_workspace_support"))]
let deps = cargo_toml.dependencies;
#[cfg(feature = "plugin_workspace_support")]
let mut deps = cargo_toml.dependencies;
#[cfg(feature = "plugin_workspace_support")]
if let Some(workspace) = cargo_toml.workspace {
// if the manifest has a workspace table, also read dependencies in there
deps.extend(workspace.dependencies);
}
let dep = deps.get("create-rust-app").unwrap_or_else(|| {
panic!(
"Expected \"{}\" to list 'create-rust-app' as a dependency.",
Expand Down
7 changes: 4 additions & 3 deletions create-rust-app/src/util/actix_web_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::str::FromStr;
use std::sync::Mutex;

use super::template_utils::SinglePageApplication;
use super::workspace_utils::FRONTEND_DIR;
use crate::util::template_utils::{to_template_name, DEFAULT_TEMPLATE, TEMPLATES};
use actix_files::NamedFile;
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -102,13 +103,13 @@ pub async fn render_views(req: HttpRequest) -> HttpResponse {
#[cfg(debug_assertions)]
{
// dev asset serving
let asset_path = &format!("./frontend{path}");
let asset_path = &format!("{frontend_dir}{path}", frontend_dir = *FRONTEND_DIR);
if std::path::PathBuf::from(asset_path).is_file() {
println!("ASSET_FILE {path} => {asset_path}");
return NamedFile::open(asset_path).unwrap().into_response(&req);
}

let public_path = &format!("./frontend/public{path}");
let public_path = &format!("{frontend_dir}/public{path}", frontend_dir = *FRONTEND_DIR);
if std::path::PathBuf::from(public_path).is_file() {
println!("PUBLIC_FILE {path} => {public_path}");
return NamedFile::open(public_path).unwrap().into_response(&req);
Expand All @@ -118,7 +119,7 @@ pub async fn render_views(req: HttpRequest) -> HttpResponse {
#[cfg(not(debug_assertions))]
{
// production asset serving
let static_path = &format!("./frontend/dist{path}");
let static_path = &format!("{frontend_dir}/dist{path}", frontend_dir = *FRONTEND_DIR);
if std::path::PathBuf::from(static_path).is_file() {
return NamedFile::open(static_path).unwrap().into_response(&req);
}
Expand Down
3 changes: 3 additions & 0 deletions create-rust-app/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
/// are exposed directly as create_rust_app::<utilty-fn>.
///

/// constants for paths and files in workspaces
pub(crate) mod workspace_utils;

#[cfg(feature = "backend_actix-web")]
mod actix_web_utils;

Expand Down
7 changes: 4 additions & 3 deletions create-rust-app/src/util/poem_utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::Mutex;

use super::workspace_utils::FRONTEND_DIR;
use poem::http::{StatusCode, Uri};
use poem::middleware::{AddData, AddDataEndpoint};
use poem::web::Data;
Expand Down Expand Up @@ -95,14 +96,14 @@ pub async fn render_views(uri: &Uri) -> impl IntoResponse {
#[cfg(debug_assertions)]
{
// dev asset serving
let asset_path = &format!("./frontend{path}");
let asset_path = &format!("{frontend_dir}{path}", frontend_dir = *FRONTEND_DIR);
if std::path::PathBuf::from(asset_path).is_file() {
println!("ASSET_FILE {path} => {asset_path}");

return file_response(asset_path).await;
}

let public_path = &format!("./frontend/public{path}");
let public_path = &format!("{frontend_dir}/public{path}", frontend_dir = *FRONTEND_DIR);
if std::path::PathBuf::from(public_path).is_file() {
println!("PUBLIC_FILE {path} => {public_path}");

Expand All @@ -113,7 +114,7 @@ pub async fn render_views(uri: &Uri) -> impl IntoResponse {
#[cfg(not(debug_assertions))]
{
// production asset serving
let static_path = &format!("./frontend/dist{path}");
let static_path = &format!("{frontend_dir}/dist{path}", frontend_dir = *FRONTEND_DIR);
if std::path::PathBuf::from(static_path).is_file() {
return file_response(static_path).await;
}
Expand Down
5 changes: 3 additions & 2 deletions create-rust-app/src/util/template_utils.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::workspace_utils::{MANIFEST_PATH, VIEWS_GLOB};
use lazy_static::lazy_static;
use std::collections::HashMap;
use tera::Tera;
Expand All @@ -12,7 +13,7 @@ lazy_static! {
/// all the Templates (html files) included in backend/views/..., uses Tera to bundle the frontend into the template
/// TODO: ensure this is accurate documentation
pub static ref TEMPLATES: Tera = {
let mut tera = match Tera::new("backend/views/**/*.html") {
let mut tera = match Tera::new(VIEWS_GLOB.as_str()) {
Ok(t) => t,
Err(e) => {
println!("Parsing error(s): {e}");
Expand Down Expand Up @@ -152,7 +153,7 @@ fn load_manifest_entries() -> ViteManifest {

use serde_json::Value;
let manifest_json = serde_json::from_str(
std::fs::read_to_string(std::path::PathBuf::from("./frontend/dist/manifest.json"))
std::fs::read_to_string(std::path::PathBuf::from(MANIFEST_PATH.as_str()))
.unwrap()
.as_str(),
)
Expand Down
84 changes: 84 additions & 0 deletions create-rust-app/src/util/workspace_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
use std::path::{Path, PathBuf};

use lazy_static::lazy_static;

lazy_static!(
pub static ref WORKSPACE_DIR: PathBuf = {
let output = std::process::Command::new(env!("CARGO"))
.arg("locate-project")
.arg("--workspace")
.arg("--message-format=plain")
.output()
.unwrap()
.stdout;
let cargo_path = Path::new(std::str::from_utf8(&output).unwrap().trim());
cargo_path.parent().unwrap().to_path_buf()
// .to_str()
// .unwrap()
// .to_owned()
};
Copy link
Owner

@Wulf Wulf Sep 28, 2023

Choose a reason for hiding this comment

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

the rest looks good to me but here we're assuming we'll have the project's source code. That's not necessarily the case (see #332 -- we split the build and binary container into two). We also don't want to go in this direction as it stops us from attempting a "single binary" build in the future as well.

It does point out that there's some existing tech debt here that needs to be addressed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should we have it stored in an environment variable instead? Similar to frontenddir

It's just that we need a way to know where the base of the project is and that's not always the current working directory

Copy link
Owner

Choose a reason for hiding this comment

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

Yeah. Some more thoughts:

I think we need to remove the feature flag. In create-rust-app/src/dev/mod.rs, we should check if it's a workspace using the Cargo.toml instead of relying on a feature flag.

Also, all the variables in this file should be &'static str and be initialized to some default unless the backing environment variable is present (and they should all be configurable by env vars).

Let me know what you think!

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've implemented most of your suggestions as of the most recent commits (also got rid of some unnecessary unwraps), but there's a few things I still need to figure out:

  • a function that can tell us if we're using workspaces (without needing source code) (rather than using the feature flag)
  • a way of finding the workspace root when the (not yet added) CRA_WORKSPACE_ROOT environment variable isn't set

Copy link
Collaborator Author

@AnthonyMichaelTDM AnthonyMichaelTDM Nov 8, 2023

Choose a reason for hiding this comment

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

btw, sorry I've been awol for so long, I was busy at work over the summer and then school's been keeping me busy too, so I haven't had much time.

Copy link
Owner

Choose a reason for hiding this comment

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

sounds awesome -- it's cool to hear about your journey so far :)

Copy link
Owner

Choose a reason for hiding this comment

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

I'll have to take some time out to look into this further

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 I figured something out, high level is that I made "finding the workspace dir" infallible (no panics, even in a container or something), then rethought the logic used in initializing these variables (getting rid of the need for the feature flag)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@Wulf is this resolved?

Copy link
Owner

Choose a reason for hiding this comment

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

beautifully done.

🙌

/// constant for the path to the project's frontend directory
pub(crate) static ref FRONTEND_DIR: String = {
match std::env::var("CRA_FRONTEND_DIR") {
Ok(dir) => dir,
Err(_) => {
#[cfg(not(feature = "plugin_workspace_support"))]
{
"./frontend".to_string()
}
#[cfg(feature = "plugin_workspace_support")]
{
if *WORKSPACE_DIR == std::env::current_dir().unwrap() {
// this is for when cargo run is run from the workspace root
return "./frontend".to_string();
} else {
// this is for when cargo run is run from teh backend directory
return "../frontend".to_string();
}
}
}
}
};
/// constant for the path to the project's manifest.json file
pub(crate) static ref MANIFEST_PATH: String = {
match std::env::var("CRA_MANIFEST_PATH") {
Ok(dir) => dir,
Err(_) => {
#[cfg(not(feature = "plugin_workspace_support"))]
{
"./frontend/dist/manifest.json".to_string()
}
#[cfg(feature = "plugin_workspace_support")]
{
if *WORKSPACE_DIR == std::env::current_dir().unwrap() {
return "./frontend/dist/manifest.json".to_string();
} else {
return "../frontend/dist/manifest.json".to_string();
}
}
}
}
};
/// constant for the path to the project's views directory
pub(crate) static ref VIEWS_GLOB: String = {
match std::env::var("CRA_VIEWS_GLOB") {
Ok(dir) => dir,
Err(_) => {
#[cfg(not(feature = "plugin_workspace_support"))]
{
"backend/views/**/*.html".to_string()
}
#[cfg(feature = "plugin_workspace_support")]
{
if *WORKSPACE_DIR == std::env::current_dir().unwrap() {
return "backend/views/**/*.html".to_string();
} else {
return "views/**/*.html".to_string();
}
}
}
}
};


);