set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cmd="${1:-help}"
shift || true
case "$cmd" in
init)
name=""
path=""
while [[ $# -gt 0 ]]; do
case "$1" in
--name)
name="${2:-}"
shift 2
;;
--path)
path="${2:-}"
shift 2
;;
-*)
shift
;;
*)
if [[ -z "$name" ]]; then
name="$1"
fi
shift
;;
esac
done
if [[ -z "$path" ]]; then
path="$(pwd)"
fi
dest=""
if [[ -n "$name" ]]; then
dest="$path/$name"
else
if [[ -z "$(ls -A "$path" 2>/dev/null)" ]]; then
name="$(basename "$path")"
dest="$path"
else
name="program"
dest="$path/$name"
fi
fi
if [[ -e "$dest" && -n "$(ls -A "$dest" 2>/dev/null)" ]]; then
echo "Refusing to init in non-empty directory: $dest" >&2
exit 1
fi
template_dir="$ROOT_DIR/templates/program"
if [[ ! -d "$template_dir" ]]; then
echo "Template not found: $template_dir" >&2
exit 1
fi
mkdir -p "$dest"
cp -R "$template_dir/." "$dest"
perl -pi -e "s/\\{\\{name\\}\\}/$name/g" "$dest/Cargo.toml"
echo "Initialized willow program in: $dest"
;;
build)
path=""
no_raydium="false"
while [[ $# -gt 0 ]]; do
case "$1" in
--path)
path="${2:-}"
shift 2
;;
--no-raydium)
no_raydium="true"
shift
;;
*)
shift
;;
esac
done
if [[ -z "$path" ]]; then
path="$(pwd)"
fi
get_name() {
local cargo_toml="$1/Cargo.toml"
awk -F'=' '
/^\[package\]/ {in_pkg=1; next}
/^\[/ {in_pkg=0}
in_pkg && $1 ~ /^[[:space:]]*name[[:space:]]*$/ {
gsub(/[[:space:]\"]/, "", $2);
print $2;
exit
}
' "$cargo_toml"
}
ensure_program_id() {
local program_path="$1"
local name="$2"
local deploy_dir="$program_path/target/deploy"
local keypair_path="$deploy_dir/${name}-keypair.json"
local program_id_rs="$program_path/src/program_id.rs"
mkdir -p "$deploy_dir"
if [[ ! -f "$keypair_path" ]]; then
solana-keygen new --no-bip39-passphrase -o "$keypair_path" -s >/dev/null
fi
local pubkey
if [[ "$keypair_path" == *" "* ]]; then
local tmp_keypair
tmp_keypair="$(mktemp)"
cp "$keypair_path" "$tmp_keypair"
pubkey="$(solana-keygen pubkey "$tmp_keypair")"
rm -f "$tmp_keypair"
else
pubkey="$(solana-keygen pubkey "$keypair_path")"
fi
if [[ ! -f "$program_id_rs" ]]; then
cat > "$program_id_rs" <<EOF
// Auto-generated by willow. Do not edit.
use pinocchio_pubkey::declare_id;
declare_id!("$pubkey");
EOF
else
perl -pi -e "s/declare_id!\\(\\\"[^\\\"]*\\\"\\)/declare_id!(\\\"$pubkey\\\")/" "$program_id_rs"
fi
}
name="$(get_name "$path")"
ensure_program_id "$path" "$name"
so_path="$path/target/deploy/${name}.so"
before_size=""
if [[ -f "$so_path" ]]; then
before_size="$(stat -f%z "$so_path" 2>/dev/null || stat -c%s "$so_path")"
fi
if [[ "$no_raydium" == "true" ]]; then
cargo build-sbf --manifest-path "$path/Cargo.toml" -- --no-default-features
else
cargo build-sbf --manifest-path "$path/Cargo.toml"
fi
fmt() {
local b="$1"
if (( b >= 1048576 )); then
printf "%.2f MB" "$(awk "BEGIN {print $b/1048576}")"
else
printf "%.2f KB" "$(awk "BEGIN {print $b/1024}")"
fi
}
if [[ -f "$so_path" ]]; then
after_size="$(stat -f%z "$so_path" 2>/dev/null || stat -c%s "$so_path")"
if [[ -n "$before_size" ]]; then
delta=$((after_size - before_size))
sign=""
if (( delta > 0 )); then sign="+"; fi
echo "sbf size: ${before_size} -> ${after_size} bytes (${sign}${delta}) | $(fmt "$before_size") -> $(fmt "$after_size")"
else
echo "sbf size: ${after_size} bytes | $(fmt "$after_size")"
fi
else
echo "sbf size: not found at ${so_path}"
fi
;;
deploy)
path=""
keypair=""
program_id=""
while [[ $# -gt 0 ]]; do
case "$1" in
--path)
path="${2:-}"
shift 2
;;
--keypair)
keypair="${2:-}"
shift 2
;;
--program-id)
program_id="${2:-}"
shift 2
;;
-*)
shift
;;
*)
shift
;;
esac
done
if [[ -z "$path" ]]; then
path="$(pwd)"
fi
get_name() {
local cargo_toml="$1/Cargo.toml"
awk -F'=' '
/^\[package\]/ {in_pkg=1; next}
/^\[/ {in_pkg=0}
in_pkg && $1 ~ /^[[:space:]]*name[[:space:]]*$/ {
gsub(/[[:space:]\"]/, "", $2);
print $2;
exit
}
' "$cargo_toml"
}
name="$(get_name "$path")"
so_path="$path/target/deploy/${name}.so"
if [[ ! -f "$so_path" ]]; then
echo "sbf file not found: $so_path" >&2
exit 1
fi
cmd=(solana program deploy "$so_path")
if [[ -n "$program_id" ]]; then
cmd+=("--program-id" "$program_id")
fi
if [[ -n "$keypair" ]]; then
cmd+=("--keypair" "$keypair")
fi
(cd "$path" && "${cmd[@]}")
;;
gen)
path=""
while [[ $# -gt 0 ]]; do
case "$1" in
--path)
path="${2:-}"
shift 2
;;
*)
shift
;;
esac
done
if [[ -z "$path" ]]; then
path="$(pwd)"
fi
(cd "$path" && cargo build)
;;
help|-h|--help)
echo "willow"
echo "Usage:"
echo " willow init [--name <program_name>] [--path <dir>]"
echo " willow build [--path <program_dir>] [--no-raydium] # build-sbf"
echo " willow deploy [--path <program_dir>] [--keypair <path>] [--program-id <path>]"
echo " willow gen [--path <program_dir>] # host build for codegen"
echo " willow help"
;;
*)
echo "Unknown command: $cmd" >&2
echo "Run: willow help" >&2
exit 1
;;
esac