├── cargo-dist ├── templates │ ├── installer │ │ ├── npm │ │ │ ├── .gitignore │ │ │ ├── install.js │ │ │ ├── run.js.j2 │ │ │ ├── package.json │ │ │ └── binary.js │ │ └── homebrew.rb.j2 │ └── ci │ │ └── github │ │ └── partials │ │ ├── publish_npm.yml.j2 │ │ ├── publish_github.yml.j2 │ │ └── publish_homebrew.yml.j2 ├── src │ ├── tests │ │ └── mod.rs │ ├── backend │ │ ├── installer │ │ │ ├── powershell.rs │ │ │ ├── shell.rs │ │ │ ├── macpkg.rs │ │ │ └── mod.rs │ │ └── mod.rs │ ├── config │ │ └── v1 │ │ │ ├── hosts │ │ │ ├── axodotdev.rs │ │ │ └── github.rs │ │ │ ├── publishers │ │ │ ├── npm.rs │ │ │ ├── user.rs │ │ │ └── homebrew.rs │ │ │ ├── builds │ │ │ └── generic.rs │ │ │ ├── installers │ │ │ ├── msi.rs │ │ │ ├── shell.rs │ │ │ ├── powershell.rs │ │ │ ├── homebrew.rs │ │ │ ├── npm.rs │ │ │ └── pkg.rs │ │ │ └── artifacts │ │ │ ├── mod.rs │ │ │ └── archives.rs │ ├── net.rs │ ├── sign │ │ └── mod.rs │ ├── build │ │ └── fake.rs │ └── platform │ │ └── github_runners.rs ├── tests │ ├── gallery │ │ ├── errors.rs │ │ ├── mod.rs │ │ └── dist │ │ │ ├── tools.rs │ │ │ └── homebrew.rs │ ├── build_setup.yml │ └── snapshots │ │ ├── error_manifest.snap │ │ ├── short-help.snap │ │ ├── lib_manifest.snap │ │ └── lib_manifest_slash.snap ├── oranda.json └── Cargo.toml ├── axoproject ├── tests │ └── projects │ │ ├── generic-workspace │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── generic1 │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── main.c │ │ │ ├── Makefile │ │ │ └── dist.toml │ │ ├── dist-workspace.toml │ │ └── generic2 │ │ │ ├── main.c │ │ │ ├── Makefile │ │ │ └── dist.toml │ │ ├── shared-workspace │ │ ├── LICENSE.txt │ │ ├── README.md │ │ ├── CHANGELOG.md │ │ ├── generic1 │ │ │ ├── CHANGELOG.md │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ ├── main.c │ │ │ ├── Makefile │ │ │ └── dist.toml │ │ ├── generic2 │ │ │ ├── main.c │ │ │ ├── Makefile │ │ │ └── dist.toml │ │ └── dist-workspace.toml │ │ ├── cargo-new │ │ ├── src │ │ │ └── main.rs │ │ ├── Cargo.lock │ │ └── Cargo.toml │ │ ├── cargo-nonvirtual │ │ ├── src │ │ │ ├── main.rs │ │ │ └── bin │ │ │ │ └── cargo-nonvirtual.rs │ │ ├── crates │ │ │ ├── test-bin │ │ │ │ ├── src │ │ │ │ │ └── main.rs │ │ │ │ └── Cargo.toml │ │ │ ├── some-lib │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ │ └── lib.rs │ │ │ ├── some-other-lib │ │ │ │ ├── Cargo.toml │ │ │ │ └── src │ │ │ │ │ └── lib.rs │ │ │ ├── some-cdylib │ │ │ │ ├── src │ │ │ │ │ └── lib.rs │ │ │ │ └── Cargo.toml │ │ │ └── some-staticlib │ │ │ │ ├── src │ │ │ │ └── lib.rs │ │ │ │ └── Cargo.toml │ │ ├── Cargo.toml │ │ └── Cargo.lock │ │ ├── cargo-virtual │ │ ├── virtual │ │ │ ├── src │ │ │ │ └── main.rs │ │ │ └── Cargo.toml │ │ ├── virtual-gui │ │ │ ├── src │ │ │ │ └── main.rs │ │ │ ├── dist.toml │ │ │ └── Cargo.toml │ │ ├── Cargo.toml │ │ ├── some-lib │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── Cargo.lock │ │ ├── generic-c │ │ ├── main.c │ │ ├── Makefile │ │ └── dist-workspace.toml │ │ ├── npm-create-react-app │ │ ├── public │ │ │ ├── robots.txt │ │ │ ├── favicon.ico │ │ │ ├── logo192.png │ │ │ ├── logo512.png │ │ │ ├── manifest.json │ │ │ └── index.html │ │ ├── src │ │ │ ├── setupTests.js │ │ │ ├── App.test.js │ │ │ ├── index.css │ │ │ ├── reportWebVitals.js │ │ │ ├── index.js │ │ │ ├── App.js │ │ │ ├── App.css │ │ │ └── logo.svg │ │ ├── .gitignore │ │ ├── package-lock.json │ │ ├── package.json │ │ └── README.md │ │ └── npm-init-legacy │ │ ├── dist-workspace.toml │ │ ├── dist.toml │ │ └── package.json ├── .gitignore ├── README.md ├── Cargo.toml ├── src │ └── local_repo.rs └── CHANGELOG.md ├── book ├── src │ ├── img │ │ ├── pr-fail.png │ │ ├── local-build.png │ │ ├── global-build.png │ │ ├── signing-totp.png │ │ ├── simple-oranda.png │ │ ├── workspace-log.png │ │ ├── quickstart-build.png │ │ ├── quickstart-plan.png │ │ ├── signing-cred-id.png │ │ ├── announcement-error.png │ │ ├── human-manifest-all.png │ │ ├── signing-properties.png │ │ ├── simple-app-manifest.png │ │ ├── workflow-artifacts.png │ │ ├── simple-github-release.png │ │ └── simple-app-manifest-with-files.png │ ├── reference │ │ ├── index.md │ │ ├── cli.md │ │ └── schema.md │ ├── quickstart │ │ └── index.md │ ├── artifacts │ │ ├── symbols.md │ │ ├── index.md │ │ └── checksums.md │ ├── installers │ │ ├── other.md │ │ ├── npm.md │ │ ├── powershell.md │ │ ├── updater.md │ │ ├── shell.md │ │ └── index.md │ ├── workspaces │ │ └── index.md │ ├── install.md │ ├── supplychain-security │ │ ├── attestations │ │ │ └── github.md │ │ └── index.md │ ├── SUMMARY.md │ ├── updating.md │ ├── custom-builds.md │ └── introduction.md └── book.toml ├── rust-toolchain.toml ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── publish-crates.yml │ └── ci.yml ├── typos.toml ├── .gitattributes ├── SECURITY.md ├── cargo-dist-schema ├── Cargo.toml └── README.md ├── LICENSE-MIT ├── dist-workspace.toml ├── Justfile └── Cargo.toml /cargo-dist/templates/installer/npm/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | 3 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/LICENSE.txt: -------------------------------------------------------------------------------- 1 | root fake license! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/README.md: -------------------------------------------------------------------------------- 1 | root fake readme! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/LICENSE.txt: -------------------------------------------------------------------------------- 1 | root fake license! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/README.md: -------------------------------------------------------------------------------- 1 | root fake readme! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | root fake changelog! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | root fake changelog! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic1/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | inner fake changelog! -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic1/LICENSE.txt: -------------------------------------------------------------------------------- 1 | inner fake license! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic1/README.md: -------------------------------------------------------------------------------- 1 | inner fake readme! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic1/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | inner fake changelog! -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic1/LICENSE.txt: -------------------------------------------------------------------------------- 1 | inner fake license! 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic1/README.md: -------------------------------------------------------------------------------- 1 | inner fake readme! 2 | -------------------------------------------------------------------------------- /cargo-dist/src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod host; 3 | mod mock; 4 | mod tag; 5 | -------------------------------------------------------------------------------- /cargo-dist/tests/gallery/errors.rs: -------------------------------------------------------------------------------- 1 | pub type Result = std::result::Result; 2 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-new/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /book/src/img/pr-fail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/pr-fail.png -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /book/src/img/local-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/local-build.png -------------------------------------------------------------------------------- /book/src/reference/index.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | The following sections are more focused on precise details. 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/virtual/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-c/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { puts("Hello, cargo-dist!"); } 4 | -------------------------------------------------------------------------------- /book/src/img/global-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/global-build.png -------------------------------------------------------------------------------- /book/src/img/signing-totp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/signing-totp.png -------------------------------------------------------------------------------- /book/src/img/simple-oranda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/simple-oranda.png -------------------------------------------------------------------------------- /book/src/img/workspace-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/workspace-log.png -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/virtual-gui/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /book/src/img/quickstart-build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/quickstart-build.png -------------------------------------------------------------------------------- /book/src/img/quickstart-plan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/quickstart-plan.png -------------------------------------------------------------------------------- /book/src/img/signing-cred-id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/signing-cred-id.png -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/test-bin/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["dist:generic1", "dist:generic2"] 3 | -------------------------------------------------------------------------------- /book/src/img/announcement-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/announcement-error.png -------------------------------------------------------------------------------- /book/src/img/human-manifest-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/human-manifest-all.png -------------------------------------------------------------------------------- /book/src/img/signing-properties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/signing-properties.png -------------------------------------------------------------------------------- /book/src/img/simple-app-manifest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/simple-app-manifest.png -------------------------------------------------------------------------------- /book/src/img/workflow-artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/workflow-artifacts.png -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.87" 3 | components = ["rustc", "cargo", "rust-std", "clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/virtual-gui/dist.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtual-gui-overloaded" 3 | version = "1.0.0" 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic1/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { puts("Hello, cargo-dist!"); } 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic2/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { puts("Hello, cargo-dist!"); } 4 | -------------------------------------------------------------------------------- /book/src/img/simple-github-release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/simple-github-release.png -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic1/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { puts("Hello, cargo-dist!"); } 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic2/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int main() { puts("Hello, cargo-dist!"); } 4 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /cargo-dist/templates/installer/npm/install.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { install } = require("./binary"); 4 | install(false); 5 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/src/bin/cargo-nonvirtual.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("hello world! (as cargo subcommand)"); 3 | } -------------------------------------------------------------------------------- /axoproject/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tests/projects/*/target 3 | 4 | # why did you generate this in the first place vscode? huh? huh??? 5 | .vscode/ 6 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "virtual", 4 | "virtual-gui", 5 | "some-lib", 6 | ] -------------------------------------------------------------------------------- /book/src/img/simple-app-manifest-with-files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/book/src/img/simple-app-manifest-with-files.png -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/axoproject/tests/projects/npm-create-react-app/public/favicon.ico -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/axoproject/tests/projects/npm-create-react-app/public/logo192.png -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astral-sh/cargo-dist/HEAD/axoproject/tests/projects/npm-create-react-app/public/logo512.png -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["dist:generic1", "dist:generic2", "cargo:../cargo-nonvirtual", "cargo:../cargo-virtual", "npm:../npm-init-legacy"] 3 | -------------------------------------------------------------------------------- /book/src/reference/cli.md: -------------------------------------------------------------------------------- 1 | # dist CLI Manual 2 | 3 | 4 | 5 | {{#include ../../../cargo-dist/tests/snapshots/markdown-help.snap:7:}} 6 | 7 | [workspace.metadata.dist]: ./config.md#metadatadist 8 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-new/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "cargo-new" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-init-legacy/dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "npm-init-legacy" 3 | version = "1.0.0" 4 | homepage = "https://workspace.axo.dev/" 5 | build-command = ["fake-command"] 6 | binaries = ["npm-init-legacy"] -------------------------------------------------------------------------------- /cargo-dist/templates/installer/npm/run.js.j2: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { run } = require("./binary"); 4 | {#- see https://github.com/axodotdev/cargo-dist/issues/1524 #} 5 | {%- if bin is defined %} 6 | run({{ bin }}); 7 | {%- endif %} 8 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-c/Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | RM := rm 3 | EXEEXT := 4 | 5 | all: main$(EXEEXT) 6 | 7 | main$(EXEEXT): 8 | $(CC) main.c -o main$(EXEEXT) 9 | 10 | clean: 11 | $(RM) -f main$(EXEEXT) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | *.snap.new 3 | /book/book/ 4 | cargo-dist/wix 5 | /dist-manifest-schema.json 6 | 7 | # Oranda 8 | oranda-debug.log 9 | /public 10 | cargo-dist/public 11 | 12 | # Samply: 13 | /profile.json.gz 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/some-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "some-lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "some-lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/virtual-gui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtual-gui" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic1/Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | RM := rm 3 | EXEEXT := 4 | 5 | all: main$(EXEEXT) 6 | 7 | main$(EXEEXT): 8 | $(CC) main.c -o main$(EXEEXT) 9 | 10 | clean: 11 | $(RM) -f main$(EXEEXT) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic2/Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | RM := rm 3 | EXEEXT := 4 | 5 | all: main$(EXEEXT) 6 | 7 | main$(EXEEXT): 8 | $(CC) main.c -o main$(EXEEXT) 9 | 10 | clean: 11 | $(RM) -f main$(EXEEXT) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic1/Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | RM := rm 3 | EXEEXT := 4 | 5 | all: main$(EXEEXT) 6 | 7 | main$(EXEEXT): 8 | $(CC) main.c -o main$(EXEEXT) 9 | 10 | clean: 11 | $(RM) -f main$(EXEEXT) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic2/Makefile: -------------------------------------------------------------------------------- 1 | CC := gcc 2 | RM := rm 3 | EXEEXT := 4 | 5 | all: main$(EXEEXT) 6 | 7 | main$(EXEEXT): 8 | $(CC) main.c -o main$(EXEEXT) 9 | 10 | clean: 11 | $(RM) -f main$(EXEEXT) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-new/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | [package] 4 | name = "cargo-new" 5 | version = "0.1.0" 6 | edition = "2021" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-other-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "some-other-lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-init-legacy/dist.toml: -------------------------------------------------------------------------------- 1 | # This file is duplicated from dist-workspace.toml because the test suite 2 | # treats this as both a workspace and a package of a larger workspace 3 | # and this involves Crimes 4 | 5 | [package] 6 | homepage = "https://package.axo.dev/" 7 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/virtual/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "virtual" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | some-lib = { version = "0.1.0", path = "../some-lib" } -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /book/src/quickstart/index.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | What you need to do to setup your project with dist depends on the language your project is written in, so choose your own adventure! 4 | 5 | * [Rust Quickstart](./rust.md) 6 | * [JavaScript Quickstart](./javascript.md) 7 | * [Everyone Else Quickstart](./everyone-else.md) 8 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/some-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-cdylib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-other-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-staticlib/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn add(left: usize, right: usize) -> usize { 2 | left + right 3 | } 4 | 5 | #[cfg(test)] 6 | mod tests { 7 | use super::*; 8 | 9 | #[test] 10 | fn it_works() { 11 | let result = add(2, 2); 12 | assert_eq!(result, 4); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/test-bin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-bin" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | some-lib = { version = "0.1.0", path = "../some-lib" } -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | - package-ecosystem: npm 10 | directory: "/cargo-dist/templates/installer/npm" 11 | schedule: 12 | interval: daily 13 | time: "10:00" 14 | -------------------------------------------------------------------------------- /book/src/artifacts/symbols.md: -------------------------------------------------------------------------------- 1 | # Symbols 2 | 3 | This feature is currently disabled [pending a rework][rework-symbols], but basically we want to save your debuginfo/symbols/sourcemaps in the form of pdbs, dSYMs, etc. This will automatically happen as a side-effect of building [archives][]. 4 | 5 | [rework-symbols]: https://github.com/axodotdev/cargo-dist/issues/136 6 | [archives]: ./archives.md -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-cdylib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "some-cdylib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | some-lib = { version = "0.1.0", path = "../some-lib" } 13 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/crates/some-staticlib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "some-staticlib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["staticlib"] 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | some-lib = { version = "0.1.0", path = "../some-lib" } 13 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-init-legacy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-init-legacy", 3 | "version": "1.0.0", 4 | "bin": { 5 | "npm-init-legacy": "src/main.js" 6 | }, 7 | "description": "a legacy project", 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-virtual/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "some-lib" 7 | version = "0.1.0" 8 | 9 | [[package]] 10 | name = "virtual" 11 | version = "0.1.0" 12 | dependencies = [ 13 | "some-lib", 14 | ] 15 | 16 | [[package]] 17 | name = "virtual-gui" 18 | version = "0.1.0" 19 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nonvirtual" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | some-lib = { version = "0.1.0", path = "crates/some-lib" } 10 | some-other-lib = { version = "0.1.0", path = "crates/some-other-lib" } 11 | 12 | [workspace] 13 | members = ["crates/*"] -------------------------------------------------------------------------------- /typos.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/crate-ci/typos # LSP: https://github.com/tekumara/typos-lsp 2 | # install: cargo install typos-cli 3 | # run: typos 4 | [default] 5 | extend-ignore-identifiers-re = [ 6 | # Allow all-caps words that are 2-letters or more and ends with 's' 7 | "[A-Z]{2,}s", 8 | ] 9 | # Ignore CLIENT_ID= 10 | extend-ignore-re = ["CLIENT_ID=[A-Za-z0-9_-]+"] 11 | 12 | [default.extend-identifiers] 13 | PnP = "PnP" 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /cargo-dist/tests/build_setup.yml: -------------------------------------------------------------------------------- 1 | - name: Some build step 2 | uses: some-action-user/some-action 3 | with: 4 | input1: input1 5 | input2: input2 6 | continue-on-error: false 7 | - name: Some Other build step 8 | uses: some-action-user/some-other-action 9 | env: 10 | ENV_VAR: ${{ github.head_ref }} 11 | - name: Some Run Step simple 12 | if: false 13 | run: echo "hello world!" 14 | timeout_minutes: 1 15 | - name: Some Run Step Multi 16 | shell: bash 17 | run: | 18 | HW="hello world" 19 | echo $HW 20 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Declare files that will always have CRLF line endings on checkout. 5 | *.ps1 text eol=crlf 6 | 7 | # Declare files that will always have LF line endings on checkout. 8 | *.sh text eol=lf 9 | *.yml text eol=lf 10 | 11 | # Denote all files that are truly binary and should not be modified. 12 | *.png binary 13 | *.jpg binary 14 | 15 | # All snapshots files are generated, collapsed them by default in the diff view 16 | *.snap linguist-generated 17 | -------------------------------------------------------------------------------- /cargo-dist/src/backend/installer/powershell.rs: -------------------------------------------------------------------------------- 1 | //! Code for generating installer.ps1 2 | 3 | use axoasset::LocalAsset; 4 | 5 | use crate::{backend::templates::TEMPLATE_INSTALLER_PS1, errors::DistResult, DistGraph}; 6 | 7 | use super::InstallerInfo; 8 | 9 | pub(crate) fn write_install_ps_script(dist: &DistGraph, info: &InstallerInfo) -> DistResult<()> { 10 | let script = dist 11 | .templates 12 | .render_file_to_clean_string(TEMPLATE_INSTALLER_PS1, info)?; 13 | LocalAsset::write_new(&script, &info.dest_path)?; 14 | dist.signer.sign(&info.dest_path)?; 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | Axo Developer Co. takes the security of our software products and services seriously. If you believe you have found a security vulnerability in this open source repository, please report the issue to us directly using GitHub private vulnerability reporting or email ashley@axo.dev. If you aren't sure you have found a security vulnerability but have a suspicion or concern, feel free to message anyways; we prefer over-communication :) 2 | 3 | Please do not report security vulnerabilities publicly, such as via GitHub issues, Twitter, or other social media. 4 | 5 | Thanks for helping make software safe for everyone! 6 | -------------------------------------------------------------------------------- /book/src/artifacts/index.md: -------------------------------------------------------------------------------- 1 | # Artifacts 2 | 3 | dist exists to help you distribute your binaries, which involves generating a lot of different files which we call *Artifacts*. Archives are the baseline artifacts that contain your binaries, and installers are the fancy artifacts that make it easy to install or run the binaries. 4 | 5 | * [Archives](./archives.md): tarballs/zips containing your binaries 6 | * [Installers](../installers/index.md): things that help fetch/install archives 7 | * [Checksums](./checksums.md): hashes of other artifacts 8 | * [Symbols](./symbols.md): debuginfo/symbols/sourcemaps of your binaries 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /book/book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["Axo Developer Co"] 3 | language = "en" 4 | multilingual = false 5 | src = "src" 6 | title = "dist" 7 | 8 | [output.html] 9 | git-repository-url = "https://github.com/astral-sh/cargo-dist" 10 | edit-url-template = "https://github.com/astral-sh/cargo-dist/edit/main/book/{path}" 11 | search.use-boolean-and = true 12 | 13 | [output.linkcheck] 14 | warning-policy = "error" 15 | 16 | [output.html.redirect] 17 | "/ci/github.html" = "./index.html" 18 | "/generic-builds.html" = "./custom-builds.html" 19 | "/way-too-quickstart.html" = "./quickstart/index.html" 20 | 21 | [preprocessor.toc] 22 | command = "mdbook-toc" 23 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById('root')); 8 | root.render( 9 | 10 | 11 | 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from './logo.svg'; 2 | import './App.css'; 3 | 4 | function App() { 5 | return ( 6 |
7 |
8 | logo 9 |

10 | Edit src/App.js and save to reload. 11 |

