Skip to content
Open
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
72 changes: 66 additions & 6 deletions .github/workflows/test-setup-new-project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ jobs:
include:
- flow_name: default
ixa_branch: ""
- flow_name: release
template: ""
- flow_name: parameters
ixa_branch: ""
template: parameters
- flow_name: release-blank
ixa_branch: release
template: blank

steps:
- uses: actions/checkout@v7
Expand All @@ -36,16 +41,71 @@ jobs:
echo "PROJECT_DIR=$project_dir" >> "$GITHUB_ENV"
cd "$project_dir"

if [ -z "${{ matrix.ixa_branch }}" ]; then
curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh
else
curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh -s "${{ matrix.ixa_branch }}"
setup_args=()
if [ -n "${{ matrix.ixa_branch }}" ]; then
setup_args+=("${{ matrix.ixa_branch }}")
fi
if [ -n "${{ matrix.template }}" ]; then
setup_args+=(--template "${{ matrix.template }}")
fi

IXA_SETUP_ASSET_BASE_URL="file://$GITHUB_WORKSPACE" \
sh "$GITHUB_WORKSPACE/scripts/setup_new_ixa_project.sh" "${setup_args[@]}"
test -f rust-toolchain.toml

- name: cargo fmt
run: cargo fmt --check
working-directory: ${{ env.PROJECT_DIR }}

- name: cargo test
run: cargo test
working-directory: ${{ env.PROJECT_DIR }}

- name: cargo run
run: cargo run
working-directory: ${{ env.PROJECT_DIR }}

- name: cargo clippy
run: cargo clippy
run: cargo clippy -- -D warnings
working-directory: ${{ env.PROJECT_DIR }}

invalid-arguments:
name: invalid setup arguments
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v7

- name: Reject invalid template arguments before project creation
shell: bash
run: |
set -euo pipefail
project_dir="$(mktemp -d)"
script="$GITHUB_WORKSPACE/scripts/setup_new_ixa_project.sh"
cd "$project_dir"

assert_rejected() {
local name="$1"
local expected="$2"
shift 2

if sh "$script" "$@" > "$name.log" 2>&1; then
echo "$name unexpectedly succeeded"
exit 1
fi
grep -F "$expected" "$name.log"
test ! -e Cargo.toml
}

assert_rejected unknown-template \
"Unknown template 'unknown'" --template unknown
assert_rejected missing-template \
"Missing value for --template" --template
assert_rejected unknown-option \
"Unknown option: --unknown" --unknown
assert_rejected multiple-branches \
"Only one Ixa branch may be specified" main release

