diff --git a/devtools/setup-dev/ansible/BUILD.bazel b/devtools/setup-dev/ansible/BUILD.bazel index 92e33b2..4bceeeb 100644 --- a/devtools/setup-dev/ansible/BUILD.bazel +++ b/devtools/setup-dev/ansible/BUILD.bazel @@ -106,10 +106,10 @@ sh_test( "all.yml", "bazel.yml", "bazel-affected-tests.yml", - "biome.yml", "buf.yml", "buildifier.yml", "buildozer.yml", + "cargo-clippy.yml", "cargo-install-update.yml", "clang-format.yml", "claude.yml", @@ -186,7 +186,9 @@ sh_test( "repo-sync.yml", "rg.yml", "ruff.yml", + "rust-analyzer.yml", "rustc.yml", + "rustfmt.yml", "sed.yml", "setup-bazel.yml", "setup-cargo.yml", @@ -206,6 +208,8 @@ sh_test( "setup-npm.yml", "setup-nvm.yml", "setup-perplexity-mcp.yml", + "setup-rust.yml", + "setup-rust-tools.yml", "setup-shell-profile.yml", "setup-spacemacs.yml", "setup-spacemacs-go.yml", @@ -350,6 +354,20 @@ sh_test( ], ) +sh_test( + name = "cargo-clippy_syntax_test", + srcs = ["test_ansible_syntax.sh"], + args = ["cargo-clippy.yml"], + data = [ + "cargo-clippy.yml", + "setup-rust.yml", + ], + tags = [ + "ansible", + "local", + ], +) + sh_test( name = "cargo-install-update_syntax_test", srcs = ["test_ansible_syntax.sh"], @@ -1701,6 +1719,20 @@ sh_test( ], ) +sh_test( + name = "rust-analyzer_syntax_test", + srcs = ["test_ansible_syntax.sh"], + args = ["rust-analyzer.yml"], + data = [ + "rust-analyzer.yml", + "setup-rust.yml", + ], + tags = [ + "ansible", + "local", + ], +) + sh_test( name = "rustc_syntax_test", srcs = ["test_ansible_syntax.sh"], @@ -1715,6 +1747,20 @@ sh_test( ], ) +sh_test( + name = "rustfmt_syntax_test", + srcs = ["test_ansible_syntax.sh"], + args = ["rustfmt.yml"], + data = [ + "rustfmt.yml", + "setup-rust.yml", + ], + tags = [ + "ansible", + "local", + ], +) + sh_test( name = "sed_syntax_test", srcs = ["test_ansible_syntax.sh"], @@ -1871,13 +1917,11 @@ sh_test( srcs = ["test_ansible_syntax.sh"], args = ["setup-devtools.yml"], data = [ - "biome.yml", "buf.yml", "cargo-install-update.yml", "clang-format.yml", "cleanup-repo.yml", "curl.yml", - "eslint.yml", "fd.yml", "gh.yml", "gmi.yml", @@ -1890,8 +1934,6 @@ sh_test( "locate.yml", "man.yml", "notmuch.yml", - "npm.yml", - "prettier.yml", "protoc.yml", "protoc-gen-go.yml", "protoc-gen-go-grpc.yml", @@ -1903,7 +1945,6 @@ sh_test( "setup-cargo.yml", "setup-devtools.yml", "setup-grpc.yml", - "setup-npm.yml", "setup-shell-profile.yml", "setup-user-bin-directory.yml", "setup-user-go-bin-directory.yml", @@ -2225,6 +2266,34 @@ sh_test( ], ) +sh_test( + name = "setup-rust_syntax_test", + srcs = ["test_ansible_syntax.sh"], + args = ["setup-rust.yml"], + data = ["setup-rust.yml"], + tags = [ + "ansible", + "local", + ], +) + +sh_test( + name = "setup-rust-tools_syntax_test", + srcs = ["test_ansible_syntax.sh"], + args = ["setup-rust-tools.yml"], + data = [ + "cargo-clippy.yml", + "rust-analyzer.yml", + "rustfmt.yml", + "setup-rust.yml", + "setup-rust-tools.yml", + ], + tags = [ + "ansible", + "local", + ], +) + sh_test( name = "setup-shell-profile_syntax_test", srcs = ["test_ansible_syntax.sh"], @@ -2838,6 +2907,7 @@ test_suite( ":buildifier_syntax_test", ":buildozer_syntax_test", ":cargo-add_syntax_test", + ":cargo-clippy_syntax_test", ":cargo-install-update_syntax_test", ":cargo-outdated_syntax_test", ":check-jsonschema_syntax_test", @@ -2933,7 +3003,9 @@ test_suite( ":repo-sync_syntax_test", ":rg_syntax_test", ":ruff_syntax_test", + ":rust-analyzer_syntax_test", ":rustc_syntax_test", + ":rustfmt_syntax_test", ":sed_syntax_test", ":semgrep_syntax_test", ":setup-bazel_syntax_test", @@ -2961,6 +3033,8 @@ test_suite( ":setup-nvm_syntax_test", ":setup-pass_syntax_test", ":setup-perplexity-mcp_syntax_test", + ":setup-rust-tools_syntax_test", + ":setup-rust_syntax_test", ":setup-shell-profile_syntax_test", ":setup-spacemacs-go_syntax_test", ":setup-spacemacs-python_syntax_test", diff --git a/devtools/setup-dev/ansible/README.org b/devtools/setup-dev/ansible/README.org index 05025fc..5aa1238 100644 --- a/devtools/setup-dev/ansible/README.org +++ b/devtools/setup-dev/ansible/README.org @@ -51,8 +51,15 @@ so it's usually a good idea to use it for Go projects. The generator includes automatic version checking and upgrade logic for Go packages. *** macOS -Homebrew is the preferred package manager for macOS, with support for taps and -installation options when needed. +Homebrew is the most preferred installation method on macOS because it provides +system-level packages, which is what this setup-dev/ansible setup generally +aims for. Even when alternative installation methods exist (e.g., =rustup= for +Rust tools, =cargo= for Rust packages), Homebrew is preferred when available +because it integrates with the system package management and ensures consistent +tooling across the development environment. + +Homebrew also supports taps and installation options when needed for specialized +packages. To regenerate the playbooks, simply run: #+begin_src sh @@ -216,6 +223,8 @@ These files are playbooks not generated from =generate_packages.go=: - setup-nvm.yml - setup-pass.yml - setup-perplexity-mcp.yml +- setup-rust.yml +- setup-rust-tools.yml - setup-shell-profile.yml - setup-spacemacs.yml - setup-spacemacs-go.yml diff --git a/devtools/setup-dev/ansible/all.yml b/devtools/setup-dev/ansible/all.yml index 3750bb5..c794545 100644 --- a/devtools/setup-dev/ansible/all.yml +++ b/devtools/setup-dev/ansible/all.yml @@ -16,5 +16,6 @@ - import_playbook: setup-wrangler.yml - import_playbook: setup-swift-tools.yml when: ansible_facts['os_family'] == "Darwin" -- import_playbook: setup-devtools.yml +- import_playbook: setup-rust-tools.yml - import_playbook: setup-sql-tools.yml +- import_playbook: setup-devtools.yml diff --git a/devtools/setup-dev/ansible/cargo-clippy.yml b/devtools/setup-dev/ansible/cargo-clippy.yml new file mode 100644 index 0000000..6a8796c --- /dev/null +++ b/devtools/setup-dev/ansible/cargo-clippy.yml @@ -0,0 +1,56 @@ +--- +# THIS FILE IS AUTO-GENERATED by generate_packages.go - DO NOT EDIT MANUALLY +# To make changes, modify generate_packages.go, install_methods.go, packages_data.go, or templates.go +# and then run: make +- import_playbook: setup-rust.yml + +- name: Ensure cargo-clippy is present + hosts: all + tasks: + - name: Include guard for cargo-clippy playbook + block: + - name: Stop early if the cargo-clippy playbook is already included + meta: end_play + when: cargo_clippy_playbook_imported is defined + - name: Ensure the cargo-clippy playbook is not included + set_fact: + cargo_clippy_playbook_imported: true + when: cargo_clippy_playbook_imported is not defined + + - name: Ensure cargo-clippy is present on MacOS + block: + - name: Check if cargo-clippy is installed + shell: command -v cargo-clippy + changed_when: False + rescue: + - name: Install cargo-clippy on MacOS + community.general.homebrew: + name: clippy + state: present + when: ansible_facts['os_family'] == "Darwin" + + - name: Ensure cargo-clippy is present on Termux + block: + - name: Check if cargo-clippy is installed + shell: command -v cargo-clippy + register: cargo_clippy_installed + ignore_errors: yes + changed_when: False + + - name: Install clippy component via rustup + command: rustup component add clippy + when: cargo_clippy_installed.rc != 0 + when: ansible_facts['env']['TERMUX_VERSION'] is defined + + - name: Install cargo-clippy via rustup-component on Debian/Ubuntu + block: + - name: Check if cargo-clippy is installed + shell: command -v cargo-clippy + register: cargo_clippy_installed + ignore_errors: yes + changed_when: False + + - name: Install clippy component via rustup + command: rustup component add clippy + when: cargo_clippy_installed.rc != 0 + when: ansible_facts['env']['TERMUX_VERSION'] is not defined and ansible_facts['os_family'] != "Darwin" diff --git a/devtools/setup-dev/ansible/ensure.sh b/devtools/setup-dev/ansible/ensure.sh index b4d9e30..2e7bb0b 100755 --- a/devtools/setup-dev/ansible/ensure.sh +++ b/devtools/setup-dev/ansible/ensure.sh @@ -9,6 +9,7 @@ PKG_CACHE="$CACHE_DIR/termux-pkg-upgrade" BREW_CACHE="$CACHE_DIR/brew-update" NALA_CACHE="$CACHE_DIR/nala-upgrade" PIP_CACHE="$CACHE_DIR/pip" +RUSTUP_CACHE="$CACHE_DIR/rustup-update" ANSIBLE_GALAXY_CACHE="$CACHE_DIR/ansible-galaxy-collection" # Detect OS @@ -171,6 +172,16 @@ else fi fi +# Update rustup if installed (upgrades rust toolchain and all components) +# Only update if not done in the last 24 hours +if command -v rustup >/dev/null 2>&1; then + if [ ! -f "$RUSTUP_CACHE" ] || [ "$(find "$RUSTUP_CACHE" -mtime +1 2>/dev/null | wc -l)" -gt 0 ]; then + echo "Updating Rust toolchain via rustup..." + rustup update + touch "$RUSTUP_CACHE" + fi +fi + # Install community.general collection if not already installed if [ ! -f "$ANSIBLE_GALAXY_CACHE" ] || [ "$(find "$ANSIBLE_GALAXY_CACHE" -mtime +1 2>/dev/null | wc -l)" -gt 0 ]; then ansible-galaxy collection install community.general diff --git a/devtools/setup-dev/ansible/install_methods.go b/devtools/setup-dev/ansible/install_methods.go index 889a96e..84ff8c0 100644 --- a/devtools/setup-dev/ansible/install_methods.go +++ b/devtools/setup-dev/ansible/install_methods.go @@ -270,6 +270,43 @@ func (g GoInstallMethod) RenderBlockInstallTask(command string) string { return indent(g.RenderInstallTask(command), 4) } +// RustupComponentMethod handles installation of Rust components via rustup. +// Components like clippy, rustfmt, and rust-analyzer are installed this way. +// Upgrades are handled by `rustup update` in ensure.sh, so this only checks +// if the component is installed. +type RustupComponentMethod struct { + Name string +} + +func (r RustupComponentMethod) GetMethodType() string { + return "rustup-component" +} + +func (r RustupComponentMethod) GetImports() []Import { + return []Import{{Playbook: "setup-rust"}} +} + +func (r RustupComponentMethod) RenderSetupTasks(_ string) string { + return "" +} + +func (r RustupComponentMethod) RenderInstallTask(command string) string { + commandID := strings.ReplaceAll(command, "-", "_") + return ` - name: Check if ` + command + ` is installed + shell: command -v ` + command + ` + register: ` + commandID + `_installed + ignore_errors: yes + changed_when: False + + - name: Install ` + r.Name + ` component via rustup + command: rustup component add ` + r.Name + ` + when: ` + commandID + `_installed.rc != 0` +} + +func (r RustupComponentMethod) RenderBlockInstallTask(command string) string { + return indent(r.RenderInstallTask(command), 4) +} + // CargoInstallMethod handles installation via Rust cargo command. // Includes update logic using cargo-install-update. type CargoInstallMethod struct { diff --git a/devtools/setup-dev/ansible/packages_data.go b/devtools/setup-dev/ansible/packages_data.go index 9d50378..8e6dfc0 100644 --- a/devtools/setup-dev/ansible/packages_data.go +++ b/devtools/setup-dev/ansible/packages_data.go @@ -109,6 +109,15 @@ var platformSpecificTools = []PlatformSpecificTool{ }, Imports: nil, }, + { + command: "cargo-clippy", + platforms: map[PlatformName]InstallMethod{ + PlatformDarwin: BrewInstallMethod{Name: "clippy"}, + PlatformDebianLike: RustupComponentMethod{Name: "clippy"}, + PlatformTermux: RustupComponentMethod{Name: "clippy"}, + }, + Imports: nil, + }, { command: "cargo-install-update", platforms: map[PlatformName]InstallMethod{ @@ -383,6 +392,24 @@ rm -rf $TMPDIR/codex }, Imports: nil, }, + { + command: "rust-analyzer", + platforms: map[PlatformName]InstallMethod{ + PlatformDarwin: BrewInstallMethod{Name: "rust-analyzer"}, + PlatformDebianLike: RustupComponentMethod{Name: "rust-analyzer"}, + PlatformTermux: RustupComponentMethod{Name: "rust-analyzer"}, + }, + Imports: nil, + }, + { + command: "rustfmt", + platforms: map[PlatformName]InstallMethod{ + PlatformDarwin: BrewInstallMethod{Name: "rustfmt"}, + PlatformDebianLike: RustupComponentMethod{Name: "rustfmt"}, + PlatformTermux: RustupComponentMethod{Name: "rustfmt"}, + }, + Imports: nil, + }, { command: "semgrep", platforms: map[PlatformName]InstallMethod{ diff --git a/devtools/setup-dev/ansible/rust-analyzer.yml b/devtools/setup-dev/ansible/rust-analyzer.yml new file mode 100644 index 0000000..dd16a38 --- /dev/null +++ b/devtools/setup-dev/ansible/rust-analyzer.yml @@ -0,0 +1,56 @@ +--- +# THIS FILE IS AUTO-GENERATED by generate_packages.go - DO NOT EDIT MANUALLY +# To make changes, modify generate_packages.go, install_methods.go, packages_data.go, or templates.go +# and then run: make +- import_playbook: setup-rust.yml + +- name: Ensure rust-analyzer is present + hosts: all + tasks: + - name: Include guard for rust-analyzer playbook + block: + - name: Stop early if the rust-analyzer playbook is already included + meta: end_play + when: rust_analyzer_playbook_imported is defined + - name: Ensure the rust-analyzer playbook is not included + set_fact: + rust_analyzer_playbook_imported: true + when: rust_analyzer_playbook_imported is not defined + + - name: Ensure rust-analyzer is present on MacOS + block: + - name: Check if rust-analyzer is installed + shell: command -v rust-analyzer + changed_when: False + rescue: + - name: Install rust-analyzer on MacOS + community.general.homebrew: + name: rust-analyzer + state: present + when: ansible_facts['os_family'] == "Darwin" + + - name: Ensure rust-analyzer is present on Termux + block: + - name: Check if rust-analyzer is installed + shell: command -v rust-analyzer + register: rust_analyzer_installed + ignore_errors: yes + changed_when: False + + - name: Install rust-analyzer component via rustup + command: rustup component add rust-analyzer + when: rust_analyzer_installed.rc != 0 + when: ansible_facts['env']['TERMUX_VERSION'] is defined + + - name: Install rust-analyzer via rustup-component on Debian/Ubuntu + block: + - name: Check if rust-analyzer is installed + shell: command -v rust-analyzer + register: rust_analyzer_installed + ignore_errors: yes + changed_when: False + + - name: Install rust-analyzer component via rustup + command: rustup component add rust-analyzer + when: rust_analyzer_installed.rc != 0 + when: ansible_facts['env']['TERMUX_VERSION'] is not defined and ansible_facts['os_family'] != "Darwin" diff --git a/devtools/setup-dev/ansible/rustfmt.yml b/devtools/setup-dev/ansible/rustfmt.yml new file mode 100644 index 0000000..880956d --- /dev/null +++ b/devtools/setup-dev/ansible/rustfmt.yml @@ -0,0 +1,56 @@ +--- +# THIS FILE IS AUTO-GENERATED by generate_packages.go - DO NOT EDIT MANUALLY +# To make changes, modify generate_packages.go, install_methods.go, packages_data.go, or templates.go +# and then run: make +- import_playbook: setup-rust.yml + +- name: Ensure rustfmt is present + hosts: all + tasks: + - name: Include guard for rustfmt playbook + block: + - name: Stop early if the rustfmt playbook is already included + meta: end_play + when: rustfmt_playbook_imported is defined + - name: Ensure the rustfmt playbook is not included + set_fact: + rustfmt_playbook_imported: true + when: rustfmt_playbook_imported is not defined + + - name: Ensure rustfmt is present on MacOS + block: + - name: Check if rustfmt is installed + shell: command -v rustfmt + changed_when: False + rescue: + - name: Install rustfmt on MacOS + community.general.homebrew: + name: rustfmt + state: present + when: ansible_facts['os_family'] == "Darwin" + + - name: Ensure rustfmt is present on Termux + block: + - name: Check if rustfmt is installed + shell: command -v rustfmt + register: rustfmt_installed + ignore_errors: yes + changed_when: False + + - name: Install rustfmt component via rustup + command: rustup component add rustfmt + when: rustfmt_installed.rc != 0 + when: ansible_facts['env']['TERMUX_VERSION'] is defined + + - name: Install rustfmt via rustup-component on Debian/Ubuntu + block: + - name: Check if rustfmt is installed + shell: command -v rustfmt + register: rustfmt_installed + ignore_errors: yes + changed_when: False + + - name: Install rustfmt component via rustup + command: rustup component add rustfmt + when: rustfmt_installed.rc != 0 + when: ansible_facts['env']['TERMUX_VERSION'] is not defined and ansible_facts['os_family'] != "Darwin" diff --git a/devtools/setup-dev/ansible/setup-devtools.yml b/devtools/setup-dev/ansible/setup-devtools.yml index 6226639..aa152ab 100644 --- a/devtools/setup-dev/ansible/setup-devtools.yml +++ b/devtools/setup-dev/ansible/setup-devtools.yml @@ -3,9 +3,6 @@ - import_playbook: sed.yml - import_playbook: grep.yml - import_playbook: man.yml -- import_playbook: eslint.yml -- import_playbook: prettier.yml -- import_playbook: biome.yml - import_playbook: gh.yml - import_playbook: jira.yml - import_playbook: tmux.yml diff --git a/devtools/setup-dev/ansible/setup-rust-tools.yml b/devtools/setup-dev/ansible/setup-rust-tools.yml new file mode 100644 index 0000000..bf102a0 --- /dev/null +++ b/devtools/setup-dev/ansible/setup-rust-tools.yml @@ -0,0 +1,4 @@ +--- +- import_playbook: rustfmt.yml +- import_playbook: cargo-clippy.yml +- import_playbook: rust-analyzer.yml diff --git a/devtools/setup-dev/ansible/setup-rust.yml b/devtools/setup-dev/ansible/setup-rust.yml new file mode 100644 index 0000000..5a143c4 --- /dev/null +++ b/devtools/setup-dev/ansible/setup-rust.yml @@ -0,0 +1,27 @@ +--- +- name: Setup Rust development environment + hosts: all + tasks: + - name: Check if rustup is installed + command: which rustup + register: rustup_check + ignore_errors: true + changed_when: false + + - name: Install rustup on macOS + community.general.homebrew: + name: rustup-init + state: present + when: ansible_facts['os_family'] == "Darwin" and rustup_check.rc != 0 + + - name: Initialize rustup on macOS + command: rustup-init -y --no-modify-path + when: ansible_facts['os_family'] == "Darwin" and rustup_check.rc != 0 + + - name: Install rustup on Debian + shell: curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path + when: ansible_facts['os_family'] == "Debian" and rustup_check.rc != 0 + + - name: Install rustup on Termux + shell: "pkg install -y rust && rustup-init -y --no-modify-path" + when: ansible_facts['env']['TERMUX_VERSION'] is defined and rustup_check.rc != 0