12 | 18 | Learn React 19 | 20 |
21 |
22 | ); 23 | } 24 | 25 | export default App; 26 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-create-react-app", 3 | "version": "0.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "npm-create-react-app", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "@testing-library/jest-dom": "^5.16.5", 12 | "@testing-library/react": "^13.4.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "react-scripts": "5.0.1", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "bin": { 20 | "App.js": "src/App.js", 21 | "index.js": "src/index.js" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cargo-dist/tests/gallery/mod.rs: -------------------------------------------------------------------------------- 1 | mod command; 2 | mod dist; 3 | mod errors; 4 | mod repo; 5 | 6 | pub use dist::*; 7 | 8 | /// Taken from cargo-insta to avoid copy-paste errors 9 | /// 10 | /// Gets the ~name of the function running this macro 11 | #[macro_export] 12 | macro_rules! _function_name { 13 | () => {{ 14 | fn f() {} 15 | fn type_name_of_val(_: T) -> &'static str { 16 | std::any::type_name::() 17 | } 18 | let mut name = type_name_of_val(f).strip_suffix("::f").unwrap_or(""); 19 | while let Some(rest) = name.strip_suffix("::{{closure}}") { 20 | name = rest; 21 | } 22 | name.split_once("::") 23 | .map(|(_module, func)| func) 24 | .unwrap_or(name) 25 | }}; 26 | } 27 | -------------------------------------------------------------------------------- /axoproject/tests/projects/cargo-nonvirtual/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "nonvirtual" 7 | version = "0.1.0" 8 | dependencies = [ 9 | "some-lib", 10 | "some-other-lib", 11 | ] 12 | 13 | [[package]] 14 | name = "some-cdylib" 15 | version = "0.1.0" 16 | dependencies = [ 17 | "some-lib", 18 | ] 19 | 20 | [[package]] 21 | name = "some-lib" 22 | version = "0.1.0" 23 | 24 | [[package]] 25 | name = "some-other-lib" 26 | version = "0.1.0" 27 | 28 | [[package]] 29 | name = "some-staticlib" 30 | version = "0.1.0" 31 | dependencies = [ 32 | "some-lib", 33 | ] 34 | 35 | [[package]] 36 | name = "test-bin" 37 | version = "0.1.0" 38 | dependencies = [ 39 | "some-lib", 40 | ] 41 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-create-react-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "bin": [ 6 | "src/index.js", 7 | "src/App.js" 8 | ], 9 | "dependencies": { 10 | }, 11 | "scripts": { 12 | "start": "react-scripts start", 13 | "build": "react-scripts build", 14 | "test": "react-scripts test", 15 | "eject": "react-scripts eject" 16 | }, 17 | "eslintConfig": { 18 | "extends": [ 19 | "react-app", 20 | "react-app/jest" 21 | ] 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /book/src/reference/schema.md: -------------------------------------------------------------------------------- 1 | # JSON Schema 2 | 3 | Many dist commands when run with `--output-format=json` will output to stdout a format we call "dist-manifest.json". This contains: 4 | 5 | * Top-level facts about the Announcement (tag, announcement title, etc) 6 | * Info about the Apps being Released as part of the Announcement ("releases") 7 | * Info about the Artifacts included in the Announcement ("announcements") 8 | 9 | As a matter of forward-compat and back-compat, basically every field in the format should be treated as optional (which the schema reflects). 10 | 11 | The latest schema can be found at: 12 | 13 | https://github.com/axodotdev/cargo-dist/releases/latest/download/dist-manifest-schema.json 14 | 15 | An example dist-manifest can be found at: 16 | 17 | https://github.com/axodotdev/axolotlsay/releases/latest/download/dist-manifest.json -------------------------------------------------------------------------------- /cargo-dist/oranda.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "readme_path": "../README.md" 4 | }, 5 | "build": { 6 | "path_prefix": "cargo-dist" 7 | }, 8 | "marketing": { 9 | "social": { 10 | "image": "https://www.axo.dev/meta_small.jpeg", 11 | "image_alt": "axo", 12 | "twitter_account": "@axodotdev" 13 | }, 14 | "analytics": { 15 | "plausible": { 16 | "domain": "opensource.axo.dev" 17 | } 18 | } 19 | }, 20 | "styles": { 21 | "theme": "axo_dark", 22 | "favicon": "https://www.axo.dev/favicon.ico" 23 | }, 24 | "components": { 25 | "changelog": true, 26 | "mdbook": { 27 | "path": "../book" 28 | }, 29 | "artifacts": { 30 | "package_managers": { 31 | "preferred": { 32 | "crates.io": "cargo install cargo-dist" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cargo-dist-schema/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-dist-schema" 3 | description = "Schema information for cargo-dist's dist-manifest.json" 4 | version.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | rust-version.workspace = true 10 | exclude = [ 11 | "book/*", 12 | "src/snapshots/*", 13 | "src/tests/", 14 | "tests/", 15 | ] 16 | 17 | [lib] 18 | doctest = false 19 | 20 | [dependencies] 21 | # would be nice to inherit this from workspace but we don't want to pull in 22 | # a full http client for the schema crate! 23 | gazenot = { version = "0.3.0", default-features = false } 24 | 25 | camino.workspace = true 26 | schemars.workspace = true 27 | semver.workspace = true 28 | serde.workspace = true 29 | serde_json.workspace = true 30 | target-lexicon.workspace = true 31 | 32 | [dev-dependencies] 33 | insta.workspace = true 34 | -------------------------------------------------------------------------------- /cargo-dist/templates/installer/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axonpminstaller", 3 | "version": "0.0.0", 4 | "repository": "https://github.com/astral-sh/cargo-dist/", 5 | "preferUnplugged": true, 6 | "artifactDownloadUrl": "", 7 | "glibcMinimum": { 8 | "major": 2, 9 | "series": 31 10 | }, 11 | "supportedPlatforms": {}, 12 | "scripts": { 13 | "postinstall": "node ./install.js", 14 | "fmt": "prettier --write **/*.js", 15 | "fmt:check": "prettier --check **/*.js" 16 | }, 17 | "engines": { 18 | "node": ">=14", 19 | "npm": ">=6" 20 | }, 21 | "volta": { 22 | "node": "18.14.1", 23 | "npm": "9.5.0" 24 | }, 25 | "dependencies": { 26 | "axios": "^1.7.9", 27 | "axios-proxy-builder": "^0.1.2", 28 | "console.table": "^0.10.0", 29 | "detect-libc": "^2.0.3", 30 | "rimraf": "^5.0.8" 31 | }, 32 | "devDependencies": { 33 | "prettier": "^3.4.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /book/src/installers/other.md: -------------------------------------------------------------------------------- 1 | # Other Installation Methods 2 | 3 | dist projects can also theoretically be installed with the following, through no active effort of our own: 4 | 5 | * [cargo-install][] (just [cargo publish][] like normal) 6 | * [cargo-binstall][] (the URL schema we use for Github Releases is auto-detected) 7 | 8 | In the future we might [support displaying these kinds of install methods][issue-info-install]. 9 | 10 | Note that cargo-install is just building from the uploaded source with the --release profile, and so if you're relying on dist or unpublished files for some key behaviours, this may cause problems. [It also disrespects your lockfile unless you pass --locked][install-locked]. You can more closely approximate dist's build with: 11 | 12 | ```sh 13 | cargo install --locked 14 | ``` 15 | 16 | Although that's still missing things like [Windows crt-static workarounds][crt-static] and the "dist" profile. 17 | 18 | 19 | -------------------------------------------------------------------------------- /axoproject/README.md: -------------------------------------------------------------------------------- 1 | # axoproject 2 | 3 | [![crates.io](https://img.shields.io/crates/v/axoproject.svg)](https://crates.io/crates/axoproject) 4 | [![docs](https://docs.rs/axoproject/badge.svg)](https://docs.rs/axoproject) 5 | [![Rust CI](https://github.com/astral-sh/cargo-dist/workflows/Rust%20CI/badge.svg?branch=main)](https://github.com/axodotdev/cargo-dist/actions/workflows/ci.yml) 6 | 7 | A tool that handles the details of detecting a cargo/npm workspace/project at a given directory, 8 | and making sense of the structure. This should be used by dist and oranda to share that logic. 9 | 10 | ## License 11 | 12 | Licensed under either of 13 | 14 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)) 15 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or [opensource.org/licenses/MIT](https://opensource.org/licenses/MIT)) 16 | 17 | at your option. 18 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/error_manifest.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | --- 5 | stdout: 6 | {"diagnostic": {"message": "This workspace doesn't have anything for dist to Release!","severity": "error","causes": [],"help": "You may need to pass the current version as --tag, or need to give all your packages the same version\n\nHere are some options:\n\n--tag=v1.0.0-FAKEVERSION will Announce: cargo-dist\n\nyou can also request any single package with --tag=cargo-dist-v1.0.0-FAKEVERSION\n","labels": [],"related": []}} 7 | 8 | stderr: 9 | × This workspace doesn't have anything for dist to Release! 10 | help: You may need to pass the current version as --tag, or need to give all your packages the same version 11 | 12 | Here are some options: 13 | 14 | --tag=v1.0.0-FAKEVERSION will Announce: cargo-dist 15 | 16 | you can also request any single package with --tag=cargo-dist-v1.0.0-FAKEVERSION 17 | -------------------------------------------------------------------------------- /book/src/workspaces/index.md: -------------------------------------------------------------------------------- 1 | # Guide 2 | 3 | dist is focused around *workspaces* - conceptual groupings of projects that exist in the same repository, and which can be built and released together. A workspace can contain just a single project, and the simplest form is easy to get going. If you just want to get going, we recommend checking the [quickstart][way-too-quickstart] guide first. 4 | 5 | For users interested in learning more about how workspaces work, and how to put them to work with more complex projects, we recommend starting with our [guide to workspace structure][structure], followed by the [simple guide][simple-guide] which walks you through a simple sample repo and the [complex guide][complex-guide] that introduces a workspace with more complicated features. 6 | 7 | [concepts]: ../reference/concepts.md 8 | [structure]: ./structure.md 9 | [simple-guide]: ./simple-guide.md 10 | [complex-guide]: ./workspace-guide.md 11 | [way-too-quickstart]: ../quickstart/index.md 12 | 13 | [workspace]: https://doc.rust-lang.org/cargo/reference/workspaces.html 14 | -------------------------------------------------------------------------------- /cargo-dist/src/backend/installer/shell.rs: -------------------------------------------------------------------------------- 1 | //! Code for generating installer.sh 2 | 3 | use axoasset::LocalAsset; 4 | use cargo_dist_schema::DistManifest; 5 | 6 | use crate::{backend::templates::TEMPLATE_INSTALLER_SH, errors::DistResult, DistGraph}; 7 | 8 | use super::InstallerInfo; 9 | 10 | pub(crate) fn write_install_sh_script( 11 | dist: &DistGraph, 12 | info: &InstallerInfo, 13 | manifest: &DistManifest, 14 | ) -> DistResult<()> { 15 | let mut info = info.clone(); 16 | let platform_support = dist.release(info.release).platform_support.clone(); 17 | 18 | info.platform_support = Some(if dist.local_builds_are_lies { 19 | // if local builds are lies, the artifacts that are "fake-built" have a different 20 | // checksum every time, so we can't use those in the generated installer 21 | platform_support 22 | } else { 23 | platform_support.with_checksums_from_manifest(manifest) 24 | }); 25 | 26 | let script = dist 27 | .templates 28 | .render_file_to_clean_string(TEMPLATE_INSTALLER_SH, &info)?; 29 | LocalAsset::write_new(&script, &info.dest_path)?; 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /book/src/install.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | 4 | 5 | Surprise! The tool for prebuilt shippable binaries has way too many ways to install it! 6 | Whichever way you choose to install it, it should be invocable as `dist ...`. 7 | 8 | 9 | ## Pre-built binaries 10 | 11 | We provide several options to access pre-built binaries for a variety of platforms. If you would like to manually download a pre-built binary, checkout [the latest release on GitHub](https://github.com/astral-sh/cargo-dist/releases/latest). 12 | 13 | This is an unofficial fork, so only the shell script installers are supported. 14 | 15 | ### Installer scripts 16 | 17 | #### macOS and Linux (not NixOS): 18 | 19 | ```sh 20 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/astral-sh/cargo-dist/releases/latest/download/cargo-dist-installer.sh | sh 21 | ``` 22 | 23 | #### Windows PowerShell: 24 | 25 | ```sh 26 | powershell -c "irm https://github.com/astral-sh/cargo-dist/releases/latest/download/cargo-dist-installer.ps1 | iex" 27 | ``` 28 | 29 | 30 | [Rust]: https://rust-lang.org 31 | [cargo]: https://doc.rust-lang.org/cargo/index.html 32 | [installed the Rust toolchain (`rustup`)]: https://rustup.rs/ 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022-2024 Axo Developer Co. 2 | Copyright (c) 2025 Astral Software Inc. 3 | 4 | Permission is hereby granted, free of charge, to any 5 | person obtaining a copy of this software and associated 6 | documentation files (the "Software"), to deal in the 7 | Software without restriction, including without 8 | limitation the rights to use, copy, modify, merge, 9 | publish, distribute, sublicense, and/or sell copies of 10 | the Software, and to permit persons to whom the Software 11 | is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice 15 | shall be included in all copies or substantial portions 16 | of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 19 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 20 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 21 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 22 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 23 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 24 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 | DEALINGS IN THE SOFTWARE. 27 | -------------------------------------------------------------------------------- /cargo-dist/templates/ci/github/partials/publish_npm.yml.j2: -------------------------------------------------------------------------------- 1 | publish-npm: 2 | needs: 3 | - plan 4 | - host 5 | {{%- for job in host_jobs %}} 6 | - custom-{{{ job.name|safe }}} 7 | {{%- endfor %}} 8 | runs-on: {{{ global_task.runner }}} 9 | env: 10 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11 | PLAN: ${{ needs.plan.outputs.val }} 12 | if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} 13 | steps: 14 | - name: Fetch npm packages 15 | uses: {{{ actions["actions/download-artifact"] | safe }}} 16 | with: 17 | pattern: artifacts-* 18 | path: npm/ 19 | merge-multiple: true 20 | - uses: {{{ actions["actions/setup-node"] | safe }}} 21 | with: 22 | node-version: '20.x' 23 | registry-url: 'https://registry.npmjs.org' 24 | - run: | 25 | for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith("-npm-package.tar.gz")] | any)'); do 26 | pkg=$(echo "$release" | jq '.artifacts[] | select(endswith("-npm-package.tar.gz"))' --raw-output) 27 | npm publish --access public "./npm/${pkg}" 28 | done 29 | env: 30 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 31 | -------------------------------------------------------------------------------- /book/src/supplychain-security/attestations/github.md: -------------------------------------------------------------------------------- 1 | # GitHub Artifact Attestations 2 | 3 | GitHub's [Artifact Attestations] feature - currently in public beta - allows for the creation of a tamper-proof, unforgeable paper trail linking build artifacts to the process which created it. Artifact Attestations is powered by [Sigstore], an open source project for signing and verifying software artifacts. 4 | 5 | Artifact Attestations is disabled by default in `dist`, and can be enabled by [setting `github-attestations = true`](../../reference/config.md#github-attestations) 6 | 7 | Note that GitHub's Artifact Attestations only supports public repositories and private repositories of an organization with the GitHub Enterprise plan. In the case of public repositories, attestations generated by GitHub Actions will be written to the Sigstore Public Good Instance and end up on [Rekor], Sigstore's immutable ledger, for public verification. 8 | 9 | Currently, verification of GitHub Artifact Attestations is only supported via GitHub CLI with [`gh attestation verify`]. 10 | 11 | [Artifact Attestations]: https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/ 12 | [Sigstore]: https://www.sigstore.dev/ 13 | [Rekor]: https://docs.sigstore.dev/logging/overview/ 14 | [`gh attestation verify`]: https://cli.github.com/manual/gh_attestation_verify 15 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/hosts/axodotdev.rs: -------------------------------------------------------------------------------- 1 | //! axodotdev host config 2 | 3 | use super::*; 4 | 5 | /// axodotdev host (raw) 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct AxodotdevHostLayer { 9 | /// Common options 10 | pub common: CommonHostLayer, 11 | } 12 | /// axodotdev host (final) 13 | #[derive(Debug, Default, Clone)] 14 | pub struct AxodotdevHostConfig { 15 | /// Common options 16 | pub common: CommonHostConfig, 17 | } 18 | 19 | impl AxodotdevHostConfig { 20 | /// Get defaults for the given workspace 21 | pub fn defaults_for_workspace(_workspaces: &WorkspaceGraph, common: &CommonHostConfig) -> Self { 22 | Self { 23 | common: common.clone(), 24 | } 25 | } 26 | } 27 | 28 | impl ApplyLayer for AxodotdevHostConfig { 29 | type Layer = AxodotdevHostLayer; 30 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 31 | self.common.apply_layer(common); 32 | } 33 | } 34 | impl ApplyLayer for AxodotdevHostLayer { 35 | type Layer = AxodotdevHostLayer; 36 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 37 | self.common.apply_layer(common); 38 | } 39 | } 40 | 41 | impl std::ops::Deref for AxodotdevHostConfig { 42 | type Target = CommonHostConfig; 43 | fn deref(&self) -> &Self::Target { 44 | &self.common 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /axoproject/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "axoproject" 3 | description = "project detection logic for various axo.dev applications" 4 | version.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | rust-version.workspace = true 10 | exclude = [ 11 | "book/*", 12 | "src/snapshots/*", 13 | "src/tests/", 14 | "tests/", 15 | ] 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [features] 20 | default = ["generic-projects", "cargo-projects", "npm-projects"] 21 | generic-projects = [] 22 | cargo-projects = ["guppy"] 23 | npm-projects = ["oro-common", "oro-package-spec", "node-semver"] 24 | 25 | [dependencies] 26 | # it would be nice to inherit these features from workspace 27 | # but you can't optional inherit workspace deps..? 28 | guppy = { version = "0.17.5", optional = true } 29 | oro-common = { version = "0.3.34", optional = true } 30 | oro-package-spec = { version = "0.3.34", optional = true } 31 | node-semver = { version = "2.1.0", optional = true } 32 | 33 | axoasset.workspace = true 34 | axoprocess.workspace = true 35 | 36 | camino.workspace = true 37 | miette.workspace = true 38 | tracing.workspace = true 39 | serde.workspace = true 40 | thiserror.workspace = true 41 | pathdiff.workspace = true 42 | itertools.workspace = true 43 | url.workspace = true 44 | parse-changelog.workspace = true 45 | semver.workspace = true 46 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/publishers/npm.rs: -------------------------------------------------------------------------------- 1 | //! npm publisher config 2 | 3 | use super::*; 4 | 5 | /// Options for npm publishes 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | pub struct NpmPublisherLayer { 8 | /// Common options 9 | pub common: CommonPublisherLayer, 10 | } 11 | /// Options for npm publishes 12 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 13 | pub struct NpmPublisherConfig { 14 | /// Common options 15 | pub common: CommonPublisherConfig, 16 | } 17 | 18 | impl NpmPublisherConfig { 19 | /// Get defaults for the given package 20 | pub fn defaults_for_package( 21 | _workspaces: &WorkspaceGraph, 22 | _pkg_idx: PackageIdx, 23 | common: &CommonPublisherConfig, 24 | ) -> Self { 25 | Self { 26 | common: common.clone(), 27 | } 28 | } 29 | } 30 | 31 | impl ApplyLayer for NpmPublisherConfig { 32 | type Layer = NpmPublisherLayer; 33 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 34 | self.common.apply_layer(common); 35 | } 36 | } 37 | impl ApplyLayer for NpmPublisherLayer { 38 | type Layer = NpmPublisherLayer; 39 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 40 | self.common.apply_layer(common); 41 | } 42 | } 43 | 44 | impl std::ops::Deref for NpmPublisherConfig { 45 | type Target = CommonPublisherConfig; 46 | fn deref(&self) -> &Self::Target { 47 | &self.common 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/publishers/user.rs: -------------------------------------------------------------------------------- 1 | //! user specified publisher config 2 | 3 | use super::*; 4 | 5 | /// Options for user specified publishes 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | pub struct UserPublisherLayer { 8 | /// Common options 9 | pub common: CommonPublisherLayer, 10 | } 11 | /// Options for user specified publishes 12 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 13 | pub struct UserPublisherConfig { 14 | /// Common options 15 | pub common: CommonPublisherConfig, 16 | } 17 | 18 | impl UserPublisherConfig { 19 | /// Get defaults for the given package 20 | pub fn defaults_for_package( 21 | _workspaces: &WorkspaceGraph, 22 | _pkg_idx: PackageIdx, 23 | common: &CommonPublisherConfig, 24 | ) -> Self { 25 | Self { 26 | common: common.clone(), 27 | } 28 | } 29 | } 30 | 31 | impl ApplyLayer for UserPublisherConfig { 32 | type Layer = UserPublisherLayer; 33 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 34 | self.common.apply_layer(common); 35 | } 36 | } 37 | impl ApplyLayer for UserPublisherLayer { 38 | type Layer = UserPublisherLayer; 39 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 40 | self.common.apply_layer(common); 41 | } 42 | } 43 | 44 | impl std::ops::Deref for UserPublisherConfig { 45 | type Target = CommonPublisherConfig; 46 | fn deref(&self) -> &Self::Target { 47 | &self.common 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/publish-crates.yml: -------------------------------------------------------------------------------- 1 | # Publishes our workspace's crates to crates.io 2 | # 3 | # This is triggered automatically by dist during the publish step 4 | # (this is a custom publish job: https://opensource.axo.dev/cargo-dist/book/ci/customizing.html#custom-jobs) 5 | # 6 | # It is assumed that all crates are versioned and released in lockstep, 7 | # and that the current commit we're working on already has them at the right version 8 | # to publish. 9 | # 10 | # By default this is invoked only for non-prereleases, because we don't 11 | # want to clutter up crates.io with a million prereleases (and cratesio gives less useful 12 | # download stats if you have lots of prereleases). 13 | name: PublishRelease 14 | 15 | on: 16 | workflow_call: 17 | inputs: 18 | plan: 19 | required: true 20 | type: string 21 | 22 | jobs: 23 | # publish the current repo state to crates.io 24 | cargo-publish: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout sources 28 | uses: actions/checkout@v4 29 | with: 30 | submodules: recursive 31 | - run: cargo publish -p axoproject --token ${CRATES_TOKEN} 32 | env: 33 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 34 | - run: cargo publish -p cargo-dist-schema --token ${CRATES_TOKEN} 35 | env: 36 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 37 | - run: cargo publish -p cargo-dist --token ${CRATES_TOKEN} 38 | env: 39 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 40 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/builds/generic.rs: -------------------------------------------------------------------------------- 1 | //! generic build config 2 | 3 | use super::*; 4 | 5 | /// generic build config (final) 6 | #[derive(Debug, Clone)] 7 | pub struct GenericBuildConfig { 8 | /// inheritable fields 9 | pub common: CommonBuildConfig, 10 | } 11 | 12 | /// generic build config (raw from file) 13 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 14 | #[serde(rename_all = "kebab-case")] 15 | pub struct GenericBuildLayer { 16 | /// inheritable fields 17 | #[serde(flatten)] 18 | pub common: CommonBuildLayer, 19 | } 20 | 21 | impl GenericBuildConfig { 22 | /// Get defaults for the given package 23 | pub fn defaults_for_package( 24 | _workspaces: &WorkspaceGraph, 25 | _pkg_idx: PackageIdx, 26 | common: &CommonBuildConfig, 27 | ) -> Self { 28 | Self { 29 | common: common.clone(), 30 | } 31 | } 32 | } 33 | 34 | impl ApplyLayer for GenericBuildConfig { 35 | type Layer = GenericBuildLayer; 36 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 37 | self.common.apply_layer(common); 38 | } 39 | } 40 | impl ApplyLayer for GenericBuildLayer { 41 | type Layer = GenericBuildLayer; 42 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 43 | self.common.apply_layer(common); 44 | } 45 | } 46 | 47 | impl std::ops::Deref for GenericBuildConfig { 48 | type Target = CommonBuildConfig; 49 | fn deref(&self) -> &Self::Target { 50 | &self.common 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/installers/msi.rs: -------------------------------------------------------------------------------- 1 | //! msi installer config 2 | 3 | use super::*; 4 | 5 | /// Options for msi installer 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct MsiInstallerLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonInstallerLayer, 12 | } 13 | /// Options for msi installer 14 | #[derive(Debug, Default, Clone)] 15 | pub struct MsiInstallerConfig { 16 | /// Common options 17 | pub common: CommonInstallerConfig, 18 | } 19 | 20 | impl MsiInstallerConfig { 21 | /// Get defaults for the given package 22 | pub fn defaults_for_package( 23 | _workspaces: &WorkspaceGraph, 24 | _pkg_idx: PackageIdx, 25 | common: &CommonInstallerConfig, 26 | ) -> Self { 27 | Self { 28 | common: common.clone(), 29 | } 30 | } 31 | } 32 | 33 | impl ApplyLayer for MsiInstallerConfig { 34 | type Layer = MsiInstallerLayer; 35 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 36 | self.common.apply_layer(common); 37 | } 38 | } 39 | impl ApplyLayer for MsiInstallerLayer { 40 | type Layer = MsiInstallerLayer; 41 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 42 | self.common.apply_layer(common); 43 | } 44 | } 45 | 46 | impl std::ops::Deref for MsiInstallerConfig { 47 | type Target = CommonInstallerConfig; 48 | fn deref(&self) -> &Self::Target { 49 | &self.common 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/publishers/homebrew.rs: -------------------------------------------------------------------------------- 1 | //! homebrew publisher config 2 | 3 | use super::*; 4 | 5 | /// Options for homebrew publishes 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | pub struct HomebrewPublisherLayer { 8 | /// Common options 9 | pub common: CommonPublisherLayer, 10 | } 11 | /// Options for homebrew publishes 12 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 13 | pub struct HomebrewPublisherConfig { 14 | /// Common options 15 | pub common: CommonPublisherConfig, 16 | } 17 | 18 | impl HomebrewPublisherConfig { 19 | /// Get defaults for the given package 20 | pub fn defaults_for_package( 21 | _workspaces: &WorkspaceGraph, 22 | _pkg_idx: PackageIdx, 23 | common: &CommonPublisherConfig, 24 | ) -> Self { 25 | Self { 26 | common: common.clone(), 27 | } 28 | } 29 | } 30 | 31 | impl ApplyLayer for HomebrewPublisherConfig { 32 | type Layer = HomebrewPublisherLayer; 33 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 34 | self.common.apply_layer(common); 35 | } 36 | } 37 | impl ApplyLayer for HomebrewPublisherLayer { 38 | type Layer = HomebrewPublisherLayer; 39 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 40 | self.common.apply_layer(common); 41 | } 42 | } 43 | 44 | impl std::ops::Deref for HomebrewPublisherConfig { 45 | type Target = CommonPublisherConfig; 46 | fn deref(&self) -> &Self::Target { 47 | &self.common 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/installers/shell.rs: -------------------------------------------------------------------------------- 1 | //! shell installer config 2 | 3 | use super::*; 4 | 5 | /// Options for shell installer 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct ShellInstallerLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonInstallerLayer, 12 | } 13 | /// Options for shell installer 14 | #[derive(Debug, Default, Clone)] 15 | pub struct ShellInstallerConfig { 16 | /// Common options 17 | pub common: CommonInstallerConfig, 18 | } 19 | 20 | impl ShellInstallerConfig { 21 | /// Get defaults for the given package 22 | pub fn defaults_for_package( 23 | _workspaces: &WorkspaceGraph, 24 | _pkg_idx: PackageIdx, 25 | common: &CommonInstallerConfig, 26 | ) -> Self { 27 | Self { 28 | common: common.clone(), 29 | } 30 | } 31 | } 32 | 33 | impl ApplyLayer for ShellInstallerConfig { 34 | type Layer = ShellInstallerLayer; 35 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 36 | self.common.apply_layer(common); 37 | } 38 | } 39 | impl ApplyLayer for ShellInstallerLayer { 40 | type Layer = ShellInstallerLayer; 41 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 42 | self.common.apply_layer(common); 43 | } 44 | } 45 | 46 | impl std::ops::Deref for ShellInstallerConfig { 47 | type Target = CommonInstallerConfig; 48 | fn deref(&self) -> &Self::Target { 49 | &self.common 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/installers/powershell.rs: -------------------------------------------------------------------------------- 1 | //! powershell installer config 2 | 3 | use super::*; 4 | 5 | /// Options for homebrew installer 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct PowershellInstallerLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonInstallerLayer, 12 | } 13 | /// Options for homebrew installer 14 | #[derive(Debug, Default, Clone)] 15 | pub struct PowershellInstallerConfig { 16 | /// Common options 17 | pub common: CommonInstallerConfig, 18 | } 19 | 20 | impl PowershellInstallerConfig { 21 | /// Get defaults for the given package 22 | pub fn defaults_for_package( 23 | _workspaces: &WorkspaceGraph, 24 | _pkg_idx: PackageIdx, 25 | common: &CommonInstallerConfig, 26 | ) -> Self { 27 | Self { 28 | common: common.clone(), 29 | } 30 | } 31 | } 32 | 33 | impl ApplyLayer for PowershellInstallerConfig { 34 | type Layer = PowershellInstallerLayer; 35 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 36 | self.common.apply_layer(common); 37 | } 38 | } 39 | impl ApplyLayer for PowershellInstallerLayer { 40 | type Layer = PowershellInstallerLayer; 41 | fn apply_layer(&mut self, Self::Layer { common }: Self::Layer) { 42 | self.common.apply_layer(common); 43 | } 44 | } 45 | 46 | impl std::ops::Deref for PowershellInstallerConfig { 47 | type Target = CommonInstallerConfig; 48 | fn deref(&self) -> &Self::Target { 49 | &self.common 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /book/src/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Introduction](./introduction.md) 4 | - [Install](./install.md) 5 | - [Quickstart](./quickstart/index.md) 6 | - [Rust](./quickstart/rust.md) 7 | - [JavaScript](./quickstart/javascript.md) 8 | - [Everyone Else](./quickstart/everyone-else.md) 9 | - [Updating](./updating.md) 10 | - [Troubleshooting](./troubleshooting.md) 11 | - [Custom Builds](./custom-builds.md) 12 | - [Supplychain Security](./supplychain-security/index.md) 13 | - [Windows Signing](./supplychain-security/signing/windows.md) 14 | - [GitHub Attestations](./supplychain-security/attestations/github.md) 15 | - [Installers](./installers/index.md) 16 | - [shell](./installers/shell.md) 17 | - [powershell](./installers/powershell.md) 18 | - [npm](./installers/npm.md) 19 | - [homebrew](./installers/homebrew.md) 20 | - [msi](./installers/msi.md) 21 | - [updater](./installers/updater.md) 22 | - [Usage](./installers/usage.md) 23 | - [Artifacts](./artifacts/index.md) 24 | - [archives](./artifacts/archives.md) 25 | - [checksums](./artifacts/checksums.md) 26 | - [symbols](./artifacts/symbols.md) 27 | - [CI](./ci/index.md) 28 | - [Customizing](./ci/customizing.md) 29 | - [Workspaces](./workspaces/index.md) 30 | - [Structure](./workspaces/structure.md) 31 | - [A Simple Application](./workspaces/simple-guide.md) 32 | - [More Complex Workspaces](./workspaces/workspace-guide.md) 33 | - [Using cargo-release](./workspaces/cargo-release-guide.md) 34 | - [Reference](./reference/index.md) 35 | - [Concepts](./reference/concepts.md) 36 | - [Artifact URLs](./reference/artifact-url.md) 37 | - [Config](./reference/config.md) 38 | - [CLI Manual](./reference/cli.md) 39 | - [dist-manifest.json](./reference/schema.md) 40 | -------------------------------------------------------------------------------- /book/src/artifacts/checksums.md: -------------------------------------------------------------------------------- 1 | # Checksums 2 | 3 | By default dist will generate a matching checksum file for each [archive][] it generates. The default checksum is sha256, so for instance `my-app-x86_64-pc-windows-msvc.zip` will also come with `my-app-x86_64-pc-windows-msvc.zip.sha256` that tools like `sha256sum` can use. This can be configured with [the checksum config][config-checksum]. 4 | 5 | [Fetching installers][fetching-installers] can also use these checksums (or ones baked into them) to validate the integrity of the files they download. With https and unsigned checksums the security benefit is minimal, but it can catch more boring problems like data corruption. 6 | 7 | The homebrew installer actually ignores your checksum setting and always uses sha256 hashes that are baked into it, as required by homebrew itself. 8 | 9 | Updating the other fetching installers to use these checksums is [still a work in progress][issue-checksum-backlog]. 10 | 11 | > since 0.24.0 12 | 13 | cargo-dist also generates a "unified" checksum file, like `sha256.sum`, which contains the checksums for all the archives it has generated, in a format that can be checked with `sha256sum -c`, for example. 14 | 15 | Individual checksums will be deprecated in a future version in favor of that unified checksum file. 16 | 17 | Although you can [pick other checksum algorithms][config-checksum], since you can only pick one, be aware that not every macOS/Linux/Windows system may have tools installed that are able to check `blake2b`, for example. 18 | 19 | [issue-checksum-backlog]: https://github.com/axodotdev/cargo-dist/issues/439 20 | 21 | [config-checksum]: ../reference/config.md#checksum 22 | 23 | [archive]: ../artifacts/archives.md 24 | [fetching-installers]: ../installers/index.md#fetching-installers 25 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-c/dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testprog" 3 | description = "A test of a C program for cargo-dist" 4 | version = "0.0.1" 5 | license = "WTFPL" 6 | repository = "https://github.com/mistydemeo/testprog" 7 | binaries = ["main"] 8 | build-command = ["make"] 9 | 10 | # Config for 'cargo dist' 11 | [dist] 12 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 13 | cargo-dist-version = "0.4.2" 14 | # CI backends to support 15 | ci = ["github"] 16 | # The installers to generate for each app 17 | installers = ["shell", "homebrew"] 18 | # Target platforms to build apps for (Rust target-triple syntax) 19 | targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"] 20 | # The archive format to use for windows builds (defaults .zip) 21 | windows-archive = ".tar.gz" 22 | # The archive format to use for non-windows builds (defaults .tar.xz) 23 | unix-archive = ".tar.gz" 24 | # A namespace to use when publishing this package to the npm registry 25 | npm-scope = "@axodotdev" 26 | # A GitHub repo to push Homebrew formulas to 27 | tap = "mistydemeo/homebrew-cargodisttest" 28 | # Publish jobs to run in CI 29 | publish-jobs = ["homebrew"] 30 | # Whether cargo-dist should create a Github Release or use an existing draft 31 | create-release = false 32 | # Whether to publish prereleases to package managers 33 | publish-prereleases = true 34 | # Publish jobs to run in CI 35 | pr-run-mode = "plan" 36 | 37 | [dist.dependencies.homebrew] 38 | cmake = { targets = ["x86_64-apple-darwin"] } 39 | libcue = { version = "2.2.1", targets = ["x86_64-apple-darwin"] } 40 | 41 | [dist.dependencies.apt] 42 | cmake = '*' 43 | libcue-dev = { version = "2.2.1-2" } 44 | 45 | [dist.dependencies.chocolatey] 46 | lftp = '*' 47 | cmake = '3.27.6' 48 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic1/dist.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic1" 3 | description = "A test of a C program for cargo-dist" 4 | version = "0.0.1" 5 | license = "WTFPL" 6 | repository = "https://github.com/mistydemeo/testprog1" 7 | binaries = ["main"] 8 | build-command = ["make"] 9 | 10 | # Config for 'cargo dist' 11 | [workspace.metadata.dist] 12 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 13 | cargo-dist-version = "0.4.2" 14 | # CI backends to support 15 | ci = ["github"] 16 | # The installers to generate for each app 17 | installers = ["shell", "homebrew"] 18 | # Target platforms to build apps for (Rust target-triple syntax) 19 | targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"] 20 | # The archive format to use for windows builds (defaults .zip) 21 | windows-archive = ".tar.gz" 22 | # The archive format to use for non-windows builds (defaults .tar.xz) 23 | unix-archive = ".tar.gz" 24 | # A namespace to use when publishing this package to the npm registry 25 | npm-scope = "@axodotdev" 26 | # A GitHub repo to push Homebrew formulas to 27 | tap = "mistydemeo/homebrew-cargodisttest" 28 | # Publish jobs to run in CI 29 | publish-jobs = ["homebrew"] 30 | # Whether cargo-dist should create a Github Release or use an existing draft 31 | create-release = false 32 | # Whether to publish prereleases to package managers 33 | publish-prereleases = true 34 | # Publish jobs to run in CI 35 | pr-run-mode = "plan" 36 | 37 | [workspace.metadata.dist.dependencies.homebrew] 38 | cmake = { targets = ["x86_64-apple-darwin"] } 39 | libcue = { version = "2.2.1", targets = ["x86_64-apple-darwin"] } 40 | 41 | [workspace.metadata.dist.dependencies.apt] 42 | cmake = '*' 43 | libcue-dev = { version = "2.2.1-2" } 44 | 45 | [workspace.metadata.dist.dependencies.chocolatey] 46 | lftp = '*' 47 | cmake = '3.27.6' 48 | -------------------------------------------------------------------------------- /axoproject/tests/projects/generic-workspace/generic2/dist.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic2" 3 | description = "A test of a C program for cargo-dist" 4 | version = "0.0.1" 5 | license = "WTFPL" 6 | repository = "https://github.com/mistydemeo/testprog2" 7 | binaries = ["main"] 8 | build-command = ["make"] 9 | 10 | # Config for 'cargo dist' 11 | [workspace.metadata.dist] 12 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 13 | cargo-dist-version = "0.4.2" 14 | # CI backends to support 15 | ci = ["github"] 16 | # The installers to generate for each app 17 | installers = ["shell", "homebrew"] 18 | # Target platforms to build apps for (Rust target-triple syntax) 19 | targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"] 20 | # The archive format to use for windows builds (defaults .zip) 21 | windows-archive = ".tar.gz" 22 | # The archive format to use for non-windows builds (defaults .tar.xz) 23 | unix-archive = ".tar.gz" 24 | # A namespace to use when publishing this package to the npm registry 25 | npm-scope = "@axodotdev" 26 | # A GitHub repo to push Homebrew formulas to 27 | tap = "mistydemeo/homebrew-cargodisttest" 28 | # Publish jobs to run in CI 29 | publish-jobs = ["homebrew"] 30 | # Whether cargo-dist should create a Github Release or use an existing draft 31 | create-release = false 32 | # Whether to publish prereleases to package managers 33 | publish-prereleases = true 34 | # Publish jobs to run in CI 35 | pr-run-mode = "plan" 36 | 37 | [workspace.metadata.dist.dependencies.homebrew] 38 | cmake = { targets = ["x86_64-apple-darwin"] } 39 | libcue = { version = "2.2.1", targets = ["x86_64-apple-darwin"] } 40 | 41 | [workspace.metadata.dist.dependencies.apt] 42 | cmake = '*' 43 | libcue-dev = { version = "2.2.1-2" } 44 | 45 | [workspace.metadata.dist.dependencies.chocolatey] 46 | lftp = '*' 47 | cmake = '3.27.6' 48 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic1/dist.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic1" 3 | description = "A test of a C program for cargo-dist" 4 | version = "0.0.1" 5 | license = "WTFPL" 6 | repository = "https://github.com/mistydemeo/testprog1" 7 | binaries = ["main"] 8 | build-command = ["make"] 9 | 10 | # Config for 'cargo dist' 11 | [workspace.metadata.dist] 12 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 13 | cargo-dist-version = "0.4.2" 14 | # CI backends to support 15 | ci = ["github"] 16 | # The installers to generate for each app 17 | installers = ["shell", "homebrew"] 18 | # Target platforms to build apps for (Rust target-triple syntax) 19 | targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"] 20 | # The archive format to use for windows builds (defaults .zip) 21 | windows-archive = ".tar.gz" 22 | # The archive format to use for non-windows builds (defaults .tar.xz) 23 | unix-archive = ".tar.gz" 24 | # A namespace to use when publishing this package to the npm registry 25 | npm-scope = "@axodotdev" 26 | # A GitHub repo to push Homebrew formulas to 27 | tap = "mistydemeo/homebrew-cargodisttest" 28 | # Publish jobs to run in CI 29 | publish-jobs = ["homebrew"] 30 | # Whether cargo-dist should create a Github Release or use an existing draft 31 | create-release = false 32 | # Whether to publish prereleases to package managers 33 | publish-prereleases = true 34 | # Publish jobs to run in CI 35 | pr-run-mode = "plan" 36 | 37 | [workspace.metadata.dist.dependencies.homebrew] 38 | cmake = { targets = ["x86_64-apple-darwin"] } 39 | libcue = { version = "2.2.1", targets = ["x86_64-apple-darwin"] } 40 | 41 | [workspace.metadata.dist.dependencies.apt] 42 | cmake = '*' 43 | libcue-dev = { version = "2.2.1-2" } 44 | 45 | [workspace.metadata.dist.dependencies.chocolatey] 46 | lftp = '*' 47 | cmake = '3.27.6' 48 | -------------------------------------------------------------------------------- /axoproject/tests/projects/shared-workspace/generic2/dist.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generic2" 3 | description = "A test of a C program for cargo-dist" 4 | version = "0.0.1" 5 | license = "WTFPL" 6 | repository = "https://github.com/mistydemeo/testprog2" 7 | binaries = ["main"] 8 | build-command = ["make"] 9 | 10 | # Config for 'cargo dist' 11 | [workspace.metadata.dist] 12 | # The preferred cargo-dist version to use in CI (Cargo.toml SemVer syntax) 13 | cargo-dist-version = "0.4.2" 14 | # CI backends to support 15 | ci = ["github"] 16 | # The installers to generate for each app 17 | installers = ["shell", "homebrew"] 18 | # Target platforms to build apps for (Rust target-triple syntax) 19 | targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-musl"] 20 | # The archive format to use for windows builds (defaults .zip) 21 | windows-archive = ".tar.gz" 22 | # The archive format to use for non-windows builds (defaults .tar.xz) 23 | unix-archive = ".tar.gz" 24 | # A namespace to use when publishing this package to the npm registry 25 | npm-scope = "@axodotdev" 26 | # A GitHub repo to push Homebrew formulas to 27 | tap = "mistydemeo/homebrew-cargodisttest" 28 | # Publish jobs to run in CI 29 | publish-jobs = ["homebrew"] 30 | # Whether cargo-dist should create a Github Release or use an existing draft 31 | create-release = false 32 | # Whether to publish prereleases to package managers 33 | publish-prereleases = true 34 | # Publish jobs to run in CI 35 | pr-run-mode = "plan" 36 | 37 | [workspace.metadata.dist.dependencies.homebrew] 38 | cmake = { targets = ["x86_64-apple-darwin"] } 39 | libcue = { version = "2.2.1", targets = ["x86_64-apple-darwin"] } 40 | 41 | [workspace.metadata.dist.dependencies.apt] 42 | cmake = '*' 43 | libcue-dev = { version = "2.2.1-2" } 44 | 45 | [workspace.metadata.dist.dependencies.chocolatey] 46 | lftp = '*' 47 | cmake = '3.27.6' 48 | -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:."] 3 | 4 | # Config for 'dist' 5 | [dist] 6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 7 | cargo-dist-version = "0.28.7-prerelease.3" 8 | # CI backends to support 9 | ci = "github" 10 | # The installers to generate for each app 11 | installers = ["shell", "powershell"] 12 | # Target platforms to build apps for (Rust target-triple syntax) 13 | targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"] 14 | # Which actions to run on pull requests 15 | pr-run-mode = "plan" 16 | # Where to host releases 17 | hosting = "github" 18 | # Whether to install an updater program 19 | install-updater = false 20 | # Whether to enable GitHub Attestations 21 | github-attestations = true 22 | # Path that installers should place binaries in 23 | install-path = "CARGO_HOME" 24 | 25 | [[dist.extra-artifacts]] 26 | artifacts = ["dist-manifest-schema.json"] 27 | build = ["cargo", "run", "--release", "--", "dist", "manifest-schema", "--output=dist-manifest-schema.json"] 28 | 29 | [dist.bin-aliases] 30 | "dist" = ["cargo-dist"] 31 | 32 | [dist.github-custom-runners.aarch64-unknown-linux-gnu.container] 33 | image = "quay.io/pypa/manylinux_2_28_x86_64" 34 | host = "x86_64-unknown-linux-musl" 35 | 36 | [dist.github-custom-runners.aarch64-unknown-linux-musl.container] 37 | image = "quay.io/pypa/manylinux_2_28_x86_64" 38 | host = "x86_64-unknown-linux-musl" 39 | 40 | [dist.github-action-commits] 41 | "actions/checkout" = "11bd71901bbe5b1630ceea73d27597364c9af683" # v4 42 | "actions/upload-artifact" = "ea165f8d65b6e75b540449e92b4886f43607fa02" # v4.6.2 43 | "actions/download-artifact" = "95815c38cf2ff2164869cbab79da8d1f422bc89e" # v4.2.1 44 | "actions/attest-build-provenance" = "c074443f1aee8d4aeeae555aebba3282517141b2" #v2.2.3 45 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /cargo-dist/src/net.rs: -------------------------------------------------------------------------------- 1 | //! Centralized logic for initializing http clients to 2 | //! ensure uniform configuration. 3 | 4 | use crate::errors::DistResult; 5 | use axoasset::reqwest; 6 | 7 | /// Settings for http clients 8 | /// 9 | /// Any settings that should apply to all http requests should 10 | /// be stored here, to avoid different configurations. 11 | #[derive(Debug, Clone, Default)] 12 | pub struct ClientSettings {} 13 | 14 | impl ClientSettings { 15 | /// Create new ClientSettings using all necessary values 16 | pub fn new() -> Self { 17 | Self::default() 18 | } 19 | } 20 | 21 | /// Create a raw reqwest client 22 | /// 23 | /// As of this writing this shouldn't be used/exposed, as we'd prefer 24 | /// to avoid proliferating random http clients. For now AxoClient 25 | /// is sufficient. 26 | fn create_reqwest_client(ClientSettings {}: &ClientSettings) -> DistResult { 27 | let client = reqwest::Client::builder() 28 | .build() 29 | .expect("failed to initialize http client"); 30 | Ok(client) 31 | } 32 | 33 | /// Create an AxoClient 34 | /// 35 | /// Ideally this should be called only once and reused! 36 | pub fn create_axoasset_client(settings: &ClientSettings) -> DistResult { 37 | let client = create_reqwest_client(settings)?; 38 | Ok(axoasset::AxoClient::with_reqwest(client)) 39 | } 40 | 41 | /// Create a Gazenot client 42 | /// 43 | /// Gazenot clients are configured to particular sourcehosts, and creating 44 | /// one will error out if certain environment variables aren't set. As such, 45 | /// this should be called in a fairly lazy/latebound way -- only when we know 46 | /// for sure we HAVE to do gazenot http requests. 47 | pub fn create_gazenot_client( 48 | ClientSettings {}: &ClientSettings, 49 | source_host: &str, 50 | owner: &str, 51 | ) -> DistResult { 52 | let client = gazenot::Gazenot::into_the_abyss(source_host, owner)?; 53 | Ok(client) 54 | } 55 | -------------------------------------------------------------------------------- /cargo-dist/src/backend/mod.rs: -------------------------------------------------------------------------------- 1 | //! The backend of dist -- things it outputs 2 | 3 | use std::time::Duration; 4 | 5 | use axoasset::SourceFile; 6 | use camino::Utf8Path; 7 | use newline_converter::dos2unix; 8 | 9 | use crate::errors::{DistError, DistResult}; 10 | 11 | pub mod ci; 12 | pub mod installer; 13 | pub mod templates; 14 | 15 | /// Check if the given file has the same contents we generated 16 | pub fn diff_files(existing_file: &Utf8Path, new_file_contents: &str) -> DistResult<()> { 17 | // FIXME: should we catch all errors, or only LocalAssetNotFound? 18 | let existing = if let Ok(file) = SourceFile::load_local(existing_file) { 19 | file 20 | } else { 21 | SourceFile::new(existing_file.as_str(), String::new()) 22 | }; 23 | diff_source(existing, new_file_contents) 24 | } 25 | 26 | pub(crate) fn diff_source(existing: SourceFile, new_file_contents: &str) -> DistResult<()> { 27 | // Normalize away newline differences, those aren't worth failing things over 28 | let a = dos2unix(existing.contents()); 29 | let b = dos2unix(new_file_contents); 30 | 31 | // Diff the files with the Pretty "patience" algorithm 32 | // 33 | // The timeout exists because essentially diff algorithms iteratively refine the results, 34 | // and can convince themselves to try way too hard for minimum benefit. Hitting the timeout 35 | // isn't fatal, it just tells the algorithm to call the result "good enough" if it hits 36 | // something pathological. 37 | let diff = similar::TextDiff::configure() 38 | .algorithm(similar::Algorithm::Patience) 39 | .timeout(Duration::from_millis(10)) 40 | .diff_lines(&a, &b) 41 | .unified_diff() 42 | .header(existing.origin_path(), existing.origin_path()) 43 | .to_string(); 44 | 45 | if !diff.is_empty() { 46 | Err(DistError::CheckFileMismatch { 47 | file: existing, 48 | diff, 49 | }) 50 | } else { 51 | Ok(()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/short-help.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | --- 5 | stdout: 6 | Professional packaging and distribution for ambitious developers 7 | 8 | Usage: dist [OPTIONS] 9 | dist 10 | 11 | Commands: 12 | build Build artifacts 13 | init Setup or update dist 14 | migrate Migrate to the latest configuration variant 15 | generate Generate one or more pieces of configuration 16 | linkage Report on the dynamic libraries used by the built artifacts 17 | manifest Generate the final build manifest without running any builds 18 | plan Get a plan of what to build (and check project status) 19 | host Host artifacts 20 | selfupdate Performs a self-update, if a new version is available, and then 'init' 21 | help Print this message or the help of the given subcommand(s) 22 | 23 | Options: 24 | -h, --help Print help (see more with '--help') 25 | -V, --version Print version 26 | 27 | GLOBAL OPTIONS: 28 | -v, --verbose How verbose logging should be (log level) [default: warn] [possible values: off, error, warn, info, debug, trace] 29 | -o, --output-format The format of the output [default: human] [possible values: human, json] 30 | --no-local-paths Strip local paths from output (e.g. in the dist manifest json) 31 | -t, --target Target triples we want to build 32 | -i, --installer Installers we want to build [possible values: shell, powershell, npm, homebrew, msi] 33 | -c, --ci CI we want to support [possible values: github] 34 | --tag The (git) tag to use for the Announcement that each invocation of dist is performing 35 | --force-tag Force package versions to match the tag 36 | --allow-dirty Allow generated files like CI scripts to be out of date 37 | 38 | stderr: 39 | -------------------------------------------------------------------------------- /cargo-dist/templates/ci/github/partials/publish_github.yml.j2: -------------------------------------------------------------------------------- 1 | # Create a GitHub Release while uploading all files to it 2 | - name: "Download GitHub Artifacts" 3 | uses: {{{ actions["actions/download-artifact"] | safe }}} 4 | with: 5 | pattern: artifacts-* 6 | path: artifacts 7 | merge-multiple: true 8 | - name: Cleanup 9 | run: | 10 | # Remove the granular manifests 11 | rm -f artifacts/*-dist-manifest.json 12 | - name: Create GitHub Release 13 | env: {{%- set manifest = "needs.host.outputs.val" if release_phase == "announce" else "steps.host.outputs.manifest" %}} 14 | PRERELEASE_FLAG: "${{ fromJson({{{ manifest | safe }}}).announcement_is_prerelease && '--prerelease' || '' }}" 15 | {{%- if create_release %}} 16 | ANNOUNCEMENT_TITLE: "${{ fromJson({{{ manifest | safe }}}).announcement_title }}" 17 | ANNOUNCEMENT_BODY: "${{ fromJson({{{ manifest | safe }}}).announcement_github_body }}" 18 | {{%- endif %}} 19 | {{%- if github_releases_repo %}} 20 | REPO: "{{{ github_releases_repo.owner | safe }}}/{{{ github_releases_repo.repo | safe }}}" 21 | RELEASE_COMMIT: "${{ fromJson({{{ manifest | safe }}}).ci.github.external_repo_commit }}" 22 | # We need to ensure `gh` uses the token that can auth 23 | # to the external repo instead of the runner token. 24 | GH_TOKEN: ${{ secrets.GH_RELEASES_TOKEN }} 25 | {{%- else %}} 26 | RELEASE_COMMIT: "${{ github.sha }}" 27 | {{%- endif %}} 28 | run: | 29 | {{%- if create_release %}} 30 | # Write and read notes from a file to avoid quoting breaking things 31 | echo "$ANNOUNCEMENT_BODY" > $RUNNER_TEMP/notes.txt 32 | {{%- else %}} 33 | # If we're editing a release in place, we need to upload things ahead of time 34 | gh release upload "${{ needs.plan.outputs.tag }}" artifacts/* 35 | {{%- endif %}} 36 | 37 | {{{ release_command|safe }}} 38 | -------------------------------------------------------------------------------- /book/src/updating.md: -------------------------------------------------------------------------------- 1 | # Updating 2 | 3 | Just 4 | 5 | `dist init` 6 | 7 | That's it! 8 | 9 | Rerun init as much as you want, it should always preserve your old settings, and should never break anything (if it does, it's a bug!). 10 | 11 | If you have a project with dist setup on it, and want to upgrade to a new version, all you should ever need to do is locally install the new version of dist and run `dist init`. 12 | 13 | If you're simply adjusting your dist config, you should also rerun `dist init` to potentially get informed of new features/constraints your change runs into. It also ensures that things like your ci.yml are updated to reflect your new config. Basically every other dist command should error out if you *have* to rerun init, so you won't get far if you don't. 14 | 15 | We recommend running without `-y` for reruns, because this is the chance dist has to tell you about new features, or additional configuration that pairs with any adjustments you may have made. It will take that chance to ask you if you want to enable the feature or change the default value. 16 | 17 | In general the init command is designed to do incremental updates to your installation, and "first setup" is just a special case of this, where every incremental update is applicable. 18 | 19 | The command usually uses the absence of a setting in your config to determine if a feature has been setup before. As such, even though dist *generally* has default values for every piece of config, init will aggressively write the default back to your config to let future invocations know they don't need to ask about it. 20 | 21 | Which also means if you missed a prompt or want to reconfigure a feature, deleting the relevant setting from your config and rerunning `init` should work. 22 | 23 | There are two settings that init will always prompt you for: 24 | 25 | * what platforms do you want to build for 26 | * what installers do you want to have 27 | 28 | So if you ever want to add a new platform or installer, rerunning `dist init` is a great way to do that -- and then it can ask followup questions if you turn on a new installer! 29 | -------------------------------------------------------------------------------- /cargo-dist/tests/gallery/dist/tools.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::gallery::command::CommandInfo; 3 | 4 | // Allowing dead code because some of these are platform-specific 5 | #[allow(dead_code)] 6 | pub struct Tools { 7 | pub git: CommandInfo, 8 | pub cargo_dist: CommandInfo, 9 | pub shellcheck: Option, 10 | pub psanalyzer: Option, 11 | pub homebrew: Option, 12 | pub npm: Option, 13 | pub pnpm: Option, 14 | pub yarn: Option, 15 | pub tar: Option, 16 | } 17 | 18 | impl Tools { 19 | fn new() -> Self { 20 | eprintln!("getting tools..."); 21 | let git = CommandInfo::new("git", None).expect("git isn't installed"); 22 | 23 | // If OVERRIDE_* is set, prefer that over the version that cargo built for us, 24 | // this lets us test our shippable builds. 25 | let cargo_dist_path = 26 | std::env::var(ENV_RUNTIME_DIST_BIN).unwrap_or_else(|_| STATIC_DIST_BIN.to_owned()); 27 | let cargo_dist = 28 | CommandInfo::new("cargo-dist", Some(&cargo_dist_path)).expect("dist isn't built!?"); 29 | cargo_dist.version().expect("couldn't parse dist version!?"); 30 | let shellcheck = CommandInfo::new("shellcheck", None); 31 | let psanalyzer = CommandInfo::new_powershell_command("Invoke-ScriptAnalyzer"); 32 | let homebrew = CommandInfo::new("brew", None); 33 | let tar = CommandInfo::new("tar", None); 34 | let npm = CommandInfo::new_js("npm", None); 35 | let pnpm = CommandInfo::new_js("pnpm", None); 36 | let yarn = CommandInfo::new_js("yarn", None); 37 | assert!(tar.is_some()); 38 | Self { 39 | git, 40 | cargo_dist, 41 | shellcheck, 42 | psanalyzer, 43 | homebrew, 44 | npm, 45 | pnpm, 46 | yarn, 47 | tar, 48 | } 49 | } 50 | } 51 | 52 | impl ToolsImpl for Tools { 53 | fn git(&self) -> &CommandInfo { 54 | &self.git 55 | } 56 | } 57 | impl Default for Tools { 58 | fn default() -> Self { 59 | Self::new() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cargo-dist/src/sign/mod.rs: -------------------------------------------------------------------------------- 1 | //! Code/artifact signing support 2 | 3 | #[cfg(unix)] 4 | use std::os::unix::fs::PermissionsExt; 5 | 6 | use axoasset::AxoClient; 7 | use camino::Utf8Path; 8 | use cargo_dist_schema::TripleNameRef; 9 | 10 | use crate::{config::ProductionMode, DistResult}; 11 | 12 | mod macos; 13 | mod ssldotcom; 14 | 15 | /// Code/artifact signing providers 16 | #[derive(Debug)] 17 | pub struct Signing { 18 | macos: Option, 19 | ssldotcom: Option, 20 | } 21 | 22 | impl Signing { 23 | /// Setup signing 24 | pub fn new( 25 | client: &AxoClient, 26 | host_target: &TripleNameRef, 27 | dist_dir: &Utf8Path, 28 | ssldotcom_windows_sign: Option, 29 | macos_sign: bool, 30 | ) -> DistResult { 31 | let ssldotcom = 32 | ssldotcom::CodeSignTool::new(client, host_target, dist_dir, ssldotcom_windows_sign)?; 33 | let macos = if macos_sign { 34 | macos::Codesign::new(host_target)? 35 | } else { 36 | None 37 | }; 38 | Ok(Self { macos, ssldotcom }) 39 | } 40 | 41 | /// Sign a file 42 | pub fn sign(&self, file: &Utf8Path) -> DistResult<()> { 43 | if let Some(signer) = &self.ssldotcom { 44 | let extension = file.extension().unwrap_or_default(); 45 | if let "exe" | "msi" | "ps1" = extension { 46 | signer.sign(file)?; 47 | } 48 | } 49 | if let Some(signer) = &self.macos { 50 | // TODO: restructure, this is just to keep Windows 51 | // from flagging dead code 52 | #[cfg(unix)] 53 | let is_executable = file.metadata()?.permissions().mode() & 0o111 != 0; 54 | #[cfg(windows)] 55 | let is_executable = true; 56 | 57 | // At the moment, we're exclusively signing executables. 58 | // In the future, we may need to sign app bundles (which are 59 | // directories) or certain other metadata files. 60 | if file.is_file() && is_executable { 61 | signer.sign(file)?; 62 | } 63 | } 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/lib_manifest.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | --- 5 | stdout: 6 | { 7 | "dist_version": "1.0.0-FAKEVERSION", 8 | "announcement_tag": "CENSORED", 9 | "announcement_tag_is_implicit": false, 10 | "announcement_is_prerelease": "CENSORED" 11 | "announcement_title": "CENSORED" 12 | "announcement_changelog": "CENSORED" 13 | "announcement_github_body": "CENSORED" 14 | "releases": [ 15 | { 16 | "app_name": "cargo-dist-schema", 17 | "app_version": "1.0.0-FAKEVERSION", 18 | "env": { 19 | "install_dir_env_var": "CARGO_DIST_SCHEMA_INSTALL_DIR", 20 | "unmanaged_dir_env_var": "CARGO_DIST_SCHEMA_UNMANAGED_INSTALL", 21 | "disable_update_env_var": "CARGO_DIST_SCHEMA_DISABLE_UPDATE", 22 | "no_modify_path_env_var": "CARGO_DIST_SCHEMA_NO_MODIFY_PATH", 23 | "print_quiet_env_var": "CARGO_DIST_SCHEMA_PRINT_QUIET", 24 | "print_verbose_env_var": "CARGO_DIST_SCHEMA_PRINT_VERBOSE", 25 | "download_url_env_var": "CARGO_DIST_SCHEMA_DOWNLOAD_URL", 26 | "github_base_url_env_var": "CARGO_DIST_SCHEMA_INSTALLER_GITHUB_BASE_URL", 27 | "ghe_base_url_env_var": "CARGO_DIST_SCHEMA_INSTALLER_GHE_BASE_URL", 28 | "github_token_env_var": "CARGO_DIST_SCHEMA_GITHUB_TOKEN" 29 | }, 30 | "display_name": "cargo-dist-schema", 31 | "display": true, 32 | "hosting": { 33 | "github": { 34 | "artifact_base_url": "https://github.com", 35 | "artifact_download_path": "/astral-sh/cargo-dist/releases/download/cargo-dist-schema-v1.0.0-FAKEVERSION", 36 | "owner": "astral-sh", 37 | "repo": "cargo-dist" 38 | } 39 | } 40 | } 41 | ], 42 | "systems": { 43 | "plan:all:": { 44 | "id": "plan:all:", 45 | "cargo_version_line": "CENSORED" 46 | "build_environment": "indeterminate" 47 | } 48 | }, 49 | "publish_prereleases": false, 50 | "force_latest": false, 51 | "ci": { 52 | "github": { 53 | "artifacts_matrix": {}, 54 | "pr_run_mode": "plan" 55 | } 56 | }, 57 | "linkage": [], 58 | "upload_files": [], 59 | "github_attestations": true 60 | } 61 | 62 | stderr: 63 | -------------------------------------------------------------------------------- /cargo-dist/tests/snapshots/lib_manifest_slash.snap: -------------------------------------------------------------------------------- 1 | --- 2 | source: cargo-dist/tests/cli-tests.rs 3 | expression: format_outputs(&output) 4 | --- 5 | stdout: 6 | { 7 | "dist_version": "1.0.0-FAKEVERSION", 8 | "announcement_tag": "CENSORED", 9 | "announcement_tag_is_implicit": false, 10 | "announcement_is_prerelease": "CENSORED" 11 | "announcement_title": "CENSORED" 12 | "announcement_changelog": "CENSORED" 13 | "announcement_github_body": "CENSORED" 14 | "releases": [ 15 | { 16 | "app_name": "cargo-dist-schema", 17 | "app_version": "1.0.0-FAKEVERSION", 18 | "env": { 19 | "install_dir_env_var": "CARGO_DIST_SCHEMA_INSTALL_DIR", 20 | "unmanaged_dir_env_var": "CARGO_DIST_SCHEMA_UNMANAGED_INSTALL", 21 | "disable_update_env_var": "CARGO_DIST_SCHEMA_DISABLE_UPDATE", 22 | "no_modify_path_env_var": "CARGO_DIST_SCHEMA_NO_MODIFY_PATH", 23 | "print_quiet_env_var": "CARGO_DIST_SCHEMA_PRINT_QUIET", 24 | "print_verbose_env_var": "CARGO_DIST_SCHEMA_PRINT_VERBOSE", 25 | "download_url_env_var": "CARGO_DIST_SCHEMA_DOWNLOAD_URL", 26 | "github_base_url_env_var": "CARGO_DIST_SCHEMA_INSTALLER_GITHUB_BASE_URL", 27 | "ghe_base_url_env_var": "CARGO_DIST_SCHEMA_INSTALLER_GHE_BASE_URL", 28 | "github_token_env_var": "CARGO_DIST_SCHEMA_GITHUB_TOKEN" 29 | }, 30 | "display_name": "cargo-dist-schema", 31 | "display": true, 32 | "hosting": { 33 | "github": { 34 | "artifact_base_url": "https://github.com", 35 | "artifact_download_path": "/astral-sh/cargo-dist/releases/download/cargo-dist-schema/v1.0.0-FAKEVERSION", 36 | "owner": "astral-sh", 37 | "repo": "cargo-dist" 38 | } 39 | } 40 | } 41 | ], 42 | "systems": { 43 | "plan:all:": { 44 | "id": "plan:all:", 45 | "cargo_version_line": "CENSORED" 46 | "build_environment": "indeterminate" 47 | } 48 | }, 49 | "publish_prereleases": false, 50 | "force_latest": false, 51 | "ci": { 52 | "github": { 53 | "artifacts_matrix": {}, 54 | "pr_run_mode": "plan" 55 | } 56 | }, 57 | "linkage": [], 58 | "upload_files": [], 59 | "github_attestations": true 60 | } 61 | 62 | stderr: 63 | -------------------------------------------------------------------------------- /cargo-dist/src/build/fake.rs: -------------------------------------------------------------------------------- 1 | //! real fake binaries, no substance, all style 2 | //! 3 | //! used by --artifacts=lies to reproduce as much of our builds as possible 4 | //! without needing to actually run platform-specific builds 5 | 6 | use axoasset::LocalAsset; 7 | use camino::Utf8PathBuf; 8 | use cargo_dist_schema::DistManifest; 9 | 10 | use crate::{BinaryIdx, CargoBuildStep, DistGraph, DistResult, GenericBuildStep}; 11 | 12 | use super::BuildExpectations; 13 | 14 | /// pretend to build a cargo target 15 | /// 16 | /// This produces empty binaries but otherwise emulates the build process as much as possible. 17 | pub fn build_fake_cargo_target( 18 | dist: &DistGraph, 19 | manifest: &mut DistManifest, 20 | target: &CargoBuildStep, 21 | ) -> DistResult<()> { 22 | build_fake_binaries(dist, manifest, &target.expected_binaries) 23 | } 24 | 25 | /// build a fake generic target 26 | /// 27 | /// This produces empty binaries but otherwise emulates the build process as much as possible. 28 | pub fn build_fake_generic_target( 29 | dist: &DistGraph, 30 | manifest: &mut DistManifest, 31 | target: &GenericBuildStep, 32 | ) -> DistResult<()> { 33 | build_fake_binaries(dist, manifest, &target.expected_binaries) 34 | } 35 | 36 | /// build fake binaries, and emulate the build process as much as possible 37 | fn build_fake_binaries( 38 | dist: &DistGraph, 39 | manifest: &mut DistManifest, 40 | binaries: &[BinaryIdx], 41 | ) -> DistResult<()> { 42 | // Shove these in a temp dir inside the dist dir, where it's safe for us to do whatever 43 | let tmp = temp_dir::TempDir::new()?; 44 | let tempdir = 45 | Utf8PathBuf::from_path_buf(tmp.path().to_owned()).expect("temp_dir made non-utf8 path!?"); 46 | let mut expectations = BuildExpectations::new_fake(dist, binaries); 47 | 48 | for idx in binaries { 49 | let binary = dist.binary(*idx); 50 | let real_fake_bin = tempdir.join(&binary.file_name); 51 | let package_id = super::package_id_string(binary.pkg_id.as_ref()); 52 | LocalAsset::write_new_all("", &real_fake_bin)?; 53 | expectations.found_bins(package_id, vec![real_fake_bin]); 54 | } 55 | 56 | expectations.process_bins(dist, manifest)?; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /cargo-dist/templates/ci/github/partials/publish_homebrew.yml.j2: -------------------------------------------------------------------------------- 1 | publish-homebrew-formula: 2 | needs: 3 | - plan 4 | - host 5 | {{%- for job in host_jobs %}} 6 | - custom-{{{ job.name|safe }}} 7 | {{%- endfor %}} 8 | runs-on: {{{ global_task.runner }}} 9 | env: 10 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 11 | PLAN: ${{ needs.plan.outputs.val }} 12 | GITHUB_USER: "axo bot" 13 | GITHUB_EMAIL: "admin+bot@axo.dev" 14 | if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} 15 | steps: 16 | - uses: {{{ actions["actions/checkout"] | safe }}} 17 | with: 18 | persist-credentials: false 19 | repository: {{{ tap }}} 20 | token: ${{ secrets.HOMEBREW_TAP_TOKEN }} 21 | # So we have access to the formula 22 | - name: Fetch homebrew formulae 23 | uses: {{{ actions["actions/download-artifact"] | safe }}} 24 | with: 25 | pattern: artifacts-* 26 | path: Formula/ 27 | merge-multiple: true 28 | # This is extra complex because you can make your Formula name not match your app name 29 | # so we need to find releases with a *.rb file, and publish with that filename. 30 | - name: Commit formula files 31 | run: | 32 | git config --global user.name "${GITHUB_USER}" 33 | git config --global user.email "${GITHUB_EMAIL}" 34 | 35 | for release in $(echo "$PLAN" | jq --compact-output '.releases[] | select([.artifacts[] | endswith(".rb")] | any)'); do 36 | filename=$(echo "$release" | jq '.artifacts[] | select(endswith(".rb"))' --raw-output) 37 | name=$(echo "$filename" | sed "s/\.rb$//") 38 | version=$(echo "$release" | jq .app_version --raw-output) 39 | 40 | export PATH="/home/linuxbrew/.linuxbrew/bin:$PATH" 41 | brew update 42 | # We avoid reformatting user-provided data such as the app description and homepage. 43 | brew style --except-cops FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict --fix "Formula/${filename}" || true 44 | 45 | git add "Formula/${filename}" 46 | git commit -m "${name} ${version}" 47 | done 48 | git push 49 | -------------------------------------------------------------------------------- /axoproject/src/local_repo.rs: -------------------------------------------------------------------------------- 1 | //! Details for git repository 2 | 3 | use axoprocess::Cmd; 4 | use camino::{Utf8Path, Utf8PathBuf}; 5 | 6 | use crate::errors::Result; 7 | 8 | /// Information about a git repo 9 | #[derive(Clone, Debug)] 10 | pub struct LocalRepo { 11 | /// The repository's absolute path on disk 12 | pub path: Utf8PathBuf, 13 | /// The repository's current HEAD 14 | /// This can be None in the case that a git repository 15 | /// has been `init`ted, but no commits have been made yet. 16 | pub head: Option, 17 | } 18 | 19 | impl LocalRepo { 20 | /// Returns a Repo for the git repository at `working_dir`. 21 | /// If git returns an error, for example if `working_dir` 22 | /// isn't a git repository, this will return an `Err`. 23 | /// The `git` param is the path to the `git` executable to use. 24 | pub fn new(git: &str, working_dir: &Utf8Path) -> Result { 25 | let path = get_root(git, working_dir)?; 26 | let head = get_head_commit(git, working_dir).ok(); 27 | 28 | Ok(Self { path, head }) 29 | } 30 | } 31 | 32 | fn get_root(git: &str, working_dir: &Utf8Path) -> Result { 33 | let mut cmd = Cmd::new(git, "detect a git repo"); 34 | cmd.arg("rev-parse") 35 | .arg("--show-toplevel") 36 | .stdout(std::process::Stdio::piped()) 37 | .stderr(std::process::Stdio::piped()) 38 | .check(false) 39 | .current_dir(working_dir); 40 | 41 | let result = cmd.output()?; 42 | // We do this two-step process here to normalize path separators, 43 | // since `git rev-parse --show-toplevel` uses Unix path separators 44 | // even on Windows. 45 | let raw = Utf8PathBuf::from(String::from_utf8(result.stdout)?.trim_end()); 46 | let root = Utf8PathBuf::from_iter(raw.components()); 47 | 48 | Ok(root) 49 | } 50 | 51 | fn get_head_commit(git: &str, working_dir: &Utf8Path) -> Result { 52 | let mut cmd = Cmd::new(git, "check for HEAD commit"); 53 | cmd.arg("rev-parse") 54 | .arg("HEAD") 55 | .stdout(std::process::Stdio::piped()) 56 | .stderr(std::process::Stdio::piped()) 57 | .check(false) 58 | .current_dir(working_dir); 59 | 60 | let result = cmd.output()?; 61 | 62 | let commit = String::from_utf8(result.stdout)?; 63 | Ok(commit.trim_end().to_owned()) 64 | } 65 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/installers/homebrew.rs: -------------------------------------------------------------------------------- 1 | //! homebrew installer config 2 | 3 | use super::*; 4 | 5 | /// Options for homebrew installer 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct HomebrewInstallerLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonInstallerLayer, 12 | /// A Homebrew tap to push the Homebrew formula to, if built 13 | pub tap: Option, 14 | /// Customize the name of the Homebrew formula 15 | pub formula: Option, 16 | } 17 | /// Options for homebrew installer 18 | #[derive(Debug, Default, Clone)] 19 | pub struct HomebrewInstallerConfig { 20 | /// Common options 21 | pub common: CommonInstallerConfig, 22 | /// A Homebrew tap to push the Homebrew formula to, if built 23 | pub tap: Option, 24 | /// Customize the name of the Homebrew formula 25 | pub formula: Option, 26 | } 27 | 28 | impl HomebrewInstallerConfig { 29 | /// Get defaults for the given package 30 | pub fn defaults_for_package( 31 | _workspaces: &WorkspaceGraph, 32 | _pkg_idx: PackageIdx, 33 | common: &CommonInstallerConfig, 34 | ) -> Self { 35 | Self { 36 | common: common.clone(), 37 | tap: None, 38 | formula: None, 39 | } 40 | } 41 | } 42 | 43 | impl ApplyLayer for HomebrewInstallerConfig { 44 | type Layer = HomebrewInstallerLayer; 45 | fn apply_layer( 46 | &mut self, 47 | Self::Layer { 48 | common, 49 | tap, 50 | formula, 51 | }: Self::Layer, 52 | ) { 53 | self.common.apply_layer(common); 54 | self.tap.apply_opt(tap); 55 | self.formula.apply_opt(formula); 56 | } 57 | } 58 | impl ApplyLayer for HomebrewInstallerLayer { 59 | type Layer = HomebrewInstallerLayer; 60 | fn apply_layer( 61 | &mut self, 62 | Self::Layer { 63 | common, 64 | tap, 65 | formula, 66 | }: Self::Layer, 67 | ) { 68 | self.common.apply_layer(common); 69 | self.tap.apply_opt(tap); 70 | self.formula.apply_opt(formula); 71 | } 72 | } 73 | 74 | impl std::ops::Deref for HomebrewInstallerConfig { 75 | type Target = CommonInstallerConfig; 76 | fn deref(&self) -> &Self::Target { 77 | &self.common 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cargo-dist/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cargo-dist" 3 | description = "Shippable application packaging for Rust" 4 | version.workspace = true 5 | edition.workspace = true 6 | license.workspace = true 7 | repository.workspace = true 8 | homepage.workspace = true 9 | rust-version.workspace = true 10 | readme = "../README.md" 11 | exclude = [ 12 | "book/*", 13 | "src/snapshots/*", 14 | "src/tests/", 15 | "tests/", 16 | ] 17 | 18 | [[bin]] 19 | name = "dist" 20 | path = "src/main.rs" 21 | 22 | [features] 23 | # Use bleeding edge features that might mess up people using 'cargo install' 24 | # with older toolchains. This is used for our prebuilt binaries. 25 | fear_no_msrv = ["axoprocess/stdout_to_stderr_modern"] 26 | tls_native_roots = ["axoasset/tls-native-roots", "gazenot/tls-native-roots", "axoupdater/tls_native_roots"] 27 | 28 | [dependencies] 29 | # Features only used by the cli 30 | clap.workspace = true 31 | serde_json.workspace = true 32 | console.workspace = true 33 | clap-cargo.workspace = true 34 | axocli.workspace = true 35 | axoupdater.workspace = true 36 | 37 | # Features used by the cli and library 38 | cargo-dist-schema.workspace = true 39 | axoproject.workspace = true 40 | 41 | axotag.workspace = true 42 | axoasset.workspace = true 43 | axoprocess.workspace = true 44 | gazenot.workspace = true 45 | 46 | comfy-table.workspace = true 47 | miette.workspace = true 48 | thiserror.workspace = true 49 | tracing.workspace = true 50 | serde.workspace = true 51 | cargo_metadata.workspace = true 52 | camino.workspace = true 53 | semver.workspace = true 54 | newline-converter.workspace = true 55 | dialoguer.workspace = true 56 | sha2.workspace = true 57 | minijinja.workspace = true 58 | include_dir.workspace = true 59 | itertools.workspace = true 60 | cargo-wix.workspace = true 61 | uuid.workspace = true 62 | mach_object.workspace = true 63 | goblin.workspace = true 64 | similar.workspace = true 65 | tokio.workspace = true 66 | temp-dir.workspace = true 67 | sha3.workspace = true 68 | blake2.workspace = true 69 | spdx.workspace = true 70 | base64.workspace = true 71 | lazy_static.workspace = true 72 | current_platform.workspace = true 73 | color-backtrace.workspace = true 74 | backtrace.workspace = true 75 | schemars.workspace = true 76 | 77 | [dev-dependencies] 78 | insta.workspace = true 79 | tar.workspace = true 80 | flate2.workspace = true 81 | 82 | [package.metadata.dist] 83 | features = ["fear_no_msrv", "tls_native_roots"] 84 | npm-package = "dist" 85 | 86 | [package.metadata.release] 87 | pre-release-replacements = [ 88 | {file="../CHANGELOG.md", search="# Unreleased", replace="# Unreleased\n\nNothing Yet!\n\n\n# Version {{version}} ({{date}})", exactly=1}, 89 | ] 90 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/installers/npm.rs: -------------------------------------------------------------------------------- 1 | //! npm installer config 2 | 3 | use super::*; 4 | 5 | /// Options for npm installer (~raw config file contents) 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct NpmInstallerLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonInstallerLayer, 12 | 13 | /// Replace the app's name with this value for the npm package's name 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub package: Option, 16 | 17 | /// A scope to prefix the npm package with (@ should be included). 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub scope: Option, 20 | } 21 | 22 | /// Options for npm installer (final) 23 | #[derive(Debug, Default, Clone)] 24 | pub struct NpmInstallerConfig { 25 | /// Common options 26 | pub common: CommonInstallerConfig, 27 | 28 | /// The app's name with this value for the npm package's name 29 | pub package: String, 30 | 31 | /// A scope to prefix the npm package with (@ should be included). 32 | pub scope: Option, 33 | } 34 | 35 | impl NpmInstallerConfig { 36 | /// Get defaults for the given package 37 | pub fn defaults_for_package( 38 | workspaces: &WorkspaceGraph, 39 | pkg_idx: PackageIdx, 40 | common: &CommonInstallerConfig, 41 | ) -> Self { 42 | let pkg = workspaces.package(pkg_idx); 43 | Self { 44 | common: common.clone(), 45 | package: pkg.name.clone(), 46 | scope: pkg.npm_scope.clone(), 47 | } 48 | } 49 | } 50 | 51 | impl ApplyLayer for NpmInstallerConfig { 52 | type Layer = NpmInstallerLayer; 53 | fn apply_layer( 54 | &mut self, 55 | Self::Layer { 56 | common, 57 | scope, 58 | package, 59 | }: Self::Layer, 60 | ) { 61 | self.common.apply_layer(common); 62 | self.scope.apply_opt(scope); 63 | self.package.apply_val(package); 64 | } 65 | } 66 | impl ApplyLayer for NpmInstallerLayer { 67 | type Layer = NpmInstallerLayer; 68 | fn apply_layer( 69 | &mut self, 70 | Self::Layer { 71 | common, 72 | scope, 73 | package, 74 | }: Self::Layer, 75 | ) { 76 | self.common.apply_layer(common); 77 | self.scope.apply_opt(scope); 78 | self.package.apply_opt(package); 79 | } 80 | } 81 | 82 | impl std::ops::Deref for NpmInstallerConfig { 83 | type Target = CommonInstallerConfig; 84 | fn deref(&self) -> &Self::Target { 85 | &self.common 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/installers/pkg.rs: -------------------------------------------------------------------------------- 1 | //! pkg installer config 2 | 3 | use super::*; 4 | 5 | /// Options for pkg installer 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct PkgInstallerLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonInstallerLayer, 12 | /// A unique identifier, in tld.domain.package format 13 | pub identifier: Option, 14 | /// The location to which the software should be installed. 15 | /// If not specified, /usr/local will be used. 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub install_location: Option, 18 | } 19 | /// Options for pkg installer 20 | #[derive(Debug, Default, Clone)] 21 | pub struct PkgInstallerConfig { 22 | /// Common options 23 | pub common: CommonInstallerConfig, 24 | /// A unique identifier, in tld.domain.package format 25 | pub identifier: Option, 26 | /// The location to which the software should be installed. 27 | /// If not specified, /usr/local will be used. 28 | pub install_location: String, 29 | } 30 | 31 | impl PkgInstallerConfig { 32 | /// Get defaults for the given package 33 | pub fn defaults_for_package( 34 | _workspaces: &WorkspaceGraph, 35 | _pkg_idx: PackageIdx, 36 | common: &CommonInstallerConfig, 37 | ) -> Self { 38 | Self { 39 | common: common.clone(), 40 | identifier: None, 41 | install_location: "/usr/local".to_owned(), 42 | } 43 | } 44 | } 45 | 46 | impl ApplyLayer for PkgInstallerConfig { 47 | type Layer = PkgInstallerLayer; 48 | fn apply_layer( 49 | &mut self, 50 | Self::Layer { 51 | common, 52 | identifier, 53 | install_location, 54 | }: Self::Layer, 55 | ) { 56 | self.common.apply_layer(common); 57 | self.identifier.apply_opt(identifier); 58 | self.install_location.apply_val(install_location); 59 | } 60 | } 61 | impl ApplyLayer for PkgInstallerLayer { 62 | type Layer = PkgInstallerLayer; 63 | fn apply_layer( 64 | &mut self, 65 | Self::Layer { 66 | common, 67 | identifier, 68 | install_location, 69 | }: Self::Layer, 70 | ) { 71 | self.common.apply_layer(common); 72 | self.identifier.apply_opt(identifier); 73 | self.install_location.apply_opt(install_location); 74 | } 75 | } 76 | 77 | impl std::ops::Deref for PkgInstallerConfig { 78 | type Target = CommonInstallerConfig; 79 | fn deref(&self) -> &Self::Target { 80 | &self.common 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /book/src/supplychain-security/index.md: -------------------------------------------------------------------------------- 1 | # Supply-chain security 2 | 3 | As software supplychain security concerns and requirements grow, `dist` is 4 | committed to making compliance with policies and regulations as turnkey as possible. 5 | 6 | If you have an integration you are looking for [file an issue](https://github.com/axodotdev/cargo-dist/issues/new) or 7 | [join our Discord](https://discord.gg/rW4JJ3Wa). 8 | 9 | ## Signing 10 | 11 | * [Windows Codesigning](./signing/windows.md) 12 | * [🔜 macOS Codesigning](https://github.com/axodotdev/cargo-dist/issues/1121) 13 | * [🔜 Linux Codesigning](https://github.com/axodotdev/cargo-dist/issues/120) 14 | * [🔜 Sigstore Signing](https://github.com/axodotdev/cargo-dist/issues/120) 15 | * [🔜 Windows Trusted Signing](https://github.com/axodotdev/cargo-dist/issues/1122) 16 | 17 | 18 | ## Attestation 19 | 20 | * [GitHub Attestation](./attestations/github.md) 21 | 22 | 23 | ## SBOMs and Dependency Managers 24 | 25 | ### cargo-cyclonedx 26 | 27 | dist can optionally generate a [CycloneDX][CycloneDX]-format Software Bill of Materials (SBOM) for Rust projects using the [cargo-cyclonedx][cargo-cyclonedx] tool. This data is stored as a standalone `bom.xml` file which is distributed alongside your binaries in your tarballs. Users can validate that SBOM file using [any compatible CycloneDX tool](https://cyclonedx.org/tool-center/). For more information about using this feature, see [the config documentation](../reference/config.html#cargo-cyclonedx). 28 | 29 | ### cargo-auditable 30 | 31 | [cargo-auditable][cargo-auditable] can optionally be used to embed dependency information into your Rust binaries, making it possible for users to check your binaries for the full dependency tree they were built from along with their precise versions. This information can then be checked later to scan your binary for any known vulnerabilities using the [cargo-audit][cargo-audit] tool. For more information about using this feature, see [the config documentation](../reference/config.html#cargo-auditable). 32 | 33 | ## Software identification 34 | 35 | dist can optionally generate an [OmniBOR artifact ID][omnibor] for software artifacts using the [omnibor-cli][omnibor-cli] tool. These identifiers are reproducible and unique to a specific version of your software. For more information about using this feature, see [the config documentation](../reference/config.html#omnibor). 36 | 37 | [CycloneDX]: https://cyclonedx.org 38 | [cargo-audit]: https://github.com/rustsec/rustsec/tree/main/cargo-audit#cargo-audit-bin-subcommand 39 | [cargo-auditable]: https://github.com/rust-secure-code/cargo-auditable 40 | [cargo-cyclonedx]: https://cyclonedx.org 41 | [omnibor]: https://omnibor.io 42 | [omnibor-cli]: https://github.com/omnibor/omnibor-rs/tree/main/omnibor-cli 43 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | 2 | build-all-platforms: 3 | #!/bin/bash -eux 4 | export AXOASSET_XZ_LEVEL=1 5 | cargo build 6 | ./target/debug/dist init --yes 7 | ./target/debug/dist build --artifacts all --target aarch64-apple-darwinx,x86_64-apple-darwin,x86_64-pc-windows-msvc,x86_64-unknown-linux-musl 8 | 9 | patch-sh-installer: 10 | #!/usr/bin/env node 11 | const fs = require('fs'); 12 | const installerUrl = process.env.INSTALLER_DOWNLOAD_URL || 'https://dl.bearcove.cloud/dump/dist-cross'; 13 | const installerPath = './target/distrib/cargo-dist-installer.sh'; 14 | 15 | const content = fs.readFileSync(installerPath, 'utf8'); 16 | const lines = content.split('\n'); 17 | let modified = false; 18 | 19 | const newLines = []; 20 | for (const line of lines) { 21 | if (line.includes('export INSTALLER_DOWNLOAD_URL')) { 22 | continue; 23 | } 24 | if (line.includes('set -u') && !modified) { 25 | modified = true; 26 | newLines.push(line); 27 | newLines.push(`export INSTALLER_DOWNLOAD_URL=${installerUrl} # patched by Justfile in dist repo, using dist_url_override feature`); 28 | continue; 29 | } 30 | newLines.push(line); 31 | } 32 | 33 | fs.writeFileSync(installerPath, newLines.join('\n')); 34 | 35 | if (modified) { 36 | console.log('\x1b[32m%s\x1b[0m', `✅ ${installerPath} patched successfully!`); 37 | console.log('\x1b[36m%s\x1b[0m', `🔗 Pointing to: ${installerUrl}`); 38 | } else { 39 | console.log('\x1b[31m%s\x1b[0m', '❌ Error: Could not find line with "set -u" in installer script'); 40 | } 41 | 42 | patch-ps1-installer: 43 | #!/usr/bin/env node 44 | const fs = require('fs'); 45 | const installerUrl = process.env.INSTALLER_DOWNLOAD_URL || 'https://dl.bearcove.cloud/dump/dist-cross'; 46 | const installerPath = './target/distrib/cargo-dist-installer.ps1'; 47 | 48 | const content = fs.readFileSync(installerPath, 'utf8'); 49 | const lines = content.split('\n'); 50 | let modified = false; 51 | 52 | const newLines = []; 53 | for (const line of lines) { 54 | if (line.includes('$env:INSTALLER_DOWNLOAD_URL = ')) { 55 | continue; 56 | } 57 | if (line.includes('$app_name = ') && !modified) { 58 | modified = true; 59 | newLines.push(`$env:INSTALLER_DOWNLOAD_URL = "${installerUrl}" # patched by Justfile in dist repo, using dist_url_override feature`); 60 | newLines.push(line); 61 | continue; 62 | } 63 | newLines.push(line); 64 | } 65 | 66 | fs.writeFileSync(installerPath, newLines.join('\n')); 67 | 68 | if (modified) { 69 | console.log('\x1b[32m%s\x1b[0m', `✅ ${installerPath} patched successfully!`); 70 | console.log('\x1b[36m%s\x1b[0m', `🔗 Pointing to: ${installerUrl}`); 71 | } else { 72 | console.log('\x1b[31m%s\x1b[0m', '❌ Error: Could not find line with "cargo-dist = " in installer script'); 73 | } 74 | 75 | dump: 76 | #!/bin/bash -eux 77 | just build-all-platforms 78 | just patch-sh-installer 79 | just patch-ps1-installer 80 | mc mirror --overwrite ./target/distrib ${DIST_TARGET:-bearcove/dump/dist-cross} 81 | -------------------------------------------------------------------------------- /book/src/custom-builds.md: -------------------------------------------------------------------------------- 1 | # Custom Builds 2 | 3 | > since 0.5.0 4 | 5 | When releasing software in languages other than Rust or JavaScript, you'll need to tell dist how to build it — there are more buildsystems than stars in the sky, and dist can't know how to run all of them (or how to figure out what to release from them). 6 | 7 | This guide assumes you've already initialized the dist config; check the [quickstart guide][quickstart-everyone-else] for how to get started. 8 | 9 | ## Examples 10 | 11 | * [example npm project](https://github.com/axodotdev/axolotlsay-js) 12 | * [example C project](https://github.com/axodotdev/cargo-dist-c-example) 13 | 14 | ### Understanding build commands 15 | 16 | Build commands are the core difference between these builds and Rust builds. Since we don't have Cargo to rely on to tell us how to build your package, it's up to you to tell us how instead. 17 | 18 | As an example, let's imagine a C program with a simple makefile-based buildsystem. Its `dist.toml` looks something like this: 19 | 20 | ```toml 21 | [package] 22 | # Your app's name 23 | name = "my_app" 24 | # The current version; make sure to keep this up to date! 25 | version = "0.1.0" 26 | # The URL to the git repository; this is used for publishing releases 27 | repository = "https://github.com/example/example" 28 | # The executables produced by your app 29 | binaries = ["main"] 30 | # The build command dist runs to produce those binaries 31 | build-command = ["make"] 32 | ``` 33 | 34 | All you need to run to build this program is `make`, so we specified `build-command = ["make"]`. If your app has a more complex build that will require multiple commands to run, it may be easier for you to add a build script to your repository. In that case, `build-command` can simply be a reference to executing it: 35 | 36 | ```toml 37 | build-command = ["./build.sh"] 38 | ``` 39 | 40 | We expose a special environment variable called `CARGO_DIST_TARGET` into your build. It contains a [Rust-style target triple][target-triple] for the platform we expect your build to build for. Depending on the language of the software you're building, you may need to use this to set appropriate cross-compilation flags. For example, when dist is building for an Apple Silicon Mac, we'll set `aarch64-apple-darwin` in order to allow your build to know when it should build for aarch64 even if the host is x86_64. 41 | 42 | On macOS, we expose several additional environment variables to help your buildsystem find dependencies. In the future, we may add more environment variables on all platforms. 43 | 44 | * `CFLAGS`/`CPPFLAGS`: Flags used by the C preprocessor and C compiler while building. 45 | * `LDFLAGS`: Flags used by the C linker. 46 | * `PKG_CONFIG_PATH`/`PKG_CONFIG_LIBDIR`: Paths for `pkg-config` to help it locate packages. 47 | * `CMAKE_INCLUDE_PATH`/`CMAKE_LIBRARY_PATH`: Paths for `cmake` to help it locate packages' configuration files. 48 | 49 | [cargo-toml]: https://doc.rust-lang.org/cargo/reference/manifest.html 50 | [quickstart-everyone-else]: ./quickstart/everyone-else.md 51 | [spdx]: https://spdx.org/licenses 52 | [target-triple]: https://doc.rust-lang.org/nightly/rustc/platform-support.html 53 | [toml]: https://en.wikipedia.org/wiki/TOML 54 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "axoproject", 4 | "cargo-dist", 5 | "cargo-dist-schema", 6 | ] 7 | resolver = "2" 8 | exclude = ["axoproject/tests/projects/"] 9 | 10 | [workspace.package] 11 | edition = "2021" 12 | license = "MIT OR Apache-2.0" 13 | repository = "https://github.com/astral-sh/cargo-dist" 14 | homepage = "https://github.com/astral-sh/cargo-dist" 15 | version = "0.28.7" 16 | rust-version = "1.74" 17 | 18 | [workspace.dependencies] 19 | # intra-workspace deps (you need to bump these versions when you cut releases too! 20 | cargo-dist-schema = { version = "=0.28.7", path = "cargo-dist-schema" } 21 | axoproject = { version = "=0.28.7", path = "axoproject", default-features = false, features = ["cargo-projects", "generic-projects", "npm-projects"] } 22 | 23 | # first-party deps 24 | axocli = { version = "0.2.0" } 25 | axoupdater = { version = "0.9.0" } 26 | axotag = "0.2.0" 27 | axoasset = { version = "1.2.0", features = ["json-serde", "toml-serde", "toml-edit", "yaml-serde", "compression", "remote"] } 28 | axoprocess = { version = "0.2.0" } 29 | gazenot = { version = "0.3.3" } 30 | 31 | # third-party deps 32 | clap = { version = "4.5.23", features = ["derive"] } 33 | console = { version = "0.15.10" } 34 | clap-cargo = { version = "0.14.0" } 35 | comfy-table = "7.1.3" 36 | miette = { version = "7.4.0" } 37 | thiserror = "2.0.9" 38 | tracing = { version = "0.1.41", features = ["log"] } 39 | serde = { version = "1.0.217", features = ["derive"] } 40 | serde_json = { version = "1.0.135" } 41 | cargo_metadata = "0.18.1" 42 | camino = { version = "1.1.9", features = ["serde1"] } 43 | semver = "1.0.24" 44 | newline-converter = "0.3.0" 45 | dialoguer = "0.11.0" 46 | sha2 = "0.10.6" 47 | minijinja = { version = "2.5.0", features = ["debug", "loader", "builtins", "json", "custom_syntax"] } 48 | include_dir = "0.7.4" 49 | itertools = "0.13.0" 50 | cargo-wix = "0.3.8" 51 | uuid = { version = "1", features = ["v4"] } 52 | mach_object = "0.1" 53 | goblin = "0.8.2" 54 | similar = "2.6.0" 55 | tokio = { version = "1.42.0", features = ["full"] } 56 | temp-dir = "0.1.14" 57 | sha3 = "0.10.8" 58 | blake2 = "0.10.6" 59 | insta = { version = "1.42.0", features = ["filters"] } 60 | tar = "0.4.43" 61 | flate2 = "1.0.35" 62 | pathdiff = { version = "0.2.3", features = ["camino"] } 63 | url = "2.5.4" 64 | parse-changelog = "0.6.10" 65 | schemars = "0.8.21" 66 | spdx = "0.10.8" 67 | base64 = "0.22.1" 68 | lazy_static = "1.5.0" 69 | current_platform = "0.2.0" 70 | color-backtrace = "0.6.1" 71 | backtrace = "0.3.74" 72 | target-lexicon = { version = "0.12.16", features = ["std"] } 73 | 74 | [workspace.metadata.release] 75 | shared-version = true 76 | tag-name = "v{{version}}" 77 | pre-release-commit-message = "release: {{version}}" 78 | publish = false 79 | 80 | [profile.dev] 81 | # level true/2 is usually overkill, cf. 82 | debug = "limited" 83 | # thin-local LTO is basically free, cf. 84 | lto = false 85 | # this speeds up xz compression etc. significantly. we could 86 | # be more selective about which packages to optimize but :shrug: 87 | package."*" = { opt-level = 2 } 88 | 89 | # The profile that 'cargo dist' will build with 90 | [profile.dist] 91 | inherits = "release" 92 | lto = "thin" 93 | -------------------------------------------------------------------------------- /cargo-dist-schema/README.md: -------------------------------------------------------------------------------- 1 | # cargo-dist-schema 2 | 3 | [![crates.io](https://img.shields.io/crates/v/cargo-dist-schema.svg)](https://crates.io/crates/cargo-dist) [![docs](https://docs.rs/cargo-dist-schema/badge.svg)](https://docs.rs/cargo-dist-schema) 4 | ![Rust CI](https://github.com/astral-sh/cargo-dist/workflows/Rust%20CI/badge.svg?branch=main) 5 | 6 | Schema reporting/parsing for dist's `dist-manifest.json`, which is the result you get from `--output-format=json` when running `cargo dist build` or `cargo dist plan`. 7 | 8 | [Read our documentation here!](https://opensource.axo.dev/cargo-dist/book/) 9 | 10 | This can be used to parse the machine-readable manifests produced by dist. Ideally it should be forward and backward compatible with newer and older versions of the manifests. 11 | 12 | This compatibility is fairly important as one tool may need to look at releases spread over *years*. Also dist is self-hosting from previous releases, so when looking at dist's own releases there will always be (at least) an off-by-one in the manifest and the tool that manifest describes. 13 | 14 | There are currently 3 epochs to dist-manifest.json: 15 | 16 | * epoch 1 <= 0.0.2 17 | * 0.0.3-prerelease9 <= epoch2 <= 0.0.6-prerelease.6 18 | * 0.0.3-prerelease.8 <= epoch3 19 | 20 | Epoch 1 was initial experimentation, and is no longer supported. 21 | 22 | Epoch 2 made some breaking changes once we had a better sense for the constraints of the design. Most notable artifacts were pull into a top-level Object that Releases simply refer to by key. This makes it possible for different releases to share an Artifact (such as debuginfo/symbol files for shared binaries). The version gap between Epoch 1 and 2 is a fuzzy zone with inadvisable-to-use prerelease copies of dist. We believe 0.0.3-prerelease9 is where things got solidified and should be the same as 0.0.3 proper. 23 | 24 | Epoch 3 has the exact same format but we removed versions from artifact id names, changing the format of URLs. This largely only affects dist's ability to fetch itself, and created a single transitory release (0.0.6-prerelease.7) which is unable to fetch itself, because it was built with an epoch2 version (0.0.5). This version is intentionally not published on crates.io. The CI was manually updated to use the right URLs to bootstrap 0.0.6-prerelease.8, which is fully in epoch3. 25 | 26 | ## A Brief Aside On Self-Hosting/Bootstrapping 27 | 28 | dist's CI is self-hosting using previous releases of itself. Ostensibly there's a coherent bootstrapping chain of releases, but around the Epoch boundary things get a bit wonky. You can always build from source with just `cargo build` so it's not exactly a big deal. 29 | 30 | But for my own edification: 31 | 32 | * [v0.0.1-prerelease1](https://github.com/axodotdev/cargo-dist/releases/tag/v0.0.1-prerelease1) was the first **unpublished** version, built with a temporary copy of itself 33 | * [v0.0.1-prerelease2](https://github.com/axodotdev/cargo-dist/releases/tag/v0.0.1-prerelease2) will be the first **published** version, built with 0.0.1-prerelease1 34 | * [v0.0.1](https://github.com/axodotdev/cargo-dist/releases/tag/v0.0.1) will be the first version built from another published version 35 | 36 | The awkward thing is that if we want the release for dist to include a feature new to itself, we need intermediate prereleases for the feature to "catch up". As such stable releases are basically never built from the previous stable release.https://github.com/axodotdev/cargo-dist/commit/8a417f239ef8f8e3ab66c46cf7c3d26afaba1c87 37 | -------------------------------------------------------------------------------- /axoproject/tests/projects/npm-create-react-app/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/artifacts/mod.rs: -------------------------------------------------------------------------------- 1 | //! artifact config 2 | 3 | pub mod archives; 4 | 5 | use super::*; 6 | use archives::*; 7 | 8 | /// app-specific artifact config (final) 9 | #[derive(Debug, Clone)] 10 | pub struct AppArtifactConfig { 11 | /// archive config 12 | pub archives: ArchiveConfig, 13 | /// Any extra artifacts and their buildscripts 14 | pub extra: Vec, 15 | } 16 | 17 | /// workspace artifact config (final) 18 | #[derive(Debug, Clone)] 19 | pub struct WorkspaceArtifactConfig { 20 | /// Whether to generate and dist a tarball containing your app's source code 21 | pub source_tarball: bool, 22 | /// Whether tarballs should include submodules 23 | pub recursive_tarball: bool, 24 | /// How to checksum 25 | pub checksum: ChecksumStyle, 26 | } 27 | /// artifact config (raw from file) 28 | #[derive(Debug, Clone, Serialize, Deserialize)] 29 | #[serde(rename_all = "kebab-case")] 30 | pub struct ArtifactLayer { 31 | /// archive config 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub archives: Option, 34 | 35 | /// Whether to generate and dist a tarball containing your app's source code 36 | /// 37 | /// (defaults to true) 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | pub source_tarball: Option, 40 | 41 | /// Whether source tarballs should include submodules 42 | /// 43 | /// (defaults to false) 44 | #[serde(skip_serializing_if = "Option::is_none")] 45 | pub recursive_tarball: Option, 46 | 47 | /// Any extra artifacts and their buildscripts 48 | #[serde(skip_serializing_if = "Option::is_none")] 49 | pub extra: Option>, 50 | 51 | /// How to checksum 52 | #[serde(skip_serializing_if = "Option::is_none")] 53 | pub checksum: Option, 54 | } 55 | impl AppArtifactConfig { 56 | /// get the defaults for a package 57 | pub fn defaults_for_package(workspaces: &WorkspaceGraph, pkg_idx: PackageIdx) -> Self { 58 | Self { 59 | archives: ArchiveConfig::defaults_for_package(workspaces, pkg_idx), 60 | extra: vec![], 61 | } 62 | } 63 | } 64 | 65 | impl WorkspaceArtifactConfig { 66 | /// get the defaults for a workspace 67 | pub fn defaults_for_workspace(_workspaces: &WorkspaceGraph) -> Self { 68 | Self { 69 | source_tarball: true, 70 | recursive_tarball: false, 71 | checksum: ChecksumStyle::Sha256, 72 | } 73 | } 74 | } 75 | 76 | impl ApplyLayer for AppArtifactConfig { 77 | type Layer = ArtifactLayer; 78 | fn apply_layer( 79 | &mut self, 80 | Self::Layer { 81 | archives, 82 | extra, 83 | // these are all workspace-only 84 | source_tarball: _, 85 | recursive_tarball: _, 86 | checksum: _, 87 | }: Self::Layer, 88 | ) { 89 | self.archives.apply_val_layer(archives); 90 | self.extra.apply_val(extra); 91 | } 92 | } 93 | 94 | impl ApplyLayer for WorkspaceArtifactConfig { 95 | type Layer = ArtifactLayer; 96 | fn apply_layer( 97 | &mut self, 98 | Self::Layer { 99 | source_tarball, 100 | recursive_tarball, 101 | checksum, 102 | // these are all app-only 103 | archives: _, 104 | extra: _, 105 | }: Self::Layer, 106 | ) { 107 | self.source_tarball.apply_val(source_tarball); 108 | self.recursive_tarball.apply_val(recursive_tarball); 109 | self.checksum.apply_val(checksum); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /cargo-dist/templates/installer/homebrew.rb.j2: -------------------------------------------------------------------------------- 1 | class {{ formula_class }} < Formula 2 | desc "{{ desc }}" 3 | {%- if homepage %} 4 | homepage "{{ homepage }}" 5 | {%- endif %} 6 | version "{{ inner.app_version }}" 7 | 8 | {#- note: this is a jinja2 macro: #} 9 | {%- macro fragment_url_and_hash(frag, cond) %} 10 | {%- if frag %} 11 | {%- if cond is defined %} 12 | if {{ cond }} 13 | url "{{ inner.base_url }}/{{ frag.id }}" 14 | {%- if frag.sha256 %} 15 | sha256 "{{ frag.sha256 }}" 16 | {%- endif %} 17 | end 18 | {%- else %} 19 | url "{{ inner.base_url }}/{{ frag.id }}" 20 | {%- if frag.sha256 %} 21 | sha256 "{{ frag.sha256 }}" 22 | {%- endif %} 23 | {%- endif %} 24 | {%- endif %} 25 | {%- endmacro %} 26 | 27 | {%- if arm64_macos or x86_64_macos %} 28 | if OS.mac? 29 | {%- if arm64_macos and x86_64_macos and arm64_macos.id == x86_64_macos.id %} 30 | {{- fragment_url_and_hash(arm64_macos) }} 31 | {%- else %} 32 | {{- fragment_url_and_hash(arm64_macos, "Hardware::CPU.arm?") }} 33 | {{- fragment_url_and_hash(x86_64_macos, "Hardware::CPU.intel?") }} 34 | {%- endif %} 35 | end 36 | {%- endif %} 37 | {%- if arm64_linux or x86_64_linux %} 38 | if OS.linux? 39 | {%- if arm64_linux and x86_64_linux and arm64_linux.id == x86_64_linux.id %} 40 | {{- fragment_url_and_hash(arm64_linux) }} 41 | {%- else %} 42 | {{- fragment_url_and_hash(arm64_linux, "Hardware::CPU.arm?") }} 43 | {{- fragment_url_and_hash(x86_64_linux, "Hardware::CPU.intel?") }} 44 | {%- endif %} 45 | end 46 | {%- endif %} 47 | 48 | {%- if license %} 49 | license {{ license }} 50 | {%- endif -%} 51 | {% for dep in dependencies %} 52 | depends_on "{{ dep }}" 53 | {%- endfor %} 54 | 55 | BINARY_ALIASES = {{ inner.bin_aliases | tojson(indent=2) | indent(2) }} 56 | 57 | def target_triple 58 | cpu = Hardware::CPU.arm? ? "aarch64" : "x86_64" 59 | os = OS.mac? ? "apple-darwin" : "unknown-linux-gnu" 60 | 61 | "#{cpu}-#{os}" 62 | end 63 | 64 | def install_binary_aliases! 65 | BINARY_ALIASES[target_triple.to_sym].each do |source, dests| 66 | dests.each do |dest| 67 | bin.install_symlink bin/source.to_s => dest 68 | end 69 | end 70 | end 71 | 72 | def install 73 | {%- macro install_fragment(frag, cond) %} 74 | {%- if frag %} 75 | if {{ cond }} 76 | {%- if frag.executables %} 77 | bin.install {% for binary in frag.executables %}"{{ binary }}"{{ ", " if not loop.last else "" }}{% endfor %} 78 | {%- endif %} 79 | {%- if frag.cdylibs and "cdylib" in install_libraries %} 80 | lib.install {% for library in frag.cdylibs %}"{{ library }}"{{ ", " if not loop.last else "" }}{% endfor %} 81 | {%- endif %} 82 | {%- if frag.cstaticlibs and "cstaticlib" in install_libraries %} 83 | lib.install {% for library in frag.cstaticlibs %}"{{ library }}"{{ ", " if not loop.last else "" }}{% endfor %} 84 | {%- endif %} 85 | end 86 | {%- endif %} 87 | {%- endmacro %} 88 | {{- install_fragment(arm64_macos, "OS.mac? && Hardware::CPU.arm?") }} 89 | {{- install_fragment(x86_64_macos, "OS.mac? && Hardware::CPU.intel?") }} 90 | {{- install_fragment(arm64_linux, "OS.linux? && Hardware::CPU.arm?") }} 91 | {{- install_fragment(x86_64_linux, "OS.linux? && Hardware::CPU.intel?") }} 92 | 93 | install_binary_aliases! 94 | 95 | # Homebrew will automatically install these, so we don't need to do that 96 | doc_files = Dir["README.*", "readme.*", "LICENSE", "LICENSE.*", "CHANGELOG.*"] 97 | leftover_contents = Dir["*"] - doc_files 98 | 99 | # Install any leftover files in pkgshare; these are probably config or 100 | # sample files. 101 | pkgshare.install(*leftover_contents) unless leftover_contents.empty? 102 | end 103 | end 104 | -------------------------------------------------------------------------------- /cargo-dist/src/backend/installer/macpkg.rs: -------------------------------------------------------------------------------- 1 | //! Code for generating installer.pkg 2 | 3 | use std::{collections::BTreeMap, fs}; 4 | 5 | use axoasset::LocalAsset; 6 | use axoprocess::Cmd; 7 | use camino::Utf8PathBuf; 8 | use serde::Serialize; 9 | use temp_dir::TempDir; 10 | use tracing::info; 11 | 12 | use crate::{create_tmp, DistResult}; 13 | 14 | use super::ExecutableZipFragment; 15 | 16 | /// Info about a package installer 17 | #[derive(Debug, Clone, Serialize)] 18 | pub struct PkgInstallerInfo { 19 | /// ExecutableZipFragment for this variant 20 | pub artifact: ExecutableZipFragment, 21 | /// Identifier for the final installer 22 | pub identifier: String, 23 | /// Default install location 24 | pub install_location: String, 25 | /// Final file path of the pkg 26 | pub file_path: Utf8PathBuf, 27 | /// Dir stuff goes to 28 | pub package_dir: Utf8PathBuf, 29 | /// The app version 30 | pub version: String, 31 | /// Executable aliases 32 | pub bin_aliases: BTreeMap>, 33 | } 34 | 35 | impl PkgInstallerInfo { 36 | /// Build the pkg installer 37 | pub fn build(&self) -> DistResult<()> { 38 | info!("building a pkg: {}", self.identifier); 39 | 40 | // We can't build directly from dist_dir because the 41 | // package installer wants the directory we feed it 42 | // to have the final package layout, which in this case 43 | // is going to be an FHS-ish path installed into a public 44 | // location. So instead we create a new tree with our stuff 45 | // like we want it, and feed that to pkgbuild. 46 | let (_build_dir, build_dir) = create_tmp()?; 47 | let bindir = build_dir.join("bin"); 48 | LocalAsset::create_dir_all(&bindir)?; 49 | let libdir = build_dir.join("lib"); 50 | LocalAsset::create_dir_all(&libdir)?; 51 | 52 | info!("Copying executables"); 53 | for exe in &self.artifact.executables { 54 | info!("{} => {:?}", &self.package_dir.join(exe), bindir.join(exe)); 55 | LocalAsset::copy_file_to_file(self.package_dir.join(exe), bindir.join(exe))?; 56 | } 57 | #[cfg(unix)] 58 | for (bin, targets) in &self.bin_aliases { 59 | for target in targets { 60 | std::os::unix::fs::symlink(bindir.join(bin), bindir.join(target))?; 61 | } 62 | } 63 | for lib in self 64 | .artifact 65 | .cdylibs 66 | .iter() 67 | .chain(self.artifact.cstaticlibs.iter()) 68 | { 69 | LocalAsset::copy_file_to_file(self.package_dir.join(lib), libdir.join(lib))?; 70 | } 71 | 72 | // The path the two pkg files get placed in while building 73 | let pkg_output = TempDir::new()?; 74 | let pkg_output_path = pkg_output.path(); 75 | let pkg_path = pkg_output_path.join("package.pkg"); 76 | let product_path = pkg_output_path.join("product.pkg"); 77 | 78 | let mut pkgcmd = Cmd::new("/usr/bin/pkgbuild", "create individual pkg"); 79 | pkgcmd.arg("--root").arg(build_dir); 80 | pkgcmd.arg("--identifier").arg(&self.identifier); 81 | pkgcmd.arg("--install-location").arg(&self.install_location); 82 | pkgcmd.arg("--version").arg(&self.version); 83 | pkgcmd.arg(&pkg_path); 84 | // Ensures stdout from the build process doesn't taint the dist-manifest 85 | pkgcmd.stdout_to_stderr(); 86 | pkgcmd.run()?; 87 | 88 | // OK, we've made a package. Now wrap it in a product pkg. 89 | let mut productcmd = Cmd::new("/usr/bin/productbuild", "create final product .pkg"); 90 | productcmd.arg("--package").arg(&pkg_path); 91 | productcmd.arg(&product_path); 92 | productcmd.stdout_to_stderr(); 93 | productcmd.run()?; 94 | 95 | fs::copy(&product_path, &self.file_path)?; 96 | 97 | Ok(()) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /book/src/installers/npm.md: -------------------------------------------------------------------------------- 1 | # npm Installer 2 | 3 | > since 0.0.6 4 | 5 | dist can automatically build and publish [npm](https://www.npmjs.com/) packages for your applications. Users can install your application with an expression like `npm install -g @axodotdev/axolotlsay`, or immediately run it with `npx @axodotdev/axolotlsay`. 6 | 7 | The npm package will [fetch][artifact-url] your prebuilt [archives](../artifacts/archives.md) and install your binaries to node_modules, exposing them as commands ("bins") of the package. 8 | If the package [unambiguously has one true command](https://docs.npmjs.com/cli/v7/commands/npx#description), then the package can be run without specifying one. 9 | 10 | Note that this is *not* (yet) a feature for publishing an npm package in your workspace. The package described here is generated as part of your release process. 11 | 12 | An "installer hint" will be provided that shows how to install via `npm` like so: 13 | 14 | ```sh 15 | npm install @axodotdev/cargodisttest@0.2.0 16 | ``` 17 | 18 | ## Quickstart 19 | 20 | To setup your npm installer you need to create an npm access token and enable the installer. This is broken up into parts because a project administrator may need to be involved in part 1, while part 2 can be done by anyone. 21 | 22 | 23 | ### Part 1: Creating an npm account and optional scope and authenticating GitHub Actions 24 | 25 | 1. Create an account on [npmjs.com](https://www.npmjs.com/signup). 26 | 1. (Optionally) If you would like to publish a "scoped" package (aka `@mycorp/pkg`) you'll need to [create an npm organization](https://www.npmjs.com/org/create). 27 | 2. Go to your npm account settings and create a granular access token: 28 | 29 | - Expiration: The default is 30 days. You can pick what works for you and your team. (NOTE: If you really want a token that does not expire you can use a Classic Token but we expect that option to eventually be fully deprecated in the near future.) 30 | - Packages and scopes: Read and write 31 | - Select packages: All packages (NOTE: because the package does not yet exist, you must pick this. However, you can (and probably should!) update this to scope the token to a single package after publish. This is sadly a limitation of the npm token system.) 32 | - Organizations: No access 33 | 34 | 3. Add the token as a [GitHub Actions Secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets) called `NPM_TOKEN` to the repository your are publishing from. 35 | 36 | 37 | ### Part 2: Enabling The npm Installer 38 | 39 | 1. run `dist init` on your project 40 | 2. when prompted to pick installers, enable "npm" 41 | 3. this should trigger a prompt for your optional scope (`@axodotdev`) 42 | 43 | ...that's it! If this worked, your config should now contain the following entries: 44 | 45 | ```toml 46 | [workspace.metadata.dist] 47 | # "..." indicates other installers you may have selected 48 | installers = ["...", "npm", "..."] 49 | # if you did not provide a scope, this won't be present 50 | npm-scope = "@axodotdev" 51 | publish-jobs = ["npm"] 52 | ``` 53 | 54 | Next make sure that `description` and `homepage` are set in your Cargo.toml. These 55 | fields are optional but make for better npm packages. 56 | 57 | ```toml 58 | [package] 59 | description = "a CLI for learning to distribute CLIs in rust" 60 | homepage = "https://github.com/axodotdev/axolotlsay" 61 | ``` 62 | 63 | ## Renaming npm packages 64 | 65 | > since 0.14.0 66 | 67 | By default the name of the npm package will be the name of the package that defines it (your Cargo package). If for whatever reason you don't want that to be the case, then you can change it with the [npm-package setting](../reference/config.md#npm-package). 68 | 69 | So with these settings: 70 | 71 | ```toml 72 | [package] 73 | name = "axolotlsay" 74 | 75 | [package.metadata.dist] 76 | npm-scope = "@axodotdev" 77 | npm-package = "cli" 78 | ``` 79 | 80 | You'll end up publish the binaries in "axolotlsay" to an npm package called "@axodotdev/cli". 81 | 82 | 83 | [artifact-url]: ../reference/artifact-url.md 84 | -------------------------------------------------------------------------------- /cargo-dist/templates/installer/npm/binary.js: -------------------------------------------------------------------------------- 1 | const { Package } = require("./binary-install"); 2 | const os = require("os"); 3 | const cTable = require("console.table"); 4 | const libc = require("detect-libc"); 5 | const { configureProxy } = require("axios-proxy-builder"); 6 | 7 | const error = (msg) => { 8 | console.error(msg); 9 | process.exit(1); 10 | }; 11 | 12 | const { 13 | name, 14 | artifactDownloadUrl, 15 | supportedPlatforms, 16 | glibcMinimum, 17 | } = require("./package.json"); 18 | 19 | const builderGlibcMajorVersion = glibcMinimum.major; 20 | const builderGlibcMInorVersion = glibcMinimum.series; 21 | 22 | const getPlatform = () => { 23 | const rawOsType = os.type(); 24 | const rawArchitecture = os.arch(); 25 | 26 | // We want to use rust-style target triples as the canonical key 27 | // for a platform, so translate the "os" library's concepts into rust ones 28 | let osType = ""; 29 | switch (rawOsType) { 30 | case "Windows_NT": 31 | osType = "pc-windows-msvc"; 32 | break; 33 | case "Darwin": 34 | osType = "apple-darwin"; 35 | break; 36 | case "Linux": 37 | osType = "unknown-linux-gnu"; 38 | break; 39 | } 40 | 41 | let arch = ""; 42 | switch (rawArchitecture) { 43 | case "x64": 44 | arch = "x86_64"; 45 | break; 46 | case "arm64": 47 | arch = "aarch64"; 48 | break; 49 | } 50 | 51 | if (rawOsType === "Linux") { 52 | if (libc.familySync() == "musl") { 53 | osType = "unknown-linux-musl-dynamic"; 54 | } else if (libc.isNonGlibcLinuxSync()) { 55 | console.warn( 56 | "Your libc is neither glibc nor musl; trying static musl binary instead", 57 | ); 58 | osType = "unknown-linux-musl-static"; 59 | } else { 60 | let libcVersion = libc.versionSync(); 61 | let splitLibcVersion = libcVersion.split("."); 62 | let libcMajorVersion = splitLibcVersion[0]; 63 | let libcMinorVersion = splitLibcVersion[1]; 64 | if ( 65 | libcMajorVersion != builderGlibcMajorVersion || 66 | libcMinorVersion < builderGlibcMInorVersion 67 | ) { 68 | // We can't run the glibc binaries, but we can run the static musl ones 69 | // if they exist 70 | console.warn( 71 | "Your glibc isn't compatible; trying static musl binary instead", 72 | ); 73 | osType = "unknown-linux-musl-static"; 74 | } 75 | } 76 | } 77 | 78 | // Assume the above succeeded and build a target triple to look things up with. 79 | // If any of it failed, this lookup will fail and we'll handle it like normal. 80 | let targetTriple = `${arch}-${osType}`; 81 | let platform = supportedPlatforms[targetTriple]; 82 | 83 | if (!platform) { 84 | error( 85 | `Platform with type "${rawOsType}" and architecture "${rawArchitecture}" is not supported by ${name}.\nYour system must be one of the following:\n\n${Object.keys( 86 | supportedPlatforms, 87 | ).join(",")}`, 88 | ); 89 | } 90 | 91 | return platform; 92 | }; 93 | 94 | const getPackage = () => { 95 | const platform = getPlatform(); 96 | const url = `${artifactDownloadUrl}/${platform.artifactName}`; 97 | let filename = platform.artifactName; 98 | let ext = platform.zipExt; 99 | let binary = new Package(name, url, filename, ext, platform.bins); 100 | 101 | return binary; 102 | }; 103 | 104 | const install = (suppressLogs) => { 105 | if (!artifactDownloadUrl || artifactDownloadUrl.length === 0) { 106 | console.warn("in demo mode, not installing binaries"); 107 | return; 108 | } 109 | const package = getPackage(); 110 | const proxy = configureProxy(package.url); 111 | 112 | return package.install(proxy, suppressLogs); 113 | }; 114 | 115 | const run = (binaryName) => { 116 | const package = getPackage(); 117 | const proxy = configureProxy(package.url); 118 | 119 | package.run(binaryName, proxy); 120 | }; 121 | 122 | module.exports = { 123 | install, 124 | run, 125 | getPackage, 126 | }; 127 | -------------------------------------------------------------------------------- /axoproject/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Version 0.7.1 (2024-04-16) 2 | 3 | * Updates several dependencies, including a relaxed axoasset range. Also removes the proof-of-concept axoasset binary. 4 | 5 | # Version 0.7.0 (2024-02-16) 6 | 7 | * Updates several dependencies, including a breaking change to miette. 8 | 9 | # Version 0.6.1 (2024-01-19) 10 | 11 | * Adds more platforms and improves their display names 12 | 13 | # Version 0.6.0 (2023-11-09) 14 | 15 | * Adds a new language-agnostic "generic" project type, with the package definition coming from a new `dist.toml` metadata file. This is gated behind the new `generic-projects` feature. 16 | 17 | # Version 0.5.0 (2023-11-01) 18 | 19 | * Uses axoasset's reexports of toml_edit and serde_json in place of direct dependencies 20 | 21 | # Version 0.4.7 (2023-09-05) 22 | 23 | * Now uses parse_changelog::title_no_link to strip links on titles 24 | * autoincludes are now found for all packages, including libraries 25 | 26 | # Version 0.4.6 (2023-08-28) 27 | 28 | * Upgrades cargo-dist config and rustc for release build. 29 | 30 | # Version 0.4.5 (2023-08-28) 31 | 32 | * The new "platforms" module contains target triple constants and a function to map triples to human-readable display strings. 33 | 34 | # Version 0.4.4 (2023-08-22) 35 | 36 | * WorkspaceInfo now supports the new features PackageInfo received in 0.4.3. 37 | * GithubRepo is now correctly exposed as public. 38 | 39 | # Version 0.4.3 (2023-08-09) 40 | 41 | * Updated dependencies. 42 | * PackageInfo can now parse the owner and repository name from a URL. 43 | * PackageInfo can now return a normalized web URL for a GitHub repository. 44 | * PackageInfo and WorkspaceInfo now have a "changelog_for_version" method based on the version from cargo-dist. 45 | 46 | # Version 0.4.2 (2023-07-04) 47 | 48 | Updating dependencies, specifically axoasset, to remove OpenSSL dependency. 49 | 50 | 51 | # Version 0.4.1 (2023-05-23) 52 | 53 | Just updating deps to get improvements to axoasset 54 | 55 | # Version 0.4.0 (2023-05-19) 56 | 57 | * the "root_dir" argument has been made the second argument in a ton of APIs, and renamed to "clamp_to_dir" 58 | * the find_file API has been factored out and exposed as public 59 | * Broken now includes a path to the manifest we found, to help with error messages and disambiguation 60 | 61 | # Version 0.3.0 (2023-04-24) 62 | 63 | * Added support for Cargo and NPM manifest keywords. For Cargo projects specifically, these will be squashed together 64 | with the categories field for now, since the dual design is very unique to crates.io specifically. 65 | 66 | # Version 0.2.0 (2023-04-10) 67 | 68 | This version reworks the design of the primary interface: 69 | 70 | * get_project(s) is now called get_workspace(s) to be more precise 71 | * get_workspaces no longer picks the "best" project for you, and instead returns results for all of them 72 | * to help you make sense of those results, they are now wrapped in a WorkspaceSearch enum that can either be: 73 | * "Found": we found and parsed the workspace, here it is 74 | * "Missing": we found no evidence of a workspace (no Cargo.toml) 75 | * "Broken": we found a manifest but failed to make sense of it (parse error, missing/weird values, etc.) 76 | * it now takes an optional "root" argument that specifies a root dir that we want to constrain the search to. 77 | most users can set this to None to just ignore the feature. 78 | * this isn't perfectly well-defined yet when a manifest is found under the root dir, but the root of the workspace 79 | is outside the root dir. this is fine for the intended purpose of clamping to a git checkout which presumably is 80 | completely self-contained as far as workspaces are concerned. 81 | * there is now a CLI app version of axoproject with json output 82 | * we now detect cstaticlibs and cdylibs in addition to binaries (in separate fields so if you don't care about them nothing has changed) 83 | 84 | In addition the internals have been significantly reworked into separate libraries like axoasset and axocli so more of our tools can share logic. 85 | 86 | 87 | # Version 0.1.0 (2023-03-27) 88 | 89 | Initial release! 90 | -------------------------------------------------------------------------------- /book/src/installers/powershell.md: -------------------------------------------------------------------------------- 1 | # PowerShell Script Installer 2 | 3 | > since 0.0.3 4 | 5 | This provides a powershell script (my-app-installer.ps1) which detects the current platform, fetches the best possible [archive][] from your [Artifact URL][artifact-url], copies the binary into your [install-path][config-install-path], and attempts to add that path to the user's PATH (see the next section for details). 6 | 7 | This kind of installer is ideal for bootstrapping setup on a fairly bare-bones system. 8 | 9 | An "installer hint" will be provided that shows how to install via `irm | iex` (the windows equivalent of `curl | sh`), like so: 10 | 11 | ```sh 12 | powershell -c "irm https://github.com/axodotdev/cargo-dist/releases/download/v0.0.5/cargo-dist-v0.0.5-installer.ps1 | iex" 13 | ``` 14 | 15 | Limitations/Caveats: 16 | 17 | * Requires a well-defined [Artifact URL][artifact-url] 18 | * Currently only really designed for "native windows", and won't detect other platforms properly 19 | * [Cannot detect situations where musl-based builds are appropriate][issue-musl] (static or dynamic) 20 | * Relies on the user's installation of `tar` and `Expand-Archive` to unpack the files 21 | * Relies on the the user's installation of `Net.Webclient` to fetch the files 22 | * [Will throw out all files except for the binary, so the binary can't rely on assets included in the archive][issue-unpack-all] 23 | * Cannot run any kind of custom install logic 24 | 25 | On the scale of Windows (where many people are still running Windows 7) commands like "Expand-Archive" and "tar" are in fact relatively new innovations. Any system that predates 2016 (PowerShell 5.0) certainly has no hope of working. I believe that someone running Windows 10 is basically guaranteed to work, and anything before that gets sketchier. 26 | 27 | In an ideal world most of these caveats improve (except for maybe the requirement of PowerShell >= 5.0 which is not pleasant to push past). 28 | 29 | 30 | ## Adding things to PATH 31 | 32 | Here is a more fleshed out description of how the powershell installer attempts to add the [install-path][config-install-path] to the user's PATH, and the limitations of that process. 33 | 34 | The most fundamental limitation is that installers fundamentally cannot edit the PATH of the currently running shell (it's a parent process). Powershell does not have an equivalent of `source`, so to the best of our knowledge restarting the shell is the only option (which if using Windows Terminal seems to mean opening a whole new window, tabs aren't good enough). As such, it benefits an installer to try to install to a directory that will already be on PATH (such as [CARGO_HOME][cargo home]). ([rustup also sends a broadcast WM_SETTINGCHANGE message](https://github.com/rust-lang/rustup/blob/bcfac6278c7c2f16a41294f7533aeee2f7f88d07/src/cli/self_update/windows.rs#L397-L409), but we couldn't find any evidence that this does anything useful.) 35 | 36 | The process we use to add [install-path][config-install-path] to the user's PATH is roughly the same process that rustup uses (hopefully making us harmonious with running rustup before/after one of our installer scripts). In the following description we will use `$install-path` as a placeholder for the path computed at install-time where the binaries get installed. Its actual value will likely look something like `C:\Users\axo\.myapp` or `C:\Users\.cargo\bin`. 37 | 38 | * we load from the registry `HKCU:\Environment`'s "Path" Item 39 | * we check if `$install-path` is contained within it already 40 | * if not, we prepend it and write the value back 41 | * prepending is used to ideally override system-installed binaries, as that is assumed to be desired when explicitly installing with not-your-system-package-manager 42 | * if we edited the registry, we prompt the user to restart their shell 43 | 44 | 45 | 46 | 47 | [issue-irm-iex]: https://github.com/axodotdev/oranda/issues/393 48 | [issue-musl]: https://github.com/axodotdev/cargo-dist/issues/75 49 | [issue-unpack-all]: https://github.com/axodotdev/cargo-dist/issues/307 50 | 51 | [config-install-path]: ../reference/config.md#install-path 52 | 53 | [archive]: ../artifacts/archives.md 54 | [artifact-url]: ../reference/artifact-url.md 55 | 56 | [cargo home]: https://doc.rust-lang.org/cargo/guide/cargo-home.html 57 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/hosts/github.rs: -------------------------------------------------------------------------------- 1 | //! github host 2 | 3 | use super::*; 4 | 5 | /// github host config (raw) 6 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 7 | #[serde(rename_all = "kebab-case")] 8 | pub struct GithubHostLayer { 9 | /// Common options 10 | #[serde(flatten)] 11 | pub common: CommonHostLayer, 12 | 13 | /// Whether we should create the Github Release for you when you push a tag. 14 | /// 15 | /// If true (default), dist will create a new Github Release and generate 16 | /// a title/body for it based on your changelog. 17 | /// 18 | /// If false, dist will assume a draft Github Release already exists 19 | /// with the title/body you want. At the end of a successful publish it will 20 | /// undraft the Github Release. 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub create: Option, 23 | 24 | /// Publish GitHub Releases to this repo instead of the current one 25 | /// 26 | /// The user must also set GH_RELEASES_TOKEN in their SECRETS 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub repo: Option, 29 | 30 | /// If `repo` is used, the commit ref to used will 31 | /// be read from the HEAD of the submodule at this path 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub submodule_path: Option, 34 | 35 | /// Which phase to create the github release in 36 | #[serde(skip_serializing_if = "Option::is_none")] 37 | pub during: Option, 38 | 39 | /// Whether GitHub Attestations is enabled (default false) 40 | #[serde(skip_serializing_if = "Option::is_none")] 41 | pub attestations: Option, 42 | } 43 | /// github host config (final) 44 | #[derive(Debug, Default, Clone)] 45 | pub struct GithubHostConfig { 46 | /// Common options 47 | pub common: CommonHostConfig, 48 | /// Whether we should create the Github Release for you 49 | pub create: bool, 50 | /// Publish GitHub Releases to this repo instead of the current one 51 | pub repo: Option, 52 | /// If github-releases-repo is used, the commit ref to used will 53 | /// be read from the HEAD of the submodule at this path 54 | pub submodule_path: Option, 55 | /// Which phase to create the github release in 56 | pub during: GithubReleasePhase, 57 | /// Whether GitHub Attestations is enabled (default false) 58 | pub attestations: bool, 59 | } 60 | 61 | impl GithubHostConfig { 62 | /// Get defaults for the given package 63 | pub fn defaults_for_workspace(_workspaces: &WorkspaceGraph, common: &CommonHostConfig) -> Self { 64 | Self { 65 | common: common.clone(), 66 | create: true, 67 | repo: None, 68 | submodule_path: None, 69 | during: GithubReleasePhase::default(), 70 | attestations: false, 71 | } 72 | } 73 | } 74 | 75 | impl ApplyLayer for GithubHostConfig { 76 | type Layer = GithubHostLayer; 77 | fn apply_layer( 78 | &mut self, 79 | Self::Layer { 80 | common, 81 | create, 82 | repo, 83 | submodule_path, 84 | during, 85 | attestations, 86 | }: Self::Layer, 87 | ) { 88 | self.common.apply_layer(common); 89 | self.create.apply_val(create); 90 | self.repo.apply_opt(repo); 91 | self.submodule_path.apply_opt(submodule_path); 92 | self.during.apply_val(during); 93 | self.attestations.apply_val(attestations); 94 | } 95 | } 96 | impl ApplyLayer for GithubHostLayer { 97 | type Layer = GithubHostLayer; 98 | fn apply_layer( 99 | &mut self, 100 | Self::Layer { 101 | common, 102 | create, 103 | repo, 104 | submodule_path, 105 | during, 106 | attestations, 107 | }: Self::Layer, 108 | ) { 109 | self.common.apply_layer(common); 110 | self.create.apply_opt(create); 111 | self.repo.apply_opt(repo); 112 | self.submodule_path.apply_opt(submodule_path); 113 | self.during.apply_opt(during); 114 | self.attestations.apply_opt(attestations); 115 | } 116 | } 117 | 118 | impl std::ops::Deref for GithubHostConfig { 119 | type Target = CommonHostConfig; 120 | fn deref(&self) -> &Self::Target { 121 | &self.common 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /cargo-dist/src/config/v1/artifacts/archives.rs: -------------------------------------------------------------------------------- 1 | //! archive config 2 | 3 | use super::*; 4 | 5 | /// archive config (final) 6 | #[derive(Debug, Clone)] 7 | pub struct ArchiveConfig { 8 | /// Include the following static files in bundles like archives. 9 | pub include: Vec, 10 | /// Whether to auto-include files like `README*`, `(UN)LICENSE*`, `RELEASES*`, and `CHANGELOG*` 11 | pub auto_includes: bool, 12 | /// The archive format to use for windows builds (defaults .zip) 13 | pub windows_archive: ZipStyle, 14 | /// The archive format to use for non-windows builds (defaults .tar.xz) 15 | pub unix_archive: ZipStyle, 16 | /// Whether to include built libraries in the release archive 17 | pub package_libraries: Vec, 18 | /// Binaries for a given platform 19 | pub binaries: SortedMap>, 20 | } 21 | 22 | /// archive config (raw from config file) 23 | #[derive(Debug, Default, Clone, Serialize, Deserialize)] 24 | #[serde(rename_all = "kebab-case")] 25 | pub struct ArchiveLayer { 26 | /// Include the following static files in bundles like archives. 27 | /// 28 | /// Paths are relative to the Cargo.toml this is defined in. 29 | /// 30 | /// Files like `README*`, `(UN)LICENSE*`, `RELEASES*`, and `CHANGELOG*` are already 31 | /// automatically detected and included (use [`DistMetadata::auto_includes`][] to prevent this). 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub include: Option>, 34 | 35 | /// Whether to auto-include files like `README*`, `(UN)LICENSE*`, `RELEASES*`, and `CHANGELOG*` 36 | /// 37 | /// Defaults to true. 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | pub auto_includes: Option, 40 | 41 | /// The archive format to use for windows builds (defaults .zip) 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | pub windows_archive: Option, 44 | 45 | /// The archive format to use for non-windows builds (defaults .tar.xz) 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub unix_archive: Option, 48 | 49 | /// Whether to include built libraries in the release archive 50 | #[serde(skip_serializing_if = "Option::is_none")] 51 | #[serde(default, with = "opt_string_or_vec")] 52 | pub package_libraries: Option>, 53 | 54 | /// Binaries for a given platform 55 | #[serde(skip_serializing_if = "Option::is_none")] 56 | pub binaries: Option>>, 57 | } 58 | 59 | impl ArchiveConfig { 60 | /// Get defaults for the given package 61 | pub fn defaults_for_package(_workspaces: &WorkspaceGraph, _pkg_idx: PackageIdx) -> Self { 62 | Self { 63 | include: vec![], 64 | auto_includes: true, 65 | windows_archive: ZipStyle::Zip, 66 | unix_archive: ZipStyle::Tar(CompressionImpl::Xzip), 67 | package_libraries: vec![], 68 | binaries: SortedMap::default(), 69 | } 70 | } 71 | } 72 | 73 | impl ApplyLayer for ArchiveConfig { 74 | type Layer = ArchiveLayer; 75 | fn apply_layer( 76 | &mut self, 77 | Self::Layer { 78 | include, 79 | auto_includes, 80 | windows_archive, 81 | unix_archive, 82 | package_libraries, 83 | binaries, 84 | }: Self::Layer, 85 | ) { 86 | self.include.apply_val(include); 87 | self.auto_includes.apply_val(auto_includes); 88 | self.windows_archive.apply_val(windows_archive); 89 | self.unix_archive.apply_val(unix_archive); 90 | self.package_libraries.apply_val(package_libraries); 91 | self.binaries.apply_val(binaries); 92 | } 93 | } 94 | impl ApplyLayer for ArchiveLayer { 95 | type Layer = ArchiveLayer; 96 | fn apply_layer( 97 | &mut self, 98 | Self::Layer { 99 | include, 100 | auto_includes, 101 | windows_archive, 102 | unix_archive, 103 | package_libraries, 104 | binaries, 105 | }: Self::Layer, 106 | ) { 107 | self.include.apply_opt(include); 108 | self.auto_includes.apply_opt(auto_includes); 109 | self.windows_archive.apply_opt(windows_archive); 110 | self.unix_archive.apply_opt(unix_archive); 111 | self.package_libraries.apply_opt(package_libraries); 112 | self.binaries.apply_opt(binaries); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # The "Normal" CI for tests and linters and whatnot 2 | name: Rust CI 3 | 4 | # Ci should be run on... 5 | on: 6 | # Every pull request (will need approval for new contributors) 7 | pull_request: 8 | # Every push to... 9 | push: 10 | branches: 11 | # The main branch 12 | - main 13 | # And once a week? 14 | # This can catch things like "rust updated and actually regressed something" 15 | schedule: 16 | - cron: "11 7 * * 1,4" 17 | 18 | # We want all these checks to fail if they spit out warnings 19 | env: 20 | RUSTFLAGS: -Dwarnings 21 | 22 | jobs: 23 | # Test and fmt the npm pkg 24 | npm: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - uses: actions/setup-node@v4 29 | with: 30 | node-version: 20 31 | - name: npm fmt 32 | working-directory: cargo-dist/templates/installer/npm 33 | run: npm ci && npm run fmt:check 34 | 35 | # Check that rustfmt is a no-op 36 | fmt: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - uses: actions/checkout@v3 40 | - uses: dtolnay/rust-toolchain@master 41 | with: 42 | toolchain: stable 43 | components: rustfmt 44 | - run: cargo fmt --all -- --check 45 | 46 | # Check that clippy is appeased 47 | clippy: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v3 51 | - uses: dtolnay/rust-toolchain@master 52 | with: 53 | toolchain: stable 54 | components: clippy 55 | - uses: swatinem/rust-cache@v2 56 | - uses: actions-rs/clippy-check@v1 57 | env: 58 | PWD: ${{ env.GITHUB_WORKSPACE }} 59 | with: 60 | token: ${{ secrets.GITHUB_TOKEN }} 61 | args: --workspace --tests --examples 62 | 63 | # Make sure the docs build without warnings 64 | docs: 65 | runs-on: ubuntu-latest 66 | env: 67 | RUSTDOCFLAGS: -Dwarnings 68 | steps: 69 | - uses: actions/checkout@master 70 | - uses: dtolnay/rust-toolchain@master 71 | with: 72 | toolchain: stable 73 | components: rust-docs 74 | - uses: swatinem/rust-cache@v2 75 | - run: cargo doc --workspace --no-deps 76 | 77 | # Check for typos (exceptions are provided by the typos.toml) 78 | typos: 79 | runs-on: ubuntu-latest 80 | steps: 81 | - uses: actions/checkout@v4 82 | - name: Check spelling of entire workspace 83 | uses: crate-ci/typos@v1.26.0 84 | 85 | # Build and run tests/doctests/examples on all platforms 86 | # FIXME: look into `cargo-hack` which lets you more aggressively 87 | # probe all your features and rust versions (see tracing's ci) 88 | test: 89 | runs-on: ${{ matrix.os }} 90 | env: 91 | # runtest the installer scripts 92 | RUIN_MY_COMPUTER_WITH_INSTALLERS: all 93 | strategy: 94 | # Test the cross-product of these platforms+toolchains 95 | matrix: 96 | os: [ubuntu-latest, windows-latest, macos-14] 97 | rust: [stable] 98 | steps: 99 | # Setup tools 100 | - uses: actions/checkout@master 101 | - uses: dtolnay/rust-toolchain@master 102 | with: 103 | toolchain: ${{ matrix.rust }} 104 | - uses: swatinem/rust-cache@v2 105 | with: 106 | key: ${{ matrix.os }} 107 | # install pnpm for npm runtests 108 | - run: npm i -g pnpm 109 | # install omnibor-cli for tests 110 | - run: | 111 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/omnibor/omnibor-rs/releases/latest/download/omnibor-cli-installer.sh | sh 112 | # Currently there is essentially no difference between default and --all-features, 113 | # with the difference essentially being polyfilling a new stdio API for MSRV. 114 | # For now avoid --all-features which causes issues with axoproject. 115 | 116 | # Run the tests/doctests (default features) 117 | # - run: cargo test --workspace 118 | # env: 119 | # PWD: ${{ env.GITHUB_WORKSPACE }} 120 | # Run the tests/doctests 121 | - run: cargo test --workspace 122 | env: 123 | PWD: ${{ env.GITHUB_WORKSPACE }} 124 | CARGO_DIST_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 125 | # Test the examples (default features) 126 | # - run: cargo test --workspace --examples --bins 127 | # env: 128 | # PWD: ${{ env.GITHUB_WORKSPACE }} 129 | # Test the examples 130 | - run: cargo test --workspace --examples --bins 131 | env: 132 | PWD: ${{ env.GITHUB_WORKSPACE }} 133 | -------------------------------------------------------------------------------- /book/src/installers/updater.md: -------------------------------------------------------------------------------- 1 | # Self-updater 2 | 3 | > since 0.12.0 4 | 5 | NOTE: This feature is currently experimental. 6 | 7 | Ordinarily, your users will need to visit your website and download an installer for the latest release in order to upgrade. Users who installed your software via a package manager, like Homebrew and npm, can use that package manager to upgrade to the latest release. For users of the [shell] and [PowerShell] installers, you can provide your users with a standalone installation program to upgrade more conveniently. 8 | 9 | If you add `install-updater = true` to your `Cargo.toml`, dist's shell and PowerShell installers will include an updater program alongside your program itself. This standalone program will be installed as the name `yourpackage-update`, and users can simply run it to poll for new releases and have them installed. The source code for this program is open source in the [axoupdater] repository. 10 | 11 | Users will interact with this updater by running the `yourpackage-update` command. It takes no options or arguments, and will automatically perform an upgrade without further input from the user. If your program supports custom external subcommands via the executable naming structure, like `git` and `cargo` do, then your user can also run `yourpackage update`. Here's a sample `axolotlsay-update` session as a demonstration of what your users will experience: 12 | 13 | ``` 14 | $ axolotlsay-update 15 | Checking for updates... 16 | downloading axolotlsay 0.2.114 aarch64-apple-darwin 17 | installing to /Users/mistydemeo/.cargo/bin 18 | axolotlsay 19 | axolotlsay-update 20 | everything's installed! 21 | New release installed! 22 | ``` 23 | 24 | If you would prefer to handle polling for updates yourself, for example in order to incorporate it as an internal subcommand of your own software, axoupdater is available as a [crate] which can be used as a library within your program. More information about how to use axoupdater as a library in your own program can be found in its README and in its [API documentation][axoupdater-docs]. 25 | 26 | ## Minimum supported version checking 27 | 28 | While dist will always fetch up to date versions of the updater when building your software, if you use axoupdater as a library then it's important to make sure that it's kept up to date to ensure compatibility. To help you test this, dist will attempt to check if the packages it's disting use axoupdater as a dependency; if it detects an unsupported, too-old version of axoupdater is in use, it will then refuse to continue to build in order to avoid distributing a package that's unsafe to update. 29 | 30 | ## GitHub Actions and Rate Limits in CI 31 | 32 | By default, axoupdater uses unauthenticated GitHub API calls when fetching release information. This is reliable in normal use, but it's much more likely to run into rate limits in the highly artificial environment of a CI test. If you're testing the standalone updater in your CI configuration, we recommend setting the `AXOUPDATER_GITHUB_TOKEN` environment variable to the value of the `GITHUB_TOKEN` secret that GitHub Action defines automatically. 33 | 34 | ```yaml 35 | env: 36 | AXOUPDATER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | ``` 38 | 39 | A sample in dist's CI configuration can be found [here][cargo-dist-ci-config]. 40 | 41 | If you use the axoupdater crate to implement the updater yourself, instructions for opting into a token in CI can be found [here][axoupdater-token-docs]. 42 | 43 | ## Releases with issues surrounding the standalone updater 44 | 45 | dist versions 0.21.1, 0.22.0 and 0.22.1 contain a bug which prevents the shell installer from installing the standalone updater alongside your binaries. This bug doesn't affect the PowerShell installer. Users of installers created with these releases will have had your software installed as normal, but won't have received an updater. Users whose first installation came via one of these installers will need to upgrade manually using a new shell installer. 46 | 47 | Users who first installed with an installer created with an older dist will still have their updater from their original installation, and so they will be able to update as normal. 48 | 49 | This issue was resolved in dist 0.23.0. 50 | 51 | [axoupdater]: https://github.com/axodotdev/axoupdater 52 | [axoupdater-docs]: https://docs.rs/axoupdater/ 53 | [axoupdater-token-docs]: https://github.com/axodotdev/axoupdater?tab=readme-ov-file#github-actions-and-rate-limits-in-ci 54 | [cargo-dist-ci-config]: https://github.com/axodotdev/cargo-dist/blob/80f2e19e5aa79b7b1f64beb62ceb07aa71566707/.github/workflows/ci.yml#L82-L85 55 | [crate]: https://crates.io/crates/axoupdater 56 | [shell]: ./shell.md 57 | [PowerShell]: ./powershell.md 58 | -------------------------------------------------------------------------------- /book/src/installers/shell.md: -------------------------------------------------------------------------------- 1 | # Shell Script Installer 2 | 3 | > since 0.0.3 4 | 5 | The "shell" installer provides a shell script (my-app-installer.sh) which detects the current platform, fetches the best possible [archive][] from your [Artifact URL][artifact-url], copies the binary into your [install-path][config-install-path], and attempts to add that path to the user's PATH (see the next section for details). 6 | 7 | This kind of installer is ideal for bootstrapping setup on a fairly bare-bones system. 8 | 9 | An "installer hint" will be provided that shows how to install via `curl | sh`, like so: 10 | 11 | ```sh 12 | curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.0.5/cargo-dist-v0.0.5-installer.sh | sh 13 | ``` 14 | 15 | Limitations/Caveats: 16 | 17 | * Requires a well-defined [Artifact URL][artifact-url] 18 | * Currently only really designed for "linux" and "macOS", and won't detect other platforms properly (and certainly won't play nice with things like nixOS). 19 | * [Cannot detect situations where musl-based builds are appropriate][issue-musl] (static or dynamic) 20 | * Relies on the user's installation of `tar` and `unzip` to unpack the files 21 | * Relies on the the user's installation of `curl` or `wget` to fetch the files 22 | * [Will throw out all files except for the binary, so the binary can't rely on assets included in the archive][issue-unpack-all] 23 | * Cannot run any kind of custom install logic 24 | 25 | In an ideal world all of these caveats improve (except for maybe relying on tar/unzip/curl/wget, that's kinda fundamental). 26 | 27 | 28 | 29 | ## Adding things to PATH 30 | 31 | Here is a more fleshed out description of how the shell installer attempts to add the [install-path][config-install-path] to the user's PATH, and the limitations of that process. 32 | 33 | The most fundamental limitation is that installers fundamentally cannot edit the PATH of the currently running shell (it's a parent process). Only an explicit `source some_file` (or the more portable `. some_file`) can do that. As such, it benefits an installer to try to install to a directory that will already be on PATH (such as [CARGO_HOME][cargo home]). Otherwise all we can do is prompt the user to run `source` themselves after the installer has run (or restart their shell to freshly source rcfiles). 34 | 35 | The process we use to add [install-path][config-install-path] to the user's PATH is roughly the same process that rustup uses (hopefully making us harmonious with running rustup before/after one of our installer scripts). In the following description we will use `$install-path` as a placeholder for the path computed at install-time where the binaries get installed. Its actual value will likely look something like `$HOME/.myapp` or `$HOME/.cargo/bin`. 36 | 37 | * we generate a shell script and write it to `$install-path/env` (let's call this `$env-path`) 38 | * the script checks if `$install-path` is in PATH already, and prepends it if not 39 | * prepending is used to ideally override system-installed binaries, as that is assumed to be desired when explicitly installing with not-your-system-package-manager 40 | * the `env` script will only be added if it doesn't already exist 41 | * if `install-path = "CARGO_HOME"`, then `$env-path` will actually be in the parent directory, mirroring the behaviour of rustup 42 | * we add `. $env-path` to `$HOME/.profile` 43 | * this is just a more portable version of `source $install-path/env` 44 | * this line will only be added if it doesn't exist (we also check for the `source` equivalent) 45 | * the file is created if it doesn't exist 46 | * [rustup shotgun blasts this line into many more files like .bashrc and .zshenv](https://github.com/rust-lang/rustup/blob/bcfac6278c7c2f16a41294f7533aeee2f7f88d07/src/cli/self_update/shell.rs#L70-L76), while still [lacking proper support for fish](https://github.com/rust-lang/rustup/issues/478) and other more obscure shells -- we opted to start conservative with just .profile 47 | * if `$HOME/.profile` was edited, we prompt the user to `source "$env-path"` or restart their shell 48 | * although this is less portable than `. "$env-path"`, it's very easy to misread/miscopy the portable version (not as much of a concern for an rcfile, but an issue for humans) 49 | * hopefully folks on platforms where this matters are aware of this issue (or they can restart their shell) 50 | 51 | 52 | 53 | [issue-musl]: https://github.com/axodotdev/cargo-dist/issues/75 54 | [issue-unpack-all]: https://github.com/axodotdev/cargo-dist/issues/307 55 | 56 | [config-install-path]: ../reference/config.md#install-path 57 | 58 | [archive]: ../artifacts/archives.md 59 | [artifact-url]: ../reference/artifact-url.md 60 | 61 | [cargo home]: https://doc.rust-lang.org/cargo/guide/cargo-home.html 62 | -------------------------------------------------------------------------------- /cargo-dist/src/platform/github_runners.rs: -------------------------------------------------------------------------------- 1 | //! Statically-known information about various GitHub Actions runner names 2 | 3 | use std::collections::HashMap; 4 | 5 | use crate::platform::targets as t; 6 | use cargo_dist_schema::{target_lexicon::Triple, GithubRunnerRef, TripleNameRef}; 7 | use tracing::warn; 8 | 9 | lazy_static::lazy_static! { 10 | static ref KNOWN_GITHUB_RUNNERS: HashMap<&'static GithubRunnerRef, &'static TripleNameRef> = { 11 | let mut m = HashMap::new(); 12 | // cf. https://github.com/actions/runner-images/blob/main/README.md 13 | // last updated 2024-10-25 14 | 15 | //-------- linux 16 | m.insert(GithubRunnerRef::from_str("ubuntu-20.04"), t::TARGET_X64_LINUX_GNU); 17 | m.insert(GithubRunnerRef::from_str("ubuntu-22.04"), t::TARGET_X64_LINUX_GNU); 18 | m.insert(GithubRunnerRef::from_str("ubuntu-24.04"), t::TARGET_X64_LINUX_GNU); 19 | m.insert(GithubRunnerRef::from_str("ubuntu-latest"), t::TARGET_X64_LINUX_GNU); 20 | 21 | //-------- windows 22 | m.insert(GithubRunnerRef::from_str("windows-2019"), t::TARGET_X64_WINDOWS); 23 | m.insert(GithubRunnerRef::from_str("windows-2022"), t::TARGET_X64_WINDOWS); 24 | m.insert(GithubRunnerRef::from_str("windows-latest"), t::TARGET_X64_WINDOWS); 25 | 26 | //-------- macos x64 27 | m.insert(GithubRunnerRef::from_str("macos-12"), t::TARGET_X64_MAC); // deprecated 28 | m.insert(GithubRunnerRef::from_str("macos-12-large"), t::TARGET_X64_MAC); 29 | m.insert(GithubRunnerRef::from_str("macos-13"), t::TARGET_X64_MAC); 30 | m.insert(GithubRunnerRef::from_str("macos-13-large"), t::TARGET_X64_MAC); 31 | m.insert(GithubRunnerRef::from_str("macos-14-large"), t::TARGET_X64_MAC); 32 | m.insert(GithubRunnerRef::from_str("macos-15-large"), t::TARGET_X64_MAC); 33 | m.insert(GithubRunnerRef::from_str("macos-latest-large"), t::TARGET_X64_MAC); 34 | 35 | //-------- macos arm64 36 | m.insert(GithubRunnerRef::from_str("macos-13-xlarge"), t::TARGET_ARM64_MAC); 37 | m.insert(GithubRunnerRef::from_str("macos-14"), t::TARGET_ARM64_MAC); 38 | m.insert(GithubRunnerRef::from_str("macos-14-xlarge"), t::TARGET_ARM64_MAC); 39 | m.insert(GithubRunnerRef::from_str("macos-15"), t::TARGET_ARM64_MAC); 40 | m.insert(GithubRunnerRef::from_str("macos-15-xlarge"), t::TARGET_ARM64_MAC); 41 | m.insert(GithubRunnerRef::from_str("macos-latest"), t::TARGET_ARM64_MAC); 42 | m.insert(GithubRunnerRef::from_str("macos-latest-xlarge"), t::TARGET_ARM64_MAC); 43 | 44 | m 45 | }; 46 | } 47 | 48 | /// Get the target triple for a given GitHub Actions runner (if we know about it) 49 | pub fn target_for_github_runner(runner: &GithubRunnerRef) -> Option<&'static TripleNameRef> { 50 | if let Some(target) = KNOWN_GITHUB_RUNNERS.get(runner).copied() { 51 | return Some(target); 52 | } 53 | 54 | let runner_str = runner.as_str(); 55 | if let Some(rest) = runner_str.strip_prefix("buildjet-") { 56 | if rest.contains("ubuntu") { 57 | if rest.ends_with("-arm") { 58 | return Some(t::TARGET_ARM64_LINUX_GNU); 59 | } else { 60 | return Some(t::TARGET_X64_LINUX_GNU); 61 | } 62 | } 63 | } 64 | 65 | None 66 | } 67 | 68 | /// Get the target triple for a given GitHub Actions runner (if we know about it), or assume x64-linux-gnu 69 | pub fn target_for_github_runner_or_default(runner: &GithubRunnerRef) -> &'static TripleNameRef { 70 | const DEFAULT_ASSUMED_TARGET: &TripleNameRef = t::TARGET_X64_LINUX_GNU; 71 | 72 | target_for_github_runner(runner).unwrap_or_else(|| { 73 | warn!( 74 | "don't know the triple for github runner '{runner}', assuming {DEFAULT_ASSUMED_TARGET}" 75 | ); 76 | DEFAULT_ASSUMED_TARGET 77 | }) 78 | } 79 | 80 | /// Gets the parsed [`Triple`] for a given GitHub runner. 81 | pub fn triple_for_github_runner_or_default(runner: &GithubRunnerRef) -> Triple { 82 | // unwrap safety: all the triples in `KNOWN_GITHUB_RUNNERS` should parse 83 | // cleanly. 84 | target_for_github_runner_or_default(runner).parse().unwrap() 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | 91 | #[test] 92 | fn test_target_for_github_runner() { 93 | assert_eq!( 94 | target_for_github_runner(GithubRunnerRef::from_str("ubuntu-22.04")), 95 | Some(t::TARGET_X64_LINUX_GNU) 96 | ); 97 | assert_eq!( 98 | target_for_github_runner(GithubRunnerRef::from_str("buildjet-8vcpu-ubuntu-2204-arm")), 99 | Some(t::TARGET_ARM64_LINUX_GNU) 100 | ); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /cargo-dist/src/backend/installer/mod.rs: -------------------------------------------------------------------------------- 1 | //! Installer Generation 2 | //! 3 | //! In the future this might get split up into submodules. 4 | 5 | use std::collections::BTreeMap; 6 | 7 | use camino::Utf8PathBuf; 8 | use cargo_dist_schema::{ArtifactId, EnvironmentVariables, Hosting, TripleName}; 9 | use homebrew::HomebrewFragments; 10 | use macpkg::PkgInstallerInfo; 11 | use serde::Serialize; 12 | 13 | use crate::{ 14 | config::{JinjaInstallPathStrategy, LibraryStyle, ZipStyle}, 15 | platform::{PlatformSupport, RuntimeConditions}, 16 | InstallReceipt, ReleaseIdx, 17 | }; 18 | 19 | use self::homebrew::HomebrewInstallerInfo; 20 | use self::msi::MsiInstallerInfo; 21 | use self::npm::NpmInstallerInfo; 22 | 23 | pub mod homebrew; 24 | pub mod macpkg; 25 | pub mod msi; 26 | pub mod npm; 27 | pub mod powershell; 28 | pub mod shell; 29 | 30 | /// A kind of an installer 31 | #[derive(Debug, Clone)] 32 | #[allow(clippy::large_enum_variant)] 33 | pub enum InstallerImpl { 34 | /// shell installer script 35 | Shell(InstallerInfo), 36 | /// powershell installer script 37 | Powershell(InstallerInfo), 38 | /// npm installer package 39 | Npm(NpmInstallerInfo), 40 | /// Homebrew formula 41 | Homebrew(HomebrewImpl), 42 | /// Windows msi installer 43 | Msi(MsiInstallerInfo), 44 | /// Mac pkg installer 45 | Pkg(PkgInstallerInfo), 46 | } 47 | 48 | /// Information needed to make a homebrew installer 49 | #[derive(Debug, Clone)] 50 | pub struct HomebrewImpl { 51 | /// Base information 52 | pub info: HomebrewInstallerInfo, 53 | 54 | /// Various fragments (arm64 mac, x86_64 mac, arm64 linux, x86_64 linux, etc.) 55 | pub fragments: HomebrewFragments, 56 | } 57 | 58 | /// Generic info about an installer 59 | #[derive(Debug, Clone, Serialize)] 60 | pub struct InstallerInfo { 61 | /// The parent release 62 | #[serde(skip)] 63 | pub release: ReleaseIdx, 64 | /// The path to generate the installer at 65 | pub dest_path: Utf8PathBuf, 66 | /// App name to use (display only) 67 | pub app_name: String, 68 | /// App version to use (display only) 69 | pub app_version: String, 70 | /// URL of the directory where artifacts can be fetched from 71 | pub base_url: String, 72 | /// Full information about configured hosting 73 | pub hosting: Hosting, 74 | /// Artifacts this installer can fetch 75 | pub artifacts: Vec, 76 | /// Description of the installer (a good heading) 77 | pub desc: String, 78 | /// Hint for how to run the installer 79 | pub hint: String, 80 | /// Where to install binaries 81 | pub install_paths: Vec, 82 | /// Custom message to display on install success 83 | pub install_success_msg: String, 84 | /// Install receipt to write, if any 85 | pub receipt: Option, 86 | /// Aliases to install binaries under 87 | pub bin_aliases: BTreeMap>>, 88 | /// Whether to install generated C dynamic libraries 89 | pub install_libraries: Vec, 90 | /// Platform-specific runtime conditions 91 | pub runtime_conditions: RuntimeConditions, 92 | /// platform support matrix 93 | pub platform_support: Option, 94 | /// Environment variables for installer customization 95 | pub env_vars: Option, 96 | } 97 | 98 | /// A fake fragment of an ExecutableZip artifact for installers 99 | #[derive(Debug, Clone, Serialize)] 100 | pub struct ExecutableZipFragment { 101 | /// The id of the artifact 102 | pub id: ArtifactId, 103 | /// The target the artifact supports 104 | pub target_triple: TripleName, 105 | /// The executables the artifact contains (name, assumed at root) 106 | pub executables: Vec, 107 | /// The dynamic libraries the artifact contains (name, assumed at root) 108 | pub cdylibs: Vec, 109 | /// The static libraries the artifact contains (name, assumed at root) 110 | pub cstaticlibs: Vec, 111 | /// The style of zip this is 112 | pub zip_style: ZipStyle, 113 | /// The updater associated with this platform 114 | pub updater: Option, 115 | /// Conditions the system being installed to should ideally satisfy to install this 116 | pub runtime_conditions: RuntimeConditions, 117 | } 118 | 119 | /// A fake fragment of an Updater artifact for installers 120 | #[derive(Debug, Clone, Serialize)] 121 | pub struct UpdaterFragment { 122 | /// The id of the artifact 123 | pub id: ArtifactId, 124 | /// The binary the artifact contains (name, assumed at root) 125 | pub binary: ArtifactId, 126 | } 127 | -------------------------------------------------------------------------------- /book/src/installers/index.md: -------------------------------------------------------------------------------- 1 | # Installers 2 | 3 | The core functionality of dist is to build your binaries and produce [tarballs / zips][archives] containing them. Basically every other kind of output it produces is considered an "installer" that helps download/install/run those binaries. 4 | 5 | Note that we use the term "installer" very loosely -- if it's fancier than a tarball, it's an installer to us! 6 | 7 | 8 | ## Supported Installers 9 | 10 | Currently supported installers include: 11 | 12 | * [shell][]: a shell script that fetches and installs executables (for `curl | sh`) 13 | * [powershell][]: a powershell script that fetches and installs executables (for `irm | iex`) 14 | * [npm][]: an npm project that fetches and runs executables (for `npx`) 15 | * [homebrew][]: a Homebrew formula that fetches and installs executables 16 | * [msi][]: a Windows msi that bundles and installs executables 17 | 18 | These keys can be specified via [`installer` in your dist config][config-installers]. The [`dist init` command][init] provides an interactive UI for enabling/disabling them. 19 | 20 | The above installers can have one of two strategies: *fetching* and *bundling* (defined below). Currently each installer is hardcoded to one particular strategy, but in the future [we may make it configurable][issue-unlock-installers]. 21 | 22 | 23 | ## Future Installers 24 | 25 | The following installers have been requested, and we're open to supporting them, but we have no specific timeline for when they will be implemented. Providing additional info/feedback on them helps us prioritize the work: 26 | 27 | * [linux docker image containing binaries](https://github.com/axodotdev/cargo-dist/issues/365) 28 | * [linux flatpak](https://github.com/axodotdev/cargo-dist/issues/25) 29 | * [macOS cask](https://github.com/axodotdev/cargo-dist/issues/309) 30 | * [macOS dmg / app](https://github.com/axodotdev/cargo-dist/issues/24) 31 | * [pypi package](https://github.com/axodotdev/cargo-dist/issues/86) 32 | * [windows winget package](https://github.com/axodotdev/cargo-dist/issues/87) 33 | 34 | 35 | 36 | ## Fetching Installers 37 | 38 | Fetching installers are thin wrappers which detect the user's current platform and download and unpack the appropriate [archive][archives] from a server. 39 | 40 | In exchange for requiring [a well-defined Artifact URL][artifact-url] and an internet connection at install-time, this strategy gives you a simple and efficient way to host prebuilt binaries and make sure that all users get the same binaries regardless of how the installed your application. 41 | 42 | Fetching installers are also easy to make "universal" (cross-platform), so your installing users don't need to care about the OS or CPU they're using -- the installer will handle that for them. 43 | 44 | Installers which support fetching: 45 | 46 | * [shell][]: a shell script that fetches and installs executables (for `curl | sh`) 47 | * [powershell][]: a powershell script that fetches and installs executables (for `irm | iex`) 48 | * [npm][]: an npm project that fetches and runs executables (for `npx`) 49 | * [homebrew][]: a Homebrew formula that fetches and installs executables 50 | 51 | 52 | ## Bundling Installers 53 | 54 | Bundling installers contain the actual binaries they will install on the user's system. 55 | 56 | These installers can work without any internet connection, which some users will demand or appreciate. 57 | 58 | Bundling requires a fundamental compromise when it comes to "universal" (cross-platform) installers, as any installer that wants to support e.g. [Intel macOS and Apple Silicon macOS][issue-macos-universal] will need to include both binaries, even if only one will ever be used. 59 | 60 | For this reason all bundling installers are currently single-platform, requiring the installing user to know what platform they're on. 61 | 62 | Installers which support bundling: 63 | 64 | * [msi][]: a Windows msi that bundles and installs executables 65 | 66 | 67 | ## Usage 68 | 69 | Our installers are meant to be usable as-is, without requiring any special options or configuration from the user. We do offer some optional configuration options for end users; see the [usage][] documentation for a description of all supported options. 70 | 71 | 72 | [config-installers]: ../reference/config.md#installers 73 | 74 | [issue-unlock-installers]: https://github.com/axodotdev/cargo-dist/issues/450 75 | [issue-info-install]: https://github.com/axodotdev/cargo-dist/issues/72 76 | [issue-macos-universal]: https://github.com/axodotdev/cargo-dist/issues/77 77 | 78 | [shell]: ./shell.md 79 | [powershell]: ./powershell.md 80 | [msi]: ./msi.md 81 | [npm]: ./npm.md 82 | [homebrew]: ./homebrew.md 83 | [usage]: ./usage.md 84 | 85 | [archives]: ../artifacts/archives.md 86 | [artifact-url]: ../reference/artifact-url.md 87 | [init]: ../reference/cli.md#dist-init 88 | -------------------------------------------------------------------------------- /cargo-dist/tests/gallery/dist/homebrew.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, process::Output}; 2 | 3 | use super::*; 4 | 5 | impl AppResult { 6 | // Runs the installer script in the system's Homebrew installation 7 | #[allow(unused_variables)] 8 | pub fn runtest_homebrew_installer(&self, ctx: &TestContext) -> Result<()> { 9 | // Only do this if we trust hashes (outside cfg so the compiler knows we use this) 10 | if !self.trust_hashes { 11 | return Ok(()); 12 | } 13 | 14 | // Only do this on macOS, and only do it if RUIN_MY_COMPUTER_WITH_INSTALLERS is set 15 | if std::env::var(ENV_RUIN_ME) 16 | .map(|s| s == "homebrew" || s == "all") 17 | .unwrap_or(false) 18 | { 19 | // only do this if the formula exists 20 | let Some(formula_path) = &self.homebrew_installer_path else { 21 | return Ok(()); 22 | }; 23 | 24 | // don't do this if the test asked not to do it, 25 | // cf. https://github.com/axodotdev/cargo-dist/issues/1525 26 | if self.homebrew_skip_install { 27 | return Ok(()); 28 | } 29 | 30 | // Only do this if Homebrew is installed 31 | let Some(homebrew) = &ctx.tools.homebrew else { 32 | return Ok(()); 33 | }; 34 | 35 | // Homebrew fails to guess that this is a formula 36 | // file if it's not in a path named Formula, 37 | // so we need to put the formula in a temp path 38 | // to hint it correctly. 39 | // (We could also skip individual lints via 40 | // --except-cop on the `brew style` CLI, but that's 41 | // a bit too much of a game of whack a mole.) 42 | let temp_root = temp_dir::TempDir::new().unwrap(); 43 | let formula_temp_path = create_formula_copy(&temp_root, formula_path).unwrap(); 44 | 45 | // We perform linting here too because we want to both 46 | // lint and runtest the `brew style --fix`ed version. 47 | // We're unable to check the fixed version into the 48 | // snapshots since it doesn't work cross-platform, so 49 | // doing them both in one place means we don't have to 50 | // run it twice. 51 | let output = brew_style(homebrew, &formula_temp_path)?; 52 | if !output.status.success() { 53 | eprintln!("{}", String::from_utf8_lossy(&output.stdout)); 54 | return Err(miette!("brew style found issues")); 55 | } 56 | 57 | eprintln!("running brew install..."); 58 | homebrew.output_checked(|cmd| cmd.arg("install").arg(&formula_temp_path))?; 59 | let prefix_output = 60 | homebrew.output_checked(|cmd| cmd.arg("--prefix").arg(&formula_temp_path))?; 61 | let prefix_raw = String::from_utf8(prefix_output.stdout).unwrap(); 62 | let prefix = prefix_raw.strip_suffix('\n').unwrap(); 63 | let bin = Utf8PathBuf::from(&prefix).join("bin"); 64 | 65 | for bin_name in ctx.options.bins_with_aliases(&self.app_name, &self.bins) { 66 | let bin_path = bin.join(bin_name); 67 | assert!(bin_path.exists(), "bin wasn't created"); 68 | } 69 | 70 | homebrew.output_checked(|cmd| cmd.arg("uninstall").arg(formula_temp_path))?; 71 | } 72 | Ok(()) 73 | } 74 | } 75 | 76 | fn create_formula_copy( 77 | temp_root: &temp_dir::TempDir, 78 | formula_path: &Utf8PathBuf, 79 | ) -> std::io::Result { 80 | let formula_temp_root = temp_root.path().join("Formula"); 81 | std::fs::create_dir(&formula_temp_root)?; 82 | let formula_temp_path = formula_temp_root.join(formula_path.file_name().unwrap()); 83 | std::fs::copy(formula_path, &formula_temp_path)?; 84 | 85 | Ok(formula_temp_path) 86 | } 87 | 88 | fn brew_style(homebrew: &CommandInfo, path: &PathBuf) -> Result { 89 | homebrew.output(|cmd| { 90 | cmd.arg("style") 91 | // We ignore audits for user-supplied metadata, 92 | // since we avoid rewriting those on behalf of 93 | // the user. We also avoid the homepage nit, 94 | // because if the user doesn't supply a homepage 95 | // it's correct that we don't generate one. 96 | // We add FormulaAuditStrict because that's the 97 | // default exclusion, and adding anything to 98 | // --except-cops overrides it. 99 | .arg("--except-cops") 100 | .arg("FormulaAudit/Homepage,FormulaAudit/Desc,FormulaAuditStrict") 101 | // Applying --fix will ensure that fixable 102 | // style issues won't be treated as errors. 103 | .arg("--fix") 104 | .arg(path) 105 | }) 106 | } 107 | -------------------------------------------------------------------------------- /book/src/introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | *dist distributes your binaries* 4 | 5 | The TL;DR is that with dist setup, just doing this: 6 | 7 | ```sh 8 | git commit -am "release: 0.2.0" 9 | git tag "v0.2.0" 10 | git push 11 | git push --tags 12 | ``` 13 | 14 | Will make [this Github Release](https://github.com/axodotdev/axolotlsay/releases/tag/v0.2.0): 15 | 16 | ![A Github Release for "axolotlsay 0.2.0" with several installers and prebuilt binaries][simple-release] 17 | 18 | Or if you're using [oranda](https://opensource.axo.dev/oranda/), you'll get [this website](https://opensource.axo.dev/axolotlsay/): 19 | 20 | ![A website for "axolotlsay" that has a widget that detects the user's platform and suggests installation methods][simple-oranda] 21 | 22 | 23 | 24 | 25 | ## Plan, Build, Host, Publish, Announce 26 | 27 | Cutting releases of your apps and distributing binaries for them has a lot of steps, and dist is quickly growing to try to cover them all! 28 | 29 | To accomplish this, dist functionality can be broken up into two parts: 30 | 31 | * building (**planning** the release; **building** binaries and installers) 32 | * distributing (**hosting** artifacts; **publishing** packages; **announcing** releases) 33 | 34 | The build functionality can be used on its own if you just want some tarballs and installers, but everything really comes together when you use the distribution functionality too. 35 | 36 | 37 | ## Building 38 | 39 | As a build tool, dist can do the following: 40 | 41 | * Pick good build flags for "shippable binaries" 42 | * Make [tarballs][] and [installers][] for the resulting binaries 43 | * Generate [machine-readable manifests][manifest] so other tools can understand the results 44 | 45 | That's a short list because "we make [installers][]" is doing a lot of heavy lifting. Each installer could be (and sometimes is!) an entire standalone tool with its own documentation and ecosystem. 46 | 47 | 48 | ## Distributing 49 | 50 | As a distribution tool, dist gets to flex its biggest superpower: **it generates [its own CI scripts][ci-providers]**. For instance, enabling [GitHub CI][github-ci] with `dist init` will generate release.yml, which implements the full pipeline of plan, build, host, publish, announce: 51 | 52 | * Plan 53 | * Waits for you to push a git tag for a new version (v1.0.0, my-app-1.0.0...) 54 | * Selects what apps in your workspace to announce new releases for based on that tag 55 | * Generates [a machine-readable manifest][manifest] with changelogs and build plans 56 | * Build 57 | * Spins up machines for each platform you support 58 | * Builds your [binaries and tarballs][tarballs] 59 | * Builds [installers][installers] for your binaries 60 | * Publish: 61 | * Uploads to package managers 62 | * Host + Announce: 63 | * Creates (or edits and undrafts) a GitHub Release 64 | * Uploads build artifacts to the Release 65 | * Adds relevant release notes from your RELEASES/CHANGELOG 66 | 67 | (Ideally "host" would come cleanly before "publish", but GitHub Releases doesn't really properly support this kind of staging, so we're forced to race the steps a bit here. Future work may provide a more robust release process.) 68 | 69 | Most of the scripts roughly amount to "install dist", "run it exactly once", "upload the artifacts it reported". **We want you to be able to copy that one dist invocation CI did, run it on your machine, and get the same results without any fuss** (not to bit-level precision, but to the kinds of precision normal people expect from cargo builds). No setting up docker, no weird linux-only shell scripts that assume a bunch of tools were setup in earlier CI steps. 70 | 71 | Of course even if we perfectly achieve this ideal, "you *can* run it locally" and "you *want to* run it locally" are different statements. 72 | 73 | ## Check Your Release Process Early And Often 74 | 75 | To that point, **release.yml can now run partially in pull-requests**. The default is to only run the "plan" step, which includes many integrity checks to help prevent "oops the release process is broken and we only found out when we tried to cut a release". 76 | 77 | ![A GitHub PR for "chore: innocently update the Cargo.toml (to cause problems)", with the Release / plan PR check failing as a result][pr-fail] 78 | 79 | You can also crank the pull-request mode up to include the "build" step, in which case the PR Workflow Summary will include an artifacts.zip containing all the build results. We don't recommend keeping this on all the time (it's slow and wasteful), but it can be useful to temporarily turn on while testing a PR. 80 | 81 | ![A GitHub Workflow Summary from running dist's release.yml with an "artifacts" download link at the bottom][workflow-artifacts] 82 | 83 | 84 | [simple-release]: ./img/simple-github-release.png 85 | [simple-oranda]: ./img/simple-oranda.png 86 | [workflow-artifacts]: ./img/workflow-artifacts.png 87 | [pr-fail]: ./img/pr-fail.png 88 | 89 | [github-ci]: ./ci/index.md 90 | [ci-providers]: ./ci/index.md 91 | [installers]: ./installers/index.md 92 | [tarballs]: ./artifacts/archives.md 93 | [manifest]: ./reference/schema.md 94 | --------------------------------------------------------------------------------