sh "$script" --help > help.log
grep -F -- "--template <name>" help.log
test ! -e Cargo.toml
27 changes: 25 additions & 2 deletions docs/book/src/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ cargo new --bin ixa_model
cd ixa_model
```

Use Ixa's new project setup script to setup the project for Ixa.
Use Ixa's new project setup script to set up the project for Ixa. By default,
the script installs the blank starter used by this guide.

```bash
curl -s https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh -s
curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh
```

The script also installs Ixa's `rust-toolchain.toml`. When Cargo is managed by
rustup, this file automatically selects or installs the compatible Rust
toolchain.

Open `src/main.rs` in your favorite editor or IDE to verify the model looks like
the following:

Expand All @@ -43,3 +48,21 @@ To run with logging enabled for just `ixa_model`:
```bash
cargo run -- --log-level ixa_model=trace
```

## Other templates

When creating a new project, you can select the `parameters` template to start
with a parameters module, parameter validation, and basic global property
integration:

```bash
curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh |
sh -s -- --template parameters
```

This template creates `src/main.rs` and `src/parameters.rs`. Running
`cargo run` validates and stores the default parameters, then prints:

```text
The current time is 100
```
196 changes: 150 additions & 46 deletions scripts/setup_new_ixa_project.sh
Original file line number Diff line number Diff line change
@@ -1,71 +1,175 @@
# To be run in a new project directory.
#!/bin/sh
#
# Run in a new project directory.
#
# Install the default blank template:
# curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh
# or if you want to use a specific branch and not the cargo release
# curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh -s <ixa-branch>
# ixa-branch: the branch of Ixa to use, default is main
#
# Install the parameters template:
# curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh -s -- --template parameters
#
# Use a specific Ixa branch instead of the released crate:
# curl -s -f -L https://raw.githubusercontent.com/CDCgov/ixa/main/scripts/setup_new_ixa_project.sh | sh -s -- <ixa-branch> --template <template-name>

ixa_branch="main"
ixa_branch_was_set="false"
template="blank"

usage() {
printf '%s\n' \
"Usage: setup_new_ixa_project.sh [ixa-branch] [--template <template-name>]" \
"" \
"Set up a new Ixa project in the current directory." \
"" \
"Arguments:" \
" ixa-branch Optional Ixa Git branch. Uses the released crate when omitted." \
"" \
"Options:" \
" --template <name> Project template: blank (default) or parameters." \
" -h, --help Show this help message."
}

fail() {
printf 'Error: %s\n' "$1" >&2
printf "Run 'setup_new_ixa_project.sh --help' for usage.\n" >&2
exit 2
}

urlencode() {
local tmp="${1}"
local encoded=""

while [ -n "$tmp" ]; do
rest="${tmp#?}" # All but the first character of the string
first="${tmp%"$rest"}" # Remove $rest, and you're left with the first character
case "$first" in
[a-zA-Z0-9.~_-]) encoded="$encoded$first" ;;
*) encoded="$encoded$(printf '%%%02X' "'$first")" ;;
urlencode_tmp=$1
urlencode_encoded=""

while [ -n "$urlencode_tmp" ]; do
urlencode_rest="${urlencode_tmp#?}"
urlencode_first="${urlencode_tmp%"$urlencode_rest"}"
case "$urlencode_first" in
[a-zA-Z0-9.~_-])
urlencode_encoded="$urlencode_encoded$urlencode_first"
;;
*)
urlencode_encoded="$urlencode_encoded$(printf '%%%02X' "'$urlencode_first")"
;;
esac
tmp="$rest"
urlencode_tmp=$urlencode_rest
done
echo "$encoded"
printf '%s\n' "$urlencode_encoded"
}

if [ -n "$1" ]; then
ixa_branch=$(urlencode $1)
while [ "$#" -gt 0 ]; do
case "$1" in
--template)
if [ "$#" -lt 2 ] || [ -z "$2" ]; then
fail "Missing value for --template."
fi
template=$2
shift 2
;;
-h | --help)
usage
exit 0
;;
--)
shift
;;
-*)
fail "Unknown option: $1"
;;
*)
if [ "$ixa_branch_was_set" = "true" ]; then
fail "Only one Ixa branch may be specified."
fi
ixa_branch=$1
ixa_branch_was_set="true"
shift
;;
esac
done

case "$template" in
blank | parameters) ;;
*)
fail "Unknown template '$template'. Expected 'blank' or 'parameters'."
;;
esac

ixa_asset_ref=$(urlencode "$ixa_branch")
# CI overrides the asset base so pull requests test files from their checkout.
if [ -n "${IXA_SETUP_ASSET_BASE_URL:-}" ]; then
ixa_asset_base_url=${IXA_SETUP_ASSET_BASE_URL%/}
else
ixa_asset_base_url="https://raw.githubusercontent.com/CDCgov/ixa/$ixa_asset_ref"
fi

# function to check if last shell command was successful, if not print input message and exit
check_success() {
if [ $? -ne 0 ]; then
echo $1
exit
download_asset() {
download_source=$1
download_destination=$2
download_description=$3

if ! curl -s -f -L -o "$download_destination" \
"$ixa_asset_base_url/$download_source"; then
printf 'Failed to download %s from Ixa.\n' "$download_description" >&2
exit 1
fi
}

echo "Setting up new Ixa project with branch $ixa_branch"
printf 'Setting up new Ixa project with branch %s and template %s\n' \
"$ixa_branch" "$template"

# check if cargo is installed
if [ -z "$(command -v cargo)" ]; then
echo "cargo could not be found, run:"
echo "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh"
exit
printf '%s\n' \
"cargo could not be found, run:" \
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh" >&2
exit 1
fi

# check if cargo.toml does not exists
# Install the selected branch's toolchain configuration before invoking Cargo
# so rustup can select or install the compatible Rust toolchain automatically.
download_asset "rust-toolchain.toml" "rust-toolchain.toml" \
"rust-toolchain.toml"

if [ ! -f "Cargo.toml" ]; then
echo "Creating Cargo project"
cargo init
printf 'Creating Cargo project\n'
if ! cargo init; then
printf 'Failed to create Cargo project.\n' >&2
exit 1
fi
fi

if [ -z "$1" ]; then
cargo add ixa
if [ "$ixa_branch_was_set" = "false" ]; then
if ! cargo add ixa; then
printf 'Failed to add the released Ixa crate.\n' >&2
exit 1
fi
else
cargo add --git "https://github.com/CDCgov/ixa" ixa --branch $ixa_branch
if ! cargo add --git "https://github.com/CDCgov/ixa" \
--branch "$ixa_branch" ixa; then
printf 'Failed to add Ixa from branch %s.\n' "$ixa_branch" >&2
exit 1
fi
fi

# add .gitignore from Ixa
curl -s -f -o .gitignore "https://raw.githubusercontent.com/CDCgov/ixa/$ixa_branch/.gitignore"
check_success "Failed to download .gitignore from Ixa"

# add the clippy.toml from Ixa
curl -s -f -o clippy.toml https://raw.githubusercontent.com/CDCgov/ixa/$ixa_branch/clippy.toml
check_success "Failed to download clippy.toml from Ixa"
download_asset ".gitignore" ".gitignore" ".gitignore"
download_asset "clippy.toml" "clippy.toml" "clippy.toml"

# override main.rs with Ixa basic example
curl -s -f -o src/main.rs https://raw.githubusercontent.com/CDCgov/ixa/$ixa_branch/examples/basic/main.rs
check_success "Failed to download main.rs from Ixa"
case "$template" in
blank)
download_asset "examples/basic/main.rs" "src/main.rs" \
"the blank template"
;;
parameters)
if ! cargo add serde --features derive; then
printf 'Failed to add Serde for the parameters template.\n' >&2
exit 1
fi
download_asset "scripts/templates/parameters/main.rs" "src/main.rs" \
"the parameters template main.rs"
download_asset "scripts/templates/parameters/parameters.rs" \
"src/parameters.rs" "the parameters template parameters.rs"
;;
esac

echo "Project setup complete from branch $ixa_branch"
echo "Run 'cargo run' to test the example code"
echo "Check out the Ixa documentation for more examples and usage: https://ixa.rs/"
printf 'Project setup complete from branch %s with template %s\n' \
"$ixa_branch" "$template"
printf "%s\n" \
"Run 'cargo run' to test the example code" \
"Check out the Ixa documentation for more examples and usage: https://ixa.rs/"
31 changes: 31 additions & 0 deletions scripts/templates/parameters/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
mod parameters;

use ixa::prelude::*;

use crate::parameters::Parameters;

define_global_property!(Params, Parameters);

fn main() {
let parameters = Parameters::default();
parameters.validate().expect("invalid Parameters");

let mut context = Context::new();
context
.set_global_property_value(Params, parameters)
.expect("parameters should only be initialized once");

let (seed, max_time) = {
let parameters = context
.get_global_property_value(Params)
.expect("parameters should be initialized");
(parameters.seed, parameters.max_time)
};

context.init_random(seed);
context.add_plan(max_time, |context| {
println!("The current time is {}", context.get_current_time());
context.shutdown();
});
context.execute();
}
Loading