Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "star-setup"
version = "0.3.0"
version = "0.3.1"
edition = "2021"
repository = "https://github.com/star-setup/core"
description = "Lightweight CLI to clone, configure, and wire single or multi-repo ecosystems"
Expand Down
53 changes: 34 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,37 @@ cargo install --git https://github.com/star-setup/core

## Usage

### Flags
#### Connection
| Flag | Description |
|------|-------------|
| `--ssh` | Clone via SSH instead of HTTPS |
| `--https` | Force HTTPS (default) |
| `--verbose` | Print commands as they run |

#### Build
| Flag | Description |
|------|-------------|
| `--build-type <TYPE>` | Build type: `Debug` (default) or `Release` |
| `--build-dir <DIR>` | Build output directory (default: `build`) |
| `--no-build` | Configure only, skip build step |
| `--clean` | Remove build directory before configuring |
| `--cmake-arg <ARG>` | Pass additional argument to CMake |
| `--meson-arg <ARG>` | Pass additional argument to Meson |

#### Mono-Repo
| Flag | Description |
|------|-------------|
| `--repos <REPOS>...` | List of dependency repositories |
| `--mono-dir <DIR>` | Workspace directory (default: `build-mono`) |
| `--profile <NAME>` | Use a saved profile |

#### Diagnostic
| Flag | Description |
|------|-------------|
| `--dry-run` | Print what would happen without making any changes |
| `--timing` | Show timing for each phase |

### Interactive Mode
Running `star-setup` without arguments launches interactive mode, guiding you through all options.

Expand All @@ -83,35 +114,19 @@ Interactive mode complete

### Single Repository Mode
```bash
# Clone and build via HTTPS
# Clone and build using a single repository
star-setup username/repo

# Clone and build via SSH
star-setup username/repo --ssh

# Common flags
star-setup username/repo --build-type Release
star-setup username/repo --build-dir out
star-setup username/repo --no-build
star-setup username/repo --clean
star-setup username/repo --verbose
star-setup username/repo --timing
star-setup username/repo --cmake-arg=-DCMAKE_CXX_COMPILER=clang++
star-setup username/repo --meson-arg=-Db_lto=true
```

### Mono-Repo Mode
Clones multiple repositories into a single workspace and auto-detects the build system. For CMake projects, generates a root `CMakeLists.txt` wiring all repositories as subdirectories. For Meson projects, generates a root `meson.build` and auto-generates local `.wrap` files bridging canonical dependency names to cloned directories.

```bash
# Manual repo list
# Clone and build a test repo and a manual repo list
star-setup username/repo --repos user/lib1 user/lib2

# Use a saved profile
# Clone and build a test repo and a saved profile
star-setup username/repo --profile myprofile

# With SSH and custom directory
star-setup username/repo --repos user/lib1 user/lib2 --ssh --mono-dir my-workspace
```

#### Workspace Structure (CMake)
Expand Down
3 changes: 3 additions & 0 deletions src/cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,7 @@ pub struct DiagnosticFlags {
/// Show timing information for each phase
#[arg(long)]
pub timing: bool,
/// If set, print commands instead of executing them without making any changes.
#[arg(long)]
pub dry_run: bool,
}
8 changes: 7 additions & 1 deletion src/cli/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ pub fn resolve_with_config(mut args: Args, config: &SetupConfig) -> Result<Resol
default.map(|e| e.timing),
false,
);
let dry_run = resolve_bool(
args.diagnostic.dry_run,
false,
default.map(|e| e.dry_run),
false,
);
let no_build = resolve_bool(
args.build.no_build,
args.build.build,
Expand Down Expand Up @@ -77,7 +83,7 @@ pub fn resolve_with_config(mut args: Args, config: &SetupConfig) -> Result<Resol
repo: args.repo,
yes: args.yes,
connection: ResolvedConnectionFlags { ssh, verbose },
diagnostic: ResolvedDiagnosticFlags { timing },
diagnostic: ResolvedDiagnosticFlags { timing, dry_run },
build: ResolvedBuildFlags {
build_type: if let Some(s) = args.build.build_type {
s.parse::<BuildType>()?
Expand Down
1 change: 1 addition & 0 deletions src/cli/resolved.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct ResolvedConnectionFlags {
/// Resolved diagnostic flags after applying config and CLI overrides.
pub struct ResolvedDiagnosticFlags {
pub timing: bool,
pub dry_run: bool,
}

/// Resolved build flags after applying config and CLI overrides.
Expand Down
91 changes: 61 additions & 30 deletions src/commands/mono/display.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,74 @@
use crate::{ctx::IoCtx, repository::repo_dir_name};
use std::{borrow::Cow, collections::HashMap, path::Path};
use std::{
collections::HashMap,
path::{Path, PathBuf},
};

/// Prints the setup completion summary including paths, executable location, and total timing.
pub fn print_setup_complete<S: std::hash::BuildHasher>(
/// Resolved display paths for the setup completion summary.
pub struct SetupPaths {
/// Canonicalized path to the mono-repo root directory.
pub mono_repo_disp: PathBuf,
/// Canonicalized path to the test repository executable, if found.
pub exe_path: Option<PathBuf>,
/// Canonicalized path to the build output directory, if no canonical map was provided.
pub build_disp: Option<PathBuf>,
}

/// Resolves display paths for setup completion summary.
#[must_use]
pub fn resolve_setup_paths<S: std::hash::BuildHasher>(
canonical_map: Option<&HashMap<String, String, S>>,
mono_repo_path: &Path,
build_path: &Path,
test_repo: &str,
total: std::time::Instant,
io: &mut IoCtx<'_>,
) {
writeln!(io.output, "Setup complete").ok();
) -> SetupPaths {
let mono_repo_disp =
dunce::canonicalize(mono_repo_path).map_or_else(|_| Cow::Borrowed(mono_repo_path), Cow::Owned);
writeln!(io.output, "Repositories in: {}", mono_repo_disp.display()).ok();
dunce::canonicalize(mono_repo_path).unwrap_or_else(|_| mono_repo_path.to_path_buf());

if let Some(map) = canonical_map {
let (exe_path, build_disp) = if let Some(map) = canonical_map {
let test_repo_name = repo_dir_name(test_repo);
if let Some((canonical, _)) = map.iter().find(|(_, v)| *v == &test_repo_name) {
let exe_name = if cfg!(windows) {
format!("{canonical}.exe")
} else {
canonical.clone()
};
let exe_path = build_path
.join("repos")
.join(&test_repo_name)
.join(&exe_name);
writeln!(
io.output,
"Executable: {}",
dunce::canonicalize(&exe_path).unwrap_or(exe_path).display()
)
.ok();
}
let exe_path = map
.iter()
.find(|(_, v)| *v == &test_repo_name)
.map(|(canonical, _)| {
let exe_name = if cfg!(windows) {
format!("{canonical}.exe")
} else {
canonical.clone()
};
let p = build_path
.join("repos")
.join(&test_repo_name)
.join(&exe_name);
dunce::canonicalize(&p).unwrap_or(p)
});
(exe_path, None)
} else {
let build_disp =
dunce::canonicalize(build_path).map_or_else(|_| Cow::Borrowed(build_path), Cow::Owned);
writeln!(io.output, "Build output in: {}", build_disp.display()).ok();
let build_disp = dunce::canonicalize(build_path).unwrap_or_else(|_| build_path.to_path_buf());
(None, Some(build_disp))
};

SetupPaths {
mono_repo_disp,
exe_path,
build_disp,
}
}

/// Prints the setup completion summary.
pub fn print_setup_complete(paths: &SetupPaths, total: std::time::Instant, io: &mut IoCtx<'_>) {
writeln!(io.output, "Setup complete").ok();
writeln!(
io.output,
"Repositories in: {}",
paths.mono_repo_disp.display()
)
.ok();
if let Some(exe) = &paths.exe_path {
writeln!(io.output, "Executable: {}", exe.display()).ok();
}
if let Some(build) = &paths.build_disp {
writeln!(io.output, "Build output in: {}", build.display()).ok();
}
if io.timing {
writeln!(io.output, "[timing] Total: {:.2?}", total.elapsed()).ok();
Expand Down
74 changes: 50 additions & 24 deletions src/commands/mono/mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ use crate::{
cli::{detect_mono_build_system, ResolvedArgs},
commands::{
build_repo_list, configure_and_build, extract_repo_input,
mono::{clone_mono_repos, generate_mono_config, print_setup_complete},
mono::{
clone_mono_repos,
display::{resolve_setup_paths, SetupPaths},
generate_mono_config, print_setup_complete,
},
prepare_build_dir, resolve_repos_for_mono, resolve_test_repo,
},
config::SetupConfig,
Expand Down Expand Up @@ -33,9 +37,18 @@ pub fn mono_repo_mode(

let mono_repo_path = base_dir.join(&args.mono.mono_dir);
let repos_path = mono_repo_path.join("repos");
crate::time!(ctx.io.timing, ctx.io.output, "Create directory", {
fs::create_dir_all(&repos_path).map_err(|e| e.to_string())?;
});
if ctx.io.dry_run {
writeln!(
ctx.io.output,
"Would create directory: {}",
repos_path.display()
)
.ok();
} else {
crate::time!(ctx.io.timing, ctx.io.output, "Create directory", {
fs::create_dir_all(&repos_path).map_err(|e| e.to_string())?;
});
}

clone_mono_repos(&repos, &repos_path, args.connection.ssh, ctx)?;

Expand All @@ -44,28 +57,41 @@ pub fn mono_repo_mode(
.map(|r| repos_path.join(repo_dir_name(r)))
.collect();

let build_system = detect_mono_build_system(&repo_dirs, ctx)?;
let build_path = mono_repo_path.join(&args.build.build_dir);

let canonical_map = generate_mono_config(
build_system,
&mono_repo_path,
&repos_path,
&repo_dirs,
&repos,
ctx,
)?;
let canonical_map = if ctx.io.dry_run {
prepare_build_dir(build_path.as_path(), args.build.clean, ctx)?;
None
} else {
let build_system = detect_mono_build_system(&repo_dirs, ctx)?;
let map = generate_mono_config(
build_system,
&mono_repo_path,
&repos_path,
&repo_dirs,
&repos,
ctx,
)?;
prepare_build_dir(build_path.as_path(), args.build.clean, ctx)?;
configure_and_build(args, &mono_repo_path, &build_path, build_system, true, ctx)?;
map
};

let build_path = mono_repo_path.join(&args.build.build_dir);
prepare_build_dir(build_path.as_path(), args.build.clean, ctx)?;
configure_and_build(args, &mono_repo_path, &build_path, build_system, true, ctx)?;
let paths = if ctx.io.dry_run {
SetupPaths {
mono_repo_disp: mono_repo_path.clone(),
exe_path: None,
build_disp: Some(build_path.clone()),
}
} else {
resolve_setup_paths(
canonical_map.as_ref(),
&mono_repo_path,
&build_path,
&test_repo,
)
};

print_setup_complete(
canonical_map.as_ref(),
&mono_repo_path,
&build_path,
&test_repo,
total,
&mut ctx.io,
);
print_setup_complete(&paths, total, &mut ctx.io);
Ok(())
}
25 changes: 21 additions & 4 deletions src/commands/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,34 @@ pub fn prepare_build_dir(
clean: bool,
ctx: &mut RunCtx<'_>,
) -> Result<(), String> {
if clean && build_path.exists() {
if clean && ctx.io.dry_run {
writeln!(ctx.io.output, "Cleaning build directory\n").ok();
writeln!(
ctx.io.output,
"Would remove directory: {}",
build_path.display()
)
.ok();
} else if clean && build_path.exists() {
writeln!(ctx.io.output, "Cleaning build directory\n").ok();
crate::time!(ctx.io.timing, ctx.io.output, "Clean", {
fs::remove_dir_all(build_path).map_err(|e| e.to_string())?;
});
}

writeln!(ctx.io.output, "Creating build directory\n").ok();
crate::time!(ctx.io.timing, ctx.io.output, "Create build directory", {
fs::create_dir_all(build_path).map_err(|e| e.to_string())?;
});
if ctx.io.dry_run {
writeln!(
ctx.io.output,
"Would create directory: {}",
build_path.display()
)
.ok();
} else {
crate::time!(ctx.io.timing, ctx.io.output, "Create build directory", {
fs::create_dir_all(build_path).map_err(|e| e.to_string())?;
});
}
Ok(())
}

Expand Down
7 changes: 5 additions & 2 deletions src/commands/single.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,11 @@ pub fn single_repo_mode(

let build_path = repo_path.join(&args.build.build_dir);
prepare_build_dir(&build_path, args.build.clean, ctx)?;
let build_system = detect_build_system(&repo_path, ctx)?;
configure_and_build(args, &repo_path, &build_path, build_system, false, ctx)?;

if !ctx.io.dry_run {
let build_system = detect_build_system(&repo_path, ctx)?;
configure_and_build(args, &repo_path, &build_path, build_system, false, ctx)?;
}

writeln!(
ctx.io.output,
Expand Down
Loading
Loading